mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
wip: refactor: cli, snd, tui
This commit is contained in:
parent
70fc3c97d1
commit
84aacfea82
58 changed files with 416 additions and 191 deletions
33
Cargo.lock
generated
33
Cargo.lock
generated
|
|
@ -2661,22 +2661,20 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
||||||
name = "tek_api"
|
name = "tek_api"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"livi",
|
||||||
|
"symphonia",
|
||||||
"tek_core",
|
"tek_core",
|
||||||
"uuid",
|
"uuid",
|
||||||
|
"vst",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tek_app"
|
name = "tek_cli"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"livi",
|
|
||||||
"suil-rs",
|
|
||||||
"symphonia",
|
|
||||||
"tek_api",
|
"tek_api",
|
||||||
"tek_core",
|
"tek_core",
|
||||||
"vst",
|
"tek_tui",
|
||||||
"wavers",
|
|
||||||
"winit",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2695,32 +2693,31 @@ dependencies = [
|
||||||
"palette",
|
"palette",
|
||||||
"rand",
|
"rand",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"tek_mixer",
|
|
||||||
"tek_sequencer",
|
|
||||||
"toml",
|
"toml",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tek_mixer"
|
name = "tek_snd"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"tek_api",
|
||||||
|
"tek_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tek_tui"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"livi",
|
"livi",
|
||||||
"suil-rs",
|
"suil-rs",
|
||||||
"symphonia",
|
"symphonia",
|
||||||
|
"tek_api",
|
||||||
"tek_core",
|
"tek_core",
|
||||||
"vst",
|
"vst",
|
||||||
"wavers",
|
"wavers",
|
||||||
"winit",
|
"winit",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tek_sequencer"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"tek_core",
|
|
||||||
"uuid",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.63"
|
version = "1.0.63"
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = [
|
members = [
|
||||||
"crates/tek_core",
|
"crates/tek_core",
|
||||||
|
"crates/tek_snd",
|
||||||
"crates/tek_api",
|
"crates/tek_api",
|
||||||
"crates/tek_app"
|
"crates/tek_cli",
|
||||||
|
"crates/tek_tui"
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@ clojure-reader = "0.1.0"
|
||||||
microxdg = "0.1.2"
|
microxdg = "0.1.2"
|
||||||
|
|
||||||
tek_core = { path = "../tek_core" }
|
tek_core = { path = "../tek_core" }
|
||||||
tek_sequencer = { path = "../tek_sequencer" }
|
#tek_sequencer = { path = "../tek_sequencer" }
|
||||||
tek_mixer = { path = "../tek_mixer" }
|
#tek_mixer = { path = "../tek_mixer" }
|
||||||
#jack = "0.10"
|
#jack = "0.10"
|
||||||
#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" ] }
|
||||||
|
|
|
||||||
|
|
@ -6,3 +6,6 @@ version = "0.1.0"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tek_core = { path = "../tek_core" }
|
tek_core = { path = "../tek_core" }
|
||||||
uuid = { version = "1.10.0", features = [ "v4" ] }
|
uuid = { version = "1.10.0", features = [ "v4" ] }
|
||||||
|
vst = "0.4.0"
|
||||||
|
livi = "0.7.4"
|
||||||
|
symphonia = { version = "0.5.4", features = [ "all" ] }
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
use crate::midly::num::u7;
|
||||||
|
|
||||||
impl Scene {
|
impl Scene {
|
||||||
pub fn from_edn <'a, 'e> (args: &[Edn<'e>]) -> Usually<Self> {
|
pub fn from_edn <'a, 'e> (args: &[Edn<'e>]) -> Usually<Self> {
|
||||||
|
|
@ -26,29 +27,29 @@ impl 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> {
|
impl MixerTrack {
|
||||||
|
const SYM_NAME: &'static str = ":name";
|
||||||
|
const SYM_GAIN: &'static str = ":gain";
|
||||||
|
const SYM_SAMPLER: &'static str = "sampler";
|
||||||
|
const SYM_LV2: &'static str = "lv2";
|
||||||
pub fn from_edn <'a, 'e> (jack: &Arc<RwLock<JackClient>>, args: &[Edn<'e>]) -> Usually<Self> {
|
pub fn from_edn <'a, 'e> (jack: &Arc<RwLock<JackClient>>, args: &[Edn<'e>]) -> Usually<Self> {
|
||||||
let mut _gain = 0.0f64;
|
let mut _gain = 0.0f64;
|
||||||
let mut track = Self::new("")?;
|
let mut track = Self::new("")?;
|
||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
let mut devices: Vec<JackDevice<E>> = vec![];
|
let mut devices: Vec<JackDevice> = vec![];
|
||||||
edn!(edn in args {
|
edn!(edn in args {
|
||||||
Edn::Map(map) => {
|
Edn::Map(map) => {
|
||||||
if let Some(Edn::Str(n)) = map.get(&Edn::Key(SYM_NAME)) {
|
if let Some(Edn::Str(n)) = map.get(&Edn::Key(Self::SYM_NAME)) {
|
||||||
track.name = n.to_string();
|
track.name = n.to_string();
|
||||||
}
|
}
|
||||||
if let Some(Edn::Double(g)) = map.get(&Edn::Key(SYM_GAIN)) {
|
if let Some(Edn::Double(g)) = map.get(&Edn::Key(Self::SYM_GAIN)) {
|
||||||
_gain = f64::from(*g);
|
_gain = f64::from(*g);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Edn::List(args) => match args.get(0) {
|
Edn::List(args) => match args.get(0) {
|
||||||
// Add a sampler device to the track
|
// Add a sampler device to the track
|
||||||
Some(Edn::Symbol(SYM_SAMPLER)) => {
|
Some(Edn::Symbol(Self::SYM_SAMPLER)) => {
|
||||||
devices.push(Sampler::from_edn(jack, &args[1..])?);
|
devices.push(Sampler::from_edn(jack, &args[1..])?);
|
||||||
panic!(
|
panic!(
|
||||||
"unsupported in track {}: {:?}; tek_mixer not compiled with feature \"sampler\"",
|
"unsupported in track {}: {:?}; tek_mixer not compiled with feature \"sampler\"",
|
||||||
|
|
@ -57,7 +58,7 @@ impl<E: Engine> Track<E> {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
// Add a LV2 plugin to the track.
|
// Add a LV2 plugin to the track.
|
||||||
Some(Edn::Symbol(SYM_LV2)) => {
|
Some(Edn::Symbol(Self::SYM_LV2)) => {
|
||||||
devices.push(LV2Plugin::from_edn(jack, &args[1..])?);
|
devices.push(LV2Plugin::from_edn(jack, &args[1..])?);
|
||||||
panic!(
|
panic!(
|
||||||
"unsupported in track {}: {:?}; tek_mixer not compiled with feature \"plugin\"",
|
"unsupported in track {}: {:?}; tek_mixer not compiled with feature \"plugin\"",
|
||||||
|
|
@ -80,7 +81,7 @@ impl<E: Engine> Track<E> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LV2Plugin {
|
impl LV2Plugin {
|
||||||
pub fn from_edn <'e, E: Engine> (jack: &Arc<RwLock<JackClient>>, args: &[Edn<'e>]) -> Usually<JackDevice<E>> {
|
pub fn from_edn <'e> (jack: &Arc<RwLock<JackClient>>, args: &[Edn<'e>]) -> Usually<JackDevice> {
|
||||||
let mut name = String::new();
|
let mut name = String::new();
|
||||||
let mut path = String::new();
|
let mut path = String::new();
|
||||||
edn!(edn in args {
|
edn!(edn in args {
|
||||||
|
|
@ -98,8 +99,8 @@ impl LV2Plugin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E: Engine> Sampler<E> {
|
impl Sampler {
|
||||||
pub fn from_edn <'e> (jack: &Arc<RwLock<JackClient>>, args: &[Edn<'e>]) -> Usually<JackDevice<E>> {
|
pub fn from_edn <'e> (jack: &Arc<RwLock<JackClient>>, args: &[Edn<'e>]) -> Usually<JackDevice> {
|
||||||
let mut name = String::new();
|
let mut name = String::new();
|
||||||
let mut dir = String::new();
|
let mut dir = String::new();
|
||||||
let mut samples = BTreeMap::new();
|
let mut samples = BTreeMap::new();
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
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,
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,242 @@
|
||||||
pub(crate) use tek_core::*;
|
pub(crate) use tek_core::*;
|
||||||
|
pub(crate) use tek_core::jack::{TransportState, Port, MidiIn, MidiOut};
|
||||||
|
pub(crate) use tek_core::midly::{MidiMessage, num::u7};
|
||||||
|
pub(crate) use std::thread::JoinHandle;
|
||||||
|
|
||||||
submod! {
|
submod! {
|
||||||
api_cmd
|
api_cmd
|
||||||
api_edn
|
api_edn
|
||||||
api_midi
|
}
|
||||||
|
|
||||||
|
/// A timer with starting point, current time, and quantization
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct Clock {
|
||||||
|
/// Playback state
|
||||||
|
pub playing: RwLock<Option<TransportState>>,
|
||||||
|
/// Global sample and usec at which playback started
|
||||||
|
pub started: RwLock<Option<(usize, usize)>>,
|
||||||
|
/// Current moment in time
|
||||||
|
pub current: Instant,
|
||||||
|
/// Note quantization factor
|
||||||
|
pub quant: Quantize,
|
||||||
|
/// Launch quantization factor
|
||||||
|
pub sync: LaunchSync,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clock {
|
||||||
|
#[inline] pub fn timebase (&self) -> &Arc<Timebase> {
|
||||||
|
&self.current.timebase
|
||||||
|
}
|
||||||
|
#[inline] pub fn pulse (&self) -> f64 {
|
||||||
|
self.current.pulse.get()
|
||||||
|
}
|
||||||
|
#[inline] pub fn quant (&self) -> f64 {
|
||||||
|
self.quant.get()
|
||||||
|
}
|
||||||
|
#[inline] pub fn sync (&self) -> f64 {
|
||||||
|
self.sync.get()
|
||||||
|
}
|
||||||
|
#[inline] pub fn next_launch_pulse (&self) -> usize {
|
||||||
|
let sync = self.sync.get() as usize;
|
||||||
|
let pulse = self.current.pulse.get() as usize;
|
||||||
|
if pulse % sync == 0 { pulse } else { (pulse / sync + 1) * sync }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TransportToolbar {
|
||||||
|
pub jack: Arc<RwLock<JackClient>>,
|
||||||
|
/// JACK transport handle.
|
||||||
|
pub transport: jack::Transport,
|
||||||
|
/// Current sample rate, tempo, and PPQ.
|
||||||
|
pub clock: Arc<Clock>,
|
||||||
|
/// Enable metronome?
|
||||||
|
pub metronome: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Arrangement {
|
||||||
|
/// JACK client handle (needs to not be dropped for standalone mode to work).
|
||||||
|
pub jack: Arc<RwLock<JackClient>>,
|
||||||
|
/// Global timebase
|
||||||
|
pub clock: Arc<Clock>,
|
||||||
|
/// Name of arranger
|
||||||
|
pub name: Arc<RwLock<String>>,
|
||||||
|
/// Collection of phrases.
|
||||||
|
pub phrases: Arc<RwLock<Vec<Phrase>>>,
|
||||||
|
/// Collection of tracks.
|
||||||
|
pub tracks: Vec<Sequencer>,
|
||||||
|
/// Collection of scenes.
|
||||||
|
pub scenes: Vec<Scene>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Sequencer {
|
||||||
|
/// Name of track
|
||||||
|
pub name: Arc<RwLock<String>>,
|
||||||
|
/// Preferred width of track column
|
||||||
|
pub width: usize,
|
||||||
|
/// Identifying color of track
|
||||||
|
pub color: ItemColor,
|
||||||
|
/// MIDI player/recorder
|
||||||
|
pub player: PhrasePlayer,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Phrase player.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PhrasePlayer {
|
||||||
|
/// Global timebase
|
||||||
|
pub clock: Arc<Clock>,
|
||||||
|
/// Start time and phrase being played
|
||||||
|
pub phrase: Option<(Instant, Option<Arc<RwLock<Phrase>>>)>,
|
||||||
|
/// Start time and next phrase
|
||||||
|
pub next_phrase: Option<(Instant, Option<Arc<RwLock<Phrase>>>)>,
|
||||||
|
/// Play input through output.
|
||||||
|
pub monitoring: bool,
|
||||||
|
/// Write input to sequence.
|
||||||
|
pub recording: bool,
|
||||||
|
/// Overdub input to sequence.
|
||||||
|
pub overdub: bool,
|
||||||
|
/// Send all notes off
|
||||||
|
pub reset: bool, // TODO?: after Some(nframes)
|
||||||
|
/// Record from MIDI ports to current sequence.
|
||||||
|
pub midi_inputs: Vec<Port<MidiIn>>,
|
||||||
|
/// Play from current sequence to MIDI ports
|
||||||
|
pub midi_outputs: Vec<Port<MidiOut>>,
|
||||||
|
/// MIDI output buffer
|
||||||
|
pub midi_note: Vec<u8>,
|
||||||
|
/// MIDI output buffer
|
||||||
|
pub midi_chunk: Vec<Vec<Vec<u8>>>,
|
||||||
|
/// Notes currently held at input
|
||||||
|
pub notes_in: Arc<RwLock<[bool; 128]>>,
|
||||||
|
/// Notes currently held at output
|
||||||
|
pub notes_out: Arc<RwLock<[bool; 128]>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone)]
|
||||||
|
pub struct Scene {
|
||||||
|
/// Name of scene
|
||||||
|
pub name: Arc<RwLock<String>>,
|
||||||
|
/// Clips in scene, one per track
|
||||||
|
pub clips: Vec<Option<Arc<RwLock<Phrase>>>>,
|
||||||
|
/// Identifying color of scene
|
||||||
|
pub color: ItemColor,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A MIDI sequence.
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// MIDI message structural
|
||||||
|
pub type PhraseData = Vec<Vec<MidiMessage>>;
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone)]
|
||||||
|
pub struct Mixer {
|
||||||
|
/// JACK client handle (needs to not be dropped for standalone mode to work).
|
||||||
|
pub jack: Arc<RwLock<JackClient>>,
|
||||||
|
pub name: String,
|
||||||
|
pub tracks: Vec<MixerTrack>,
|
||||||
|
pub selected_track: usize,
|
||||||
|
pub selected_column: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A mixer track.
|
||||||
|
#[derive(Default, Debug, Clone)]
|
||||||
|
pub struct MixerTrack {
|
||||||
|
pub name: String,
|
||||||
|
/// Inputs and outputs of 1st and last device
|
||||||
|
pub ports: JackPorts,
|
||||||
|
/// Device chain
|
||||||
|
pub devices: Vec<JackDevice>,
|
||||||
|
/// Device selector
|
||||||
|
pub device: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The sampler plugin plays sounds.
|
||||||
|
#[derive(Default, Debug, Clone)]
|
||||||
|
pub struct Sampler {
|
||||||
|
pub jack: Arc<RwLock<JackClient>>,
|
||||||
|
pub name: String,
|
||||||
|
pub cursor: (usize, usize),
|
||||||
|
pub editing: Option<Arc<RwLock<Sample>>>,
|
||||||
|
pub mapped: BTreeMap<u7, Arc<RwLock<Sample>>>,
|
||||||
|
pub unmapped: Vec<Arc<RwLock<Sample>>>,
|
||||||
|
pub voices: Arc<RwLock<Vec<Voice>>>,
|
||||||
|
pub ports: JackPorts,
|
||||||
|
pub buffer: Vec<Vec<f32>>,
|
||||||
|
pub modal: Arc<Mutex<Option<Box<dyn Exit + Send>>>>,
|
||||||
|
pub output_gain: f32
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A sound sample.
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct Sample {
|
||||||
|
pub name: String,
|
||||||
|
pub start: usize,
|
||||||
|
pub end: usize,
|
||||||
|
pub channels: Vec<Vec<f32>>,
|
||||||
|
pub rate: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A currently playing instance of a sample.
|
||||||
|
#[derive(Default, Debug, Clone)]
|
||||||
|
pub struct Voice {
|
||||||
|
pub sample: Arc<RwLock<Sample>>,
|
||||||
|
pub after: usize,
|
||||||
|
pub position: usize,
|
||||||
|
pub velocity: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A plugin device.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Plugin {
|
||||||
|
/// JACK client handle (needs to not be dropped for standalone mode to work).
|
||||||
|
pub jack: Arc<RwLock<JackClient>>,
|
||||||
|
pub name: String,
|
||||||
|
pub path: Option<String>,
|
||||||
|
pub plugin: Option<PluginKind>,
|
||||||
|
pub selected: usize,
|
||||||
|
pub mapping: bool,
|
||||||
|
pub ports: JackPorts,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Supported plugin formats.
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub enum PluginKind {
|
||||||
|
#[default] None,
|
||||||
|
LV2(LV2Plugin),
|
||||||
|
VST2 {
|
||||||
|
instance: ::vst::host::PluginInstance
|
||||||
|
},
|
||||||
|
VST3,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A LV2 plugin.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct LV2Plugin {
|
||||||
|
pub world: livi::World,
|
||||||
|
pub instance: livi::Instance,
|
||||||
|
pub plugin: livi::LiviPlugin,
|
||||||
|
pub features: Arc<livi::Features>,
|
||||||
|
pub port_list: Vec<livi::Port>,
|
||||||
|
pub input_buffer: Vec<livi::event::LV2AtomSequence>,
|
||||||
|
pub ui_thread: Option<JoinHandle<()>>,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,105 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
/// A timer with starting point, current time, and quantization
|
|
||||||
#[derive(Debug, Default)] pub struct TransportTime {
|
|
||||||
/// Playback state
|
|
||||||
pub playing: RwLock<Option<TransportState>>,
|
|
||||||
/// Global sample and usec at which playback started
|
|
||||||
pub started: RwLock<Option<(usize, usize)>>,
|
|
||||||
/// Current moment in time
|
|
||||||
pub current: Instant,
|
|
||||||
/// Note quantization factor
|
|
||||||
pub quant: Quantize,
|
|
||||||
/// Launch quantization factor
|
|
||||||
pub sync: LaunchSync,
|
|
||||||
}
|
|
||||||
impl TransportTime {
|
|
||||||
#[inline] pub fn timebase (&self) -> &Arc<Timebase> { &self.current.timebase }
|
|
||||||
#[inline] pub fn pulse (&self) -> f64 { self.current.pulse.get() }
|
|
||||||
#[inline] pub fn quant (&self) -> f64 { self.quant.get() }
|
|
||||||
#[inline] pub fn sync (&self) -> f64 { self.sync.get() }
|
|
||||||
#[inline] pub fn next_launch_pulse (&self) -> usize {
|
|
||||||
let sync = self.sync.get() as usize;
|
|
||||||
let pulse = self.current.pulse.get() as usize;
|
|
||||||
if pulse % sync == 0 { pulse } else { (pulse / sync + 1) * sync }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Stores and displays time-related state.
|
|
||||||
pub struct TransportToolbar<E: Engine> {
|
|
||||||
_engine: PhantomData<E>,
|
|
||||||
/// JACK client handle (needs to not be dropped for standalone mode to work).
|
|
||||||
pub jack: Arc<RwLock<JackClient>>,
|
|
||||||
/// JACK transport handle.
|
|
||||||
pub transport: Transport,
|
|
||||||
/// Current sample rate, tempo, and PPQ.
|
|
||||||
pub clock: Arc<TransportTime>,
|
|
||||||
/// Enable metronome?
|
|
||||||
pub metronome: bool,
|
|
||||||
/// Whether the toolbar is focused
|
|
||||||
pub focused: bool,
|
|
||||||
/// Which part of the toolbar is focused
|
|
||||||
pub focus: TransportToolbarFocus,
|
|
||||||
}
|
|
||||||
/// Which item of the transport toolbar is focused
|
|
||||||
#[derive(Clone, Copy, PartialEq)]
|
|
||||||
pub enum TransportToolbarFocus { Bpm, Sync, PlayPause, Clock, Quant, }
|
|
||||||
impl<E: Engine> TransportToolbar<E> {
|
|
||||||
pub fn new (jack: &Arc<RwLock<JackClient>>, clock: Option<&Arc<TransportTime>>) -> Self {
|
|
||||||
Self {
|
|
||||||
_engine: Default::default(),
|
|
||||||
focused: false,
|
|
||||||
focus: TransportToolbarFocus::PlayPause,
|
|
||||||
metronome: false,
|
|
||||||
transport: jack.read().unwrap().transport(),
|
|
||||||
jack: jack.clone(),
|
|
||||||
clock: match clock {
|
|
||||||
Some(clock) => clock.clone(),
|
|
||||||
None => {
|
|
||||||
let timebase = Arc::new(Timebase::default());
|
|
||||||
Arc::new(TransportTime {
|
|
||||||
playing: Some(TransportState::Stopped).into(),
|
|
||||||
quant: 24.into(),
|
|
||||||
sync: (timebase.ppq.get() * 4.).into(),
|
|
||||||
current: Instant::default(),
|
|
||||||
started: None.into(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn toggle_play (&mut self) -> Usually<()> {
|
|
||||||
let playing = self.clock.playing.read().unwrap().expect("1st sample has not been processed yet");
|
|
||||||
let playing = match playing {
|
|
||||||
TransportState::Stopped => {
|
|
||||||
self.transport.start()?;
|
|
||||||
Some(TransportState::Starting)
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
self.transport.stop()?;
|
|
||||||
self.transport.locate(0)?;
|
|
||||||
Some(TransportState::Stopped)
|
|
||||||
},
|
|
||||||
};
|
|
||||||
*self.clock.playing.write().unwrap() = playing;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl TransportToolbarFocus {
|
|
||||||
pub fn next (&mut self) {
|
|
||||||
*self = match self {
|
|
||||||
Self::PlayPause => Self::Bpm,
|
|
||||||
Self::Bpm => Self::Quant,
|
|
||||||
Self::Quant => Self::Sync,
|
|
||||||
Self::Sync => Self::Clock,
|
|
||||||
Self::Clock => Self::PlayPause,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn prev (&mut self) {
|
|
||||||
*self = match self {
|
|
||||||
Self::PlayPause => Self::Clock,
|
|
||||||
Self::Bpm => Self::PlayPause,
|
|
||||||
Self::Quant => Self::Bpm,
|
|
||||||
Self::Sync => Self::Quant,
|
|
||||||
Self::Clock => Self::Sync,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +1,12 @@
|
||||||
[package]
|
[package]
|
||||||
name = "tek_app"
|
name = "tek_cli"
|
||||||
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" }
|
tek_api = { path = "../tek_api" }
|
||||||
|
tek_tui = { path = "../tek_tui" }
|
||||||
livi = "0.7.4"
|
|
||||||
suil-rs = { path = "../suil" }
|
|
||||||
symphonia = { version = "0.5.4", features = [ "all" ] }
|
|
||||||
vst = "0.4.0"
|
|
||||||
#vst3 = "0.1.0"
|
|
||||||
wavers = "1.4.3"
|
|
||||||
winit = { version = "0.30.4", features = [ "x11" ] }
|
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
1
crates/tek_cli/README.md
Normal file
1
crates/tek_cli/README.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
cli entry points
|
||||||
|
|
@ -2,7 +2,7 @@ include!("lib.rs");
|
||||||
/// Application entrypoint.
|
/// Application entrypoint.
|
||||||
pub fn main () -> Usually<()> {
|
pub fn main () -> Usually<()> {
|
||||||
Tui::run(JackClient::new("tek_transport")?.activate_with(|jack|{
|
Tui::run(JackClient::new("tek_transport")?.activate_with(|jack|{
|
||||||
let mut transport = TransportToolbar::new(jack, None);
|
let mut transport = TransportToolbarView::new(jack, None);
|
||||||
transport.focused = true;
|
transport.focused = true;
|
||||||
Ok(transport)
|
Ok(transport)
|
||||||
})?)?;
|
})?)?;
|
||||||
0
crates/tek_cli/src/lib.rs
Normal file
0
crates/tek_cli/src/lib.rs
Normal file
|
|
@ -20,5 +20,4 @@ toml = "0.8.12"
|
||||||
#no_deadlocks = "1.3.2"
|
#no_deadlocks = "1.3.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tek_mixer = { version = "0.1.0", path = "../tek_mixer" }
|
#tek_app = { version = "0.1.0", path = "../tek_app" }
|
||||||
tek_sequencer = { version = "0.1.0", path = "../tek_sequencer" }
|
|
||||||
|
|
|
||||||
8
crates/tek_snd/Cargo.toml
Normal file
8
crates/tek_snd/Cargo.toml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "tek_snd"
|
||||||
|
edition = "2021"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tek_core = { path = "../tek_core" }
|
||||||
|
tek_api = { path = "../tek_api" }
|
||||||
1
crates/tek_snd/README.md
Normal file
1
crates/tek_snd/README.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
jack-based audio engine
|
||||||
10
crates/tek_snd/src/lib.rs
Normal file
10
crates/tek_snd/src/lib.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
pub(crate) use tek_core::*;
|
||||||
|
submod! {
|
||||||
|
snd_arranger
|
||||||
|
snd_mixer
|
||||||
|
snd_plugin
|
||||||
|
snd_sampler
|
||||||
|
snd_sequencer
|
||||||
|
snd_transport
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
impl<E: Engine> Audio for Arranger<E> {
|
|
||||||
|
impl Audio for Arranger {
|
||||||
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
||||||
if let Some(ref transport) = self.transport {
|
if let Some(ref transport) = self.transport {
|
||||||
transport.write().unwrap().process(client, scope);
|
transport.write().unwrap().process(client, scope);
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
impl<E: Engine> Audio for Mixer<E> {
|
|
||||||
|
impl Audio for Mixer {
|
||||||
fn process (&mut self, _: &Client, _: &ProcessScope) -> Control {
|
fn process (&mut self, _: &Client, _: &ProcessScope) -> Control {
|
||||||
Control::Continue
|
Control::Continue
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
impl<E: Engine> Audio for Plugin<E> {
|
|
||||||
|
impl Audio for Plugin {
|
||||||
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
||||||
match self.plugin.as_mut() {
|
match self.plugin.as_mut() {
|
||||||
Some(PluginKind::LV2(LV2Plugin {
|
Some(PluginKind::LV2(LV2Plugin {
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
impl<E: Engine> Audio for Sampler<E> {
|
|
||||||
|
impl Audio for Sampler {
|
||||||
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
||||||
self.process_midi_in(scope);
|
self.process_midi_in(scope);
|
||||||
self.clear_output_buffer();
|
self.clear_output_buffer();
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
/// JACK process callback for sequencer app
|
/// JACK process callback for sequencer app
|
||||||
impl<E: Engine> Audio for Sequencer<E> {
|
impl Audio for Sequencer {
|
||||||
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
||||||
if let Some(ref transport) = self.transport {
|
if let Some(ref transport) = self.transport {
|
||||||
transport.write().unwrap().process(client, scope);
|
transport.write().unwrap().process(client, scope);
|
||||||
|
|
@ -9,6 +10,7 @@ impl<E: Engine> Audio for Sequencer<E> {
|
||||||
Control::Continue
|
Control::Continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// JACK process callback for a sequencer's phrase player/recorder.
|
/// JACK process callback for a sequencer's phrase player/recorder.
|
||||||
impl Audio for PhrasePlayer {
|
impl Audio for PhrasePlayer {
|
||||||
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
||||||
|
|
@ -34,6 +36,7 @@ impl Audio for PhrasePlayer {
|
||||||
Control::Continue
|
Control::Continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Methods used primarily by the process callback
|
/// Methods used primarily by the process callback
|
||||||
impl PhrasePlayer {
|
impl PhrasePlayer {
|
||||||
fn is_rolling (&self) -> bool {
|
fn is_rolling (&self) -> bool {
|
||||||
|
|
@ -199,6 +202,7 @@ impl PhrasePlayer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add "all notes off" to the start of a buffer.
|
/// Add "all notes off" to the start of a buffer.
|
||||||
pub fn all_notes_off (output: &mut PhraseChunk) {
|
pub fn all_notes_off (output: &mut PhraseChunk) {
|
||||||
let mut buf = vec![];
|
let mut buf = vec![];
|
||||||
|
|
@ -207,6 +211,7 @@ pub fn all_notes_off (output: &mut PhraseChunk) {
|
||||||
evt.write(&mut buf).unwrap();
|
evt.write(&mut buf).unwrap();
|
||||||
output[0].push(buf);
|
output[0].push(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return boxed iterator of MIDI events
|
/// Return boxed iterator of MIDI events
|
||||||
pub fn parse_midi_input (input: MidiIter) -> Box<dyn Iterator<Item=(usize, LiveEvent, &[u8])> + '_> {
|
pub fn parse_midi_input (input: MidiIter) -> Box<dyn Iterator<Item=(usize, LiveEvent, &[u8])> + '_> {
|
||||||
Box::new(input.map(|RawMidi { time, bytes }|(
|
Box::new(input.map(|RawMidi { time, bytes }|(
|
||||||
|
|
@ -215,6 +220,7 @@ pub fn parse_midi_input (input: MidiIter) -> Box<dyn Iterator<Item=(usize, LiveE
|
||||||
bytes
|
bytes
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update notes_in array
|
/// Update notes_in array
|
||||||
pub fn update_keys (keys: &mut[bool;128], message: &MidiMessage) {
|
pub fn update_keys (keys: &mut[bool;128], message: &MidiMessage) {
|
||||||
match message {
|
match message {
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
impl<E: Engine> Audio for TransportToolbar<E> {
|
impl Audio for TransportToolbar {
|
||||||
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
|
||||||
let times = scope.cycle_times().unwrap();
|
let times = scope.cycle_times().unwrap();
|
||||||
let CycleTimes { current_frames, current_usecs, next_usecs: _, period_usecs: _ } = times;
|
let CycleTimes { current_frames, current_usecs, next_usecs: _, period_usecs: _ } = times;
|
||||||
16
crates/tek_tui/Cargo.toml
Normal file
16
crates/tek_tui/Cargo.toml
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
[package]
|
||||||
|
name = "tek_tui"
|
||||||
|
edition = "2021"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tek_core = { path = "../tek_core" }
|
||||||
|
tek_api = { path = "../tek_api" }
|
||||||
|
|
||||||
|
livi = "0.7.4"
|
||||||
|
suil-rs = { path = "../suil" }
|
||||||
|
symphonia = { version = "0.5.4", features = [ "all" ] }
|
||||||
|
vst = "0.4.0"
|
||||||
|
#vst3 = "0.1.0"
|
||||||
|
wavers = "1.4.3"
|
||||||
|
winit = { version = "0.30.4", features = [ "x11" ] }
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
/// Root level object for standalone `tek_arranger`
|
/// Root level object for standalone `tek_arranger`
|
||||||
pub struct Arranger<E: Engine> {
|
pub struct ArrangerView<E: Engine> {
|
||||||
/// JACK client handle (needs to not be dropped for standalone mode to work).
|
/// JACK client handle (needs to not be dropped for standalone mode to work).
|
||||||
pub jack: Arc<RwLock<JackClient>>,
|
pub jack: Arc<RwLock<JackClient>>,
|
||||||
/// Which view is focused
|
/// Which view is focused
|
||||||
81
crates/tek_tui/src/transport.rs
Normal file
81
crates/tek_tui/src/transport.rs
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
use crate::*;
|
||||||
|
/// Stores and displays time-related state.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TransportView<E: Engine> {
|
||||||
|
_engine: PhantomData<E>,
|
||||||
|
state: TransportToolbar,
|
||||||
|
focused: bool,
|
||||||
|
focus: TransportFocus,
|
||||||
|
}
|
||||||
|
/// Which item of the transport toolbar is focused
|
||||||
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
|
pub enum TransportFocus {
|
||||||
|
Bpm,
|
||||||
|
Sync,
|
||||||
|
PlayPause,
|
||||||
|
Clock,
|
||||||
|
Quant,
|
||||||
|
}
|
||||||
|
impl<E: Engine> TransportView<E> {
|
||||||
|
pub fn new (jack: &Arc<RwLock<JackClient>>, clock: Option<&Arc<TransportTime>>) -> Self {
|
||||||
|
Self {
|
||||||
|
_engine: Default::default(),
|
||||||
|
focused: false,
|
||||||
|
focus: TransportFocus::PlayPause,
|
||||||
|
state: TransportToolbar {
|
||||||
|
metronome: false,
|
||||||
|
transport: jack.read().unwrap().transport(),
|
||||||
|
jack: jack.clone(),
|
||||||
|
clock: match clock {
|
||||||
|
Some(clock) => clock.clone(),
|
||||||
|
None => {
|
||||||
|
let timebase = Arc::new(Timebase::default());
|
||||||
|
Arc::new(TransportTime {
|
||||||
|
playing: Some(TransportState::Stopped).into(),
|
||||||
|
quant: 24.into(),
|
||||||
|
sync: (timebase.ppq.get() * 4.).into(),
|
||||||
|
current: Instant::default(),
|
||||||
|
started: None.into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn toggle_play (&mut self) -> Usually<()> {
|
||||||
|
let playing = self.clock.playing.read().unwrap().expect("1st sample has not been processed yet");
|
||||||
|
let playing = match playing {
|
||||||
|
TransportState::Stopped => {
|
||||||
|
self.transport.start()?;
|
||||||
|
Some(TransportState::Starting)
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
self.transport.stop()?;
|
||||||
|
self.transport.locate(0)?;
|
||||||
|
Some(TransportState::Stopped)
|
||||||
|
},
|
||||||
|
};
|
||||||
|
*self.clock.playing.write().unwrap() = playing;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl TransportFocus {
|
||||||
|
pub fn next (&mut self) {
|
||||||
|
*self = match self {
|
||||||
|
Self::PlayPause => Self::Bpm,
|
||||||
|
Self::Bpm => Self::Quant,
|
||||||
|
Self::Quant => Self::Sync,
|
||||||
|
Self::Sync => Self::Clock,
|
||||||
|
Self::Clock => Self::PlayPause,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn prev (&mut self) {
|
||||||
|
*self = match self {
|
||||||
|
Self::PlayPause => Self::Clock,
|
||||||
|
Self::Bpm => Self::PlayPause,
|
||||||
|
Self::Quant => Self::Bpm,
|
||||||
|
Self::Sync => Self::Quant,
|
||||||
|
Self::Clock => Self::Sync,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,10 +18,10 @@ pub enum TransportCommand {
|
||||||
SetQuant(f64),
|
SetQuant(f64),
|
||||||
SetSync(f64),
|
SetSync(f64),
|
||||||
}
|
}
|
||||||
impl<E: Engine> Command<TransportToolbar<E>> for TransportCommand {
|
impl<E: Engine> Command<TransportView<E>> for TransportCommand {
|
||||||
fn translate (self, state: &TransportToolbar<E>) -> Self {
|
fn translate (self, state: &TransportView<E>) -> Self {
|
||||||
use TransportCommand::*;
|
use TransportCommand::*;
|
||||||
use TransportToolbarFocus::*;
|
use TransportViewFocus::*;
|
||||||
match self {
|
match self {
|
||||||
Increment => match state.focus {
|
Increment => match state.focus {
|
||||||
Bpm =>
|
Bpm =>
|
||||||
|
|
@ -75,7 +75,7 @@ impl<E: Engine> Command<TransportToolbar<E>> for TransportCommand {
|
||||||
};
|
};
|
||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
fn execute (self, state: &mut TransportToolbar<E>) -> Perhaps<Self> {
|
fn execute (self, state: &mut TransportView<E>) -> Perhaps<Self> {
|
||||||
use TransportCommand::*;
|
use TransportCommand::*;
|
||||||
match self.translate(&state) {
|
match self.translate(&state) {
|
||||||
FocusNext =>
|
FocusNext =>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue