mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-04-04 05:10:44 +02:00
wip: nromalize
This commit is contained in:
parent
7ff1d989a9
commit
513b8354a3
14 changed files with 2324 additions and 2337 deletions
347
app/tek_impls.rs
347
app/tek_impls.rs
|
|
@ -941,55 +941,6 @@ mod clock {
|
|||
});
|
||||
}
|
||||
|
||||
impl Connect {
|
||||
pub fn collect (exact: &[impl AsRef<str>], re: &[impl AsRef<str>], re_all: &[impl AsRef<str>])
|
||||
-> Vec<Self>
|
||||
{
|
||||
let mut connections = vec![];
|
||||
for port in exact.iter() { connections.push(Self::exact(port)) }
|
||||
for port in re.iter() { connections.push(Self::regexp(port)) }
|
||||
for port in re_all.iter() { connections.push(Self::regexp_all(port)) }
|
||||
connections
|
||||
}
|
||||
/// Connect to this exact port
|
||||
pub fn exact (name: impl AsRef<str>) -> Self {
|
||||
let info = format!("=:{}", name.as_ref()).into();
|
||||
let name = Some(Exact(name.as_ref().into()));
|
||||
Self { name, scope: Some(One), status: Arc::new(RwLock::new(vec![])), info }
|
||||
}
|
||||
pub fn regexp (name: impl AsRef<str>) -> Self {
|
||||
let info = format!("~:{}", name.as_ref()).into();
|
||||
let name = Some(RegExp(name.as_ref().into()));
|
||||
Self { name, scope: Some(One), status: Arc::new(RwLock::new(vec![])), info }
|
||||
}
|
||||
pub fn regexp_all (name: impl AsRef<str>) -> Self {
|
||||
let info = format!("+:{}", name.as_ref()).into();
|
||||
let name = Some(RegExp(name.as_ref().into()));
|
||||
Self { name, scope: Some(All), status: Arc::new(RwLock::new(vec![])), info }
|
||||
}
|
||||
pub fn info (&self) -> Arc<str> {
|
||||
format!(" ({}) {} {}", {
|
||||
let status = self.status.read().unwrap();
|
||||
let mut ok = 0;
|
||||
for (_, _, state) in status.iter() {
|
||||
if *state == Connected {
|
||||
ok += 1
|
||||
}
|
||||
}
|
||||
format!("{ok}/{}", status.len())
|
||||
}, match self.scope {
|
||||
None => "x",
|
||||
Some(One) => " ",
|
||||
Some(All) => "*",
|
||||
}, match &self.name {
|
||||
None => format!("x"),
|
||||
Some(Exact(name)) => format!("= {name}"),
|
||||
Some(RegExp(name)) => format!("~ {name}"),
|
||||
}).into()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Selection {
|
||||
pub fn describe (
|
||||
&self,
|
||||
|
|
@ -1118,26 +1069,8 @@ impl InteriorMutable<usize> for AtomicUsize { fn set (&self, value: usize) -> us
|
|||
impl PartialEq for MenuItem { fn eq (&self, other: &Self) -> bool { self.0 == other.0 } }
|
||||
impl AsRef<Arc<[MenuItem]>> for MenuItems { fn as_ref (&self) -> &Arc<[MenuItem]> { &self.0 } }
|
||||
impl HasClipsSize for App { fn clips_size (&self) -> &Measure<Tui> { &self.project.size_inner } }
|
||||
impl HasJack<'static> for MidiInput { fn jack (&self) -> &Jack<'static> { &self.jack } }
|
||||
impl HasJack<'static> for MidiOutput { fn jack (&self) -> &Jack<'static> { &self.jack } }
|
||||
impl HasJack<'static> for AudioInput { fn jack (&self) -> &Jack<'static> { &self.jack } }
|
||||
impl HasJack<'static> for AudioOutput { fn jack (&self) -> &Jack<'static> { &self.jack } }
|
||||
impl HasJack<'static> for App { fn jack (&self) -> &Jack<'static> { &self.jack } }
|
||||
impl HasJack<'static> for Arrangement { fn jack (&self) -> &Jack<'static> { &self.jack } }
|
||||
impl<J: HasJack<'static>> RegisterPorts for J {
|
||||
fn midi_in (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<MidiInput> {
|
||||
MidiInput::new(self.jack(), name, connect)
|
||||
}
|
||||
fn midi_out (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<MidiOutput> {
|
||||
MidiOutput::new(self.jack(), name, connect)
|
||||
}
|
||||
fn audio_in (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<AudioInput> {
|
||||
AudioInput::new(self.jack(), name, connect)
|
||||
}
|
||||
fn audio_out (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<AudioOutput> {
|
||||
AudioOutput::new(self.jack(), name, connect)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "clock")] impl_has!(Clock: |self: Track|self.sequencer.clock);
|
||||
|
||||
|
|
@ -3562,286 +3495,6 @@ mod pool {
|
|||
|
||||
mod config {
|
||||
use crate::*;
|
||||
|
||||
/// Command-line configuration.
|
||||
#[cfg(feature = "cli")] impl Cli {
|
||||
pub fn run (&self) -> Usually<()> {
|
||||
if let Action::Version = self.action {
|
||||
return Ok(tek_show_version())
|
||||
}
|
||||
|
||||
let mut config = Config::new(None);
|
||||
config.init()?;
|
||||
|
||||
if let Action::Config = self.action {
|
||||
tek_print_config(&config);
|
||||
} else if let Action::List = self.action {
|
||||
todo!("list sessions")
|
||||
} else if let Action::Resume = self.action {
|
||||
todo!("resume session")
|
||||
} else if let Action::New {
|
||||
name, bpm, tracks, scenes, sync_lead, sync_follow,
|
||||
midi_from, midi_from_re, midi_to, midi_to_re,
|
||||
left_from, right_from, left_to, right_to, ..
|
||||
} = &self.action {
|
||||
|
||||
// Connect to JACK
|
||||
let name = name.as_ref().map_or("tek", |x|x.as_str());
|
||||
let jack = Jack::new(&name)?;
|
||||
|
||||
// TODO: Collect audio IO:
|
||||
let empty = &[] as &[&str];
|
||||
let left_froms = Connect::collect(&left_from, empty, empty);
|
||||
let left_tos = Connect::collect(&left_to, empty, empty);
|
||||
let right_froms = Connect::collect(&right_from, empty, empty);
|
||||
let right_tos = Connect::collect(&right_to, empty, empty);
|
||||
let _audio_froms = &[left_froms.as_slice(), right_froms.as_slice()];
|
||||
let _audio_tos = &[left_tos.as_slice(), right_tos.as_slice()];
|
||||
|
||||
// Create initial project:
|
||||
let clock = Clock::new(&jack, *bpm)?;
|
||||
let mut project = Arrangement::new(
|
||||
&jack,
|
||||
None,
|
||||
clock,
|
||||
vec![],
|
||||
vec![],
|
||||
Connect::collect(&midi_from, &[] as &[&str], &midi_from_re).iter().enumerate()
|
||||
.map(|(index, connect)|jack.midi_in(&format!("M/{index}"), &[connect.clone()]))
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
Connect::collect(&midi_to, &[] as &[&str], &midi_to_re).iter().enumerate()
|
||||
.map(|(index, connect)|jack.midi_out(&format!("{index}/M"), &[connect.clone()]))
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
);
|
||||
project.tracks_add(tracks.unwrap_or(0), None, &[], &[])?;
|
||||
project.scenes_add(scenes.unwrap_or(0))?;
|
||||
|
||||
if matches!(self.action, Action::Status) {
|
||||
// Show status and exit
|
||||
tek_print_status(&project);
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
// Initialize the app state
|
||||
let app = tek(&jack, project, config, ":menu");
|
||||
if matches!(self.action, Action::Headless) {
|
||||
// TODO: Headless mode (daemon + client over IPC, then over network...)
|
||||
println!("todo headless");
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
// Run the [Tui] and [Jack] threads with the [App] state.
|
||||
Tui::new(Box::new(std::io::stdout()))?.run(true, &jack.run(move|jack|{
|
||||
|
||||
// Between jack init and app's first cycle:
|
||||
|
||||
jack.sync_lead(*sync_lead, |mut state|{
|
||||
let clock = app.clock();
|
||||
clock.playhead.update_from_sample(state.position.frame() as f64);
|
||||
state.position.bbt = Some(clock.bbt());
|
||||
state.position
|
||||
})?;
|
||||
|
||||
jack.sync_follow(*sync_follow)?;
|
||||
|
||||
// FIXME: They don't work properly.
|
||||
|
||||
Ok(app)
|
||||
|
||||
})?)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
const CONFIG_DIR: &'static str = "tek";
|
||||
const CONFIG_SUB: &'static str = "v0";
|
||||
const CONFIG: &'static str = "tek.edn";
|
||||
const DEFAULTS: &'static str = include_str!("./tek.edn");
|
||||
/// Create a new app configuration from a set of XDG base directories,
|
||||
pub fn new (dirs: Option<BaseDirectories>) -> Self {
|
||||
let default = ||BaseDirectories::with_profile(Self::CONFIG_DIR, Self::CONFIG_SUB);
|
||||
let dirs = dirs.unwrap_or_else(default);
|
||||
Self { dirs, ..Default::default() }
|
||||
}
|
||||
/// Write initial contents of configuration.
|
||||
pub fn init (&mut self) -> Usually<()> {
|
||||
self.init_one(Self::CONFIG, Self::DEFAULTS, |cfgs, dsl|{
|
||||
cfgs.add(&dsl)?;
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
/// Write initial contents of a configuration file.
|
||||
pub fn init_one (
|
||||
&mut self, path: &str, defaults: &str, mut each: impl FnMut(&mut Self, &str)->Usually<()>
|
||||
) -> Usually<()> {
|
||||
if self.dirs.find_config_file(path).is_none() {
|
||||
//println!("Creating {path:?}");
|
||||
std::fs::write(self.dirs.place_config_file(path)?, defaults)?;
|
||||
}
|
||||
Ok(if let Some(path) = self.dirs.find_config_file(path) {
|
||||
//println!("Loading {path:?}");
|
||||
let src = std::fs::read_to_string(&path)?;
|
||||
src.as_str().each(move|item|each(self, item))?;
|
||||
} else {
|
||||
return Err(format!("{path}: not found").into())
|
||||
})
|
||||
}
|
||||
/// Add statements to configuration from [Dsl] source.
|
||||
pub fn add (&mut self, dsl: impl Language) -> Usually<&mut Self> {
|
||||
dsl.each(|item|self.add_one(item))?;
|
||||
Ok(self)
|
||||
}
|
||||
fn add_one (&self, item: impl Language) -> Usually<()> {
|
||||
if let Some(expr) = item.expr()? {
|
||||
let head = expr.head()?;
|
||||
let tail = expr.tail()?;
|
||||
let name = tail.head()?;
|
||||
let body = tail.tail()?;
|
||||
//println!("Config::load: {} {} {}", head.unwrap_or_default(), name.unwrap_or_default(), body.unwrap_or_default());
|
||||
match head {
|
||||
Some("mode") if let Some(name) = name => load_mode(&self.modes, &name, &body)?,
|
||||
Some("keys") if let Some(name) = name => load_bind(&self.binds, &name, &body)?,
|
||||
Some("view") if let Some(name) = name => load_view(&self.views, &name, &body)?,
|
||||
_ => return Err(format!("Config::load: expected view/keys/mode, got: {item:?}").into())
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
return Err(format!("Config::load: expected expr, got: {item:?}").into())
|
||||
}
|
||||
}
|
||||
pub fn get_mode (&self, mode: impl AsRef<str>) -> Option<Arc<Mode<Arc<str>>>> {
|
||||
self.modes.clone().read().unwrap().get(mode.as_ref()).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
impl Mode<Arc<str>> {
|
||||
/// Add a definition to the mode.
|
||||
///
|
||||
/// Supported definitions:
|
||||
///
|
||||
/// - (name ...) -> name
|
||||
/// - (info ...) -> description
|
||||
/// - (keys ...) -> key bindings
|
||||
/// - (mode ...) -> submode
|
||||
/// - ... -> view
|
||||
///
|
||||
/// ```
|
||||
/// let mut mode: tek::Mode<std::sync::Arc<str>> = Default::default();
|
||||
/// mode.add("(name hello)").unwrap();
|
||||
/// ```
|
||||
pub fn add (&mut self, dsl: impl Language) -> Usually<()> {
|
||||
Ok(if let Ok(Some(expr)) = dsl.expr() && let Ok(Some(head)) = expr.head() {
|
||||
//println!("Mode::add: {head} {:?}", expr.tail());
|
||||
let tail = expr.tail()?.map(|x|x.trim()).unwrap_or("");
|
||||
match head {
|
||||
"name" => self.add_name(tail)?,
|
||||
"info" => self.add_info(tail)?,
|
||||
"keys" => self.add_keys(tail)?,
|
||||
"mode" => self.add_mode(tail)?,
|
||||
_ => self.add_view(tail)?,
|
||||
};
|
||||
} else if let Ok(Some(word)) = dsl.word() {
|
||||
self.add_view(word);
|
||||
} else {
|
||||
return Err(format!("Mode::add: unexpected: {dsl:?}").into());
|
||||
})
|
||||
|
||||
//DslParse(dsl, ||Err(format!("Mode::add: unexpected: {dsl:?}").into()))
|
||||
//.word(|word|self.add_view(word))
|
||||
//.expr(|expr|expr.head(|head|{
|
||||
////println!("Mode::add: {head} {:?}", expr.tail());
|
||||
//let tail = expr.tail()?.map(|x|x.trim()).unwrap_or("");
|
||||
//match head {
|
||||
//"name" => self.add_name(tail),
|
||||
//"info" => self.add_info(tail),
|
||||
//"keys" => self.add_keys(tail)?,
|
||||
//"mode" => self.add_mode(tail)?,
|
||||
//_ => self.add_view(tail),
|
||||
//};
|
||||
//}))
|
||||
}
|
||||
|
||||
fn add_name (&mut self, dsl: impl Language) -> Perhaps<()> {
|
||||
Ok(dsl.src()?.map(|src|self.name.push(src.into())))
|
||||
}
|
||||
fn add_info (&mut self, dsl: impl Language) -> Perhaps<()> {
|
||||
Ok(dsl.src()?.map(|src|self.info.push(src.into())))
|
||||
}
|
||||
fn add_view (&mut self, dsl: impl Language) -> Perhaps<()> {
|
||||
Ok(dsl.src()?.map(|src|self.view.push(src.into())))
|
||||
}
|
||||
fn add_keys (&mut self, dsl: impl Language) -> Perhaps<()> {
|
||||
Ok(Some(dsl.each(|expr|{ self.keys.push(expr.trim().into()); Ok(()) })?))
|
||||
}
|
||||
fn add_mode (&mut self, dsl: impl Language) -> Perhaps<()> {
|
||||
Ok(Some(if let Some(id) = dsl.head()? {
|
||||
load_mode(&self.modes, &id, &dsl.tail())?;
|
||||
} else {
|
||||
return Err(format!("Mode::add: self: incomplete: {dsl:?}").into());
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: Clone + Ord, C> Bind<E, C> {
|
||||
/// Create a new event map
|
||||
pub fn new () -> Self {
|
||||
Default::default()
|
||||
}
|
||||
/// Add a binding to an owned event map.
|
||||
pub fn def (mut self, event: E, binding: Binding<C>) -> Self {
|
||||
self.add(event, binding);
|
||||
self
|
||||
}
|
||||
/// Add a binding to an event map.
|
||||
pub fn add (&mut self, event: E, binding: Binding<C>) -> &mut Self {
|
||||
if !self.0.contains_key(&event) {
|
||||
self.0.insert(event.clone(), Default::default());
|
||||
}
|
||||
self.0.get_mut(&event).unwrap().push(binding);
|
||||
self
|
||||
}
|
||||
/// Return the binding(s) that correspond to an event.
|
||||
pub fn query (&self, event: &E) -> Option<&[Binding<C>]> {
|
||||
self.0.get(event).map(|x|x.as_slice())
|
||||
}
|
||||
/// Return the first binding that corresponds to an event, considering conditions.
|
||||
pub fn dispatch (&self, event: &E) -> Option<&Binding<C>> {
|
||||
self.query(event)
|
||||
.map(|bb|bb.iter().filter(|b|b.condition.as_ref().map(|c|(c.0)()).unwrap_or(true)).next())
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
impl Bind<TuiEvent, Arc<str>> {
|
||||
pub fn load (lang: &impl Language) -> Usually<Self> {
|
||||
let mut map = Bind::new();
|
||||
lang.each(|item|if item.expr().head() == Ok(Some("see")) {
|
||||
// TODO
|
||||
Ok(())
|
||||
} else if let Ok(Some(_word)) = item.expr().head().word() {
|
||||
if let Some(key) = TuiEvent::from_dsl(item.expr()?.head()?)? {
|
||||
map.add(key, Binding {
|
||||
commands: [item.expr()?.tail()?.unwrap_or_default().into()].into(),
|
||||
condition: None,
|
||||
description: None,
|
||||
source: None
|
||||
});
|
||||
Ok(())
|
||||
} else if Some(":char") == item.expr()?.head()? {
|
||||
// TODO
|
||||
return Ok(())
|
||||
} else {
|
||||
return Err(format!("Config::load_bind: invalid key: {:?}", item.expr()?.head()?).into())
|
||||
}
|
||||
} else {
|
||||
return Err(format!("Config::load_bind: unexpected: {item:?}").into())
|
||||
})?;
|
||||
Ok(map)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod dialog {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue