mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
wip: refactor into api and app
This commit is contained in:
parent
051c3d3663
commit
70fc3c97d1
61 changed files with 453 additions and 255 deletions
22
Cargo.lock
generated
22
Cargo.lock
generated
|
|
@ -2657,6 +2657,28 @@ version = "0.12.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tek_api"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"tek_core",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tek_app"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"livi",
|
||||||
|
"suil-rs",
|
||||||
|
"symphonia",
|
||||||
|
"tek_api",
|
||||||
|
"tek_core",
|
||||||
|
"vst",
|
||||||
|
"wavers",
|
||||||
|
"winit",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tek_core"
|
name = "tek_core"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,6 @@
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = [
|
members = [
|
||||||
"crates/tek_core",
|
"crates/tek_core",
|
||||||
"crates/tek_mixer",
|
"crates/tek_api",
|
||||||
"crates/tek_sequencer"
|
"crates/tek_app"
|
||||||
]
|
]
|
||||||
|
|
|
||||||
8
crates/tek_api/Cargo.toml
Normal file
8
crates/tek_api/Cargo.toml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "tek_api"
|
||||||
|
edition = "2021"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tek_core = { path = "../tek_core" }
|
||||||
|
uuid = { version = "1.10.0", features = [ "v4" ] }
|
||||||
143
crates/tek_api/src/api_cmd.rs
Normal file
143
crates/tek_api/src/api_cmd.rs
Normal file
|
|
@ -0,0 +1,143 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum ArrangerCommand {
|
||||||
|
Focus(FocusCommand),
|
||||||
|
Transport(TransportCommand),
|
||||||
|
Phrases(PhrasePoolCommand),
|
||||||
|
Editor(PhraseEditorCommand),
|
||||||
|
Arrangement(ArrangementCommand),
|
||||||
|
EditPhrase(Option<Arc<RwLock<Phrase>>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum ArrangementCommand {
|
||||||
|
New,
|
||||||
|
Load,
|
||||||
|
Save,
|
||||||
|
ToggleViewMode,
|
||||||
|
Delete,
|
||||||
|
Activate,
|
||||||
|
Increment,
|
||||||
|
Decrement,
|
||||||
|
ZoomIn,
|
||||||
|
ZoomOut,
|
||||||
|
Go(Direction),
|
||||||
|
Edit(Option<Arc<RwLock<Phrase>>>),
|
||||||
|
Scene(SceneCommand),
|
||||||
|
Track(TrackCommand),
|
||||||
|
Clip(ClipCommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum SceneCommand {
|
||||||
|
Next,
|
||||||
|
Prev,
|
||||||
|
Add,
|
||||||
|
Delete,
|
||||||
|
MoveForward,
|
||||||
|
MoveBack,
|
||||||
|
RandomColor,
|
||||||
|
SetSize(usize),
|
||||||
|
SetZoom(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum TrackCommand {
|
||||||
|
Next,
|
||||||
|
Prev,
|
||||||
|
Add,
|
||||||
|
Delete,
|
||||||
|
MoveForward,
|
||||||
|
MoveBack,
|
||||||
|
RandomColor,
|
||||||
|
SetSize(usize),
|
||||||
|
SetZoom(usize),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum ClipCommand {
|
||||||
|
SetLoop(bool),
|
||||||
|
Get(usize, usize),
|
||||||
|
Put(usize, usize, Option<Arc<RwLock<Phrase>>>),
|
||||||
|
Edit(Option<Arc<RwLock<Phrase>>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
pub enum SequencerCommand {
|
||||||
|
Focus(FocusCommand),
|
||||||
|
Transport(TransportCommand),
|
||||||
|
Phrases(PhrasePoolCommand),
|
||||||
|
Editor(PhraseEditorCommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
pub enum PhrasePoolCommand {
|
||||||
|
Prev,
|
||||||
|
Next,
|
||||||
|
MoveUp,
|
||||||
|
MoveDown,
|
||||||
|
Delete,
|
||||||
|
Append,
|
||||||
|
Insert,
|
||||||
|
Duplicate,
|
||||||
|
RandomColor,
|
||||||
|
Edit,
|
||||||
|
Import,
|
||||||
|
Export,
|
||||||
|
Rename(PhraseRenameCommand),
|
||||||
|
Length(PhraseLengthCommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
pub enum PhraseRenameCommand {
|
||||||
|
Begin,
|
||||||
|
Backspace,
|
||||||
|
Append(char),
|
||||||
|
Set(String),
|
||||||
|
Confirm,
|
||||||
|
Cancel,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
pub enum PhraseLengthCommand {
|
||||||
|
Begin,
|
||||||
|
Next,
|
||||||
|
Prev,
|
||||||
|
Inc,
|
||||||
|
Dec,
|
||||||
|
Set(usize),
|
||||||
|
Confirm,
|
||||||
|
Cancel,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
pub enum PhraseEditorCommand {
|
||||||
|
// TODO: 1-9 seek markers that by default start every 8th of the phrase
|
||||||
|
ToggleDirection,
|
||||||
|
EnterEditMode,
|
||||||
|
ExitEditMode,
|
||||||
|
NoteAppend,
|
||||||
|
NoteSet,
|
||||||
|
NoteCursorSet(usize),
|
||||||
|
NoteLengthSet(usize),
|
||||||
|
NoteScrollSet(usize),
|
||||||
|
TimeCursorSet(usize),
|
||||||
|
TimeScrollSet(usize),
|
||||||
|
TimeZoomSet(usize),
|
||||||
|
Go(Direction),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
|
pub enum TransportCommand {
|
||||||
|
FocusNext,
|
||||||
|
FocusPrev,
|
||||||
|
Play(Option<usize>),
|
||||||
|
Pause(Option<usize>),
|
||||||
|
SeekUsec(f64),
|
||||||
|
SeekSample(f64),
|
||||||
|
SeekPulse(f64),
|
||||||
|
SetBpm(f64),
|
||||||
|
SetQuant(f64),
|
||||||
|
SetSync(f64),
|
||||||
|
}
|
||||||
158
crates/tek_api/src/api_edn.rs
Normal file
158
crates/tek_api/src/api_edn.rs
Normal file
|
|
@ -0,0 +1,158 @@
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
impl Scene {
|
||||||
|
pub fn from_edn <'a, 'e> (args: &[Edn<'e>]) -> Usually<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 = Self::new(name.unwrap_or(""), clips);
|
||||||
|
Ok(scene)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const SYM_NAME: &'static str = ":name";
|
||||||
|
const SYM_GAIN: &'static str = ":gain";
|
||||||
|
const SYM_SAMPLER: &'static str = "sampler";
|
||||||
|
const SYM_LV2: &'static str = "lv2";
|
||||||
|
|
||||||
|
impl<E: Engine> Track<E> {
|
||||||
|
pub fn from_edn <'a, 'e> (jack: &Arc<RwLock<JackClient>>, args: &[Edn<'e>]) -> Usually<Self> {
|
||||||
|
let mut _gain = 0.0f64;
|
||||||
|
let mut track = Self::new("")?;
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
let mut devices: Vec<JackDevice<E>> = vec![];
|
||||||
|
edn!(edn in args {
|
||||||
|
Edn::Map(map) => {
|
||||||
|
if let Some(Edn::Str(n)) = map.get(&Edn::Key(SYM_NAME)) {
|
||||||
|
track.name = n.to_string();
|
||||||
|
}
|
||||||
|
if let Some(Edn::Double(g)) = map.get(&Edn::Key(SYM_GAIN)) {
|
||||||
|
_gain = f64::from(*g);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Edn::List(args) => match args.get(0) {
|
||||||
|
// Add a sampler device to the track
|
||||||
|
Some(Edn::Symbol(SYM_SAMPLER)) => {
|
||||||
|
devices.push(Sampler::from_edn(jack, &args[1..])?);
|
||||||
|
panic!(
|
||||||
|
"unsupported in track {}: {:?}; tek_mixer not compiled with feature \"sampler\"",
|
||||||
|
&track.name,
|
||||||
|
args.get(0).unwrap()
|
||||||
|
)
|
||||||
|
},
|
||||||
|
// Add a LV2 plugin to the track.
|
||||||
|
Some(Edn::Symbol(SYM_LV2)) => {
|
||||||
|
devices.push(LV2Plugin::from_edn(jack, &args[1..])?);
|
||||||
|
panic!(
|
||||||
|
"unsupported in track {}: {:?}; tek_mixer not compiled with feature \"plugin\"",
|
||||||
|
&track.name,
|
||||||
|
args.get(0).unwrap()
|
||||||
|
)
|
||||||
|
},
|
||||||
|
None =>
|
||||||
|
panic!("empty list track {}", &track.name),
|
||||||
|
_ =>
|
||||||
|
panic!("unexpected in track {}: {:?}", &track.name, args.get(0).unwrap())
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
});
|
||||||
|
for device in devices {
|
||||||
|
track.add_device(device);
|
||||||
|
}
|
||||||
|
Ok(track)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LV2Plugin {
|
||||||
|
pub fn from_edn <'e, E: Engine> (jack: &Arc<RwLock<JackClient>>, args: &[Edn<'e>]) -> Usually<JackDevice<E>> {
|
||||||
|
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::new_lv2(jack, &name, &path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Engine> Sampler<E> {
|
||||||
|
pub fn from_edn <'e> (jack: &Arc<RwLock<JackClient>>, args: &[Edn<'e>]) -> Usually<JackDevice<E>> {
|
||||||
|
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::from_edn(jack, &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(jack, &name, Some(samples))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sample {
|
||||||
|
pub fn from_edn <'e> (jack: &Arc<RwLock<JackClient>>, 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)))))
|
||||||
|
}
|
||||||
|
}
|
||||||
28
crates/tek_api/src/api_midi.rs
Normal file
28
crates/tek_api/src/api_midi.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
use crate::*;
|
||||||
|
pub(crate) use tek_core::midly::MidiMessage;
|
||||||
|
|
||||||
|
/// MIDI message structural
|
||||||
|
pub type PhraseData = Vec<Vec<MidiMessage>>;
|
||||||
|
|
||||||
|
/// A MIDI sequence.
|
||||||
|
#[derive(Debug, Clone)] pub struct Phrase {
|
||||||
|
pub uuid: uuid::Uuid,
|
||||||
|
/// Name of phrase
|
||||||
|
pub name: String,
|
||||||
|
/// Temporal resolution in pulses per quarter note
|
||||||
|
pub ppq: usize,
|
||||||
|
/// Length of phrase in pulses
|
||||||
|
pub length: usize,
|
||||||
|
/// Notes in phrase
|
||||||
|
pub notes: PhraseData,
|
||||||
|
/// Whether to loop the phrase or play it once
|
||||||
|
pub loop_on: bool,
|
||||||
|
/// Start of loop
|
||||||
|
pub loop_start: usize,
|
||||||
|
/// Length of loop
|
||||||
|
pub loop_length: usize,
|
||||||
|
/// All notes are displayed with minimum length
|
||||||
|
pub percussive: bool,
|
||||||
|
/// Identifying color of phrase
|
||||||
|
pub color: ItemColorTriplet,
|
||||||
|
}
|
||||||
6
crates/tek_api/src/lib.rs
Normal file
6
crates/tek_api/src/lib.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
pub(crate) use tek_core::*;
|
||||||
|
submod! {
|
||||||
|
api_cmd
|
||||||
|
api_edn
|
||||||
|
api_midi
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
[package]
|
[package]
|
||||||
name = "tek_mixer"
|
name = "tek_app"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tek_core = { path = "../tek_core" }
|
tek_core = { path = "../tek_core" }
|
||||||
|
tek_api = { path = "../tek_api" }
|
||||||
|
|
||||||
livi = "0.7.4"
|
livi = "0.7.4"
|
||||||
suil-rs = { path = "../suil" }
|
suil-rs = { path = "../suil" }
|
||||||
|
|
@ -32,3 +33,15 @@ path = "src/sampler_cli.rs"
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "tek_plugin"
|
name = "tek_plugin"
|
||||||
path = "src/plugin_cli.rs"
|
path = "src/plugin_cli.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "tek_sequencer"
|
||||||
|
path = "src/sequencer_cli.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "tek_arranger"
|
||||||
|
path = "src/arranger_cli.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "tek_transport"
|
||||||
|
path = "src/transport_cli.rs"
|
||||||
|
|
@ -21,3 +21,25 @@ This crate implements time sync and JACK transport control.
|
||||||
* Todo: edit numeric values
|
* Todo: edit numeric values
|
||||||
* Todo: jump to time/bbt markers
|
* Todo: jump to time/bbt markers
|
||||||
* Todo: count xruns
|
* Todo: count xruns
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# `tek_mixer`
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// - Meters: propagate clipping:
|
||||||
|
// - If one stage clips, all stages after it are marked red
|
||||||
|
// - If one track clips, all tracks that feed from it are marked red?
|
||||||
|
|
||||||
|
# `tek_track`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# `tek_sampler`
|
||||||
|
|
||||||
|
This crate implements a sampler device which plays audio files
|
||||||
|
in response to MIDI notes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# `tek_plugin`
|
||||||
|
|
@ -9,7 +9,7 @@ pub enum ArrangerCommand {
|
||||||
Arrangement(ArrangementCommand),
|
Arrangement(ArrangementCommand),
|
||||||
EditPhrase(Option<Arc<RwLock<Phrase>>>),
|
EditPhrase(Option<Arc<RwLock<Phrase>>>),
|
||||||
}
|
}
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone)]
|
||||||
pub enum ArrangementCommand {
|
pub enum ArrangementCommand {
|
||||||
New,
|
New,
|
||||||
Load,
|
Load,
|
||||||
|
|
@ -33,7 +33,7 @@ pub enum ArrangementCommand {
|
||||||
GoDown,
|
GoDown,
|
||||||
GoLeft,
|
GoLeft,
|
||||||
GoRight,
|
GoRight,
|
||||||
Edit,
|
Edit(Option<Arc<RwLock<Phrase>>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Engine> Command<Arranger<E>> for ArrangerCommand {
|
impl<E: Engine> Command<Arranger<E>> for ArrangerCommand {
|
||||||
|
|
@ -75,7 +75,7 @@ impl<E: Engine> Command<Arrangement<E>> for ArrangementCommand {
|
||||||
New => todo!(),
|
New => todo!(),
|
||||||
Load => todo!(),
|
Load => todo!(),
|
||||||
Save => todo!(),
|
Save => todo!(),
|
||||||
Edit => todo!(),
|
Edit(phrase) => { state.phrase = phrase.clone() },
|
||||||
ToggleViewMode => { state.mode.to_next(); },
|
ToggleViewMode => { state.mode.to_next(); },
|
||||||
Delete => { state.delete(); },
|
Delete => { state.delete(); },
|
||||||
Activate => { state.activate(); },
|
Activate => { state.activate(); },
|
||||||
|
|
@ -78,7 +78,7 @@ impl Handle<Tui> for Arrangement<Tui> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InputToCommand<Tui, Arrangement<Tui>> for ArrangementCommand {
|
impl InputToCommand<Tui, Arrangement<Tui>> for ArrangementCommand {
|
||||||
fn input_to_command (_: &Arrangement<Tui>, input: &TuiInput) -> Option<Self> {
|
fn input_to_command (state: &Arrangement<Tui>, input: &TuiInput) -> Option<Self> {
|
||||||
use ArrangementCommand::*;
|
use ArrangementCommand::*;
|
||||||
match input.event() {
|
match input.event() {
|
||||||
key!(KeyCode::Char('`')) => Some(ToggleViewMode),
|
key!(KeyCode::Char('`')) => Some(ToggleViewMode),
|
||||||
|
|
@ -95,7 +95,7 @@ impl InputToCommand<Tui, Arrangement<Tui>> for ArrangementCommand {
|
||||||
key!(KeyCode::Char('c')) => Some(RandomColor),
|
key!(KeyCode::Char('c')) => Some(RandomColor),
|
||||||
key!(KeyCode::Char('s')) => Some(Put),
|
key!(KeyCode::Char('s')) => Some(Put),
|
||||||
key!(KeyCode::Char('g')) => Some(Get),
|
key!(KeyCode::Char('g')) => Some(Get),
|
||||||
key!(KeyCode::Char('e')) => Some(Edit),
|
key!(KeyCode::Char('e')) => Some(Edit(state.phrase())),
|
||||||
key!(Ctrl-KeyCode::Char('a')) => Some(AddScene),
|
key!(Ctrl-KeyCode::Char('a')) => Some(AddScene),
|
||||||
key!(Ctrl-KeyCode::Char('t')) => Some(AddTrack),
|
key!(Ctrl-KeyCode::Char('t')) => Some(AddTrack),
|
||||||
key!(KeyCode::Char('l')) => Some(ToggleLoop),
|
key!(KeyCode::Char('l')) => Some(ToggleLoop),
|
||||||
45
crates/tek_app/src/lib.rs
Normal file
45
crates/tek_app/src/lib.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
pub(crate) use tek_core::*;
|
||||||
|
pub(crate) use tek_core::crossterm::event::{KeyCode, KeyModifiers};
|
||||||
|
pub(crate) use tek_core::midly::{num::u7, live::LiveEvent, MidiMessage};
|
||||||
|
pub(crate) use tek_core::jack::*;
|
||||||
|
|
||||||
|
pub(crate) use std::collections::BTreeMap;
|
||||||
|
pub(crate) use std::sync::{Arc, Mutex, RwLock};
|
||||||
|
pub(crate) use std::path::PathBuf;
|
||||||
|
pub(crate) use std::ffi::OsString;
|
||||||
|
pub(crate) use std::fs::read_dir;
|
||||||
|
|
||||||
|
submod! {
|
||||||
|
arranger
|
||||||
|
arranger_cmd
|
||||||
|
arranger_snd
|
||||||
|
arranger_tui
|
||||||
|
arranger_tui_bar
|
||||||
|
arranger_tui_cmd
|
||||||
|
arranger_tui_col
|
||||||
|
arranger_tui_hor
|
||||||
|
arranger_tui_ver
|
||||||
|
sequencer
|
||||||
|
sequencer_cmd
|
||||||
|
sequencer_snd
|
||||||
|
sequencer_tui
|
||||||
|
transport
|
||||||
|
transport_cmd
|
||||||
|
transport_snd
|
||||||
|
transport_tui
|
||||||
|
mixer
|
||||||
|
mixer_snd
|
||||||
|
mixer_cmd
|
||||||
|
mixer_tui
|
||||||
|
sampler
|
||||||
|
sampler_snd
|
||||||
|
sampler_cmd
|
||||||
|
plugin
|
||||||
|
plugin_snd
|
||||||
|
plugin_cmd
|
||||||
|
plugin_tui
|
||||||
|
plugin_lv2
|
||||||
|
plugin_lv2_gui
|
||||||
|
plugin_vst2
|
||||||
|
plugin_vst3
|
||||||
|
}
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
# `tek_mixer`
|
|
||||||
|
|
||||||
// TODO:
|
|
||||||
// - Meters: propagate clipping:
|
|
||||||
// - If one stage clips, all stages after it are marked red
|
|
||||||
// - If one track clips, all tracks that feed from it are marked red?
|
|
||||||
|
|
||||||
# `tek_track`
|
|
||||||
|
|
||||||
# `tek_sampler`
|
|
||||||
|
|
||||||
This crate implements a sampler device which plays audio files
|
|
||||||
in response to MIDI notes.
|
|
||||||
|
|
||||||
# `tek_plugin`
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
pub(crate) use tek_core::*;
|
|
||||||
pub(crate) use tek_core::crossterm::event::{KeyCode, KeyModifiers};
|
|
||||||
pub(crate) use tek_core::midly::{num::u7, live::LiveEvent, MidiMessage};
|
|
||||||
pub(crate) use tek_core::jack::*;
|
|
||||||
|
|
||||||
pub(crate) use std::collections::BTreeMap;
|
|
||||||
pub(crate) use std::sync::{Arc, Mutex, RwLock};
|
|
||||||
pub(crate) use std::path::PathBuf;
|
|
||||||
pub(crate) use std::ffi::OsString;
|
|
||||||
pub(crate) use std::fs::read_dir;
|
|
||||||
|
|
||||||
submod! {
|
|
||||||
mixer mixer_snd mixer_edn mixer_cmd mixer_tui
|
|
||||||
sampler sampler_snd sampler_edn sampler_cmd
|
|
||||||
plugin plugin_snd plugin_edn plugin_cmd plugin_tui plugin_lv2 plugin_lv2_gui plugin_vst2 plugin_vst3
|
|
||||||
}
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
const SYM_NAME: &'static str = ":name";
|
|
||||||
const SYM_GAIN: &'static str = ":gain";
|
|
||||||
const SYM_SAMPLER: &'static str = "sampler";
|
|
||||||
const SYM_LV2: &'static str = "lv2";
|
|
||||||
|
|
||||||
impl<E: Engine> Track<E> {
|
|
||||||
pub fn from_edn <'a, 'e> (jack: &Arc<RwLock<JackClient>>, args: &[Edn<'e>]) -> Usually<Self> {
|
|
||||||
let mut _gain = 0.0f64;
|
|
||||||
let mut track = Self::new("")?;
|
|
||||||
#[allow(unused_mut)]
|
|
||||||
let mut devices: Vec<JackDevice<E>> = vec![];
|
|
||||||
edn!(edn in args {
|
|
||||||
Edn::Map(map) => {
|
|
||||||
if let Some(Edn::Str(n)) = map.get(&Edn::Key(SYM_NAME)) {
|
|
||||||
track.name = n.to_string();
|
|
||||||
}
|
|
||||||
if let Some(Edn::Double(g)) = map.get(&Edn::Key(SYM_GAIN)) {
|
|
||||||
_gain = f64::from(*g);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Edn::List(args) => match args.get(0) {
|
|
||||||
// Add a sampler device to the track
|
|
||||||
Some(Edn::Symbol(SYM_SAMPLER)) => {
|
|
||||||
devices.push(Sampler::from_edn(jack, &args[1..])?);
|
|
||||||
panic!(
|
|
||||||
"unsupported in track {}: {:?}; tek_mixer not compiled with feature \"sampler\"",
|
|
||||||
&track.name,
|
|
||||||
args.get(0).unwrap()
|
|
||||||
)
|
|
||||||
},
|
|
||||||
// Add a LV2 plugin to the track.
|
|
||||||
Some(Edn::Symbol(SYM_LV2)) => {
|
|
||||||
devices.push(LV2Plugin::from_edn(jack, &args[1..])?);
|
|
||||||
panic!(
|
|
||||||
"unsupported in track {}: {:?}; tek_mixer not compiled with feature \"plugin\"",
|
|
||||||
&track.name,
|
|
||||||
args.get(0).unwrap()
|
|
||||||
)
|
|
||||||
},
|
|
||||||
None =>
|
|
||||||
panic!("empty list track {}", &track.name),
|
|
||||||
_ =>
|
|
||||||
panic!("unexpected in track {}: {:?}", &track.name, args.get(0).unwrap())
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
});
|
|
||||||
for device in devices {
|
|
||||||
track.add_device(device);
|
|
||||||
}
|
|
||||||
Ok(track)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
impl LV2Plugin {
|
|
||||||
pub fn from_edn <'e, E: Engine> (jack: &Arc<RwLock<JackClient>>, args: &[Edn<'e>]) -> Usually<JackDevice<E>> {
|
|
||||||
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::new_lv2(jack, &name, &path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
impl<E: Engine> Sampler<E> {
|
|
||||||
pub fn from_edn <'e> (jack: &Arc<RwLock<JackClient>>, args: &[Edn<'e>]) -> Usually<JackDevice<E>> {
|
|
||||||
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::from_edn(jack, &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(jack, &name, Some(samples))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sample {
|
|
||||||
pub fn from_edn <'e> (jack: &Arc<RwLock<JackClient>>, 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)))))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "tek_sequencer"
|
|
||||||
edition = "2021"
|
|
||||||
version = "0.1.0"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
tek_core = { path = "../tek_core" }
|
|
||||||
uuid = { version = "1.10.0", features = [ "v4" ] }
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
path = "src/lib.rs"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "tek_sequencer"
|
|
||||||
path = "src/sequencer_cli.rs"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "tek_arranger"
|
|
||||||
path = "src/arranger_cli.rs"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "tek_transport"
|
|
||||||
path = "src/transport_cli.rs"
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
impl Scene {
|
|
||||||
pub fn from_edn <'a, 'e> (args: &[Edn<'e>]) -> Usually<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 = Self::new(name.unwrap_or(""), clips);
|
|
||||||
Ok(scene)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
/// Phrase editor.
|
|
||||||
|
|
||||||
pub(crate) use tek_core::*;
|
|
||||||
pub(crate) use tek_core::crossterm::event::KeyCode;
|
|
||||||
pub(crate) use tek_core::midly::{num::u7, live::LiveEvent, MidiMessage};
|
|
||||||
pub(crate) use tek_core::jack::*;
|
|
||||||
pub(crate) use std::sync::{Arc, RwLock};
|
|
||||||
|
|
||||||
submod! {
|
|
||||||
arranger
|
|
||||||
arranger_cmd
|
|
||||||
arranger_snd
|
|
||||||
arranger_tui
|
|
||||||
arranger_tui_bar
|
|
||||||
arranger_tui_cmd
|
|
||||||
arranger_tui_col
|
|
||||||
arranger_tui_hor
|
|
||||||
arranger_tui_ver
|
|
||||||
sequencer
|
|
||||||
sequencer_cmd
|
|
||||||
sequencer_snd
|
|
||||||
sequencer_tui
|
|
||||||
transport
|
|
||||||
transport_cmd
|
|
||||||
transport_snd
|
|
||||||
transport_tui
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Octave number (from -1 to 9)
|
|
||||||
pub const NTH_OCTAVE: [&'static str;11] = [
|
|
||||||
"-1", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"
|
|
||||||
];
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue