mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-03-13 11:50:44 +01:00
move the jack stuff into tengri
This commit is contained in:
parent
9ae35830c3
commit
6295f2e601
12 changed files with 107 additions and 440 deletions
4
.gitmodules
vendored
4
.gitmodules
vendored
|
|
@ -6,8 +6,8 @@
|
|||
path = tengri
|
||||
url = https://codeberg.org/unspeaker/tengri
|
||||
[submodule "deps/rust-jack"]
|
||||
path = rust-jack
|
||||
path = tengri/rust-jack
|
||||
url = https://codeberg.org/unspeaker/rust-jack
|
||||
[submodule "deps/dizzle"]
|
||||
path = dizzle
|
||||
path = tengri/dizzle
|
||||
url = ssh://git@codeberg.org/unspeaker/dizzle.git
|
||||
|
|
|
|||
3
Cargo.lock
generated
3
Cargo.lock
generated
|
|
@ -2992,9 +2992,7 @@ dependencies = [
|
|||
"builder-pattern",
|
||||
"bumpalo",
|
||||
"clap",
|
||||
"dizzle",
|
||||
"gtk",
|
||||
"jack",
|
||||
"konst",
|
||||
"livi",
|
||||
"midly",
|
||||
|
|
@ -3035,6 +3033,7 @@ dependencies = [
|
|||
"better-panic",
|
||||
"crossterm 0.29.0",
|
||||
"dizzle",
|
||||
"jack",
|
||||
"palette",
|
||||
"quanta",
|
||||
"rand 0.8.5",
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@ rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
|||
|
||||
[dependencies]
|
||||
tengri = { path = "./tengri", features = [ "tui", "dsl" ] }
|
||||
dizzle = { path = "./dizzle" }
|
||||
jack = { path = "./rust-jack" }
|
||||
|
||||
ansi_term = { version = "0.12.1" }
|
||||
atomic_float = { version = "1.0.0" }
|
||||
|
|
|
|||
22
app/tek.rs
22
app/tek.rs
|
|
@ -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),*>)? {
|
||||
|
|
|
|||
311
app/tek_impls.rs
311
app/tek_impls.rs
|
|
@ -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
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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
|
||||
///
|
||||
/// ```
|
||||
|
|
|
|||
123
app/tek_trait.rs
123
app/tek_trait.rs
|
|
@ -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() }
|
||||
|
|
|
|||
|
|
@ -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>>;
|
||||
|
||||
|
|
|
|||
1
dizzle
1
dizzle
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 909b94cbd4beffb49be314724dc79db9374bcc99
|
||||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 764a38a880ab4749ea60aa7e53cd814b858e606c
|
||||
2
tengri
2
tengri
|
|
@ -1 +1 @@
|
|||
Subproject commit 5d0dc40fdcd7cc022d1e468d9bf59de722949ace
|
||||
Subproject commit c1b727bafc27dc715d7239a0bc63a1cdfe962bc2
|
||||
Loading…
Add table
Add a link
Reference in a new issue