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.width = cli.track_width;
|
||||
let name = track.name.read().unwrap();
|
||||
let name = track.name.clone();
|
||||
track.player.midi_ins.push(
|
||||
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",
|
||||
"itertools 0.14.0",
|
||||
"konst",
|
||||
"tek_layout",
|
||||
"tek_tui",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tek_engine"
|
||||
name = "tek_input"
|
||||
version = "0.2.0"
|
||||
|
||||
[[package]]
|
||||
name = "tek_layout"
|
||||
name = "tek_output"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"tek_engine",
|
||||
"tek_edn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -795,8 +794,8 @@ dependencies = [
|
|||
"rand",
|
||||
"ratatui",
|
||||
"tek_edn",
|
||||
"tek_engine",
|
||||
"tek_layout",
|
||||
"tek_input",
|
||||
"tek_output",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ pub enum Token<'a> {
|
|||
impl<'a> Token<'a> {
|
||||
pub fn chomp (source: &'a str) -> Result<(&'a str, Self), ParseError> {
|
||||
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() {
|
||||
state = match state {
|
||||
// must begin expression
|
||||
|
|
@ -29,21 +30,33 @@ impl<'a> Token<'a> {
|
|||
Key(_, _, 0) => unreachable!(),
|
||||
Num(source, index, length) => match c {
|
||||
'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))
|
||||
},
|
||||
Sym(source, index, length) => match c {
|
||||
'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))
|
||||
},
|
||||
Key(source, index, length) => match c {
|
||||
'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))
|
||||
},
|
||||
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))
|
||||
},
|
||||
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_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 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 {
|
||||
Self {
|
||||
_engine: PhantomData::default(),
|
||||
|
|
|
|||
109
src/arranger.rs
109
src/arranger.rs
|
|
@ -94,6 +94,11 @@ from_jack!(|jack| Arranger {
|
|||
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) => {
|
||||
//let pool_w = if self.pool.visible { self.splits[1] } else { 0 };
|
||||
//let color = self.color;
|
||||
|
|
@ -108,58 +113,54 @@ from_jack!(|jack| Arranger {
|
|||
//Self::render_mode(self))))), Fill::y(&"fixme: self.editor"))))));
|
||||
//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
|
||||
#[derive(Clone)]
|
||||
pub struct ArrangerStatus {
|
||||
pub(crate) width: usize,
|
||||
pub(crate) cpu: Option<String>,
|
||||
pub(crate) size: String,
|
||||
pub(crate) playing: bool,
|
||||
}
|
||||
from!(|state:&Arranger|ArrangerStatus = {
|
||||
let samples = state.clock.chunk.load(Relaxed);
|
||||
let rate = state.clock.timebase.sr.get();
|
||||
let buffer = samples as f64 / rate;
|
||||
let width = state.size.w();
|
||||
Self {
|
||||
width,
|
||||
playing: state.clock.is_rolling(),
|
||||
cpu: state.perf.percentage().map(|cpu|format!("│{cpu:.01}%")),
|
||||
size: format!("{}x{}│", width, state.size.h()),
|
||||
}
|
||||
});
|
||||
render!(TuiOut: (self: ArrangerStatus) => Fixed::y(2, lay!(
|
||||
Self::help(),
|
||||
Fill::xy(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))),
|
||||
)));
|
||||
impl ArrangerStatus {
|
||||
fn help () -> impl Content<TuiOut> {
|
||||
let single = |binding, command|row!(" ", col!(
|
||||
Tui::fg(TuiTheme::yellow(), binding),
|
||||
command
|
||||
));
|
||||
let double = |(b1, c1), (b2, c2)|col!(
|
||||
row!(" ", Tui::fg(TuiTheme::yellow(), b1), " ", c1,),
|
||||
row!(" ", Tui::fg(TuiTheme::yellow(), b2), " ", c2,),
|
||||
);
|
||||
Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(50), row!(
|
||||
single("SPACE", "play/pause"),
|
||||
single(" Ctrl", " scroll"),
|
||||
single(" ▲▼▶◀", " cell"),
|
||||
double(("p", "put"), ("g", "get")),
|
||||
double(("q", "enqueue"), ("e", "edit")),
|
||||
single(" wsad", " note"),
|
||||
double(("a", "append"), ("s", "set"),),
|
||||
double((",.", "length"), ("<>", "triplet"),),
|
||||
double(("[]", "phrase"), ("{}", "order"),),
|
||||
))
|
||||
}
|
||||
fn stats (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
row!(&self.cpu, &self.size)
|
||||
}
|
||||
}
|
||||
///// Status bar for arranger app
|
||||
//#[derive(Clone)]
|
||||
//pub struct ArrangerStatus {
|
||||
//pub(crate) width: usize,
|
||||
//pub(crate) cpu: Option<Arc<str>>,
|
||||
//pub(crate) size: Arc<str>,
|
||||
//pub(crate) playing: bool,
|
||||
//}
|
||||
//from!(|state:&Arranger|ArrangerStatus = {
|
||||
//let samples = state.clock.chunk.load(Relaxed);
|
||||
//let rate = state.clock.timebase.sr.get();
|
||||
//let buffer = samples as f64 / rate;
|
||||
//let width = state.size.w();
|
||||
//Self {
|
||||
//width,
|
||||
//playing: state.clock.is_rolling(),
|
||||
//cpu: state.perf.percentage().map(|cpu|format!("│{cpu:.01}%").into()),
|
||||
//size: format!("{}x{}│", width, state.size.h()).into(),
|
||||
//}
|
||||
//});
|
||||
//render!(TuiOut: (self: ArrangerStatus) => Fixed::y(2, lay!(
|
||||
//Self::help(),
|
||||
//Fill::xy(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))),
|
||||
//)));
|
||||
//impl ArrangerStatus {
|
||||
//fn help () -> impl Content<TuiOut> {
|
||||
//let single = |binding, command|row!(" ", col!(
|
||||
//Tui::fg(TuiTheme::yellow(), binding),
|
||||
//command
|
||||
//));
|
||||
//let double = |(b1, c1), (b2, c2)|col!(
|
||||
//row!(" ", Tui::fg(TuiTheme::yellow(), b1), " ", c1,),
|
||||
//row!(" ", Tui::fg(TuiTheme::yellow(), b2), " ", c2,),
|
||||
//);
|
||||
//Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(50), row!(
|
||||
//single("SPACE", "play/pause"),
|
||||
//single(" Ctrl", " scroll"),
|
||||
//single(" ▲▼▶◀", " cell"),
|
||||
//double(("p", "put"), ("g", "get")),
|
||||
//double(("q", "enqueue"), ("e", "edit")),
|
||||
//single(" wsad", " note"),
|
||||
//double(("a", "append"), ("s", "set"),),
|
||||
//double((",.", "length"), ("<>", "triplet"),),
|
||||
//double(("[]", "phrase"), ("{}", "order"),),
|
||||
//))
|
||||
//}
|
||||
//fn stats (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
//row!(&self.cpu, &self.size)
|
||||
//}
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,8 @@ impl Arranger {
|
|||
pub fn scene_add (&mut self, name: Option<&str>, color: Option<ItemPalette>)
|
||||
-> Usually<&mut ArrangerScene>
|
||||
{
|
||||
let name = name.map_or_else(||self.scene_default_name(), |x|x.to_string());
|
||||
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()],
|
||||
color: color.unwrap_or_else(ItemPalette::random),
|
||||
};
|
||||
|
|
@ -16,8 +15,8 @@ impl Arranger {
|
|||
pub fn scene_del (&mut self, index: usize) {
|
||||
todo!("delete scene");
|
||||
}
|
||||
fn scene_default_name (&self) -> String {
|
||||
format!("Sc{:3>}", self.scenes.len() + 1)
|
||||
fn scene_default_name (&self) -> Arc<str> {
|
||||
format!("Sc{:3>}", self.scenes.len() + 1).into()
|
||||
}
|
||||
pub fn selected_scene (&self) -> Option<&ArrangerScene> {
|
||||
self.selected.scene().and_then(|s|self.scenes.get(s))
|
||||
|
|
@ -28,14 +27,14 @@ impl Arranger {
|
|||
}
|
||||
#[derive(Default, Debug, Clone)] pub struct ArrangerScene {
|
||||
/// Name of scene
|
||||
pub(crate) name: Arc<RwLock<String>>,
|
||||
pub(crate) name: Arc<str>,
|
||||
/// Clips in scene, one per track
|
||||
pub(crate) clips: Vec<Option<Arc<RwLock<MidiClip>>>>,
|
||||
/// Identifying color of scene
|
||||
pub(crate) color: ItemPalette,
|
||||
}
|
||||
impl ArrangerScene {
|
||||
pub fn name (&self) -> &Arc<RwLock<String>> {
|
||||
pub fn name (&self) -> &Arc<str> {
|
||||
&self.name
|
||||
}
|
||||
pub fn clips (&self) -> &Vec<Option<Arc<RwLock<MidiClip>>>> {
|
||||
|
|
@ -45,7 +44,7 @@ impl ArrangerScene {
|
|||
self.color
|
||||
}
|
||||
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
|
||||
pub fn pulses (&self) -> usize {
|
||||
|
|
|
|||
|
|
@ -21,17 +21,13 @@ impl ArrangerSelection {
|
|||
&self,
|
||||
tracks: &[ArrangerTrack],
|
||||
scenes: &[ArrangerScene],
|
||||
) -> String {
|
||||
) -> Arc<str> {
|
||||
format!("Selected: {}", match self {
|
||||
Self::Mix => "Everything".to_string(),
|
||||
Self::Track(t) => match tracks.get(*t) {
|
||||
Some(track) => format!("T{t}: {}", &track.name.read().unwrap()),
|
||||
None => "T??".into(),
|
||||
},
|
||||
Self::Scene(s) => match scenes.get(*s) {
|
||||
Some(scene) => format!("S{s}: {}", &scene.name.read().unwrap()),
|
||||
None => "S??".into(),
|
||||
},
|
||||
Self::Track(t) => tracks.get(*t).map(|track|format!("T{t}: {}", &track.name))
|
||||
.unwrap_or_else(||"T??".into()),
|
||||
Self::Scene(s) => scenes.get(*s).map(|scene|format!("S{s}: {}", &scene.name))
|
||||
.unwrap_or_else(||"S??".into()),
|
||||
Self::Clip(t, s) => match (tracks.get(*t), scenes.get(*s)) {
|
||||
(Some(_), Some(scene)) => match scene.clip(*t) {
|
||||
Some(clip) => format!("T{t} S{s} C{}", &clip.read().unwrap().name),
|
||||
|
|
@ -39,7 +35,7 @@ impl ArrangerSelection {
|
|||
},
|
||||
_ => format!("T{t} S{s}: Empty"),
|
||||
}
|
||||
})
|
||||
}).into()
|
||||
}
|
||||
pub fn track (&self) -> Option<usize> {
|
||||
use ArrangerSelection::*;
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
use crate::*;
|
||||
impl Arranger {
|
||||
pub fn track_next_name (&self) -> String {
|
||||
format!("Tr{}", self.tracks.len() + 1)
|
||||
pub fn track_next_name (&self) -> Arc<str> {
|
||||
format!("Tr{:02}", self.tracks.len() + 1).into()
|
||||
}
|
||||
pub fn track_add (&mut self, name: Option<&str>, color: Option<ItemPalette>)
|
||||
-> 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 {
|
||||
width: name.len() + 2,
|
||||
name: Arc::new(name.into()),
|
||||
color: color.unwrap_or_else(ItemPalette::random),
|
||||
player: MidiPlayer::from(&self.clock),
|
||||
name,
|
||||
};
|
||||
self.tracks.push(track);
|
||||
let index = self.tracks.len() - 1;
|
||||
|
|
@ -26,7 +26,7 @@ impl Arranger {
|
|||
}
|
||||
#[derive(Debug)] pub struct ArrangerTrack {
|
||||
/// Name of track
|
||||
pub name: Arc<RwLock<String>>,
|
||||
pub name: Arc<str>,
|
||||
/// Preferred width of track column
|
||||
pub width: usize,
|
||||
/// Identifying color of track
|
||||
|
|
@ -38,7 +38,7 @@ has_clock!(|self:ArrangerTrack|self.player.clock());
|
|||
has_player!(|self:ArrangerTrack|self.player);
|
||||
impl ArrangerTrack {
|
||||
/// Name of track
|
||||
pub fn name (&self) -> &Arc<RwLock<String>> {
|
||||
pub fn name (&self) -> &Arc<str> {
|
||||
&self.name
|
||||
}
|
||||
/// Preferred width of track column
|
||||
|
|
@ -54,7 +54,7 @@ impl ArrangerTrack {
|
|||
self.color
|
||||
}
|
||||
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) {
|
||||
*self.width_mut() += 1;
|
||||
|
|
|
|||
|
|
@ -3,27 +3,25 @@ pub(crate) const HEADER_H: u16 = 0; // 5
|
|||
pub(crate) const SCENES_W_OFFSET: u16 = 0;
|
||||
render!(TuiOut: (self: Arranger) => {
|
||||
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 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 editor = Fixed::y(20, enclosed(&self.editor));
|
||||
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||[
|
||||
(0, 2, self.output_row_header(), self.output_row_cells()),
|
||||
(2, 2, self.elapsed_row_header(), self.elapsed_row_cells()),
|
||||
(4, 2, self.next_row_header(), self.next_row_cells()),
|
||||
(2, 3, self.elapsed_row_header(), self.elapsed_row_cells()),
|
||||
(4, 3, self.next_row_header(), self.next_row_cells()),
|
||||
(6, 3, self.track_row_header(), self.track_row_cells()),
|
||||
(9, 20, self.scene_row_headers(), self.scene_row_cells()),
|
||||
(29, 2, self.input_row_header(), self.input_row_cells()),
|
||||
(8, 20, self.scene_row_headers(), self.scene_row_cells()),
|
||||
(25, 2, self.input_row_header(), self.input_row_cells()),
|
||||
].into_iter(),
|
||||
move|(y, h, header, cells), index|map_south(y, h, Fill::x(Align::w(Bsp::e(
|
||||
Fixed::xy(scenes_w, h, header),
|
||||
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 {
|
||||
|
|
@ -63,7 +61,7 @@ impl Arranger {
|
|||
(move||Fixed::y(2, Map::new(||self.tracks_with_widths(), move|(_, track, x1, x2), i| {
|
||||
let w = (x2 - x1) as u16;
|
||||
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)))
|
||||
})).boxed()).into()
|
||||
}
|
||||
|
|
@ -73,19 +71,17 @@ impl Arranger {
|
|||
}
|
||||
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| {
|
||||
let color = track.color();
|
||||
//let color = track.color();
|
||||
let color: ItemPalette = track.color().dark.into();
|
||||
let timebase = self.clock().timebase();
|
||||
let elapsed = {
|
||||
let mut result = String::new();
|
||||
let value = Tui::fg_bg(color.lightest.rgb, color.base.rgb,
|
||||
if let Some((_, Some(phrase))) = track.player.play_phrase().as_ref() {
|
||||
let length = phrase.read().unwrap().length;
|
||||
let elapsed = track.player.pulses_since_start().unwrap() as usize;
|
||||
result = format!("+{:>}", timebase.format_beats_1_short((elapsed % length) as f64))
|
||||
}
|
||||
result
|
||||
};
|
||||
let value = Tui::fg_bg(color.lightest.rgb, color.base.rgb, elapsed);
|
||||
format!("+{:>}", timebase.format_beats_1_short((elapsed % length) as f64))
|
||||
} else {
|
||||
String::new()
|
||||
});
|
||||
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))
|
||||
})).boxed()).into()
|
||||
|
|
@ -109,12 +105,14 @@ impl Arranger {
|
|||
(||Tui::bold(true, Tui::fg(TuiTheme::g(128), "Track")).boxed()).into()
|
||||
}
|
||||
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 name = format!(" {}", &track.name);
|
||||
Tui::bg(color.base.rgb, map_east(x1 as u16, (x2 - x1) as u16,
|
||||
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()))))))
|
||||
})).boxed()).into()
|
||||
Self::phat_cell(color, color.darkest.rgb.into(),
|
||||
Tui::bold(true, name))))) })).boxed()).into()
|
||||
}
|
||||
|
||||
fn input_row_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> {
|
||||
|
|
@ -138,7 +136,7 @@ impl Arranger {
|
|||
move|(_, scene, y1, y2), i| {
|
||||
let h = (y2 - y1) as u16;
|
||||
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);
|
||||
*last_color.write().unwrap() = color;
|
||||
map_south(y1 as u16, 2, Fill::x(cell))
|
||||
|
|
@ -147,7 +145,7 @@ impl Arranger {
|
|||
}).into()
|
||||
}
|
||||
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)
|
||||
|
|
@ -175,8 +173,7 @@ impl Arranger {
|
|||
}
|
||||
/// name and width of track
|
||||
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, name))
|
||||
Tui::bold(true, Tui::fg(track.color.lightest.rgb, track.name().clone()))
|
||||
}
|
||||
/// beats until switchover
|
||||
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| {
|
||||
let h = (y2 - y1) as u16;
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
|
@ -235,7 +232,7 @@ impl Arranger {
|
|||
scene.color.base.rgb, if playing { "▶ " } else { " " }
|
||||
);
|
||||
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), _|
|
||||
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<'_> {
|
||||
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<'_> {
|
||||
let w = self.size.w();
|
||||
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(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 {
|
||||
fn new (compact: bool, clock: &Clock) -> Self {
|
||||
let (beat, time) = clock.started.read().unwrap().as_ref().map(|started|{
|
||||
let current_usec = clock.global.usec.get() - started.usec.get();
|
||||
let bpm = format!("{:.3}", clock.timebase.bpm.get()).into();
|
||||
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)),
|
||||
format!("{:.3}s", current_usec/1000000.)
|
||||
clock.timebase.format_beats_1(clock.timebase.usecs_to_pulse(now)).into(),
|
||||
format!("{:.3}s", now/1000000.).into()
|
||||
)
|
||||
}).unwrap_or_else(||("-.-.--".to_string(), "-.---s".to_string()));
|
||||
Self { compact, bpm: format!("{:.3}", clock.timebase.bpm.get()), beat, time }
|
||||
} else {
|
||||
("-.-.--".to_string().into(), "-.---s".to_string().into())
|
||||
};
|
||||
Self { compact, bpm, beat, time }
|
||||
}
|
||||
}
|
||||
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)),
|
||||
)));
|
||||
|
||||
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 {
|
||||
fn new (compact: bool, clock: &Clock) -> Self {
|
||||
let rate = clock.timebase.sr.get();
|
||||
|
|
@ -82,9 +85,9 @@ impl OutputStats {
|
|||
format!("{:.1}kHz", rate / 1000.)
|
||||
} else {
|
||||
format!("{:.0}Hz", rate)
|
||||
},
|
||||
buffer_size: format!("{chunk}"),
|
||||
latency: format!("{:.1}ms", chunk as f64 / rate * 1000.),
|
||||
}.into(),
|
||||
buffer_size: format!("{chunk}").into(),
|
||||
latency: format!("{:.1}ms", chunk as f64 / rate * 1000.).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ use crate::*;
|
|||
impl_time_unit!(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 (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000);
|
||||
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.sample.set(self.timebase.pulses_to_sample(pulse));
|
||||
}
|
||||
#[inline] pub fn format_beat (&self) -> String {
|
||||
self.timebase.format_beats_1(self.pulse.get())
|
||||
#[inline] pub fn format_beat (&self) -> Arc<str> {
|
||||
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()
|
||||
}
|
||||
/// 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 ppq = self.ppq.get() as usize;
|
||||
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
|
||||
#[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 ppq = self.ppq.get() as usize;
|
||||
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
|
||||
#[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 ppq = self.ppq.get() as usize;
|
||||
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
|
||||
#[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 ppq = self.ppq.get() as usize;
|
||||
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,
|
||||
Select(usize),
|
||||
Chdir(PathBuf),
|
||||
Filter(String),
|
||||
Filter(Arc<str>),
|
||||
}
|
||||
render!(TuiOut: (self: FileBrowser) => /*Stack::down(|add|{
|
||||
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])),
|
||||
|
||||
":transport" => Box::new(TransportView::new(true, &self.player.clock)),
|
||||
":clip-play" => Box::new(ClipSelected::play_phrase(&self.player)),
|
||||
":clip-next" => Box::new(ClipSelected::next_phrase(&self.player)),
|
||||
":clip-edit" => Box::new(MidiEditClip(&self.editor)),
|
||||
":edit-stat" => Box::new(MidiEditStatus(&self.editor)),
|
||||
":clip-play" => Box::new(self.player.play_status()),
|
||||
":clip-next" => Box::new(self.player.next_status()),
|
||||
":clip-edit" => Box::new(self.editor.clip_status()),
|
||||
":edit-stat" => Box::new(self.editor.edit_status()),
|
||||
":pool-view" => Box::new(PoolView(self.compact, &self.pool)),
|
||||
":midi-view" => Box::new(&self.editor),
|
||||
|
||||
|
|
|
|||
|
|
@ -30,10 +30,10 @@ impl Groovebox {
|
|||
}
|
||||
fn selector_view (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
row!(
|
||||
ClipSelected::play_phrase(&self.player),
|
||||
ClipSelected::next_phrase(&self.player),
|
||||
MidiEditClip(&self.editor),
|
||||
MidiEditStatus(&self.editor),
|
||||
self.player.play_status(),
|
||||
self.player.next_status(),
|
||||
self.editor.clip_status(),
|
||||
self.editor.edit_status(),
|
||||
)
|
||||
}
|
||||
fn sample_view (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
|
|
@ -53,7 +53,7 @@ impl Groovebox {
|
|||
PoolView(self.compact, &self.pool))
|
||||
}
|
||||
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 };
|
||||
Fixed::x(sampler_w, Push::y(sampler_y, Fill::y(
|
||||
SampleList::new(self.compact, &self.sampler, &self.editor))))
|
||||
|
|
|
|||
|
|
@ -242,12 +242,12 @@ impl RegisterPort for Arc<RwLock<JackConnection>> {
|
|||
/// Event enum for JACK events.
|
||||
pub enum JackEvent {
|
||||
ThreadInit,
|
||||
Shutdown(ClientStatus, String),
|
||||
Shutdown(ClientStatus, Arc<str>),
|
||||
Freewheel(bool),
|
||||
SampleRate(Frames),
|
||||
ClientRegistration(String, bool),
|
||||
ClientRegistration(Arc<str>, bool),
|
||||
PortRegistration(PortId, bool),
|
||||
PortRename(PortId, String, String),
|
||||
PortRename(PortId, Arc<str>, Arc<str>),
|
||||
PortsConnected(PortId, PortId, bool),
|
||||
GraphReorder,
|
||||
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 title: String,
|
||||
pub title: Arc<str>,
|
||||
pub items: Vec<MenuItem<E, S, C>>,
|
||||
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_point; pub(crate) use midi_point::*;
|
||||
pub(crate) mod midi_view; pub(crate) use midi_view::*;
|
||||
|
||||
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.
|
||||
pub fn all_notes_off (output: &mut [Vec<Vec<u8>>]) {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ pub trait HasMidiClip {
|
|||
pub struct MidiClip {
|
||||
pub uuid: uuid::Uuid,
|
||||
/// Name of phrase
|
||||
pub name: String,
|
||||
pub name: Arc<str>,
|
||||
/// Temporal resolution in pulses per quarter note
|
||||
pub ppq: usize,
|
||||
/// Length of phrase in pulses
|
||||
|
|
@ -49,7 +49,7 @@ impl MidiClip {
|
|||
) -> Self {
|
||||
Self {
|
||||
uuid: uuid::Uuid::new_v4(),
|
||||
name: name.as_ref().to_string(),
|
||||
name: name.as_ref().into(),
|
||||
ppq: PPQ,
|
||||
length,
|
||||
notes: notes.unwrap_or(vec![Vec::with_capacity(16);length]),
|
||||
|
|
|
|||
|
|
@ -122,6 +122,39 @@ impl MidiEditor {
|
|||
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 {
|
||||
|
|
|
|||
|
|
@ -52,7 +52,9 @@ impl MidiPlayer {
|
|||
midi_from: &[impl AsRef<str>],
|
||||
midi_to: &[impl AsRef<str>],
|
||||
) -> 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 {
|
||||
clock: Clock::from(jack),
|
||||
play_phrase: None,
|
||||
|
|
@ -62,19 +64,20 @@ impl MidiPlayer {
|
|||
overdub: false,
|
||||
|
||||
notes_in: RwLock::new([false;128]).into(),
|
||||
midi_ins: vec![
|
||||
jack.midi_in(&format!("M/{name}"), midi_from)?,
|
||||
],
|
||||
|
||||
midi_outs: vec![
|
||||
jack.midi_out(&format!("{name}/M"), midi_to)?,
|
||||
],
|
||||
midi_ins: vec![midi_in],
|
||||
midi_outs: vec![midi_out],
|
||||
notes_out: RwLock::new([false;128]).into(),
|
||||
reset: true,
|
||||
|
||||
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 {
|
||||
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ pub enum PhrasePoolCommand {
|
|||
Swap(usize, usize),
|
||||
Import(usize, PathBuf),
|
||||
Export(usize, PathBuf),
|
||||
SetName(usize, String),
|
||||
SetName(usize, Arc<str>),
|
||||
SetLength(usize, usize),
|
||||
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 {
|
||||
/// JACK client handle (needs to not be dropped for standalone mode to work).
|
||||
pub jack: Arc<RwLock<JackConnection>>,
|
||||
pub name: String,
|
||||
pub name: Arc<str>,
|
||||
pub tracks: Vec<MixerTrack>,
|
||||
pub selected_track: usize,
|
||||
pub selected_column: usize,
|
||||
|
|
@ -13,7 +13,7 @@ pub struct Mixer {
|
|||
/// A mixer track.
|
||||
#[derive(Debug)]
|
||||
pub struct MixerTrack {
|
||||
pub name: String,
|
||||
pub name: Arc<str>,
|
||||
/// Inputs of 1st device
|
||||
pub audio_ins: Vec<Port<AudioIn>>,
|
||||
/// Outputs of last device
|
||||
|
|
@ -47,7 +47,7 @@ impl Mixer {
|
|||
impl MixerTrack {
|
||||
pub fn new (name: &str) -> Usually<Self> {
|
||||
Ok(Self {
|
||||
name: name.to_string(),
|
||||
name: name.to_string().into(),
|
||||
audio_ins: vec![],
|
||||
audio_outs: vec![],
|
||||
devices: vec![],
|
||||
|
|
@ -267,7 +267,7 @@ const SYM_LV2: &str = "lv2";
|
|||
from_edn!("mixer/track" => |jack: &Arc<RwLock<JackConnection>>, args| -> MixerTrack {
|
||||
let mut _gain = 0.0f64;
|
||||
let mut track = MixerTrack {
|
||||
name: String::new(),
|
||||
name: "".into(),
|
||||
audio_ins: vec![],
|
||||
audio_outs: vec![],
|
||||
devices: vec![],
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ pub use self::lv2::LV2Plugin;
|
|||
pub struct Plugin {
|
||||
/// JACK client handle (needs to not be dropped for standalone mode to work).
|
||||
pub jack: Arc<RwLock<JackConnection>>,
|
||||
pub name: String,
|
||||
pub path: Option<String>,
|
||||
pub name: Arc<str>,
|
||||
pub path: Option<Arc<str>>,
|
||||
pub plugin: Option<PluginKind>,
|
||||
pub selected: usize,
|
||||
pub mapping: bool,
|
||||
|
|
@ -47,7 +47,7 @@ impl Plugin {
|
|||
Ok(Self {
|
||||
jack: jack.clone(),
|
||||
name: name.into(),
|
||||
path: Some(String::from(path)),
|
||||
path: Some(String::from(path).into()),
|
||||
plugin: Some(PluginKind::LV2(LV2Plugin::new(path)?)),
|
||||
selected: 0,
|
||||
mapping: false,
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ pub struct PoolModel {
|
|||
#[derive(Debug, Clone)]
|
||||
pub enum PoolMode {
|
||||
/// Renaming a pattern
|
||||
Rename(usize, String),
|
||||
Rename(usize, Arc<str>),
|
||||
/// Editing the length of a pattern
|
||||
Length(usize, usize, PhraseLengthFocus),
|
||||
/// Load phrase from disk
|
||||
|
|
@ -147,10 +147,10 @@ fn to_phrases_command (state: &PoolModel, input: &Event) -> Option<PoolCommand>
|
|||
return None
|
||||
},
|
||||
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(
|
||||
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')) => {
|
||||
let mut phrase = state.phrases()[index].read().unwrap().duplicate();
|
||||
|
|
|
|||
|
|
@ -30,14 +30,14 @@ impl PhraseLength {
|
|||
pub fn ticks (&self) -> usize {
|
||||
self.pulses % self.ppq
|
||||
}
|
||||
pub fn bars_string (&self) -> String {
|
||||
format!("{}", self.bars())
|
||||
pub fn bars_string (&self) -> Arc<str> {
|
||||
format!("{}", self.bars()).into()
|
||||
}
|
||||
pub fn beats_string (&self) -> String {
|
||||
format!("{}", self.beats())
|
||||
pub fn beats_string (&self) -> Arc<str> {
|
||||
format!("{}", self.beats()).into()
|
||||
}
|
||||
pub fn ticks_string (&self) -> String {
|
||||
format!("{:>02}", self.ticks())
|
||||
pub fn ticks_string (&self) -> Arc<str> {
|
||||
format!("{:>02}", self.ticks()).into()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ pub enum PhraseRenameCommand {
|
|||
Begin,
|
||||
Cancel,
|
||||
Confirm,
|
||||
Set(String),
|
||||
Set(Arc<str>),
|
||||
}
|
||||
|
||||
impl Command<PoolModel> for PhraseRenameCommand {
|
||||
|
|
@ -16,7 +16,7 @@ impl Command<PoolModel> for PhraseRenameCommand {
|
|||
Some(PoolMode::Rename(phrase, ref mut old_name)) => match self {
|
||||
Set(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 => {
|
||||
let old_name = old_name.clone();
|
||||
|
|
@ -24,7 +24,7 @@ impl Command<PoolModel> for PhraseRenameCommand {
|
|||
return Ok(Some(Self::Set(old_name)))
|
||||
},
|
||||
Cancel => {
|
||||
state.phrases()[phrase].write().unwrap().name = old_name.clone();
|
||||
state.phrases()[phrase].write().unwrap().name = old_name.clone().into();
|
||||
},
|
||||
_ => unreachable!()
|
||||
},
|
||||
|
|
@ -40,14 +40,14 @@ impl InputToCommand<Event, PoolModel> for PhraseRenameCommand {
|
|||
if let Some(PoolMode::Rename(_, ref old_name)) = state.phrases_mode() {
|
||||
Some(match input {
|
||||
kpat!(Char(c)) => {
|
||||
let mut new_name = old_name.clone();
|
||||
let mut new_name = old_name.clone().to_string();
|
||||
new_name.push(*c);
|
||||
Self::Set(new_name)
|
||||
Self::Set(new_name.into())
|
||||
},
|
||||
kpat!(Backspace) => {
|
||||
let mut new_name = old_name.clone();
|
||||
let mut new_name = old_name.clone().to_string();
|
||||
new_name.pop();
|
||||
Self::Set(new_name)
|
||||
Self::Set(new_name.into())
|
||||
},
|
||||
kpat!(Enter) => Self::Confirm,
|
||||
kpat!(Esc) => Self::Cancel,
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ use crate::*;
|
|||
|
||||
pub struct ClipSelected {
|
||||
pub(crate) title: &'static str,
|
||||
pub(crate) name: String,
|
||||
pub(crate) name: Arc<str>,
|
||||
pub(crate) color: ItemPalette,
|
||||
pub(crate) time: String,
|
||||
pub(crate) time: Arc<str>,
|
||||
}
|
||||
|
||||
render!(TuiOut: (self: ClipSelected) =>
|
||||
|
|
@ -16,9 +16,9 @@ impl ClipSelected {
|
|||
pub fn play_phrase <T: HasPlayPhrase + HasClock> (state: &T) -> Self {
|
||||
let (name, color) = if let Some((_, Some(phrase))) = state.play_phrase() {
|
||||
let MidiClip { ref name, color, .. } = *phrase.read().unwrap();
|
||||
(name.clone(), color)
|
||||
(name.clone().into(), color)
|
||||
} else {
|
||||
("".to_string(), TuiTheme::g(64).into())
|
||||
("".to_string().into(), TuiTheme::g(64).into())
|
||||
};
|
||||
Self {
|
||||
title: "Now",
|
||||
|
|
@ -28,14 +28,14 @@ impl ClipSelected {
|
|||
.map(|(times, time)|format!("{:>3}x {:>}",
|
||||
times+1.0,
|
||||
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
|
||||
pub fn next_phrase <T: HasPlayPhrase> (state: &T) -> Self {
|
||||
let mut time = String::from("--.-.--");
|
||||
let mut name = String::from("");
|
||||
let mut time: Arc<str> = String::from("--.-.--").into();
|
||||
let mut name: Arc<str> = String::from("").into();
|
||||
let mut color = ItemPalette::from(TuiTheme::g(64));
|
||||
if let Some((t, Some(phrase))) = state.next_phrase() {
|
||||
let phrase = phrase.read().unwrap();
|
||||
|
|
@ -50,7 +50,7 @@ impl ClipSelected {
|
|||
} else {
|
||||
String::new()
|
||||
}
|
||||
}
|
||||
}.into()
|
||||
} else if let Some((t, Some(phrase))) = state.play_phrase() {
|
||||
let phrase = phrase.read().unwrap();
|
||||
if phrase.looped {
|
||||
|
|
@ -59,10 +59,12 @@ impl ClipSelected {
|
|||
let target = t.pulse.get() + phrase.length as f64;
|
||||
let current = state.clock().playhead.pulse.get();
|
||||
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 {
|
||||
name = "Stop".to_string();
|
||||
name = "Stop".to_string().into();
|
||||
}
|
||||
};
|
||||
Self { title: "Next", time, name, color, }
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ render!(TuiOut: (self: PoolView<'a>) => {
|
|||
let Self(compact, model) = self;
|
||||
let PoolModel { phrases, mode, .. } = self.1;
|
||||
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)
|
||||
).enclose(Map::new(||model.phrases().iter(), |clip, i|{
|
||||
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::e(When(selected, Tui::bold(true, Tui::fg(TuiTheme::g(255), "◀")))),
|
||||
)))
|
||||
}))
|
||||
}))))
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use super::*;
|
|||
/// A sound sample.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Sample {
|
||||
pub name: String,
|
||||
pub name: Arc<str>,
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
pub channels: Vec<Vec<f32>>,
|
||||
|
|
@ -24,8 +24,8 @@ pub struct Sample {
|
|||
}
|
||||
|
||||
impl Sample {
|
||||
pub fn new (name: &str, start: usize, end: usize, channels: Vec<Vec<f32>>) -> Self {
|
||||
Self { name: name.to_string(), start, end, channels, rate: None, gain: 1.0 }
|
||||
pub fn new (name: impl AsRef<str>, start: usize, end: usize, channels: Vec<Vec<f32>>) -> Self {
|
||||
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 {
|
||||
Voice {
|
||||
|
|
|
|||
|
|
@ -18,9 +18,7 @@ render!(TuiOut: (self: SampleList<'a>) => {
|
|||
let note_pt = editor.note_point();
|
||||
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| {
|
||||
|
||||
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 fg = TuiTheme::g(160);
|
||||
if sampler.mapped[note].is_some() {
|
||||
|
|
@ -33,13 +31,20 @@ render!(TuiOut: (self: SampleList<'a>) => {
|
|||
fg = Color::Rgb(224,64,32)
|
||||
}
|
||||
}
|
||||
|
||||
offset(Tui::fg_bg(fg, bg, format!("{note:3} {}", if *compact {
|
||||
let label = if *compact {
|
||||
String::default()
|
||||
} 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 {
|
||||
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))))
|
||||
}
|
||||
fn status_view (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
let edit_clip = MidiEditClip(&self.editor);
|
||||
let selectors = When(self.selectors, Bsp::e(ClipSelected::play_phrase(&self.player), ClipSelected::next_phrase(&self.player)));
|
||||
row!(selectors, edit_clip, MidiEditStatus(&self.editor))
|
||||
row!(
|
||||
When(self.selectors, Bsp::e(
|
||||
self.player.play_status(),
|
||||
self.player.next_status(),
|
||||
)),
|
||||
self.editor.clip_status(),
|
||||
self.editor.edit_status(),
|
||||
)
|
||||
}
|
||||
fn selector_view (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
row!(
|
||||
ClipSelected::play_phrase(&self.player),
|
||||
ClipSelected::next_phrase(&self.player),
|
||||
MidiEditClip(&self.editor),
|
||||
MidiEditStatus(&self.editor),
|
||||
self.player.play_status(),
|
||||
self.player.next_status(),
|
||||
self.editor.clip_status(),
|
||||
self.editor.edit_status(),
|
||||
)
|
||||
}
|
||||
fn pool_view (&self) -> impl Content<TuiOut> + use<'_> {
|
||||
|
|
@ -180,8 +185,8 @@ command!(|self: SequencerCommand, state: Sequencer|match self {
|
|||
#[derive(Clone)]
|
||||
pub struct SequencerStatus {
|
||||
pub(crate) width: usize,
|
||||
pub(crate) cpu: Option<String>,
|
||||
pub(crate) size: String,
|
||||
pub(crate) cpu: Option<Arc<str>>,
|
||||
pub(crate) size: Arc<str>,
|
||||
pub(crate) playing: bool,
|
||||
}
|
||||
from!(|state:&Sequencer|SequencerStatus = {
|
||||
|
|
@ -192,8 +197,8 @@ from!(|state:&Sequencer|SequencerStatus = {
|
|||
Self {
|
||||
width,
|
||||
playing: state.clock.is_rolling(),
|
||||
cpu: state.perf.percentage().map(|cpu|format!("│{cpu:.01}%")),
|
||||
size: format!("{}x{}│", width, state.size.h()),
|
||||
cpu: state.perf.percentage().map(|cpu|format!("│{cpu:.01}%").into()),
|
||||
size: format!("{}x{}│", width, state.size.h()).into(),
|
||||
}
|
||||
});
|
||||
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);
|
||||
|
||||
impl Content<TuiOut> for Repeat<'_> {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue