big ass refactor (rip client)

This commit is contained in:
🪞👃🪞 2024-07-03 14:51:48 +03:00
parent 94c1f83ef2
commit 8c3cf53c67
56 changed files with 2232 additions and 1891 deletions

View file

@ -1,29 +1,85 @@
use crate::core::*;
use std::path::{Path, PathBuf};
use std::fs::{File, create_dir_all};
const CONFIG_FILE_NAME: &'static str = "tek.toml";
const PROJECT_FILE_NAME: &'static str = "project.toml";
pub fn create_dirs (xdg: &microxdg::XdgApp) -> Result<(), Box<dyn Error>> {
use std::{path::Path,fs::{File,create_dir_all}};
let config_dir = xdg.app_config()?;
if !Path::new(&config_dir).exists() {
println!("Creating {config_dir:?}");
create_dir_all(&config_dir)?;
}
let config_path = config_dir.join(CONFIG_FILE_NAME);
if !Path::new(&config_path).exists() {
println!("Creating {config_path:?}");
File::create_new(&config_path)?;
}
let data_dir = xdg.app_data()?;
if !Path::new(&data_dir).exists() {
println!("Creating {data_dir:?}");
create_dir_all(&data_dir)?;
}
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(())
pub struct AppPaths {
config_dir: PathBuf,
config_file: PathBuf,
data_dir: PathBuf,
project_file: PathBuf,
}
impl AppPaths {
pub fn new (xdg: &XdgApp) -> Usually<Self> {
let config_dir = PathBuf::from(xdg.app_config()?);
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 })
}
pub fn should_create (&self) -> bool {
for path in [
&self.config_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)?;
}
}
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
View 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
View 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
}
},
})
}
}

View file

@ -1,5 +1,4 @@
use crate::core::*;
use super::*;
use crate::{core::*, view::*};
pub trait Focus {
fn unfocus (&mut self);
@ -187,3 +186,4 @@ impl Focus for FocusRow {
})
}
}

View file

@ -1,25 +1,22 @@
use crate::core::*;
use super::*;
use crate::{core::*,model::*};
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 {
LauncherView::Tracks => handle_keymap(state, event, KEYMAP_TRACKS)?,
LauncherView::Sequencer => {
LauncherMode::Tracks => {
handle_keymap(state, event, KEYMAP_TRACKS)?
},
LauncherMode::Sequencer => {
let i = state.col().saturating_sub(1);
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 {
true
}
},
LauncherView::Chains => {
LauncherMode::Chains => {
let i = state.col().saturating_sub(1);
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 {
true
}
@ -50,35 +47,38 @@ pub const KEYMAP_TRACKS: &'static [KeyBinding<Launcher>] = keymap!(Launcher {
[Enter, NONE, "activate", "activate item at cursor", activate],
});
fn duplicate (_: &mut Launcher) -> Usually<bool> {
unimplemented!();
Ok(true)
}
fn activate (state: &mut Launcher) -> Usually<bool> {
if let (
Some((_scene_id, scene)),
Some((track_id, track)),
) = (state.scene(), state.track()) {
// Launch clip
if let Some(phrase_id) = scene.clips.get(track_id) {
track.sequencer.state().sequence = *phrase_id;
}
if state.playing == TransportState::Stopped {
state.transport.start()?;
state.playing = TransportState::Starting;
}
} else if let Some((_scene_id, scene)) = state.scene() {
// Launch scene
for (track_id, track) in state.tracks.iter().enumerate() {
if let Some(phrase_id) = scene.clips.get(track_id) {
track.sequencer.state().sequence = *phrase_id;
}
}
if state.playing == TransportState::Stopped {
state.transport.start()?;
state.playing = TransportState::Starting;
}
} else if let Some((_track_id, _track)) = state.track() {
// Rename track?
}
fn activate (_: &mut Launcher) -> Usually<bool> {
unimplemented!();
//if let (
//Some((_scene_id, scene)),
//Some((track_id, track)),
//) = (state.scene_mut(), state.track_mut()) {
//// Launch clip
//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;
//}
//} else if let Some((_scene_id, scene)) = state.scene() {
//// Launch scene
//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;
//}
//} else if let Some((_track_id, _track)) = state.track() {
//// Rename track?
//}
//let track = state.active_track().unwrap();
//let scene = state.active_scene();
@ -104,15 +104,18 @@ fn activate (state: &mut Launcher) -> Usually<bool> {
//}
Ok(true)
}
fn rename (_: &mut Launcher) -> Usually<bool> {
Ok(true)
}
fn add_track (state: &mut Launcher) -> Usually<bool> {
let name = format!("Track {}", state.tracks.len() + 1);
state.tracks.push(Track::new(&name, &state.timebase, None, None)?);
state.cursor.0 = state.tracks.len();
Ok(true)
}
fn delete_track (state: &mut Launcher) -> Usually<bool> {
if state.tracks.len() > 0 && 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)
}
fn cursor_up (state: &mut Launcher) -> Usually<bool> {
state.dec_row();
Ok(true)
}
fn cursor_down (state: &mut Launcher) -> Usually<bool> {
state.inc_row();
Ok(true)
}
fn cursor_left (state: &mut Launcher) -> Usually<bool> {
state.dec_col();
Ok(true)
}
fn cursor_right (state: &mut Launcher) -> Usually<bool> {
state.inc_col();
Ok(true)
}
fn toggle_help (state: &mut Launcher) -> Usually<bool> {
state.show_help = !state.show_help;
Ok(true)
}
fn focus_next (state: &mut Launcher) -> Usually<bool> {
match state.view {
LauncherView::Tracks => { state.view = LauncherView::Sequencer; },
LauncherView::Sequencer => { state.view = LauncherView::Chains; },
LauncherView::Chains => { state.view = LauncherView::Tracks; },
_ => {},
LauncherMode::Tracks => { state.view = LauncherMode::Sequencer; },
LauncherMode::Sequencer => { state.view = LauncherMode::Chains; },
LauncherMode::Chains => { state.view = LauncherMode::Tracks; },
};
Ok(true)
}
fn focus_prev (state: &mut Launcher) -> Usually<bool> {
match state.view {
LauncherView::Tracks => { state.view = LauncherView::Chains; },
LauncherView::Chains => { state.view = LauncherView::Sequencer; },
LauncherView::Sequencer => { state.view = LauncherView::Tracks; },
_ => {},
LauncherMode::Tracks => { state.view = LauncherMode::Chains; },
LauncherMode::Chains => { state.view = LauncherMode::Sequencer; },
LauncherMode::Sequencer => { state.view = LauncherMode::Tracks; },
};
Ok(true)
}
fn clip_next (state: &mut Launcher) -> Usually<bool> {
if state.cursor.0 >= 1 && 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];
scene.clips[clip_id] = match scene.clips[clip_id] {
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
} else {
Some(i + 1)
@ -174,13 +183,14 @@ fn clip_next (state: &mut Launcher) -> Usually<bool> {
}
Ok(true)
}
fn clip_prev (state: &mut Launcher) -> Usually<bool> {
if state.cursor.0 >= 1 && state.cursor.1 >= 1 {
let scene_id = state.cursor.1 - 1;
let clip_id = state.cursor.0 - 1;
let scene = &mut state.scenes[scene_id];
scene.clips[clip_id] = match scene.clips[clip_id] {
None => Some(state.tracks[clip_id].sequencer.state().phrases.len().saturating_sub(1)),
None => Some(state.tracks[clip_id].sequencer.phrases.len().saturating_sub(1)),
Some(i) => if i == 0 {
None
} else {
@ -190,6 +200,7 @@ fn clip_prev (state: &mut Launcher) -> Usually<bool> {
}
Ok(true)
}
fn play_toggle (s: &mut Launcher) -> Usually<bool> {
s.playing = match s.playing {
TransportState::Stopped => {
@ -207,15 +218,19 @@ fn play_toggle (s: &mut Launcher) -> Usually<bool> {
//fn play_start (_: &mut Launcher) -> Usually<bool> {
//unimplemented!()
//}
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)
}
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)
}
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)
}

59
src/control/mixer.rs Normal file
View 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
View 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
View 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)
}

View file

@ -1,8 +1,9 @@
use crate::core::*;
use super::*;
pub fn handle (s: &mut Sequencer, event: &AppEvent) -> Usually<bool> {
handle_keymap(s, event, KEYMAP)
use crate::{core::*, model::*};
pub fn handle (state: &mut Sequencer, event: &AppEvent) -> Usually<bool> {
handle_keymap(state, event, KEYMAP)
}
pub const KEYMAP: &'static [KeyBinding<Sequencer>] = keymap!(Sequencer {
[Up, NONE, "cursor_up", "move cursor up", cursor_up],
[Down, NONE, "cursor_down", "move cursor down", cursor_down],
@ -37,15 +38,18 @@ pub const KEYMAP: &'static [KeyBinding<Sequencer>] = keymap!(Sequencer {
[Char('7'), NONE, "seq_7", "Phrase 7", focus_seq(6)],
[Char('8'), NONE, "seq_8", "Phrase 8", focus_seq(7)],
});
const fn focus_seq (i: usize) -> impl Fn(&mut Sequencer)->Usually<bool> {
move |s: &mut Sequencer| {
s.sequence = Some(i);
Ok(true)
}
}
fn nop (_: &mut Sequencer) -> Usually<bool> {
Ok(false)
}
fn note_add (s: &mut Sequencer) -> Usually<bool> {
if s.sequence.is_none() {
return Ok(false)
@ -70,70 +74,82 @@ fn note_add (s: &mut Sequencer) -> Usually<bool> {
};
Ok(true)
}
fn note_del (_: &mut Sequencer) -> Usually<bool> {
Ok(true)
}
fn time_cursor_inc (s: &mut Sequencer) -> Usually<bool> {
s.time_cursor = s.time_cursor + 1;
Ok(true)
}
fn time_cursor_dec (s: &mut Sequencer) -> Usually<bool> {
s.time_cursor = s.time_cursor.saturating_sub(1);
Ok(true)
}
fn note_cursor_inc (s: &mut Sequencer) -> Usually<bool> {
s.note_cursor = s.note_cursor + 1;
Ok(true)
}
fn note_cursor_dec (s: &mut Sequencer) -> Usually<bool> {
s.note_cursor = s.note_cursor.saturating_sub(1);
Ok(true)
}
fn cursor_up (s: &mut Sequencer) -> Usually<bool> {
match s.view {
SequencerView::Vertical => time_cursor_dec(s),
SequencerView::Horizontal => note_cursor_dec(s),
SequencerMode::Vertical => time_cursor_dec(s),
SequencerMode::Horizontal => note_cursor_dec(s),
_ => Ok(false)
}?;
Ok(true)
}
fn cursor_down (s: &mut Sequencer) -> Usually<bool> {
match s.view {
SequencerView::Vertical => time_cursor_inc(s),
SequencerView::Horizontal => note_cursor_inc(s),
SequencerMode::Vertical => time_cursor_inc(s),
SequencerMode::Horizontal => note_cursor_inc(s),
_ => Ok(false)
}?;
Ok(true)
}
fn cursor_left (s: &mut Sequencer) -> Usually<bool> {
match s.view {
SequencerView::Vertical => note_cursor_dec(s),
SequencerView::Horizontal => time_cursor_dec(s),
SequencerMode::Vertical => note_cursor_dec(s),
SequencerMode::Horizontal => time_cursor_dec(s),
_ => Ok(false)
}?;
Ok(true)
}
fn cursor_right (s: &mut Sequencer) -> Usually<bool> {
match s.view {
SequencerView::Vertical => note_cursor_inc(s),
SequencerView::Horizontal => time_cursor_inc(s),
SequencerMode::Vertical => note_cursor_inc(s),
SequencerMode::Horizontal => time_cursor_inc(s),
_ => Ok(false)
}?;
Ok(true)
}
fn cursor_duration_inc (_: &mut Sequencer) -> Usually<bool> {
//s.cursor.2 = s.cursor.2 + 1
Ok(true)
}
fn cursor_duration_dec (_: &mut Sequencer) -> Usually<bool> {
//if s.cursor.2 > 0 { s.cursor.2 = s.cursor.2 - 1 }
Ok(true)
}
fn mode_next (s: &mut Sequencer) -> Usually<bool> {
s.view = s.view.next();
Ok(true)
}
impl SequencerView {
impl SequencerMode {
fn next (&self) -> Self {
match self {
Self::Horizontal => Self::Vertical,
@ -143,12 +159,14 @@ impl SequencerView {
}
}
}
fn stop_and_rewind (s: &mut Sequencer) -> Usually<bool> {
s.transport.stop()?;
s.transport.locate(0)?;
s.playing = TransportState::Stopped;
Ok(true)
}
fn toggle_play (s: &mut Sequencer) -> Usually<bool> {
s.playing = match s.playing {
TransportState::Stopped => {
@ -163,24 +181,29 @@ fn toggle_play (s: &mut Sequencer) -> Usually<bool> {
};
Ok(true)
}
fn toggle_record (s: &mut Sequencer) -> Usually<bool> {
s.recording = !s.recording;
Ok(true)
}
fn toggle_overdub (s: &mut Sequencer) -> Usually<bool> {
s.overdub = !s.overdub;
Ok(true)
}
fn toggle_monitor (s: &mut Sequencer) -> Usually<bool> {
s.monitoring = !s.monitoring;
Ok(true)
}
fn quantize_next (s: &mut Sequencer) -> Usually<bool> {
if s.time_zoom < 64 {
s.time_zoom = s.time_zoom * 2;
}
Ok(true)
}
fn quantize_prev (s: &mut Sequencer) -> Usually<bool> {
if s.time_zoom > 1 {
s.time_zoom = s.time_zoom / 2;
@ -192,6 +215,7 @@ fn zoom_in (s: &mut Sequencer) -> Usually<bool> {
s.time_zoom = s.time_zoom / 2;
Ok(true)
}
fn zoom_out (s: &mut Sequencer) -> Usually<bool> {
s.time_zoom = s.time_zoom * 2;
Ok(true)

View file

@ -6,6 +6,8 @@ pub use std::collections::BTreeMap;
pub use std::sync::{Arc, Mutex, MutexGuard};
pub use std::sync::atomic::{Ordering, AtomicBool, AtomicUsize};
pub use std::sync::mpsc::{channel, Sender, Receiver};
pub use microxdg::XdgApp;
pub use ratatui::prelude::*;
pub use midly::{MidiMessage, live::LiveEvent, num::u7};
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::*;)* }; }
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 use crate::{key, keymap};
// Reexport macros:
pub use crate::{
render,
handle,
process,
keymap,
key
};

View file

@ -1,5 +1,9 @@
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.
pub trait Device: Render + Handle + PortList + Send + Sync {
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> {
self.render.lock().unwrap()(&*self.state.lock().unwrap(), buf, area)
}

View file

@ -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)]
pub enum AppEvent {
/// Terminal input

View file

@ -1,7 +1,51 @@
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 {
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 =
@ -16,23 +60,6 @@ pub type DynamicProcessHandler =
pub type BoxedProcessHandler =
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::{
AsyncClient,
AudioIn,

View file

@ -1,5 +1,14 @@
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 {
fg: Some(Color::Gray),
bg: None,
@ -24,3 +33,4 @@ pub const KEY_STYLE: [Style;12] = [
pub const KEYS_VERTICAL: [&'static str; 6] = [
"", "", "", "", "", "",
];

View file

@ -2,34 +2,38 @@ use crate::core::*;
use ratatui::widgets::WidgetRef;
/// Trait for things that render to the display.
pub trait Render {
pub trait Render: Send {
// Render something to an area of the buffer.
// Returns area used by component.
// This is insufficient but for the most basic dynamic layout algorithms.
fn render (&self, _b: &mut Buffer, _a: Rect) -> Usually<Rect> {
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 {
//Box::new(self)
//}
}
impl<T: Fn(&mut Buffer, Rect) -> Usually<Rect>> Render for T {
#[macro_export] macro_rules! render {
($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> + Send> Render for T {
fn render (&self, b: &mut Buffer, a: Rect) -> Usually<Rect> {
(*self).render(b, a)
(*self)(b, a)
}
}

View file

@ -1,28 +1,77 @@
use crate::core::*;
use better_panic::{Settings, Verbosity};
use crossterm::terminal::{
EnterAlternateScreen, LeaveAlternateScreen,
enable_raw_mode, disable_raw_mode
};
impl<T: Render + Handle + Send + Sized + 'static> Run for T {}
pub trait Run: Render + Handle + Send + Sized + 'static {
fn run (self, callback: Option<impl FnOnce(Arc<Mutex<Self>>)->Usually<()>>) -> Usually<()> {
let device = Arc::new(Mutex::new(self));
fn run (self, init: Option<impl FnOnce(Arc<Mutex<Self>>)->Usually<()>>) -> Usually<()> {
let app = Arc::new(Mutex::new(self));
let exited = Arc::new(AtomicBool::new(false));
let _input_thread = input_thread(&exited, &device);
let _input_thread = input_thread(&exited, &app);
terminal_setup()?;
panic_hook_setup();
let main_thread = main_thread(&exited, &device)?;
if let Some(callback) = callback {
callback(device);
let main_thread = main_thread(&exited, &app)?;
if let Some(init) = init {
init(app)?;
}
main_thread.join();
main_thread.join().unwrap();
terminal_teardown()?;
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
pub fn input_thread (
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(())
}

View file

@ -1,15 +1,23 @@
use crate::core::*;
#[derive(Default)]
use atomic_float::AtomicF64;
#[derive(Debug)]
pub struct Timebase {
/// Frames per second
pub rate: ::atomic_float::AtomicF64,
pub rate: AtomicF64,
/// Beats per minute
pub bpm: ::atomic_float::AtomicF64,
pub bpm: AtomicF64,
/// 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 {
pub fn new (rate: f64, bpm: f64, ppq: f64) -> Self {
Self { rate: rate.into(), bpm: bpm.into(), ppq: ppq.into() }
@ -146,7 +154,6 @@ impl Timebase {
ticks
}
}
#[cfg(test)] mod test {
use super::*;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,256 +6,321 @@ extern crate clap;
extern crate jack;
extern crate crossterm;
use clap::{Parser};
use std::error::Error;
pub mod core;
pub mod cli;
pub mod config;
pub mod device;
pub mod layout;
pub mod control;
pub mod core;
pub mod model;
pub mod view;
use crate::core::*;
use crate::device::*;
use crate::model::*;
use crate::view::*;
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)]
pub struct App {
client: Option<DynamicAsyncClient>,
phrases: BTreeMap<usize, Phrase>,
scenes: BTreeMap<usize, Scene>,
tracks: BTreeMap<usize, Track>,
frame: usize,
scene: Vec<usize>,
timebase: Arc<Timebase>,
}
struct SceneGrid {}
impl Render for SceneGrid {}
struct Chains {}
impl Render for Chains {}
struct Sequencer {}
impl Render for Sequencer {}
impl Render for App {
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
crate::device::Transport {
timebase: &self.timebase,
playing: TransportState::Stopped,
record: false,
overdub: false,
monitor: false,
frame: 0,
}.render(buf, area)?;
SceneGrid {
}.render(buf, area)?;
Chains {}.render(buf, area)?;
Sequencer {}.render(buf, area)?;
Ok(area)
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()))));
}
}
impl Handle for App {
fn handle (&mut self, _e: &AppEvent) -> Usually<bool> {
Ok(true)
}
}
impl Process for App {
fn process (&mut self, _c: &Client, _s: &ProcessScope) -> Control {
Control::Continue
}
}
pub fn main () -> Usually<()> {
App::default().run(Some(|app: Arc<Mutex<App>>|{
app.lock().unwrap().client = Some(jack_run("tek", &app)?);
Ok(())
}))
}
}
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())
}
};
}
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![
Track::new("Drums", &timebase, Some(vec![
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(),
//Plugin::lv2("Panagement", "file:///home/user/.lv2/Auburn Sounds Panagement 2.lv2")?.boxed(),
]), Some(vec![
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() }
})),
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() },
})),
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() },
})),
]))?,
Track::new("Odin2", &timebase, Some(vec![
Plugin::lv2("Odin2", "file:///home/user/.lv2/Odin2.lv2")?.boxed(),
]), 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(),
//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))
]), None)?,
];
state.jack = Some(jack_run("tek", &app)?);
Ok(())
}))
}
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(())
#[derive(Default)]
pub struct App {
pub xdg: Option<Arc<XdgApp>>,
pub jack: Option<DynamicAsyncClient>,
pub phrases: Vec<Phrase>,
pub scenes: Vec<Scene>,
pub tracks: Vec<Track>,
pub frame: usize,
pub scene: Vec<usize>,
pub timebase: Arc<Timebase>,
pub modal: Option<Box<dyn Component>>,
}
process!(App);
render!(App |self, buf, area| {
let Rect { x, mut y, width, height } = area;
y = y + TransportView {
timebase: &self.timebase,
playing: TransportState::Stopped,
record: false,
overdub: false,
monitor: false,
frame: 0,
}.render(buf, area)?.height;
y = y + SceneGridView {
buf,
area: Rect { x, y, width, height: height / 3 },
name: "",
focused: true,
scenes: &self.scenes,
tracks: &self.tracks,
cursor: &(0, 0)
}.draw()?.height;
y = y + ChainView {
focused: true,
chain: None
}.render(buf, Rect { x, y, width, height: height / 3 })?.height;
y = y + SequencerView {
focused: true,
ppq: self.timebase.ppq() as usize,
track: None,
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)?;
}
Ok(area)
});
handle!(App |self, e| {
if let Some(ref mut modal) = self.modal {
if modal.handle(e)? {
self.modal = None;
return Ok(true)
};
}
handle_keymap(self, e, keymap!(App {
[F(1), NONE, "toggle_help", "toggle help", toggle_help],
[Up, NONE, "cursor_up", "move cursor up", cursor_up],
[Down, NONE, "cursor_down", "move cursor down", cursor_down],
[Left, NONE, "cursor_left", "move cursor left", cursor_left],
[Right, NONE, "cursor_right", "move cursor right", cursor_right],
[Char('.'), NONE, "increment", "increment value at cursor", increment],
[Char(','), NONE, "decrement", "decrement value at cursor", decrement],
[Delete, CONTROL, "delete", "delete track", delete],
[Char('d'), CONTROL, "duplicate", "duplicate scene or track", duplicate],
[Enter, NONE, "activate", "activate item at cursor", activate],
[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],
}))
});
fn increment (_: &mut App) -> Usually<bool> { Ok(true) }
fn decrement (_: &mut App) -> Usually<bool> { Ok(true) }
fn delete (_: &mut App) -> Usually<bool> { Ok(true) }
fn duplicate (_: &mut App) -> Usually<bool> { Ok(true) }
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) }
//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![
//Track::new("Drums", &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(),
////Plugin::lv2("Panagement", "file:///home/user/.lv2/Auburn Sounds Panagement 2.lv2")?.boxed(),
//]), Some(vec![
//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() }
//})),
//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() },
//})),
//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() },
//})),
//]))?,
//Track::new("Odin2", &timebase, Some(vec![
//Plugin::lv2("Odin2", "file:///home/user/.lv2/Odin2.lv2")?.boxed(),
//]), 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(),
////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
View 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
View 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
View 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
View 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)
//}
//}

View file

@ -1,17 +1,17 @@
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 name: String,
pub length: usize,
pub notes: PhraseData,
}
impl Default for Phrase {
fn default () -> Self {
Self::new("", 0, None)
}
}
impl Phrase {
pub fn new (name: &str, length: usize, notes: Option<PhraseData>) -> Self {
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
View 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)
}
}

View file

@ -39,3 +39,4 @@ impl LV2Plugin {
})
}
}

View file

@ -3,7 +3,7 @@ use super::*;
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(
&std::path::Path::new("/nix/store/ij3sz7nqg5l7v2dygdvzy3w6cj62bd6r-helm-0.9.0/lib/lxvst/helm.so"),
host.clone()
@ -12,3 +12,4 @@ fn set_vst_plugin (host: &Arc<Mutex<Plugin>>, path: &str) -> Usually<PluginKind>
instance: loader.instance()?
})
}

175
src/model/sampler.rs Normal file
View 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
View 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:?}"));
}
}
}
}

View file

@ -1,11 +1,12 @@
use crate::core::*;
use crate::device::*;
use crate::layout::*;
use crate::model::*;
pub struct Track {
pub name: String,
pub sequencer: DynamicDevice<Sequencer>,
pub chain: DynamicDevice<Chain>,
pub sequencer: Sequencer,
pub chain: Chain,
}
impl Track {
pub fn new (
name: &str,
@ -15,10 +16,11 @@ impl Track {
) -> Usually<Self> {
let sequencer = Sequencer::new(&name, tempo, phrases)?;
let chain = Chain::new(&name, devices)?;
let (client, _status) = Client::new("init", ClientOptions::NO_START_SERVER)?;
{
if let (Some(output), Some(input)) = (
sequencer.midi_outs()?.get(0).clone(),
if let Some(item) = chain.state().items.get(0) {
if let Some(item) = chain.items.get(0) {
if let Some(port) = item.midi_ins()?.get(0) {
Some(port.clone())
} else {
@ -28,14 +30,13 @@ impl Track {
None
}
) {
if let Some(client) = &sequencer.client {
client.as_client().connect_ports_by_name(&output, &input)?;
}
client.connect_ports_by_name(&output, &input)?;
}
}
Ok(Self { name: name.to_string(), sequencer, chain })
}
}
impl PortList for Track {
fn midi_ins (&self) -> Usually<Vec<String>> {
self.sequencer.midi_ins()
@ -44,3 +45,4 @@ impl PortList for Track {
self.chain.audio_outs()
}
}

15
src/view.rs Normal file
View 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;

View file

@ -1,57 +1,34 @@
mod plugin; pub use self::plugin::*;
mod sampler; pub use self::sampler::*;
use crate::core::*;
use crate::layout::*;
use crate::view::*;
use crate::model::*;
pub struct Chain {
pub name: String,
pub focused: bool,
pub focus: usize,
pub items: Vec<Box<dyn Device>>,
pub view: ChainView,
pub adding: bool,
}
pub enum ChainView {
pub enum ChainViewMode {
Hidden,
Compact,
Row,
Column,
}
impl Chain {
pub fn new (name: &str, items: Option<Vec<Box<dyn Device>>>) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
Ok(DynamicDevice::new(render, handle, process, Self {
name: name.into(),
focused: false,
focus: 0,
items: items.unwrap_or_else(||vec![]),
view: ChainView::Column,
adding: false
}))
}
pub struct ChainView<'a> {
pub focused: bool,
pub chain: Option<&'a Chain>,
}
impl PortList for Chain {
fn midi_ins (&self) -> Usually<Vec<String>> {
if let Some(device) = self.items.get(0) {
device.midi_ins()
impl<'a> Render for ChainView<'a> {
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let style = Some(Style::default().green().dim());
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 {
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)
@ -64,18 +41,18 @@ pub fn render (state: &Chain, buf: &mut Buffer, area: Rect)
Style::default().green().dim()
});
let result = match state.view {
ChainView::Hidden => Rect { x, y, width: 0, height: 0 },
ChainView::Compact => {
ChainViewMode::Hidden => Rect { x, y, width: 0, height: 0 },
ChainViewMode::Compact => {
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());
draw_box_styled(buf, area, selected)
},
ChainView::Row => {
ChainViewMode::Row => {
draw_box_styled(buf, area, selected);
let (area, _) = Row::draw(buf, area, &state.items, 0)?;
area
},
ChainView::Column => {
ChainViewMode::Column => {
draw_as_column(state, buf, area, selected)?
},
};
@ -89,8 +66,8 @@ pub fn draw_as_row (
let mut h = 0u16;
let mut frames = vec![];
for (i, device) in state.items.iter().enumerate() {
let mut x2 = 0u16;
let mut y2 = 1u16;
let x2 = 0u16;
let _y2 = 1u16;
//for port in device.midi_ins()?.iter() {
//port.blit(buf, x, y + y2, Some(Style::default()));
//x2 = x2.max(port.len() as u16);
@ -129,7 +106,7 @@ pub fn draw_as_row (
h = h.max(frame.height);
x = x + frame.width;
}
Ok((area, frames))
Ok((Rect { x: area.x, y: area.y, width, height: h }, frames))
}
pub fn draw_as_column (
@ -215,73 +192,3 @@ pub fn draw_as_column (
draw_box_styled(buf, frames[state.focus], selected);
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)
}

View file

@ -1,6 +1,7 @@
use crate::core::*;
use super::*;
pub struct SceneGrid<'a> {
use crate::model::*;
use crate::view::*;
pub struct SceneGridView<'a> {
pub buf: &'a mut Buffer,
pub area: Rect,
pub name: &'a str,
@ -9,7 +10,7 @@ pub struct SceneGrid<'a> {
pub tracks: &'a[Track],
pub cursor: &'a(usize, usize),
}
impl<'a> SceneGrid<'a> {
impl<'a> SceneGridView<'a> {
pub fn new (
buf: &'a mut Buffer,
area: Rect,
@ -123,9 +124,7 @@ impl<'a> SceneGrid<'a> {
let clip = scene.clips.get(track);
let index = index as u16;
let label = if let Some(Some(clip)) = clip {
let track = self.tracks[track].sequencer.state();
let phrase = track.phrases.get(*clip);
if let Some(phrase) = phrase {
if let Some(phrase) = self.tracks[track].sequencer.phrases.get(*clip) {
format!("{}", phrase.name)
} else {
format!("????")

50
src/view/launcher.rs Normal file
View 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)
}

View file

@ -1,6 +1,6 @@
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> {
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {

0
src/view/layout/focus.rs Normal file
View file

68
src/view/mixer.rs Normal file
View 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
View 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
View 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
View 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 })
}

View file

@ -45,12 +45,7 @@ pub fn draw (
Ok(area)
}
pub fn timer (
buf: &mut Buffer,
area: Rect,
time0: usize,
now: usize
) {
pub fn timer (buf: &mut Buffer, area: Rect, time0: usize, now: usize) {
let x = area.x + 5;
for step in time0..(time0+area.width as usize).saturating_sub(5) {
buf.set_string(x + step as u16, area.y, &"-", if step == now {
@ -61,12 +56,9 @@ pub fn timer (
}
}
pub fn keys (
buf: &mut Buffer,
area: Rect,
note0: usize,
notes: &[bool],
) -> Usually<Rect> {
pub fn keys (buf: &mut Buffer, area: Rect, note0: usize, _notes: &[bool])
-> Usually<Rect>
{
let bw = Style::default().dim();
let Rect { x, y, width, height } = area;
let h = height.saturating_sub(2);
@ -98,13 +90,13 @@ pub fn lanes (
phrase: &Phrase,
ppq: usize,
time_z: usize,
time0: usize,
_time0: usize,
note0: usize,
) {
let Rect { x, y, width, height } = area;
let time0 = time0 / time_z;
let time1 = time0 + width as usize;
let note1 = note0 + height as usize;
//let time0 = time0 / time_z;
//let time1 = time0 + width as usize;
//let note1 = note0 + height as usize;
let bg = Style::default();
let (bw, wh) = (bg.dim(), bg.white());
let offset = 5;

View file

@ -1,4 +1,3 @@
use crate::core::*;
use super::*;
pub fn draw (

View file

@ -1,7 +1,6 @@
use crate::core::*;
use crate::layout::*;
pub struct Transport<'a> {
pub struct TransportView<'a> {
pub timebase: &'a Arc<Timebase>,
pub playing: TransportState,
pub record: bool,
@ -10,7 +9,7 @@ pub struct Transport<'a> {
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> {
let Rect { x, y, width, .. } = area;
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()));
}
pub fn draw_play_stop (buf: &mut Buffer, x: u16, y: u16, state: &TransportState) {
let style = Style::default().gray();
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()
}));
}
pub fn draw_rec (buf: &mut Buffer, x: u16, y: u16, on: bool) {
"⏺ REC".blit(buf, x, y, Some(if on {
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()
}))
}
pub fn draw_dub (buf: &mut Buffer, x: u16, y: u16, on: bool) {
"⏺ DUB".blit(buf, x, y, Some(if on {
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()
}))
}
pub fn draw_mon (buf: &mut Buffer, x: u16, y: u16, on: bool) {
"⏺ MON".blit(buf, x, y, Some(if on {
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()
}))
}
pub fn draw_bpm (buf: &mut Buffer, x: u16, y: u16, bpm: usize) {
let style = Style::default().not_dim();
"BPM"
@ -88,3 +92,4 @@ pub fn draw_bpm (buf: &mut Buffer, x: u16, y: u16, bpm: usize) {
"1/16"
.blit(buf, x + 29, y, Some(style.bold()));
}