wip: modularize once again

This commit is contained in:
🪞👃🪞 2025-01-08 18:50:15 +01:00
parent e08c9d1790
commit 3b6ff81dad
44 changed files with 984 additions and 913 deletions

34
Cargo.lock generated
View file

@ -1408,17 +1408,15 @@ dependencies = [
name = "tek" name = "tek"
version = "0.2.0" version = "0.2.0"
dependencies = [ dependencies = [
"atomic_float",
"backtrace", "backtrace",
"clap", "clap",
"jack",
"livi", "livi",
"midly",
"once_cell", "once_cell",
"palette", "palette",
"quanta",
"rand", "rand",
"symphonia", "symphonia",
"tek_jack",
"tek_midi",
"tek_tui", "tek_tui",
"toml", "toml",
"uuid", "uuid",
@ -1438,6 +1436,24 @@ dependencies = [
name = "tek_input" name = "tek_input"
version = "0.2.0" version = "0.2.0"
[[package]]
name = "tek_jack"
version = "0.2.0"
dependencies = [
"jack",
]
[[package]]
name = "tek_midi"
version = "0.2.0"
dependencies = [
"jack",
"midly",
"tek_jack",
"tek_time",
"tek_tui",
]
[[package]] [[package]]
name = "tek_output" name = "tek_output"
version = "0.2.0" version = "0.2.0"
@ -1445,6 +1461,16 @@ dependencies = [
"tek_edn", "tek_edn",
] ]
[[package]]
name = "tek_time"
version = "0.2.0"
dependencies = [
"atomic_float",
"quanta",
"tek_jack",
"tek_tui",
]
[[package]] [[package]]
name = "tek_tui" name = "tek_tui"
version = "0.2.0" version = "0.2.0"

View file

@ -4,17 +4,15 @@ edition = "2021"
version = "0.2.0" version = "0.2.0"
[dependencies] [dependencies]
tek_tui = { path = "./tui" } tek_tui = { path = "./tui" }
tek_jack = { path = "./jack" }
tek_midi = { path = "./midi" }
atomic_float = "1.0.0"
backtrace = "0.3.72" backtrace = "0.3.72"
clap = { version = "4.5.4", features = [ "derive" ] } clap = { version = "4.5.4", features = [ "derive" ] }
jack = { path = "./rust-jack" }
livi = "0.7.4" livi = "0.7.4"
midly = "0.5"
once_cell = "1.19.0" once_cell = "1.19.0"
palette = { version = "0.7.6", features = [ "random" ] } palette = { version = "0.7.6", features = [ "random" ] }
quanta = "0.12.3"
rand = "0.8.5" rand = "0.8.5"
symphonia = { version = "0.5.4", features = [ "all" ] } symphonia = { version = "0.5.4", features = [ "all" ] }
toml = "0.8.12" toml = "0.8.12"

View file

@ -130,3 +130,6 @@ test:
cd edn && cargo test && cd .. cd edn && cargo test && cd ..
cd tui && cargo test && cd .. cd tui && cargo test && cd ..
cargo test cargo test
cloc:
for src in {edn,input,jack,midi,output,time,tui,.}; do echo $src; cloc $src/src; done

View file

@ -1,12 +1,5 @@
#[allow(unused_imports)] use std::sync::Arc; use tek::*;
#[allow(unused_imports)] use clap::{self, Parser, Subcommand, ValueEnum}; #[allow(unused_imports)] use clap::{self, Parser, Subcommand, ValueEnum};
#[allow(unused_imports)] use tek::{
*,
jack::*,
tek_input::*,
tek_output::*,
tek_tui::{*, ratatui::prelude::Color}
};
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
#[command(version, about, long_about = None)] #[command(version, about, long_about = None)]
@ -105,73 +98,131 @@ pub fn main () -> Usually<()> {
let jack = JackConnection::new(name)?; let jack = JackConnection::new(name)?;
let engine = Tui::new()?; let engine = Tui::new()?;
Ok(match cli.mode { Ok(match cli.mode {
Clock =>
engine.run(&jack.activate_with(|jack|Ok( Clock => engine.run(&jack.activate_with(|jack|Ok(crate::TransportTui {
crate::TransportTui::new(jack)? clock: crate::Clock::from(jack),
))?)?, jack: jack.clone()
Sequencer { midi_from, midi_to, .. } => }))?)?,
engine.run(&jack.activate_with(|jack|Ok({
let mut app = crate::Sequencer::new(jack)?; Sequencer {
let jack = jack.read().unwrap(); midi_from,
let midi_in = jack.register_port("i", MidiIn::default())?; midi_to, ..
let midi_out = jack.register_port("o", MidiOut::default())?; } => engine.run(&jack.activate_with(|jack|Ok({
connect_from(&jack, &midi_in, &midi_from)?; let clock = crate::Clock::from(jack);
connect_to(&jack, &midi_out, &midi_to)?; let phrase = Arc::new(RwLock::new(crate::MidiClip::new(
app.player.midi_ins.push(midi_in); "Clip", true, 4 * clock.timebase.ppq.get() as usize,
app.player.midi_outs.push(midi_out); None, Some(ItemColor::random().into())
app )));
}))?)?, let midi_in = jack.read().unwrap().register_port("i", MidiIn::default())?;
Sampler { midi_from, l_from, r_from, l_to, r_to, .. } => connect_from(&jack, &midi_in, &midi_from)?;
engine.run(&jack.activate_with(|jack|Ok( let midi_out = jack.read().unwrap().register_port("o", MidiOut::default())?;
tek::SamplerTui { connect_to(&jack, &midi_out, &midi_to)?;
cursor: (0, 0), crate::Sequencer {
editing: None, _jack: jack.clone(),
mode: None, pool: PoolModel::from(&phrase),
size: Measure::new(), editor: crate::MidiEditor::from(&phrase),
note_lo: 36.into(), player: crate::MidiPlayer::new(&clock, &phrase, &[midi_in], &[midi_out])?,
note_pt: 36.into(), compact: true,
color: ItemPalette::from(Color::Rgb(64, 128, 32)), transport: true,
state: tek::Sampler::new( selectors: true,
jack, &"sampler", size: Measure::new(),
&midi_from, midi_buf: vec![vec![];65536],
&[&l_from, &r_from], note_buf: vec![],
&[&l_to, &r_to], perf: PerfModel::default(),
)?, status: true,
} clock,
))?)?, }
Groovebox { midi_from, midi_to, l_from, r_from, l_to, r_to, .. } => }))?)?,
engine.run(&jack.activate_with(|jack|Ok({
let app = tek::Groovebox::new( Sampler {
jack, midi_from, l_from, r_from, l_to, r_to, ..
&midi_from, &midi_to, } => engine.run(&jack.activate_with(|jack|Ok(
tek::SamplerTui {
cursor: (0, 0),
editing: None,
mode: None,
size: Measure::new(),
note_lo: 36.into(),
note_pt: 36.into(),
color: ItemPalette::from(Color::Rgb(64, 128, 32)),
state: tek::Sampler::new(jack, &"sampler",
&midi_from,
&[&l_from, &r_from], &[&l_from, &r_from],
&[&l_to, &r_to], &[&l_to, &r_to],
)?; )?,
if let Some(bpm) = cli.bpm { }
app.clock().timebase.bpm.set(bpm); ))?)?,
}
if cli.sync_lead { Groovebox {
jack.read().unwrap().client().register_timebase_callback(false, |mut state|{ midi_from, midi_to, l_from, r_from, l_to, r_to, ..
app.clock().playhead.update_from_sample(state.position.frame() as f64); } => engine.run(&jack.activate_with(|jack|Ok({
state.position.bbt = Some(app.clock().bbt()); let phrase = Arc::new(RwLock::new(MidiClip::new(
state.position "Clip", true, 4 * player.clock.timebase.ppq.get() as usize,
})? None, Some(ItemColor::random().into())
} else if cli.sync_follow { )));
jack.read().unwrap().client().register_timebase_callback(false, |state|{ let mut player = crate::midi::MidiPlayer::new(jack, &"sequencer", Some(&phrase),
app.clock().playhead.update_from_sample(state.position.frame() as f64); &midi_from,
state.position &midi_to
})? )?;
} player.play_phrase = Some((Moment::zero(&player.clock.timebase), Some(phrase.clone())));
app let sampler = crate::sampler::Sampler::new(jack, &"sampler",
}))?)?, midi_from,
Arranger { scenes, tracks, track_width, midi_from, midi_to, .. } => &[l_from, r_from],
engine.run(&jack.activate_with(|jack|Ok({ &[l_to, r_to ],
let mut app = crate::Arranger::try_from(jack)?; )?;
app.color = ItemPalette::random(); jack.read().unwrap().client().connect_ports(
app.tracks_add(tracks, track_width, midi_from.as_slice(), midi_to.as_slice())?; &player.midi_outs[0],
app.scenes_add(scenes)?; &sampler.midi_in
app )?;
}))?)?, let app = tek::Groovebox {
player,
sampler,
_jack: jack.clone(),
pool: PoolModel::from(&phrase),
editor: MidiEditor::from(&phrase),
compact: true,
status: true,
size: Measure::new(),
midi_buf: vec![vec![];65536],
note_buf: vec![],
perf: PerfModel::default(),
};
let app = tek::Groovebox::new(
jack,
&midi_from, &midi_to,
&[&l_from, &r_from],
&[&l_to, &r_to],
)?;
if let Some(bpm) = cli.bpm {
app.clock().timebase.bpm.set(bpm);
}
if cli.sync_lead {
jack.read().unwrap().client().register_timebase_callback(false, |mut state|{
app.clock().playhead.update_from_sample(state.position.frame() as f64);
state.position.bbt = Some(app.clock().bbt());
state.position
})?
} else if cli.sync_follow {
jack.read().unwrap().client().register_timebase_callback(false, |state|{
app.clock().playhead.update_from_sample(state.position.frame() as f64);
state.position
})?
}
app
}))?)?,
Arranger {
scenes, tracks, track_width, midi_from, midi_to, ..
} => engine.run(&jack.activate_with(|jack|Ok({
let mut app = crate::Arranger::new(jack);
app.tracks_add(tracks, track_width, midi_from.as_slice(), midi_to.as_slice())?;
app.scenes_add(scenes)?;
app
}))?)?,
_ => todo!() _ => todo!()
}) })
} }

7
jack/Cargo.toml Normal file
View file

@ -0,0 +1,7 @@
[package]
name = "tek_jack"
edition = "2021"
version = "0.2.0"
[dependencies]
jack = { path = "../rust-jack" }

13
jack/src/from_jack.rs Normal file
View file

@ -0,0 +1,13 @@
use crate::*;
/// Implement [TryFrom<&Arc<RwLock<JackConnection>>>]: create app state from wrapped JACK handle.
#[macro_export] macro_rules! from_jack {
(|$jack:ident|$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)? $cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? TryFrom<&Arc<RwLock<JackConnection>>> for $Struct $(<$($L),*$($T),*>)? {
type Error = Box<dyn std::error::Error>;
fn try_from ($jack: &Arc<RwLock<JackConnection>>) -> Usually<Self> {
Ok($cb)
}
}
};
}

26
jack/src/jack_audio.rs Normal file
View file

@ -0,0 +1,26 @@
use crate::*;
/// Implement [Audio]: provide JACK callbacks.
#[macro_export] macro_rules! audio {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?,$c:ident,$s:ident|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? Audio for $Struct $(<$($L),*$($T),*>)? {
#[inline] fn process (&mut $self, $c: &Client, $s: &ProcessScope) -> Control { $cb }
}
}
}
/// Trait for thing that has a JACK process callback.
pub trait Audio: Send + Sync {
fn process (&mut self, _: &Client, _: &ProcessScope) -> Control {
Control::Continue
}
fn callback (
state: &Arc<RwLock<Self>>, client: &Client, scope: &ProcessScope
) -> Control where Self: Sized {
if let Ok(mut state) = state.write() {
state.process(client, scope)
} else {
Control::Quit
}
}
}

132
jack/src/jack_connection.rs Normal file
View file

@ -0,0 +1,132 @@
use crate::*;
/// This is a boxed realtime callback.
pub type BoxedAudioHandler = Box<dyn FnMut(&Client, &ProcessScope) -> Control + Send>;
/// This is the notification handler wrapper for a boxed realtime callback.
pub type DynamicAudioHandler = ClosureProcessHandler<(), BoxedAudioHandler>;
/// This is a boxed [JackEvent] callback.
pub type BoxedJackEventHandler = Box<dyn Fn(JackEvent) + Send + Sync>;
/// This is the notification handler wrapper for a boxed [JackEvent] callback.
pub type DynamicNotifications = Notifications<BoxedJackEventHandler>;
/// This is a running JACK [AsyncClient] with maximum type erasure.
/// It has one [Box] containing a function that handles [JackEvent]s,
/// and another [Box] containing a function that handles realtime IO,
/// and that's all it knows about them.
pub type DynamicAsyncClient = AsyncClient<DynamicNotifications, DynamicAudioHandler>;
/// This is a connection which may be `Inactive`, `Activating`, or `Active`.
/// In the `Active` and `Inactive` states, its `client` method returns a
/// [Client] which you can use to talk to the JACK API.
#[derive(Debug)]
pub enum JackConnection {
/// Before activation.
Inactive(Client),
/// During activation.
Activating,
/// After activation. Must not be dropped for JACK thread to persist.
Active(DynamicAsyncClient),
}
impl From<JackConnection> for Client {
fn from (jack: JackConnection) -> Self {
match jack {
JackConnection::Inactive(client) => client,
JackConnection::Activating => panic!("jack client still activating"),
JackConnection::Active(_) => panic!("jack client already activated"),
}
}
}
impl JackConnection {
pub fn new (name: &str) -> Usually<Self> {
let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?;
Ok(Self::Inactive(client))
}
/// Return the internal [Client] handle that lets you call the JACK API.
pub fn client (&self) -> &Client {
match self {
Self::Inactive(ref client) => client,
Self::Activating => panic!("jack client has not finished activation"),
Self::Active(ref client) => client.as_client(),
}
}
/// Activate a connection with an application.
///
/// Consume a `JackConnection::Inactive`,
/// binding a process callback and
/// returning a `JackConnection::Active`.
///
/// Needs work. Strange ownership situation between the callback
/// and the host object.
fn activate (
self,
mut cb: impl FnMut(&Arc<RwLock<Self>>, &Client, &ProcessScope) -> Control + Send + 'static,
) -> Usually<Arc<RwLock<Self>>>
where
Self: Send + Sync + 'static
{
let client = Client::from(self);
let state = Arc::new(RwLock::new(Self::Activating));
let event = Box::new(move|_|{/*TODO*/}) as Box<dyn Fn(JackEvent) + Send + Sync>;
let events = Notifications(event);
let frame = Box::new({let state = state.clone(); move|c: &_, s: &_|cb(&state, c, s)});
let frames = ClosureProcessHandler::new(frame as BoxedAudioHandler);
*state.write().unwrap() = Self::Active(client.activate_async(events, frames)?);
Ok(state)
}
/// Activate a connection with an application.
///
/// * Wrap a [JackConnection::Inactive] into [Arc<RwLock<_>>].
/// * Pass it to the `init` callback
/// * This allows user code to connect to JACK
/// * While user code retains clone of the
/// [Arc<RwLock<JackConnection>>] that is
/// passed to `init`, the audio engine is running.
pub fn activate_with <T: Audio + 'static> (
self,
init: impl FnOnce(&Arc<RwLock<JackConnection>>)->Usually<T>
)
-> Usually<Arc<RwLock<T>>>
{
// Wrap self for multiple ownership.
let connection = Arc::new(RwLock::new(self));
// Run init callback. Return value is target. Target must retain clone of `connection`.
let target = Arc::new(RwLock::new(init(&connection)?));
// Swap the `client` from the `JackConnection::Inactive`
// for a `JackConnection::Activating`.
let mut client = Self::Activating;
std::mem::swap(&mut*connection.write().unwrap(), &mut client);
// Replace the `JackConnection::Activating` with a
// `JackConnection::Active` wrapping the [AsyncClient]
// returned by the activation.
*connection.write().unwrap() = Self::Active(Client::from(client).activate_async(
// This is the misc notifications handler. It's a struct that wraps a [Box]
// which performs type erasure on a callback that takes [JackEvent], which is
// one of the available misc notifications.
Notifications(Box::new(move|_|{/*TODO*/}) as BoxedJackEventHandler),
// This is the main processing handler. It's a struct that wraps a [Box]
// which performs type erasure on a callback that takes [Client] and [ProcessScope]
// and passes them down to the `target`'s `process` callback, which in turn
// implements audio and MIDI input and output on a realtime basis.
ClosureProcessHandler::new(Box::new({
let target = target.clone();
move|c: &_, s: &_|if let Ok(mut target) = target.write() {
target.process(c, s)
} else {
Control::Quit
}
}) as BoxedAudioHandler),
)?);
Ok(target)
}
pub fn port_by_name (&self, name: &str) -> Option<Port<Unowned>> {
self.client().port_by_name(name)
}
pub fn register_port <PS: PortSpec> (&self, name: &str, spec: PS) -> Usually<Port<PS>> {
Ok(self.client().register_port(name, spec)?)
}
}

65
jack/src/jack_event.rs Normal file
View file

@ -0,0 +1,65 @@
use crate::*;
#[derive(Debug, Clone, PartialEq)]
/// Event enum for JACK events.
pub enum JackEvent {
ThreadInit,
Shutdown(ClientStatus, Arc<str>),
Freewheel(bool),
SampleRate(Frames),
ClientRegistration(Arc<str>, bool),
PortRegistration(PortId, bool),
PortRename(PortId, Arc<str>, Arc<str>),
PortsConnected(PortId, PortId, bool),
GraphReorder,
XRun,
}
/// Generic notification handler that emits [JackEvent]
pub struct Notifications<T: Fn(JackEvent) + Send>(pub T);
impl<T: Fn(JackEvent) + Send> NotificationHandler for Notifications<T> {
fn thread_init(&self, _: &Client) {
self.0(JackEvent::ThreadInit);
}
unsafe fn shutdown(&mut self, status: ClientStatus, reason: &str) {
self.0(JackEvent::Shutdown(status, reason.into()));
}
fn freewheel(&mut self, _: &Client, enabled: bool) {
self.0(JackEvent::Freewheel(enabled));
}
fn sample_rate(&mut self, _: &Client, frames: Frames) -> Control {
self.0(JackEvent::SampleRate(frames));
Control::Quit
}
fn client_registration(&mut self, _: &Client, name: &str, reg: bool) {
self.0(JackEvent::ClientRegistration(name.into(), reg));
}
fn port_registration(&mut self, _: &Client, id: PortId, reg: bool) {
self.0(JackEvent::PortRegistration(id, reg));
}
fn port_rename(&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control {
self.0(JackEvent::PortRename(id, old.into(), new.into()));
Control::Continue
}
fn ports_connected(&mut self, _: &Client, a: PortId, b: PortId, are: bool) {
self.0(JackEvent::PortsConnected(a, b, are));
}
fn graph_reorder(&mut self, _: &Client) -> Control {
self.0(JackEvent::GraphReorder);
Control::Continue
}
fn xrun(&mut self, _: &Client) -> Control {
self.0(JackEvent::XRun);
Control::Continue
}
}

185
jack/src/jack_port.rs Normal file
View file

@ -0,0 +1,185 @@
use crate::*;
/// This is a utility trait for things that may register or connect [Port]s.
/// It contains shorthand methods to this purpose. It's implemented for
/// `Arc<RwLock<JackConnection>>` for terse port registration in the
/// `init` callback of [JackClient::activate_with].
pub trait RegisterPort {
fn midi_in (&self, name: impl AsRef<str>, connect: &[impl AsRef<str>]) -> Usually<Port<MidiIn>>;
fn midi_out (&self, name: impl AsRef<str>, connect: &[impl AsRef<str>]) -> Usually<Port<MidiOut>>;
fn audio_in (&self, name: impl AsRef<str>, connect: &[impl AsRef<str>]) -> Usually<Port<AudioIn>>;
fn audio_out (&self, name: impl AsRef<str>, connect: &[impl AsRef<str>]) -> Usually<Port<AudioOut>>;
}
impl RegisterPort for Arc<RwLock<JackConnection>> {
fn midi_in (&self, name: impl AsRef<str>, connect: &[impl AsRef<str>]) -> Usually<Port<MidiIn>> {
let jack = self.read().unwrap();
let input = jack.client().register_port(name.as_ref(), MidiIn::default())?;
for port in connect.iter() {
let port = port.as_ref();
if let Some(output) = jack.port_by_name(port).as_ref() {
jack.client().connect_ports(output, &input)?;
} else {
panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names.");
}
}
Ok(input)
}
fn midi_out (&self, name: impl AsRef<str>, connect: &[impl AsRef<str>]) -> Usually<Port<MidiOut>> {
let jack = self.read().unwrap();
let output = jack.client().register_port(name.as_ref(), MidiOut::default())?;
for port in connect.iter() {
let port = port.as_ref();
if let Some(input) = jack.port_by_name(port).as_ref() {
jack.client().connect_ports(&output, input)?;
} else {
panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names.");
}
}
Ok(output)
}
fn audio_in (&self, name: impl AsRef<str>, connect: &[impl AsRef<str>]) -> Usually<Port<AudioIn>> {
let jack = self.read().unwrap();
let input = jack.client().register_port(name.as_ref(), AudioIn::default())?;
for port in connect.iter() {
let port = port.as_ref();
if let Some(output) = jack.port_by_name(port).as_ref() {
jack.client().connect_ports(output, &input)?;
} else {
panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names.");
}
}
Ok(input)
}
fn audio_out (&self, name: impl AsRef<str>, connect: &[impl AsRef<str>]) -> Usually<Port<AudioOut>> {
let jack = self.read().unwrap();
let output = jack.client().register_port(name.as_ref(), AudioOut::default())?;
for port in connect.iter() {
let port = port.as_ref();
if let Some(input) = jack.port_by_name(port).as_ref() {
jack.client().connect_ports(&output, input)?;
} else {
panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names.");
}
}
Ok(output)
}
}
///// Collection of JACK ports as [AudioIn]/[AudioOut]/[MidiIn]/[MidiOut].
//#[derive(Default, Debug)]
//pub struct JackPorts {
//pub audio_ins: BTreeMap<String, Port<AudioIn>>,
//pub midi_ins: BTreeMap<String, Port<MidiIn>>,
//pub audio_outs: BTreeMap<String, Port<AudioOut>>,
//pub midi_outs: BTreeMap<String, Port<MidiOut>>,
//}
///// Collection of JACK ports as [Unowned].
//#[derive(Default, Debug)]
//pub struct UnownedJackPorts {
//pub audio_ins: BTreeMap<String, Port<Unowned>>,
//pub midi_ins: BTreeMap<String, Port<Unowned>>,
//pub audio_outs: BTreeMap<String, Port<Unowned>>,
//pub midi_outs: BTreeMap<String, Port<Unowned>>,
//}
//impl JackPorts {
//pub fn clone_unowned(&self) -> UnownedJackPorts {
//let mut unowned = UnownedJackPorts::default();
//for (name, port) in self.midi_ins.iter() {
//unowned.midi_ins.insert(name.clone(), port.clone_unowned());
//}
//for (name, port) in self.midi_outs.iter() {
//unowned.midi_outs.insert(name.clone(), port.clone_unowned());
//}
//for (name, port) in self.audio_ins.iter() {
//unowned.audio_ins.insert(name.clone(), port.clone_unowned());
//}
//for (name, port) in self.audio_outs.iter() {
//unowned
//.audio_outs
//.insert(name.clone(), port.clone_unowned());
//}
//unowned
//}
//}
///// Implement the `Ports` trait.
//#[macro_export]
//macro_rules! ports {
//($T:ty $({ $(audio: {
//$(ins: |$ai_arg:ident|$ai_impl:expr,)?
//$(outs: |$ao_arg:ident|$ao_impl:expr,)?
//})? $(midi: {
//$(ins: |$mi_arg:ident|$mi_impl:expr,)?
//$(outs: |$mo_arg:ident|$mo_impl:expr,)?
//})?})?) => {
//impl Ports for $T {$(
//$(
//$(fn audio_ins <'a> (&'a self) -> Usually<Vec<&'a Port<Unowned>>> {
//let cb = |$ai_arg:&'a Self|$ai_impl;
//cb(self)
//})?
//)?
//$(
//$(fn audio_outs <'a> (&'a self) -> Usually<Vec<&'a Port<Unowned>>> {
//let cb = (|$ao_arg:&'a Self|$ao_impl);
//cb(self)
//})?
//)?
//)? $(
//$(
//$(fn midi_ins <'a> (&'a self) -> Usually<Vec<&'a Port<Unowned>>> {
//let cb = (|$mi_arg:&'a Self|$mi_impl);
//cb(self)
//})?
//)?
//$(
//$(fn midi_outs <'a> (&'a self) -> Usually<Vec<&'a Port<Unowned>>> {
//let cb = (|$mo_arg:&'a Self|$mo_impl);
//cb(self)
//})?
//)?
//)?}
//};
//}
/// Trait for things that may expose JACK ports.
pub trait Ports {
fn audio_ins(&self) -> Usually<Vec<&Port<Unowned>>> {
Ok(vec![])
}
fn audio_outs(&self) -> Usually<Vec<&Port<Unowned>>> {
Ok(vec![])
}
fn midi_ins(&self) -> Usually<Vec<&Port<Unowned>>> {
Ok(vec![])
}
fn midi_outs(&self) -> Usually<Vec<&Port<Unowned>>> {
Ok(vec![])
}
}
fn register_ports<T: PortSpec + Copy>(
client: &Client,
names: Vec<String>,
spec: T,
) -> Usually<BTreeMap<String, Port<T>>> {
names
.into_iter()
.try_fold(BTreeMap::new(), |mut ports, name| {
let port = client.register_port(&name, spec)?;
ports.insert(name, port);
Ok(ports)
})
}
fn query_ports(client: &Client, names: Vec<String>) -> BTreeMap<String, Port<Unowned>> {
names.into_iter().fold(BTreeMap::new(), |mut ports, name| {
let port = client.port_by_name(&name).unwrap();
ports.insert(name, port);
ports
})
}

222
jack/src/lib.rs Normal file
View file

@ -0,0 +1,222 @@
pub(crate) use std::sync::{Arc, RwLock};
pub(crate) use std::collections::BTreeMap;
pub use ::jack;
pub(crate) use ::jack::{
contrib::ClosureProcessHandler, NotificationHandler,
Client, AsyncClient, ClientOptions, ClientStatus,
ProcessScope, Control, CycleTimes, Frames,
Port, PortId, PortSpec, Unowned, MidiIn, MidiOut, AudioIn, AudioOut,
Transport, TransportState, MidiIter, MidiWriter, RawMidi,
};
mod from_jack; pub use self::from_jack::*;
mod jack_audio; pub use self::jack_audio::*;
mod jack_connection; pub use self::jack_connection::*;
mod jack_event; pub use self::jack_event::*;
mod jack_port; pub use self::jack_port::*;
pub(crate) type Usually<T> = Result<T, Box<dyn std::error::Error>>;
////////////////////////////////////////////////////////////////////////////////////
///// A [AudioComponent] bound to a JACK client and a set of ports.
//pub struct JackDevice<E: Engine> {
///// The active JACK client of this device.
//pub client: DynamicAsyncClient,
///// The device state, encapsulated for sharing between threads.
//pub state: Arc<RwLock<Box<dyn AudioComponent<E>>>>,
///// Unowned copies of the device's JACK ports, for connecting to the device.
///// The "real" readable/writable `Port`s are owned by the `state`.
//pub ports: UnownedJackPorts,
//}
//impl<E: Engine> std::fmt::Debug for JackDevice<E> {
//fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
//f.debug_struct("JackDevice")
//.field("ports", &self.ports)
//.finish()
//}
//}
//impl<E: Engine> Render for JackDevice<E> {
//type Engine = E;
//fn min_size(&self, to: E::Size) -> Perhaps<E::Size> {
//self.state.read().unwrap().layout(to)
//}
//fn render(&self, to: &mut E::Output) -> Usually<()> {
//self.state.read().unwrap().render(to)
//}
//}
//impl<E: Engine> Handle<E> for JackDevice<E> {
//fn handle(&mut self, from: &E::Input) -> Perhaps<E::Handled> {
//self.state.write().unwrap().handle(from)
//}
//}
//impl<E: Engine> Ports for JackDevice<E> {
//fn audio_ins(&self) -> Usually<Vec<&Port<Unowned>>> {
//Ok(self.ports.audio_ins.values().collect())
//}
//fn audio_outs(&self) -> Usually<Vec<&Port<Unowned>>> {
//Ok(self.ports.audio_outs.values().collect())
//}
//fn midi_ins(&self) -> Usually<Vec<&Port<Unowned>>> {
//Ok(self.ports.midi_ins.values().collect())
//}
//fn midi_outs(&self) -> Usually<Vec<&Port<Unowned>>> {
//Ok(self.ports.midi_outs.values().collect())
//}
//}
//impl<E: Engine> JackDevice<E> {
///// Returns a locked mutex of the state's contents.
//pub fn state(&self) -> LockResult<RwLockReadGuard<Box<dyn AudioComponent<E>>>> {
//self.state.read()
//}
///// Returns a locked mutex of the state's contents.
//pub fn state_mut(&self) -> LockResult<RwLockWriteGuard<Box<dyn AudioComponent<E>>>> {
//self.state.write()
//}
//pub fn connect_midi_in(&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
//Ok(self
//.client
//.as_client()
//.connect_ports(port, self.midi_ins()?[index])?)
//}
//pub fn connect_midi_out(&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
//Ok(self
//.client
//.as_client()
//.connect_ports(self.midi_outs()?[index], port)?)
//}
//pub fn connect_audio_in(&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
//Ok(self
//.client
//.as_client()
//.connect_ports(port, self.audio_ins()?[index])?)
//}
//pub fn connect_audio_out(&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
//Ok(self
//.client
//.as_client()
//.connect_ports(self.audio_outs()?[index], port)?)
//}
//}
///// `JackDevice` factory. Creates JACK `Client`s, performs port registration
///// and activation, and encapsulates a `AudioComponent` into a `JackDevice`.
//pub struct Jack {
//pub client: Client,
//pub midi_ins: Vec<String>,
//pub audio_ins: Vec<String>,
//pub midi_outs: Vec<String>,
//pub audio_outs: Vec<String>,
//}
//impl Jack {
//pub fn new(name: &str) -> Usually<Self> {
//Ok(Self {
//midi_ins: vec![],
//audio_ins: vec![],
//midi_outs: vec![],
//audio_outs: vec![],
//client: Client::new(name, ClientOptions::NO_START_SERVER)?.0,
//})
//}
//pub fn run<'a: 'static, D, E>(
//self,
//state: impl FnOnce(JackPorts) -> Box<D>,
//) -> Usually<JackDevice<E>>
//where
//D: AudioComponent<E> + Sized + 'static,
//E: Engine + 'static,
//{
//let owned_ports = JackPorts {
//audio_ins: register_ports(&self.client, self.audio_ins, AudioIn::default())?,
//audio_outs: register_ports(&self.client, self.audio_outs, AudioOut::default())?,
//midi_ins: register_ports(&self.client, self.midi_ins, MidiIn::default())?,
//midi_outs: register_ports(&self.client, self.midi_outs, MidiOut::default())?,
//};
//let midi_outs = owned_ports
//.midi_outs
//.values()
//.map(|p| Ok(p.name()?))
//.collect::<Usually<Vec<_>>>()?;
//let midi_ins = owned_ports
//.midi_ins
//.values()
//.map(|p| Ok(p.name()?))
//.collect::<Usually<Vec<_>>>()?;
//let audio_outs = owned_ports
//.audio_outs
//.values()
//.map(|p| Ok(p.name()?))
//.collect::<Usually<Vec<_>>>()?;
//let audio_ins = owned_ports
//.audio_ins
//.values()
//.map(|p| Ok(p.name()?))
//.collect::<Usually<Vec<_>>>()?;
//let state = Arc::new(RwLock::new(state(owned_ports) as Box<dyn AudioComponent<E>>));
//let client = self.client.activate_async(
//Notifications(Box::new({
//let _state = state.clone();
//move |_event| {
//// FIXME: this deadlocks
////state.lock().unwrap().handle(&event).unwrap();
//}
//}) as Box<dyn Fn(JackEvent) + Send + Sync>),
//ClosureProcessHandler::new(Box::new({
//let state = state.clone();
//move |c: &Client, s: &ProcessScope| state.write().unwrap().process(c, s)
//}) as BoxedAudioHandler),
//)?;
//Ok(JackDevice {
//ports: UnownedJackPorts {
//audio_ins: query_ports(&client.as_client(), audio_ins),
//audio_outs: query_ports(&client.as_client(), audio_outs),
//midi_ins: query_ports(&client.as_client(), midi_ins),
//midi_outs: query_ports(&client.as_client(), midi_outs),
//},
//client,
//state,
//})
//}
//pub fn audio_in(mut self, name: &str) -> Self {
//self.audio_ins.push(name.to_string());
//self
//}
//pub fn audio_out(mut self, name: &str) -> Self {
//self.audio_outs.push(name.to_string());
//self
//}
//pub fn midi_in(mut self, name: &str) -> Self {
//self.midi_ins.push(name.to_string());
//self
//}
//pub fn midi_out(mut self, name: &str) -> Self {
//self.midi_outs.push(name.to_string());
//self
//}
//}
///// A UI component that may be associated with a JACK client by the `Jack` factory.
//pub trait AudioComponent<E: Engine>: Component<E> + Audio {
///// Perform type erasure for collecting heterogeneous devices.
//fn boxed(self) -> Box<dyn AudioComponent<E>>
//where
//Self: Sized + 'static,
//{
//Box::new(self)
//}
//}
///// All things that implement the required traits can be treated as `AudioComponent`.
//impl<E: Engine, W: Component<E> + Audio> AudioComponent<E> for W {}
/////////
/*
*/

12
midi/Cargo.toml Normal file
View file

@ -0,0 +1,12 @@
[package]
name = "tek_midi"
edition = "2021"
version = "0.2.0"
[dependencies]
tek_tui = { path = "../tui" }
tek_jack = { path = "../jack" }
tek_time = { path = "../time" }
jack = { path = "../rust-jack" }
midly = "0.5"

View file

@ -1,17 +1,18 @@
use crate::*; pub(crate) use ::tek_tui::{*, tek_input::*, tek_output::*, crossterm::event::KeyCode};
pub(crate) use ::tek_jack::*;
pub(crate) mod midi_pool; pub(crate) use midi_pool::*; mod midi_pool; pub(crate) use midi_pool::*;
pub(crate) mod midi_clip; pub(crate) use midi_clip::*; mod midi_clip; pub(crate) use midi_clip::*;
pub(crate) mod midi_launch; pub(crate) use midi_launch::*; mod midi_launch; pub(crate) use midi_launch::*;
pub(crate) mod midi_player; pub(crate) use midi_player::*; mod midi_player; pub(crate) use midi_player::*;
pub(crate) mod midi_in; pub(crate) use midi_in::*; mod midi_in; pub(crate) use midi_in::*;
pub(crate) mod midi_out; pub(crate) use midi_out::*; mod midi_out; pub(crate) use midi_out::*;
pub(crate) mod midi_note; pub(crate) use midi_note::*; mod midi_note; pub(crate) use midi_note::*;
pub(crate) mod midi_range; pub(crate) use midi_range::*; mod midi_range; pub(crate) use midi_range::*;
pub(crate) mod midi_point; pub(crate) use midi_point::*; mod midi_point; pub(crate) use midi_point::*;
pub(crate) mod midi_view; pub(crate) use midi_view::*; mod midi_view; pub(crate) use midi_view::*;
pub(crate) mod midi_editor; pub(crate) use midi_editor::*; mod midi_editor; pub(crate) use midi_editor::*;
/// 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 [Vec<Vec<u8>>]) { pub fn all_notes_off (output: &mut [Vec<Vec<u8>>]) {
@ -39,3 +40,4 @@ pub fn update_keys (keys: &mut[bool;128], message: &MidiMessage) {
_ => {} _ => {}
} }
} }

View file

@ -27,7 +27,35 @@ pub struct Arranger {
pub perf: PerfModel, pub perf: PerfModel,
pub compact: bool, pub compact: bool,
} }
has_clock!(|self: Arranger|&self.clock);
has_phrases!(|self: Arranger|self.pool.phrases);
has_editor!(|self: Arranger|self.editor);
handle!(TuiIn: |self: Arranger, input|ArrangerCommand::execute_with_state(self, input.event()));
impl Arranger { impl Arranger {
pub fn new (jack: &Arc<RwLock<JackConnection>>) -> Self {
let clock = Clock::from(jack);
let phrase = Arc::new(RwLock::new(MidiClip::new(
"Clip", true, 4 * clock.timebase.ppq.get() as usize,
None, Some(ItemColor::random().into())
)));
Self {
clock,
pool: (&phrase).into(),
editor: (&phrase).into(),
selected: ArrangerSelection::Clip(0, 0),
scenes: vec![],
tracks: vec![],
color: ItemPalette::random(),
mode: ArrangerMode::V(1),
size: Measure::new(),
splits: [12, 20],
midi_buf: vec![vec![];65536],
note_buf: vec![],
perf: PerfModel::default(),
jack: jack.clone(),
compact: true,
}
}
pub fn selected (&self) -> ArrangerSelection { pub fn selected (&self) -> ArrangerSelection {
self.selected self.selected
} }
@ -70,31 +98,3 @@ impl Arranger {
} }
} }
} }
from_jack!(|jack| Arranger {
let clock = Clock::from(jack);
let phrase = Arc::new(RwLock::new(MidiClip::new(
"Clip", true, 4 * clock.timebase.ppq.get() as usize,
None, Some(ItemColor::random().into())
)));
Self {
clock,
pool: (&phrase).into(),
editor: (&phrase).into(),
selected: ArrangerSelection::Clip(0, 0),
scenes: vec![],
tracks: vec![],
color: TuiTheme::bg().into(),
mode: ArrangerMode::V(1),
size: Measure::new(),
splits: [12, 20],
midi_buf: vec![vec![];65536],
note_buf: vec![],
perf: PerfModel::default(),
jack: jack.clone(),
compact: true,
}
});
has_clock!(|self: Arranger|&self.clock);
has_phrases!(|self: Arranger|self.pool.phrases);
has_editor!(|self: Arranger|self.editor);
handle!(TuiIn: |self: Arranger, input|ArrangerCommand::execute_with_state(self, input.event()));

View file

@ -1,7 +1,6 @@
mod groovebox_audio; pub use self::groovebox_audio::*; mod groovebox_audio; pub use self::groovebox_audio::*;
mod groovebox_command; pub use self::groovebox_command::*; mod groovebox_command; pub use self::groovebox_command::*;
mod groovebox_tui; pub use self::groovebox_tui::*; mod groovebox_tui; pub use self::groovebox_tui::*;
mod groovebox_edn; pub use self::groovebox_edn::*;
use crate::*; use crate::*;
use super::*; use super::*;
@ -25,38 +24,48 @@ pub struct Groovebox {
pub midi_buf: Vec<Vec<Vec<u8>>>, pub midi_buf: Vec<Vec<Vec<u8>>>,
pub perf: PerfModel, pub perf: PerfModel,
} }
has_clock!(|self: Groovebox|self.player.clock()); has_clock!(|self: Groovebox|self.player.clock());
impl Groovebox {
pub fn new (
jack: &Arc<RwLock<JackConnection>>,
midi_from: &[impl AsRef<str>],
midi_to: &[impl AsRef<str>],
audio_from: &[&[impl AsRef<str>];2],
audio_to: &[&[impl AsRef<str>];2],
) -> Usually<Self> {
let sampler = crate::sampler::Sampler::new(jack, &"sampler", midi_from, audio_from, audio_to)?;
let mut player = crate::midi::MidiPlayer::new(jack, &"sequencer", &midi_from, &midi_to)?;
jack.read().unwrap().client().connect_ports(&player.midi_outs[0], &sampler.midi_in)?;
let phrase = Arc::new(RwLock::new(MidiClip::new(
"Clip", true, 4 * player.clock.timebase.ppq.get() as usize,
None, Some(ItemColor::random().into())
)));
player.play_phrase = Some((Moment::zero(&player.clock.timebase), Some(phrase.clone())));
Ok(Self {
player,
sampler,
_jack: jack.clone(),
pool: crate::pool::PoolModel::from(&phrase), impl EdnViewData<TuiOut> for &Groovebox {
editor: crate::midi::MidiEditor::from(&phrase), fn get_unit (&self, item: EdnItem<&str>) -> u16 {
use EdnItem::*;
match item.to_str() {
":sample-h" => if self.compact { 0 } else { 5 },
":samples-w" => if self.compact { 4 } else { 11 },
":samples-y" => if self.compact { 1 } else { 0 },
":pool-w" => if self.compact { 5 } else {
let w = self.size.w();
if w > 60 { 20 } else if w > 40 { 15 } else { 10 }
},
_ => 0
}
}
fn get_content <'a> (&'a self, item: EdnItem<&str>) -> RenderBox<'a, TuiOut> {
use EdnItem::*;
match item {
Nil => Box::new(()),
Sym(bol) => match bol {
":input-meter-l" => Meter("L/", self.sampler.input_meter[0]).boxed(),
":input-meter-r" => Box::new(Meter("R/", self.sampler.input_meter[1])),
compact: true, ":transport" => Box::new(TransportView::new(true, &self.player.clock)),
status: true, ":clip-play" => Box::new(self.player.play_status()),
size: Measure::new(), ":clip-next" => Box::new(self.player.next_status()),
midi_buf: vec![vec![];65536], ":clip-edit" => Box::new(self.editor.clip_status()),
note_buf: vec![], ":edit-stat" => Box::new(self.editor.edit_status()),
perf: PerfModel::default(), ":pool-view" => Box::new(PoolView(self.compact, &self.pool)),
}) ":midi-view" => Box::new(&self.editor),
":sample-view" => Box::new(SampleViewer::from_sampler(&self.sampler, self.editor.note_point())),
":sample-stat" => Box::new(SamplerStatus(&self.sampler, self.editor.note_point())),
":samples-view" => Box::new(SampleList::new(self.compact, &self.sampler, &self.editor)),
_ => panic!("unknown sym {bol:?}")
},
Exp(items) => Box::new(EdnView::from_items(*self, items.as_slice())),
_ => panic!("no content for {item:?}")
}
} }
} }

View file

@ -1,44 +0,0 @@
use crate::*;
impl EdnViewData<TuiOut> for &Groovebox {
fn get_unit (&self, item: EdnItem<&str>) -> u16 {
use EdnItem::*;
match item.to_str() {
":sample-h" => if self.compact { 0 } else { 5 },
":samples-w" => if self.compact { 4 } else { 11 },
":samples-y" => if self.compact { 1 } else { 0 },
":pool-w" => if self.compact { 5 } else {
let w = self.size.w();
if w > 60 { 20 } else if w > 40 { 15 } else { 10 }
},
_ => 0
}
}
fn get_content <'a> (&'a self, item: EdnItem<&str>) -> RenderBox<'a, TuiOut> {
use EdnItem::*;
match item {
Nil => Box::new(()),
Sym(bol) => match bol {
":input-meter-l" => Meter("L/", self.sampler.input_meter[0]).boxed(),
":input-meter-r" => Box::new(Meter("R/", self.sampler.input_meter[1])),
":transport" => Box::new(TransportView::new(true, &self.player.clock)),
":clip-play" => Box::new(self.player.play_status()),
":clip-next" => Box::new(self.player.next_status()),
":clip-edit" => Box::new(self.editor.clip_status()),
":edit-stat" => Box::new(self.editor.edit_status()),
":pool-view" => Box::new(PoolView(self.compact, &self.pool)),
":midi-view" => Box::new(&self.editor),
":sample-view" => Box::new(SampleViewer::from_sampler(&self.sampler, self.editor.note_point())),
":sample-stat" => Box::new(SamplerStatus(&self.sampler, self.editor.note_point())),
":samples-view" => Box::new(SampleList::new(self.compact, &self.sampler, &self.editor)),
_ => panic!("unknown sym {bol:?}")
},
Exp(items) => Box::new(EdnView::from_items(*self, items.as_slice())),
_ => panic!("no content for {item:?}")
}
}
}

View file

@ -13,7 +13,7 @@ render!(TuiOut: (self: Groovebox) => self.size.of(
Bsp::n(self.status_view(), Bsp::n(self.status_view(),
Bsp::w(self.pool_view(), Fill::xy(Bsp::e(self.sampler_view(), &self.editor))))))))); Bsp::w(self.pool_view(), Fill::xy(Bsp::e(self.sampler_view(), &self.editor)))))))));
// this almost works: // this almost does:
//impl Content<TuiOut> for Groovebox { //impl Content<TuiOut> for Groovebox {
//fn content (&self) -> impl Render<TuiOut> { //fn content (&self) -> impl Render<TuiOut> {
//self.size.of(EdnView::from_source(self, EDN)) //self.size.of(EdnView::from_source(self, EDN))

View file

@ -1,624 +0,0 @@
use crate::*;
pub use ::jack as libjack;
pub use ::jack::{
contrib::ClosureProcessHandler, NotificationHandler,
Client, AsyncClient, ClientOptions, ClientStatus,
ProcessScope, Control, CycleTimes, Frames,
Port, PortId, PortSpec, Unowned, MidiIn, MidiOut, AudioIn, AudioOut,
Transport, TransportState, MidiIter, MidiWriter, RawMidi,
};
/// Implement [TryFrom<&Arc<RwLock<JackConnection>>>]: create app state from wrapped JACK handle.
#[macro_export] macro_rules! from_jack {
(|$jack:ident|$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)? $cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? TryFrom<&Arc<RwLock<JackConnection>>> for $Struct $(<$($L),*$($T),*>)? {
type Error = Box<dyn std::error::Error>;
fn try_from ($jack: &Arc<RwLock<JackConnection>>) -> Usually<Self> {
Ok($cb)
}
}
};
}
/// Implement [Audio]: provide JACK callbacks.
#[macro_export] macro_rules! audio {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?,$c:ident,$s:ident|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? Audio for $Struct $(<$($L),*$($T),*>)? {
#[inline] fn process (&mut $self, $c: &Client, $s: &ProcessScope) -> Control { $cb }
}
}
}
/// Trait for thing that has a JACK process callback.
pub trait Audio: Send + Sync {
fn process (&mut self, _: &Client, _: &ProcessScope) -> Control {
Control::Continue
}
fn callback (
state: &Arc<RwLock<Self>>, client: &Client, scope: &ProcessScope
) -> Control where Self: Sized {
if let Ok(mut state) = state.write() {
state.process(client, scope)
} else {
Control::Quit
}
}
}
/// This is a boxed realtime callback.
pub type BoxedAudioHandler = Box<dyn FnMut(&Client, &ProcessScope) -> Control + Send>;
/// This is the notification handler wrapper for a boxed realtime callback.
pub type DynamicAudioHandler = ClosureProcessHandler<(), BoxedAudioHandler>;
/// This is a boxed [JackEvent] callback.
pub type BoxedJackEventHandler = Box<dyn Fn(JackEvent) + Send + Sync>;
/// This is the notification handler wrapper for a boxed [JackEvent] callback.
pub type DynamicNotifications = Notifications<BoxedJackEventHandler>;
/// This is a running JACK [AsyncClient] with maximum type erasure.
/// It has one [Box] containing a function that handles [JackEvent]s,
/// and another [Box] containing a function that handles realtime IO,
/// and that's all it knows about them.
pub type DynamicAsyncClient = AsyncClient<DynamicNotifications, DynamicAudioHandler>;
/// This is a connection which may be `Inactive`, `Activating`, or `Active`.
/// In the `Active` and `Inactive` states, its `client` method returns a
/// [Client] which you can use to talk to the JACK API.
#[derive(Debug)]
pub enum JackConnection {
/// Before activation.
Inactive(Client),
/// During activation.
Activating,
/// After activation. Must not be dropped for JACK thread to persist.
Active(DynamicAsyncClient),
}
from!(|jack: JackConnection|Client = match jack {
JackConnection::Inactive(client) => client,
JackConnection::Activating => panic!("jack client still activating"),
JackConnection::Active(_) => panic!("jack client already activated"),
});
impl JackConnection {
pub fn new (name: &str) -> Usually<Self> {
let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?;
Ok(Self::Inactive(client))
}
/// Return the internal [Client] handle that lets you call the JACK API.
pub fn client (&self) -> &Client {
match self {
Self::Inactive(ref client) => client,
Self::Activating => panic!("jack client has not finished activation"),
Self::Active(ref client) => client.as_client(),
}
}
/// Activate a connection with an application.
///
/// Consume a `JackConnection::Inactive`,
/// binding a process callback and
/// returning a `JackConnection::Active`.
///
/// Needs work. Strange ownership situation between the callback
/// and the host object.
fn activate (
self,
mut cb: impl FnMut(&Arc<RwLock<Self>>, &Client, &ProcessScope) -> Control + Send + 'static,
) -> Usually<Arc<RwLock<Self>>>
where
Self: Send + Sync + 'static
{
let client = Client::from(self);
let state = Arc::new(RwLock::new(Self::Activating));
let event = Box::new(move|_|{/*TODO*/}) as Box<dyn Fn(JackEvent) + Send + Sync>;
let events = Notifications(event);
let frame = Box::new({let state = state.clone(); move|c: &_, s: &_|cb(&state, c, s)});
let frames = ClosureProcessHandler::new(frame as BoxedAudioHandler);
*state.write().unwrap() = Self::Active(client.activate_async(events, frames)?);
Ok(state)
}
/// Activate a connection with an application.
///
/// * Wrap a [JackConnection::Inactive] into [Arc<RwLock<_>>].
/// * Pass it to the `init` callback
/// * This allows user code to connect to JACK
/// * While user code retains clone of the
/// [Arc<RwLock<JackConnection>>] that is
/// passed to `init`, the audio engine is running.
pub fn activate_with <T: Audio + 'static> (
self,
init: impl FnOnce(&Arc<RwLock<JackConnection>>)->Usually<T>
)
-> Usually<Arc<RwLock<T>>>
{
// Wrap self for multiple ownership.
let connection = Arc::new(RwLock::new(self));
// Run init callback. Return value is target. Target must retain clone of `connection`.
let target = Arc::new(RwLock::new(init(&connection)?));
// Swap the `client` from the `JackConnection::Inactive`
// for a `JackConnection::Activating`.
let mut client = Self::Activating;
std::mem::swap(&mut*connection.write().unwrap(), &mut client);
// Replace the `JackConnection::Activating` with a
// `JackConnection::Active` wrapping the [AsyncClient]
// returned by the activation.
*connection.write().unwrap() = Self::Active(Client::from(client).activate_async(
// This is the misc notifications handler. It's a struct that wraps a [Box]
// which performs type erasure on a callback that takes [JackEvent], which is
// one of the available misc notifications.
Notifications(Box::new(move|_|{/*TODO*/}) as BoxedJackEventHandler),
// This is the main processing handler. It's a struct that wraps a [Box]
// which performs type erasure on a callback that takes [Client] and [ProcessScope]
// and passes them down to the `target`'s `process` callback, which in turn
// implements audio and MIDI input and output on a realtime basis.
ClosureProcessHandler::new(Box::new({
let target = target.clone();
move|c: &_, s: &_|if let Ok(mut target) = target.write() {
target.process(c, s)
} else {
Control::Quit
}
}) as BoxedAudioHandler),
)?);
Ok(target)
}
pub fn port_by_name (&self, name: &str) -> Option<Port<Unowned>> {
self.client().port_by_name(name)
}
pub fn register_port <PS: PortSpec> (&self, name: &str, spec: PS) -> Usually<Port<PS>> {
Ok(self.client().register_port(name, spec)?)
}
}
/// This is a utility trait for things that may register or connect [Port]s.
/// It contains shorthand methods to this purpose. It's implemented for
/// `Arc<RwLock<JackConnection>>` for terse port registration in the
/// `init` callback of [JackClient::activate_with].
pub trait RegisterPort {
fn midi_in (&self, name: impl AsRef<str>, connect: &[impl AsRef<str>]) -> Usually<Port<MidiIn>>;
fn midi_out (&self, name: impl AsRef<str>, connect: &[impl AsRef<str>]) -> Usually<Port<MidiOut>>;
fn audio_in (&self, name: impl AsRef<str>, connect: &[impl AsRef<str>]) -> Usually<Port<AudioIn>>;
fn audio_out (&self, name: impl AsRef<str>, connect: &[impl AsRef<str>]) -> Usually<Port<AudioOut>>;
}
impl RegisterPort for Arc<RwLock<JackConnection>> {
fn midi_in (&self, name: impl AsRef<str>, connect: &[impl AsRef<str>]) -> Usually<Port<MidiIn>> {
let jack = self.read().unwrap();
let input = jack.client().register_port(name.as_ref(), MidiIn::default())?;
for port in connect.iter() {
let port = port.as_ref();
if let Some(output) = jack.port_by_name(port).as_ref() {
jack.client().connect_ports(output, &input)?;
} else {
panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names.");
}
}
Ok(input)
}
fn midi_out (&self, name: impl AsRef<str>, connect: &[impl AsRef<str>]) -> Usually<Port<MidiOut>> {
let jack = self.read().unwrap();
let output = jack.client().register_port(name.as_ref(), MidiOut::default())?;
for port in connect.iter() {
let port = port.as_ref();
if let Some(input) = jack.port_by_name(port).as_ref() {
jack.client().connect_ports(&output, input)?;
} else {
panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names.");
}
}
Ok(output)
}
fn audio_in (&self, name: impl AsRef<str>, connect: &[impl AsRef<str>]) -> Usually<Port<AudioIn>> {
let jack = self.read().unwrap();
let input = jack.client().register_port(name.as_ref(), AudioIn::default())?;
for port in connect.iter() {
let port = port.as_ref();
if let Some(output) = jack.port_by_name(port).as_ref() {
jack.client().connect_ports(output, &input)?;
} else {
panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names.");
}
}
Ok(input)
}
fn audio_out (&self, name: impl AsRef<str>, connect: &[impl AsRef<str>]) -> Usually<Port<AudioOut>> {
let jack = self.read().unwrap();
let output = jack.client().register_port(name.as_ref(), AudioOut::default())?;
for port in connect.iter() {
let port = port.as_ref();
if let Some(input) = jack.port_by_name(port).as_ref() {
jack.client().connect_ports(&output, input)?;
} else {
panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names.");
}
}
Ok(output)
}
}
#[derive(Debug, Clone, PartialEq)]
/// Event enum for JACK events.
pub enum JackEvent {
ThreadInit,
Shutdown(ClientStatus, Arc<str>),
Freewheel(bool),
SampleRate(Frames),
ClientRegistration(Arc<str>, bool),
PortRegistration(PortId, bool),
PortRename(PortId, Arc<str>, Arc<str>),
PortsConnected(PortId, PortId, bool),
GraphReorder,
XRun,
}
/// Generic notification handler that emits [JackEvent]
pub struct Notifications<T: Fn(JackEvent) + Send>(pub T);
impl<T: Fn(JackEvent) + Send> NotificationHandler for Notifications<T> {
fn thread_init(&self, _: &Client) {
self.0(JackEvent::ThreadInit);
}
unsafe fn shutdown(&mut self, status: ClientStatus, reason: &str) {
self.0(JackEvent::Shutdown(status, reason.into()));
}
fn freewheel(&mut self, _: &Client, enabled: bool) {
self.0(JackEvent::Freewheel(enabled));
}
fn sample_rate(&mut self, _: &Client, frames: Frames) -> Control {
self.0(JackEvent::SampleRate(frames));
Control::Quit
}
fn client_registration(&mut self, _: &Client, name: &str, reg: bool) {
self.0(JackEvent::ClientRegistration(name.into(), reg));
}
fn port_registration(&mut self, _: &Client, id: PortId, reg: bool) {
self.0(JackEvent::PortRegistration(id, reg));
}
fn port_rename(&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control {
self.0(JackEvent::PortRename(id, old.into(), new.into()));
Control::Continue
}
fn ports_connected(&mut self, _: &Client, a: PortId, b: PortId, are: bool) {
self.0(JackEvent::PortsConnected(a, b, are));
}
fn graph_reorder(&mut self, _: &Client) -> Control {
self.0(JackEvent::GraphReorder);
Control::Continue
}
fn xrun(&mut self, _: &Client) -> Control {
self.0(JackEvent::XRun);
Control::Continue
}
}
////////////////////////////////////////////////////////////////////////////////////
///// A [AudioComponent] bound to a JACK client and a set of ports.
//pub struct JackDevice<E: Engine> {
///// The active JACK client of this device.
//pub client: DynamicAsyncClient,
///// The device state, encapsulated for sharing between threads.
//pub state: Arc<RwLock<Box<dyn AudioComponent<E>>>>,
///// Unowned copies of the device's JACK ports, for connecting to the device.
///// The "real" readable/writable `Port`s are owned by the `state`.
//pub ports: UnownedJackPorts,
//}
//impl<E: Engine> std::fmt::Debug for JackDevice<E> {
//fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
//f.debug_struct("JackDevice")
//.field("ports", &self.ports)
//.finish()
//}
//}
//impl<E: Engine> Render for JackDevice<E> {
//type Engine = E;
//fn min_size(&self, to: E::Size) -> Perhaps<E::Size> {
//self.state.read().unwrap().layout(to)
//}
//fn render(&self, to: &mut E::Output) -> Usually<()> {
//self.state.read().unwrap().render(to)
//}
//}
//impl<E: Engine> Handle<E> for JackDevice<E> {
//fn handle(&mut self, from: &E::Input) -> Perhaps<E::Handled> {
//self.state.write().unwrap().handle(from)
//}
//}
//impl<E: Engine> Ports for JackDevice<E> {
//fn audio_ins(&self) -> Usually<Vec<&Port<Unowned>>> {
//Ok(self.ports.audio_ins.values().collect())
//}
//fn audio_outs(&self) -> Usually<Vec<&Port<Unowned>>> {
//Ok(self.ports.audio_outs.values().collect())
//}
//fn midi_ins(&self) -> Usually<Vec<&Port<Unowned>>> {
//Ok(self.ports.midi_ins.values().collect())
//}
//fn midi_outs(&self) -> Usually<Vec<&Port<Unowned>>> {
//Ok(self.ports.midi_outs.values().collect())
//}
//}
//impl<E: Engine> JackDevice<E> {
///// Returns a locked mutex of the state's contents.
//pub fn state(&self) -> LockResult<RwLockReadGuard<Box<dyn AudioComponent<E>>>> {
//self.state.read()
//}
///// Returns a locked mutex of the state's contents.
//pub fn state_mut(&self) -> LockResult<RwLockWriteGuard<Box<dyn AudioComponent<E>>>> {
//self.state.write()
//}
//pub fn connect_midi_in(&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
//Ok(self
//.client
//.as_client()
//.connect_ports(port, self.midi_ins()?[index])?)
//}
//pub fn connect_midi_out(&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
//Ok(self
//.client
//.as_client()
//.connect_ports(self.midi_outs()?[index], port)?)
//}
//pub fn connect_audio_in(&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
//Ok(self
//.client
//.as_client()
//.connect_ports(port, self.audio_ins()?[index])?)
//}
//pub fn connect_audio_out(&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
//Ok(self
//.client
//.as_client()
//.connect_ports(self.audio_outs()?[index], port)?)
//}
//}
///// Collection of JACK ports as [AudioIn]/[AudioOut]/[MidiIn]/[MidiOut].
//#[derive(Default, Debug)]
//pub struct JackPorts {
//pub audio_ins: BTreeMap<String, Port<AudioIn>>,
//pub midi_ins: BTreeMap<String, Port<MidiIn>>,
//pub audio_outs: BTreeMap<String, Port<AudioOut>>,
//pub midi_outs: BTreeMap<String, Port<MidiOut>>,
//}
///// Collection of JACK ports as [Unowned].
//#[derive(Default, Debug)]
//pub struct UnownedJackPorts {
//pub audio_ins: BTreeMap<String, Port<Unowned>>,
//pub midi_ins: BTreeMap<String, Port<Unowned>>,
//pub audio_outs: BTreeMap<String, Port<Unowned>>,
//pub midi_outs: BTreeMap<String, Port<Unowned>>,
//}
//impl JackPorts {
//pub fn clone_unowned(&self) -> UnownedJackPorts {
//let mut unowned = UnownedJackPorts::default();
//for (name, port) in self.midi_ins.iter() {
//unowned.midi_ins.insert(name.clone(), port.clone_unowned());
//}
//for (name, port) in self.midi_outs.iter() {
//unowned.midi_outs.insert(name.clone(), port.clone_unowned());
//}
//for (name, port) in self.audio_ins.iter() {
//unowned.audio_ins.insert(name.clone(), port.clone_unowned());
//}
//for (name, port) in self.audio_outs.iter() {
//unowned
//.audio_outs
//.insert(name.clone(), port.clone_unowned());
//}
//unowned
//}
//}
///// Implement the `Ports` trait.
//#[macro_export]
//macro_rules! ports {
//($T:ty $({ $(audio: {
//$(ins: |$ai_arg:ident|$ai_impl:expr,)?
//$(outs: |$ao_arg:ident|$ao_impl:expr,)?
//})? $(midi: {
//$(ins: |$mi_arg:ident|$mi_impl:expr,)?
//$(outs: |$mo_arg:ident|$mo_impl:expr,)?
//})?})?) => {
//impl Ports for $T {$(
//$(
//$(fn audio_ins <'a> (&'a self) -> Usually<Vec<&'a Port<Unowned>>> {
//let cb = |$ai_arg:&'a Self|$ai_impl;
//cb(self)
//})?
//)?
//$(
//$(fn audio_outs <'a> (&'a self) -> Usually<Vec<&'a Port<Unowned>>> {
//let cb = (|$ao_arg:&'a Self|$ao_impl);
//cb(self)
//})?
//)?
//)? $(
//$(
//$(fn midi_ins <'a> (&'a self) -> Usually<Vec<&'a Port<Unowned>>> {
//let cb = (|$mi_arg:&'a Self|$mi_impl);
//cb(self)
//})?
//)?
//$(
//$(fn midi_outs <'a> (&'a self) -> Usually<Vec<&'a Port<Unowned>>> {
//let cb = (|$mo_arg:&'a Self|$mo_impl);
//cb(self)
//})?
//)?
//)?}
//};
//}
///// `JackDevice` factory. Creates JACK `Client`s, performs port registration
///// and activation, and encapsulates a `AudioComponent` into a `JackDevice`.
//pub struct Jack {
//pub client: Client,
//pub midi_ins: Vec<String>,
//pub audio_ins: Vec<String>,
//pub midi_outs: Vec<String>,
//pub audio_outs: Vec<String>,
//}
//impl Jack {
//pub fn new(name: &str) -> Usually<Self> {
//Ok(Self {
//midi_ins: vec![],
//audio_ins: vec![],
//midi_outs: vec![],
//audio_outs: vec![],
//client: Client::new(name, ClientOptions::NO_START_SERVER)?.0,
//})
//}
//pub fn run<'a: 'static, D, E>(
//self,
//state: impl FnOnce(JackPorts) -> Box<D>,
//) -> Usually<JackDevice<E>>
//where
//D: AudioComponent<E> + Sized + 'static,
//E: Engine + 'static,
//{
//let owned_ports = JackPorts {
//audio_ins: register_ports(&self.client, self.audio_ins, AudioIn::default())?,
//audio_outs: register_ports(&self.client, self.audio_outs, AudioOut::default())?,
//midi_ins: register_ports(&self.client, self.midi_ins, MidiIn::default())?,
//midi_outs: register_ports(&self.client, self.midi_outs, MidiOut::default())?,
//};
//let midi_outs = owned_ports
//.midi_outs
//.values()
//.map(|p| Ok(p.name()?))
//.collect::<Usually<Vec<_>>>()?;
//let midi_ins = owned_ports
//.midi_ins
//.values()
//.map(|p| Ok(p.name()?))
//.collect::<Usually<Vec<_>>>()?;
//let audio_outs = owned_ports
//.audio_outs
//.values()
//.map(|p| Ok(p.name()?))
//.collect::<Usually<Vec<_>>>()?;
//let audio_ins = owned_ports
//.audio_ins
//.values()
//.map(|p| Ok(p.name()?))
//.collect::<Usually<Vec<_>>>()?;
//let state = Arc::new(RwLock::new(state(owned_ports) as Box<dyn AudioComponent<E>>));
//let client = self.client.activate_async(
//Notifications(Box::new({
//let _state = state.clone();
//move |_event| {
//// FIXME: this deadlocks
////state.lock().unwrap().handle(&event).unwrap();
//}
//}) as Box<dyn Fn(JackEvent) + Send + Sync>),
//ClosureProcessHandler::new(Box::new({
//let state = state.clone();
//move |c: &Client, s: &ProcessScope| state.write().unwrap().process(c, s)
//}) as BoxedAudioHandler),
//)?;
//Ok(JackDevice {
//ports: UnownedJackPorts {
//audio_ins: query_ports(&client.as_client(), audio_ins),
//audio_outs: query_ports(&client.as_client(), audio_outs),
//midi_ins: query_ports(&client.as_client(), midi_ins),
//midi_outs: query_ports(&client.as_client(), midi_outs),
//},
//client,
//state,
//})
//}
//pub fn audio_in(mut self, name: &str) -> Self {
//self.audio_ins.push(name.to_string());
//self
//}
//pub fn audio_out(mut self, name: &str) -> Self {
//self.audio_outs.push(name.to_string());
//self
//}
//pub fn midi_in(mut self, name: &str) -> Self {
//self.midi_ins.push(name.to_string());
//self
//}
//pub fn midi_out(mut self, name: &str) -> Self {
//self.midi_outs.push(name.to_string());
//self
//}
//}
///// A UI component that may be associated with a JACK client by the `Jack` factory.
//pub trait AudioComponent<E: Engine>: Component<E> + Audio {
///// Perform type erasure for collecting heterogeneous devices.
//fn boxed(self) -> Box<dyn AudioComponent<E>>
//where
//Self: Sized + 'static,
//{
//Box::new(self)
//}
//}
///// All things that implement the required traits can be treated as `AudioComponent`.
//impl<E: Engine, W: Component<E> + Audio> AudioComponent<E> for W {}
/////////
/*
/// Trait for things that may expose JACK ports.
pub trait Ports {
fn audio_ins(&self) -> Usually<Vec<&Port<Unowned>>> {
Ok(vec![])
}
fn audio_outs(&self) -> Usually<Vec<&Port<Unowned>>> {
Ok(vec![])
}
fn midi_ins(&self) -> Usually<Vec<&Port<Unowned>>> {
Ok(vec![])
}
fn midi_outs(&self) -> Usually<Vec<&Port<Unowned>>> {
Ok(vec![])
}
}
fn register_ports<T: PortSpec + Copy>(
client: &Client,
names: Vec<String>,
spec: T,
) -> Usually<BTreeMap<String, Port<T>>> {
names
.into_iter()
.try_fold(BTreeMap::new(), |mut ports, name| {
let port = client.register_port(&name, spec)?;
ports.insert(name, port);
Ok(ports)
})
}
fn query_ports(client: &Client, names: Vec<String>) -> BTreeMap<String, Port<Unowned>> {
names.into_iter().fold(BTreeMap::new(), |mut ports, name| {
let port = client.port_by_name(&name).unwrap();
ports.insert(name, port);
ports
})
}
*/

View file

@ -24,6 +24,8 @@ pub(crate) use ::tek_tui::{
buffer::Cell, buffer::Cell,
} }
}; };
pub use ::tek_jack;
pub(crate) use ::tek_jack::{*, jack::{*, contrib::*}};
pub(crate) use std::cmp::{Ord, Eq, PartialEq}; pub(crate) use std::cmp::{Ord, Eq, PartialEq};
pub(crate) use std::collections::BTreeMap; pub(crate) use std::collections::BTreeMap;
@ -40,14 +42,10 @@ pub(crate) use std::thread::{spawn, JoinHandle};
pub(crate) use std::time::Duration; pub(crate) use std::time::Duration;
pub mod arranger; pub use self::arranger::*; pub mod arranger; pub use self::arranger::*;
pub mod clock; pub use self::clock::*;
pub mod field; pub use self::field::*;
pub mod file; pub use self::file::*; pub mod file; pub use self::file::*;
pub mod focus; pub use self::focus::*; pub mod focus; pub use self::focus::*;
pub mod groovebox; pub use self::groovebox::*; pub mod groovebox; pub use self::groovebox::*;
pub mod jack; pub use self::jack::*;
pub mod meter; pub use self::meter::*; pub mod meter; pub use self::meter::*;
pub mod midi; pub use self::midi::*;
pub mod mixer; pub use self::mixer::*; pub mod mixer; pub use self::mixer::*;
pub mod piano; pub use self::piano::*; pub mod piano; pub use self::piano::*;
pub mod plugin; pub use self::plugin::*; pub mod plugin; pub use self::plugin::*;
@ -55,9 +53,6 @@ pub mod pool; pub use self::pool::*;
pub mod sampler; pub use self::sampler::*; pub mod sampler; pub use self::sampler::*;
pub mod sequencer; pub use self::sequencer::*; pub mod sequencer; pub use self::sequencer::*;
pub use ::atomic_float;
pub(crate) use atomic_float::*;
pub use ::midly::{self, num::u7}; pub use ::midly::{self, num::u7};
pub(crate) use ::midly::{ pub(crate) use ::midly::{
Smf, Smf,
@ -73,37 +68,6 @@ testmod! { test }
($($name:ident)*) => { $(#[cfg(test)] mod $name;)* }; ($($name:ident)*) => { $(#[cfg(test)] mod $name;)* };
} }
pub trait Gettable<T> {
/// Returns current value
fn get (&self) -> T;
}
pub trait Mutable<T>: Gettable<T> {
/// Sets new value, returns old
fn set (&mut self, value: T) -> T;
}
pub trait InteriorMutable<T>: Gettable<T> {
/// Sets new value, returns old
fn set (&self, value: T) -> T;
}
impl Gettable<bool> for AtomicBool {
fn get (&self) -> bool { self.load(Relaxed) }
}
impl InteriorMutable<bool> for AtomicBool {
fn set (&self, value: bool) -> bool { self.swap(value, Relaxed) }
}
impl Gettable<usize> for AtomicUsize {
fn get (&self) -> usize { self.load(Relaxed) }
}
impl InteriorMutable<usize> for AtomicUsize {
fn set (&self, value: usize) -> usize { self.swap(value, Relaxed) }
}
#[derive(Default)] #[derive(Default)]
pub struct BigBuffer { pub struct BigBuffer {
pub width: usize, pub width: usize,

View file

@ -6,7 +6,7 @@ use MidiEditCommand::*;
use PhrasePoolCommand::*; use PhrasePoolCommand::*;
/// Root view for standalone `tek_sequencer`. /// Root view for standalone `tek_sequencer`.
pub struct Sequencer { pub struct Sequencer {
_jack: Arc<RwLock<JackConnection>>, pub _jack: Arc<RwLock<JackConnection>>,
pub pool: PoolModel, pub pool: PoolModel,
pub editor: MidiEditor, pub editor: MidiEditor,
@ -23,32 +23,6 @@ pub struct Sequencer {
pub midi_buf: Vec<Vec<Vec<u8>>>, pub midi_buf: Vec<Vec<Vec<u8>>>,
pub perf: PerfModel, pub perf: PerfModel,
} }
impl Sequencer {
pub fn new (jack: &Arc<RwLock<JackConnection>>) -> Usually<Self> {
let clock = Clock::from(jack);
let phrase = Arc::new(RwLock::new(MidiClip::new(
"Clip", true, 4 * clock.timebase.ppq.get() as usize,
None, Some(ItemColor::random().into())
)));
Ok(Self {
_jack: jack.clone(),
pool: PoolModel::from(&phrase),
editor: MidiEditor::from(&phrase),
player: MidiPlayer::from((&clock, &phrase)),
compact: true,
transport: true,
selectors: true,
size: Measure::new(),
midi_buf: vec![vec![];65536],
note_buf: vec![],
perf: PerfModel::default(),
status: true,
clock,
})
}
}
render!(TuiOut: (self: Sequencer) => self.size.of( render!(TuiOut: (self: Sequencer) => self.size.of(
Bsp::s(self.toolbar_view(), Bsp::s(self.toolbar_view(),
Bsp::n(self.selector_view(), Bsp::n(self.selector_view(),

13
time/Cargo.toml Normal file
View file

@ -0,0 +1,13 @@
[package]
name = "tek_time"
edition = "2021"
version = "0.2.0"
[dependencies]
tek_tui = { path = "../tui" }
tek_jack = { path = "../jack" }
atomic_float = "1.0.0"
quanta = "0.12.3"
#jack = { path = "../rust-jack" }
#midly = "0.5"

View file

@ -1,23 +1,25 @@
use crate::*; use crate::*;
use ClockCommand::{Play, Pause, SetBpm, SetQuant, SetSync}; use ClockCommand::{Play, Pause, SetBpm, SetQuant, SetSync};
use FocusCommand::{Next, Prev}; //use FocusCommand::{Next, Prev};
use KeyCode::{Enter, Left, Right, Char}; use KeyCode::{Enter, Left, Right, Char};
/// Transport clock app. /// Transport clock app.
pub struct TransportTui { pub struct TransportTui {
pub jack: Arc<RwLock<JackConnection>>, pub jack: Arc<RwLock<JackConnection>>,
pub clock: Clock, pub clock: Clock,
} }
handle!(TuiIn: |self: TransportTui, input|ClockCommand::execute_with_state(self, input.event()));
keymap!(TRANSPORT_KEYS = |state: TransportTui, input: Event| ClockCommand {
key(Char(' ')) =>
if state.clock().is_stopped() { Play(None) } else { Pause(None) },
shift(key(Char(' '))) =>
if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }
});
has_clock!(|self: TransportTui|&self.clock); has_clock!(|self: TransportTui|&self.clock);
audio!(|self: TransportTui, client, scope|ClockAudio(self).process(client, scope)); audio!(|self: TransportTui, client, scope|ClockAudio(self).process(client, scope));
render!(TuiOut: (self: TransportTui) => TransportView { render!(TuiOut: (self: TransportTui) => TransportView {
compact: false, compact: false,
clock: &self.clock clock: &self.clock
}); });
impl TransportTui {
pub fn new (jack: &Arc<RwLock<JackConnection>>) -> Usually<Self> {
Ok(Self { jack: jack.clone(), clock: Clock::from(jack) })
}
}
pub struct TransportView<'a> { pub compact: bool, pub clock: &'a Clock } pub struct TransportView<'a> { pub compact: bool, pub clock: &'a Clock }
impl<'a> TransportView<'a> { impl<'a> TransportView<'a> {
@ -103,13 +105,6 @@ render!(TuiOut: (self: OutputStats) => Either(self.compact,
Bsp::e(Tui::fg(TuiTheme::g(255), format!("{:.3}ms", self.latency)), " latency"), Bsp::e(Tui::fg(TuiTheme::g(255), format!("{:.3}ms", self.latency)), " latency"),
))); )));
handle!(TuiIn: |self: TransportTui, input|ClockCommand::execute_with_state(self, input.event()));
keymap!(TRANSPORT_KEYS = |state: TransportTui, input: Event| ClockCommand {
key(Char(' ')) =>
if state.clock().is_stopped() { Play(None) } else { Pause(None) },
shift(key(Char(' '))) =>
if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }
});
// TODO: // TODO:
//keymap!(TRANSPORT_BPM_KEYS = |state: Clock, input: Event| ClockCommand { //keymap!(TRANSPORT_BPM_KEYS = |state: Clock, input: Event| ClockCommand {
//key(Char(',')) => SetBpm(state.bpm().get() - 1.0), //key(Char(',')) => SetBpm(state.bpm().get() - 1.0),

View file

@ -1,14 +1,24 @@
use crate::*; mod clock_tui; pub use self::clock_tui::*;
mod microsecond; pub use self::microsecond::*;
mod moment; pub use self::moment::*;
mod perf; pub use self::perf::*;
mod pulse; pub use self::pulse::*;
mod sample_count; pub use self::sample_count::*;
mod sample_rate; pub use self::sample_rate::*;
mod timebase; pub use self::timebase::*;
mod unit; pub use self::unit::*;
pub mod clock_tui; pub use self::clock_tui::*; pub(crate) use ::tek_jack::{*, jack::{*, contrib::*}};
pub mod microsecond; pub(crate) use self::microsecond::*; pub(crate) use std::sync::{Arc, Mutex, RwLock, atomic::{AtomicUsize, Ordering::*}};
pub mod moment; pub(crate) use self::moment::*; pub(crate) use std::ops::{Add, Sub, Mul, Div, Rem};
pub mod perf; pub(crate) use self::perf::*; pub use ::atomic_float; pub(crate) use atomic_float::*;
pub mod pulse; pub(crate) use self::pulse::*; pub(crate) use ::tek_tui::{
pub mod sample_count; pub(crate) use self::sample_count::*; *,
pub mod sample_rate; pub(crate) use self::sample_rate::*; tek_input::*,
pub mod timebase; pub(crate) use self::timebase::*; tek_output::*,
pub mod unit; pub(crate) use self::unit::*; crossterm::event::KeyCode,
ratatui::prelude::*
};
pub trait HasClock: Send + Sync { pub trait HasClock: Send + Sync {
fn clock (&self) -> &Clock; fn clock (&self) -> &Clock;
@ -201,12 +211,12 @@ impl Clock {
Ok(()) Ok(())
} }
pub fn bbt (&self) -> ::jack::contrib::PositionBBT { pub fn bbt (&self) -> PositionBBT {
let pulse = self.playhead.pulse.get() as i32; let pulse = self.playhead.pulse.get() as i32;
let ppq = self.timebase.ppq.get() as i32; let ppq = self.timebase.ppq.get() as i32;
let bpm = self.timebase.bpm.get(); let bpm = self.timebase.bpm.get();
let bar = (pulse / ppq) / 4; let bar = (pulse / ppq) / 4;
::jack::contrib::PositionBBT { PositionBBT {
bar: 1 + bar, bar: 1 + bar,
beat: 1 + (pulse / ppq) % 4, beat: 1 + (pulse / ppq) % 4,
tick: (pulse % ppq), tick: (pulse % ppq),

View file

@ -17,8 +17,9 @@ mod tui_color; pub use self::tui_color::*;
mod tui_style; pub use self::tui_style::*; mod tui_style; pub use self::tui_style::*;
mod tui_theme; pub use self::tui_theme::*; mod tui_theme; pub use self::tui_theme::*;
mod tui_border; pub use self::tui_border::*; mod tui_border; pub use self::tui_border::*;
mod tui_field; pub use self::tui_field::*;
pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::*}}; pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicUsize, AtomicBool, Ordering::*}};
pub(crate) use std::io::{stdout, Stdout}; pub(crate) use std::io::{stdout, Stdout};
pub(crate) use std::error::Error; pub(crate) use std::error::Error;
@ -38,6 +39,37 @@ pub type Perhaps<T> = Result<Option<T>, Box<dyn Error>>;
}; };
} }
pub trait Gettable<T> {
/// Returns current value
fn get (&self) -> T;
}
pub trait Mutable<T>: Gettable<T> {
/// Sets new value, returns old
fn set (&mut self, value: T) -> T;
}
pub trait InteriorMutable<T>: Gettable<T> {
/// Sets new value, returns old
fn set (&self, value: T) -> T;
}
impl Gettable<bool> for AtomicBool {
fn get (&self) -> bool { self.load(Relaxed) }
}
impl InteriorMutable<bool> for AtomicBool {
fn set (&self, value: bool) -> bool { self.swap(value, Relaxed) }
}
impl Gettable<usize> for AtomicUsize {
fn get (&self) -> usize { self.load(Relaxed) }
}
impl InteriorMutable<usize> for AtomicUsize {
fn set (&self, value: usize) -> usize { self.swap(value, Relaxed) }
}
pub use ::better_panic; pub(crate) use better_panic::{Settings, Verbosity}; pub use ::better_panic; pub(crate) use better_panic::{Settings, Verbosity};
pub use ::palette; pub(crate) use ::palette::{*, convert::*, okhsl::*}; pub use ::palette; pub(crate) use ::palette::{*, convert::*, okhsl::*};