move the jack stuff into tengri

This commit is contained in:
okay stopped screaming 2026-02-23 21:21:17 +02:00
parent 9ae35830c3
commit 6295f2e601
12 changed files with 107 additions and 440 deletions

View file

View file

@ -13,8 +13,8 @@ extern crate xdg;
pub(crate) use ::xdg::BaseDirectories;
pub extern crate atomic_float;
pub(crate) use atomic_float::AtomicF64;
pub extern crate jack;
pub(crate) use ::jack::{*, contrib::{*, ClosureProcessHandler}};
//pub extern crate jack;
//pub(crate) use ::jack::{*, contrib::{*, ClosureProcessHandler}};
pub extern crate midly;
pub(crate) use ::midly::{Smf, TrackEventKind, MidiMessage, Error as MidiError, num::*, live::*};
pub extern crate tengri;
@ -301,10 +301,10 @@ pub fn tek_print_status (project: &Arrangement) {
}
/// Return boxed iterator of MIDI events
pub fn parse_midi_input <'a> (input: ::jack::MidiIter<'a>)
pub fn parse_midi_input <'a> (input: ::tengri::jack::MidiIter<'a>)
-> Box<dyn Iterator<Item=(usize, LiveEvent<'a>, &'a [u8])> + 'a>
{
Box::new(input.map(|::jack::RawMidi { time, bytes }|(
Box::new(input.map(|::tengri::jack::RawMidi { time, bytes }|(
time as usize,
LiveEvent::parse(bytes).unwrap(),
bytes
@ -426,24 +426,10 @@ impl Device {
}
}
audio!(|self: DeviceAudio<'a>, client, scope|{
use Device::*;
match self.0 {
Mute => { Control::Continue },
Bypass => { /*TODO*/ Control::Continue },
#[cfg(feature = "sampler")] Sampler(sampler) => sampler.process(client, scope),
#[cfg(feature = "lv2")] Lv2(lv2) => lv2.process(client, scope),
#[cfg(feature = "vst2")] Vst2 => { todo!() }, // TODO
#[cfg(feature = "vst3")] Vst3 => { todo!() }, // TODO
#[cfg(feature = "clap")] Clap => { todo!() }, // TODO
#[cfg(feature = "sf2")] Sf2 => { todo!() }, // TODO
}
});
//take!(DeviceCommand|state: Arrangement, iter|state.selected_device().as_ref()
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
#[macro_export] macro_rules! has_clip {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? HasMidiClip for $Struct $(<$($L),*$($T),*>)? {

View file

@ -280,80 +280,8 @@ mod app {
}
}
}
}
mod dialog {
use crate::*;
impl Dialog {
/// ```
/// let _ = tek::Dialog::welcome();
/// ```
pub fn welcome () -> Self {
Self::Menu(1, MenuItems([
MenuItem("Resume session".into(), Arc::new(Box::new(|_|Ok(())))),
MenuItem("Create new session".into(), Arc::new(Box::new(|app|Ok({
app.dialog = Dialog::None;
app.mode = app.config.modes.clone().read().unwrap().get(":arranger").cloned().unwrap();
})))),
MenuItem("Load old session".into(), Arc::new(Box::new(|_|Ok(())))),
].into()))
}
/// FIXME: generalize
/// ```
/// let _ = tek::Dialog::welcome().menu_selected();
/// ```
pub fn menu_selected (&self) -> Option<usize> {
if let Self::Menu(selected, _) = self { Some(*selected) } else { None }
}
/// FIXME: generalize
/// ```
/// let _ = tek::Dialog::welcome().menu_next();
/// ```
pub fn menu_next (&self) -> Self {
match self {
Self::Menu(index, items) => Self::Menu(wrap_inc(*index, items.0.len()), items.clone()),
_ => Self::None
}
}
/// FIXME: generalize
/// ```
/// let _ = tek::Dialog::welcome().menu_prev();
/// ```
pub fn menu_prev (&self) -> Self {
match self {
Self::Menu(index, items) => Self::Menu(wrap_dec(*index, items.0.len()), items.clone()),
_ => Self::None
}
}
/// FIXME: generalize
/// ```
/// let _ = tek::Dialog::welcome().device_kind();
/// ```
pub fn device_kind (&self) -> Option<usize> {
if let Self::Device(index) = self { Some(*index) } else { None }
}
/// FIXME: generalize
/// ```
/// let _ = tek::Dialog::welcome().device_kind_next();
/// ```
pub fn device_kind_next (&self) -> Option<usize> {
self.device_kind().map(|index|(index + 1) % device_kinds().len())
}
/// FIXME: generalize
/// ```
/// let _ = tek::Dialog::welcome().device_kind_prev();
/// ```
pub fn device_kind_prev (&self) -> Option<usize> {
self.device_kind().map(|index|index.overflowing_sub(1).0.min(device_kinds().len().saturating_sub(1)))
}
/// FIXME: implement
pub fn message (&self) -> Option<&str> { todo!() }
/// FIXME: implement
pub fn browser (&self) -> Option<&Arc<Browse>> { todo!() }
/// FIXME: implement
pub fn browser_target (&self) -> Option<&BrowseTarget> { todo!() }
}
impl_audio!(App: tek_jack_process, tek_jack_event);
}
#[cfg(feature = "vst2")] impl<E: Engine> ::vst::host::Host for Plugin<E> {}
@ -1703,8 +1631,6 @@ impl AsRef<Arc<[MenuItem]>> for MenuItems { fn as_ref (&self) -> &Arc<[MenuItem]
impl<T: TracksView + ScenesView + Send + Sync> ClipsView for T {}
impl HasClipsSize for App { fn clips_size (&self) -> &Measure<TuiOut> { &self.project.size_inner } }
impl<'j> HasJack<'j> for Jack<'j> { fn jack (&self) -> &Jack<'j> { self } }
impl<'j> HasJack<'j> for &Jack<'j> { fn jack (&self) -> &Jack<'j> { self } }
impl HasJack<'static> for MidiInput { fn jack (&self) -> &Jack<'static> { &self.jack } }
impl HasJack<'static> for MidiOutput { fn jack (&self) -> &Jack<'static> { &self.jack } }
impl HasJack<'static> for AudioInput { fn jack (&self) -> &Jack<'static> { &self.jack } }
@ -1735,8 +1661,6 @@ impl<J: HasJack<'static>> RegisterPorts for J {
#[cfg(feature = "port")] impl_has!(Vec<MidiInput>: |self: Sequencer|self.midi_ins);
#[cfg(feature = "port")] impl_has!(Vec<MidiOutput>: |self: Sequencer|self.midi_outs);
audio!(App: tek_jack_process, tek_jack_event);
impl_has!(Jack<'static>: |self: App|self.jack);
impl_has!(Dialog: |self: App|self.dialog);
impl_has!(Measure<TuiOut>: |self: App|self.size);
@ -1838,141 +1762,6 @@ namespace!(App: Option<Arc<RwLock<MidiClip>>> {
};
});
mod draw {
use crate::*;
impl Draw<TuiOut> for MidiEditor {
fn draw (&self, to: &mut TuiOut) { self.content().draw(to) }
}
impl Draw<TuiOut> for PianoHorizontal {
fn draw (&self, to: &mut TuiOut) { self.content().draw(to) }
}
}
mod jack {
use crate::*;
/// Implement [Jack] constructor and methods
impl<'j> Jack<'j> {
/// Register new [Client] and wrap it for shared use.
pub fn new_run <T: HasJack<'j> + Audio + Send + Sync + 'static> (
name: &impl AsRef<str>,
init: impl FnOnce(Jack<'j>)->Usually<T>
) -> Usually<Arc<RwLock<T>>> {
Jack::new(name)?.run(init)
}
pub fn new (name: &impl AsRef<str>) -> Usually<Self> {
let client = Client::new(name.as_ref(), ClientOptions::NO_START_SERVER)?.0;
Ok(Jack(Arc::new(RwLock::new(JackState::Inactive(client)))))
}
pub fn run <T: HasJack<'j> + Audio + Send + Sync + 'static>
(self, init: impl FnOnce(Self)->Usually<T>) -> Usually<Arc<RwLock<T>>>
{
let client_state = self.0.clone();
let app: Arc<RwLock<T>> = Arc::new(RwLock::new(init(self)?));
let mut state = Activating;
std::mem::swap(&mut*client_state.write().unwrap(), &mut state);
if let Inactive(client) = state {
// 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.
let notify = JackNotify(Box::new({
let app = app.clone();
move|event|(&mut*app.write().unwrap()).handle(event)
}) 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 `app`'s `process` callback, which in turn
// implements audio and MIDI input and output on a realtime basis.
let process = ClosureProcessHandler::new(Box::new({
let app = app.clone();
move|c: &_, s: &_|if let Ok(mut app) = app.write() {
app.process(c, s)
} else {
Control::Quit
}
}) as BoxedAudioHandler);
// Launch a client with the two handlers.
*client_state.write().unwrap() = Active(
client.activate_async(notify, process)?
);
} else {
unreachable!();
}
Ok(app)
}
/// Run something with the client.
pub fn with_client <T> (&self, op: impl FnOnce(&Client)->T) -> T {
match &*self.0.read().unwrap() {
Inert => panic!("jack client not activated"),
Inactive(client) => op(client),
Activating => panic!("jack client has not finished activation"),
Active(client) => op(client.as_client()),
}
}
}
impl<T: Fn(JackEvent) + Send> NotificationHandler for JackNotify<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
}
}
impl JackPerfModel for PerfModel {
fn update_from_jack_scope (&self, t0: Option<u64>, scope: &ProcessScope) {
if let Some(t0) = t0 {
let t1 = self.clock.raw();
self.used.store(
self.clock.delta_as_nanos(t0, t1) as f64,
Relaxed,
);
self.window.store(
scope.cycle_times().unwrap().period_usecs as f64,
Relaxed,
);
}
}
}
}
mod time {
use crate::*;
impl Moment {
@ -3062,6 +2851,12 @@ mod audio {
}
}
}
impl Draw<TuiOut> for MidiEditor {
fn draw (&self, to: &mut TuiOut) { self.content().draw(to) }
}
impl Draw<TuiOut> for PianoHorizontal {
fn draw (&self, to: &mut TuiOut) { self.content().draw(to) }
}
}
#[cfg(feature = "sampler")] mod sampler {
@ -3565,7 +3360,7 @@ mod audio {
})
}
audio!(Sampler: sampler_jack_process);
impl_audio!(Sampler: sampler_jack_process);
pub(crate) fn sampler_jack_process (state: &mut Sampler, _: &Client, scope: &ProcessScope) -> Control {
if let Some(midi_in) = &state.midi_in {
for midi in midi_in.port().iter(scope) {
@ -3619,7 +3414,7 @@ mod audio {
use crate::*;
audio!(Lv2: lv2_jack_process);
impl_audio!(Lv2: lv2_jack_process);
impl Lv2 {
const INPUT_BUFFER: usize = 1024;
@ -4246,3 +4041,91 @@ mod config {
}
}
}
mod dialog {
use crate::*;
impl Dialog {
/// ```
/// let _ = tek::Dialog::welcome();
/// ```
pub fn welcome () -> Self {
Self::Menu(1, MenuItems([
MenuItem("Resume session".into(), Arc::new(Box::new(|_|Ok(())))),
MenuItem("Create new session".into(), Arc::new(Box::new(|app|Ok({
app.dialog = Dialog::None;
app.mode = app.config.modes.clone().read().unwrap().get(":arranger").cloned().unwrap();
})))),
MenuItem("Load old session".into(), Arc::new(Box::new(|_|Ok(())))),
].into()))
}
/// FIXME: generalize
/// ```
/// let _ = tek::Dialog::welcome().menu_selected();
/// ```
pub fn menu_selected (&self) -> Option<usize> {
if let Self::Menu(selected, _) = self { Some(*selected) } else { None }
}
/// FIXME: generalize
/// ```
/// let _ = tek::Dialog::welcome().menu_next();
/// ```
pub fn menu_next (&self) -> Self {
match self {
Self::Menu(index, items) => Self::Menu(wrap_inc(*index, items.0.len()), items.clone()),
_ => Self::None
}
}
/// FIXME: generalize
/// ```
/// let _ = tek::Dialog::welcome().menu_prev();
/// ```
pub fn menu_prev (&self) -> Self {
match self {
Self::Menu(index, items) => Self::Menu(wrap_dec(*index, items.0.len()), items.clone()),
_ => Self::None
}
}
/// FIXME: generalize
/// ```
/// let _ = tek::Dialog::welcome().device_kind();
/// ```
pub fn device_kind (&self) -> Option<usize> {
if let Self::Device(index) = self { Some(*index) } else { None }
}
/// FIXME: generalize
/// ```
/// let _ = tek::Dialog::welcome().device_kind_next();
/// ```
pub fn device_kind_next (&self) -> Option<usize> {
self.device_kind().map(|index|(index + 1) % device_kinds().len())
}
/// FIXME: generalize
/// ```
/// let _ = tek::Dialog::welcome().device_kind_prev();
/// ```
pub fn device_kind_prev (&self) -> Option<usize> {
self.device_kind().map(|index|index.overflowing_sub(1).0.min(device_kinds().len().saturating_sub(1)))
}
/// FIXME: implement
pub fn message (&self) -> Option<&str> { todo!() }
/// FIXME: implement
pub fn browser (&self) -> Option<&Arc<Browse>> { todo!() }
/// FIXME: implement
pub fn browser_target (&self) -> Option<&BrowseTarget> { todo!() }
}
}
impl_audio!(|self: DeviceAudio<'a>, client, scope|{
use Device::*;
match self.0 {
Mute => { Control::Continue },
Bypass => { /*TODO*/ Control::Continue },
#[cfg(feature = "sampler")] Sampler(sampler) => sampler.process(client, scope),
#[cfg(feature = "lv2")] Lv2(lv2) => lv2.process(client, scope),
#[cfg(feature = "vst2")] Vst2 => { todo!() }, // TODO
#[cfg(feature = "vst3")] Vst3 => { todo!() }, // TODO
#[cfg(feature = "clap")] Clap => { todo!() }, // TODO
#[cfg(feature = "sf2")] Sf2 => { todo!() }, // TODO
}
});

View file

@ -2,58 +2,6 @@ use crate::*;
use clap::{self, Parser, Subcommand};
use builder_pattern::Builder;
/// Wraps [JackState], and through it [jack::Client] when connected.
///
/// ```
/// let jack = tek::Jack::default();
/// ```
#[derive(Clone, Debug, Default)] pub struct Jack<'j> (
pub(crate) Arc<RwLock<JackState<'j>>>
);
/// This is a connection which may be [Inactive], [Activating], or [Active].
/// In the [Active] and [Inactive] states, [JackState::client] returns a
/// [jack::Client], which you can use to talk to the JACK API.
///
/// ```
/// let state = tek::JackState::default();
/// ```
#[derive(Debug, Default)] pub enum JackState<'j> {
/// Unused
#[default] Inert,
/// Before activation.
Inactive(Client),
/// During activation.
Activating,
/// After activation. Must not be dropped for JACK thread to persist.
Active(DynamicAsyncClient<'j>),
}
/// Event enum for JACK events.
///
/// ```
/// let event = tek::JackEvent::XRun;
/// ```
#[derive(Debug, Clone, PartialEq)] 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]
///
/// ```
/// let notify = tek::JackNotify(|_|{});
/// ```
pub struct JackNotify<T: Fn(JackEvent) + Send>(pub T);
/// Total state
///
/// ```

View file

@ -1,125 +1,6 @@
use crate::*;
use std::sync::atomic::Ordering;
/// Things that can provide a [jack::Client] reference.
///
/// ```
/// use tek::{Jack, HasJack};
///
/// let jack: &Jack = Jacked::default().jack();
///
/// #[derive(Default)] struct Jacked<'j>(Jack<'j>);
///
/// impl<'j> tek::HasJack<'j> for Jacked<'j> {
/// fn jack (&self) -> &Jack<'j> { &self.0 }
/// }
/// ```
pub trait HasJack<'j>: Send + Sync {
/// Return the internal [jack::Client] handle
/// that lets you call the JACK API.
fn jack (&self) -> &Jack<'j>;
fn with_client <T> (&self, op: impl FnOnce(&Client)->T) -> T {
self.jack().with_client(op)
}
fn port_by_name (&self, name: &str) -> Option<Port<Unowned>> {
self.with_client(|client|client.port_by_name(name))
}
fn port_by_id (&self, id: u32) -> Option<Port<Unowned>> {
self.with_client(|c|c.port_by_id(id))
}
fn register_port <PS: PortSpec + Default> (&self, name: impl AsRef<str>) -> Usually<Port<PS>> {
self.with_client(|client|Ok(client.register_port(name.as_ref(), PS::default())?))
}
fn sync_lead (&self, enable: bool, callback: impl Fn(TimebaseInfo)->Position) -> Usually<()> {
if enable {
self.with_client(|client|match client.register_timebase_callback(false, callback) {
Ok(_) => Ok(()),
Err(e) => Err(e)
})?
}
Ok(())
}
fn sync_follow (&self, _enable: bool) -> Usually<()> {
// TODO: sync follow
Ok(())
}
}
/// Trait for thing that has a JACK process callback.
pub trait Audio {
/// Handle a JACK event.
fn handle (&mut self, _event: JackEvent) {}
/// Projecss a JACK chunk.
fn process (&mut self, _: &Client, _: &ProcessScope) -> Control {
Control::Continue
}
/// The JACK process callback function passed to the server.
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
}
}
}
/// Implement [Audio]: provide JACK callbacks.
#[macro_export] macro_rules! audio {
(|
$self1:ident:
$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?,$c:ident,$s:ident
|$cb:expr$(;|$self2:ident,$e:ident|$cb2:expr)?) => {
impl $(<$($L),*$($T $(: $U)?),*>)? Audio for $Struct $(<$($L),*$($T),*>)? {
#[inline] fn process (&mut $self1, $c: &Client, $s: &ProcessScope) -> Control { $cb }
$(#[inline] fn handle (&mut $self2, $e: JackEvent) { $cb2 })?
}
};
($Struct:ident: $process:ident, $handle:ident) => {
impl Audio for $Struct {
#[inline] fn process (&mut self, c: &Client, s: &ProcessScope) -> Control {
$process(self, c, s)
}
#[inline] fn handle (&mut self, e: JackEvent) {
$handle(self, e)
}
}
};
($Struct:ident: $process:ident) => {
impl Audio for $Struct {
#[inline] fn process (&mut self, c: &Client, s: &ProcessScope) -> Control {
$process(self, c, s)
}
}
};
}
pub trait JackPerfModel {
fn update_from_jack_scope (&self, t0: Option<u64>, scope: &ProcessScope);
}
pub trait HasN<T>: Send + Sync {
fn get_nth (&self, key: usize) -> &T;
fn get_nth_mut (&mut self, key: usize) -> &mut T;
}
pub trait Gettable<T> {
/// Returns current value
fn get (&self) -> T;
@ -249,7 +130,7 @@ pub trait HasClock: AsRef<Clock> + AsMut<Clock> {
}
pub trait HasDevices: AsRef<Vec<Device>> + AsMut<Vec<Device>> {
fn devices_mut (&mut self) -> &mut Vec<Device> { self.as_mut() }
fn devices (&self) -> &Vec<Device> { self.as_reF() }
fn devices (&self) -> &Vec<Device> { self.as_ref() }
}
pub trait HasSelection: AsRef<Selection> + AsMut<Selection> {
fn selection_mut (&mut self) -> &mut Selection { self.as_mut() }
@ -264,7 +145,7 @@ pub trait HasScene: AsRef<Option<Scene>> + AsMut<Option<Scene>> {
fn scene (&self) -> Option<&Scene> { self.as_ref() }
}
pub trait HasScenes: AsRef<Vec<Scene>> + AsMut<Vec<Scene>> {
fn scenes (&self) -> &Vec<Scene> { self.as_reF() }
fn scenes (&self) -> &Vec<Scene> { self.as_ref() }
fn scenes_mut (&mut self) -> &mut Vec<Scene> { self.as_mut() }
/// Generate the default name for a new scene
fn scene_default_name (&self) -> Arc<str> { format!("s{:3>}", self.scenes().len() + 1).into() }

View file

@ -1,31 +1,5 @@
use crate::*;
/// Running JACK [AsyncClient] with maximum type erasure.
///
/// One [Box] contains function that handles [JackEvent]s.
///
/// Another [Box] containing a function that handles realtime IO.
///
/// That's all it knows about them.
pub type DynamicAsyncClient<'j>
= AsyncClient<DynamicNotifications<'j>, DynamicAudioHandler<'j>>;
/// Notification handler wrapper for [BoxedAudioHandler].
pub type DynamicAudioHandler<'j> =
ClosureProcessHandler<(), BoxedAudioHandler<'j>>;
/// Boxed realtime callback.
pub type BoxedAudioHandler<'j> =
Box<dyn FnMut(&Client, &ProcessScope) -> Control + Send + Sync + 'j>;
/// Notification handler wrapper for [BoxedJackEventHandler].
pub type DynamicNotifications<'j> =
JackNotify<BoxedJackEventHandler<'j>>;
/// Boxed [JackEvent] callback.
pub type BoxedJackEventHandler<'j> =
Box<dyn Fn(JackEvent) + Send + Sync + 'j>;
pub type MidiData =
Vec<Vec<MidiMessage>>;