mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-02-01 08:36:42 +01:00
big ass refactor (rip client)
This commit is contained in:
parent
94c1f83ef2
commit
8c3cf53c67
56 changed files with 2232 additions and 1891 deletions
|
|
@ -1,29 +1,85 @@
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::fs::{File, create_dir_all};
|
||||||
|
|
||||||
const CONFIG_FILE_NAME: &'static str = "tek.toml";
|
const CONFIG_FILE_NAME: &'static str = "tek.toml";
|
||||||
const PROJECT_FILE_NAME: &'static str = "project.toml";
|
const PROJECT_FILE_NAME: &'static str = "project.toml";
|
||||||
|
|
||||||
pub fn create_dirs (xdg: µxdg::XdgApp) -> Result<(), Box<dyn Error>> {
|
pub struct AppPaths {
|
||||||
use std::{path::Path,fs::{File,create_dir_all}};
|
config_dir: PathBuf,
|
||||||
let config_dir = xdg.app_config()?;
|
config_file: PathBuf,
|
||||||
if !Path::new(&config_dir).exists() {
|
data_dir: PathBuf,
|
||||||
println!("Creating {config_dir:?}");
|
project_file: PathBuf,
|
||||||
create_dir_all(&config_dir)?;
|
|
||||||
}
|
}
|
||||||
let config_path = config_dir.join(CONFIG_FILE_NAME);
|
impl AppPaths {
|
||||||
if !Path::new(&config_path).exists() {
|
pub fn new (xdg: &XdgApp) -> Usually<Self> {
|
||||||
println!("Creating {config_path:?}");
|
let config_dir = PathBuf::from(xdg.app_config()?);
|
||||||
File::create_new(&config_path)?;
|
let config_file = PathBuf::from(config_dir.join(CONFIG_FILE_NAME));
|
||||||
|
let data_dir = PathBuf::from(xdg.app_data()?);
|
||||||
|
let project_file = PathBuf::from(data_dir.join(PROJECT_FILE_NAME));
|
||||||
|
Ok(Self { config_dir, config_file, data_dir, project_file })
|
||||||
}
|
}
|
||||||
let data_dir = xdg.app_data()?;
|
pub fn should_create (&self) -> bool {
|
||||||
if !Path::new(&data_dir).exists() {
|
for path in [
|
||||||
println!("Creating {data_dir:?}");
|
&self.config_dir,
|
||||||
create_dir_all(&data_dir)?;
|
&self.config_file,
|
||||||
|
&self.data_dir,
|
||||||
|
&self.project_file,
|
||||||
|
].iter() {
|
||||||
|
if !Path::new(path).exists() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
pub fn create (&self) -> Usually<()> {
|
||||||
|
for dir in [&self.config_dir, &self.data_dir].iter() {
|
||||||
|
if !Path::new(dir).exists() {
|
||||||
|
create_dir_all(&dir)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for file in [&self.config_file, &self.project_file].iter() {
|
||||||
|
if !Path::new(file).exists() {
|
||||||
|
File::create_new(&file)?;
|
||||||
}
|
}
|
||||||
let project_path = data_dir.join(PROJECT_FILE_NAME);
|
|
||||||
if !Path::new(&project_path).exists() {
|
|
||||||
println!("Creating {project_path:?}");
|
|
||||||
File::create_new(&project_path)?;
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SetupModal(pub Option<Arc<XdgApp>>);
|
||||||
|
|
||||||
|
render!(SetupModal |self, buf, area| {
|
||||||
|
let lines = [
|
||||||
|
(" ", Style::default().white().on_black().not_dim().bold()),
|
||||||
|
(" Welcome to TEK. ", Style::default().white().on_black().not_dim().bold()),
|
||||||
|
(" ", Style::default().white().on_black().not_dim().bold()),
|
||||||
|
(" Press ENTER to create the ", Style::default().white().on_black().not_dim()),
|
||||||
|
(" following directories: ", Style::default().white().on_black().not_dim()),
|
||||||
|
(" ", Style::default().white().on_black().not_dim().bold()),
|
||||||
|
(" Configuration directory: ", Style::default().white().on_black().not_dim()),
|
||||||
|
(" ~/.config/tek ", Style::default().white().on_black().not_dim().bold()),
|
||||||
|
(" ", Style::default().white().on_black().not_dim()),
|
||||||
|
(" Data directory: ", Style::default().white().on_black().not_dim()),
|
||||||
|
(" ~/.local/share/tek ", Style::default().white().on_black().not_dim().bold()),
|
||||||
|
(" ", Style::default().white().on_black().not_dim()),
|
||||||
|
];
|
||||||
|
let width = lines[0].0.len() as u16;
|
||||||
|
let x = area.x + (area.width - width) / 2;
|
||||||
|
for (i, (line, style)) in lines.iter().enumerate() {
|
||||||
|
line.blit(buf, x, area.y + area.height / 2 - (lines.len() / 2) as u16 + i as u16, Some(*style));
|
||||||
|
}
|
||||||
|
Ok(area)
|
||||||
|
});
|
||||||
|
handle!(SetupModal |self, e| {
|
||||||
|
if let AppEvent::Input(::crossterm::event::Event::Key(KeyEvent {
|
||||||
|
code: KeyCode::Enter,
|
||||||
|
..
|
||||||
|
})) = e {
|
||||||
|
AppPaths::new(&self.0.as_ref().unwrap())?.create()?;
|
||||||
|
self.0 = None;
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
||||||
9
src/control.rs
Normal file
9
src/control.rs
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
pub mod chain;
|
||||||
|
pub mod focus;
|
||||||
|
pub mod launcher;
|
||||||
|
pub mod mixer;
|
||||||
|
pub mod plugin;
|
||||||
|
pub mod sampler;
|
||||||
|
pub mod sequencer;
|
||||||
|
|
||||||
|
pub use self::focus::*;
|
||||||
72
src/control/chain.rs
Normal file
72
src/control/chain.rs
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
use crate::{core::*, model::*};
|
||||||
|
use super::focus::*;
|
||||||
|
|
||||||
|
pub fn handle (state: &mut Chain, event: &AppEvent) -> Usually<bool> {
|
||||||
|
Ok(handle_focus(state, event, keymap!(Chain {
|
||||||
|
[Up, NONE, "focus_up", "focus row above",
|
||||||
|
|s: &mut Chain|s.handle_focus(&FocusEvent::Backward)],
|
||||||
|
[Down, NONE, "focus_down", "focus row below",
|
||||||
|
|s: &mut Chain|s.handle_focus(&FocusEvent::Forward)],
|
||||||
|
[Enter, NONE, "focus_down", "focus row below",
|
||||||
|
|s: &mut Chain|s.handle_focus(&FocusEvent::Inward)],
|
||||||
|
[Esc, NONE, "focus_down", "focus row below",
|
||||||
|
|s: &mut Chain|s.handle_focus(&FocusEvent::Outward)],
|
||||||
|
}))? || handle_keymap(state, event, keymap!(Chain {
|
||||||
|
[Char('a'), NONE, "add_device", "add a device", add_device]
|
||||||
|
}))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_device (state: &mut Chain) -> Usually<bool> {
|
||||||
|
state.adding = true;
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Focus for Chain {
|
||||||
|
fn unfocus (&mut self) {
|
||||||
|
self.focused = false
|
||||||
|
}
|
||||||
|
fn focused (&self) -> Option<&Box<dyn Device>> {
|
||||||
|
match self.focused {
|
||||||
|
true => self.items.get(self.focus),
|
||||||
|
false => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn focused_mut (&mut self) -> Option<&mut Box<dyn Device>> {
|
||||||
|
match self.focused {
|
||||||
|
true => self.items.get_mut(self.focus),
|
||||||
|
false => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn handle_focus (&mut self, event: &FocusEvent) -> Usually<bool> {
|
||||||
|
Ok(match event {
|
||||||
|
FocusEvent::Backward => {
|
||||||
|
if self.focus == 0 {
|
||||||
|
self.focus = self.items.len();
|
||||||
|
}
|
||||||
|
self.focus = self.focus - 1;
|
||||||
|
true
|
||||||
|
},
|
||||||
|
FocusEvent::Forward => {
|
||||||
|
self.focus = self.focus + 1;
|
||||||
|
if self.focus >= self.items.len() {
|
||||||
|
self.focus = 0;
|
||||||
|
}
|
||||||
|
true
|
||||||
|
},
|
||||||
|
FocusEvent::Inward => {
|
||||||
|
self.focused = true;
|
||||||
|
self.items[self.focus].handle(&AppEvent::Focus)?;
|
||||||
|
true
|
||||||
|
},
|
||||||
|
FocusEvent::Outward => {
|
||||||
|
if self.focused {
|
||||||
|
self.focused = false;
|
||||||
|
self.items[self.focus].handle(&AppEvent::Blur)?;
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
use crate::core::*;
|
use crate::{core::*, view::*};
|
||||||
use super::*;
|
|
||||||
|
|
||||||
pub trait Focus {
|
pub trait Focus {
|
||||||
fn unfocus (&mut self);
|
fn unfocus (&mut self);
|
||||||
|
|
@ -187,3 +186,4 @@ impl Focus for FocusRow {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1,25 +1,22 @@
|
||||||
use crate::core::*;
|
use crate::{core::*,model::*};
|
||||||
use super::*;
|
|
||||||
pub fn handle (state: &mut Launcher, event: &AppEvent) -> Usually<bool> {
|
pub fn handle (state: &mut Launcher, event: &AppEvent) -> Usually<bool> {
|
||||||
//if let Some(ref modal) = state.modal {
|
|
||||||
//if modal.handle_with_state(state, event)? {
|
|
||||||
//return Ok(true)
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
Ok(handle_keymap(state, event, KEYMAP)? || match state.view {
|
Ok(handle_keymap(state, event, KEYMAP)? || match state.view {
|
||||||
LauncherView::Tracks => handle_keymap(state, event, KEYMAP_TRACKS)?,
|
LauncherMode::Tracks => {
|
||||||
LauncherView::Sequencer => {
|
handle_keymap(state, event, KEYMAP_TRACKS)?
|
||||||
|
},
|
||||||
|
LauncherMode::Sequencer => {
|
||||||
let i = state.col().saturating_sub(1);
|
let i = state.col().saturating_sub(1);
|
||||||
if let Some(track) = state.tracks.get_mut(i) {
|
if let Some(track) = state.tracks.get_mut(i) {
|
||||||
crate::device::sequencer::handle(&mut *track.sequencer.state(), event)?
|
crate::control::sequencer::handle(&mut track.sequencer, event)?
|
||||||
} else {
|
} else {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
LauncherView::Chains => {
|
LauncherMode::Chains => {
|
||||||
let i = state.col().saturating_sub(1);
|
let i = state.col().saturating_sub(1);
|
||||||
if let Some(track) = state.tracks.get_mut(i) {
|
if let Some(track) = state.tracks.get_mut(i) {
|
||||||
crate::device::chain::handle(&mut *track.chain.state(), event)?
|
crate::control::chain::handle(&mut track.chain, event)?
|
||||||
} else {
|
} else {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
@ -50,35 +47,38 @@ pub const KEYMAP_TRACKS: &'static [KeyBinding<Launcher>] = keymap!(Launcher {
|
||||||
[Enter, NONE, "activate", "activate item at cursor", activate],
|
[Enter, NONE, "activate", "activate item at cursor", activate],
|
||||||
});
|
});
|
||||||
fn duplicate (_: &mut Launcher) -> Usually<bool> {
|
fn duplicate (_: &mut Launcher) -> Usually<bool> {
|
||||||
|
unimplemented!();
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
fn activate (state: &mut Launcher) -> Usually<bool> {
|
|
||||||
if let (
|
fn activate (_: &mut Launcher) -> Usually<bool> {
|
||||||
Some((_scene_id, scene)),
|
unimplemented!();
|
||||||
Some((track_id, track)),
|
//if let (
|
||||||
) = (state.scene(), state.track()) {
|
//Some((_scene_id, scene)),
|
||||||
// Launch clip
|
//Some((track_id, track)),
|
||||||
if let Some(phrase_id) = scene.clips.get(track_id) {
|
//) = (state.scene_mut(), state.track_mut()) {
|
||||||
track.sequencer.state().sequence = *phrase_id;
|
//// Launch clip
|
||||||
}
|
//if let Some(phrase_id) = scene.clips.get(track_id) {
|
||||||
if state.playing == TransportState::Stopped {
|
//track.sequencer.sequence = *phrase_id;
|
||||||
state.transport.start()?;
|
//}
|
||||||
state.playing = TransportState::Starting;
|
//if state.playing == TransportState::Stopped {
|
||||||
}
|
//state.transport.start()?;
|
||||||
} else if let Some((_scene_id, scene)) = state.scene() {
|
//state.playing = TransportState::Starting;
|
||||||
// Launch scene
|
//}
|
||||||
for (track_id, track) in state.tracks.iter().enumerate() {
|
//} else if let Some((_scene_id, scene)) = state.scene() {
|
||||||
if let Some(phrase_id) = scene.clips.get(track_id) {
|
//// Launch scene
|
||||||
track.sequencer.state().sequence = *phrase_id;
|
//for (track_id, track) in state.tracks.iter().enumerate() {
|
||||||
}
|
//if let Some(phrase_id) = scene.clips.get(track_id) {
|
||||||
}
|
//track.sequencer.sequence = *phrase_id;
|
||||||
if state.playing == TransportState::Stopped {
|
//}
|
||||||
state.transport.start()?;
|
//}
|
||||||
state.playing = TransportState::Starting;
|
//if state.playing == TransportState::Stopped {
|
||||||
}
|
//state.transport.start()?;
|
||||||
} else if let Some((_track_id, _track)) = state.track() {
|
//state.playing = TransportState::Starting;
|
||||||
// Rename track?
|
//}
|
||||||
}
|
//} else if let Some((_track_id, _track)) = state.track() {
|
||||||
|
//// Rename track?
|
||||||
|
//}
|
||||||
|
|
||||||
//let track = state.active_track().unwrap();
|
//let track = state.active_track().unwrap();
|
||||||
//let scene = state.active_scene();
|
//let scene = state.active_scene();
|
||||||
|
|
@ -104,15 +104,18 @@ fn activate (state: &mut Launcher) -> Usually<bool> {
|
||||||
//}
|
//}
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rename (_: &mut Launcher) -> Usually<bool> {
|
fn rename (_: &mut Launcher) -> Usually<bool> {
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_track (state: &mut Launcher) -> Usually<bool> {
|
fn add_track (state: &mut Launcher) -> Usually<bool> {
|
||||||
let name = format!("Track {}", state.tracks.len() + 1);
|
let name = format!("Track {}", state.tracks.len() + 1);
|
||||||
state.tracks.push(Track::new(&name, &state.timebase, None, None)?);
|
state.tracks.push(Track::new(&name, &state.timebase, None, None)?);
|
||||||
state.cursor.0 = state.tracks.len();
|
state.cursor.0 = state.tracks.len();
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_track (state: &mut Launcher) -> Usually<bool> {
|
fn delete_track (state: &mut Launcher) -> Usually<bool> {
|
||||||
if state.tracks.len() > 0 && state.cursor.0 >= 1 {
|
if state.tracks.len() > 0 && state.cursor.0 >= 1 {
|
||||||
state.tracks.remove(state.cursor.0 - 1);
|
state.tracks.remove(state.cursor.0 - 1);
|
||||||
|
|
@ -120,44 +123,50 @@ fn delete_track (state: &mut Launcher) -> Usually<bool> {
|
||||||
}
|
}
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cursor_up (state: &mut Launcher) -> Usually<bool> {
|
fn cursor_up (state: &mut Launcher) -> Usually<bool> {
|
||||||
state.dec_row();
|
state.dec_row();
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cursor_down (state: &mut Launcher) -> Usually<bool> {
|
fn cursor_down (state: &mut Launcher) -> Usually<bool> {
|
||||||
state.inc_row();
|
state.inc_row();
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cursor_left (state: &mut Launcher) -> Usually<bool> {
|
fn cursor_left (state: &mut Launcher) -> Usually<bool> {
|
||||||
state.dec_col();
|
state.dec_col();
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cursor_right (state: &mut Launcher) -> Usually<bool> {
|
fn cursor_right (state: &mut Launcher) -> Usually<bool> {
|
||||||
state.inc_col();
|
state.inc_col();
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_help (state: &mut Launcher) -> Usually<bool> {
|
fn toggle_help (state: &mut Launcher) -> Usually<bool> {
|
||||||
state.show_help = !state.show_help;
|
state.show_help = !state.show_help;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn focus_next (state: &mut Launcher) -> Usually<bool> {
|
fn focus_next (state: &mut Launcher) -> Usually<bool> {
|
||||||
match state.view {
|
match state.view {
|
||||||
LauncherView::Tracks => { state.view = LauncherView::Sequencer; },
|
LauncherMode::Tracks => { state.view = LauncherMode::Sequencer; },
|
||||||
LauncherView::Sequencer => { state.view = LauncherView::Chains; },
|
LauncherMode::Sequencer => { state.view = LauncherMode::Chains; },
|
||||||
LauncherView::Chains => { state.view = LauncherView::Tracks; },
|
LauncherMode::Chains => { state.view = LauncherMode::Tracks; },
|
||||||
_ => {},
|
|
||||||
};
|
};
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn focus_prev (state: &mut Launcher) -> Usually<bool> {
|
fn focus_prev (state: &mut Launcher) -> Usually<bool> {
|
||||||
match state.view {
|
match state.view {
|
||||||
LauncherView::Tracks => { state.view = LauncherView::Chains; },
|
LauncherMode::Tracks => { state.view = LauncherMode::Chains; },
|
||||||
LauncherView::Chains => { state.view = LauncherView::Sequencer; },
|
LauncherMode::Chains => { state.view = LauncherMode::Sequencer; },
|
||||||
LauncherView::Sequencer => { state.view = LauncherView::Tracks; },
|
LauncherMode::Sequencer => { state.view = LauncherMode::Tracks; },
|
||||||
_ => {},
|
|
||||||
};
|
};
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clip_next (state: &mut Launcher) -> Usually<bool> {
|
fn clip_next (state: &mut Launcher) -> Usually<bool> {
|
||||||
if state.cursor.0 >= 1 && state.cursor.1 >= 1 {
|
if state.cursor.0 >= 1 && state.cursor.1 >= 1 {
|
||||||
let scene_id = state.cursor.1 - 1;
|
let scene_id = state.cursor.1 - 1;
|
||||||
|
|
@ -165,7 +174,7 @@ fn clip_next (state: &mut Launcher) -> Usually<bool> {
|
||||||
let scene = &mut state.scenes[scene_id];
|
let scene = &mut state.scenes[scene_id];
|
||||||
scene.clips[clip_id] = match scene.clips[clip_id] {
|
scene.clips[clip_id] = match scene.clips[clip_id] {
|
||||||
None => Some(0),
|
None => Some(0),
|
||||||
Some(i) => if i >= state.tracks[clip_id].sequencer.state().phrases.len().saturating_sub(1) {
|
Some(i) => if i >= state.tracks[clip_id].sequencer.phrases.len().saturating_sub(1) {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(i + 1)
|
Some(i + 1)
|
||||||
|
|
@ -174,13 +183,14 @@ fn clip_next (state: &mut Launcher) -> Usually<bool> {
|
||||||
}
|
}
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clip_prev (state: &mut Launcher) -> Usually<bool> {
|
fn clip_prev (state: &mut Launcher) -> Usually<bool> {
|
||||||
if state.cursor.0 >= 1 && state.cursor.1 >= 1 {
|
if state.cursor.0 >= 1 && state.cursor.1 >= 1 {
|
||||||
let scene_id = state.cursor.1 - 1;
|
let scene_id = state.cursor.1 - 1;
|
||||||
let clip_id = state.cursor.0 - 1;
|
let clip_id = state.cursor.0 - 1;
|
||||||
let scene = &mut state.scenes[scene_id];
|
let scene = &mut state.scenes[scene_id];
|
||||||
scene.clips[clip_id] = match scene.clips[clip_id] {
|
scene.clips[clip_id] = match scene.clips[clip_id] {
|
||||||
None => Some(state.tracks[clip_id].sequencer.state().phrases.len().saturating_sub(1)),
|
None => Some(state.tracks[clip_id].sequencer.phrases.len().saturating_sub(1)),
|
||||||
Some(i) => if i == 0 {
|
Some(i) => if i == 0 {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -190,6 +200,7 @@ fn clip_prev (state: &mut Launcher) -> Usually<bool> {
|
||||||
}
|
}
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn play_toggle (s: &mut Launcher) -> Usually<bool> {
|
fn play_toggle (s: &mut Launcher) -> Usually<bool> {
|
||||||
s.playing = match s.playing {
|
s.playing = match s.playing {
|
||||||
TransportState::Stopped => {
|
TransportState::Stopped => {
|
||||||
|
|
@ -207,15 +218,19 @@ fn play_toggle (s: &mut Launcher) -> Usually<bool> {
|
||||||
//fn play_start (_: &mut Launcher) -> Usually<bool> {
|
//fn play_start (_: &mut Launcher) -> Usually<bool> {
|
||||||
//unimplemented!()
|
//unimplemented!()
|
||||||
//}
|
//}
|
||||||
|
|
||||||
fn record_toggle (s: &mut Launcher) -> Usually<bool> {
|
fn record_toggle (s: &mut Launcher) -> Usually<bool> {
|
||||||
s.sequencer().map(|mut s|s.recording = !s.recording);
|
s.sequencer_mut().map(|s|s.recording = !s.recording);
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn overdub_toggle (s: &mut Launcher) -> Usually<bool> {
|
fn overdub_toggle (s: &mut Launcher) -> Usually<bool> {
|
||||||
s.sequencer().map(|mut s|s.overdub = !s.overdub);
|
s.sequencer_mut().map(|s|s.overdub = !s.overdub);
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn monitor_toggle (s: &mut Launcher) -> Usually<bool> {
|
fn monitor_toggle (s: &mut Launcher) -> Usually<bool> {
|
||||||
s.sequencer().map(|mut s|s.monitoring = !s.monitoring);
|
s.sequencer_mut().map(|s|s.monitoring = !s.monitoring);
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
59
src/control/mixer.rs
Normal file
59
src/control/mixer.rs
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
use crate::{core::*, model::*};
|
||||||
|
|
||||||
|
pub fn handle (state: &mut Mixer, event: &AppEvent) -> Usually<bool> {
|
||||||
|
if let AppEvent::Input(crossterm::event::Event::Key(event)) = event {
|
||||||
|
|
||||||
|
match event.code {
|
||||||
|
//KeyCode::Char('c') => {
|
||||||
|
//if event.modifiers == KeyModifiers::CONTROL {
|
||||||
|
//state.exit();
|
||||||
|
//}
|
||||||
|
//},
|
||||||
|
KeyCode::Down => {
|
||||||
|
state.selected_track = (state.selected_track + 1) % state.tracks.len();
|
||||||
|
println!("{}", state.selected_track);
|
||||||
|
return Ok(true)
|
||||||
|
},
|
||||||
|
KeyCode::Up => {
|
||||||
|
if state.selected_track == 0 {
|
||||||
|
state.selected_track = state.tracks.len() - 1;
|
||||||
|
} else {
|
||||||
|
state.selected_track = state.selected_track - 1;
|
||||||
|
}
|
||||||
|
println!("{}", state.selected_track);
|
||||||
|
return Ok(true)
|
||||||
|
},
|
||||||
|
KeyCode::Left => {
|
||||||
|
if state.selected_column == 0 {
|
||||||
|
state.selected_column = 6
|
||||||
|
} else {
|
||||||
|
state.selected_column = state.selected_column - 1;
|
||||||
|
}
|
||||||
|
return Ok(true)
|
||||||
|
},
|
||||||
|
KeyCode::Right => {
|
||||||
|
if state.selected_column == 6 {
|
||||||
|
state.selected_column = 0
|
||||||
|
} else {
|
||||||
|
state.selected_column = state.selected_column + 1;
|
||||||
|
}
|
||||||
|
return Ok(true)
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
println!("\n{event:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// - Meters: propagate clipping:
|
||||||
|
// - If one stage clips, all stages after it are marked red
|
||||||
|
// - If one track clips, all tracks that feed from it are marked red?
|
||||||
|
|
||||||
|
pub const ACTIONS: [(&'static str, &'static str);2] = [
|
||||||
|
("+/-", "Adjust"),
|
||||||
|
("Ins/Del", "Add/remove track"),
|
||||||
|
];
|
||||||
69
src/control/plugin.rs
Normal file
69
src/control/plugin.rs
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
use crate::{core::*, model::*};
|
||||||
|
|
||||||
|
pub fn handle (s: &mut Plugin, event: &AppEvent) -> Usually<bool> {
|
||||||
|
handle_keymap(s, event, keymap!(Plugin {
|
||||||
|
|
||||||
|
[Up, NONE, "cursor_up", "move cursor up",
|
||||||
|
|s: &mut Plugin|{
|
||||||
|
if s.selected > 0 {
|
||||||
|
s.selected = s.selected - 1
|
||||||
|
} else {
|
||||||
|
s.selected = match &s.plugin {
|
||||||
|
Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1,
|
||||||
|
_ => 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(true)
|
||||||
|
}],
|
||||||
|
|
||||||
|
[Down, NONE, "cursor_down", "move cursor down",
|
||||||
|
|s: &mut Plugin|{
|
||||||
|
s.selected = s.selected + 1;
|
||||||
|
match &s.plugin {
|
||||||
|
Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => {
|
||||||
|
if s.selected >= port_list.len() {
|
||||||
|
s.selected = 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
Ok(true)
|
||||||
|
}],
|
||||||
|
|
||||||
|
[Char(','), NONE, "decrement", "decrement value",
|
||||||
|
|s: &mut Plugin|{
|
||||||
|
match s.plugin.as_mut() {
|
||||||
|
Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => {
|
||||||
|
let index = port_list[s.selected].index;
|
||||||
|
if let Some(value) = instance.control_input(index) {
|
||||||
|
instance.set_control_input(index, value - 0.01);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
Ok(true)
|
||||||
|
}],
|
||||||
|
|
||||||
|
[Char('.'), NONE, "increment", "increment value",
|
||||||
|
|s: &mut Plugin|{
|
||||||
|
match s.plugin.as_mut() {
|
||||||
|
Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => {
|
||||||
|
let index = port_list[s.selected].index;
|
||||||
|
if let Some(value) = instance.control_input(index) {
|
||||||
|
instance.set_control_input(index, value + 0.01);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
Ok(true)
|
||||||
|
}],
|
||||||
|
|
||||||
|
[Char('m'), NONE, "toggle_midi_map", "toggle midi map mode",
|
||||||
|
|s: &mut Plugin|{
|
||||||
|
s.mapping = !s.mapping;
|
||||||
|
Ok(true)
|
||||||
|
}]
|
||||||
|
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
45
src/control/sampler.rs
Normal file
45
src/control/sampler.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
use crate::{core::*, model::*};
|
||||||
|
|
||||||
|
pub fn handle (state: &mut Sampler, event: &AppEvent) -> Usually<bool> {
|
||||||
|
Ok(handle_keymap(state, event, KEYMAP)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const KEYMAP: &'static [KeyBinding<Sampler>] = keymap!(Sampler {
|
||||||
|
[Up, NONE, "cursor_up", "move cursor up", cursor_up],
|
||||||
|
[Down, NONE, "cursor_down", "move cursor down", cursor_down],
|
||||||
|
[Char('t'), NONE, "trigger", "play current sample", trigger],
|
||||||
|
[Enter, NONE, "select", "select item under cursor", select],
|
||||||
|
});
|
||||||
|
|
||||||
|
fn cursor_up (state: &mut Sampler) -> Usually<bool> {
|
||||||
|
state.cursor.0 = if state.cursor.0 == 0 {
|
||||||
|
state.samples.len() - 1
|
||||||
|
} else {
|
||||||
|
state.cursor.0 - 1
|
||||||
|
};
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cursor_down (state: &mut Sampler) -> Usually<bool> {
|
||||||
|
state.cursor.0 = (state.cursor.0 + 1) % state.samples.len();
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn trigger (state: &mut Sampler) -> Usually<bool> {
|
||||||
|
for (i, sample) in state.samples.values().enumerate() {
|
||||||
|
if i == state.cursor.0 {
|
||||||
|
state.voices.push(sample.play(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn select (state: &mut Sampler) -> Usually<bool> {
|
||||||
|
for (i, _sample) in state.samples.values().enumerate() {
|
||||||
|
if i == state.cursor.0 {
|
||||||
|
//state.voices.push(sample.play(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
use crate::core::*;
|
use crate::{core::*, model::*};
|
||||||
use super::*;
|
|
||||||
pub fn handle (s: &mut Sequencer, event: &AppEvent) -> Usually<bool> {
|
pub fn handle (state: &mut Sequencer, event: &AppEvent) -> Usually<bool> {
|
||||||
handle_keymap(s, event, KEYMAP)
|
handle_keymap(state, event, KEYMAP)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const KEYMAP: &'static [KeyBinding<Sequencer>] = keymap!(Sequencer {
|
pub const KEYMAP: &'static [KeyBinding<Sequencer>] = keymap!(Sequencer {
|
||||||
[Up, NONE, "cursor_up", "move cursor up", cursor_up],
|
[Up, NONE, "cursor_up", "move cursor up", cursor_up],
|
||||||
[Down, NONE, "cursor_down", "move cursor down", cursor_down],
|
[Down, NONE, "cursor_down", "move cursor down", cursor_down],
|
||||||
|
|
@ -37,15 +38,18 @@ pub const KEYMAP: &'static [KeyBinding<Sequencer>] = keymap!(Sequencer {
|
||||||
[Char('7'), NONE, "seq_7", "Phrase 7", focus_seq(6)],
|
[Char('7'), NONE, "seq_7", "Phrase 7", focus_seq(6)],
|
||||||
[Char('8'), NONE, "seq_8", "Phrase 8", focus_seq(7)],
|
[Char('8'), NONE, "seq_8", "Phrase 8", focus_seq(7)],
|
||||||
});
|
});
|
||||||
|
|
||||||
const fn focus_seq (i: usize) -> impl Fn(&mut Sequencer)->Usually<bool> {
|
const fn focus_seq (i: usize) -> impl Fn(&mut Sequencer)->Usually<bool> {
|
||||||
move |s: &mut Sequencer| {
|
move |s: &mut Sequencer| {
|
||||||
s.sequence = Some(i);
|
s.sequence = Some(i);
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn nop (_: &mut Sequencer) -> Usually<bool> {
|
fn nop (_: &mut Sequencer) -> Usually<bool> {
|
||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn note_add (s: &mut Sequencer) -> Usually<bool> {
|
fn note_add (s: &mut Sequencer) -> Usually<bool> {
|
||||||
if s.sequence.is_none() {
|
if s.sequence.is_none() {
|
||||||
return Ok(false)
|
return Ok(false)
|
||||||
|
|
@ -70,70 +74,82 @@ fn note_add (s: &mut Sequencer) -> Usually<bool> {
|
||||||
};
|
};
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn note_del (_: &mut Sequencer) -> Usually<bool> {
|
fn note_del (_: &mut Sequencer) -> Usually<bool> {
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn time_cursor_inc (s: &mut Sequencer) -> Usually<bool> {
|
fn time_cursor_inc (s: &mut Sequencer) -> Usually<bool> {
|
||||||
s.time_cursor = s.time_cursor + 1;
|
s.time_cursor = s.time_cursor + 1;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn time_cursor_dec (s: &mut Sequencer) -> Usually<bool> {
|
fn time_cursor_dec (s: &mut Sequencer) -> Usually<bool> {
|
||||||
s.time_cursor = s.time_cursor.saturating_sub(1);
|
s.time_cursor = s.time_cursor.saturating_sub(1);
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn note_cursor_inc (s: &mut Sequencer) -> Usually<bool> {
|
fn note_cursor_inc (s: &mut Sequencer) -> Usually<bool> {
|
||||||
s.note_cursor = s.note_cursor + 1;
|
s.note_cursor = s.note_cursor + 1;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn note_cursor_dec (s: &mut Sequencer) -> Usually<bool> {
|
fn note_cursor_dec (s: &mut Sequencer) -> Usually<bool> {
|
||||||
s.note_cursor = s.note_cursor.saturating_sub(1);
|
s.note_cursor = s.note_cursor.saturating_sub(1);
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cursor_up (s: &mut Sequencer) -> Usually<bool> {
|
fn cursor_up (s: &mut Sequencer) -> Usually<bool> {
|
||||||
match s.view {
|
match s.view {
|
||||||
SequencerView::Vertical => time_cursor_dec(s),
|
SequencerMode::Vertical => time_cursor_dec(s),
|
||||||
SequencerView::Horizontal => note_cursor_dec(s),
|
SequencerMode::Horizontal => note_cursor_dec(s),
|
||||||
_ => Ok(false)
|
_ => Ok(false)
|
||||||
}?;
|
}?;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cursor_down (s: &mut Sequencer) -> Usually<bool> {
|
fn cursor_down (s: &mut Sequencer) -> Usually<bool> {
|
||||||
match s.view {
|
match s.view {
|
||||||
SequencerView::Vertical => time_cursor_inc(s),
|
SequencerMode::Vertical => time_cursor_inc(s),
|
||||||
SequencerView::Horizontal => note_cursor_inc(s),
|
SequencerMode::Horizontal => note_cursor_inc(s),
|
||||||
_ => Ok(false)
|
_ => Ok(false)
|
||||||
}?;
|
}?;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cursor_left (s: &mut Sequencer) -> Usually<bool> {
|
fn cursor_left (s: &mut Sequencer) -> Usually<bool> {
|
||||||
match s.view {
|
match s.view {
|
||||||
SequencerView::Vertical => note_cursor_dec(s),
|
SequencerMode::Vertical => note_cursor_dec(s),
|
||||||
SequencerView::Horizontal => time_cursor_dec(s),
|
SequencerMode::Horizontal => time_cursor_dec(s),
|
||||||
_ => Ok(false)
|
_ => Ok(false)
|
||||||
}?;
|
}?;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cursor_right (s: &mut Sequencer) -> Usually<bool> {
|
fn cursor_right (s: &mut Sequencer) -> Usually<bool> {
|
||||||
match s.view {
|
match s.view {
|
||||||
SequencerView::Vertical => note_cursor_inc(s),
|
SequencerMode::Vertical => note_cursor_inc(s),
|
||||||
SequencerView::Horizontal => time_cursor_inc(s),
|
SequencerMode::Horizontal => time_cursor_inc(s),
|
||||||
_ => Ok(false)
|
_ => Ok(false)
|
||||||
}?;
|
}?;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cursor_duration_inc (_: &mut Sequencer) -> Usually<bool> {
|
fn cursor_duration_inc (_: &mut Sequencer) -> Usually<bool> {
|
||||||
//s.cursor.2 = s.cursor.2 + 1
|
//s.cursor.2 = s.cursor.2 + 1
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cursor_duration_dec (_: &mut Sequencer) -> Usually<bool> {
|
fn cursor_duration_dec (_: &mut Sequencer) -> Usually<bool> {
|
||||||
//if s.cursor.2 > 0 { s.cursor.2 = s.cursor.2 - 1 }
|
//if s.cursor.2 > 0 { s.cursor.2 = s.cursor.2 - 1 }
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mode_next (s: &mut Sequencer) -> Usually<bool> {
|
fn mode_next (s: &mut Sequencer) -> Usually<bool> {
|
||||||
s.view = s.view.next();
|
s.view = s.view.next();
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
impl SequencerView {
|
impl SequencerMode {
|
||||||
fn next (&self) -> Self {
|
fn next (&self) -> Self {
|
||||||
match self {
|
match self {
|
||||||
Self::Horizontal => Self::Vertical,
|
Self::Horizontal => Self::Vertical,
|
||||||
|
|
@ -143,12 +159,14 @@ impl SequencerView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stop_and_rewind (s: &mut Sequencer) -> Usually<bool> {
|
fn stop_and_rewind (s: &mut Sequencer) -> Usually<bool> {
|
||||||
s.transport.stop()?;
|
s.transport.stop()?;
|
||||||
s.transport.locate(0)?;
|
s.transport.locate(0)?;
|
||||||
s.playing = TransportState::Stopped;
|
s.playing = TransportState::Stopped;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_play (s: &mut Sequencer) -> Usually<bool> {
|
fn toggle_play (s: &mut Sequencer) -> Usually<bool> {
|
||||||
s.playing = match s.playing {
|
s.playing = match s.playing {
|
||||||
TransportState::Stopped => {
|
TransportState::Stopped => {
|
||||||
|
|
@ -163,24 +181,29 @@ fn toggle_play (s: &mut Sequencer) -> Usually<bool> {
|
||||||
};
|
};
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_record (s: &mut Sequencer) -> Usually<bool> {
|
fn toggle_record (s: &mut Sequencer) -> Usually<bool> {
|
||||||
s.recording = !s.recording;
|
s.recording = !s.recording;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_overdub (s: &mut Sequencer) -> Usually<bool> {
|
fn toggle_overdub (s: &mut Sequencer) -> Usually<bool> {
|
||||||
s.overdub = !s.overdub;
|
s.overdub = !s.overdub;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_monitor (s: &mut Sequencer) -> Usually<bool> {
|
fn toggle_monitor (s: &mut Sequencer) -> Usually<bool> {
|
||||||
s.monitoring = !s.monitoring;
|
s.monitoring = !s.monitoring;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn quantize_next (s: &mut Sequencer) -> Usually<bool> {
|
fn quantize_next (s: &mut Sequencer) -> Usually<bool> {
|
||||||
if s.time_zoom < 64 {
|
if s.time_zoom < 64 {
|
||||||
s.time_zoom = s.time_zoom * 2;
|
s.time_zoom = s.time_zoom * 2;
|
||||||
}
|
}
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn quantize_prev (s: &mut Sequencer) -> Usually<bool> {
|
fn quantize_prev (s: &mut Sequencer) -> Usually<bool> {
|
||||||
if s.time_zoom > 1 {
|
if s.time_zoom > 1 {
|
||||||
s.time_zoom = s.time_zoom / 2;
|
s.time_zoom = s.time_zoom / 2;
|
||||||
|
|
@ -192,6 +215,7 @@ fn zoom_in (s: &mut Sequencer) -> Usually<bool> {
|
||||||
s.time_zoom = s.time_zoom / 2;
|
s.time_zoom = s.time_zoom / 2;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn zoom_out (s: &mut Sequencer) -> Usually<bool> {
|
fn zoom_out (s: &mut Sequencer) -> Usually<bool> {
|
||||||
s.time_zoom = s.time_zoom * 2;
|
s.time_zoom = s.time_zoom * 2;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
|
|
@ -6,6 +6,8 @@ pub use std::collections::BTreeMap;
|
||||||
pub use std::sync::{Arc, Mutex, MutexGuard};
|
pub use std::sync::{Arc, Mutex, MutexGuard};
|
||||||
pub use std::sync::atomic::{Ordering, AtomicBool, AtomicUsize};
|
pub use std::sync::atomic::{Ordering, AtomicBool, AtomicUsize};
|
||||||
pub use std::sync::mpsc::{channel, Sender, Receiver};
|
pub use std::sync::mpsc::{channel, Sender, Receiver};
|
||||||
|
|
||||||
|
pub use microxdg::XdgApp;
|
||||||
pub use ratatui::prelude::*;
|
pub use ratatui::prelude::*;
|
||||||
pub use midly::{MidiMessage, live::LiveEvent, num::u7};
|
pub use midly::{MidiMessage, live::LiveEvent, num::u7};
|
||||||
pub use crossterm::{ExecutableCommand, QueueableCommand};
|
pub use crossterm::{ExecutableCommand, QueueableCommand};
|
||||||
|
|
@ -13,8 +15,15 @@ pub use crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers};
|
||||||
|
|
||||||
macro_rules! submod { ($($name:ident)*) => { $(mod $name; pub use self::$name::*;)* }; }
|
macro_rules! submod { ($($name:ident)*) => { $(mod $name; pub use self::$name::*;)* }; }
|
||||||
|
|
||||||
submod!( device handle jack keymap port render run time );
|
submod!( device handle jack keymap midi port render run time );
|
||||||
|
|
||||||
pub type Usually<T> = Result<T, Box<dyn Error>>;
|
pub type Usually<T> = Result<T, Box<dyn Error>>;
|
||||||
|
|
||||||
pub use crate::{key, keymap};
|
// Reexport macros:
|
||||||
|
pub use crate::{
|
||||||
|
render,
|
||||||
|
handle,
|
||||||
|
process,
|
||||||
|
keymap,
|
||||||
|
key
|
||||||
|
};
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
|
|
||||||
|
pub trait Component: Render + Handle {}
|
||||||
|
|
||||||
|
impl<T: Render + Handle> Component for T {}
|
||||||
|
|
||||||
/// A UI component that may have presence on the JACK grap.
|
/// A UI component that may have presence on the JACK grap.
|
||||||
pub trait Device: Render + Handle + PortList + Send + Sync {
|
pub trait Device: Render + Handle + PortList + Send + Sync {
|
||||||
fn boxed (self) -> Box<dyn Device> where Self: Sized + 'static {
|
fn boxed (self) -> Box<dyn Device> where Self: Sized + 'static {
|
||||||
|
|
@ -25,7 +29,7 @@ impl<T> Handle for DynamicDevice<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Render for DynamicDevice<T> {
|
impl<T: Send> Render for DynamicDevice<T> {
|
||||||
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||||
self.render.lock().unwrap()(&*self.state.lock().unwrap(), buf, area)
|
self.render.lock().unwrap()(&*self.state.lock().unwrap(), buf, area)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,26 @@ pub trait Handle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export] macro_rules! handle {
|
||||||
|
($T:ty) => {
|
||||||
|
impl Handle for $T {}
|
||||||
|
};
|
||||||
|
($T:ty |$self:ident, $e:ident|$block:tt) => {
|
||||||
|
impl Handle for $T {
|
||||||
|
fn handle (&mut $self, $e: &AppEvent) -> Usually<bool> {
|
||||||
|
$block
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($T:ty = $handle:path) => {
|
||||||
|
impl Handle for $T {
|
||||||
|
fn handle (&mut self, e: &AppEvent) -> Usually<bool> {
|
||||||
|
$handle(self, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum AppEvent {
|
pub enum AppEvent {
|
||||||
/// Terminal input
|
/// Terminal input
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,51 @@
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
|
|
||||||
|
pub fn jack_run <T> (name: &str, app: &Arc<Mutex<T>>) -> Usually<DynamicAsyncClient>
|
||||||
|
where T: Handle + Process + Send + 'static
|
||||||
|
{
|
||||||
|
let options = ClientOptions::NO_START_SERVER;
|
||||||
|
let (client, _status) = Client::new(name, options)?;
|
||||||
|
Ok(client.activate_async(
|
||||||
|
Notifications(Box::new({
|
||||||
|
let _app = app.clone();
|
||||||
|
move|_event|{
|
||||||
|
// FIXME: this deadlocks
|
||||||
|
//app.lock().unwrap().handle(&event).unwrap();
|
||||||
|
}
|
||||||
|
}) as Box<dyn Fn(AppEvent) + Send + Sync>),
|
||||||
|
ClosureProcessHandler::new(Box::new({
|
||||||
|
let app = app.clone();
|
||||||
|
move|c: &Client, s: &ProcessScope|{
|
||||||
|
app.lock().unwrap().process(c, s)
|
||||||
|
}
|
||||||
|
}) as BoxedProcessHandler)
|
||||||
|
)?)
|
||||||
|
}
|
||||||
|
|
||||||
pub trait Process {
|
pub trait Process {
|
||||||
fn process (&mut self, c: &Client, s: &ProcessScope) -> Control;
|
fn process (&mut self, _: &Client, _: &ProcessScope) -> Control {
|
||||||
|
Control::Continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export] macro_rules! process {
|
||||||
|
($T:ty) => {
|
||||||
|
impl Process for $T {}
|
||||||
|
};
|
||||||
|
($T:ty |$self:ident, $c:ident, $s:ident|$block:tt) => {
|
||||||
|
impl Process for $T {
|
||||||
|
fn process (&mut $self, $c: &Client, $s: &ProcessScope) -> Control {
|
||||||
|
$block
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($T:ty = $process:path) => {
|
||||||
|
impl Process for $T {
|
||||||
|
fn process (&mut self, c: &Client, s: &ProcessScope) -> Control {
|
||||||
|
$process(self, c, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type DynamicAsyncClient =
|
pub type DynamicAsyncClient =
|
||||||
|
|
@ -16,23 +60,6 @@ pub type DynamicProcessHandler =
|
||||||
pub type BoxedProcessHandler =
|
pub type BoxedProcessHandler =
|
||||||
Box<dyn FnMut(&Client, &ProcessScope)-> Control + Send>;
|
Box<dyn FnMut(&Client, &ProcessScope)-> Control + Send>;
|
||||||
|
|
||||||
pub fn jack_run <T> (name: &str, app: &Arc<Mutex<T>>) -> Usually<DynamicAsyncClient>
|
|
||||||
where T: Handle + Process + Send + 'static
|
|
||||||
{
|
|
||||||
let options = ClientOptions::NO_START_SERVER;
|
|
||||||
let (client, _status) = Client::new(name, options)?;
|
|
||||||
Ok(client.activate_async(
|
|
||||||
Notifications(Box::new({
|
|
||||||
let app = app.clone();
|
|
||||||
move|event|{app.lock().unwrap().handle(&event).unwrap();}
|
|
||||||
}) as Box<dyn Fn(AppEvent) + Send + Sync>),
|
|
||||||
ClosureProcessHandler::new(Box::new({
|
|
||||||
let app = app.clone();
|
|
||||||
move|c: &Client, s: &ProcessScope|{app.lock().unwrap().process(c, s)}
|
|
||||||
}) as BoxedProcessHandler)
|
|
||||||
)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub use ::jack::{
|
pub use ::jack::{
|
||||||
AsyncClient,
|
AsyncClient,
|
||||||
AudioIn,
|
AudioIn,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,14 @@
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
|
|
||||||
|
pub type PhraseData =
|
||||||
|
BTreeMap<usize, Vec<MidiMessage>>;
|
||||||
|
|
||||||
|
pub type MIDIMessage =
|
||||||
|
Vec<u8>;
|
||||||
|
|
||||||
|
pub type MIDIChunk =
|
||||||
|
[Option<Vec<MIDIMessage>>];
|
||||||
|
|
||||||
pub const KEY_WHITE: Style = Style {
|
pub const KEY_WHITE: Style = Style {
|
||||||
fg: Some(Color::Gray),
|
fg: Some(Color::Gray),
|
||||||
bg: None,
|
bg: None,
|
||||||
|
|
@ -24,3 +33,4 @@ pub const KEY_STYLE: [Style;12] = [
|
||||||
pub const KEYS_VERTICAL: [&'static str; 6] = [
|
pub const KEYS_VERTICAL: [&'static str; 6] = [
|
||||||
"▄", "▄", "█", "▀", "▀", "▀",
|
"▄", "▄", "█", "▀", "▀", "▀",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -2,34 +2,38 @@ use crate::core::*;
|
||||||
use ratatui::widgets::WidgetRef;
|
use ratatui::widgets::WidgetRef;
|
||||||
|
|
||||||
/// Trait for things that render to the display.
|
/// Trait for things that render to the display.
|
||||||
pub trait Render {
|
pub trait Render: Send {
|
||||||
// Render something to an area of the buffer.
|
// Render something to an area of the buffer.
|
||||||
// Returns area used by component.
|
// Returns area used by component.
|
||||||
// This is insufficient but for the most basic dynamic layout algorithms.
|
// This is insufficient but for the most basic dynamic layout algorithms.
|
||||||
fn render (&self, _b: &mut Buffer, _a: Rect) -> Usually<Rect> {
|
fn render (&self, _b: &mut Buffer, _a: Rect) -> Usually<Rect> {
|
||||||
Ok(Rect { x: 0, y: 0, width: 0, height: 0 })
|
Ok(Rect { x: 0, y: 0, width: 0, height: 0 })
|
||||||
}
|
}
|
||||||
fn min_width (&self) -> u16 {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
fn max_width (&self) -> u16 {
|
|
||||||
u16::MAX
|
|
||||||
}
|
|
||||||
fn min_height (&self) -> u16 {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
fn max_height (&self) -> u16 {
|
|
||||||
u16::MAX
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//fn boxed (self) -> Box<dyn Render> where Self: Sized + 'static {
|
#[macro_export] macro_rules! render {
|
||||||
//Box::new(self)
|
($T:ty) => {
|
||||||
//}
|
impl Render for $T {}
|
||||||
|
};
|
||||||
|
($T:ty |$self:ident, $buf:ident, $area:ident|$block:tt) => {
|
||||||
|
impl Render for $T {
|
||||||
|
fn render (&$self, $buf: &mut Buffer, $area: Rect) -> Usually<Rect> {
|
||||||
|
$block
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($T:ty = $render:path) => {
|
||||||
|
impl Render for $T {
|
||||||
|
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||||
|
$render(self, buf, area)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Fn(&mut Buffer, Rect) -> Usually<Rect>> Render for T {
|
impl<T: Fn(&mut Buffer, Rect) -> Usually<Rect> + Send> Render for T {
|
||||||
fn render (&self, b: &mut Buffer, a: Rect) -> Usually<Rect> {
|
fn render (&self, b: &mut Buffer, a: Rect) -> Usually<Rect> {
|
||||||
(*self).render(b, a)
|
(*self)(b, a)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
114
src/core/run.rs
114
src/core/run.rs
|
|
@ -1,28 +1,77 @@
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
|
use better_panic::{Settings, Verbosity};
|
||||||
use crossterm::terminal::{
|
use crossterm::terminal::{
|
||||||
EnterAlternateScreen, LeaveAlternateScreen,
|
EnterAlternateScreen, LeaveAlternateScreen,
|
||||||
enable_raw_mode, disable_raw_mode
|
enable_raw_mode, disable_raw_mode
|
||||||
};
|
};
|
||||||
|
|
||||||
|
impl<T: Render + Handle + Send + Sized + 'static> Run for T {}
|
||||||
|
|
||||||
pub trait Run: Render + Handle + Send + Sized + 'static {
|
pub trait Run: Render + Handle + Send + Sized + 'static {
|
||||||
fn run (self, callback: Option<impl FnOnce(Arc<Mutex<Self>>)->Usually<()>>) -> Usually<()> {
|
fn run (self, init: Option<impl FnOnce(Arc<Mutex<Self>>)->Usually<()>>) -> Usually<()> {
|
||||||
let device = Arc::new(Mutex::new(self));
|
let app = Arc::new(Mutex::new(self));
|
||||||
let exited = Arc::new(AtomicBool::new(false));
|
let exited = Arc::new(AtomicBool::new(false));
|
||||||
let _input_thread = input_thread(&exited, &device);
|
let _input_thread = input_thread(&exited, &app);
|
||||||
terminal_setup()?;
|
terminal_setup()?;
|
||||||
panic_hook_setup();
|
panic_hook_setup();
|
||||||
let main_thread = main_thread(&exited, &device)?;
|
let main_thread = main_thread(&exited, &app)?;
|
||||||
if let Some(callback) = callback {
|
if let Some(init) = init {
|
||||||
callback(device);
|
init(app)?;
|
||||||
}
|
}
|
||||||
main_thread.join();
|
main_thread.join().unwrap();
|
||||||
terminal_teardown()?;
|
terminal_teardown()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Render + Handle + Send + Sized + 'static> Run for T {}
|
/// Set up panic hook
|
||||||
|
pub fn panic_hook_setup () {
|
||||||
|
let better_panic_handler = Settings::auto().verbosity(Verbosity::Full).create_panic_handler();
|
||||||
|
std::panic::set_hook(Box::new(move |info: &std::panic::PanicInfo|{
|
||||||
|
stdout().execute(LeaveAlternateScreen).unwrap();
|
||||||
|
disable_raw_mode().unwrap();
|
||||||
|
better_panic_handler(info);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set up terminal
|
||||||
|
pub fn terminal_setup () -> Usually<()> {
|
||||||
|
stdout().execute(EnterAlternateScreen)?;
|
||||||
|
enable_raw_mode()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
/// Cleanup
|
||||||
|
pub fn terminal_teardown () -> Usually<()> {
|
||||||
|
stdout().execute(LeaveAlternateScreen)?;
|
||||||
|
disable_raw_mode()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Main thread render loop
|
||||||
|
pub fn main_thread (
|
||||||
|
exited: &Arc<AtomicBool>,
|
||||||
|
device: &Arc<Mutex<impl Render + Send + 'static>>
|
||||||
|
) -> Usually<JoinHandle<()>> {
|
||||||
|
let exited = exited.clone();
|
||||||
|
let device = device.clone();
|
||||||
|
let mut terminal = ratatui::Terminal::new(CrosstermBackend::new(stdout()))?;
|
||||||
|
let sleep = std::time::Duration::from_millis(16);
|
||||||
|
Ok(spawn(move || loop {
|
||||||
|
|
||||||
|
terminal.draw(|frame|{
|
||||||
|
let area = frame.size();
|
||||||
|
let buffer = frame.buffer_mut();
|
||||||
|
device.lock().unwrap().render(buffer, area).expect("Failed to render content.");
|
||||||
|
}).expect("Failed to render frame");
|
||||||
|
|
||||||
|
if exited.fetch_and(true, Ordering::Relaxed) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
std::thread::sleep(sleep);
|
||||||
|
|
||||||
|
}))
|
||||||
|
}
|
||||||
/// Spawn thread that listens for user input
|
/// Spawn thread that listens for user input
|
||||||
pub fn input_thread (
|
pub fn input_thread (
|
||||||
exited: &Arc<AtomicBool>,
|
exited: &Arc<AtomicBool>,
|
||||||
|
|
@ -54,52 +103,3 @@ pub fn input_thread (
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set up terminal
|
|
||||||
pub fn terminal_setup () -> Usually<()> {
|
|
||||||
stdout().execute(EnterAlternateScreen)?;
|
|
||||||
enable_raw_mode()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
/// Set up panic hook
|
|
||||||
pub fn panic_hook_setup () {
|
|
||||||
let better_panic_handler = better_panic::Settings::auto()
|
|
||||||
.verbosity(better_panic::Verbosity::Full)
|
|
||||||
.create_panic_handler();
|
|
||||||
std::panic::set_hook(Box::new(move |info: &std::panic::PanicInfo|{
|
|
||||||
stdout().execute(LeaveAlternateScreen).unwrap();
|
|
||||||
crossterm::terminal::disable_raw_mode().unwrap();
|
|
||||||
better_panic_handler(info);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
/// Main thread render loop
|
|
||||||
pub fn main_thread (
|
|
||||||
exited: &Arc<AtomicBool>,
|
|
||||||
device: &Arc<Mutex<impl Render + Send + 'static>>
|
|
||||||
) -> Usually<JoinHandle<()>> {
|
|
||||||
let exited = exited.clone();
|
|
||||||
let device = device.clone();
|
|
||||||
let mut terminal = ratatui::Terminal::new(CrosstermBackend::new(stdout()))?;
|
|
||||||
let sleep = std::time::Duration::from_millis(16);
|
|
||||||
Ok(spawn(move || loop {
|
|
||||||
|
|
||||||
terminal.draw(|frame|{
|
|
||||||
let area = frame.size();
|
|
||||||
let buffer = frame.buffer_mut();
|
|
||||||
device.lock().unwrap().render(buffer, area).expect("Failed to render content.");
|
|
||||||
}).expect("Failed to render frame");
|
|
||||||
|
|
||||||
if exited.fetch_and(true, Ordering::Relaxed) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
std::thread::sleep(sleep);
|
|
||||||
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
/// Cleanup
|
|
||||||
pub fn terminal_teardown () -> Usually<()> {
|
|
||||||
stdout().execute(LeaveAlternateScreen)?;
|
|
||||||
crossterm::terminal::disable_raw_mode()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,23 @@
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
|
use atomic_float::AtomicF64;
|
||||||
#[derive(Default)]
|
#[derive(Debug)]
|
||||||
pub struct Timebase {
|
pub struct Timebase {
|
||||||
/// Frames per second
|
/// Frames per second
|
||||||
pub rate: ::atomic_float::AtomicF64,
|
pub rate: AtomicF64,
|
||||||
/// Beats per minute
|
/// Beats per minute
|
||||||
pub bpm: ::atomic_float::AtomicF64,
|
pub bpm: AtomicF64,
|
||||||
/// Ticks per beat
|
/// Ticks per beat
|
||||||
pub ppq: ::atomic_float::AtomicF64,
|
pub ppq: AtomicF64,
|
||||||
|
}
|
||||||
|
impl Default for Timebase {
|
||||||
|
fn default () -> Self {
|
||||||
|
Self {
|
||||||
|
rate: 48000f64.into(),
|
||||||
|
bpm: 125f64.into(),
|
||||||
|
ppq: 96f64.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Timebase {
|
impl Timebase {
|
||||||
pub fn new (rate: f64, bpm: f64, ppq: f64) -> Self {
|
pub fn new (rate: f64, bpm: f64, ppq: f64) -> Self {
|
||||||
Self { rate: rate.into(), bpm: bpm.into(), ppq: ppq.into() }
|
Self { rate: rate.into(), bpm: bpm.into(), ppq: ppq.into() }
|
||||||
|
|
@ -146,7 +154,6 @@ impl Timebase {
|
||||||
ticks
|
ticks
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)] mod test {
|
#[cfg(test)] mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,281 +0,0 @@
|
||||||
use crate::core::*;
|
|
||||||
|
|
||||||
mod lv2; pub use lv2::*;
|
|
||||||
mod vst2;
|
|
||||||
mod vst3;
|
|
||||||
|
|
||||||
pub struct Plugin {
|
|
||||||
name: String,
|
|
||||||
path: Option<String>,
|
|
||||||
plugin: Option<PluginKind>,
|
|
||||||
selected: usize,
|
|
||||||
mapping: bool,
|
|
||||||
midi_ins: Vec<Port<MidiIn>>,
|
|
||||||
midi_outs: Vec<Port<MidiOut>>,
|
|
||||||
audio_ins: Vec<Port<AudioIn>>,
|
|
||||||
audio_outs: Vec<Port<AudioOut>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum PluginKind {
|
|
||||||
LV2(LV2Plugin),
|
|
||||||
VST2 {
|
|
||||||
instance: ::vst::host::PluginInstance
|
|
||||||
},
|
|
||||||
VST3,
|
|
||||||
}
|
|
||||||
|
|
||||||
const HELM: &'static str = "file:///nix/store/ij3sz7nqg5l7v2dygdvzy3w6cj62bd6r-helm-0.9.0/lib/lv2/helm.lv2";
|
|
||||||
|
|
||||||
impl Plugin {
|
|
||||||
/// Load a LV2 plugin.
|
|
||||||
pub fn lv2 (name: &str, path: &str) -> Usually<DynamicDevice<Self>> {
|
|
||||||
let host = Self::new(name)?;
|
|
||||||
let plugin = LV2Plugin::new(path)?;
|
|
||||||
let mut state = host.state();
|
|
||||||
let client = host.client.as_ref().unwrap().as_client();
|
|
||||||
let (midi_ins, midi_outs, audio_ins, audio_outs) = (
|
|
||||||
plugin.plugin.port_counts().atom_sequence_inputs,
|
|
||||||
plugin.plugin.port_counts().atom_sequence_outputs,
|
|
||||||
plugin.plugin.port_counts().audio_inputs,
|
|
||||||
plugin.plugin.port_counts().audio_outputs,
|
|
||||||
);
|
|
||||||
state.midi_ins = {
|
|
||||||
let mut ports = vec![];
|
|
||||||
for i in 0..midi_ins {
|
|
||||||
ports.push(client.register_port(&format!("midi-in-{i}"), MidiIn::default())?)
|
|
||||||
}
|
|
||||||
ports
|
|
||||||
};
|
|
||||||
state.midi_outs = {
|
|
||||||
let mut ports = vec![];
|
|
||||||
for i in 0..midi_outs {
|
|
||||||
ports.push(client.register_port(&format!("midi-out-{i}"), MidiOut::default())?)
|
|
||||||
}
|
|
||||||
ports
|
|
||||||
};
|
|
||||||
state.audio_ins = {
|
|
||||||
let mut ports = vec![];
|
|
||||||
for i in 0..audio_ins {
|
|
||||||
ports.push(client.register_port(&format!("audio-in-{i}"), AudioIn::default())?)
|
|
||||||
}
|
|
||||||
ports
|
|
||||||
};
|
|
||||||
state.audio_outs = {
|
|
||||||
let mut ports = vec![];
|
|
||||||
for i in 0..audio_outs {
|
|
||||||
ports.push(client.register_port(&format!("audio-out-{i}"), AudioOut::default())?)
|
|
||||||
}
|
|
||||||
ports
|
|
||||||
};
|
|
||||||
state.plugin = Some(PluginKind::LV2(plugin));
|
|
||||||
state.path = Some(String::from(path));
|
|
||||||
std::mem::drop(state);
|
|
||||||
Ok(host)
|
|
||||||
}
|
|
||||||
pub fn new (name: &str) -> Usually<DynamicDevice<Self>> {
|
|
||||||
DynamicDevice::new(render, handle, Self::process, Self {
|
|
||||||
name: name.into(),
|
|
||||||
path: None,
|
|
||||||
plugin: None,
|
|
||||||
selected: 0,
|
|
||||||
mapping: false,
|
|
||||||
midi_ins: vec![],
|
|
||||||
midi_outs: vec![],
|
|
||||||
audio_ins: vec![],
|
|
||||||
audio_outs: vec![],
|
|
||||||
}).activate(Client::new(name, ClientOptions::NO_START_SERVER)?.0)
|
|
||||||
}
|
|
||||||
pub fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
|
||||||
match self.plugin.as_mut() {
|
|
||||||
Some(PluginKind::LV2(LV2Plugin { features, ref mut instance, .. })) => {
|
|
||||||
let urid = features.midi_urid();
|
|
||||||
let mut inputs = vec![];
|
|
||||||
for port in self.midi_ins.iter() {
|
|
||||||
let mut atom = ::livi::event::LV2AtomSequence::new(
|
|
||||||
&features,
|
|
||||||
scope.n_frames() as usize
|
|
||||||
);
|
|
||||||
for event in port.iter(scope) {
|
|
||||||
match event.bytes.len() {
|
|
||||||
3 => atom.push_midi_event::<3>(
|
|
||||||
event.time as i64,
|
|
||||||
urid,
|
|
||||||
&event.bytes[0..3]
|
|
||||||
).unwrap(),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
inputs.push(atom);
|
|
||||||
}
|
|
||||||
let mut outputs = vec![];
|
|
||||||
for _ in self.midi_outs.iter() {
|
|
||||||
outputs.push(::livi::event::LV2AtomSequence::new(
|
|
||||||
&features,
|
|
||||||
scope.n_frames() as usize
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let ports = ::livi::EmptyPortConnections::new()
|
|
||||||
.with_atom_sequence_inputs(
|
|
||||||
inputs.iter()
|
|
||||||
)
|
|
||||||
.with_atom_sequence_outputs(
|
|
||||||
outputs.iter_mut()
|
|
||||||
)
|
|
||||||
.with_audio_inputs(
|
|
||||||
self.audio_ins.iter().map(|o|o.as_slice(scope))
|
|
||||||
)
|
|
||||||
.with_audio_outputs(
|
|
||||||
self.audio_outs.iter_mut().map(|o|o.as_mut_slice(scope))
|
|
||||||
);
|
|
||||||
unsafe {
|
|
||||||
instance.run(scope.n_frames() as usize, ports).unwrap()
|
|
||||||
};
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
Control::Continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PortList for Plugin {
|
|
||||||
fn audio_ins (&self) -> Usually<Vec<String>> {
|
|
||||||
let mut ports = vec![];
|
|
||||||
for port in self.audio_ins.iter() {
|
|
||||||
ports.push(port.name()?);
|
|
||||||
}
|
|
||||||
Ok(ports)
|
|
||||||
}
|
|
||||||
fn audio_outs (&self) -> Usually<Vec<String>> {
|
|
||||||
let mut ports = vec![];
|
|
||||||
for port in self.audio_outs.iter() {
|
|
||||||
ports.push(port.name()?);
|
|
||||||
}
|
|
||||||
Ok(ports)
|
|
||||||
}
|
|
||||||
fn midi_ins (&self) -> Usually<Vec<String>> {
|
|
||||||
let mut ports = vec![];
|
|
||||||
for port in self.midi_ins.iter() {
|
|
||||||
ports.push(port.name()?);
|
|
||||||
}
|
|
||||||
Ok(ports)
|
|
||||||
}
|
|
||||||
fn midi_outs (&self) -> Usually<Vec<String>> {
|
|
||||||
let mut ports = vec![];
|
|
||||||
for port in self.midi_outs.iter() {
|
|
||||||
ports.push(port.name()?);
|
|
||||||
}
|
|
||||||
Ok(ports)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render (state: &Plugin, buf: &mut Buffer, area: Rect)
|
|
||||||
-> Usually<Rect>
|
|
||||||
{
|
|
||||||
let Rect { x, y, height, .. } = area;
|
|
||||||
let mut width = 20u16;
|
|
||||||
match &state.plugin {
|
|
||||||
Some(PluginKind::LV2(LV2Plugin { port_list, instance, .. })) => {
|
|
||||||
let start = state.selected.saturating_sub((height as usize / 2).saturating_sub(1));
|
|
||||||
let end = start + height as usize - 2;
|
|
||||||
//draw_box(buf, Rect { x, y, width, height });
|
|
||||||
for i in start..end {
|
|
||||||
if let Some(port) = port_list.get(i) {
|
|
||||||
let value = if let Some(value) = instance.control_input(port.index) {
|
|
||||||
value
|
|
||||||
} else {
|
|
||||||
port.default_value
|
|
||||||
};
|
|
||||||
//let label = &format!("C·· M·· {:25} = {value:.03}", port.name);
|
|
||||||
let label = &format!("{:25} = {value:.03}", port.name);
|
|
||||||
width = width.max(label.len() as u16 + 4);
|
|
||||||
label.blit(buf, x + 2, y + 1 + i as u16 - start as u16, if i == state.selected {
|
|
||||||
Some(Style::default().green())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
draw_header(state, buf, area.x, area.y, width)?;
|
|
||||||
Ok(Rect { width, ..area })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn draw_header (state: &Plugin, buf: &mut Buffer, x: u16, y: u16, w: u16) -> Usually<Rect> {
|
|
||||||
let style = Style::default().gray();
|
|
||||||
let label1 = format!(" {}", state.name);
|
|
||||||
label1.blit(buf, x + 1, y, Some(style.white().bold()));
|
|
||||||
let label2 = format!("{}…", &HELM[..(w as usize - 10).min(HELM.len())]);
|
|
||||||
label2.blit(buf, x + 2 + label1.len() as u16, y, Some(style.not_dim()));
|
|
||||||
Ok(Rect { x, y, width: w, height: 1 })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle (s: &mut Plugin, event: &AppEvent) -> Usually<bool> {
|
|
||||||
handle_keymap(s, event, keymap!(Plugin {
|
|
||||||
|
|
||||||
[Up, NONE, "cursor_up", "move cursor up",
|
|
||||||
|s: &mut Plugin|{
|
|
||||||
if s.selected > 0 {
|
|
||||||
s.selected = s.selected - 1
|
|
||||||
} else {
|
|
||||||
s.selected = match &s.plugin {
|
|
||||||
Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1,
|
|
||||||
_ => 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(true)
|
|
||||||
}],
|
|
||||||
|
|
||||||
[Down, NONE, "cursor_down", "move cursor down",
|
|
||||||
|s: &mut Plugin|{
|
|
||||||
s.selected = s.selected + 1;
|
|
||||||
match &s.plugin {
|
|
||||||
Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => {
|
|
||||||
if s.selected >= port_list.len() {
|
|
||||||
s.selected = 0;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
Ok(true)
|
|
||||||
}],
|
|
||||||
|
|
||||||
[Char(','), NONE, "decrement", "decrement value",
|
|
||||||
|s: &mut Plugin|{
|
|
||||||
match s.plugin.as_mut() {
|
|
||||||
Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => {
|
|
||||||
let index = port_list[s.selected].index;
|
|
||||||
if let Some(value) = instance.control_input(index) {
|
|
||||||
instance.set_control_input(index, value - 0.01);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
Ok(true)
|
|
||||||
}],
|
|
||||||
|
|
||||||
[Char('.'), NONE, "increment", "increment value",
|
|
||||||
|s: &mut Plugin|{
|
|
||||||
match s.plugin.as_mut() {
|
|
||||||
Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => {
|
|
||||||
let index = port_list[s.selected].index;
|
|
||||||
if let Some(value) = instance.control_input(index) {
|
|
||||||
instance.set_control_input(index, value + 0.01);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
Ok(true)
|
|
||||||
}],
|
|
||||||
|
|
||||||
[Char('m'), NONE, "toggle_midi_map", "toggle midi map mode",
|
|
||||||
|s: &mut Plugin|{
|
|
||||||
s.mapping = !s.mapping;
|
|
||||||
Ok(true)
|
|
||||||
}]
|
|
||||||
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
@ -1,263 +0,0 @@
|
||||||
use crate::core::*;
|
|
||||||
|
|
||||||
pub struct Voice {
|
|
||||||
sample: Arc<Sample>,
|
|
||||||
after: usize,
|
|
||||||
position: usize,
|
|
||||||
}
|
|
||||||
impl Voice {
|
|
||||||
fn chunk (&mut self, mut frames: usize) -> Option<Vec<Vec<f32>>> {
|
|
||||||
// Create output buffer for each channel
|
|
||||||
let mut chunk = vec![vec![];self.sample.channels.len()];
|
|
||||||
// If it's not time to play yet, count down
|
|
||||||
if self.after >= frames {
|
|
||||||
self.after = self.after - frames;
|
|
||||||
return Some(chunk)
|
|
||||||
}
|
|
||||||
// If the voice will start playing within the current buffer,
|
|
||||||
// subtract the remaining number of wait frames.
|
|
||||||
if self.after > 0 && self.after < frames {
|
|
||||||
chunk = vec![vec![0.0;self.after];self.sample.channels.len()];
|
|
||||||
frames = frames - self.after;
|
|
||||||
self.after = 0;
|
|
||||||
}
|
|
||||||
if self.position < self.sample.end {
|
|
||||||
let start = self.position.min(self.sample.end);
|
|
||||||
let end = (self.position + frames).min(self.sample.end);
|
|
||||||
for (i, channel) in self.sample.channels.iter().enumerate() {
|
|
||||||
chunk[i].extend_from_slice(&channel[start..end]);
|
|
||||||
};
|
|
||||||
self.position = self.position + frames;
|
|
||||||
Some(chunk)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Sample {
|
|
||||||
pub name: String,
|
|
||||||
pub start: usize,
|
|
||||||
pub end: usize,
|
|
||||||
pub channels: Vec<Vec<f32>>,
|
|
||||||
}
|
|
||||||
impl Sample {
|
|
||||||
pub fn new (name: &str, start: usize, end: usize, channels: Vec<Vec<f32>>) -> Arc<Self> {
|
|
||||||
Arc::new(Self { name: name.to_string(), start, end, channels })
|
|
||||||
}
|
|
||||||
fn play (self: &Arc<Self>, after: usize) -> Voice {
|
|
||||||
Voice { sample: self.clone(), after, position: self.start }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Sampler {
|
|
||||||
name: String,
|
|
||||||
cursor: (usize, usize),
|
|
||||||
samples: BTreeMap<u7, Arc<Sample>>,
|
|
||||||
voices: Vec<Voice>,
|
|
||||||
midi_in: Port<MidiIn>,
|
|
||||||
audio_ins: Vec<Port<AudioIn>>,
|
|
||||||
audio_outs: Vec<Port<AudioOut>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sampler {
|
|
||||||
pub fn new (
|
|
||||||
name: &str,
|
|
||||||
samples: Option<BTreeMap<u7, Arc<Sample>>>,
|
|
||||||
) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
|
|
||||||
let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?;
|
|
||||||
DynamicDevice::new(render, handle, Self::process, Self {
|
|
||||||
name: name.into(),
|
|
||||||
cursor: (0, 0),
|
|
||||||
samples: samples.unwrap_or(BTreeMap::new()),
|
|
||||||
voices: vec![],
|
|
||||||
midi_in: client.register_port("midi", ::jack::MidiIn::default())?,
|
|
||||||
audio_ins: vec![
|
|
||||||
client.register_port("recL", ::jack::AudioIn::default())?,
|
|
||||||
client.register_port("recR", ::jack::AudioIn::default())?,
|
|
||||||
],
|
|
||||||
audio_outs: vec![
|
|
||||||
client.register_port("outL", ::jack::AudioOut::default())?,
|
|
||||||
client.register_port("outR", ::jack::AudioOut::default())?,
|
|
||||||
],
|
|
||||||
}).activate(client)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
|
||||||
// Output buffer: this will be copied to the audio outs.
|
|
||||||
let channel_count = self.audio_outs.len();
|
|
||||||
let mut mixed = vec![vec![0.0;scope.n_frames() as usize];channel_count];
|
|
||||||
// Process MIDI input to add new voices.
|
|
||||||
for RawMidi { time, bytes } in self.midi_in.iter(scope) {
|
|
||||||
if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() {
|
|
||||||
if let MidiMessage::NoteOn { ref key, .. } = message {
|
|
||||||
if let Some(sample) = self.samples.get(key) {
|
|
||||||
self.voices.push(sample.play(time as usize));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Emit next chunk of each currently playing voice,
|
|
||||||
// dropping voices that have reached their ends.
|
|
||||||
let mut voices = vec![];
|
|
||||||
std::mem::swap(&mut voices, &mut self.voices);
|
|
||||||
loop {
|
|
||||||
if voices.len() < 1 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
let mut voice = voices.swap_remove(0);
|
|
||||||
if let Some(chunk) = voice.chunk(scope.n_frames() as usize) {
|
|
||||||
for (i, channel) in chunk.iter().enumerate() {
|
|
||||||
let buffer = &mut mixed[i % channel_count];
|
|
||||||
for (i, sample) in channel.iter().enumerate() {
|
|
||||||
buffer[i] += sample;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.voices.push(voice);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Write output buffer to output ports.
|
|
||||||
for (i, port) in self.audio_outs.iter_mut().enumerate() {
|
|
||||||
let buffer = &mixed[i];
|
|
||||||
for (i, value) in port.as_mut_slice(scope).iter_mut().enumerate() {
|
|
||||||
*value = *buffer.get(i).unwrap_or(&0.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Control::Continue
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_sample (&mut self, _path: &str) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PortList for Sampler {
|
|
||||||
fn midi_ins (&self) -> Usually<Vec<String>> {
|
|
||||||
Ok(vec![self.midi_in.name()?])
|
|
||||||
}
|
|
||||||
fn audio_ins (&self) -> Usually<Vec<String>> {
|
|
||||||
let mut ports = vec![];
|
|
||||||
for port in self.audio_ins.iter() {
|
|
||||||
ports.push(port.name()?);
|
|
||||||
}
|
|
||||||
Ok(ports)
|
|
||||||
}
|
|
||||||
fn audio_outs (&self) -> Usually<Vec<String>> {
|
|
||||||
let mut ports = vec![];
|
|
||||||
for port in self.audio_outs.iter() {
|
|
||||||
ports.push(port.name()?);
|
|
||||||
}
|
|
||||||
Ok(ports)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render (state: &Sampler, buf: &mut Buffer, Rect { x, y, height, .. }: Rect)
|
|
||||||
-> Usually<Rect>
|
|
||||||
{
|
|
||||||
let style = Style::default().gray();
|
|
||||||
let title = format!(" {} ({})", state.name, state.voices.len());
|
|
||||||
title.blit(buf, x+1, y, Some(style.white().bold().not_dim()));
|
|
||||||
let mut width = title.len() + 2;
|
|
||||||
for (i, (note, sample)) in state.samples.iter().enumerate() {
|
|
||||||
let style = if i == state.cursor.0 {
|
|
||||||
Style::default().green()
|
|
||||||
} else {
|
|
||||||
Style::default()
|
|
||||||
};
|
|
||||||
let i = i as u16;
|
|
||||||
let y1 = y+1+i;
|
|
||||||
if y1 >= y + height {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if i as usize == state.cursor.0 {
|
|
||||||
"⯈".blit(buf, x+1, y1, Some(style.bold()));
|
|
||||||
}
|
|
||||||
let label1 = format!("{note:3} {:8}", sample.name);
|
|
||||||
let label2 = format!("{:>6} {:>6}", sample.start, sample.end);
|
|
||||||
label1.blit(buf, x+2, y1, Some(style.bold()));
|
|
||||||
label2.blit(buf, x+3+label1.len()as u16, y1, Some(style));
|
|
||||||
width = width.max(label1.len() + label2.len() + 4);
|
|
||||||
}
|
|
||||||
let height = ((1 + state.samples.len()) as u16).min(height);
|
|
||||||
Ok(Rect { x, y, width: width as u16, height })
|
|
||||||
}
|
|
||||||
|
|
||||||
//fn render_table (
|
|
||||||
//state: &mut Sampler,
|
|
||||||
//stdout: &mut Stdout,
|
|
||||||
//offset: (u16, u16),
|
|
||||||
//) -> Result<(), Box<dyn Error>> {
|
|
||||||
//let move_to = |col, row| crossterm::cursor::MoveTo(offset.0 + col, offset.1 + row);
|
|
||||||
//stdout.queue(move_to(0, 3))?.queue(
|
|
||||||
//Print(" Name Rate Trigger Route")
|
|
||||||
//)?;
|
|
||||||
//for (i, sample) in state.samples.lock().unwrap().iter().enumerate() {
|
|
||||||
//let row = 4 + i as u16;
|
|
||||||
//for (j, (column, field)) in [
|
|
||||||
//(0, format!(" {:7} ", sample.name)),
|
|
||||||
//(9, format!(" {:.1}Hz ", sample.rate)),
|
|
||||||
//(18, format!(" MIDI C{} {} ", sample.trigger.0, sample.trigger.1)),
|
|
||||||
//(33, format!(" {:.1}dB -> Output ", sample.gain)),
|
|
||||||
//(50, format!(" {} ", sample.playing.unwrap_or(0))),
|
|
||||||
//].into_iter().enumerate() {
|
|
||||||
//stdout.queue(move_to(column, row))?;
|
|
||||||
//if state.selected_sample == i && state.selected_column == j {
|
|
||||||
//stdout.queue(PrintStyledContent(field.to_string().bold().reverse()))?;
|
|
||||||
//} else {
|
|
||||||
//stdout.queue(PrintStyledContent(field.to_string().bold()))?;
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//Ok(())
|
|
||||||
//}
|
|
||||||
|
|
||||||
//fn render_meters (
|
|
||||||
//state: &mut Sampler,
|
|
||||||
//stdout: &mut Stdout,
|
|
||||||
//offset: (u16, u16),
|
|
||||||
//) -> Result<(), Box<dyn Error>> {
|
|
||||||
//let move_to = |col, row| crossterm::cursor::MoveTo(offset.0 + col, offset.1 + row);
|
|
||||||
//for (i, sample) in state.samples.lock().iter().enumerate() {
|
|
||||||
//let row = 4 + i as u16;
|
|
||||||
//stdout.queue(move_to(32, row))?.queue(
|
|
||||||
//PrintStyledContent("▁".green())
|
|
||||||
//)?;
|
|
||||||
//}
|
|
||||||
//Ok(())
|
|
||||||
//}
|
|
||||||
|
|
||||||
pub fn handle (state: &mut Sampler, event: &AppEvent) -> Usually<bool> {
|
|
||||||
Ok(handle_keymap(state, event, KEYMAP)?)
|
|
||||||
}
|
|
||||||
pub const KEYMAP: &'static [KeyBinding<Sampler>] = keymap!(Sampler {
|
|
||||||
[Up, NONE, "cursor_up", "move cursor up", cursor_up],
|
|
||||||
[Down, NONE, "cursor_down", "move cursor down", cursor_down],
|
|
||||||
[Char('t'), NONE, "trigger", "play current sample", trigger],
|
|
||||||
[Enter, NONE, "select", "select item under cursor", select],
|
|
||||||
});
|
|
||||||
fn cursor_up (state: &mut Sampler) -> Usually<bool> {
|
|
||||||
state.cursor.0 = if state.cursor.0 == 0 {
|
|
||||||
state.samples.len() - 1
|
|
||||||
} else {
|
|
||||||
state.cursor.0 - 1
|
|
||||||
};
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
fn cursor_down (state: &mut Sampler) -> Usually<bool> {
|
|
||||||
state.cursor.0 = (state.cursor.0 + 1) % state.samples.len();
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
fn trigger (state: &mut Sampler) -> Usually<bool> {
|
|
||||||
for (i, sample) in state.samples.values().enumerate() {
|
|
||||||
if i == state.cursor.0 {
|
|
||||||
state.voices.push(sample.play(0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
fn select (state: &mut Sampler) -> Usually<bool> {
|
|
||||||
for (i, _sample) in state.samples.values().enumerate() {
|
|
||||||
if i == state.cursor.0 {
|
|
||||||
//state.voices.push(sample.play(0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
@ -1,281 +0,0 @@
|
||||||
use crate::core::*;
|
|
||||||
use crate::layout::*;
|
|
||||||
use crate::device::*;
|
|
||||||
mod grid; pub use self::grid::*;
|
|
||||||
mod handle; pub use self::handle::*;
|
|
||||||
mod scene; pub use self::scene::*;
|
|
||||||
pub struct Launcher {
|
|
||||||
name: String,
|
|
||||||
timebase: Arc<Timebase>,
|
|
||||||
transport: ::jack::Transport,
|
|
||||||
playing: TransportState,
|
|
||||||
monitoring: bool,
|
|
||||||
recording: bool,
|
|
||||||
overdub: bool,
|
|
||||||
current_frame: usize,
|
|
||||||
cursor: (usize, usize),
|
|
||||||
pub tracks: Vec<Track>,
|
|
||||||
scenes: Vec<Scene>,
|
|
||||||
show_help: bool,
|
|
||||||
view: LauncherView,
|
|
||||||
modal: Option<Box<dyn Modal<Self>>>,
|
|
||||||
}
|
|
||||||
impl Launcher {
|
|
||||||
pub fn new (
|
|
||||||
name: &str,
|
|
||||||
timebase: &Arc<Timebase>,
|
|
||||||
tracks: Option<Vec<Track>>,
|
|
||||||
scenes: Option<Vec<Scene>>
|
|
||||||
) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
|
|
||||||
let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?;
|
|
||||||
let transport = client.transport();
|
|
||||||
let ppq = timebase.ppq() as usize;
|
|
||||||
DynamicDevice::new(render, handle, process, Self {
|
|
||||||
name: name.into(),
|
|
||||||
view: LauncherView::Chains,
|
|
||||||
playing: transport.query_state()?,
|
|
||||||
transport,
|
|
||||||
timebase: timebase.clone(),
|
|
||||||
monitoring: true,
|
|
||||||
recording: false,
|
|
||||||
overdub: true,
|
|
||||||
cursor: (2, 2),
|
|
||||||
current_frame: 0,
|
|
||||||
scenes: scenes.unwrap_or_else(||vec![Scene::new(&"Scene 1", &[None])]),
|
|
||||||
tracks: if let Some(tracks) = tracks { tracks } else { vec![
|
|
||||||
Track::new("Track 1", &timebase, None, Some(vec![
|
|
||||||
Phrase::new("MIDI Clip 1", ppq * 4, Some(BTreeMap::from([
|
|
||||||
( ppq * 0, vec![MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }] ),
|
|
||||||
( ppq * 1, vec![MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }] ),
|
|
||||||
( ppq * 2, vec![MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }] ),
|
|
||||||
( ppq * 3, vec![MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }] ),
|
|
||||||
])))
|
|
||||||
]))?,
|
|
||||||
] },
|
|
||||||
show_help: true,
|
|
||||||
modal: None
|
|
||||||
}).activate(client)
|
|
||||||
}
|
|
||||||
fn cols (&self) -> usize {
|
|
||||||
(self.tracks.len() + 2) as usize
|
|
||||||
}
|
|
||||||
fn col (&self) -> usize {
|
|
||||||
self.cursor.0 as usize
|
|
||||||
}
|
|
||||||
fn dec_col (&mut self) {
|
|
||||||
self.cursor.0 = if self.cursor.0 > 0 {
|
|
||||||
self.cursor.0 - 1
|
|
||||||
} else {
|
|
||||||
(self.cols() - 1) as usize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn inc_col (&mut self) {
|
|
||||||
self.cursor.0 = if self.cursor.0 >= self.cols() - 1 {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
self.cursor.0 + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn rows (&self) -> usize {
|
|
||||||
(self.scenes.len() + 2) as usize
|
|
||||||
}
|
|
||||||
fn row (&self) -> usize {
|
|
||||||
self.cursor.1 as usize
|
|
||||||
}
|
|
||||||
fn dec_row (&mut self) {
|
|
||||||
self.cursor.1 = if self.cursor.1 > 0 {
|
|
||||||
self.cursor.1 - 1
|
|
||||||
} else {
|
|
||||||
self.rows() - 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn inc_row (&mut self) {
|
|
||||||
self.cursor.1 = if self.cursor.1 >= self.rows() - 1 {
|
|
||||||
0
|
|
||||||
} else {
|
|
||||||
self.cursor.1 + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn track <'a> (&'a self) -> Option<(usize, &'a Track)> {
|
|
||||||
match self.col() { 0 => None, _ => {
|
|
||||||
let id = self.col() as usize - 1;
|
|
||||||
self.tracks.get(id).map(|t|(id, t))
|
|
||||||
} }
|
|
||||||
}
|
|
||||||
pub fn scene <'a> (&'a self) -> Option<(usize, &'a Scene)> {
|
|
||||||
match self.row() { 0 => None, _ => {
|
|
||||||
let id = self.row() as usize - 1;
|
|
||||||
self.scenes.get(id).map(|t|(id, t))
|
|
||||||
} }
|
|
||||||
}
|
|
||||||
pub fn sequencer <'a> (&'a self) -> Option<MutexGuard<Sequencer>> {
|
|
||||||
Some(self.track()?.1.sequencer.state())
|
|
||||||
}
|
|
||||||
pub fn chain <'a> (&'a self) -> Option<MutexGuard<Chain>> {
|
|
||||||
Some(self.track()?.1.chain.state())
|
|
||||||
}
|
|
||||||
pub fn phrase_id (&self) -> Option<usize> {
|
|
||||||
let (track_id, _) = self.track()?;
|
|
||||||
let (_, scene) = self.scene()?;
|
|
||||||
*scene.clips.get(track_id)?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl DynamicDevice<Launcher> {
|
|
||||||
pub fn connect (&self, midi_in: &str, audio_outs: &[&str]) -> Usually<&Self> {
|
|
||||||
{
|
|
||||||
let state = &self.state();
|
|
||||||
let (client, _status) = Client::new(
|
|
||||||
&format!("{}-init", &state.name), ClientOptions::NO_START_SERVER
|
|
||||||
)?;
|
|
||||||
let midi_ins = client.ports(Some(midi_in), None, PortFlags::IS_OUTPUT);
|
|
||||||
let audio_outs: Vec<Vec<String>> = audio_outs.iter()
|
|
||||||
.map(|pattern|client.ports(Some(pattern), None, PortFlags::IS_INPUT))
|
|
||||||
.collect();
|
|
||||||
for (i, sequencer) in state.tracks.iter().enumerate() {
|
|
||||||
for sequencer_midi_in in sequencer.midi_ins()?.iter() {
|
|
||||||
for midi_in in midi_ins.iter() {
|
|
||||||
client.connect_ports_by_name(&midi_in, &sequencer_midi_in)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let chain: &DynamicDevice<Chain> = &state.tracks[i].chain;
|
|
||||||
for port in sequencer.midi_outs()?.iter() {
|
|
||||||
for midi_in in chain.midi_ins()?.iter() {
|
|
||||||
client.connect_ports_by_name(&port, &midi_in)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (j, port) in chain.audio_outs()?.iter().enumerate() {
|
|
||||||
for audio_out in audio_outs[j % audio_outs.len()].iter() {
|
|
||||||
client.connect_ports_by_name(&port, &audio_out)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl PortList for Launcher {}
|
|
||||||
pub fn process (state: &mut Launcher, _: &Client, _: &ProcessScope) -> Control {
|
|
||||||
let transport = state.transport.query().unwrap();
|
|
||||||
state.playing = transport.state;
|
|
||||||
state.current_frame = transport.pos.frame() as usize;
|
|
||||||
Control::Continue
|
|
||||||
}
|
|
||||||
pub fn render (state: &Launcher, buf: &mut Buffer, mut area: Rect) -> Usually<Rect> {
|
|
||||||
//area.width = 80; // DOS mode
|
|
||||||
//area.height = 25;
|
|
||||||
let Rect { x, mut y, width, height } = area;
|
|
||||||
|
|
||||||
y = y + crate::device::Transport {
|
|
||||||
timebase: &state.timebase,
|
|
||||||
playing: state.playing,
|
|
||||||
record: state.sequencer().map(|s|s.recording).unwrap_or(false),
|
|
||||||
overdub: state.sequencer().map(|s|s.overdub).unwrap_or(false),
|
|
||||||
monitor: state.sequencer().map(|s|s.monitoring).unwrap_or(false),
|
|
||||||
frame: state.current_frame
|
|
||||||
}.render(buf, area)?.height;
|
|
||||||
|
|
||||||
y = y + crate::device::launcher::SceneGrid {
|
|
||||||
buf,
|
|
||||||
area: Rect { x, y, width, height },
|
|
||||||
name: &state.name,
|
|
||||||
focused: state.view.is_tracks(),
|
|
||||||
scenes: &state.scenes,
|
|
||||||
tracks: &state.tracks,
|
|
||||||
cursor: &state.cursor
|
|
||||||
}.draw()?.height;
|
|
||||||
|
|
||||||
y = y + draw_section_chains(
|
|
||||||
state,
|
|
||||||
buf,
|
|
||||||
Rect { x, y, width, height: height/3 }
|
|
||||||
)?.height;
|
|
||||||
|
|
||||||
y = y + draw_section_sequencer(
|
|
||||||
state,
|
|
||||||
buf,
|
|
||||||
Rect { x, y, width, height: height - y }
|
|
||||||
)?.height;
|
|
||||||
|
|
||||||
area.height = y;
|
|
||||||
if state.show_help {
|
|
||||||
let style = Some(Style::default().bold().white().not_dim().on_black().italic());
|
|
||||||
let hide = "[Tab] Mode [Arrows] Move [.,] Value [F1] Toggle help ";
|
|
||||||
hide.blit(buf, x + (width - hide.len() as u16) / 2, height - 1, style);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(ref modal) = state.modal {
|
|
||||||
for cell in buf.content.iter_mut() {
|
|
||||||
cell.fg = ::ratatui::style::Color::Gray;
|
|
||||||
cell.modifier = ::ratatui::style::Modifier::DIM;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(area)
|
|
||||||
}
|
|
||||||
fn draw_section_sequencer (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
|
||||||
let Rect { x, y, width, height } = area;
|
|
||||||
let style = Some(Style::default().green().dim());
|
|
||||||
let view = &state.view;
|
|
||||||
match view {
|
|
||||||
LauncherView::Sequencer => {
|
|
||||||
lozenge_left(buf, x, y, height, style);
|
|
||||||
lozenge_right(buf, x + width - 1, y, height, style);
|
|
||||||
},
|
|
||||||
_ => {},
|
|
||||||
};
|
|
||||||
let track = state.track();
|
|
||||||
if track.is_none() {
|
|
||||||
return Ok(area);
|
|
||||||
}
|
|
||||||
let track = track.unwrap().1;
|
|
||||||
let sequencer = track.sequencer.state();
|
|
||||||
crate::device::sequencer::horizontal::draw(
|
|
||||||
buf,
|
|
||||||
area,
|
|
||||||
match state.phrase_id().map(|id|sequencer.phrases.get(id)) {
|
|
||||||
Some(Some(phrase)) => Some(phrase),
|
|
||||||
_ => None
|
|
||||||
},
|
|
||||||
state.timebase.ppq() as usize,
|
|
||||||
sequencer.time_cursor,
|
|
||||||
sequencer.time_start,
|
|
||||||
sequencer.time_zoom,
|
|
||||||
sequencer.note_cursor,
|
|
||||||
sequencer.note_start,
|
|
||||||
Some(match view {
|
|
||||||
LauncherView::Sequencer => Style::default().green().not_dim(),
|
|
||||||
_ => Style::default().green().dim(),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
fn draw_section_chains (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
|
||||||
let style = Some(Style::default().green().dim());
|
|
||||||
if state.view.is_chains() {
|
|
||||||
let Rect { x, y, width, height} = area;
|
|
||||||
lozenge_left(buf, x, y, height, style);
|
|
||||||
lozenge_right(buf, x + width - 1, y, height, style);
|
|
||||||
}
|
|
||||||
let chain = state.chain();
|
|
||||||
let _ = if let Some(chain) = &chain {
|
|
||||||
let (_, plugins) = crate::device::chain::draw_as_row(
|
|
||||||
&*chain, buf, area, style
|
|
||||||
)?;
|
|
||||||
plugins
|
|
||||||
} else {
|
|
||||||
vec![]
|
|
||||||
};
|
|
||||||
Ok(area)
|
|
||||||
}
|
|
||||||
pub enum LauncherView {
|
|
||||||
Tracks,
|
|
||||||
Sequencer,
|
|
||||||
Chains
|
|
||||||
}
|
|
||||||
impl LauncherView {
|
|
||||||
fn is_chains (&self) -> bool {
|
|
||||||
match self { Self::Chains => true, _ => false }
|
|
||||||
}
|
|
||||||
fn is_tracks (&self) -> bool {
|
|
||||||
match self { Self::Tracks => true, _ => false }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,249 +0,0 @@
|
||||||
use crate::core::*;
|
|
||||||
use crate::layout::*;
|
|
||||||
|
|
||||||
pub struct Mixer {
|
|
||||||
name: String,
|
|
||||||
tracks: Vec<Track>,
|
|
||||||
selected_track: usize,
|
|
||||||
selected_column: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Mixer {
|
|
||||||
pub fn new (name: &str) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
|
|
||||||
let (client, _status) = Client::new(name, ClientOptions::NO_START_SERVER)?;
|
|
||||||
Ok(DynamicDevice::new(render, handle, process, Self {
|
|
||||||
name: name.into(),
|
|
||||||
selected_column: 0,
|
|
||||||
selected_track: 1,
|
|
||||||
tracks: vec![
|
|
||||||
Track::new(&client, 1, "Mono 1")?,
|
|
||||||
Track::new(&client, 1, "Mono 2")?,
|
|
||||||
Track::new(&client, 2, "Stereo 1")?,
|
|
||||||
Track::new(&client, 2, "Stereo 2")?,
|
|
||||||
Track::new(&client, 2, "Stereo 3")?,
|
|
||||||
Track::new(&client, 2, "Bus 1")?,
|
|
||||||
Track::new(&client, 2, "Bus 2")?,
|
|
||||||
Track::new(&client, 2, "Mix")?,
|
|
||||||
],
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn process (
|
|
||||||
_: &mut Mixer,
|
|
||||||
_: &Client,
|
|
||||||
_: &ProcessScope
|
|
||||||
) -> Control {
|
|
||||||
Control::Continue
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render (state: &Mixer, buf: &mut Buffer, mut area: Rect)
|
|
||||||
-> Usually<Rect>
|
|
||||||
{
|
|
||||||
if area.height < 2 {
|
|
||||||
return Ok(area)
|
|
||||||
}
|
|
||||||
area.x = area.width.saturating_sub(80) / 2;
|
|
||||||
area.width = area.width.min(80);
|
|
||||||
area.height = state.tracks.len() as u16 + 2;
|
|
||||||
draw_box(buf, area);
|
|
||||||
let x = area.x + 1;
|
|
||||||
let y = area.y + 1;
|
|
||||||
let _h = area.height - 2;
|
|
||||||
for (i, track) in state.tracks.iter().enumerate() {
|
|
||||||
//buf.set_string(
|
|
||||||
//x, y + index as u16,
|
|
||||||
//&track.name, Style::default().bold().not_dim()
|
|
||||||
//);
|
|
||||||
for (j, (column, field)) in [
|
|
||||||
(0, format!(" {:10} ", track.name)),
|
|
||||||
(12, format!(" {:.1}dB ", track.gain)),
|
|
||||||
(22, format!(" [ ] ")),
|
|
||||||
(30, format!(" C ")),
|
|
||||||
(35, format!(" {:.1}dB ", track.level)),
|
|
||||||
(45, format!(" [ ] ")),
|
|
||||||
(51, format!(" {:7} ", track.route)),
|
|
||||||
].into_iter().enumerate() {
|
|
||||||
buf.set_string(
|
|
||||||
x + column as u16,
|
|
||||||
y + i as u16,
|
|
||||||
field,
|
|
||||||
if state.selected_track == i && state.selected_column == j {
|
|
||||||
Style::default().white().bold().not_dim()
|
|
||||||
} else {
|
|
||||||
Style::default().not_dim()
|
|
||||||
}
|
|
||||||
);
|
|
||||||
//stdout.queue(move_to(column, row))?;
|
|
||||||
//if state.selected_track == i && state.selected_column == j {
|
|
||||||
//stdout.queue(PrintStyledContent(field.to_string().bold().reverse()))?;
|
|
||||||
//} else {
|
|
||||||
//stdout.queue(PrintStyledContent(field.to_string().bold()))?;
|
|
||||||
//}
|
|
||||||
//fn render_meters (
|
|
||||||
//state: &mut Mixer,
|
|
||||||
//stdout: &mut Stdout,
|
|
||||||
//offset: Rect
|
|
||||||
//) -> Result<(), Box<dyn Error>> {
|
|
||||||
//let move_to = |col, row| crossterm::cursor::MoveTo(offset.0 + col, offset.1 + row);
|
|
||||||
//for (i, track) in state.tracks.iter().enumerate() {
|
|
||||||
//let row = (i + 1) as u16;
|
|
||||||
//stdout
|
|
||||||
//.queue(move_to(10, row))?.queue(PrintStyledContent("▁".green()))?
|
|
||||||
//.queue(move_to(20, row))?.queue(PrintStyledContent("▁".green()))?
|
|
||||||
//.queue(move_to(28, row))?.queue(PrintStyledContent("▁".green()))?
|
|
||||||
//.queue(move_to(43, row))?.queue(PrintStyledContent("▁".green()))?;
|
|
||||||
//}
|
|
||||||
//Ok(())
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(area)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle (state: &mut Mixer, event: &AppEvent) -> Usually<bool> {
|
|
||||||
if let AppEvent::Input(crossterm::event::Event::Key(event)) = event {
|
|
||||||
|
|
||||||
match event.code {
|
|
||||||
//KeyCode::Char('c') => {
|
|
||||||
//if event.modifiers == KeyModifiers::CONTROL {
|
|
||||||
//state.exit();
|
|
||||||
//}
|
|
||||||
//},
|
|
||||||
KeyCode::Down => {
|
|
||||||
state.selected_track = (state.selected_track + 1) % state.tracks.len();
|
|
||||||
println!("{}", state.selected_track);
|
|
||||||
return Ok(true)
|
|
||||||
},
|
|
||||||
KeyCode::Up => {
|
|
||||||
if state.selected_track == 0 {
|
|
||||||
state.selected_track = state.tracks.len() - 1;
|
|
||||||
} else {
|
|
||||||
state.selected_track = state.selected_track - 1;
|
|
||||||
}
|
|
||||||
println!("{}", state.selected_track);
|
|
||||||
return Ok(true)
|
|
||||||
},
|
|
||||||
KeyCode::Left => {
|
|
||||||
if state.selected_column == 0 {
|
|
||||||
state.selected_column = 6
|
|
||||||
} else {
|
|
||||||
state.selected_column = state.selected_column - 1;
|
|
||||||
}
|
|
||||||
return Ok(true)
|
|
||||||
},
|
|
||||||
KeyCode::Right => {
|
|
||||||
if state.selected_column == 6 {
|
|
||||||
state.selected_column = 0
|
|
||||||
} else {
|
|
||||||
state.selected_column = state.selected_column + 1;
|
|
||||||
}
|
|
||||||
return Ok(true)
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
println!("\n{event:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO:
|
|
||||||
// - Meters: propagate clipping:
|
|
||||||
// - If one stage clips, all stages after it are marked red
|
|
||||||
// - If one track clips, all tracks that feed from it are marked red?
|
|
||||||
|
|
||||||
pub const ACTIONS: [(&'static str, &'static str);2] = [
|
|
||||||
("+/-", "Adjust"),
|
|
||||||
("Ins/Del", "Add/remove track"),
|
|
||||||
];
|
|
||||||
|
|
||||||
pub struct Track {
|
|
||||||
name: String,
|
|
||||||
channels: u8,
|
|
||||||
input_ports: Vec<Port<AudioIn>>,
|
|
||||||
pre_gain_meter: f64,
|
|
||||||
gain: f64,
|
|
||||||
insert_ports: Vec<Port<AudioOut>>,
|
|
||||||
return_ports: Vec<Port<AudioIn>>,
|
|
||||||
post_gain_meter: f64,
|
|
||||||
post_insert_meter: f64,
|
|
||||||
level: f64,
|
|
||||||
pan: f64,
|
|
||||||
output_ports: Vec<Port<AudioOut>>,
|
|
||||||
post_fader_meter: f64,
|
|
||||||
route: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Track {
|
|
||||||
pub fn new (jack: &Client, channels: u8, name: &str) -> Result<Self, Box<dyn Error>> {
|
|
||||||
let mut input_ports = vec![];
|
|
||||||
let mut insert_ports = vec![];
|
|
||||||
let mut return_ports = vec![];
|
|
||||||
let mut output_ports = vec![];
|
|
||||||
for channel in 1..=channels {
|
|
||||||
input_ports.push(jack.register_port(&format!("{name} [input {channel}]"), AudioIn::default())?);
|
|
||||||
output_ports.push(jack.register_port(&format!("{name} [out {channel}]"), AudioOut::default())?);
|
|
||||||
let insert_port = jack.register_port(&format!("{name} [pre {channel}]"), AudioOut::default())?;
|
|
||||||
let return_port = jack.register_port(&format!("{name} [insert {channel}]"), AudioIn::default())?;
|
|
||||||
jack.connect_ports(&insert_port, &return_port)?;
|
|
||||||
insert_ports.push(insert_port);
|
|
||||||
return_ports.push(return_port);
|
|
||||||
}
|
|
||||||
Ok(Self {
|
|
||||||
name: name.into(),
|
|
||||||
channels,
|
|
||||||
input_ports,
|
|
||||||
pre_gain_meter: 0.0,
|
|
||||||
gain: 0.0,
|
|
||||||
post_gain_meter: 0.0,
|
|
||||||
insert_ports,
|
|
||||||
return_ports,
|
|
||||||
post_insert_meter: 0.0,
|
|
||||||
level: 0.0,
|
|
||||||
pan: 0.0,
|
|
||||||
post_fader_meter: 0.0,
|
|
||||||
route: "---".into(),
|
|
||||||
output_ports,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//impl<W: Write> Input<TUI<W>, bool> for Mixer {
|
|
||||||
//fn handle (&mut self, engine: &mut TUI<W>) -> Result<Option<bool>> {
|
|
||||||
//Ok(None)
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
|
|
||||||
//impl<W: Write> Output<TUI<W>, [u16;2]> for Mixer {
|
|
||||||
//fn render (&self, envine: &mut TUI<W>) -> Result<Option<[u16;2]>> {
|
|
||||||
|
|
||||||
//let tracks_table = Columns::new()
|
|
||||||
//.add(titles)
|
|
||||||
//.add(input_meters)
|
|
||||||
//.add(gains)
|
|
||||||
//.add(gain_meters)
|
|
||||||
//.add(pres)
|
|
||||||
//.add(pre_meters)
|
|
||||||
//.add(levels)
|
|
||||||
//.add(pans)
|
|
||||||
//.add(pan_meters)
|
|
||||||
//.add(posts)
|
|
||||||
//.add(routes)
|
|
||||||
|
|
||||||
//Rows::new()
|
|
||||||
//.add(Columns::new()
|
|
||||||
//.add(Rows::new()
|
|
||||||
//.add("[Arrows]".bold())
|
|
||||||
//.add("Navigate"))
|
|
||||||
//.add(Rows::new()
|
|
||||||
//.add("[+/-]".bold())
|
|
||||||
//.add("Adjust"))
|
|
||||||
//.add(Rows::new()
|
|
||||||
//.add("[Ins/Del]".bold())
|
|
||||||
//.add("Add/remove track")))
|
|
||||||
//.add(tracks_table)
|
|
||||||
//.render(engine)
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
mod chain; pub use self::chain::{Chain, Plugin, Sampler, Sample, Voice};
|
|
||||||
mod launcher; pub use self::launcher::{Launcher, Scene};
|
|
||||||
mod looper; pub use self::looper::Looper;
|
|
||||||
mod mixer; pub use self::mixer::Mixer;
|
|
||||||
mod sequencer; pub use self::sequencer::{Sequencer, Phrase};
|
|
||||||
mod track; pub use self::track::Track;
|
|
||||||
mod transport; pub use self::transport::Transport;
|
|
||||||
|
|
@ -1,210 +0,0 @@
|
||||||
use crate::core::*;
|
|
||||||
use crate::layout::*;
|
|
||||||
|
|
||||||
mod keys; use self::keys::*;
|
|
||||||
mod handle; pub use self::handle::*;
|
|
||||||
mod phrase; pub use self::phrase::*;
|
|
||||||
|
|
||||||
pub mod horizontal;
|
|
||||||
pub mod 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: SequencerView,
|
|
||||||
/// 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,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum SequencerView { Tiny, Compact, Horizontal, Vertical, }
|
|
||||||
|
|
||||||
impl Sequencer {
|
|
||||||
pub fn new (
|
|
||||||
name: &str,
|
|
||||||
timebase: &Arc<Timebase>,
|
|
||||||
phrases: Option<Vec<Phrase>>,
|
|
||||||
) -> Usually<DynamicDevice<Self>> {
|
|
||||||
let (client, _status) = Client::new(name, ClientOptions::NO_START_SERVER)?;
|
|
||||||
let transport = client.transport();
|
|
||||||
DynamicDevice::new(render, handle, Self::process, 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: SequencerView::Horizontal,
|
|
||||||
notes_on: vec![false;128],
|
|
||||||
note_start: 12,
|
|
||||||
note_cursor: 0,
|
|
||||||
time_zoom: 24,
|
|
||||||
time_start: 0,
|
|
||||||
time_cursor: 0,
|
|
||||||
}).activate(client)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn phrase <'a> (&'a self) -> Option<&'a Phrase> {
|
|
||||||
self.phrases.get(self.sequence?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
|
||||||
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 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()?]) }
|
|
||||||
}
|
|
||||||
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 {
|
|
||||||
SequencerView::Tiny => Rect { x, y, width, height: 0 },
|
|
||||||
SequencerView::Compact => Rect { x, y, width, height: 0 },
|
|
||||||
SequencerView::Vertical => self::vertical::draw(s, buf, Rect {
|
|
||||||
x, y: y + header.height, width, height,
|
|
||||||
})?,
|
|
||||||
SequencerView::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::device::Transport {
|
|
||||||
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 })
|
|
||||||
}
|
|
||||||
/// 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:?}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
503
src/main.rs
503
src/main.rs
|
|
@ -6,256 +6,321 @@ extern crate clap;
|
||||||
extern crate jack;
|
extern crate jack;
|
||||||
extern crate crossterm;
|
extern crate crossterm;
|
||||||
|
|
||||||
use clap::{Parser};
|
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
pub mod core;
|
|
||||||
pub mod cli;
|
pub mod cli;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod device;
|
pub mod control;
|
||||||
pub mod layout;
|
pub mod core;
|
||||||
|
pub mod model;
|
||||||
|
pub mod view;
|
||||||
|
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
use crate::device::*;
|
use crate::model::*;
|
||||||
|
use crate::view::*;
|
||||||
|
|
||||||
|
pub fn main () -> Usually<()> {
|
||||||
|
App::default().run(Some(|app: Arc<Mutex<App>>|{
|
||||||
|
let mut state = app.lock().unwrap();
|
||||||
|
let xdg = Arc::new(microxdg::XdgApp::new("tek")?);
|
||||||
|
state.xdg = Some(xdg.clone());
|
||||||
|
if crate::config::AppPaths::new(&xdg)?.should_create() {
|
||||||
|
state.modal = Some(Box::new(crate::config::SetupModal(Some(xdg.clone()))));
|
||||||
|
}
|
||||||
|
state.scenes = vec![
|
||||||
|
Scene::new("Intro", vec![None, None, None, None]),
|
||||||
|
];
|
||||||
|
state.phrases = vec![
|
||||||
|
Phrase::new("4 kicks", state.timebase.ppq() as usize * 4, None),
|
||||||
|
];
|
||||||
|
state.tracks = vec![
|
||||||
|
Track::new("Drums", &state.timebase, 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(),
|
||||||
|
]), None)?,
|
||||||
|
];
|
||||||
|
state.jack = Some(jack_run("tek", &app)?);
|
||||||
|
Ok(())
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
mod new {
|
|
||||||
use crate::core::*;
|
|
||||||
use crate::layout::Stack;
|
|
||||||
type Phrase = (String, usize, BTreeMap<usize, Vec<MidiMessage>>);
|
|
||||||
type Scene = (String, Option<usize>, Vec<Option<usize>>);
|
|
||||||
type Track = (String, usize);
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct App {
|
pub struct App {
|
||||||
client: Option<DynamicAsyncClient>,
|
pub xdg: Option<Arc<XdgApp>>,
|
||||||
phrases: BTreeMap<usize, Phrase>,
|
pub jack: Option<DynamicAsyncClient>,
|
||||||
scenes: BTreeMap<usize, Scene>,
|
pub phrases: Vec<Phrase>,
|
||||||
tracks: BTreeMap<usize, Track>,
|
pub scenes: Vec<Scene>,
|
||||||
frame: usize,
|
pub tracks: Vec<Track>,
|
||||||
scene: Vec<usize>,
|
pub frame: usize,
|
||||||
timebase: Arc<Timebase>,
|
pub scene: Vec<usize>,
|
||||||
|
pub timebase: Arc<Timebase>,
|
||||||
|
pub modal: Option<Box<dyn Component>>,
|
||||||
}
|
}
|
||||||
struct SceneGrid {}
|
|
||||||
impl Render for SceneGrid {}
|
process!(App);
|
||||||
struct Chains {}
|
|
||||||
impl Render for Chains {}
|
render!(App |self, buf, area| {
|
||||||
struct Sequencer {}
|
let Rect { x, mut y, width, height } = area;
|
||||||
impl Render for Sequencer {}
|
|
||||||
impl Render for App {
|
y = y + TransportView {
|
||||||
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
|
||||||
crate::device::Transport {
|
|
||||||
timebase: &self.timebase,
|
timebase: &self.timebase,
|
||||||
playing: TransportState::Stopped,
|
playing: TransportState::Stopped,
|
||||||
record: false,
|
record: false,
|
||||||
overdub: false,
|
overdub: false,
|
||||||
monitor: false,
|
monitor: false,
|
||||||
frame: 0,
|
frame: 0,
|
||||||
}.render(buf, area)?;
|
}.render(buf, area)?.height;
|
||||||
SceneGrid {
|
|
||||||
}.render(buf, area)?;
|
y = y + SceneGridView {
|
||||||
Chains {}.render(buf, area)?;
|
buf,
|
||||||
Sequencer {}.render(buf, area)?;
|
area: Rect { x, y, width, height: height / 3 },
|
||||||
Ok(area)
|
name: "",
|
||||||
}
|
focused: true,
|
||||||
}
|
scenes: &self.scenes,
|
||||||
impl Handle for App {
|
tracks: &self.tracks,
|
||||||
fn handle (&mut self, _e: &AppEvent) -> Usually<bool> {
|
cursor: &(0, 0)
|
||||||
Ok(true)
|
}.draw()?.height;
|
||||||
}
|
|
||||||
}
|
y = y + ChainView {
|
||||||
impl Process for App {
|
focused: true,
|
||||||
fn process (&mut self, _c: &Client, _s: &ProcessScope) -> Control {
|
chain: None
|
||||||
Control::Continue
|
}.render(buf, Rect { x, y, width, height: height / 3 })?.height;
|
||||||
}
|
|
||||||
}
|
y = y + SequencerView {
|
||||||
pub fn main () -> Usually<()> {
|
focused: true,
|
||||||
App::default().run(Some(|app: Arc<Mutex<App>>|{
|
ppq: self.timebase.ppq() as usize,
|
||||||
app.lock().unwrap().client = Some(jack_run("tek", &app)?);
|
track: None,
|
||||||
Ok(())
|
phrase: None,
|
||||||
}))
|
}.render(buf, Rect { x, y, width, height })?.height;
|
||||||
|
|
||||||
|
if let Some(ref modal) = self.modal {
|
||||||
|
for cell in buf.content.iter_mut() {
|
||||||
|
cell.fg = ratatui::style::Color::Gray;
|
||||||
|
cell.modifier = ratatui::style::Modifier::DIM;
|
||||||
}
|
}
|
||||||
|
modal.render(buf, area)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! sample {
|
Ok(area)
|
||||||
($note:expr, $name:expr, $src:expr) => {
|
});
|
||||||
{
|
|
||||||
let mut channels: Vec<wavers::Samples<f32>> = vec![];
|
handle!(App |self, e| {
|
||||||
for channel in wavers::Wav::from_path($src)?.channels() {
|
if let Some(ref mut modal) = self.modal {
|
||||||
channels.push(channel);
|
if modal.handle(e)? {
|
||||||
}
|
self.modal = None;
|
||||||
let mut end = 0;
|
return Ok(true)
|
||||||
let mut data: Vec<Vec<f32>> = vec![];
|
|
||||||
for samples in channels.iter() {
|
|
||||||
let channel = Vec::from(samples.as_ref());
|
|
||||||
end = end.max(channel.len());
|
|
||||||
data.push(channel);
|
|
||||||
}
|
|
||||||
(u7::from_int_lossy($note).into(), Sample::new($name, 0, end, data).into())
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
handle_keymap(self, e, keymap!(App {
|
||||||
|
[F(1), NONE, "toggle_help", "toggle help", toggle_help],
|
||||||
|
|
||||||
fn main () -> Usually<()> {
|
[Up, NONE, "cursor_up", "move cursor up", cursor_up],
|
||||||
let _cli = cli::Cli::parse();
|
[Down, NONE, "cursor_down", "move cursor down", cursor_down],
|
||||||
let xdg = microxdg::XdgApp::new("tek")?;
|
[Left, NONE, "cursor_left", "move cursor left", cursor_left],
|
||||||
crate::config::create_dirs(&xdg)?;
|
[Right, NONE, "cursor_right", "move cursor right", cursor_right],
|
||||||
//run(Sampler::new("Sampler#000")?)
|
[Char('.'), NONE, "increment", "increment value at cursor", increment],
|
||||||
let (client, _) = Client::new("init", ClientOptions::NO_START_SERVER)?;
|
[Char(','), NONE, "decrement", "decrement value at cursor", decrement],
|
||||||
let timebase = Arc::new(Timebase::new(client.sample_rate() as f64, 125.0, 96.0));
|
[Delete, CONTROL, "delete", "delete track", delete],
|
||||||
let ppq = timebase.ppq() as usize;
|
[Char('d'), CONTROL, "duplicate", "duplicate scene or track", duplicate],
|
||||||
macro_rules! play {
|
[Enter, NONE, "activate", "activate item at cursor", activate],
|
||||||
($t1:expr => [ $($msg:expr),* $(,)? ]) => {
|
|
||||||
( $t1 * ppq / 4, vec![ $($msg),* ] )
|
|
||||||
}
|
|
||||||
}
|
|
||||||
macro_rules! phrase {
|
|
||||||
($($t:expr => $msg:expr),* $(,)?) => {{
|
|
||||||
let mut phrase = BTreeMap::new();
|
|
||||||
$(phrase.insert($t, vec![]);)*
|
|
||||||
$(phrase.get_mut(&$t).unwrap().push($msg);)*
|
|
||||||
phrase
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
Launcher::new("Launcher#0", &timebase,
|
|
||||||
Some(vec![
|
|
||||||
|
|
||||||
Track::new("Drums", &timebase, Some(vec![
|
[Tab, NONE, "focus_next", "focus next area", focus_next],
|
||||||
|
[Tab, SHIFT, "focus_prev", "focus previous area", focus_prev],
|
||||||
|
[Char(' '), NONE, "play_toggle", "play or pause", play_toggle],
|
||||||
|
[Char('r'), NONE, "record_toggle", "toggle recording", record_toggle],
|
||||||
|
[Char('d'), NONE, "overdub_toggle", "toggle overdub", overdub_toggle],
|
||||||
|
[Char('m'), NONE, "monitor_toggle", "toggle input monitoring", monitor_toggle],
|
||||||
|
[Char('r'), CONTROL, "rename", "rename current element", rename],
|
||||||
|
[Char('t'), CONTROL, "add_track", "add a new track", add_track],
|
||||||
|
//[Char(' '), SHIFT, "play_start", "play from start", play_start],
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
|
||||||
Sampler::new("Sampler", Some(BTreeMap::from([
|
fn increment (_: &mut App) -> Usually<bool> { Ok(true) }
|
||||||
sample!(36, "Kick", "/home/user/Lab/Music/pak/kik.wav"),
|
fn decrement (_: &mut App) -> Usually<bool> { Ok(true) }
|
||||||
sample!(40, "Snare", "/home/user/Lab/Music/pak/sna.wav"),
|
fn delete (_: &mut App) -> Usually<bool> { Ok(true) }
|
||||||
sample!(44, "Hihat", "/home/user/Lab/Music/pak/chh.wav"),
|
fn duplicate (_: &mut App) -> Usually<bool> { Ok(true) }
|
||||||
])))?.boxed(),
|
fn activate (_: &mut App) -> Usually<bool> { Ok(true) }
|
||||||
|
fn rename (_: &mut App) -> Usually<bool> { Ok(true) }
|
||||||
|
fn add_track (_: &mut App) -> Usually<bool> { Ok(true) }
|
||||||
|
fn delete_track (_: &mut App) -> Usually<bool> { Ok(true) }
|
||||||
|
fn cursor_up (_: &mut App) -> Usually<bool> { Ok(true) }
|
||||||
|
fn cursor_down (_: &mut App) -> Usually<bool> { Ok(true) }
|
||||||
|
fn cursor_left (_: &mut App) -> Usually<bool> { Ok(true) }
|
||||||
|
fn cursor_right (_: &mut App) -> Usually<bool> { Ok(true) }
|
||||||
|
fn toggle_help (_: &mut App) -> Usually<bool> { Ok(true) }
|
||||||
|
fn focus_next (_: &mut App) -> Usually<bool> { Ok(true) }
|
||||||
|
fn focus_prev (_: &mut App) -> Usually<bool> { Ok(true) }
|
||||||
|
fn clip_next (_: &mut App) -> Usually<bool> { Ok(true) }
|
||||||
|
fn clip_prev (_: &mut App) -> Usually<bool> { Ok(true) }
|
||||||
|
fn play_toggle (_: &mut App) -> Usually<bool> { Ok(true) }
|
||||||
|
fn record_toggle (_: &mut App) -> Usually<bool> { Ok(true) }
|
||||||
|
fn overdub_toggle (_: &mut App) -> Usually<bool> { Ok(true) }
|
||||||
|
fn monitor_toggle (_: &mut App) -> Usually<bool> { Ok(true) }
|
||||||
|
|
||||||
//Plugin::lv2("Panagement", "file:///home/user/.lv2/Auburn Sounds Panagement 2.lv2")?.boxed(),
|
//fn main () -> Usually<()> {
|
||||||
|
//let _cli = cli::Cli::parse();
|
||||||
|
//let xdg = microxdg::XdgApp::new("tek")?;
|
||||||
|
//crate::config::create_dirs(&xdg)?;
|
||||||
|
////run(Sampler::new("Sampler#000")?)
|
||||||
|
//let (client, _) = Client::new("init", ClientOptions::NO_START_SERVER)?;
|
||||||
|
//let timebase = Arc::new(Timebase::new(client.sample_rate() as f64, 125.0, 96.0));
|
||||||
|
//let ppq = timebase.ppq() as usize;
|
||||||
|
//macro_rules! play {
|
||||||
|
//($t1:expr => [ $($msg:expr),* $(,)? ]) => {
|
||||||
|
//( $t1 * ppq / 4, vec![ $($msg),* ] )
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//macro_rules! phrase {
|
||||||
|
//($($t:expr => $msg:expr),* $(,)?) => {{
|
||||||
|
//let mut phrase = BTreeMap::new();
|
||||||
|
//$(phrase.insert($t, vec![]);)*
|
||||||
|
//$(phrase.get_mut(&$t).unwrap().push($msg);)*
|
||||||
|
//phrase
|
||||||
|
//}}
|
||||||
|
//}
|
||||||
|
//Launcher::new("Launcher#0", &timebase,
|
||||||
|
//Some(vec![
|
||||||
|
|
||||||
]), Some(vec![
|
//Track::new("Drums", &timebase, Some(vec![
|
||||||
|
|
||||||
Phrase::new("KSH", ppq * 4, Some(phrase! {
|
//Sampler::new("Sampler", Some(BTreeMap::from([
|
||||||
00 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
//sample!(36, "Kick", "/home/user/Lab/Music/pak/kik.wav"),
|
||||||
00 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
|
//sample!(40, "Snare", "/home/user/Lab/Music/pak/sna.wav"),
|
||||||
01 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
|
//sample!(44, "Hihat", "/home/user/Lab/Music/pak/chh.wav"),
|
||||||
02 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
|
//])))?.boxed(),
|
||||||
04 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
|
|
||||||
04 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
|
|
||||||
06 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
|
|
||||||
08 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
|
|
||||||
10 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
|
||||||
10 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
|
|
||||||
11 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
|
|
||||||
12 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
|
|
||||||
12 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
|
|
||||||
14 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
|
|
||||||
14 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() }
|
|
||||||
})),
|
|
||||||
|
|
||||||
Phrase::new("4K", ppq * 4, Some(phrase! {
|
////Plugin::lv2("Panagement", "file:///home/user/.lv2/Auburn Sounds Panagement 2.lv2")?.boxed(),
|
||||||
00 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
|
||||||
04 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
|
||||||
08 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
|
||||||
12 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
|
||||||
})),
|
|
||||||
|
|
||||||
Phrase::new("KS", ppq * 4, Some(phrase! {
|
//]), Some(vec![
|
||||||
00 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
|
||||||
04 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
|
|
||||||
10 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
|
||||||
12 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
|
|
||||||
})),
|
|
||||||
|
|
||||||
]))?,
|
//Phrase::new("KSH", ppq * 4, Some(phrase! {
|
||||||
|
//00 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
||||||
|
//00 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
|
||||||
|
//01 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
|
||||||
|
//02 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
|
||||||
|
//04 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
|
||||||
|
//04 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
|
||||||
|
//06 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
|
||||||
|
//08 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
|
||||||
|
//10 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
||||||
|
//10 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
|
||||||
|
//11 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
|
||||||
|
//12 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
|
||||||
|
//12 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() },
|
||||||
|
//14 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
|
||||||
|
//14 * ppq/4 => MidiMessage::NoteOn { key: 44.into(), vel: 100.into() }
|
||||||
|
//})),
|
||||||
|
|
||||||
Track::new("Odin2", &timebase, Some(vec![
|
//Phrase::new("4K", ppq * 4, Some(phrase! {
|
||||||
|
//00 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
||||||
|
//04 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
||||||
|
//08 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
||||||
|
//12 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
||||||
|
//})),
|
||||||
|
|
||||||
Plugin::lv2("Odin2", "file:///home/user/.lv2/Odin2.lv2")?.boxed(),
|
//Phrase::new("KS", ppq * 4, Some(phrase! {
|
||||||
|
//00 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
||||||
|
//04 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
|
||||||
|
//10 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
||||||
|
//12 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
|
||||||
|
//})),
|
||||||
|
|
||||||
]), Some(vec![
|
//]))?,
|
||||||
Phrase::new("E G A Bb", ppq * 4, Some(BTreeMap::from([
|
|
||||||
play!(2 => [
|
|
||||||
MidiMessage::NoteOff { key: 42.into(), vel: 100.into() },
|
|
||||||
MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
|
||||||
]),
|
|
||||||
play!(6 => [
|
|
||||||
MidiMessage::NoteOff { key: 36.into(), vel: 100.into() },
|
|
||||||
MidiMessage::NoteOn { key: 39.into(), vel: 100.into() },
|
|
||||||
]),
|
|
||||||
play!(10 => [
|
|
||||||
MidiMessage::NoteOff { key: 39.into(), vel: 100.into() },
|
|
||||||
MidiMessage::NoteOn { key: 41.into(), vel: 100.into() },
|
|
||||||
]),
|
|
||||||
play!(14 => [
|
|
||||||
MidiMessage::NoteOff { key: 41.into(), vel: 100.into() },
|
|
||||||
MidiMessage::NoteOn { key: 42.into(), vel: 100.into() },
|
|
||||||
]),
|
|
||||||
]))),
|
|
||||||
Phrase::new("E E G Bb", ppq * 4, Some(BTreeMap::from([
|
|
||||||
play!(2 => [
|
|
||||||
MidiMessage::NoteOff { key: 42.into(), vel: 100.into() },
|
|
||||||
MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
|
||||||
]),
|
|
||||||
play!(6 => [
|
|
||||||
MidiMessage::NoteOff { key: 36.into(), vel: 100.into() },
|
|
||||||
MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
|
||||||
]),
|
|
||||||
play!(10 => [
|
|
||||||
MidiMessage::NoteOff { key: 36.into(), vel: 100.into() },
|
|
||||||
MidiMessage::NoteOn { key: 39.into(), vel: 100.into() },
|
|
||||||
]),
|
|
||||||
play!(14 => [
|
|
||||||
MidiMessage::NoteOff { key: 39.into(), vel: 100.into() },
|
|
||||||
MidiMessage::NoteOn { key: 42.into(), vel: 100.into() },
|
|
||||||
]),
|
|
||||||
]))),
|
|
||||||
Phrase::new("E E E E", ppq * 4, Some(BTreeMap::from([
|
|
||||||
play!(0 => [
|
|
||||||
MidiMessage::NoteOff { key: 36.into(), vel: 100.into() },
|
|
||||||
]),
|
|
||||||
play!(2 => [
|
|
||||||
MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
|
||||||
]),
|
|
||||||
play!(4 => [
|
|
||||||
MidiMessage::NoteOff { key: 36.into(), vel: 100.into() },
|
|
||||||
]),
|
|
||||||
play!(6 => [
|
|
||||||
MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
|
||||||
]),
|
|
||||||
play!(8 => [
|
|
||||||
MidiMessage::NoteOff { key: 36.into(), vel: 100.into() },
|
|
||||||
]),
|
|
||||||
play!(10 => [
|
|
||||||
MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
|
||||||
]),
|
|
||||||
play!(12 => [
|
|
||||||
MidiMessage::NoteOff { key: 36.into(), vel: 100.into() },
|
|
||||||
]),
|
|
||||||
play!(14 => [
|
|
||||||
MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
|
||||||
]),
|
|
||||||
])))
|
|
||||||
]))?,
|
|
||||||
|
|
||||||
//Plugin::lv2("Helm", "file:///home/user/.lv2/Helm.lv2", &[1, 0, 0, 2])?.boxed(),
|
//Track::new("Odin2", &timebase, Some(vec![
|
||||||
//Plugin::lv2("Kick/ChowKick", "file:///home/user/.lv2/ChowKick.lv2", &[1, 1, 0, 2])?.boxed(),
|
|
||||||
//Plugin::lv2("Bass/Helm", "file:///home/user/.lv2/Helm.lv2", &[1, 0, 0, 2])?.boxed(),
|
|
||||||
//Plugin::lv2("Pads/Odin2", "file:///home/user/.lv2/Odin2.lv2", &[1, 0, 0, 2])?.boxed(),
|
|
||||||
]),
|
|
||||||
Some(vec![
|
|
||||||
Scene::new(&"Scene 1", &[Some(0), None, None, None]),
|
|
||||||
Scene::new(&"Scene 2", &[Some(0), Some(0), None, None]),
|
|
||||||
Scene::new(&"Scene 3", &[Some(1), Some(1), None, None]),
|
|
||||||
Scene::new(&"Scene 4", &[Some(2), Some(2), None, None]),
|
|
||||||
//Scene::new(&"Scene#03", &[None, Some(0), None, None]),
|
|
||||||
//Scene::new(&"Scene#04", &[None, None, None, None]),
|
|
||||||
//Scene::new(&"Scene#05", &[None, None, None, None]),
|
|
||||||
])
|
|
||||||
|
|
||||||
)?
|
//Plugin::lv2("Odin2", "file:///home/user/.lv2/Odin2.lv2")?.boxed(),
|
||||||
.run(Some(init))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init (state: Arc<Mutex<DynamicDevice<Launcher>>>) -> Usually<()> {
|
//]), Some(vec![
|
||||||
let input = ".*nanoKEY.*";
|
//Phrase::new("E G A Bb", ppq * 4, Some(BTreeMap::from([
|
||||||
let output = ["Komplete.*:playback_FL", "Komplete.*:playback_FR"];
|
//play!(2 => [
|
||||||
let state = state.lock().unwrap();
|
//MidiMessage::NoteOff { key: 42.into(), vel: 100.into() },
|
||||||
state.connect(input, &output)?;
|
//MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
||||||
Ok(())
|
//]),
|
||||||
}
|
//play!(6 => [
|
||||||
|
//MidiMessage::NoteOff { key: 36.into(), vel: 100.into() },
|
||||||
|
//MidiMessage::NoteOn { key: 39.into(), vel: 100.into() },
|
||||||
|
//]),
|
||||||
|
//play!(10 => [
|
||||||
|
//MidiMessage::NoteOff { key: 39.into(), vel: 100.into() },
|
||||||
|
//MidiMessage::NoteOn { key: 41.into(), vel: 100.into() },
|
||||||
|
//]),
|
||||||
|
//play!(14 => [
|
||||||
|
//MidiMessage::NoteOff { key: 41.into(), vel: 100.into() },
|
||||||
|
//MidiMessage::NoteOn { key: 42.into(), vel: 100.into() },
|
||||||
|
//]),
|
||||||
|
//]))),
|
||||||
|
//Phrase::new("E E G Bb", ppq * 4, Some(BTreeMap::from([
|
||||||
|
//play!(2 => [
|
||||||
|
//MidiMessage::NoteOff { key: 42.into(), vel: 100.into() },
|
||||||
|
//MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
||||||
|
//]),
|
||||||
|
//play!(6 => [
|
||||||
|
//MidiMessage::NoteOff { key: 36.into(), vel: 100.into() },
|
||||||
|
//MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
||||||
|
//]),
|
||||||
|
//play!(10 => [
|
||||||
|
//MidiMessage::NoteOff { key: 36.into(), vel: 100.into() },
|
||||||
|
//MidiMessage::NoteOn { key: 39.into(), vel: 100.into() },
|
||||||
|
//]),
|
||||||
|
//play!(14 => [
|
||||||
|
//MidiMessage::NoteOff { key: 39.into(), vel: 100.into() },
|
||||||
|
//MidiMessage::NoteOn { key: 42.into(), vel: 100.into() },
|
||||||
|
//]),
|
||||||
|
//]))),
|
||||||
|
//Phrase::new("E E E E", ppq * 4, Some(BTreeMap::from([
|
||||||
|
//play!(0 => [
|
||||||
|
//MidiMessage::NoteOff { key: 36.into(), vel: 100.into() },
|
||||||
|
//]),
|
||||||
|
//play!(2 => [
|
||||||
|
//MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
||||||
|
//]),
|
||||||
|
//play!(4 => [
|
||||||
|
//MidiMessage::NoteOff { key: 36.into(), vel: 100.into() },
|
||||||
|
//]),
|
||||||
|
//play!(6 => [
|
||||||
|
//MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
||||||
|
//]),
|
||||||
|
//play!(8 => [
|
||||||
|
//MidiMessage::NoteOff { key: 36.into(), vel: 100.into() },
|
||||||
|
//]),
|
||||||
|
//play!(10 => [
|
||||||
|
//MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
||||||
|
//]),
|
||||||
|
//play!(12 => [
|
||||||
|
//MidiMessage::NoteOff { key: 36.into(), vel: 100.into() },
|
||||||
|
//]),
|
||||||
|
//play!(14 => [
|
||||||
|
//MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
|
||||||
|
//]),
|
||||||
|
//])))
|
||||||
|
//]))?,
|
||||||
|
|
||||||
|
////Plugin::lv2("Helm", "file:///home/user/.lv2/Helm.lv2", &[1, 0, 0, 2])?.boxed(),
|
||||||
|
////Plugin::lv2("Kick/ChowKick", "file:///home/user/.lv2/ChowKick.lv2", &[1, 1, 0, 2])?.boxed(),
|
||||||
|
////Plugin::lv2("Bass/Helm", "file:///home/user/.lv2/Helm.lv2", &[1, 0, 0, 2])?.boxed(),
|
||||||
|
////Plugin::lv2("Pads/Odin2", "file:///home/user/.lv2/Odin2.lv2", &[1, 0, 0, 2])?.boxed(),
|
||||||
|
//]),
|
||||||
|
//Some(vec![
|
||||||
|
//Scene::new(&"Scene 1", &[Some(0), None, None, None]),
|
||||||
|
//Scene::new(&"Scene 2", &[Some(0), Some(0), None, None]),
|
||||||
|
//Scene::new(&"Scene 3", &[Some(1), Some(1), None, None]),
|
||||||
|
//Scene::new(&"Scene 4", &[Some(2), Some(2), None, None]),
|
||||||
|
////Scene::new(&"Scene#03", &[None, Some(0), None, None]),
|
||||||
|
////Scene::new(&"Scene#04", &[None, None, None, None]),
|
||||||
|
////Scene::new(&"Scene#05", &[None, None, None, None]),
|
||||||
|
//])
|
||||||
|
|
||||||
|
//)?
|
||||||
|
//.run(Some(init))
|
||||||
|
//}
|
||||||
|
|
||||||
|
//fn init (state: Arc<Mutex<DynamicDevice<Launcher>>>) -> Usually<()> {
|
||||||
|
//let input = ".*nanoKEY.*";
|
||||||
|
//let output = ["Komplete.*:playback_FL", "Komplete.*:playback_FR"];
|
||||||
|
//let state = state.lock().unwrap();
|
||||||
|
//state.connect(input, &output)?;
|
||||||
|
//Ok(())
|
||||||
|
//}
|
||||||
|
|
|
||||||
20
src/model.rs
Normal file
20
src/model.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
pub mod chain;
|
||||||
|
pub mod launcher;
|
||||||
|
pub mod looper;
|
||||||
|
pub mod mixer;
|
||||||
|
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::launcher::{Launcher, LauncherMode};
|
||||||
|
pub use self::chain::Chain;
|
||||||
|
pub use self::sampler::{Sampler, Sample};
|
||||||
|
pub use self::mixer::Mixer;
|
||||||
|
pub use self::plugin::{Plugin, PluginKind, lv2::LV2Plugin};
|
||||||
46
src/model/chain.rs
Normal file
46
src/model/chain.rs
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
use crate::{core::*, view::*};
|
||||||
|
|
||||||
|
pub struct Chain {
|
||||||
|
pub name: String,
|
||||||
|
pub focused: bool,
|
||||||
|
pub focus: usize,
|
||||||
|
pub items: Vec<Box<dyn Device>>,
|
||||||
|
pub view: ChainViewMode,
|
||||||
|
pub adding: bool,
|
||||||
|
}
|
||||||
|
render!(Chain = crate::view::chain::render);
|
||||||
|
handle!(Chain = crate::control::chain::handle);
|
||||||
|
process!(Chain);
|
||||||
|
impl Chain {
|
||||||
|
pub fn new (name: &str, items: Option<Vec<Box<dyn Device>>>) -> Usually<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
name: name.into(),
|
||||||
|
focused: false,
|
||||||
|
focus: 0,
|
||||||
|
items: items.unwrap_or_else(||vec![]),
|
||||||
|
view: ChainViewMode::Column,
|
||||||
|
adding: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PortList for Chain {
|
||||||
|
fn midi_ins (&self) -> Usually<Vec<String>> {
|
||||||
|
if let Some(device) = self.items.get(0) {
|
||||||
|
device.midi_ins()
|
||||||
|
} else {
|
||||||
|
Ok(vec![])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn audio_outs (&self) -> Usually<Vec<String>> {
|
||||||
|
if let Some(device) = self.items.get(self.items.len().saturating_sub(1)) {
|
||||||
|
device.audio_outs()
|
||||||
|
} else {
|
||||||
|
Ok(vec![])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process (_: &mut Chain, _: &Client, _: &ProcessScope) -> Control {
|
||||||
|
Control::Continue
|
||||||
|
}
|
||||||
184
src/model/launcher.rs
Normal file
184
src/model/launcher.rs
Normal file
|
|
@ -0,0 +1,184 @@
|
||||||
|
use crate::{core::*, model::*};
|
||||||
|
pub enum LauncherMode {
|
||||||
|
Tracks,
|
||||||
|
Sequencer,
|
||||||
|
Chains
|
||||||
|
}
|
||||||
|
impl LauncherMode {
|
||||||
|
pub fn is_chains (&self) -> bool {
|
||||||
|
match self { Self::Chains => true, _ => false }
|
||||||
|
}
|
||||||
|
pub fn is_tracks (&self) -> bool {
|
||||||
|
match self { Self::Tracks => true, _ => false }
|
||||||
|
}
|
||||||
|
pub fn is_sequencer (&self) -> bool {
|
||||||
|
match self { Self::Sequencer => true, _ => false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub struct Launcher {
|
||||||
|
pub name: String,
|
||||||
|
pub timebase: Arc<Timebase>,
|
||||||
|
pub transport: ::jack::Transport,
|
||||||
|
pub playing: TransportState,
|
||||||
|
pub monitoring: bool,
|
||||||
|
pub recording: bool,
|
||||||
|
pub overdub: bool,
|
||||||
|
pub current_frame: usize,
|
||||||
|
pub cursor: (usize, usize),
|
||||||
|
pub tracks: Vec<Track>,
|
||||||
|
pub scenes: Vec<Scene>,
|
||||||
|
pub show_help: bool,
|
||||||
|
pub view: LauncherMode,
|
||||||
|
}
|
||||||
|
render!(Launcher = crate::view::launcher::render);
|
||||||
|
handle!(Launcher = crate::control::launcher::handle);
|
||||||
|
process!(Launcher |self, _client, _scope| {
|
||||||
|
let transport = self.transport.query().unwrap();
|
||||||
|
self.playing = transport.state;
|
||||||
|
self.current_frame = transport.pos.frame() as usize;
|
||||||
|
Control::Continue
|
||||||
|
});
|
||||||
|
impl PortList for Launcher {}
|
||||||
|
impl Launcher {
|
||||||
|
pub fn new (
|
||||||
|
name: &str,
|
||||||
|
timebase: &Arc<Timebase>,
|
||||||
|
tracks: Option<Vec<Track>>,
|
||||||
|
scenes: Option<Vec<Scene>>
|
||||||
|
) -> Result<Self, Box<dyn Error>> {
|
||||||
|
let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?;
|
||||||
|
let transport = client.transport();
|
||||||
|
let ppq = timebase.ppq() as usize;
|
||||||
|
Ok(Self {
|
||||||
|
name: name.into(),
|
||||||
|
view: LauncherMode::Chains,
|
||||||
|
playing: transport.query_state()?,
|
||||||
|
transport,
|
||||||
|
timebase: timebase.clone(),
|
||||||
|
monitoring: true,
|
||||||
|
recording: false,
|
||||||
|
overdub: true,
|
||||||
|
cursor: (2, 2),
|
||||||
|
current_frame: 0,
|
||||||
|
scenes: scenes.unwrap_or_else(||vec![Scene::new(&"Scene 1", &[None])]),
|
||||||
|
tracks: if let Some(tracks) = tracks { tracks } else { vec![
|
||||||
|
Track::new("Track 1", &timebase, None, Some(vec![
|
||||||
|
Phrase::new("MIDI Clip 1", ppq * 4, Some(BTreeMap::from([
|
||||||
|
( ppq * 0, vec![MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }] ),
|
||||||
|
( ppq * 1, vec![MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }] ),
|
||||||
|
( ppq * 2, vec![MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }] ),
|
||||||
|
( ppq * 3, vec![MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }] ),
|
||||||
|
])))
|
||||||
|
]))?,
|
||||||
|
] },
|
||||||
|
show_help: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn connect (&self, midi_in: &str, audio_outs: &[&str]) -> Usually<()> {
|
||||||
|
let (client, _status) = Client::new(
|
||||||
|
&format!("{}-init", &self.name), ClientOptions::NO_START_SERVER
|
||||||
|
)?;
|
||||||
|
let midi_ins = client.ports(Some(midi_in), None, PortFlags::IS_OUTPUT);
|
||||||
|
let audio_outs: Vec<Vec<String>> = audio_outs.iter()
|
||||||
|
.map(|pattern|client.ports(Some(pattern), None, PortFlags::IS_INPUT))
|
||||||
|
.collect();
|
||||||
|
for (i, sequencer) in self.tracks.iter().enumerate() {
|
||||||
|
for sequencer_midi_in in sequencer.midi_ins()?.iter() {
|
||||||
|
for midi_in in midi_ins.iter() {
|
||||||
|
client.connect_ports_by_name(&midi_in, &sequencer_midi_in)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let chain: &Chain = &self.tracks[i].chain;
|
||||||
|
for port in sequencer.midi_outs()?.iter() {
|
||||||
|
for midi_in in chain.midi_ins()?.iter() {
|
||||||
|
client.connect_ports_by_name(&port, &midi_in)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (j, port) in chain.audio_outs()?.iter().enumerate() {
|
||||||
|
for audio_out in audio_outs[j % audio_outs.len()].iter() {
|
||||||
|
client.connect_ports_by_name(&port, &audio_out)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn cols (&self) -> usize {
|
||||||
|
(self.tracks.len() + 2) as usize
|
||||||
|
}
|
||||||
|
pub fn col (&self) -> usize {
|
||||||
|
self.cursor.0 as usize
|
||||||
|
}
|
||||||
|
pub fn dec_col (&mut self) {
|
||||||
|
self.cursor.0 = if self.cursor.0 > 0 {
|
||||||
|
self.cursor.0 - 1
|
||||||
|
} else {
|
||||||
|
(self.cols() - 1) as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn inc_col (&mut self) {
|
||||||
|
self.cursor.0 = if self.cursor.0 >= self.cols() - 1 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
self.cursor.0 + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn rows (&self) -> usize {
|
||||||
|
(self.scenes.len() + 2) as usize
|
||||||
|
}
|
||||||
|
pub fn row (&self) -> usize {
|
||||||
|
self.cursor.1 as usize
|
||||||
|
}
|
||||||
|
pub fn dec_row (&mut self) {
|
||||||
|
self.cursor.1 = if self.cursor.1 > 0 {
|
||||||
|
self.cursor.1 - 1
|
||||||
|
} else {
|
||||||
|
self.rows() - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn inc_row (&mut self) {
|
||||||
|
self.cursor.1 = if self.cursor.1 >= self.rows() - 1 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
self.cursor.1 + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn track (&self) -> Option<(usize, &Track)> {
|
||||||
|
match self.col() { 0 => None, _ => {
|
||||||
|
let id = self.col() as usize - 1;
|
||||||
|
self.tracks.get(id).map(|t|(id, t))
|
||||||
|
} }
|
||||||
|
}
|
||||||
|
pub fn track_mut (&mut self) -> Option<(usize, &mut Track)> {
|
||||||
|
match self.col() { 0 => None, _ => {
|
||||||
|
let id = self.col() as usize - 1;
|
||||||
|
self.tracks.get_mut(id).map(|t|(id, t))
|
||||||
|
} }
|
||||||
|
}
|
||||||
|
pub fn scene (&self) -> Option<(usize, &Scene)> {
|
||||||
|
match self.row() { 0 => None, _ => {
|
||||||
|
let id = self.row() as usize - 1;
|
||||||
|
self.scenes.get(id).map(|t|(id, t))
|
||||||
|
} }
|
||||||
|
}
|
||||||
|
pub fn scene_mut (&mut self) -> Option<(usize, &mut Scene)> {
|
||||||
|
match self.row() { 0 => None, _ => {
|
||||||
|
let id = self.row() as usize - 1;
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
pub fn phrase_id (&self) -> Option<usize> {
|
||||||
|
let (track_id, _) = self.track()?;
|
||||||
|
let (_, scene) = self.scene()?;
|
||||||
|
*scene.clips.get(track_id)?
|
||||||
|
}
|
||||||
|
}
|
||||||
132
src/model/mixer.rs
Normal file
132
src/model/mixer.rs
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
use crate::core::*;
|
||||||
|
|
||||||
|
pub struct Mixer {
|
||||||
|
pub name: String,
|
||||||
|
pub tracks: Vec<MixerTrack>,
|
||||||
|
pub selected_track: usize,
|
||||||
|
pub selected_column: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mixer {
|
||||||
|
pub fn new (name: &str) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
|
||||||
|
let (client, _status) = Client::new(name, ClientOptions::NO_START_SERVER)?;
|
||||||
|
Ok(DynamicDevice::new(
|
||||||
|
crate::view::mixer::render,
|
||||||
|
crate::control::mixer::handle,
|
||||||
|
process,
|
||||||
|
Self {
|
||||||
|
name: name.into(),
|
||||||
|
selected_column: 0,
|
||||||
|
selected_track: 1,
|
||||||
|
tracks: vec![
|
||||||
|
MixerTrack::new(&client, 1, "Mono 1")?,
|
||||||
|
MixerTrack::new(&client, 1, "Mono 2")?,
|
||||||
|
MixerTrack::new(&client, 2, "Stereo 1")?,
|
||||||
|
MixerTrack::new(&client, 2, "Stereo 2")?,
|
||||||
|
MixerTrack::new(&client, 2, "Stereo 3")?,
|
||||||
|
MixerTrack::new(&client, 2, "Bus 1")?,
|
||||||
|
MixerTrack::new(&client, 2, "Bus 2")?,
|
||||||
|
MixerTrack::new(&client, 2, "Mix")?,
|
||||||
|
],
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process (
|
||||||
|
_: &mut Mixer,
|
||||||
|
_: &Client,
|
||||||
|
_: &ProcessScope
|
||||||
|
) -> Control {
|
||||||
|
Control::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MixerTrack {
|
||||||
|
pub name: String,
|
||||||
|
pub channels: u8,
|
||||||
|
pub input_ports: Vec<Port<AudioIn>>,
|
||||||
|
pub pre_gain_meter: f64,
|
||||||
|
pub gain: f64,
|
||||||
|
pub insert_ports: Vec<Port<AudioOut>>,
|
||||||
|
pub return_ports: Vec<Port<AudioIn>>,
|
||||||
|
pub post_gain_meter: f64,
|
||||||
|
pub post_insert_meter: f64,
|
||||||
|
pub level: f64,
|
||||||
|
pub pan: f64,
|
||||||
|
pub output_ports: Vec<Port<AudioOut>>,
|
||||||
|
pub post_fader_meter: f64,
|
||||||
|
pub route: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MixerTrack {
|
||||||
|
pub fn new (jack: &Client, channels: u8, name: &str) -> Result<Self, Box<dyn Error>> {
|
||||||
|
let mut input_ports = vec![];
|
||||||
|
let mut insert_ports = vec![];
|
||||||
|
let mut return_ports = vec![];
|
||||||
|
let mut output_ports = vec![];
|
||||||
|
for channel in 1..=channels {
|
||||||
|
input_ports.push(jack.register_port(&format!("{name} [input {channel}]"), AudioIn::default())?);
|
||||||
|
output_ports.push(jack.register_port(&format!("{name} [out {channel}]"), AudioOut::default())?);
|
||||||
|
let insert_port = jack.register_port(&format!("{name} [pre {channel}]"), AudioOut::default())?;
|
||||||
|
let return_port = jack.register_port(&format!("{name} [insert {channel}]"), AudioIn::default())?;
|
||||||
|
jack.connect_ports(&insert_port, &return_port)?;
|
||||||
|
insert_ports.push(insert_port);
|
||||||
|
return_ports.push(return_port);
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
name: name.into(),
|
||||||
|
channels,
|
||||||
|
input_ports,
|
||||||
|
pre_gain_meter: 0.0,
|
||||||
|
gain: 0.0,
|
||||||
|
post_gain_meter: 0.0,
|
||||||
|
insert_ports,
|
||||||
|
return_ports,
|
||||||
|
post_insert_meter: 0.0,
|
||||||
|
level: 0.0,
|
||||||
|
pan: 0.0,
|
||||||
|
post_fader_meter: 0.0,
|
||||||
|
route: "---".into(),
|
||||||
|
output_ports,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//impl<W: Write> Input<TUI<W>, bool> for Mixer {
|
||||||
|
//fn handle (&mut self, engine: &mut TUI<W>) -> Result<Option<bool>> {
|
||||||
|
//Ok(None)
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
|
||||||
|
//impl<W: Write> Output<TUI<W>, [u16;2]> for Mixer {
|
||||||
|
//fn render (&self, envine: &mut TUI<W>) -> Result<Option<[u16;2]>> {
|
||||||
|
|
||||||
|
//let tracks_table = Columns::new()
|
||||||
|
//.add(titles)
|
||||||
|
//.add(input_meters)
|
||||||
|
//.add(gains)
|
||||||
|
//.add(gain_meters)
|
||||||
|
//.add(pres)
|
||||||
|
//.add(pre_meters)
|
||||||
|
//.add(levels)
|
||||||
|
//.add(pans)
|
||||||
|
//.add(pan_meters)
|
||||||
|
//.add(posts)
|
||||||
|
//.add(routes)
|
||||||
|
|
||||||
|
//Rows::new()
|
||||||
|
//.add(Columns::new()
|
||||||
|
//.add(Rows::new()
|
||||||
|
//.add("[Arrows]".bold())
|
||||||
|
//.add("Navigate"))
|
||||||
|
//.add(Rows::new()
|
||||||
|
//.add("[+/-]".bold())
|
||||||
|
//.add("Adjust"))
|
||||||
|
//.add(Rows::new()
|
||||||
|
//.add("[Ins/Del]".bold())
|
||||||
|
//.add("Add/remove track")))
|
||||||
|
//.add(tracks_table)
|
||||||
|
//.render(engine)
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
|
|
||||||
pub type PhraseData = BTreeMap<usize, Vec<MidiMessage>>;
|
|
||||||
|
|
||||||
pub type MIDIMessage = Vec<u8>;
|
|
||||||
|
|
||||||
pub type MIDIChunk = [Option<Vec<MIDIMessage>>];
|
|
||||||
|
|
||||||
pub struct Phrase {
|
pub struct Phrase {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub length: usize,
|
pub length: usize,
|
||||||
pub notes: PhraseData,
|
pub notes: PhraseData,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Phrase {
|
||||||
|
fn default () -> Self {
|
||||||
|
Self::new("", 0, None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Phrase {
|
impl Phrase {
|
||||||
pub fn new (name: &str, length: usize, notes: Option<PhraseData>) -> Self {
|
pub fn new (name: &str, length: usize, notes: Option<PhraseData>) -> Self {
|
||||||
Self { name: name.to_string(), length, notes: notes.unwrap_or(BTreeMap::new()) }
|
Self { name: name.to_string(), length, notes: notes.unwrap_or(BTreeMap::new()) }
|
||||||
|
|
@ -127,9 +127,3 @@ impl Phrase {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Phrase {
|
|
||||||
fn default () -> Self {
|
|
||||||
Self::new("", 0, None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
176
src/model/plugin.rs
Normal file
176
src/model/plugin.rs
Normal file
|
|
@ -0,0 +1,176 @@
|
||||||
|
use crate::core::*;
|
||||||
|
|
||||||
|
pub mod lv2;
|
||||||
|
pub mod vst2;
|
||||||
|
pub mod vst3;
|
||||||
|
|
||||||
|
use self::lv2::*;
|
||||||
|
|
||||||
|
pub struct Plugin {
|
||||||
|
pub name: String,
|
||||||
|
pub path: Option<String>,
|
||||||
|
pub plugin: Option<PluginKind>,
|
||||||
|
pub selected: usize,
|
||||||
|
pub mapping: bool,
|
||||||
|
pub midi_ins: Vec<Port<MidiIn>>,
|
||||||
|
pub midi_outs: Vec<Port<MidiOut>>,
|
||||||
|
pub audio_ins: Vec<Port<AudioIn>>,
|
||||||
|
pub audio_outs: Vec<Port<AudioOut>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum PluginKind {
|
||||||
|
LV2(LV2Plugin),
|
||||||
|
VST2 {
|
||||||
|
instance: ::vst::host::PluginInstance
|
||||||
|
},
|
||||||
|
VST3,
|
||||||
|
}
|
||||||
|
|
||||||
|
const HELM: &'static str = "file:///nix/store/ij3sz7nqg5l7v2dygdvzy3w6cj62bd6r-helm-0.9.0/lib/lv2/helm.lv2";
|
||||||
|
|
||||||
|
impl Plugin {
|
||||||
|
/// Load a LV2 plugin.
|
||||||
|
pub fn lv2 (name: &str, path: &str) -> Usually<DynamicDevice<Self>> {
|
||||||
|
let host = Self::new(name)?;
|
||||||
|
let plugin = LV2Plugin::new(path)?;
|
||||||
|
let mut state = host.state();
|
||||||
|
let client = host.client.as_ref().unwrap().as_client();
|
||||||
|
let (midi_ins, midi_outs, audio_ins, audio_outs) = (
|
||||||
|
plugin.plugin.port_counts().atom_sequence_inputs,
|
||||||
|
plugin.plugin.port_counts().atom_sequence_outputs,
|
||||||
|
plugin.plugin.port_counts().audio_inputs,
|
||||||
|
plugin.plugin.port_counts().audio_outputs,
|
||||||
|
);
|
||||||
|
state.midi_ins = {
|
||||||
|
let mut ports = vec![];
|
||||||
|
for i in 0..midi_ins {
|
||||||
|
ports.push(client.register_port(&format!("midi-in-{i}"), MidiIn::default())?)
|
||||||
|
}
|
||||||
|
ports
|
||||||
|
};
|
||||||
|
state.midi_outs = {
|
||||||
|
let mut ports = vec![];
|
||||||
|
for i in 0..midi_outs {
|
||||||
|
ports.push(client.register_port(&format!("midi-out-{i}"), MidiOut::default())?)
|
||||||
|
}
|
||||||
|
ports
|
||||||
|
};
|
||||||
|
state.audio_ins = {
|
||||||
|
let mut ports = vec![];
|
||||||
|
for i in 0..audio_ins {
|
||||||
|
ports.push(client.register_port(&format!("audio-in-{i}"), AudioIn::default())?)
|
||||||
|
}
|
||||||
|
ports
|
||||||
|
};
|
||||||
|
state.audio_outs = {
|
||||||
|
let mut ports = vec![];
|
||||||
|
for i in 0..audio_outs {
|
||||||
|
ports.push(client.register_port(&format!("audio-out-{i}"), AudioOut::default())?)
|
||||||
|
}
|
||||||
|
ports
|
||||||
|
};
|
||||||
|
state.plugin = Some(PluginKind::LV2(plugin));
|
||||||
|
state.path = Some(String::from(path));
|
||||||
|
std::mem::drop(state);
|
||||||
|
Ok(host)
|
||||||
|
}
|
||||||
|
pub fn new (name: &str) -> Usually<DynamicDevice<Self>> {
|
||||||
|
DynamicDevice::new(
|
||||||
|
crate::view::plugin::render,
|
||||||
|
crate::control::plugin::handle,
|
||||||
|
Self::process,
|
||||||
|
Self {
|
||||||
|
name: name.into(),
|
||||||
|
path: None,
|
||||||
|
plugin: None,
|
||||||
|
selected: 0,
|
||||||
|
mapping: false,
|
||||||
|
midi_ins: vec![],
|
||||||
|
midi_outs: vec![],
|
||||||
|
audio_ins: vec![],
|
||||||
|
audio_outs: vec![],
|
||||||
|
}
|
||||||
|
).activate(Client::new(name, ClientOptions::NO_START_SERVER)?.0)
|
||||||
|
}
|
||||||
|
pub fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
||||||
|
match self.plugin.as_mut() {
|
||||||
|
Some(PluginKind::LV2(LV2Plugin { features, ref mut instance, .. })) => {
|
||||||
|
let urid = features.midi_urid();
|
||||||
|
let mut inputs = vec![];
|
||||||
|
for port in self.midi_ins.iter() {
|
||||||
|
let mut atom = ::livi::event::LV2AtomSequence::new(
|
||||||
|
&features,
|
||||||
|
scope.n_frames() as usize
|
||||||
|
);
|
||||||
|
for event in port.iter(scope) {
|
||||||
|
match event.bytes.len() {
|
||||||
|
3 => atom.push_midi_event::<3>(
|
||||||
|
event.time as i64,
|
||||||
|
urid,
|
||||||
|
&event.bytes[0..3]
|
||||||
|
).unwrap(),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inputs.push(atom);
|
||||||
|
}
|
||||||
|
let mut outputs = vec![];
|
||||||
|
for _ in self.midi_outs.iter() {
|
||||||
|
outputs.push(::livi::event::LV2AtomSequence::new(
|
||||||
|
&features,
|
||||||
|
scope.n_frames() as usize
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let ports = ::livi::EmptyPortConnections::new()
|
||||||
|
.with_atom_sequence_inputs(
|
||||||
|
inputs.iter()
|
||||||
|
)
|
||||||
|
.with_atom_sequence_outputs(
|
||||||
|
outputs.iter_mut()
|
||||||
|
)
|
||||||
|
.with_audio_inputs(
|
||||||
|
self.audio_ins.iter().map(|o|o.as_slice(scope))
|
||||||
|
)
|
||||||
|
.with_audio_outputs(
|
||||||
|
self.audio_outs.iter_mut().map(|o|o.as_mut_slice(scope))
|
||||||
|
);
|
||||||
|
unsafe {
|
||||||
|
instance.run(scope.n_frames() as usize, ports).unwrap()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
Control::Continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PortList for Plugin {
|
||||||
|
fn audio_ins (&self) -> Usually<Vec<String>> {
|
||||||
|
let mut ports = vec![];
|
||||||
|
for port in self.audio_ins.iter() {
|
||||||
|
ports.push(port.name()?);
|
||||||
|
}
|
||||||
|
Ok(ports)
|
||||||
|
}
|
||||||
|
fn audio_outs (&self) -> Usually<Vec<String>> {
|
||||||
|
let mut ports = vec![];
|
||||||
|
for port in self.audio_outs.iter() {
|
||||||
|
ports.push(port.name()?);
|
||||||
|
}
|
||||||
|
Ok(ports)
|
||||||
|
}
|
||||||
|
fn midi_ins (&self) -> Usually<Vec<String>> {
|
||||||
|
let mut ports = vec![];
|
||||||
|
for port in self.midi_ins.iter() {
|
||||||
|
ports.push(port.name()?);
|
||||||
|
}
|
||||||
|
Ok(ports)
|
||||||
|
}
|
||||||
|
fn midi_outs (&self) -> Usually<Vec<String>> {
|
||||||
|
let mut ports = vec![];
|
||||||
|
for port in self.midi_outs.iter() {
|
||||||
|
ports.push(port.name()?);
|
||||||
|
}
|
||||||
|
Ok(ports)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -39,3 +39,4 @@ impl LV2Plugin {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3,7 +3,7 @@ use super::*;
|
||||||
|
|
||||||
impl ::vst::host::Host for Plugin {}
|
impl ::vst::host::Host for Plugin {}
|
||||||
|
|
||||||
fn set_vst_plugin (host: &Arc<Mutex<Plugin>>, path: &str) -> Usually<PluginKind> {
|
fn set_vst_plugin (host: &Arc<Mutex<Plugin>>, _path: &str) -> Usually<PluginKind> {
|
||||||
let mut loader = ::vst::host::PluginLoader::load(
|
let mut loader = ::vst::host::PluginLoader::load(
|
||||||
&std::path::Path::new("/nix/store/ij3sz7nqg5l7v2dygdvzy3w6cj62bd6r-helm-0.9.0/lib/lxvst/helm.so"),
|
&std::path::Path::new("/nix/store/ij3sz7nqg5l7v2dygdvzy3w6cj62bd6r-helm-0.9.0/lib/lxvst/helm.so"),
|
||||||
host.clone()
|
host.clone()
|
||||||
|
|
@ -12,3 +12,4 @@ fn set_vst_plugin (host: &Arc<Mutex<Plugin>>, path: &str) -> Usually<PluginKind>
|
||||||
instance: loader.instance()?
|
instance: loader.instance()?
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
175
src/model/sampler.rs
Normal file
175
src/model/sampler.rs
Normal file
|
|
@ -0,0 +1,175 @@
|
||||||
|
use crate::core::*;
|
||||||
|
|
||||||
|
pub struct Voice {
|
||||||
|
pub sample: Arc<Sample>,
|
||||||
|
pub after: usize,
|
||||||
|
pub position: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Sample {
|
||||||
|
pub name: String,
|
||||||
|
pub start: usize,
|
||||||
|
pub end: usize,
|
||||||
|
pub channels: Vec<Vec<f32>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Sampler {
|
||||||
|
pub name: String,
|
||||||
|
pub cursor: (usize, usize),
|
||||||
|
pub samples: BTreeMap<u7, Arc<Sample>>,
|
||||||
|
pub voices: Vec<Voice>,
|
||||||
|
pub midi_in: Port<MidiIn>,
|
||||||
|
pub audio_ins: Vec<Port<AudioIn>>,
|
||||||
|
pub audio_outs: Vec<Port<AudioOut>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Voice {
|
||||||
|
pub fn chunk (&mut self, mut frames: usize) -> Option<Vec<Vec<f32>>> {
|
||||||
|
// Create output buffer for each channel
|
||||||
|
let mut chunk = vec![vec![];self.sample.channels.len()];
|
||||||
|
// If it's not time to play yet, count down
|
||||||
|
if self.after >= frames {
|
||||||
|
self.after = self.after - frames;
|
||||||
|
return Some(chunk)
|
||||||
|
}
|
||||||
|
// If the voice will start playing within the current buffer,
|
||||||
|
// subtract the remaining number of wait frames.
|
||||||
|
if self.after > 0 && self.after < frames {
|
||||||
|
chunk = vec![vec![0.0;self.after];self.sample.channels.len()];
|
||||||
|
frames = frames - self.after;
|
||||||
|
self.after = 0;
|
||||||
|
}
|
||||||
|
if self.position < self.sample.end {
|
||||||
|
let start = self.position.min(self.sample.end);
|
||||||
|
let end = (self.position + frames).min(self.sample.end);
|
||||||
|
for (i, channel) in self.sample.channels.iter().enumerate() {
|
||||||
|
chunk[i].extend_from_slice(&channel[start..end]);
|
||||||
|
};
|
||||||
|
self.position = self.position + frames;
|
||||||
|
Some(chunk)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sample {
|
||||||
|
pub fn new (name: &str, start: usize, end: usize, channels: Vec<Vec<f32>>) -> Arc<Self> {
|
||||||
|
Arc::new(Self { name: name.to_string(), start, end, channels })
|
||||||
|
}
|
||||||
|
pub fn play (self: &Arc<Self>, after: usize) -> Voice {
|
||||||
|
Voice { sample: self.clone(), after, position: self.start }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sampler {
|
||||||
|
pub fn new (
|
||||||
|
name: &str,
|
||||||
|
samples: Option<BTreeMap<u7, Arc<Sample>>>,
|
||||||
|
) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
|
||||||
|
let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?;
|
||||||
|
DynamicDevice::new(
|
||||||
|
crate::view::sampler::render,
|
||||||
|
crate::control::sampler::handle,
|
||||||
|
Self::process,
|
||||||
|
Self {
|
||||||
|
name: name.into(),
|
||||||
|
cursor: (0, 0),
|
||||||
|
samples: samples.unwrap_or(BTreeMap::new()),
|
||||||
|
voices: vec![],
|
||||||
|
midi_in: client.register_port("midi", ::jack::MidiIn::default())?,
|
||||||
|
audio_ins: vec![
|
||||||
|
client.register_port("recL", ::jack::AudioIn::default())?,
|
||||||
|
client.register_port("recR", ::jack::AudioIn::default())?,
|
||||||
|
],
|
||||||
|
audio_outs: vec![
|
||||||
|
client.register_port("outL", ::jack::AudioOut::default())?,
|
||||||
|
client.register_port("outR", ::jack::AudioOut::default())?,
|
||||||
|
],
|
||||||
|
}).activate(client)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
||||||
|
// Output buffer: this will be copied to the audio outs.
|
||||||
|
let channel_count = self.audio_outs.len();
|
||||||
|
let mut mixed = vec![vec![0.0;scope.n_frames() as usize];channel_count];
|
||||||
|
// Process MIDI input to add new voices.
|
||||||
|
for RawMidi { time, bytes } in self.midi_in.iter(scope) {
|
||||||
|
if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() {
|
||||||
|
if let MidiMessage::NoteOn { ref key, .. } = message {
|
||||||
|
if let Some(sample) = self.samples.get(key) {
|
||||||
|
self.voices.push(sample.play(time as usize));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Emit next chunk of each currently playing voice,
|
||||||
|
// dropping voices that have reached their ends.
|
||||||
|
let mut voices = vec![];
|
||||||
|
std::mem::swap(&mut voices, &mut self.voices);
|
||||||
|
loop {
|
||||||
|
if voices.len() < 1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
let mut voice = voices.swap_remove(0);
|
||||||
|
if let Some(chunk) = voice.chunk(scope.n_frames() as usize) {
|
||||||
|
for (i, channel) in chunk.iter().enumerate() {
|
||||||
|
let buffer = &mut mixed[i % channel_count];
|
||||||
|
for (i, sample) in channel.iter().enumerate() {
|
||||||
|
buffer[i] += sample;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.voices.push(voice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Write output buffer to output ports.
|
||||||
|
for (i, port) in self.audio_outs.iter_mut().enumerate() {
|
||||||
|
let buffer = &mixed[i];
|
||||||
|
for (i, value) in port.as_mut_slice(scope).iter_mut().enumerate() {
|
||||||
|
*value = *buffer.get(i).unwrap_or(&0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Control::Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_sample (&mut self, _path: &str) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PortList for Sampler {
|
||||||
|
fn midi_ins (&self) -> Usually<Vec<String>> {
|
||||||
|
Ok(vec![self.midi_in.name()?])
|
||||||
|
}
|
||||||
|
fn audio_ins (&self) -> Usually<Vec<String>> {
|
||||||
|
let mut ports = vec![];
|
||||||
|
for port in self.audio_ins.iter() {
|
||||||
|
ports.push(port.name()?);
|
||||||
|
}
|
||||||
|
Ok(ports)
|
||||||
|
}
|
||||||
|
fn audio_outs (&self) -> Usually<Vec<String>> {
|
||||||
|
let mut ports = vec![];
|
||||||
|
for port in self.audio_outs.iter() {
|
||||||
|
ports.push(port.name()?);
|
||||||
|
}
|
||||||
|
Ok(ports)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export] macro_rules! sample {
|
||||||
|
($note:expr, $name:expr, $src:expr) => {
|
||||||
|
{
|
||||||
|
let mut channels: Vec<wavers::Samples<f32>> = vec![];
|
||||||
|
for channel in wavers::Wav::from_path($src)?.channels() {
|
||||||
|
channels.push(channel);
|
||||||
|
}
|
||||||
|
let mut end = 0;
|
||||||
|
let mut data: Vec<Vec<f32>> = vec![];
|
||||||
|
for samples in channels.iter() {
|
||||||
|
let channel = Vec::from(samples.as_ref());
|
||||||
|
end = end.max(channel.len());
|
||||||
|
data.push(channel);
|
||||||
|
}
|
||||||
|
(u7::from_int_lossy($note).into(), Sample::new($name, 0, end, data).into())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
141
src/model/sequencer.rs
Normal file
141
src/model/sequencer.rs
Normal file
|
|
@ -0,0 +1,141 @@
|
||||||
|
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:?}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
use crate::device::*;
|
use crate::model::*;
|
||||||
use crate::layout::*;
|
|
||||||
pub struct Track {
|
pub struct Track {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub sequencer: DynamicDevice<Sequencer>,
|
pub sequencer: Sequencer,
|
||||||
pub chain: DynamicDevice<Chain>,
|
pub chain: Chain,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Track {
|
impl Track {
|
||||||
pub fn new (
|
pub fn new (
|
||||||
name: &str,
|
name: &str,
|
||||||
|
|
@ -15,10 +16,11 @@ impl Track {
|
||||||
) -> Usually<Self> {
|
) -> Usually<Self> {
|
||||||
let sequencer = Sequencer::new(&name, tempo, phrases)?;
|
let sequencer = Sequencer::new(&name, tempo, phrases)?;
|
||||||
let chain = Chain::new(&name, devices)?;
|
let chain = Chain::new(&name, devices)?;
|
||||||
|
let (client, _status) = Client::new("init", ClientOptions::NO_START_SERVER)?;
|
||||||
{
|
{
|
||||||
if let (Some(output), Some(input)) = (
|
if let (Some(output), Some(input)) = (
|
||||||
sequencer.midi_outs()?.get(0).clone(),
|
sequencer.midi_outs()?.get(0).clone(),
|
||||||
if let Some(item) = chain.state().items.get(0) {
|
if let Some(item) = chain.items.get(0) {
|
||||||
if let Some(port) = item.midi_ins()?.get(0) {
|
if let Some(port) = item.midi_ins()?.get(0) {
|
||||||
Some(port.clone())
|
Some(port.clone())
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -28,14 +30,13 @@ impl Track {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
if let Some(client) = &sequencer.client {
|
client.connect_ports_by_name(&output, &input)?;
|
||||||
client.as_client().connect_ports_by_name(&output, &input)?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Self { name: name.to_string(), sequencer, chain })
|
Ok(Self { name: name.to_string(), sequencer, chain })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PortList for Track {
|
impl PortList for Track {
|
||||||
fn midi_ins (&self) -> Usually<Vec<String>> {
|
fn midi_ins (&self) -> Usually<Vec<String>> {
|
||||||
self.sequencer.midi_ins()
|
self.sequencer.midi_ins()
|
||||||
|
|
@ -44,3 +45,4 @@ impl PortList for Track {
|
||||||
self.chain.audio_outs()
|
self.chain.audio_outs()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
15
src/view.rs
Normal file
15
src/view.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
pub mod chain;
|
||||||
|
pub mod grid;
|
||||||
|
pub mod layout;
|
||||||
|
pub mod launcher;
|
||||||
|
pub mod mixer;
|
||||||
|
pub mod sampler;
|
||||||
|
pub mod sequencer;
|
||||||
|
pub mod transport;
|
||||||
|
pub mod plugin;
|
||||||
|
|
||||||
|
pub use self::layout::*;
|
||||||
|
pub use self::transport::TransportView;
|
||||||
|
pub use self::grid::SceneGridView;
|
||||||
|
pub use self::chain::{ChainView, ChainViewMode};
|
||||||
|
pub use self::sequencer::SequencerView;
|
||||||
|
|
@ -1,58 +1,35 @@
|
||||||
mod plugin; pub use self::plugin::*;
|
|
||||||
mod sampler; pub use self::sampler::*;
|
|
||||||
|
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
use crate::layout::*;
|
use crate::view::*;
|
||||||
|
use crate::model::*;
|
||||||
|
|
||||||
pub struct Chain {
|
pub enum ChainViewMode {
|
||||||
pub name: String,
|
|
||||||
pub focused: bool,
|
|
||||||
pub focus: usize,
|
|
||||||
pub items: Vec<Box<dyn Device>>,
|
|
||||||
pub view: ChainView,
|
|
||||||
pub adding: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum ChainView {
|
|
||||||
Hidden,
|
Hidden,
|
||||||
Compact,
|
Compact,
|
||||||
Row,
|
Row,
|
||||||
Column,
|
Column,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Chain {
|
pub struct ChainView<'a> {
|
||||||
pub fn new (name: &str, items: Option<Vec<Box<dyn Device>>>) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
|
pub focused: bool,
|
||||||
Ok(DynamicDevice::new(render, handle, process, Self {
|
pub chain: Option<&'a Chain>,
|
||||||
name: name.into(),
|
|
||||||
focused: false,
|
|
||||||
focus: 0,
|
|
||||||
items: items.unwrap_or_else(||vec![]),
|
|
||||||
view: ChainView::Column,
|
|
||||||
adding: false
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PortList for Chain {
|
impl<'a> Render for ChainView<'a> {
|
||||||
fn midi_ins (&self) -> Usually<Vec<String>> {
|
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||||
if let Some(device) = self.items.get(0) {
|
let style = Some(Style::default().green().dim());
|
||||||
device.midi_ins()
|
if self.focused {
|
||||||
|
let Rect { x, y, width, height} = area;
|
||||||
|
lozenge_left(buf, x, y, height, style);
|
||||||
|
lozenge_right(buf, x + width - 1, y, height, style);
|
||||||
|
};
|
||||||
|
let (area, _plugins) = if let Some(ref chain) = self.chain {
|
||||||
|
draw_as_row(&*chain, buf, area, style)?
|
||||||
} else {
|
} else {
|
||||||
Ok(vec![])
|
(area, vec![])
|
||||||
|
};
|
||||||
|
Ok(area)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn audio_outs (&self) -> Usually<Vec<String>> {
|
|
||||||
if let Some(device) = self.items.get(self.items.len().saturating_sub(1)) {
|
|
||||||
device.audio_outs()
|
|
||||||
} else {
|
|
||||||
Ok(vec![])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn process (_: &mut Chain, _: &Client, _: &ProcessScope) -> Control {
|
|
||||||
Control::Continue
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render (state: &Chain, buf: &mut Buffer, area: Rect)
|
pub fn render (state: &Chain, buf: &mut Buffer, area: Rect)
|
||||||
-> Usually<Rect>
|
-> Usually<Rect>
|
||||||
|
|
@ -64,18 +41,18 @@ pub fn render (state: &Chain, buf: &mut Buffer, area: Rect)
|
||||||
Style::default().green().dim()
|
Style::default().green().dim()
|
||||||
});
|
});
|
||||||
let result = match state.view {
|
let result = match state.view {
|
||||||
ChainView::Hidden => Rect { x, y, width: 0, height: 0 },
|
ChainViewMode::Hidden => Rect { x, y, width: 0, height: 0 },
|
||||||
ChainView::Compact => {
|
ChainViewMode::Compact => {
|
||||||
let area = Rect { x, y, width: (state.name.len() + 4) as u16, height: 3 };
|
let area = Rect { x, y, width: (state.name.len() + 4) as u16, height: 3 };
|
||||||
buf.set_string(area.x + 2, area.y + 1, &state.name, Style::default());
|
buf.set_string(area.x + 2, area.y + 1, &state.name, Style::default());
|
||||||
draw_box_styled(buf, area, selected)
|
draw_box_styled(buf, area, selected)
|
||||||
},
|
},
|
||||||
ChainView::Row => {
|
ChainViewMode::Row => {
|
||||||
draw_box_styled(buf, area, selected);
|
draw_box_styled(buf, area, selected);
|
||||||
let (area, _) = Row::draw(buf, area, &state.items, 0)?;
|
let (area, _) = Row::draw(buf, area, &state.items, 0)?;
|
||||||
area
|
area
|
||||||
},
|
},
|
||||||
ChainView::Column => {
|
ChainViewMode::Column => {
|
||||||
draw_as_column(state, buf, area, selected)?
|
draw_as_column(state, buf, area, selected)?
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -89,8 +66,8 @@ pub fn draw_as_row (
|
||||||
let mut h = 0u16;
|
let mut h = 0u16;
|
||||||
let mut frames = vec![];
|
let mut frames = vec![];
|
||||||
for (i, device) in state.items.iter().enumerate() {
|
for (i, device) in state.items.iter().enumerate() {
|
||||||
let mut x2 = 0u16;
|
let x2 = 0u16;
|
||||||
let mut y2 = 1u16;
|
let _y2 = 1u16;
|
||||||
//for port in device.midi_ins()?.iter() {
|
//for port in device.midi_ins()?.iter() {
|
||||||
//port.blit(buf, x, y + y2, Some(Style::default()));
|
//port.blit(buf, x, y + y2, Some(Style::default()));
|
||||||
//x2 = x2.max(port.len() as u16);
|
//x2 = x2.max(port.len() as u16);
|
||||||
|
|
@ -129,7 +106,7 @@ pub fn draw_as_row (
|
||||||
h = h.max(frame.height);
|
h = h.max(frame.height);
|
||||||
x = x + frame.width;
|
x = x + frame.width;
|
||||||
}
|
}
|
||||||
Ok((area, frames))
|
Ok((Rect { x: area.x, y: area.y, width, height: h }, frames))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_as_column (
|
pub fn draw_as_column (
|
||||||
|
|
@ -215,73 +192,3 @@ pub fn draw_as_column (
|
||||||
draw_box_styled(buf, frames[state.focus], selected);
|
draw_box_styled(buf, frames[state.focus], selected);
|
||||||
Ok(area)
|
Ok(area)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Focus for Chain {
|
|
||||||
fn unfocus (&mut self) {
|
|
||||||
self.focused = false
|
|
||||||
}
|
|
||||||
fn focused (&self) -> Option<&Box<dyn Device>> {
|
|
||||||
match self.focused {
|
|
||||||
true => self.items.get(self.focus),
|
|
||||||
false => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn focused_mut (&mut self) -> Option<&mut Box<dyn Device>> {
|
|
||||||
match self.focused {
|
|
||||||
true => self.items.get_mut(self.focus),
|
|
||||||
false => None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn handle_focus (&mut self, event: &FocusEvent) -> Usually<bool> {
|
|
||||||
Ok(match event {
|
|
||||||
FocusEvent::Backward => {
|
|
||||||
if self.focus == 0 {
|
|
||||||
self.focus = self.items.len();
|
|
||||||
}
|
|
||||||
self.focus = self.focus - 1;
|
|
||||||
true
|
|
||||||
},
|
|
||||||
FocusEvent::Forward => {
|
|
||||||
self.focus = self.focus + 1;
|
|
||||||
if self.focus >= self.items.len() {
|
|
||||||
self.focus = 0;
|
|
||||||
}
|
|
||||||
true
|
|
||||||
},
|
|
||||||
FocusEvent::Inward => {
|
|
||||||
self.focused = true;
|
|
||||||
self.items[self.focus].handle(&AppEvent::Focus)?;
|
|
||||||
true
|
|
||||||
},
|
|
||||||
FocusEvent::Outward => {
|
|
||||||
if self.focused {
|
|
||||||
self.focused = false;
|
|
||||||
self.items[self.focus].handle(&AppEvent::Blur)?;
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle (state: &mut Chain, event: &AppEvent) -> Usually<bool> {
|
|
||||||
Ok(handle_focus(state, event, keymap!(Chain {
|
|
||||||
[Up, NONE, "focus_up", "focus row above",
|
|
||||||
|s: &mut Chain|s.handle_focus(&FocusEvent::Backward)],
|
|
||||||
[Down, NONE, "focus_down", "focus row below",
|
|
||||||
|s: &mut Chain|s.handle_focus(&FocusEvent::Forward)],
|
|
||||||
[Enter, NONE, "focus_down", "focus row below",
|
|
||||||
|s: &mut Chain|s.handle_focus(&FocusEvent::Inward)],
|
|
||||||
[Esc, NONE, "focus_down", "focus row below",
|
|
||||||
|s: &mut Chain|s.handle_focus(&FocusEvent::Outward)],
|
|
||||||
}))? || handle_keymap(state, event, keymap!(Chain {
|
|
||||||
[Char('a'), NONE, "add_device", "add a device", add_device]
|
|
||||||
}))?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_device (state: &mut Chain) -> Usually<bool> {
|
|
||||||
state.adding = true;
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
use super::*;
|
use crate::model::*;
|
||||||
pub struct SceneGrid<'a> {
|
use crate::view::*;
|
||||||
|
pub struct SceneGridView<'a> {
|
||||||
pub buf: &'a mut Buffer,
|
pub buf: &'a mut Buffer,
|
||||||
pub area: Rect,
|
pub area: Rect,
|
||||||
pub name: &'a str,
|
pub name: &'a str,
|
||||||
|
|
@ -9,7 +10,7 @@ pub struct SceneGrid<'a> {
|
||||||
pub tracks: &'a[Track],
|
pub tracks: &'a[Track],
|
||||||
pub cursor: &'a(usize, usize),
|
pub cursor: &'a(usize, usize),
|
||||||
}
|
}
|
||||||
impl<'a> SceneGrid<'a> {
|
impl<'a> SceneGridView<'a> {
|
||||||
pub fn new (
|
pub fn new (
|
||||||
buf: &'a mut Buffer,
|
buf: &'a mut Buffer,
|
||||||
area: Rect,
|
area: Rect,
|
||||||
|
|
@ -123,9 +124,7 @@ impl<'a> SceneGrid<'a> {
|
||||||
let clip = scene.clips.get(track);
|
let clip = scene.clips.get(track);
|
||||||
let index = index as u16;
|
let index = index as u16;
|
||||||
let label = if let Some(Some(clip)) = clip {
|
let label = if let Some(Some(clip)) = clip {
|
||||||
let track = self.tracks[track].sequencer.state();
|
if let Some(phrase) = self.tracks[track].sequencer.phrases.get(*clip) {
|
||||||
let phrase = track.phrases.get(*clip);
|
|
||||||
if let Some(phrase) = phrase {
|
|
||||||
format!("⯈{}", phrase.name)
|
format!("⯈{}", phrase.name)
|
||||||
} else {
|
} else {
|
||||||
format!("????")
|
format!("????")
|
||||||
50
src/view/launcher.rs
Normal file
50
src/view/launcher.rs
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
use crate::{core::*, model::*, view::*};
|
||||||
|
|
||||||
|
pub fn render (state: &Launcher, buf: &mut Buffer, mut area: Rect) -> Usually<Rect> {
|
||||||
|
//area.width = 80; // DOS mode
|
||||||
|
//area.height = 25;
|
||||||
|
let Rect { x, mut y, width, height } = area;
|
||||||
|
|
||||||
|
y = y + TransportView {
|
||||||
|
timebase: &state.timebase,
|
||||||
|
playing: state.playing,
|
||||||
|
record: state.sequencer().map(|s|s.recording).unwrap_or(false),
|
||||||
|
overdub: state.sequencer().map(|s|s.overdub).unwrap_or(false),
|
||||||
|
monitor: state.sequencer().map(|s|s.monitoring).unwrap_or(false),
|
||||||
|
frame: state.current_frame
|
||||||
|
}.render(buf, area)?.height;
|
||||||
|
|
||||||
|
y = y + SceneGridView {
|
||||||
|
buf,
|
||||||
|
area: Rect { x, y, width, height },
|
||||||
|
name: &state.name,
|
||||||
|
focused: state.view.is_tracks(),
|
||||||
|
scenes: &state.scenes,
|
||||||
|
tracks: &state.tracks,
|
||||||
|
cursor: &state.cursor
|
||||||
|
}.draw()?.height;
|
||||||
|
|
||||||
|
if let Some(chain) = state.chain() {
|
||||||
|
y = y + ChainView {
|
||||||
|
focused: state.view.is_chains(),
|
||||||
|
chain: Some(&*chain),
|
||||||
|
}.render(buf, Rect { x, y, width, height: height/3 })?.height
|
||||||
|
}
|
||||||
|
|
||||||
|
let track = state.track().map(|t|t.1);
|
||||||
|
y = y + SequencerView {
|
||||||
|
focused: state.view.is_sequencer(),
|
||||||
|
ppq: state.timebase.ppq() as usize,
|
||||||
|
track: track,
|
||||||
|
phrase: track.unwrap().sequencer.phrases.get(state.phrase_id().unwrap())
|
||||||
|
}.render(buf, Rect { x, y, width, height: height - y })?.height;
|
||||||
|
|
||||||
|
area.height = y;
|
||||||
|
if state.show_help {
|
||||||
|
let style = Some(Style::default().bold().white().not_dim().on_black().italic());
|
||||||
|
let hide = "[Tab] Mode [Arrows] Move [.,] Value [F1] Toggle help ";
|
||||||
|
hide.blit(buf, x + (width - hide.len() as u16) / 2, height - 1, style);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(area)
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
|
|
||||||
pub struct Stack<'a>(pub &'a[Box<dyn Render>]);
|
pub struct Stack<'a>(pub &'a[Box<dyn Render + Sync>]);
|
||||||
|
|
||||||
impl<'a> Render for Stack<'a> {
|
impl<'a> Render for Stack<'a> {
|
||||||
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||||
0
src/view/layout/focus.rs
Normal file
0
src/view/layout/focus.rs
Normal file
68
src/view/mixer.rs
Normal file
68
src/view/mixer.rs
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
use crate::core::*;
|
||||||
|
use crate::view::*;
|
||||||
|
use crate::model::*;
|
||||||
|
|
||||||
|
pub fn render (state: &Mixer, buf: &mut Buffer, mut area: Rect)
|
||||||
|
-> Usually<Rect>
|
||||||
|
{
|
||||||
|
if area.height < 2 {
|
||||||
|
return Ok(area)
|
||||||
|
}
|
||||||
|
area.x = area.width.saturating_sub(80) / 2;
|
||||||
|
area.width = area.width.min(80);
|
||||||
|
area.height = state.tracks.len() as u16 + 2;
|
||||||
|
draw_box(buf, area);
|
||||||
|
let x = area.x + 1;
|
||||||
|
let y = area.y + 1;
|
||||||
|
let _h = area.height - 2;
|
||||||
|
for (i, track) in state.tracks.iter().enumerate() {
|
||||||
|
//buf.set_string(
|
||||||
|
//x, y + index as u16,
|
||||||
|
//&track.name, Style::default().bold().not_dim()
|
||||||
|
//);
|
||||||
|
for (j, (column, field)) in [
|
||||||
|
(0, format!(" {:10} ", track.name)),
|
||||||
|
(12, format!(" {:.1}dB ", track.gain)),
|
||||||
|
(22, format!(" [ ] ")),
|
||||||
|
(30, format!(" C ")),
|
||||||
|
(35, format!(" {:.1}dB ", track.level)),
|
||||||
|
(45, format!(" [ ] ")),
|
||||||
|
(51, format!(" {:7} ", track.route)),
|
||||||
|
].into_iter().enumerate() {
|
||||||
|
buf.set_string(
|
||||||
|
x + column as u16,
|
||||||
|
y + i as u16,
|
||||||
|
field,
|
||||||
|
if state.selected_track == i && state.selected_column == j {
|
||||||
|
Style::default().white().bold().not_dim()
|
||||||
|
} else {
|
||||||
|
Style::default().not_dim()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
//stdout.queue(move_to(column, row))?;
|
||||||
|
//if state.selected_track == i && state.selected_column == j {
|
||||||
|
//stdout.queue(PrintStyledContent(field.to_string().bold().reverse()))?;
|
||||||
|
//} else {
|
||||||
|
//stdout.queue(PrintStyledContent(field.to_string().bold()))?;
|
||||||
|
//}
|
||||||
|
//fn render_meters (
|
||||||
|
//state: &mut Mixer,
|
||||||
|
//stdout: &mut Stdout,
|
||||||
|
//offset: Rect
|
||||||
|
//) -> Result<(), Box<dyn Error>> {
|
||||||
|
//let move_to = |col, row| crossterm::cursor::MoveTo(offset.0 + col, offset.1 + row);
|
||||||
|
//for (i, track) in state.tracks.iter().enumerate() {
|
||||||
|
//let row = (i + 1) as u16;
|
||||||
|
//stdout
|
||||||
|
//.queue(move_to(10, row))?.queue(PrintStyledContent("▁".green()))?
|
||||||
|
//.queue(move_to(20, row))?.queue(PrintStyledContent("▁".green()))?
|
||||||
|
//.queue(move_to(28, row))?.queue(PrintStyledContent("▁".green()))?
|
||||||
|
//.queue(move_to(43, row))?.queue(PrintStyledContent("▁".green()))?;
|
||||||
|
//}
|
||||||
|
//Ok(())
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(area)
|
||||||
|
}
|
||||||
|
|
||||||
48
src/view/plugin.rs
Normal file
48
src/view/plugin.rs
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
use crate::{core::*, model::*};
|
||||||
|
|
||||||
|
pub fn render (state: &Plugin, buf: &mut Buffer, area: Rect)
|
||||||
|
-> Usually<Rect>
|
||||||
|
{
|
||||||
|
let Rect { x, y, height, .. } = area;
|
||||||
|
let mut width = 20u16;
|
||||||
|
match &state.plugin {
|
||||||
|
Some(PluginKind::LV2(LV2Plugin { port_list, instance, .. })) => {
|
||||||
|
let start = state.selected.saturating_sub((height as usize / 2).saturating_sub(1));
|
||||||
|
let end = start + height as usize - 2;
|
||||||
|
//draw_box(buf, Rect { x, y, width, height });
|
||||||
|
for i in start..end {
|
||||||
|
if let Some(port) = port_list.get(i) {
|
||||||
|
let value = if let Some(value) = instance.control_input(port.index) {
|
||||||
|
value
|
||||||
|
} else {
|
||||||
|
port.default_value
|
||||||
|
};
|
||||||
|
//let label = &format!("C·· M·· {:25} = {value:.03}", port.name);
|
||||||
|
let label = &format!("{:25} = {value:.03}", port.name);
|
||||||
|
width = width.max(label.len() as u16 + 4);
|
||||||
|
label.blit(buf, x + 2, y + 1 + i as u16 - start as u16, if i == state.selected {
|
||||||
|
Some(Style::default().green())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
draw_header(state, buf, area.x, area.y, width)?;
|
||||||
|
Ok(Rect { width, ..area })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_header (state: &Plugin, buf: &mut Buffer, x: u16, y: u16, w: u16) -> Usually<Rect> {
|
||||||
|
let style = Style::default().gray();
|
||||||
|
let label1 = format!(" {}", state.name);
|
||||||
|
label1.blit(buf, x + 1, y, Some(style.white().bold()));
|
||||||
|
if let Some(ref path) = state.path {
|
||||||
|
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()));
|
||||||
|
}
|
||||||
|
Ok(Rect { x, y, width: w, height: 1 })
|
||||||
|
}
|
||||||
78
src/view/sampler.rs
Normal file
78
src/view/sampler.rs
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
use crate::core::*;
|
||||||
|
use crate::model::*;
|
||||||
|
|
||||||
|
pub fn render (state: &Sampler, buf: &mut Buffer, Rect { x, y, height, .. }: Rect)
|
||||||
|
-> Usually<Rect>
|
||||||
|
{
|
||||||
|
let style = Style::default().gray();
|
||||||
|
let title = format!(" {} ({})", state.name, state.voices.len());
|
||||||
|
title.blit(buf, x+1, y, Some(style.white().bold().not_dim()));
|
||||||
|
let mut width = title.len() + 2;
|
||||||
|
for (i, (note, sample)) in state.samples.iter().enumerate() {
|
||||||
|
let style = if i == state.cursor.0 {
|
||||||
|
Style::default().green()
|
||||||
|
} else {
|
||||||
|
Style::default()
|
||||||
|
};
|
||||||
|
let i = i as u16;
|
||||||
|
let y1 = y+1+i;
|
||||||
|
if y1 >= y + height {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if i as usize == state.cursor.0 {
|
||||||
|
"⯈".blit(buf, x+1, y1, Some(style.bold()));
|
||||||
|
}
|
||||||
|
let label1 = format!("{note:3} {:8}", sample.name);
|
||||||
|
let label2 = format!("{:>6} {:>6}", sample.start, sample.end);
|
||||||
|
label1.blit(buf, x+2, y1, Some(style.bold()));
|
||||||
|
label2.blit(buf, x+3+label1.len()as u16, y1, Some(style));
|
||||||
|
width = width.max(label1.len() + label2.len() + 4);
|
||||||
|
}
|
||||||
|
let height = ((1 + state.samples.len()) as u16).min(height);
|
||||||
|
Ok(Rect { x, y, width: width as u16, height })
|
||||||
|
}
|
||||||
|
|
||||||
|
//fn render_table (
|
||||||
|
//state: &mut Sampler,
|
||||||
|
//stdout: &mut Stdout,
|
||||||
|
//offset: (u16, u16),
|
||||||
|
//) -> Result<(), Box<dyn Error>> {
|
||||||
|
//let move_to = |col, row| crossterm::cursor::MoveTo(offset.0 + col, offset.1 + row);
|
||||||
|
//stdout.queue(move_to(0, 3))?.queue(
|
||||||
|
//Print(" Name Rate Trigger Route")
|
||||||
|
//)?;
|
||||||
|
//for (i, sample) in state.samples.lock().unwrap().iter().enumerate() {
|
||||||
|
//let row = 4 + i as u16;
|
||||||
|
//for (j, (column, field)) in [
|
||||||
|
//(0, format!(" {:7} ", sample.name)),
|
||||||
|
//(9, format!(" {:.1}Hz ", sample.rate)),
|
||||||
|
//(18, format!(" MIDI C{} {} ", sample.trigger.0, sample.trigger.1)),
|
||||||
|
//(33, format!(" {:.1}dB -> Output ", sample.gain)),
|
||||||
|
//(50, format!(" {} ", sample.playing.unwrap_or(0))),
|
||||||
|
//].into_iter().enumerate() {
|
||||||
|
//stdout.queue(move_to(column, row))?;
|
||||||
|
//if state.selected_sample == i && state.selected_column == j {
|
||||||
|
//stdout.queue(PrintStyledContent(field.to_string().bold().reverse()))?;
|
||||||
|
//} else {
|
||||||
|
//stdout.queue(PrintStyledContent(field.to_string().bold()))?;
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//Ok(())
|
||||||
|
//}
|
||||||
|
|
||||||
|
//fn render_meters (
|
||||||
|
//state: &mut Sampler,
|
||||||
|
//stdout: &mut Stdout,
|
||||||
|
//offset: (u16, u16),
|
||||||
|
//) -> Result<(), Box<dyn Error>> {
|
||||||
|
//let move_to = |col, row| crossterm::cursor::MoveTo(offset.0 + col, offset.1 + row);
|
||||||
|
//for (i, sample) in state.samples.lock().iter().enumerate() {
|
||||||
|
//let row = 4 + i as u16;
|
||||||
|
//stdout.queue(move_to(32, row))?.queue(
|
||||||
|
//PrintStyledContent("▁".green())
|
||||||
|
//)?;
|
||||||
|
//}
|
||||||
|
//Ok(())
|
||||||
|
//}
|
||||||
|
|
||||||
104
src/view/sequencer.rs
Normal file
104
src/view/sequencer.rs
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
use crate::{core::*,model::*,view::*};
|
||||||
|
|
||||||
|
pub mod horizontal;
|
||||||
|
pub mod vertical;
|
||||||
|
|
||||||
|
pub struct SequencerView<'a> {
|
||||||
|
pub focused: bool,
|
||||||
|
pub phrase: Option<&'a Phrase>,
|
||||||
|
pub track: Option<&'a Track>,
|
||||||
|
pub ppq: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Render for SequencerView<'a> {
|
||||||
|
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||||
|
let Rect { x, y, width, height } = area;
|
||||||
|
let style = Some(Style::default().green().dim());
|
||||||
|
if self.focused {
|
||||||
|
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()
|
||||||
|
})
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
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 })
|
||||||
|
}
|
||||||
|
|
@ -45,12 +45,7 @@ pub fn draw (
|
||||||
Ok(area)
|
Ok(area)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn timer (
|
pub fn timer (buf: &mut Buffer, area: Rect, time0: usize, now: usize) {
|
||||||
buf: &mut Buffer,
|
|
||||||
area: Rect,
|
|
||||||
time0: usize,
|
|
||||||
now: usize
|
|
||||||
) {
|
|
||||||
let x = area.x + 5;
|
let x = area.x + 5;
|
||||||
for step in time0..(time0+area.width as usize).saturating_sub(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 {
|
buf.set_string(x + step as u16, area.y, &"-", if step == now {
|
||||||
|
|
@ -61,12 +56,9 @@ pub fn timer (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn keys (
|
pub fn keys (buf: &mut Buffer, area: Rect, note0: usize, _notes: &[bool])
|
||||||
buf: &mut Buffer,
|
-> Usually<Rect>
|
||||||
area: Rect,
|
{
|
||||||
note0: usize,
|
|
||||||
notes: &[bool],
|
|
||||||
) -> Usually<Rect> {
|
|
||||||
let bw = Style::default().dim();
|
let bw = Style::default().dim();
|
||||||
let Rect { x, y, width, height } = area;
|
let Rect { x, y, width, height } = area;
|
||||||
let h = height.saturating_sub(2);
|
let h = height.saturating_sub(2);
|
||||||
|
|
@ -98,13 +90,13 @@ pub fn lanes (
|
||||||
phrase: &Phrase,
|
phrase: &Phrase,
|
||||||
ppq: usize,
|
ppq: usize,
|
||||||
time_z: usize,
|
time_z: usize,
|
||||||
time0: usize,
|
_time0: usize,
|
||||||
note0: usize,
|
note0: usize,
|
||||||
) {
|
) {
|
||||||
let Rect { x, y, width, height } = area;
|
let Rect { x, y, width, height } = area;
|
||||||
let time0 = time0 / time_z;
|
//let time0 = time0 / time_z;
|
||||||
let time1 = time0 + width as usize;
|
//let time1 = time0 + width as usize;
|
||||||
let note1 = note0 + height as usize;
|
//let note1 = note0 + height as usize;
|
||||||
let bg = Style::default();
|
let bg = Style::default();
|
||||||
let (bw, wh) = (bg.dim(), bg.white());
|
let (bw, wh) = (bg.dim(), bg.white());
|
||||||
let offset = 5;
|
let offset = 5;
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
use crate::core::*;
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub fn draw (
|
pub fn draw (
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use crate::core::*;
|
use crate::core::*;
|
||||||
use crate::layout::*;
|
|
||||||
|
|
||||||
pub struct Transport<'a> {
|
pub struct TransportView<'a> {
|
||||||
pub timebase: &'a Arc<Timebase>,
|
pub timebase: &'a Arc<Timebase>,
|
||||||
pub playing: TransportState,
|
pub playing: TransportState,
|
||||||
pub record: bool,
|
pub record: bool,
|
||||||
|
|
@ -10,7 +9,7 @@ pub struct Transport<'a> {
|
||||||
pub frame: usize,
|
pub frame: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Render for Transport<'a> {
|
impl<'a> Render for TransportView<'a> {
|
||||||
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||||
let Rect { x, y, width, .. } = area;
|
let Rect { x, y, width, .. } = area;
|
||||||
draw_play_stop(buf, x + 1, y, &self.playing);
|
draw_play_stop(buf, x + 1, y, &self.playing);
|
||||||
|
|
@ -40,6 +39,7 @@ pub fn draw_timer (buf: &mut Buffer, x: u16, y: u16, timebase: &Arc<Timebase>, f
|
||||||
);
|
);
|
||||||
timer.blit(buf, x - timer.len() as u16, y, Some(Style::default().not_dim()));
|
timer.blit(buf, x - timer.len() as u16, y, Some(Style::default().not_dim()));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_play_stop (buf: &mut Buffer, x: u16, y: u16, state: &TransportState) {
|
pub fn draw_play_stop (buf: &mut Buffer, x: u16, y: u16, state: &TransportState) {
|
||||||
let style = Style::default().gray();
|
let style = Style::default().gray();
|
||||||
match state {
|
match state {
|
||||||
|
|
@ -52,6 +52,7 @@ pub fn draw_play_stop (buf: &mut Buffer, x: u16, y: u16, state: &TransportState)
|
||||||
TransportState::Rolling => style.not_dim().white().bold()
|
TransportState::Rolling => style.not_dim().white().bold()
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_rec (buf: &mut Buffer, x: u16, y: u16, on: bool) {
|
pub fn draw_rec (buf: &mut Buffer, x: u16, y: u16, on: bool) {
|
||||||
"⏺ REC".blit(buf, x, y, Some(if on {
|
"⏺ REC".blit(buf, x, y, Some(if on {
|
||||||
Style::default().bold().red()
|
Style::default().bold().red()
|
||||||
|
|
@ -59,6 +60,7 @@ pub fn draw_rec (buf: &mut Buffer, x: u16, y: u16, on: bool) {
|
||||||
Style::default().bold().dim()
|
Style::default().bold().dim()
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_dub (buf: &mut Buffer, x: u16, y: u16, on: bool) {
|
pub fn draw_dub (buf: &mut Buffer, x: u16, y: u16, on: bool) {
|
||||||
"⏺ DUB".blit(buf, x, y, Some(if on {
|
"⏺ DUB".blit(buf, x, y, Some(if on {
|
||||||
Style::default().bold().yellow()
|
Style::default().bold().yellow()
|
||||||
|
|
@ -66,6 +68,7 @@ pub fn draw_dub (buf: &mut Buffer, x: u16, y: u16, on: bool) {
|
||||||
Style::default().bold().dim()
|
Style::default().bold().dim()
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_mon (buf: &mut Buffer, x: u16, y: u16, on: bool) {
|
pub fn draw_mon (buf: &mut Buffer, x: u16, y: u16, on: bool) {
|
||||||
"⏺ MON".blit(buf, x, y, Some(if on {
|
"⏺ MON".blit(buf, x, y, Some(if on {
|
||||||
Style::default().bold().green()
|
Style::default().bold().green()
|
||||||
|
|
@ -73,6 +76,7 @@ pub fn draw_mon (buf: &mut Buffer, x: u16, y: u16, on: bool) {
|
||||||
Style::default().bold().dim()
|
Style::default().bold().dim()
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_bpm (buf: &mut Buffer, x: u16, y: u16, bpm: usize) {
|
pub fn draw_bpm (buf: &mut Buffer, x: u16, y: u16, bpm: usize) {
|
||||||
let style = Style::default().not_dim();
|
let style = Style::default().not_dim();
|
||||||
"BPM"
|
"BPM"
|
||||||
|
|
@ -88,3 +92,4 @@ pub fn draw_bpm (buf: &mut Buffer, x: u16, y: u16, bpm: usize) {
|
||||||
"1/16"
|
"1/16"
|
||||||
.blit(buf, x + 29, y, Some(style.bold()));
|
.blit(buf, x + 29, y, Some(style.bold()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue