mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
use Arc<str> where applicable; use konst split_at
This commit is contained in:
parent
411fc0c4bc
commit
305481adee
35 changed files with 286 additions and 273 deletions
|
|
@ -55,7 +55,7 @@ fn add_tracks (jack: &JackConnection, app: &mut Arranger, cli: &ArrangerCli) ->
|
||||||
track_color_1.mix(track_color_2, i as f32 / n as f32).into()
|
track_color_1.mix(track_color_2, i as f32 / n as f32).into()
|
||||||
))?;
|
))?;
|
||||||
track.width = cli.track_width;
|
track.width = cli.track_width;
|
||||||
let name = track.name.read().unwrap();
|
let name = track.name.clone();
|
||||||
track.player.midi_ins.push(
|
track.player.midi_ins.push(
|
||||||
jack.register_port(&format!("{}I", &name), MidiIn::default())?
|
jack.register_port(&format!("{}I", &name), MidiIn::default())?
|
||||||
);
|
);
|
||||||
|
|
|
||||||
11
edn/Cargo.lock
generated
11
edn/Cargo.lock
generated
|
|
@ -770,19 +770,18 @@ dependencies = [
|
||||||
"clojure-reader",
|
"clojure-reader",
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
"konst",
|
"konst",
|
||||||
"tek_layout",
|
|
||||||
"tek_tui",
|
"tek_tui",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tek_engine"
|
name = "tek_input"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tek_layout"
|
name = "tek_output"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"tek_engine",
|
"tek_edn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -795,8 +794,8 @@ dependencies = [
|
||||||
"rand",
|
"rand",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"tek_edn",
|
"tek_edn",
|
||||||
"tek_engine",
|
"tek_input",
|
||||||
"tek_layout",
|
"tek_output",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@ pub enum Token<'a> {
|
||||||
impl<'a> Token<'a> {
|
impl<'a> Token<'a> {
|
||||||
pub fn chomp (source: &'a str) -> Result<(&'a str, Self), ParseError> {
|
pub fn chomp (source: &'a str) -> Result<(&'a str, Self), ParseError> {
|
||||||
use Token::*;
|
use Token::*;
|
||||||
let mut state = Self::default();
|
use konst::string::{split_at, char_indices};
|
||||||
|
let mut state = Self::Nil;
|
||||||
for (index, c) in source.char_indices() {
|
for (index, c) in source.char_indices() {
|
||||||
state = match state {
|
state = match state {
|
||||||
// must begin expression
|
// must begin expression
|
||||||
|
|
@ -29,21 +30,33 @@ impl<'a> Token<'a> {
|
||||||
Key(_, _, 0) => unreachable!(),
|
Key(_, _, 0) => unreachable!(),
|
||||||
Num(source, index, length) => match c {
|
Num(source, index, length) => match c {
|
||||||
'0'..='9' => Num(source, index, length + 1),
|
'0'..='9' => Num(source, index, length + 1),
|
||||||
' '|'\n'|'\r'|'\t' => return Ok((&source[index+length..], Num(source, index, length))),
|
' '|'\n'|'\r'|'\t' => return Ok((
|
||||||
|
split_at(source, index+length).1,
|
||||||
|
Num(source, index, length)
|
||||||
|
)),
|
||||||
_ => return Err(ParseError::Unexpected(c))
|
_ => return Err(ParseError::Unexpected(c))
|
||||||
},
|
},
|
||||||
Sym(source, index, length) => match c {
|
Sym(source, index, length) => match c {
|
||||||
'a'..='z'|'0'..='9'|'-' => Sym(source, index, length + 1),
|
'a'..='z'|'0'..='9'|'-' => Sym(source, index, length + 1),
|
||||||
' '|'\n'|'\r'|'\t' => return Ok((&source[index+length..], Sym(source, index, length))),
|
' '|'\n'|'\r'|'\t' => return Ok((
|
||||||
|
split_at(source, index+length).1,
|
||||||
|
Sym(source, index, length)
|
||||||
|
)),
|
||||||
_ => return Err(ParseError::Unexpected(c))
|
_ => return Err(ParseError::Unexpected(c))
|
||||||
},
|
},
|
||||||
Key(source, index, length) => match c {
|
Key(source, index, length) => match c {
|
||||||
'a'..='z'|'0'..='9'|'-'|'/' => Key(source, index, length + 1),
|
'a'..='z'|'0'..='9'|'-'|'/' => Key(source, index, length + 1),
|
||||||
' '|'\n'|'\r'|'\t' => return Ok((&source[index+length..], Key(source, index, length))),
|
' '|'\n'|'\r'|'\t' => return Ok((
|
||||||
|
split_at(source, index+length).1,
|
||||||
|
Key(source, index, length)
|
||||||
|
)),
|
||||||
_ => return Err(ParseError::Unexpected(c))
|
_ => return Err(ParseError::Unexpected(c))
|
||||||
},
|
},
|
||||||
Exp(source, index, length, 0) => match c {
|
Exp(source, index, length, 0) => match c {
|
||||||
' '|'\n'|'\r'|'\t' => return Ok((&source[index+length..], Exp(source, index, length, 0))),
|
' '|'\n'|'\r'|'\t' => return Ok((
|
||||||
|
split_at(source, index+length).1,
|
||||||
|
Exp(source, index, length, 0)
|
||||||
|
)),
|
||||||
_ => return Err(ParseError::Unexpected(c))
|
_ => return Err(ParseError::Unexpected(c))
|
||||||
},
|
},
|
||||||
Exp(source, index, length, depth) => match c {
|
Exp(source, index, length, depth) => match c {
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ impl<E: Output> Measure<E> {
|
||||||
pub fn set_w (&self, w: impl Into<usize>) { self.x.store(w.into(), Relaxed) }
|
pub fn set_w (&self, w: impl Into<usize>) { self.x.store(w.into(), Relaxed) }
|
||||||
pub fn set_h (&self, h: impl Into<usize>) { self.y.store(h.into(), Relaxed) }
|
pub fn set_h (&self, h: impl Into<usize>) { self.y.store(h.into(), Relaxed) }
|
||||||
pub fn set_wh (&self, w: impl Into<usize>, h: impl Into<usize>) { self.set_w(w); self.set_h(h); }
|
pub fn set_wh (&self, w: impl Into<usize>, h: impl Into<usize>) { self.set_w(w); self.set_h(h); }
|
||||||
pub fn format (&self) -> String { format!("{}x{}", self.w(), self.h()) }
|
pub fn format (&self) -> Arc<str> { format!("{}x{}", self.w(), self.h()).into() }
|
||||||
pub fn new () -> Self {
|
pub fn new () -> Self {
|
||||||
Self {
|
Self {
|
||||||
_engine: PhantomData::default(),
|
_engine: PhantomData::default(),
|
||||||
|
|
|
||||||
109
src/arranger.rs
109
src/arranger.rs
|
|
@ -94,6 +94,11 @@ from_jack!(|jack| Arranger {
|
||||||
compact: true,
|
compact: true,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
has_clock!(|self: Arranger|&self.clock);
|
||||||
|
has_phrases!(|self: Arranger|self.pool.phrases);
|
||||||
|
has_editor!(|self: Arranger|self.editor);
|
||||||
|
handle!(TuiIn: |self: Arranger, input|ArrangerCommand::execute_with_state(self, input.event()));
|
||||||
|
|
||||||
//render!(TuiOut: (self: Arranger) => {
|
//render!(TuiOut: (self: Arranger) => {
|
||||||
//let pool_w = if self.pool.visible { self.splits[1] } else { 0 };
|
//let pool_w = if self.pool.visible { self.splits[1] } else { 0 };
|
||||||
//let color = self.color;
|
//let color = self.color;
|
||||||
|
|
@ -108,58 +113,54 @@ from_jack!(|jack| Arranger {
|
||||||
//Self::render_mode(self))))), Fill::y(&"fixme: self.editor"))))));
|
//Self::render_mode(self))))), Fill::y(&"fixme: self.editor"))))));
|
||||||
//self.size.of(layout)
|
//self.size.of(layout)
|
||||||
//});
|
//});
|
||||||
has_clock!(|self: Arranger|&self.clock);
|
|
||||||
has_phrases!(|self: Arranger|self.pool.phrases);
|
|
||||||
has_editor!(|self: Arranger|self.editor);
|
|
||||||
handle!(TuiIn: |self: Arranger, input|ArrangerCommand::execute_with_state(self, input.event()));
|
|
||||||
|
|
||||||
/// Status bar for arranger app
|
///// Status bar for arranger app
|
||||||
#[derive(Clone)]
|
//#[derive(Clone)]
|
||||||
pub struct ArrangerStatus {
|
//pub struct ArrangerStatus {
|
||||||
pub(crate) width: usize,
|
//pub(crate) width: usize,
|
||||||
pub(crate) cpu: Option<String>,
|
//pub(crate) cpu: Option<Arc<str>>,
|
||||||
pub(crate) size: String,
|
//pub(crate) size: Arc<str>,
|
||||||
pub(crate) playing: bool,
|
//pub(crate) playing: bool,
|
||||||
}
|
//}
|
||||||
from!(|state:&Arranger|ArrangerStatus = {
|
//from!(|state:&Arranger|ArrangerStatus = {
|
||||||
let samples = state.clock.chunk.load(Relaxed);
|
//let samples = state.clock.chunk.load(Relaxed);
|
||||||
let rate = state.clock.timebase.sr.get();
|
//let rate = state.clock.timebase.sr.get();
|
||||||
let buffer = samples as f64 / rate;
|
//let buffer = samples as f64 / rate;
|
||||||
let width = state.size.w();
|
//let width = state.size.w();
|
||||||
Self {
|
//Self {
|
||||||
width,
|
//width,
|
||||||
playing: state.clock.is_rolling(),
|
//playing: state.clock.is_rolling(),
|
||||||
cpu: state.perf.percentage().map(|cpu|format!("│{cpu:.01}%")),
|
//cpu: state.perf.percentage().map(|cpu|format!("│{cpu:.01}%").into()),
|
||||||
size: format!("{}x{}│", width, state.size.h()),
|
//size: format!("{}x{}│", width, state.size.h()).into(),
|
||||||
}
|
//}
|
||||||
});
|
//});
|
||||||
render!(TuiOut: (self: ArrangerStatus) => Fixed::y(2, lay!(
|
//render!(TuiOut: (self: ArrangerStatus) => Fixed::y(2, lay!(
|
||||||
Self::help(),
|
//Self::help(),
|
||||||
Fill::xy(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))),
|
//Fill::xy(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))),
|
||||||
)));
|
//)));
|
||||||
impl ArrangerStatus {
|
//impl ArrangerStatus {
|
||||||
fn help () -> impl Content<TuiOut> {
|
//fn help () -> impl Content<TuiOut> {
|
||||||
let single = |binding, command|row!(" ", col!(
|
//let single = |binding, command|row!(" ", col!(
|
||||||
Tui::fg(TuiTheme::yellow(), binding),
|
//Tui::fg(TuiTheme::yellow(), binding),
|
||||||
command
|
//command
|
||||||
));
|
//));
|
||||||
let double = |(b1, c1), (b2, c2)|col!(
|
//let double = |(b1, c1), (b2, c2)|col!(
|
||||||
row!(" ", Tui::fg(TuiTheme::yellow(), b1), " ", c1,),
|
//row!(" ", Tui::fg(TuiTheme::yellow(), b1), " ", c1,),
|
||||||
row!(" ", Tui::fg(TuiTheme::yellow(), b2), " ", c2,),
|
//row!(" ", Tui::fg(TuiTheme::yellow(), b2), " ", c2,),
|
||||||
);
|
//);
|
||||||
Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(50), row!(
|
//Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(50), row!(
|
||||||
single("SPACE", "play/pause"),
|
//single("SPACE", "play/pause"),
|
||||||
single(" Ctrl", " scroll"),
|
//single(" Ctrl", " scroll"),
|
||||||
single(" ▲▼▶◀", " cell"),
|
//single(" ▲▼▶◀", " cell"),
|
||||||
double(("p", "put"), ("g", "get")),
|
//double(("p", "put"), ("g", "get")),
|
||||||
double(("q", "enqueue"), ("e", "edit")),
|
//double(("q", "enqueue"), ("e", "edit")),
|
||||||
single(" wsad", " note"),
|
//single(" wsad", " note"),
|
||||||
double(("a", "append"), ("s", "set"),),
|
//double(("a", "append"), ("s", "set"),),
|
||||||
double((",.", "length"), ("<>", "triplet"),),
|
//double((",.", "length"), ("<>", "triplet"),),
|
||||||
double(("[]", "phrase"), ("{}", "order"),),
|
//double(("[]", "phrase"), ("{}", "order"),),
|
||||||
))
|
//))
|
||||||
}
|
//}
|
||||||
fn stats (&self) -> impl Content<TuiOut> + use<'_> {
|
//fn stats (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
row!(&self.cpu, &self.size)
|
//row!(&self.cpu, &self.size)
|
||||||
}
|
//}
|
||||||
}
|
//}
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,8 @@ impl Arranger {
|
||||||
pub fn scene_add (&mut self, name: Option<&str>, color: Option<ItemPalette>)
|
pub fn scene_add (&mut self, name: Option<&str>, color: Option<ItemPalette>)
|
||||||
-> Usually<&mut ArrangerScene>
|
-> Usually<&mut ArrangerScene>
|
||||||
{
|
{
|
||||||
let name = name.map_or_else(||self.scene_default_name(), |x|x.to_string());
|
|
||||||
let scene = ArrangerScene {
|
let scene = ArrangerScene {
|
||||||
name: Arc::new(name.into()),
|
name: name.map_or_else(||self.scene_default_name(), |x|x.to_string().into()),
|
||||||
clips: vec![None;self.tracks.len()],
|
clips: vec![None;self.tracks.len()],
|
||||||
color: color.unwrap_or_else(ItemPalette::random),
|
color: color.unwrap_or_else(ItemPalette::random),
|
||||||
};
|
};
|
||||||
|
|
@ -16,8 +15,8 @@ impl Arranger {
|
||||||
pub fn scene_del (&mut self, index: usize) {
|
pub fn scene_del (&mut self, index: usize) {
|
||||||
todo!("delete scene");
|
todo!("delete scene");
|
||||||
}
|
}
|
||||||
fn scene_default_name (&self) -> String {
|
fn scene_default_name (&self) -> Arc<str> {
|
||||||
format!("Sc{:3>}", self.scenes.len() + 1)
|
format!("Sc{:3>}", self.scenes.len() + 1).into()
|
||||||
}
|
}
|
||||||
pub fn selected_scene (&self) -> Option<&ArrangerScene> {
|
pub fn selected_scene (&self) -> Option<&ArrangerScene> {
|
||||||
self.selected.scene().and_then(|s|self.scenes.get(s))
|
self.selected.scene().and_then(|s|self.scenes.get(s))
|
||||||
|
|
@ -28,14 +27,14 @@ impl Arranger {
|
||||||
}
|
}
|
||||||
#[derive(Default, Debug, Clone)] pub struct ArrangerScene {
|
#[derive(Default, Debug, Clone)] pub struct ArrangerScene {
|
||||||
/// Name of scene
|
/// Name of scene
|
||||||
pub(crate) name: Arc<RwLock<String>>,
|
pub(crate) name: Arc<str>,
|
||||||
/// Clips in scene, one per track
|
/// Clips in scene, one per track
|
||||||
pub(crate) clips: Vec<Option<Arc<RwLock<MidiClip>>>>,
|
pub(crate) clips: Vec<Option<Arc<RwLock<MidiClip>>>>,
|
||||||
/// Identifying color of scene
|
/// Identifying color of scene
|
||||||
pub(crate) color: ItemPalette,
|
pub(crate) color: ItemPalette,
|
||||||
}
|
}
|
||||||
impl ArrangerScene {
|
impl ArrangerScene {
|
||||||
pub fn name (&self) -> &Arc<RwLock<String>> {
|
pub fn name (&self) -> &Arc<str> {
|
||||||
&self.name
|
&self.name
|
||||||
}
|
}
|
||||||
pub fn clips (&self) -> &Vec<Option<Arc<RwLock<MidiClip>>>> {
|
pub fn clips (&self) -> &Vec<Option<Arc<RwLock<MidiClip>>>> {
|
||||||
|
|
@ -45,7 +44,7 @@ impl ArrangerScene {
|
||||||
self.color
|
self.color
|
||||||
}
|
}
|
||||||
pub fn longest_name (scenes: &[Self]) -> usize {
|
pub fn longest_name (scenes: &[Self]) -> usize {
|
||||||
scenes.iter().map(|s|s.name().read().unwrap().len()).fold(0, usize::max)
|
scenes.iter().map(|s|s.name.len()).fold(0, usize::max)
|
||||||
}
|
}
|
||||||
/// Returns the pulse length of the longest phrase in the scene
|
/// Returns the pulse length of the longest phrase in the scene
|
||||||
pub fn pulses (&self) -> usize {
|
pub fn pulses (&self) -> usize {
|
||||||
|
|
|
||||||
|
|
@ -21,17 +21,13 @@ impl ArrangerSelection {
|
||||||
&self,
|
&self,
|
||||||
tracks: &[ArrangerTrack],
|
tracks: &[ArrangerTrack],
|
||||||
scenes: &[ArrangerScene],
|
scenes: &[ArrangerScene],
|
||||||
) -> String {
|
) -> Arc<str> {
|
||||||
format!("Selected: {}", match self {
|
format!("Selected: {}", match self {
|
||||||
Self::Mix => "Everything".to_string(),
|
Self::Mix => "Everything".to_string(),
|
||||||
Self::Track(t) => match tracks.get(*t) {
|
Self::Track(t) => tracks.get(*t).map(|track|format!("T{t}: {}", &track.name))
|
||||||
Some(track) => format!("T{t}: {}", &track.name.read().unwrap()),
|
.unwrap_or_else(||"T??".into()),
|
||||||
None => "T??".into(),
|
Self::Scene(s) => scenes.get(*s).map(|scene|format!("S{s}: {}", &scene.name))
|
||||||
},
|
.unwrap_or_else(||"S??".into()),
|
||||||
Self::Scene(s) => match scenes.get(*s) {
|
|
||||||
Some(scene) => format!("S{s}: {}", &scene.name.read().unwrap()),
|
|
||||||
None => "S??".into(),
|
|
||||||
},
|
|
||||||
Self::Clip(t, s) => match (tracks.get(*t), scenes.get(*s)) {
|
Self::Clip(t, s) => match (tracks.get(*t), scenes.get(*s)) {
|
||||||
(Some(_), Some(scene)) => match scene.clip(*t) {
|
(Some(_), Some(scene)) => match scene.clip(*t) {
|
||||||
Some(clip) => format!("T{t} S{s} C{}", &clip.read().unwrap().name),
|
Some(clip) => format!("T{t} S{s} C{}", &clip.read().unwrap().name),
|
||||||
|
|
@ -39,7 +35,7 @@ impl ArrangerSelection {
|
||||||
},
|
},
|
||||||
_ => format!("T{t} S{s}: Empty"),
|
_ => format!("T{t} S{s}: Empty"),
|
||||||
}
|
}
|
||||||
})
|
}).into()
|
||||||
}
|
}
|
||||||
pub fn track (&self) -> Option<usize> {
|
pub fn track (&self) -> Option<usize> {
|
||||||
use ArrangerSelection::*;
|
use ArrangerSelection::*;
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
impl Arranger {
|
impl Arranger {
|
||||||
pub fn track_next_name (&self) -> String {
|
pub fn track_next_name (&self) -> Arc<str> {
|
||||||
format!("Tr{}", self.tracks.len() + 1)
|
format!("Tr{:02}", self.tracks.len() + 1).into()
|
||||||
}
|
}
|
||||||
pub fn track_add (&mut self, name: Option<&str>, color: Option<ItemPalette>)
|
pub fn track_add (&mut self, name: Option<&str>, color: Option<ItemPalette>)
|
||||||
-> Usually<&mut ArrangerTrack>
|
-> Usually<&mut ArrangerTrack>
|
||||||
{
|
{
|
||||||
let name = name.map_or_else(||self.track_next_name(), |x|x.to_string());
|
let name = name.map_or_else(||self.track_next_name(), |x|x.to_string().into());
|
||||||
let track = ArrangerTrack {
|
let track = ArrangerTrack {
|
||||||
width: name.len() + 2,
|
width: name.len() + 2,
|
||||||
name: Arc::new(name.into()),
|
|
||||||
color: color.unwrap_or_else(ItemPalette::random),
|
color: color.unwrap_or_else(ItemPalette::random),
|
||||||
player: MidiPlayer::from(&self.clock),
|
player: MidiPlayer::from(&self.clock),
|
||||||
|
name,
|
||||||
};
|
};
|
||||||
self.tracks.push(track);
|
self.tracks.push(track);
|
||||||
let index = self.tracks.len() - 1;
|
let index = self.tracks.len() - 1;
|
||||||
|
|
@ -26,7 +26,7 @@ impl Arranger {
|
||||||
}
|
}
|
||||||
#[derive(Debug)] pub struct ArrangerTrack {
|
#[derive(Debug)] pub struct ArrangerTrack {
|
||||||
/// Name of track
|
/// Name of track
|
||||||
pub name: Arc<RwLock<String>>,
|
pub name: Arc<str>,
|
||||||
/// Preferred width of track column
|
/// Preferred width of track column
|
||||||
pub width: usize,
|
pub width: usize,
|
||||||
/// Identifying color of track
|
/// Identifying color of track
|
||||||
|
|
@ -38,7 +38,7 @@ has_clock!(|self:ArrangerTrack|self.player.clock());
|
||||||
has_player!(|self:ArrangerTrack|self.player);
|
has_player!(|self:ArrangerTrack|self.player);
|
||||||
impl ArrangerTrack {
|
impl ArrangerTrack {
|
||||||
/// Name of track
|
/// Name of track
|
||||||
pub fn name (&self) -> &Arc<RwLock<String>> {
|
pub fn name (&self) -> &Arc<str> {
|
||||||
&self.name
|
&self.name
|
||||||
}
|
}
|
||||||
/// Preferred width of track column
|
/// Preferred width of track column
|
||||||
|
|
@ -54,7 +54,7 @@ impl ArrangerTrack {
|
||||||
self.color
|
self.color
|
||||||
}
|
}
|
||||||
fn longest_name (tracks: &[Self]) -> usize {
|
fn longest_name (tracks: &[Self]) -> usize {
|
||||||
tracks.iter().map(|s|s.name().read().unwrap().len()).fold(0, usize::max)
|
tracks.iter().map(|s|s.name.len()).fold(0, usize::max)
|
||||||
}
|
}
|
||||||
fn width_inc (&mut self) {
|
fn width_inc (&mut self) {
|
||||||
*self.width_mut() += 1;
|
*self.width_mut() += 1;
|
||||||
|
|
|
||||||
|
|
@ -3,27 +3,25 @@ pub(crate) const HEADER_H: u16 = 0; // 5
|
||||||
pub(crate) const SCENES_W_OFFSET: u16 = 0;
|
pub(crate) const SCENES_W_OFFSET: u16 = 0;
|
||||||
render!(TuiOut: (self: Arranger) => {
|
render!(TuiOut: (self: Arranger) => {
|
||||||
let toolbar = |x|Bsp::s(self.toolbar_view(), x);
|
let toolbar = |x|Bsp::s(self.toolbar_view(), x);
|
||||||
let status = |x|Bsp::n(self.status_view(), x);
|
|
||||||
let pool = |x|Bsp::w(self.pool_view(), x);
|
let pool = |x|Bsp::w(self.pool_view(), x);
|
||||||
let editing = |x|Bsp::n(self.editor_status_view(), x);
|
let editing = |x|Bsp::n(Bsp::e(self.editor.clip_status(), self.editor.edit_status()), x);
|
||||||
let enclosed = |x|Outer(Style::default().fg(Color::Rgb(72,72,72))).enclose(x);
|
let enclosed = |x|Outer(Style::default().fg(Color::Rgb(72,72,72))).enclose(x);
|
||||||
let editor = Fixed::y(20, enclosed(&self.editor));
|
|
||||||
let scenes_w = 16;//.max(SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16);
|
let scenes_w = 16;//.max(SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16);
|
||||||
let arrrrrr = Fixed::y(32, Map::new(
|
let arrrrrr = Fixed::y(27, Map::new(
|
||||||
move||[
|
move||[
|
||||||
(0, 2, self.output_row_header(), self.output_row_cells()),
|
(0, 2, self.output_row_header(), self.output_row_cells()),
|
||||||
(2, 2, self.elapsed_row_header(), self.elapsed_row_cells()),
|
(2, 3, self.elapsed_row_header(), self.elapsed_row_cells()),
|
||||||
(4, 2, self.next_row_header(), self.next_row_cells()),
|
(4, 3, self.next_row_header(), self.next_row_cells()),
|
||||||
(6, 3, self.track_row_header(), self.track_row_cells()),
|
(6, 3, self.track_row_header(), self.track_row_cells()),
|
||||||
(9, 20, self.scene_row_headers(), self.scene_row_cells()),
|
(8, 20, self.scene_row_headers(), self.scene_row_cells()),
|
||||||
(29, 2, self.input_row_header(), self.input_row_cells()),
|
(25, 2, self.input_row_header(), self.input_row_cells()),
|
||||||
].into_iter(),
|
].into_iter(),
|
||||||
move|(y, h, header, cells), index|map_south(y, h, Fill::x(Align::w(Bsp::e(
|
move|(y, h, header, cells), index|map_south(y, h, Fill::x(Align::w(Bsp::e(
|
||||||
Fixed::xy(scenes_w, h, header),
|
Fixed::xy(scenes_w, h, header),
|
||||||
Fixed::xy(self.tracks.len() as u16*6, h, cells)
|
Fixed::xy(self.tracks.len() as u16*6, h, cells)
|
||||||
))))));
|
))))));
|
||||||
|
|
||||||
self.size.of(toolbar(status(pool(editing(Bsp::n(editor, arrrrrr))))))
|
self.size.of(toolbar(pool(editing(Bsp::s(arrrrrr, enclosed(&self.editor))))))
|
||||||
|
|
||||||
});
|
});
|
||||||
impl Arranger {
|
impl Arranger {
|
||||||
|
|
@ -63,7 +61,7 @@ impl Arranger {
|
||||||
(move||Fixed::y(2, Map::new(||self.tracks_with_widths(), move|(_, track, x1, x2), i| {
|
(move||Fixed::y(2, Map::new(||self.tracks_with_widths(), move|(_, track, x1, x2), i| {
|
||||||
let w = (x2 - x1) as u16;
|
let w = (x2 - x1) as u16;
|
||||||
let color: ItemPalette = track.color().dark.into();
|
let color: ItemPalette = track.color().dark.into();
|
||||||
let cell = Bsp::s(format!("MutSol"), Self::phat_hi(color.dark.rgb, color.darker.rgb));
|
let cell = Bsp::s(format!(" M S "), Self::phat_hi(color.dark.rgb, color.darker.rgb));
|
||||||
map_east(x1 as u16, w, Fixed::x(w, Self::cell(color, cell)))
|
map_east(x1 as u16, w, Fixed::x(w, Self::cell(color, cell)))
|
||||||
})).boxed()).into()
|
})).boxed()).into()
|
||||||
}
|
}
|
||||||
|
|
@ -73,19 +71,17 @@ impl Arranger {
|
||||||
}
|
}
|
||||||
fn elapsed_row_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
|
fn elapsed_row_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
|
||||||
(move||Fixed::y(2, Map::new(||self.tracks_with_widths(), move|(_, track, x1, x2), i| {
|
(move||Fixed::y(2, Map::new(||self.tracks_with_widths(), move|(_, track, x1, x2), i| {
|
||||||
let color = track.color();
|
//let color = track.color();
|
||||||
let color: ItemPalette = track.color().dark.into();
|
let color: ItemPalette = track.color().dark.into();
|
||||||
let timebase = self.clock().timebase();
|
let timebase = self.clock().timebase();
|
||||||
let elapsed = {
|
let value = Tui::fg_bg(color.lightest.rgb, color.base.rgb,
|
||||||
let mut result = String::new();
|
|
||||||
if let Some((_, Some(phrase))) = track.player.play_phrase().as_ref() {
|
if let Some((_, Some(phrase))) = track.player.play_phrase().as_ref() {
|
||||||
let length = phrase.read().unwrap().length;
|
let length = phrase.read().unwrap().length;
|
||||||
let elapsed = track.player.pulses_since_start().unwrap() as usize;
|
let elapsed = track.player.pulses_since_start().unwrap() as usize;
|
||||||
result = format!("+{:>}", timebase.format_beats_1_short((elapsed % length) as f64))
|
format!("+{:>}", timebase.format_beats_1_short((elapsed % length) as f64))
|
||||||
}
|
} else {
|
||||||
result
|
String::new()
|
||||||
};
|
});
|
||||||
let value = Tui::fg_bg(color.lightest.rgb, color.base.rgb, elapsed);
|
|
||||||
let cell = Bsp::s(value, Self::phat_hi(color.dark.rgb, color.darker.rgb));
|
let cell = Bsp::s(value, Self::phat_hi(color.dark.rgb, color.darker.rgb));
|
||||||
Tui::bg(color.base.rgb, map_east(x1 as u16, (x2 - x1) as u16, cell))
|
Tui::bg(color.base.rgb, map_east(x1 as u16, (x2 - x1) as u16, cell))
|
||||||
})).boxed()).into()
|
})).boxed()).into()
|
||||||
|
|
@ -109,12 +105,14 @@ impl Arranger {
|
||||||
(||Tui::bold(true, Tui::fg(TuiTheme::g(128), "Track")).boxed()).into()
|
(||Tui::bold(true, Tui::fg(TuiTheme::g(128), "Track")).boxed()).into()
|
||||||
}
|
}
|
||||||
fn track_row_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
|
fn track_row_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
|
||||||
(move||Fixed::y(2, Map::new(||self.tracks_with_widths(), move|(_, track, x1, x2), i| {
|
let iter = ||self.tracks_with_widths();
|
||||||
|
(move||Fixed::y(2, Map::new(iter, move|(_, track, x1, x2), i| {
|
||||||
let color = track.color();
|
let color = track.color();
|
||||||
|
let name = format!(" {}", &track.name);
|
||||||
Tui::bg(color.base.rgb, map_east(x1 as u16, (x2 - x1) as u16,
|
Tui::bg(color.base.rgb, map_east(x1 as u16, (x2 - x1) as u16,
|
||||||
Tui::fg_bg(color.lightest.rgb, color.base.rgb,
|
Tui::fg_bg(color.lightest.rgb, color.base.rgb,
|
||||||
Self::phat_cell(color, color.darkest.rgb.into(), Tui::bold(true, format!("{}", track.name.read().unwrap()))))))
|
Self::phat_cell(color, color.darkest.rgb.into(),
|
||||||
})).boxed()).into()
|
Tui::bold(true, name))))) })).boxed()).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn input_row_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
|
fn input_row_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
|
||||||
|
|
@ -138,7 +136,7 @@ impl Arranger {
|
||||||
move|(_, scene, y1, y2), i| {
|
move|(_, scene, y1, y2), i| {
|
||||||
let h = (y2 - y1) as u16;
|
let h = (y2 - y1) as u16;
|
||||||
let color = scene.color();
|
let color = scene.color();
|
||||||
let name = format!(" {}", scene.name.read().unwrap());
|
let name = format!("🭬{}", &scene.name);
|
||||||
let cell = Self::phat_cell(color, *last_color.read().unwrap(), name);
|
let cell = Self::phat_cell(color, *last_color.read().unwrap(), name);
|
||||||
*last_color.write().unwrap() = color;
|
*last_color.write().unwrap() = color;
|
||||||
map_south(y1 as u16, 2, Fill::x(cell))
|
map_south(y1 as u16, 2, Fill::x(cell))
|
||||||
|
|
@ -147,7 +145,7 @@ impl Arranger {
|
||||||
}).into()
|
}).into()
|
||||||
}
|
}
|
||||||
fn scene_row_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
|
fn scene_row_cells <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
|
||||||
(||Tui::bg(Color::Rgb(20,40,60), "").boxed()).into()
|
(||Tui::bg(self.color.darkest.rgb, "").boxed()).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tracks_with_widths (&self)
|
pub fn tracks_with_widths (&self)
|
||||||
|
|
@ -175,8 +173,7 @@ impl Arranger {
|
||||||
}
|
}
|
||||||
/// name and width of track
|
/// name and width of track
|
||||||
fn cell_name (track: &ArrangerTrack, _w: usize) -> impl Content<TuiOut> {
|
fn cell_name (track: &ArrangerTrack, _w: usize) -> impl Content<TuiOut> {
|
||||||
let name = track.name().read().unwrap().clone();
|
Tui::bold(true, Tui::fg(track.color.lightest.rgb, track.name().clone()))
|
||||||
Tui::bold(true, Tui::fg(track.color.lightest.rgb, name))
|
|
||||||
}
|
}
|
||||||
/// beats until switchover
|
/// beats until switchover
|
||||||
fn cell_until_next (track: &ArrangerTrack, current: &Arc<Moment>)
|
fn cell_until_next (track: &ArrangerTrack, current: &Arc<Moment>)
|
||||||
|
|
@ -207,7 +204,7 @@ impl Arranger {
|
||||||
Map::new(||self.scenes_with_heights(1), move|(_, scene, y1, y2), i| {
|
Map::new(||self.scenes_with_heights(1), move|(_, scene, y1, y2), i| {
|
||||||
let h = (y2 - y1) as u16;
|
let h = (y2 - y1) as u16;
|
||||||
let color = scene.color();
|
let color = scene.color();
|
||||||
let cell = Fixed::y(h, Fixed::x(scenes_w, Self::cell(color, scene.name.read().unwrap().clone())));
|
let cell = Fixed::y(h, Fixed::x(scenes_w, Self::cell(color, scene.name.clone())));
|
||||||
map_south(y1 as u16, 1, cell)
|
map_south(y1 as u16, 1, cell)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -235,7 +232,7 @@ impl Arranger {
|
||||||
scene.color.base.rgb, if playing { "▶ " } else { " " }
|
scene.color.base.rgb, if playing { "▶ " } else { " " }
|
||||||
);
|
);
|
||||||
let name = Tui::fg_bg(scene.color.lightest.rgb, scene.color.base.rgb,
|
let name = Tui::fg_bg(scene.color.lightest.rgb, scene.color.base.rgb,
|
||||||
Expand::x(1, Tui::bold(true, scene.name.read().unwrap().clone()))
|
Expand::x(1, Tui::bold(true, scene.name.clone()))
|
||||||
);
|
);
|
||||||
let clips = Map::new(||Arranger::tracks_with_widths_static(tracks), move|(index, track, x1, x2), _|
|
let clips = Map::new(||Arranger::tracks_with_widths_static(tracks), move|(index, track, x1, x2), _|
|
||||||
Push::x((x2 - x1) as u16, Self::cell_clip(scene, index, track, (x2 - x1) as u16, height))
|
Push::x((x2 - x1) as u16, Self::cell_clip(scene, index, track, (x2 - x1) as u16, height))
|
||||||
|
|
@ -264,20 +261,6 @@ impl Arranger {
|
||||||
fn toolbar_view (&self) -> impl Content<TuiOut> + use<'_> {
|
fn toolbar_view (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
Fill::x(Fixed::y(2, Align::x(TransportView::new(true, &self.clock))))
|
Fill::x(Fixed::y(2, Align::x(TransportView::new(true, &self.clock))))
|
||||||
}
|
}
|
||||||
fn status_view (&self) -> impl Content<TuiOut> + use<'_> {
|
|
||||||
ArrangerStatus::from(self)
|
|
||||||
//let edit_clip = MidiEditClip(&self.editor);
|
|
||||||
////let selectors = When(false, Bsp::e(ClipSelected::play_phrase(&self.player), ClipSelected::next_phrase(&self.player)));
|
|
||||||
//row!([>selectors,<] edit_clip, MidiEditStatus(&self.editor))
|
|
||||||
}
|
|
||||||
fn editor_status_view (&self) -> impl Content<TuiOut> + use<'_> {
|
|
||||||
Bsp::e(
|
|
||||||
//ClipSelected::play_phrase(&self.player),
|
|
||||||
//ClipSelected::next_phrase(&self.player),
|
|
||||||
MidiEditClip(&self.editor),
|
|
||||||
MidiEditStatus(&self.editor),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
fn pool_view (&self) -> impl Content<TuiOut> + use<'_> {
|
fn pool_view (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
let w = self.size.w();
|
let w = self.size.w();
|
||||||
let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 };
|
let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 };
|
||||||
|
|
|
||||||
|
|
@ -46,17 +46,20 @@ render!(TuiOut: (self: PlayPause) => Tui::bg(
|
||||||
Tui::fg(Color::Rgb(0, 255, 0), Bsp::s(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)),
|
Tui::fg(Color::Rgb(0, 255, 0), Bsp::s(" 🭍🭑🬽 ", " 🭞🭜🭘 ",)),
|
||||||
Tui::fg(Color::Rgb(255, 128, 0), Bsp::s(" ▗▄▖ ", " ▝▀▘ ",))))))));
|
Tui::fg(Color::Rgb(255, 128, 0), Bsp::s(" ▗▄▖ ", " ▝▀▘ ",))))))));
|
||||||
|
|
||||||
pub struct BeatStats { compact: bool, bpm: String, beat: String, time: String, }
|
pub struct BeatStats { compact: bool, bpm: Arc<str>, beat: Arc<str>, time: Arc<str>, }
|
||||||
impl BeatStats {
|
impl BeatStats {
|
||||||
fn new (compact: bool, clock: &Clock) -> Self {
|
fn new (compact: bool, clock: &Clock) -> Self {
|
||||||
let (beat, time) = clock.started.read().unwrap().as_ref().map(|started|{
|
let bpm = format!("{:.3}", clock.timebase.bpm.get()).into();
|
||||||
let current_usec = clock.global.usec.get() - started.usec.get();
|
let (beat, time) = if let Some(started) = clock.started.read().unwrap().as_ref() {
|
||||||
|
let now = clock.global.usec.get() - started.usec.get();
|
||||||
(
|
(
|
||||||
clock.timebase.format_beats_1(clock.timebase.usecs_to_pulse(current_usec)),
|
clock.timebase.format_beats_1(clock.timebase.usecs_to_pulse(now)).into(),
|
||||||
format!("{:.3}s", current_usec/1000000.)
|
format!("{:.3}s", now/1000000.).into()
|
||||||
)
|
)
|
||||||
}).unwrap_or_else(||("-.-.--".to_string(), "-.---s".to_string()));
|
} else {
|
||||||
Self { compact, bpm: format!("{:.3}", clock.timebase.bpm.get()), beat, time }
|
("-.-.--".to_string().into(), "-.---s".to_string().into())
|
||||||
|
};
|
||||||
|
Self { compact, bpm, beat, time }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
render!(TuiOut: (self: BeatStats) => Either(self.compact,
|
render!(TuiOut: (self: BeatStats) => Either(self.compact,
|
||||||
|
|
@ -71,7 +74,7 @@ render!(TuiOut: (self: BeatStats) => Either(self.compact,
|
||||||
Bsp::e("Time ", Tui::fg(TuiTheme::g(255), &self.time)),
|
Bsp::e("Time ", Tui::fg(TuiTheme::g(255), &self.time)),
|
||||||
)));
|
)));
|
||||||
|
|
||||||
pub struct OutputStats { compact: bool, sample_rate: String, buffer_size: String, latency: String, }
|
pub struct OutputStats { compact: bool, sample_rate: Arc<str>, buffer_size: Arc<str>, latency: Arc<str>, }
|
||||||
impl OutputStats {
|
impl OutputStats {
|
||||||
fn new (compact: bool, clock: &Clock) -> Self {
|
fn new (compact: bool, clock: &Clock) -> Self {
|
||||||
let rate = clock.timebase.sr.get();
|
let rate = clock.timebase.sr.get();
|
||||||
|
|
@ -82,9 +85,9 @@ impl OutputStats {
|
||||||
format!("{:.1}kHz", rate / 1000.)
|
format!("{:.1}kHz", rate / 1000.)
|
||||||
} else {
|
} else {
|
||||||
format!("{:.0}Hz", rate)
|
format!("{:.0}Hz", rate)
|
||||||
},
|
}.into(),
|
||||||
buffer_size: format!("{chunk}"),
|
buffer_size: format!("{chunk}").into(),
|
||||||
latency: format!("{:.1}ms", chunk as f64 / rate * 1000.),
|
latency: format!("{:.1}ms", chunk as f64 / rate * 1000.).into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,10 @@ use crate::*;
|
||||||
impl_time_unit!(Microsecond);
|
impl_time_unit!(Microsecond);
|
||||||
|
|
||||||
impl Microsecond {
|
impl Microsecond {
|
||||||
#[inline] pub fn format_msu (&self) -> String {
|
#[inline] pub fn format_msu (&self) -> Arc<str> {
|
||||||
let usecs = self.get() as usize;
|
let usecs = self.get() as usize;
|
||||||
let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000);
|
let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000);
|
||||||
let (minutes, seconds) = (seconds / 60, seconds % 60);
|
let (minutes, seconds) = (seconds / 60, seconds % 60);
|
||||||
format!("{minutes}:{seconds:02}:{msecs:03}")
|
format!("{minutes}:{seconds:02}:{msecs:03}").into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ impl Moment {
|
||||||
self.pulse.set(pulse);
|
self.pulse.set(pulse);
|
||||||
self.sample.set(self.timebase.pulses_to_sample(pulse));
|
self.sample.set(self.timebase.pulses_to_sample(pulse));
|
||||||
}
|
}
|
||||||
#[inline] pub fn format_beat (&self) -> String {
|
#[inline] pub fn format_beat (&self) -> Arc<str> {
|
||||||
self.timebase.format_beats_1(self.pulse.get())
|
self.timebase.format_beats_1(self.pulse.get()).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -78,32 +78,32 @@ impl Timebase {
|
||||||
events.map(|(time, event)|(self.quantize(step, time).0, event)).collect()
|
events.map(|(time, event)|(self.quantize(step, time).0, event)).collect()
|
||||||
}
|
}
|
||||||
/// Format a number of pulses into Beat.Bar.Pulse starting from 0
|
/// Format a number of pulses into Beat.Bar.Pulse starting from 0
|
||||||
#[inline] pub fn format_beats_0 (&self, pulse: f64) -> String {
|
#[inline] pub fn format_beats_0 (&self, pulse: f64) -> Arc<str> {
|
||||||
let pulse = pulse as usize;
|
let pulse = pulse as usize;
|
||||||
let ppq = self.ppq.get() as usize;
|
let ppq = self.ppq.get() as usize;
|
||||||
let (beats, pulses) = if ppq > 0 { (pulse / ppq, pulse % ppq) } else { (0, 0) };
|
let (beats, pulses) = if ppq > 0 { (pulse / ppq, pulse % ppq) } else { (0, 0) };
|
||||||
format!("{}.{}.{pulses:02}", beats / 4, beats % 4)
|
format!("{}.{}.{pulses:02}", beats / 4, beats % 4).into()
|
||||||
}
|
}
|
||||||
/// Format a number of pulses into Beat.Bar starting from 0
|
/// Format a number of pulses into Beat.Bar starting from 0
|
||||||
#[inline] pub fn format_beats_0_short (&self, pulse: f64) -> String {
|
#[inline] pub fn format_beats_0_short (&self, pulse: f64) -> Arc<str> {
|
||||||
let pulse = pulse as usize;
|
let pulse = pulse as usize;
|
||||||
let ppq = self.ppq.get() as usize;
|
let ppq = self.ppq.get() as usize;
|
||||||
let beats = if ppq > 0 { pulse / ppq } else { 0 };
|
let beats = if ppq > 0 { pulse / ppq } else { 0 };
|
||||||
format!("{}.{}", beats / 4, beats % 4)
|
format!("{}.{}", beats / 4, beats % 4).into()
|
||||||
}
|
}
|
||||||
/// Format a number of pulses into Beat.Bar.Pulse starting from 1
|
/// Format a number of pulses into Beat.Bar.Pulse starting from 1
|
||||||
#[inline] pub fn format_beats_1 (&self, pulse: f64) -> String {
|
#[inline] pub fn format_beats_1 (&self, pulse: f64) -> Arc<str> {
|
||||||
let pulse = pulse as usize;
|
let pulse = pulse as usize;
|
||||||
let ppq = self.ppq.get() as usize;
|
let ppq = self.ppq.get() as usize;
|
||||||
let (beats, pulses) = if ppq > 0 { (pulse / ppq, pulse % ppq) } else { (0, 0) };
|
let (beats, pulses) = if ppq > 0 { (pulse / ppq, pulse % ppq) } else { (0, 0) };
|
||||||
format!("{}.{}.{pulses:02}", beats / 4 + 1, beats % 4 + 1)
|
format!("{}.{}.{pulses:02}", beats / 4 + 1, beats % 4 + 1).into()
|
||||||
}
|
}
|
||||||
/// Format a number of pulses into Beat.Bar.Pulse starting from 1
|
/// Format a number of pulses into Beat.Bar.Pulse starting from 1
|
||||||
#[inline] pub fn format_beats_1_short (&self, pulse: f64) -> String {
|
#[inline] pub fn format_beats_1_short (&self, pulse: f64) -> Arc<str> {
|
||||||
let pulse = pulse as usize;
|
let pulse = pulse as usize;
|
||||||
let ppq = self.ppq.get() as usize;
|
let ppq = self.ppq.get() as usize;
|
||||||
let beats = if ppq > 0 { pulse / ppq } else { 0 };
|
let beats = if ppq > 0 { pulse / ppq } else { 0 };
|
||||||
format!("{}.{}", beats / 4 + 1, beats % 4 + 1)
|
format!("{}.{}", beats / 4 + 1, beats % 4 + 1).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ pub enum FileBrowserCommand {
|
||||||
Confirm,
|
Confirm,
|
||||||
Select(usize),
|
Select(usize),
|
||||||
Chdir(PathBuf),
|
Chdir(PathBuf),
|
||||||
Filter(String),
|
Filter(Arc<str>),
|
||||||
}
|
}
|
||||||
render!(TuiOut: (self: FileBrowser) => /*Stack::down(|add|{
|
render!(TuiOut: (self: FileBrowser) => /*Stack::down(|add|{
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,10 @@ impl EdnViewData<TuiOut> for &Groovebox {
|
||||||
":input-meter-r" => Box::new(Meter("R/", self.sampler.input_meter[1])),
|
":input-meter-r" => Box::new(Meter("R/", self.sampler.input_meter[1])),
|
||||||
|
|
||||||
":transport" => Box::new(TransportView::new(true, &self.player.clock)),
|
":transport" => Box::new(TransportView::new(true, &self.player.clock)),
|
||||||
":clip-play" => Box::new(ClipSelected::play_phrase(&self.player)),
|
":clip-play" => Box::new(self.player.play_status()),
|
||||||
":clip-next" => Box::new(ClipSelected::next_phrase(&self.player)),
|
":clip-next" => Box::new(self.player.next_status()),
|
||||||
":clip-edit" => Box::new(MidiEditClip(&self.editor)),
|
":clip-edit" => Box::new(self.editor.clip_status()),
|
||||||
":edit-stat" => Box::new(MidiEditStatus(&self.editor)),
|
":edit-stat" => Box::new(self.editor.edit_status()),
|
||||||
":pool-view" => Box::new(PoolView(self.compact, &self.pool)),
|
":pool-view" => Box::new(PoolView(self.compact, &self.pool)),
|
||||||
":midi-view" => Box::new(&self.editor),
|
":midi-view" => Box::new(&self.editor),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,10 +30,10 @@ impl Groovebox {
|
||||||
}
|
}
|
||||||
fn selector_view (&self) -> impl Content<TuiOut> + use<'_> {
|
fn selector_view (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
row!(
|
row!(
|
||||||
ClipSelected::play_phrase(&self.player),
|
self.player.play_status(),
|
||||||
ClipSelected::next_phrase(&self.player),
|
self.player.next_status(),
|
||||||
MidiEditClip(&self.editor),
|
self.editor.clip_status(),
|
||||||
MidiEditStatus(&self.editor),
|
self.editor.edit_status(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
fn sample_view (&self) -> impl Content<TuiOut> + use<'_> {
|
fn sample_view (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
|
|
@ -53,7 +53,7 @@ impl Groovebox {
|
||||||
PoolView(self.compact, &self.pool))
|
PoolView(self.compact, &self.pool))
|
||||||
}
|
}
|
||||||
fn sampler_view (&self) -> impl Content<TuiOut> + use<'_> {
|
fn sampler_view (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
let sampler_w = if self.compact { 4 } else { 11 };
|
let sampler_w = if self.compact { 4 } else { 40 };
|
||||||
let sampler_y = if self.compact { 1 } else { 0 };
|
let sampler_y = if self.compact { 1 } else { 0 };
|
||||||
Fixed::x(sampler_w, Push::y(sampler_y, Fill::y(
|
Fixed::x(sampler_w, Push::y(sampler_y, Fill::y(
|
||||||
SampleList::new(self.compact, &self.sampler, &self.editor))))
|
SampleList::new(self.compact, &self.sampler, &self.editor))))
|
||||||
|
|
|
||||||
|
|
@ -242,12 +242,12 @@ impl RegisterPort for Arc<RwLock<JackConnection>> {
|
||||||
/// Event enum for JACK events.
|
/// Event enum for JACK events.
|
||||||
pub enum JackEvent {
|
pub enum JackEvent {
|
||||||
ThreadInit,
|
ThreadInit,
|
||||||
Shutdown(ClientStatus, String),
|
Shutdown(ClientStatus, Arc<str>),
|
||||||
Freewheel(bool),
|
Freewheel(bool),
|
||||||
SampleRate(Frames),
|
SampleRate(Frames),
|
||||||
ClientRegistration(String, bool),
|
ClientRegistration(Arc<str>, bool),
|
||||||
PortRegistration(PortId, bool),
|
PortRegistration(PortId, bool),
|
||||||
PortRename(PortId, String, String),
|
PortRename(PortId, Arc<str>, Arc<str>),
|
||||||
PortsConnected(PortId, PortId, bool),
|
PortsConnected(PortId, PortId, bool),
|
||||||
GraphReorder,
|
GraphReorder,
|
||||||
XRun,
|
XRun,
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ impl<E: Engine, S, C: Command<S>> MenuBar<E, S, C> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub struct Menu<E: Engine, S, C: Command<S>> {
|
pub struct Menu<E: Engine, S, C: Command<S>> {
|
||||||
pub title: String,
|
pub title: Arc<str>,
|
||||||
pub items: Vec<MenuItem<E, S, C>>,
|
pub items: Vec<MenuItem<E, S, C>>,
|
||||||
pub index: Option<usize>,
|
pub index: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,7 @@ pub(crate) mod midi_note; pub(crate) use midi_note::*;
|
||||||
pub(crate) mod midi_range; pub(crate) use midi_range::*;
|
pub(crate) mod midi_range; pub(crate) use midi_range::*;
|
||||||
pub(crate) mod midi_point; pub(crate) use midi_point::*;
|
pub(crate) mod midi_point; pub(crate) use midi_point::*;
|
||||||
pub(crate) mod midi_view; pub(crate) use midi_view::*;
|
pub(crate) mod midi_view; pub(crate) use midi_view::*;
|
||||||
|
|
||||||
pub(crate) mod midi_editor; pub(crate) use midi_editor::*;
|
pub(crate) mod midi_editor; pub(crate) use midi_editor::*;
|
||||||
pub(crate) mod midi_status; pub(crate) use midi_status::*;
|
|
||||||
|
|
||||||
/// Add "all notes off" to the start of a buffer.
|
/// Add "all notes off" to the start of a buffer.
|
||||||
pub fn all_notes_off (output: &mut [Vec<Vec<u8>>]) {
|
pub fn all_notes_off (output: &mut [Vec<Vec<u8>>]) {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ pub trait HasMidiClip {
|
||||||
pub struct MidiClip {
|
pub struct MidiClip {
|
||||||
pub uuid: uuid::Uuid,
|
pub uuid: uuid::Uuid,
|
||||||
/// Name of phrase
|
/// Name of phrase
|
||||||
pub name: String,
|
pub name: Arc<str>,
|
||||||
/// Temporal resolution in pulses per quarter note
|
/// Temporal resolution in pulses per quarter note
|
||||||
pub ppq: usize,
|
pub ppq: usize,
|
||||||
/// Length of phrase in pulses
|
/// Length of phrase in pulses
|
||||||
|
|
@ -49,7 +49,7 @@ impl MidiClip {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
uuid: uuid::Uuid::new_v4(),
|
uuid: uuid::Uuid::new_v4(),
|
||||||
name: name.as_ref().to_string(),
|
name: name.as_ref().into(),
|
||||||
ppq: PPQ,
|
ppq: PPQ,
|
||||||
length,
|
length,
|
||||||
notes: notes.unwrap_or(vec![Vec::with_capacity(16);length]),
|
notes: notes.unwrap_or(vec![Vec::with_capacity(16);length]),
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,39 @@ impl MidiEditor {
|
||||||
self.mode.redraw();
|
self.mode.redraw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clip_status (&self) -> impl Content<TuiOut> + '_ {
|
||||||
|
let (color, name, length, looped) = if let Some(phrase) = self.phrase().as_ref().map(|p|p.read().unwrap()) {
|
||||||
|
(phrase.color, phrase.name.clone(), phrase.length, phrase.looped)
|
||||||
|
} else {
|
||||||
|
(ItemPalette::from(TuiTheme::g(64)), String::new().into(), 0, false)
|
||||||
|
};
|
||||||
|
row!(
|
||||||
|
FieldV(color, "Edit", format!("{name} ({length})")),
|
||||||
|
FieldV(color, "Loop", looped.to_string())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn edit_status (&self) -> impl Content<TuiOut> + '_ {
|
||||||
|
let (color, name, length, looped) = if let Some(phrase) = self.phrase().as_ref().map(|p|p.read().unwrap()) {
|
||||||
|
(phrase.color, phrase.name.clone(), phrase.length, phrase.looped)
|
||||||
|
} else {
|
||||||
|
(ItemPalette::from(TuiTheme::g(64)), String::new().into(), 0, false)
|
||||||
|
};
|
||||||
|
let time_point = self.time_point();
|
||||||
|
let time_start = self.time_start();
|
||||||
|
let time_end = self.time_end();
|
||||||
|
let time_axis = self.time_axis().get();
|
||||||
|
let time_zoom = self.time_zoom().get();
|
||||||
|
let time_lock = if self.time_lock().get() { "[lock]" } else { " " };
|
||||||
|
let time_field = FieldV(color, "Time", format!("{length}/{time_zoom}+{time_point} {time_lock}"));
|
||||||
|
|
||||||
|
let note_point = format!("{:>3}", self.note_point());
|
||||||
|
let note_name = format!("{:4}", Note::pitch_to_name(self.note_point()));
|
||||||
|
let note_len = format!("{:>4}", self.note_len());;;;
|
||||||
|
let note_field = FieldV(color, "Note", format!("{note_name} {note_point} {note_len}"));
|
||||||
|
Bsp::e(time_field, note_field,)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for MidiEditor {
|
impl std::fmt::Debug for MidiEditor {
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,9 @@ impl MidiPlayer {
|
||||||
midi_from: &[impl AsRef<str>],
|
midi_from: &[impl AsRef<str>],
|
||||||
midi_to: &[impl AsRef<str>],
|
midi_to: &[impl AsRef<str>],
|
||||||
) -> Usually<Self> {
|
) -> Usually<Self> {
|
||||||
let name = name.as_ref();
|
let name = name.as_ref();
|
||||||
|
let midi_in = jack.midi_in(&format!("M/{name}"), midi_from)?;
|
||||||
|
let midi_out = jack.midi_out(&format!("{name}/M"), midi_to)?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
clock: Clock::from(jack),
|
clock: Clock::from(jack),
|
||||||
play_phrase: None,
|
play_phrase: None,
|
||||||
|
|
@ -62,19 +64,20 @@ impl MidiPlayer {
|
||||||
overdub: false,
|
overdub: false,
|
||||||
|
|
||||||
notes_in: RwLock::new([false;128]).into(),
|
notes_in: RwLock::new([false;128]).into(),
|
||||||
midi_ins: vec![
|
midi_ins: vec![midi_in],
|
||||||
jack.midi_in(&format!("M/{name}"), midi_from)?,
|
midi_outs: vec![midi_out],
|
||||||
],
|
|
||||||
|
|
||||||
midi_outs: vec![
|
|
||||||
jack.midi_out(&format!("{name}/M"), midi_to)?,
|
|
||||||
],
|
|
||||||
notes_out: RwLock::new([false;128]).into(),
|
notes_out: RwLock::new([false;128]).into(),
|
||||||
reset: true,
|
reset: true,
|
||||||
|
|
||||||
note_buf: vec![0;8],
|
note_buf: vec![0;8],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
pub fn play_status (&self) -> impl Content<TuiOut> {
|
||||||
|
ClipSelected::play_phrase(self)
|
||||||
|
}
|
||||||
|
pub fn next_status (&self) -> impl Content<TuiOut> {
|
||||||
|
ClipSelected::next_phrase(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl std::fmt::Debug for MidiPlayer {
|
impl std::fmt::Debug for MidiPlayer {
|
||||||
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ pub enum PhrasePoolCommand {
|
||||||
Swap(usize, usize),
|
Swap(usize, usize),
|
||||||
Import(usize, PathBuf),
|
Import(usize, PathBuf),
|
||||||
Export(usize, PathBuf),
|
Export(usize, PathBuf),
|
||||||
SetName(usize, String),
|
SetName(usize, Arc<str>),
|
||||||
SetLength(usize, usize),
|
SetLength(usize, usize),
|
||||||
SetColor(usize, ItemColor),
|
SetColor(usize, ItemColor),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
pub struct MidiEditClip<'a>(pub &'a MidiEditor);
|
|
||||||
render!(TuiOut: (self: MidiEditClip<'a>) => {
|
|
||||||
let (color, name, length, looped) = if let Some(phrase) = self.0.phrase().as_ref().map(|p|p.read().unwrap()) {
|
|
||||||
(phrase.color, phrase.name.clone(), phrase.length, phrase.looped)
|
|
||||||
} else {
|
|
||||||
(ItemPalette::from(TuiTheme::g(64)), String::new(), 0, false)
|
|
||||||
};
|
|
||||||
row!(
|
|
||||||
FieldV(color, "Edit", format!("{name} ({length})")),
|
|
||||||
FieldV(color, "Loop", looped.to_string())
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
pub struct MidiEditStatus<'a>(pub &'a MidiEditor);
|
|
||||||
render!(TuiOut: (self: MidiEditStatus<'a>) => {
|
|
||||||
let (color, name, length, looped) = if let Some(phrase) = self.0.phrase().as_ref().map(|p|p.read().unwrap()) {
|
|
||||||
(phrase.color, phrase.name.clone(), phrase.length, phrase.looped)
|
|
||||||
} else {
|
|
||||||
(ItemPalette::from(TuiTheme::g(64)), String::new(), 0, false)
|
|
||||||
};
|
|
||||||
let time_point = self.0.time_point();
|
|
||||||
let time_start = self.0.time_start();
|
|
||||||
let time_end = self.0.time_end();
|
|
||||||
let time_axis = self.0.time_axis().get();
|
|
||||||
let time_zoom = self.0.time_zoom().get();
|
|
||||||
let time_lock = if self.0.time_lock().get() { "[lock]" } else { " " };
|
|
||||||
let time_field = FieldV(color, "Time", format!("{length}/{time_zoom}+{time_point} {time_lock}"));
|
|
||||||
|
|
||||||
let note_point = format!("{:>3}", self.0.note_point());
|
|
||||||
let note_name = format!("{:4}", Note::pitch_to_name(self.0.note_point()));
|
|
||||||
let note_len = format!("{:>4}", self.0.note_len());;;;
|
|
||||||
let note_field = FieldV(color, "Note", format!("{note_name} {note_point} {note_len}"));
|
|
||||||
Bsp::e(time_field, note_field,)
|
|
||||||
});
|
|
||||||
|
|
@ -4,7 +4,7 @@ use crate::*;
|
||||||
pub struct Mixer {
|
pub struct Mixer {
|
||||||
/// JACK client handle (needs to not be dropped for standalone mode to work).
|
/// JACK client handle (needs to not be dropped for standalone mode to work).
|
||||||
pub jack: Arc<RwLock<JackConnection>>,
|
pub jack: Arc<RwLock<JackConnection>>,
|
||||||
pub name: String,
|
pub name: Arc<str>,
|
||||||
pub tracks: Vec<MixerTrack>,
|
pub tracks: Vec<MixerTrack>,
|
||||||
pub selected_track: usize,
|
pub selected_track: usize,
|
||||||
pub selected_column: usize,
|
pub selected_column: usize,
|
||||||
|
|
@ -13,7 +13,7 @@ pub struct Mixer {
|
||||||
/// A mixer track.
|
/// A mixer track.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct MixerTrack {
|
pub struct MixerTrack {
|
||||||
pub name: String,
|
pub name: Arc<str>,
|
||||||
/// Inputs of 1st device
|
/// Inputs of 1st device
|
||||||
pub audio_ins: Vec<Port<AudioIn>>,
|
pub audio_ins: Vec<Port<AudioIn>>,
|
||||||
/// Outputs of last device
|
/// Outputs of last device
|
||||||
|
|
@ -47,7 +47,7 @@ impl Mixer {
|
||||||
impl MixerTrack {
|
impl MixerTrack {
|
||||||
pub fn new (name: &str) -> Usually<Self> {
|
pub fn new (name: &str) -> Usually<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
name: name.to_string(),
|
name: name.to_string().into(),
|
||||||
audio_ins: vec![],
|
audio_ins: vec![],
|
||||||
audio_outs: vec![],
|
audio_outs: vec![],
|
||||||
devices: vec![],
|
devices: vec![],
|
||||||
|
|
@ -267,7 +267,7 @@ const SYM_LV2: &str = "lv2";
|
||||||
from_edn!("mixer/track" => |jack: &Arc<RwLock<JackConnection>>, args| -> MixerTrack {
|
from_edn!("mixer/track" => |jack: &Arc<RwLock<JackConnection>>, args| -> MixerTrack {
|
||||||
let mut _gain = 0.0f64;
|
let mut _gain = 0.0f64;
|
||||||
let mut track = MixerTrack {
|
let mut track = MixerTrack {
|
||||||
name: String::new(),
|
name: "".into(),
|
||||||
audio_ins: vec![],
|
audio_ins: vec![],
|
||||||
audio_outs: vec![],
|
audio_outs: vec![],
|
||||||
devices: vec![],
|
devices: vec![],
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@ pub use self::lv2::LV2Plugin;
|
||||||
pub struct Plugin {
|
pub struct Plugin {
|
||||||
/// JACK client handle (needs to not be dropped for standalone mode to work).
|
/// JACK client handle (needs to not be dropped for standalone mode to work).
|
||||||
pub jack: Arc<RwLock<JackConnection>>,
|
pub jack: Arc<RwLock<JackConnection>>,
|
||||||
pub name: String,
|
pub name: Arc<str>,
|
||||||
pub path: Option<String>,
|
pub path: Option<Arc<str>>,
|
||||||
pub plugin: Option<PluginKind>,
|
pub plugin: Option<PluginKind>,
|
||||||
pub selected: usize,
|
pub selected: usize,
|
||||||
pub mapping: bool,
|
pub mapping: bool,
|
||||||
|
|
@ -47,7 +47,7 @@ impl Plugin {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
jack: jack.clone(),
|
jack: jack.clone(),
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
path: Some(String::from(path)),
|
path: Some(String::from(path).into()),
|
||||||
plugin: Some(PluginKind::LV2(LV2Plugin::new(path)?)),
|
plugin: Some(PluginKind::LV2(LV2Plugin::new(path)?)),
|
||||||
selected: 0,
|
selected: 0,
|
||||||
mapping: false,
|
mapping: false,
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ pub struct PoolModel {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum PoolMode {
|
pub enum PoolMode {
|
||||||
/// Renaming a pattern
|
/// Renaming a pattern
|
||||||
Rename(usize, String),
|
Rename(usize, Arc<str>),
|
||||||
/// Editing the length of a pattern
|
/// Editing the length of a pattern
|
||||||
Length(usize, usize, PhraseLengthFocus),
|
Length(usize, usize, PhraseLengthFocus),
|
||||||
/// Load phrase from disk
|
/// Load phrase from disk
|
||||||
|
|
@ -147,10 +147,10 @@ fn to_phrases_command (state: &PoolModel, input: &Event) -> Option<PoolCommand>
|
||||||
return None
|
return None
|
||||||
},
|
},
|
||||||
kpat!(Char('a')) | kpat!(Shift-Char('A')) => Cmd::Phrase(PhrasePoolCommand::Add(count, MidiClip::new(
|
kpat!(Char('a')) | kpat!(Shift-Char('A')) => Cmd::Phrase(PhrasePoolCommand::Add(count, MidiClip::new(
|
||||||
String::from("Clip"), true, 4 * PPQ, None, Some(ItemPalette::random())
|
"Clip", true, 4 * PPQ, None, Some(ItemPalette::random())
|
||||||
))),
|
))),
|
||||||
kpat!(Char('i')) => Cmd::Phrase(PhrasePoolCommand::Add(index + 1, MidiClip::new(
|
kpat!(Char('i')) => Cmd::Phrase(PhrasePoolCommand::Add(index + 1, MidiClip::new(
|
||||||
String::from("Clip"), true, 4 * PPQ, None, Some(ItemPalette::random())
|
"Clip", true, 4 * PPQ, None, Some(ItemPalette::random())
|
||||||
))),
|
))),
|
||||||
kpat!(Char('d')) | kpat!(Shift-Char('D')) => {
|
kpat!(Char('d')) | kpat!(Shift-Char('D')) => {
|
||||||
let mut phrase = state.phrases()[index].read().unwrap().duplicate();
|
let mut phrase = state.phrases()[index].read().unwrap().duplicate();
|
||||||
|
|
|
||||||
|
|
@ -30,14 +30,14 @@ impl PhraseLength {
|
||||||
pub fn ticks (&self) -> usize {
|
pub fn ticks (&self) -> usize {
|
||||||
self.pulses % self.ppq
|
self.pulses % self.ppq
|
||||||
}
|
}
|
||||||
pub fn bars_string (&self) -> String {
|
pub fn bars_string (&self) -> Arc<str> {
|
||||||
format!("{}", self.bars())
|
format!("{}", self.bars()).into()
|
||||||
}
|
}
|
||||||
pub fn beats_string (&self) -> String {
|
pub fn beats_string (&self) -> Arc<str> {
|
||||||
format!("{}", self.beats())
|
format!("{}", self.beats()).into()
|
||||||
}
|
}
|
||||||
pub fn ticks_string (&self) -> String {
|
pub fn ticks_string (&self) -> Arc<str> {
|
||||||
format!("{:>02}", self.ticks())
|
format!("{:>02}", self.ticks()).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ pub enum PhraseRenameCommand {
|
||||||
Begin,
|
Begin,
|
||||||
Cancel,
|
Cancel,
|
||||||
Confirm,
|
Confirm,
|
||||||
Set(String),
|
Set(Arc<str>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Command<PoolModel> for PhraseRenameCommand {
|
impl Command<PoolModel> for PhraseRenameCommand {
|
||||||
|
|
@ -16,7 +16,7 @@ impl Command<PoolModel> for PhraseRenameCommand {
|
||||||
Some(PoolMode::Rename(phrase, ref mut old_name)) => match self {
|
Some(PoolMode::Rename(phrase, ref mut old_name)) => match self {
|
||||||
Set(s) => {
|
Set(s) => {
|
||||||
state.phrases()[phrase].write().unwrap().name = s;
|
state.phrases()[phrase].write().unwrap().name = s;
|
||||||
return Ok(Some(Self::Set(old_name.clone())))
|
return Ok(Some(Self::Set(old_name.clone().into())))
|
||||||
},
|
},
|
||||||
Confirm => {
|
Confirm => {
|
||||||
let old_name = old_name.clone();
|
let old_name = old_name.clone();
|
||||||
|
|
@ -24,7 +24,7 @@ impl Command<PoolModel> for PhraseRenameCommand {
|
||||||
return Ok(Some(Self::Set(old_name)))
|
return Ok(Some(Self::Set(old_name)))
|
||||||
},
|
},
|
||||||
Cancel => {
|
Cancel => {
|
||||||
state.phrases()[phrase].write().unwrap().name = old_name.clone();
|
state.phrases()[phrase].write().unwrap().name = old_name.clone().into();
|
||||||
},
|
},
|
||||||
_ => unreachable!()
|
_ => unreachable!()
|
||||||
},
|
},
|
||||||
|
|
@ -40,14 +40,14 @@ impl InputToCommand<Event, PoolModel> for PhraseRenameCommand {
|
||||||
if let Some(PoolMode::Rename(_, ref old_name)) = state.phrases_mode() {
|
if let Some(PoolMode::Rename(_, ref old_name)) = state.phrases_mode() {
|
||||||
Some(match input {
|
Some(match input {
|
||||||
kpat!(Char(c)) => {
|
kpat!(Char(c)) => {
|
||||||
let mut new_name = old_name.clone();
|
let mut new_name = old_name.clone().to_string();
|
||||||
new_name.push(*c);
|
new_name.push(*c);
|
||||||
Self::Set(new_name)
|
Self::Set(new_name.into())
|
||||||
},
|
},
|
||||||
kpat!(Backspace) => {
|
kpat!(Backspace) => {
|
||||||
let mut new_name = old_name.clone();
|
let mut new_name = old_name.clone().to_string();
|
||||||
new_name.pop();
|
new_name.pop();
|
||||||
Self::Set(new_name)
|
Self::Set(new_name.into())
|
||||||
},
|
},
|
||||||
kpat!(Enter) => Self::Confirm,
|
kpat!(Enter) => Self::Confirm,
|
||||||
kpat!(Esc) => Self::Cancel,
|
kpat!(Esc) => Self::Cancel,
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ use crate::*;
|
||||||
|
|
||||||
pub struct ClipSelected {
|
pub struct ClipSelected {
|
||||||
pub(crate) title: &'static str,
|
pub(crate) title: &'static str,
|
||||||
pub(crate) name: String,
|
pub(crate) name: Arc<str>,
|
||||||
pub(crate) color: ItemPalette,
|
pub(crate) color: ItemPalette,
|
||||||
pub(crate) time: String,
|
pub(crate) time: Arc<str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
render!(TuiOut: (self: ClipSelected) =>
|
render!(TuiOut: (self: ClipSelected) =>
|
||||||
|
|
@ -16,9 +16,9 @@ impl ClipSelected {
|
||||||
pub fn play_phrase <T: HasPlayPhrase + HasClock> (state: &T) -> Self {
|
pub fn play_phrase <T: HasPlayPhrase + HasClock> (state: &T) -> Self {
|
||||||
let (name, color) = if let Some((_, Some(phrase))) = state.play_phrase() {
|
let (name, color) = if let Some((_, Some(phrase))) = state.play_phrase() {
|
||||||
let MidiClip { ref name, color, .. } = *phrase.read().unwrap();
|
let MidiClip { ref name, color, .. } = *phrase.read().unwrap();
|
||||||
(name.clone(), color)
|
(name.clone().into(), color)
|
||||||
} else {
|
} else {
|
||||||
("".to_string(), TuiTheme::g(64).into())
|
("".to_string().into(), TuiTheme::g(64).into())
|
||||||
};
|
};
|
||||||
Self {
|
Self {
|
||||||
title: "Now",
|
title: "Now",
|
||||||
|
|
@ -28,14 +28,14 @@ impl ClipSelected {
|
||||||
.map(|(times, time)|format!("{:>3}x {:>}",
|
.map(|(times, time)|format!("{:>3}x {:>}",
|
||||||
times+1.0,
|
times+1.0,
|
||||||
state.clock().timebase.format_beats_1(time)))
|
state.clock().timebase.format_beats_1(time)))
|
||||||
.unwrap_or_else(||String::from(" "))
|
.unwrap_or_else(||String::from(" ")).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shows next phrase with beats remaining until switchover
|
/// Shows next phrase with beats remaining until switchover
|
||||||
pub fn next_phrase <T: HasPlayPhrase> (state: &T) -> Self {
|
pub fn next_phrase <T: HasPlayPhrase> (state: &T) -> Self {
|
||||||
let mut time = String::from("--.-.--");
|
let mut time: Arc<str> = String::from("--.-.--").into();
|
||||||
let mut name = String::from("");
|
let mut name: Arc<str> = String::from("").into();
|
||||||
let mut color = ItemPalette::from(TuiTheme::g(64));
|
let mut color = ItemPalette::from(TuiTheme::g(64));
|
||||||
if let Some((t, Some(phrase))) = state.next_phrase() {
|
if let Some((t, Some(phrase))) = state.next_phrase() {
|
||||||
let phrase = phrase.read().unwrap();
|
let phrase = phrase.read().unwrap();
|
||||||
|
|
@ -50,7 +50,7 @@ impl ClipSelected {
|
||||||
} else {
|
} else {
|
||||||
String::new()
|
String::new()
|
||||||
}
|
}
|
||||||
}
|
}.into()
|
||||||
} else if let Some((t, Some(phrase))) = state.play_phrase() {
|
} else if let Some((t, Some(phrase))) = state.play_phrase() {
|
||||||
let phrase = phrase.read().unwrap();
|
let phrase = phrase.read().unwrap();
|
||||||
if phrase.looped {
|
if phrase.looped {
|
||||||
|
|
@ -59,10 +59,12 @@ impl ClipSelected {
|
||||||
let target = t.pulse.get() + phrase.length as f64;
|
let target = t.pulse.get() + phrase.length as f64;
|
||||||
let current = state.clock().playhead.pulse.get();
|
let current = state.clock().playhead.pulse.get();
|
||||||
if target > current {
|
if target > current {
|
||||||
time = format!("-{:>}", state.clock().timebase.format_beats_0(target - current))
|
time = format!("-{:>}", state.clock().timebase.format_beats_0(
|
||||||
|
target - current
|
||||||
|
)).into()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
name = "Stop".to_string();
|
name = "Stop".to_string().into();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Self { title: "Next", time, name, color, }
|
Self { title: "Next", time, name, color, }
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ render!(TuiOut: (self: PoolView<'a>) => {
|
||||||
let Self(compact, model) = self;
|
let Self(compact, model) = self;
|
||||||
let PoolModel { phrases, mode, .. } = self.1;
|
let PoolModel { phrases, mode, .. } = self.1;
|
||||||
let color = self.1.phrase().read().unwrap().color;
|
let color = self.1.phrase().read().unwrap().color;
|
||||||
Outer(
|
Bsp::b(Repeat(" "), Tui::bg(color.darkest.rgb, Outer(
|
||||||
Style::default().fg(color.dark.rgb).bg(color.darkest.rgb)
|
Style::default().fg(color.dark.rgb).bg(color.darkest.rgb)
|
||||||
).enclose(Map::new(||model.phrases().iter(), |clip, i|{
|
).enclose(Map::new(||model.phrases().iter(), |clip, i|{
|
||||||
let item_height = 1;
|
let item_height = 1;
|
||||||
|
|
@ -20,5 +20,5 @@ render!(TuiOut: (self: PoolView<'a>) => {
|
||||||
Align::w(When(selected, Tui::bold(true, Tui::fg(TuiTheme::g(255), "▶")))),
|
Align::w(When(selected, Tui::bold(true, Tui::fg(TuiTheme::g(255), "▶")))),
|
||||||
Align::e(When(selected, Tui::bold(true, Tui::fg(TuiTheme::g(255), "◀")))),
|
Align::e(When(selected, Tui::bold(true, Tui::fg(TuiTheme::g(255), "◀")))),
|
||||||
)))
|
)))
|
||||||
}))
|
}))))
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use super::*;
|
||||||
/// A sound sample.
|
/// A sound sample.
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct Sample {
|
pub struct Sample {
|
||||||
pub name: String,
|
pub name: Arc<str>,
|
||||||
pub start: usize,
|
pub start: usize,
|
||||||
pub end: usize,
|
pub end: usize,
|
||||||
pub channels: Vec<Vec<f32>>,
|
pub channels: Vec<Vec<f32>>,
|
||||||
|
|
@ -24,8 +24,8 @@ pub struct Sample {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sample {
|
impl Sample {
|
||||||
pub fn new (name: &str, start: usize, end: usize, channels: Vec<Vec<f32>>) -> Self {
|
pub fn new (name: impl AsRef<str>, start: usize, end: usize, channels: Vec<Vec<f32>>) -> Self {
|
||||||
Self { name: name.to_string(), start, end, channels, rate: None, gain: 1.0 }
|
Self { name: name.as_ref().into(), start, end, channels, rate: None, gain: 1.0 }
|
||||||
}
|
}
|
||||||
pub fn play (sample: &Arc<RwLock<Self>>, after: usize, velocity: &u7) -> Voice {
|
pub fn play (sample: &Arc<RwLock<Self>>, after: usize, velocity: &u7) -> Voice {
|
||||||
Voice {
|
Voice {
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,7 @@ render!(TuiOut: (self: SampleList<'a>) => {
|
||||||
let note_pt = editor.note_point();
|
let note_pt = editor.note_point();
|
||||||
let note_hi = editor.note_hi();
|
let note_hi = editor.note_hi();
|
||||||
Outer(Style::default().fg(TuiTheme::g(96))).enclose(Map::new(move||(note_lo..=note_hi).rev(), move|note, i| {
|
Outer(Style::default().fg(TuiTheme::g(96))).enclose(Map::new(move||(note_lo..=note_hi).rev(), move|note, i| {
|
||||||
|
|
||||||
let offset = |a|Push::y(i as u16, Align::n(Fixed::y(1, Fill::x(a))));
|
let offset = |a|Push::y(i as u16, Align::n(Fixed::y(1, Fill::x(a))));
|
||||||
|
|
||||||
let mut bg = if note == note_pt { TuiTheme::g(64) } else { Color::Reset };
|
let mut bg = if note == note_pt { TuiTheme::g(64) } else { Color::Reset };
|
||||||
let mut fg = TuiTheme::g(160);
|
let mut fg = TuiTheme::g(160);
|
||||||
if sampler.mapped[note].is_some() {
|
if sampler.mapped[note].is_some() {
|
||||||
|
|
@ -33,13 +31,20 @@ render!(TuiOut: (self: SampleList<'a>) => {
|
||||||
fg = Color::Rgb(224,64,32)
|
fg = Color::Rgb(224,64,32)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let label = if *compact {
|
||||||
offset(Tui::fg_bg(fg, bg, format!("{note:3} {}", if *compact {
|
|
||||||
String::default()
|
String::default()
|
||||||
} else if let Some(sample) = &sampler.mapped[note] {
|
} else if let Some(sample) = &sampler.mapped[note] {
|
||||||
sample.read().unwrap().name.clone()
|
let sample = sample.read().unwrap();
|
||||||
|
format!("{:8} {:3} {:6}-{:6}/{:6}",
|
||||||
|
sample.name,
|
||||||
|
sample.gain,
|
||||||
|
sample.start,
|
||||||
|
sample.end,
|
||||||
|
sample.channels[0].len()
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
String::from("(none)")
|
String::from("(none)")
|
||||||
})))
|
};
|
||||||
|
offset(Tui::fg_bg(fg, bg, format!("{note:3} {}", label)))
|
||||||
}))
|
}))
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -57,16 +57,21 @@ impl Sequencer {
|
||||||
Fill::x(Fixed::y(2, Align::x(TransportView::new(true, &self.player.clock))))
|
Fill::x(Fixed::y(2, Align::x(TransportView::new(true, &self.player.clock))))
|
||||||
}
|
}
|
||||||
fn status_view (&self) -> impl Content<TuiOut> + use<'_> {
|
fn status_view (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
let edit_clip = MidiEditClip(&self.editor);
|
row!(
|
||||||
let selectors = When(self.selectors, Bsp::e(ClipSelected::play_phrase(&self.player), ClipSelected::next_phrase(&self.player)));
|
When(self.selectors, Bsp::e(
|
||||||
row!(selectors, edit_clip, MidiEditStatus(&self.editor))
|
self.player.play_status(),
|
||||||
|
self.player.next_status(),
|
||||||
|
)),
|
||||||
|
self.editor.clip_status(),
|
||||||
|
self.editor.edit_status(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
fn selector_view (&self) -> impl Content<TuiOut> + use<'_> {
|
fn selector_view (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
row!(
|
row!(
|
||||||
ClipSelected::play_phrase(&self.player),
|
self.player.play_status(),
|
||||||
ClipSelected::next_phrase(&self.player),
|
self.player.next_status(),
|
||||||
MidiEditClip(&self.editor),
|
self.editor.clip_status(),
|
||||||
MidiEditStatus(&self.editor),
|
self.editor.edit_status(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
fn pool_view (&self) -> impl Content<TuiOut> + use<'_> {
|
fn pool_view (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
|
|
@ -180,8 +185,8 @@ command!(|self: SequencerCommand, state: Sequencer|match self {
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SequencerStatus {
|
pub struct SequencerStatus {
|
||||||
pub(crate) width: usize,
|
pub(crate) width: usize,
|
||||||
pub(crate) cpu: Option<String>,
|
pub(crate) cpu: Option<Arc<str>>,
|
||||||
pub(crate) size: String,
|
pub(crate) size: Arc<str>,
|
||||||
pub(crate) playing: bool,
|
pub(crate) playing: bool,
|
||||||
}
|
}
|
||||||
from!(|state:&Sequencer|SequencerStatus = {
|
from!(|state:&Sequencer|SequencerStatus = {
|
||||||
|
|
@ -192,8 +197,8 @@ from!(|state:&Sequencer|SequencerStatus = {
|
||||||
Self {
|
Self {
|
||||||
width,
|
width,
|
||||||
playing: state.clock.is_rolling(),
|
playing: state.clock.is_rolling(),
|
||||||
cpu: state.perf.percentage().map(|cpu|format!("│{cpu:.01}%")),
|
cpu: state.perf.percentage().map(|cpu|format!("│{cpu:.01}%").into()),
|
||||||
size: format!("{}x{}│", width, state.size.h()),
|
size: format!("{}x{}│", width, state.size.h()).into(),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
render!(TuiOut: (self: SequencerStatus) => Fixed::y(2, lay!(
|
render!(TuiOut: (self: SequencerStatus) => Fixed::y(2, lay!(
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,15 @@ impl Content<TuiOut> for String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Content<TuiOut> for Arc<str> {
|
||||||
|
fn layout (&self, to: [u16;4]) -> [u16;4] {
|
||||||
|
to.center_xy([self.chars().count() as u16, 1])
|
||||||
|
}
|
||||||
|
fn render (&self, to: &mut TuiOut) {
|
||||||
|
to.blit(self, to.area.x(), to.area.y(), None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Repeat<'a>(pub &'a str);
|
pub struct Repeat<'a>(pub &'a str);
|
||||||
|
|
||||||
impl Content<TuiOut> for Repeat<'_> {
|
impl Content<TuiOut> for Repeat<'_> {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue