mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 04:06:45 +01:00
wip: split edn parsers
This commit is contained in:
parent
b7d7864792
commit
a54798994b
15 changed files with 275 additions and 140 deletions
|
|
@ -4,6 +4,10 @@ edition = "2021"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
clap = { version = "4.5.4", features = [ "derive" ] }
|
||||||
|
clojure-reader = "0.1.0"
|
||||||
|
microxdg = "0.1.2"
|
||||||
|
|
||||||
tek_core = { path = "../tek_core" }
|
tek_core = { path = "../tek_core" }
|
||||||
tek_jack = { path = "../tek_jack" }
|
tek_jack = { path = "../tek_jack" }
|
||||||
tek_plugin = { path = "../tek_plugin" }
|
tek_plugin = { path = "../tek_plugin" }
|
||||||
|
|
@ -13,11 +17,9 @@ tek_timer = { path = "../tek_timer" }
|
||||||
tek_chain = { path = "../tek_chain" }
|
tek_chain = { path = "../tek_chain" }
|
||||||
tek_mixer = { path = "../tek_mixer" }
|
tek_mixer = { path = "../tek_mixer" }
|
||||||
#jack = "0.10"
|
#jack = "0.10"
|
||||||
#clap = { version = "4.5.4", features = [ "derive" ] }
|
|
||||||
#crossterm = "0.27"
|
#crossterm = "0.27"
|
||||||
#ratatui = { version = "0.26.3", features = [ "unstable-widget-ref", "underline-color" ] }
|
#ratatui = { version = "0.26.3", features = [ "unstable-widget-ref", "underline-color" ] }
|
||||||
#backtrace = "0.3.72"
|
#backtrace = "0.3.72"
|
||||||
#microxdg = "0.1.2"
|
|
||||||
#toml = "0.8.12"
|
#toml = "0.8.12"
|
||||||
#better-panic = "0.3.0"
|
#better-panic = "0.3.0"
|
||||||
#midly = "0.5"
|
#midly = "0.5"
|
||||||
|
|
@ -31,7 +33,6 @@ tek_mixer = { path = "../tek_mixer" }
|
||||||
#fraction = "0.15.3"
|
#fraction = "0.15.3"
|
||||||
#rlsf = "0.2.1"
|
#rlsf = "0.2.1"
|
||||||
#r8brain-rs = "0.3.5"
|
#r8brain-rs = "0.3.5"
|
||||||
#clojure-reader = "0.1.0"
|
|
||||||
#once_cell = "1.19.0"
|
#once_cell = "1.19.0"
|
||||||
|
|
||||||
#symphonia = { version = "0.5.4", features = [ "all" ] }
|
#symphonia = { version = "0.5.4", features = [ "all" ] }
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
use tek_timer::TransportToolbar;
|
||||||
|
use tek_sequencer::Arranger;
|
||||||
|
|
||||||
/// Root of application state.
|
/// Root of application state.
|
||||||
pub struct App {
|
pub struct App {
|
||||||
|
|
@ -29,7 +31,7 @@ pub struct App {
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub fn new () -> Usually<Self> {
|
pub fn new () -> Usually<Self> {
|
||||||
let xdg = Arc::new(microxdg::XdgApp::new("tek")?);
|
let xdg = Arc::new(XdgApp::new("tek")?);
|
||||||
let first_run = crate::config::AppPaths::new(&xdg)?.should_create();
|
let first_run = crate::config::AppPaths::new(&xdg)?.should_create();
|
||||||
let jack = JackClient::Inactive(Client::new("tek", ClientOptions::NO_START_SERVER)?.0);
|
let jack = JackClient::Inactive(Client::new("tek", ClientOptions::NO_START_SERVER)?.0);
|
||||||
*MODAL.lock().unwrap() = first_run.then(||{
|
*MODAL.lock().unwrap() = first_run.then(||{
|
||||||
|
|
@ -49,31 +51,6 @@ impl App {
|
||||||
_xdg: Some(xdg),
|
_xdg: Some(xdg),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
process!(App |self, _client, scope| {
|
|
||||||
let (
|
|
||||||
reset, current_frames, chunk_size, current_usecs, next_usecs, period_usecs
|
|
||||||
) = self.transport.update(&scope);
|
|
||||||
self.chunk_size = chunk_size;
|
|
||||||
for track in self.arranger.tracks.iter_mut() {
|
|
||||||
track.process(
|
|
||||||
self.midi_in.as_ref().map(|p|p.iter(&scope)),
|
|
||||||
&self.transport.timebase,
|
|
||||||
self.transport.playing,
|
|
||||||
self.transport.started,
|
|
||||||
self.transport.quant as usize,
|
|
||||||
reset,
|
|
||||||
&scope,
|
|
||||||
(current_frames as usize, self.chunk_size),
|
|
||||||
(current_usecs as usize, next_usecs.saturating_sub(current_usecs) as usize),
|
|
||||||
period_usecs as f64
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Control::Continue
|
|
||||||
});
|
|
||||||
|
|
||||||
impl App {
|
|
||||||
pub fn client (&self) -> &Client {
|
pub fn client (&self) -> &Client {
|
||||||
self.jack.as_ref().unwrap().client()
|
self.jack.as_ref().unwrap().client()
|
||||||
}
|
}
|
||||||
|
|
@ -113,7 +90,6 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
render!(App |self, buf, area| {
|
render!(App |self, buf, area| {
|
||||||
Split::down([
|
Split::down([
|
||||||
&self.transport,
|
&self.transport,
|
||||||
|
|
@ -128,3 +104,25 @@ render!(App |self, buf, area| {
|
||||||
}
|
}
|
||||||
Ok(area)
|
Ok(area)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
process!(App |self, _client, scope| {
|
||||||
|
let (
|
||||||
|
reset, current_frames, chunk_size, current_usecs, next_usecs, period_usecs
|
||||||
|
) = self.transport.update(&scope);
|
||||||
|
self.chunk_size = chunk_size;
|
||||||
|
for track in self.arranger.tracks.iter_mut() {
|
||||||
|
track.process(
|
||||||
|
self.midi_in.as_ref().map(|p|p.iter(&scope)),
|
||||||
|
&self.transport.timebase,
|
||||||
|
self.transport.playing,
|
||||||
|
self.transport.started,
|
||||||
|
self.transport.quant as usize,
|
||||||
|
reset,
|
||||||
|
&scope,
|
||||||
|
(current_frames as usize, self.chunk_size),
|
||||||
|
(current_usecs as usize, next_usecs.saturating_sub(current_usecs) as usize),
|
||||||
|
period_usecs as f64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Control::Continue
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
//! Global settings.
|
//! Global settings.
|
||||||
|
|
||||||
use crate::core::*;
|
use crate::*;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::fs::{File, create_dir_all};
|
use std::fs::{File, create_dir_all};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
//! Handling of input events.
|
//! Handling of input events.
|
||||||
|
|
||||||
use crate::{core::*, handle, App, AppFocus, model::MODAL};
|
use crate::*;
|
||||||
|
|
||||||
handle!{
|
handle!{
|
||||||
App |self, e| {
|
App |self, e| {
|
||||||
|
|
@ -38,12 +38,9 @@ fn handle_modal (e: &AppEvent) -> Usually<bool> {
|
||||||
|
|
||||||
fn handle_focused (state: &mut App, e: &AppEvent) -> Usually<bool> {
|
fn handle_focused (state: &mut App, e: &AppEvent) -> Usually<bool> {
|
||||||
match state.section {
|
match state.section {
|
||||||
AppFocus::Transport =>
|
AppFocus::Transport => state.transport.handle(e),
|
||||||
handle_keymap(state, e, crate::devices::transport::KEYMAP_TRANSPORT),
|
AppFocus::Arranger => state.arranger.sequencer.handle(e),
|
||||||
AppFocus::Arranger =>
|
AppFocus::Sequencer => state.arranger.sequencer.handle(e),
|
||||||
handle_keymap(state, e, crate::devices::arranger::KEYMAP_ARRANGER),
|
|
||||||
AppFocus::Sequencer =>
|
|
||||||
handle_keymap(state, e, crate::devices::sequencer::KEYMAP_SEQUENCER),
|
|
||||||
AppFocus::Chain => Ok(if state.entered {
|
AppFocus::Chain => Ok(if state.entered {
|
||||||
handle_device(state, e)? ||
|
handle_device(state, e)? ||
|
||||||
handle_keymap(state, e, crate::control::KEYMAP_CHAIN)?
|
handle_keymap(state, e, crate::control::KEYMAP_CHAIN)?
|
||||||
|
|
@ -88,11 +85,11 @@ pub const KEYMAP_GLOBAL: &'static [KeyBinding<App>] = keymap!(App {
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}],
|
}],
|
||||||
[Char('='), NONE, "zoom_in", "show fewer ticks per block", |app: &mut App| {
|
[Char('='), NONE, "zoom_in", "show fewer ticks per block", |app: &mut App| {
|
||||||
app.sequencer.time_axis.scale_mut(&prev_note_length);
|
app.arranger.sequencer.time_axis.scale_mut(&prev_note_length);
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}],
|
}],
|
||||||
[Char('-'), NONE, "zoom_out", "show more ticks per block", |app: &mut App| {
|
[Char('-'), NONE, "zoom_out", "show more ticks per block", |app: &mut App| {
|
||||||
app.sequencer.time_axis.scale_mut(&next_note_length);
|
app.arranger.sequencer.time_axis.scale_mut(&next_note_length);
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}],
|
}],
|
||||||
[Char('x'), NONE, "extend", "double the current clip", |app: &mut App| {
|
[Char('x'), NONE, "extend", "double the current clip", |app: &mut App| {
|
||||||
|
|
@ -102,7 +99,7 @@ pub const KEYMAP_GLOBAL: &'static [KeyBinding<App>] = keymap!(App {
|
||||||
phrase.notes = notes;
|
phrase.notes = notes;
|
||||||
phrase.length = phrase.length * 2;
|
phrase.length = phrase.length * 2;
|
||||||
});
|
});
|
||||||
app.sequencer.show(app.arranger.phrase())?;
|
app.arranger.sequencer.show(app.arranger.phrase())?;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}],
|
}],
|
||||||
[Char('l'), NONE, "loop_toggle", "toggle looping", |_app: &mut App| {
|
[Char('l'), NONE, "loop_toggle", "toggle looping", |_app: &mut App| {
|
||||||
|
|
@ -147,14 +144,14 @@ pub const KEYMAP_FOCUS: &'static [KeyBinding<App>] = keymap!(App {
|
||||||
app.entered = false;
|
app.entered = false;
|
||||||
app.transport.entered = app.entered;
|
app.transport.entered = app.entered;
|
||||||
app.arranger.entered = app.entered;
|
app.arranger.entered = app.entered;
|
||||||
app.sequencer.entered = app.entered;
|
app.arranger.sequencer.entered = app.entered;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}],
|
}],
|
||||||
[Enter, NONE, "focus_enter", "activate item at cursor", |app: &mut App|{
|
[Enter, NONE, "focus_enter", "activate item at cursor", |app: &mut App|{
|
||||||
app.entered = true;
|
app.entered = true;
|
||||||
app.transport.entered = app.entered;
|
app.transport.entered = app.entered;
|
||||||
app.arranger.entered = app.entered;
|
app.arranger.entered = app.entered;
|
||||||
app.sequencer.entered = app.entered;
|
app.arranger.sequencer.entered = app.entered;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}],
|
}],
|
||||||
});
|
});
|
||||||
|
|
@ -165,8 +162,8 @@ pub fn focus_next (app: &mut App) -> Usually<bool> {
|
||||||
app.transport.entered = app.entered;
|
app.transport.entered = app.entered;
|
||||||
app.arranger.focused = app.section == AppFocus::Arranger;
|
app.arranger.focused = app.section == AppFocus::Arranger;
|
||||||
app.arranger.entered = app.entered;
|
app.arranger.entered = app.entered;
|
||||||
app.sequencer.focused = app.section == AppFocus::Sequencer;
|
app.arranger.sequencer.focused = app.section == AppFocus::Sequencer;
|
||||||
app.sequencer.entered = app.entered;
|
app.arranger.sequencer.entered = app.entered;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -176,7 +173,7 @@ pub fn focus_prev (app: &mut App) -> Usually<bool> {
|
||||||
app.transport.entered = app.entered;
|
app.transport.entered = app.entered;
|
||||||
app.arranger.focused = app.section == AppFocus::Arranger;
|
app.arranger.focused = app.section == AppFocus::Arranger;
|
||||||
app.arranger.entered = app.entered;
|
app.arranger.entered = app.entered;
|
||||||
app.sequencer.focused = app.section == AppFocus::Sequencer;
|
app.arranger.sequencer.focused = app.section == AppFocus::Sequencer;
|
||||||
app.sequencer.entered = app.entered;
|
app.arranger.sequencer.entered = app.entered;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,13 +13,7 @@
|
||||||
//! * [Sample::load_edn]
|
//! * [Sample::load_edn]
|
||||||
//! * [LV2Plugin::load_edn]
|
//! * [LV2Plugin::load_edn]
|
||||||
|
|
||||||
use crate::{core::*, model::*, App};
|
use crate::*;
|
||||||
use crate::devices::{
|
|
||||||
arranger::Scene,
|
|
||||||
sequencer::Phrase,
|
|
||||||
sampler::{Sampler, Sample, read_sample_data}
|
|
||||||
};
|
|
||||||
use crate::devices::plugin::{Plugin, LV2Plugin};
|
|
||||||
use clojure_reader::{edn::{read, Edn}, error::Error as EdnError};
|
use clojure_reader::{edn::{read, Edn}, error::Error as EdnError};
|
||||||
|
|
||||||
/// EDN parsing helper.
|
/// EDN parsing helper.
|
||||||
|
|
@ -239,81 +233,3 @@ impl Phrase {
|
||||||
Ok(phrase)
|
Ok(phrase)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sampler {
|
|
||||||
fn load_edn <'e> (args: &[Edn<'e>]) -> Usually<JackDevice> {
|
|
||||||
let mut name = String::new();
|
|
||||||
let mut dir = String::new();
|
|
||||||
let mut samples = BTreeMap::new();
|
|
||||||
edn!(edn in args {
|
|
||||||
Edn::Map(map) => {
|
|
||||||
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
|
|
||||||
name = String::from(*n);
|
|
||||||
}
|
|
||||||
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":dir")) {
|
|
||||||
dir = String::from(*n);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Edn::List(args) => match args.get(0) {
|
|
||||||
Some(Edn::Symbol("sample")) => {
|
|
||||||
let (midi, sample) = Sample::load_edn(&dir, &args[1..])?;
|
|
||||||
if let Some(midi) = midi {
|
|
||||||
samples.insert(midi, sample);
|
|
||||||
} else {
|
|
||||||
panic!("sample without midi binding: {}", sample.read().unwrap().name);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => panic!("unexpected in sampler {name}: {args:?}")
|
|
||||||
},
|
|
||||||
_ => panic!("unexpected in sampler {name}: {edn:?}")
|
|
||||||
});
|
|
||||||
Self::new(&name, Some(samples))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sample {
|
|
||||||
fn load_edn <'e> (dir: &str, args: &[Edn<'e>]) -> Usually<(Option<u7>, Arc<RwLock<Self>>)> {
|
|
||||||
let mut name = String::new();
|
|
||||||
let mut file = String::new();
|
|
||||||
let mut midi = None;
|
|
||||||
let mut start = 0usize;
|
|
||||||
edn!(edn in args {
|
|
||||||
Edn::Map(map) => {
|
|
||||||
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
|
|
||||||
name = String::from(*n);
|
|
||||||
}
|
|
||||||
if let Some(Edn::Str(f)) = map.get(&Edn::Key(":file")) {
|
|
||||||
file = String::from(*f);
|
|
||||||
}
|
|
||||||
if let Some(Edn::Int(i)) = map.get(&Edn::Key(":start")) {
|
|
||||||
start = *i as usize;
|
|
||||||
}
|
|
||||||
if let Some(Edn::Int(m)) = map.get(&Edn::Key(":midi")) {
|
|
||||||
midi = Some(u7::from(*m as u8));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => panic!("unexpected in sample {name}"),
|
|
||||||
});
|
|
||||||
let (end, data) = read_sample_data(&format!("{dir}/{file}"))?;
|
|
||||||
Ok((midi, Arc::new(RwLock::new(Self::new(&name, start, end, data)))))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LV2Plugin {
|
|
||||||
fn load_edn <'e> (args: &[Edn<'e>]) -> Usually<JackDevice> {
|
|
||||||
let mut name = String::new();
|
|
||||||
let mut path = String::new();
|
|
||||||
edn!(edn in args {
|
|
||||||
Edn::Map(map) => {
|
|
||||||
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
|
|
||||||
name = String::from(*n);
|
|
||||||
}
|
|
||||||
if let Some(Edn::Str(p)) = map.get(&Edn::Key(":path")) {
|
|
||||||
path = String::from(*p);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => panic!("unexpected in lv2 '{name}'"),
|
|
||||||
});
|
|
||||||
Plugin::lv2(&name, &path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
//! Help modal / command palette.
|
//! Help modal / command palette.
|
||||||
|
|
||||||
use crate::{core::*, view::*};
|
use crate::*;
|
||||||
|
|
||||||
/// Command palette.
|
/// Command palette.
|
||||||
pub struct HelpModal {
|
pub struct HelpModal {
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,22 @@
|
||||||
#![allow(macro_expanded_macro_exports_accessed_by_absolute_paths)]
|
#![allow(macro_expanded_macro_exports_accessed_by_absolute_paths)]
|
||||||
#![allow(ambiguous_glob_reexports)]
|
#![allow(ambiguous_glob_reexports)]
|
||||||
|
|
||||||
extern crate clap;
|
|
||||||
extern crate jack as _jack;
|
|
||||||
extern crate crossterm;
|
|
||||||
|
|
||||||
pub(crate) use tek_core::*;
|
pub(crate) use tek_core::*;
|
||||||
|
pub(crate) use tek_jack::{*, jack::*};
|
||||||
|
pub(crate) use tek_timer::*;
|
||||||
|
pub(crate) use microxdg::XdgApp;
|
||||||
|
|
||||||
|
submod! {
|
||||||
|
app
|
||||||
|
app_focus
|
||||||
|
app_paths
|
||||||
|
cli
|
||||||
|
control
|
||||||
|
edn
|
||||||
|
help
|
||||||
|
modal
|
||||||
|
setup
|
||||||
|
}
|
||||||
|
|
||||||
/// Application entrypoint.
|
/// Application entrypoint.
|
||||||
pub fn main () -> Usually<()> {
|
pub fn main () -> Usually<()> {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
//! Inital setup dialog (TODO: make this the options dialog too?)
|
//! Inital setup dialog (TODO: make this the options dialog too?)
|
||||||
|
|
||||||
use crate::{core::*, config::AppPaths};
|
use crate::*;
|
||||||
|
|
||||||
/// Appears on first run (i.e. if state dir is missing).
|
/// Appears on first run (i.e. if state dir is missing).
|
||||||
pub struct SetupModal(pub Option<Arc<XdgApp>>, pub bool);
|
pub struct SetupModal(pub Option<Arc<XdgApp>>, pub bool);
|
||||||
|
|
|
||||||
45
crates/tek_chain/src/chain_edn.rs
Normal file
45
crates/tek_chain/src/chain_edn.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
impl Track {
|
||||||
|
fn load_edn <'a, 'e> (app: &'a mut App, args: &[Edn<'e>]) -> Usually<&'a mut Self> {
|
||||||
|
let ppq = app.transport.ppq();
|
||||||
|
let mut name = None;
|
||||||
|
let mut _gain = 0.0f64;
|
||||||
|
let mut devices: Vec<JackDevice> = vec![];
|
||||||
|
let mut phrases: Vec<Phrase> = vec![];
|
||||||
|
edn!(edn in args {
|
||||||
|
Edn::Map(map) => {
|
||||||
|
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
|
||||||
|
name = Some(*n);
|
||||||
|
}
|
||||||
|
if let Some(Edn::Double(g)) = map.get(&Edn::Key(":gain")) {
|
||||||
|
_gain = f64::from(*g)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Edn::List(args) => match args.get(0) {
|
||||||
|
Some(Edn::Symbol("phrase")) => {
|
||||||
|
phrases.push(Phrase::load_edn(ppq, &args[1..])?)
|
||||||
|
},
|
||||||
|
Some(Edn::Symbol("sampler")) => {
|
||||||
|
devices.push(Sampler::load_edn(&args[1..])?)
|
||||||
|
},
|
||||||
|
Some(Edn::Symbol("lv2")) => {
|
||||||
|
devices.push(LV2Plugin::load_edn(&args[1..])?)
|
||||||
|
},
|
||||||
|
None => panic!("empty list track {}",
|
||||||
|
name.unwrap_or("")
|
||||||
|
),
|
||||||
|
_ => panic!("unexpected in track {}: {:?}",
|
||||||
|
name.unwrap_or(""),
|
||||||
|
args.get(0).unwrap()
|
||||||
|
)
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
});
|
||||||
|
let track = app.arranger.track_add(name)?;
|
||||||
|
for phrase in phrases { track.phrases.push(Arc::new(RwLock::new(phrase))); }
|
||||||
|
for device in devices { track.add_device(device)?; }
|
||||||
|
Ok(track)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -4,11 +4,9 @@ edition = "2021"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.5.4", features = [ "derive" ] }
|
|
||||||
crossterm = "0.27"
|
crossterm = "0.27"
|
||||||
ratatui = { version = "0.26.3", features = [ "unstable-widget-ref", "underline-color" ] }
|
ratatui = { version = "0.26.3", features = [ "unstable-widget-ref", "underline-color" ] }
|
||||||
backtrace = "0.3.72"
|
backtrace = "0.3.72"
|
||||||
microxdg = "0.1.2"
|
|
||||||
toml = "0.8.12"
|
toml = "0.8.12"
|
||||||
better-panic = "0.3.0"
|
better-panic = "0.3.0"
|
||||||
midly = "0.5"
|
midly = "0.5"
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,10 @@ pub use ratatui;
|
||||||
pub use crossterm;
|
pub use crossterm;
|
||||||
pub use midly;
|
pub use midly;
|
||||||
pub use std::sync::{Arc, Mutex, RwLock};
|
pub use std::sync::{Arc, Mutex, RwLock};
|
||||||
|
pub use std::collections::BTreeMap;
|
||||||
pub use crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers};
|
pub use crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers};
|
||||||
|
pub use ratatui::prelude::{Rect, Style, Color, Buffer};
|
||||||
|
pub use ratatui::style::Stylize;
|
||||||
|
|
||||||
pub(crate) use std::error::Error;
|
pub(crate) use std::error::Error;
|
||||||
pub(crate) use std::io::{stdout};
|
pub(crate) use std::io::{stdout};
|
||||||
|
|
|
||||||
20
crates/tek_plugin/src/lv2_edn.rs
Normal file
20
crates/tek_plugin/src/lv2_edn.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
use crate::*;
|
||||||
|
impl LV2Plugin {
|
||||||
|
fn load_edn <'e> (args: &[Edn<'e>]) -> Usually<JackDevice> {
|
||||||
|
let mut name = String::new();
|
||||||
|
let mut path = String::new();
|
||||||
|
edn!(edn in args {
|
||||||
|
Edn::Map(map) => {
|
||||||
|
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
|
||||||
|
name = String::from(*n);
|
||||||
|
}
|
||||||
|
if let Some(Edn::Str(p)) = map.get(&Edn::Key(":path")) {
|
||||||
|
path = String::from(*p);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => panic!("unexpected in lv2 '{name}'"),
|
||||||
|
});
|
||||||
|
Plugin::lv2(&name, &path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
60
crates/tek_sampler/src/sampler_edn.rs
Normal file
60
crates/tek_sampler/src/sampler_edn.rs
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
impl Sampler {
|
||||||
|
fn load_edn <'e> (args: &[Edn<'e>]) -> Usually<JackDevice> {
|
||||||
|
let mut name = String::new();
|
||||||
|
let mut dir = String::new();
|
||||||
|
let mut samples = BTreeMap::new();
|
||||||
|
edn!(edn in args {
|
||||||
|
Edn::Map(map) => {
|
||||||
|
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
|
||||||
|
name = String::from(*n);
|
||||||
|
}
|
||||||
|
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":dir")) {
|
||||||
|
dir = String::from(*n);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Edn::List(args) => match args.get(0) {
|
||||||
|
Some(Edn::Symbol("sample")) => {
|
||||||
|
let (midi, sample) = Sample::load_edn(&dir, &args[1..])?;
|
||||||
|
if let Some(midi) = midi {
|
||||||
|
samples.insert(midi, sample);
|
||||||
|
} else {
|
||||||
|
panic!("sample without midi binding: {}", sample.read().unwrap().name);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => panic!("unexpected in sampler {name}: {args:?}")
|
||||||
|
},
|
||||||
|
_ => panic!("unexpected in sampler {name}: {edn:?}")
|
||||||
|
});
|
||||||
|
Self::new(&name, Some(samples))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sample {
|
||||||
|
fn load_edn <'e> (dir: &str, args: &[Edn<'e>]) -> Usually<(Option<u7>, Arc<RwLock<Self>>)> {
|
||||||
|
let mut name = String::new();
|
||||||
|
let mut file = String::new();
|
||||||
|
let mut midi = None;
|
||||||
|
let mut start = 0usize;
|
||||||
|
edn!(edn in args {
|
||||||
|
Edn::Map(map) => {
|
||||||
|
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
|
||||||
|
name = String::from(*n);
|
||||||
|
}
|
||||||
|
if let Some(Edn::Str(f)) = map.get(&Edn::Key(":file")) {
|
||||||
|
file = String::from(*f);
|
||||||
|
}
|
||||||
|
if let Some(Edn::Int(i)) = map.get(&Edn::Key(":start")) {
|
||||||
|
start = *i as usize;
|
||||||
|
}
|
||||||
|
if let Some(Edn::Int(m)) = map.get(&Edn::Key(":midi")) {
|
||||||
|
midi = Some(u7::from(*m as u8));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => panic!("unexpected in sample {name}"),
|
||||||
|
});
|
||||||
|
let (end, data) = read_sample_data(&format!("{dir}/{file}"))?;
|
||||||
|
Ok((midi, Arc::new(RwLock::new(Self::new(&name, start, end, data)))))
|
||||||
|
}
|
||||||
|
}
|
||||||
58
crates/tek_sequencer/src/phrase_edn.rs
Normal file
58
crates/tek_sequencer/src/phrase_edn.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
impl Phrase {
|
||||||
|
fn load_edn <'e> (ppq: usize, args: &[Edn<'e>]) -> Usually<Self> {
|
||||||
|
let mut phrase = Self::default();
|
||||||
|
let mut name = String::new();
|
||||||
|
let mut beats = 0usize;
|
||||||
|
let mut steps = 0usize;
|
||||||
|
edn!(edn in args {
|
||||||
|
Edn::Map(map) => {
|
||||||
|
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
|
||||||
|
name = String::from(*n);
|
||||||
|
}
|
||||||
|
if let Some(Edn::Int(b)) = map.get(&Edn::Key(":beats")) {
|
||||||
|
beats = *b as usize;
|
||||||
|
phrase.length = ppq * beats;
|
||||||
|
for _ in phrase.notes.len()..phrase.length {
|
||||||
|
phrase.notes.push(Vec::with_capacity(16))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(Edn::Int(s)) = map.get(&Edn::Key(":steps")) {
|
||||||
|
steps = *s as usize;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Edn::List(args) => {
|
||||||
|
let time = (match args.get(0) {
|
||||||
|
Some(Edn::Key(text)) => text[1..].parse::<f64>()?,
|
||||||
|
Some(Edn::Int(i)) => *i as f64,
|
||||||
|
Some(Edn::Double(f)) => f64::from(*f),
|
||||||
|
_ => panic!("unexpected in phrase '{name}': {:?}", args.get(0)),
|
||||||
|
} * beats as f64 * ppq as f64 / steps as f64) as usize;
|
||||||
|
for edn in args[1..].iter() {
|
||||||
|
match edn {
|
||||||
|
Edn::List(args) => if let (
|
||||||
|
Some(Edn::Int(key)),
|
||||||
|
Some(Edn::Int(vel)),
|
||||||
|
) = (
|
||||||
|
args.get(0),
|
||||||
|
args.get(1),
|
||||||
|
) {
|
||||||
|
let (key, vel) = (
|
||||||
|
u7::from((*key as u8).min(127)),
|
||||||
|
u7::from((*vel as u8).min(127)),
|
||||||
|
);
|
||||||
|
phrase.notes[time].push(MidiMessage::NoteOn { key, vel })
|
||||||
|
} else {
|
||||||
|
panic!("unexpected list in phrase '{name}'")
|
||||||
|
},
|
||||||
|
_ => panic!("unexpected in phrase '{name}': {edn:?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => panic!("unexpected in phrase '{name}': {edn:?}"),
|
||||||
|
});
|
||||||
|
phrase.name = name;
|
||||||
|
Ok(phrase)
|
||||||
|
}
|
||||||
|
}
|
||||||
28
crates/tek_sequencer/src/scene_edn.rs
Normal file
28
crates/tek_sequencer/src/scene_edn.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
impl Scene {
|
||||||
|
fn load_edn <'a, 'e> (app: &'a mut App, args: &[Edn<'e>]) -> Usually<&'a mut Self> {
|
||||||
|
let mut name = None;
|
||||||
|
let mut clips = vec![];
|
||||||
|
edn!(edn in args {
|
||||||
|
Edn::Map(map) => {
|
||||||
|
let key = map.get(&Edn::Key(":name"));
|
||||||
|
if let Some(Edn::Str(n)) = key {
|
||||||
|
name = Some(*n);
|
||||||
|
} else {
|
||||||
|
panic!("unexpected key in scene '{name:?}': {key:?}")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Edn::Symbol("_") => {
|
||||||
|
clips.push(None);
|
||||||
|
},
|
||||||
|
Edn::Int(i) => {
|
||||||
|
clips.push(Some(*i as usize));
|
||||||
|
},
|
||||||
|
_ => panic!("unexpected in scene '{name:?}': {edn:?}")
|
||||||
|
});
|
||||||
|
let scene = app.arranger.scene_add(name)?;
|
||||||
|
scene.clips = clips;
|
||||||
|
Ok(scene)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue