mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
wip: modularize once again
This commit is contained in:
parent
e08c9d1790
commit
3b6ff81dad
44 changed files with 984 additions and 913 deletions
34
Cargo.lock
generated
34
Cargo.lock
generated
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -5,16 +5,14 @@ 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"
|
||||||
|
|
|
||||||
3
Justfile
3
Justfile
|
|
@ -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
|
||||||
|
|
|
||||||
117
bin/tek.rs
117
bin/tek.rs
|
|
@ -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,24 +98,45 @@ 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)?;
|
|
||||||
let jack = jack.read().unwrap();
|
|
||||||
let midi_in = jack.register_port("i", MidiIn::default())?;
|
|
||||||
let midi_out = jack.register_port("o", MidiOut::default())?;
|
|
||||||
connect_from(&jack, &midi_in, &midi_from)?;
|
|
||||||
connect_to(&jack, &midi_out, &midi_to)?;
|
|
||||||
app.player.midi_ins.push(midi_in);
|
|
||||||
app.player.midi_outs.push(midi_out);
|
|
||||||
app
|
|
||||||
}))?)?,
|
}))?)?,
|
||||||
Sampler { midi_from, l_from, r_from, l_to, r_to, .. } =>
|
|
||||||
engine.run(&jack.activate_with(|jack|Ok(
|
Sequencer {
|
||||||
|
midi_from,
|
||||||
|
midi_to, ..
|
||||||
|
} => engine.run(&jack.activate_with(|jack|Ok({
|
||||||
|
let clock = crate::Clock::from(jack);
|
||||||
|
let phrase = Arc::new(RwLock::new(crate::MidiClip::new(
|
||||||
|
"Clip", true, 4 * clock.timebase.ppq.get() as usize,
|
||||||
|
None, Some(ItemColor::random().into())
|
||||||
|
)));
|
||||||
|
let midi_in = jack.read().unwrap().register_port("i", MidiIn::default())?;
|
||||||
|
connect_from(&jack, &midi_in, &midi_from)?;
|
||||||
|
let midi_out = jack.read().unwrap().register_port("o", MidiOut::default())?;
|
||||||
|
connect_to(&jack, &midi_out, &midi_to)?;
|
||||||
|
crate::Sequencer {
|
||||||
|
_jack: jack.clone(),
|
||||||
|
pool: PoolModel::from(&phrase),
|
||||||
|
editor: crate::MidiEditor::from(&phrase),
|
||||||
|
player: crate::MidiPlayer::new(&clock, &phrase, &[midi_in], &[midi_out])?,
|
||||||
|
compact: true,
|
||||||
|
transport: true,
|
||||||
|
selectors: true,
|
||||||
|
size: Measure::new(),
|
||||||
|
midi_buf: vec![vec![];65536],
|
||||||
|
note_buf: vec![],
|
||||||
|
perf: PerfModel::default(),
|
||||||
|
status: true,
|
||||||
|
clock,
|
||||||
|
}
|
||||||
|
}))?)?,
|
||||||
|
|
||||||
|
Sampler {
|
||||||
|
midi_from, l_from, r_from, l_to, r_to, ..
|
||||||
|
} => engine.run(&jack.activate_with(|jack|Ok(
|
||||||
tek::SamplerTui {
|
tek::SamplerTui {
|
||||||
cursor: (0, 0),
|
cursor: (0, 0),
|
||||||
editing: None,
|
editing: None,
|
||||||
|
|
@ -131,16 +145,51 @@ pub fn main () -> Usually<()> {
|
||||||
note_lo: 36.into(),
|
note_lo: 36.into(),
|
||||||
note_pt: 36.into(),
|
note_pt: 36.into(),
|
||||||
color: ItemPalette::from(Color::Rgb(64, 128, 32)),
|
color: ItemPalette::from(Color::Rgb(64, 128, 32)),
|
||||||
state: tek::Sampler::new(
|
state: tek::Sampler::new(jack, &"sampler",
|
||||||
jack, &"sampler",
|
|
||||||
&midi_from,
|
&midi_from,
|
||||||
&[&l_from, &r_from],
|
&[&l_from, &r_from],
|
||||||
&[&l_to, &r_to],
|
&[&l_to, &r_to],
|
||||||
)?,
|
)?,
|
||||||
}
|
}
|
||||||
))?)?,
|
))?)?,
|
||||||
Groovebox { midi_from, midi_to, l_from, r_from, l_to, r_to, .. } =>
|
|
||||||
engine.run(&jack.activate_with(|jack|Ok({
|
Groovebox {
|
||||||
|
midi_from, midi_to, l_from, r_from, l_to, r_to, ..
|
||||||
|
} => engine.run(&jack.activate_with(|jack|Ok({
|
||||||
|
let phrase = Arc::new(RwLock::new(MidiClip::new(
|
||||||
|
"Clip", true, 4 * player.clock.timebase.ppq.get() as usize,
|
||||||
|
None, Some(ItemColor::random().into())
|
||||||
|
)));
|
||||||
|
let mut player = crate::midi::MidiPlayer::new(jack, &"sequencer", Some(&phrase),
|
||||||
|
&midi_from,
|
||||||
|
&midi_to
|
||||||
|
)?;
|
||||||
|
player.play_phrase = Some((Moment::zero(&player.clock.timebase), Some(phrase.clone())));
|
||||||
|
let sampler = crate::sampler::Sampler::new(jack, &"sampler",
|
||||||
|
midi_from,
|
||||||
|
&[l_from, r_from],
|
||||||
|
&[l_to, r_to ],
|
||||||
|
)?;
|
||||||
|
jack.read().unwrap().client().connect_ports(
|
||||||
|
&player.midi_outs[0],
|
||||||
|
&sampler.midi_in
|
||||||
|
)?;
|
||||||
|
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(
|
let app = tek::Groovebox::new(
|
||||||
jack,
|
jack,
|
||||||
&midi_from, &midi_to,
|
&midi_from, &midi_to,
|
||||||
|
|
@ -164,14 +213,16 @@ pub fn main () -> Usually<()> {
|
||||||
}
|
}
|
||||||
app
|
app
|
||||||
}))?)?,
|
}))?)?,
|
||||||
Arranger { scenes, tracks, track_width, midi_from, midi_to, .. } =>
|
|
||||||
engine.run(&jack.activate_with(|jack|Ok({
|
Arranger {
|
||||||
let mut app = crate::Arranger::try_from(jack)?;
|
scenes, tracks, track_width, midi_from, midi_to, ..
|
||||||
app.color = ItemPalette::random();
|
} => 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.tracks_add(tracks, track_width, midi_from.as_slice(), midi_to.as_slice())?;
|
||||||
app.scenes_add(scenes)?;
|
app.scenes_add(scenes)?;
|
||||||
app
|
app
|
||||||
}))?)?,
|
}))?)?,
|
||||||
|
|
||||||
_ => todo!()
|
_ => todo!()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
7
jack/Cargo.toml
Normal file
7
jack/Cargo.toml
Normal 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
13
jack/src/from_jack.rs
Normal 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
26
jack/src/jack_audio.rs
Normal 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
132
jack/src/jack_connection.rs
Normal 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
65
jack/src/jack_event.rs
Normal 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
185
jack/src/jack_port.rs
Normal 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
222
jack/src/lib.rs
Normal 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
12
midi/Cargo.toml
Normal 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"
|
||||||
|
|
@ -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) {
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -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()));
|
|
||||||
|
|
|
||||||
|
|
@ -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:?}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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:?}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
624
src/jack.rs
624
src/jack.rs
|
|
@ -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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
40
src/lib.rs
40
src/lib.rs
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
13
time/Cargo.toml
Normal 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"
|
||||||
|
|
||||||
|
|
@ -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),
|
||||||
|
|
@ -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),
|
||||||
|
|
@ -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::*};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue