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
|
path = tengri
|
||||||
url = https://codeberg.org/unspeaker/tengri
|
url = https://codeberg.org/unspeaker/tengri
|
||||||
[submodule "deps/rust-jack"]
|
[submodule "deps/rust-jack"]
|
||||||
path = rust-jack
|
path = tengri/rust-jack
|
||||||
url = https://codeberg.org/unspeaker/rust-jack
|
url = https://codeberg.org/unspeaker/rust-jack
|
||||||
[submodule "deps/dizzle"]
|
[submodule "deps/dizzle"]
|
||||||
path = dizzle
|
path = tengri/dizzle
|
||||||
url = ssh://git@codeberg.org/unspeaker/dizzle.git
|
url = ssh://git@codeberg.org/unspeaker/dizzle.git
|
||||||
|
|
|
||||||
3
Cargo.lock
generated
3
Cargo.lock
generated
|
|
@ -2992,9 +2992,7 @@ dependencies = [
|
||||||
"builder-pattern",
|
"builder-pattern",
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"clap",
|
"clap",
|
||||||
"dizzle",
|
|
||||||
"gtk",
|
"gtk",
|
||||||
"jack",
|
|
||||||
"konst",
|
"konst",
|
||||||
"livi",
|
"livi",
|
||||||
"midly",
|
"midly",
|
||||||
|
|
@ -3035,6 +3033,7 @@ dependencies = [
|
||||||
"better-panic",
|
"better-panic",
|
||||||
"crossterm 0.29.0",
|
"crossterm 0.29.0",
|
||||||
"dizzle",
|
"dizzle",
|
||||||
|
"jack",
|
||||||
"palette",
|
"palette",
|
||||||
"quanta",
|
"quanta",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,6 @@ rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tengri = { path = "./tengri", features = [ "tui", "dsl" ] }
|
tengri = { path = "./tengri", features = [ "tui", "dsl" ] }
|
||||||
dizzle = { path = "./dizzle" }
|
|
||||||
jack = { path = "./rust-jack" }
|
|
||||||
|
|
||||||
ansi_term = { version = "0.12.1" }
|
ansi_term = { version = "0.12.1" }
|
||||||
atomic_float = { version = "1.0.0" }
|
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(crate) use ::xdg::BaseDirectories;
|
||||||
pub extern crate atomic_float;
|
pub extern crate atomic_float;
|
||||||
pub(crate) use atomic_float::AtomicF64;
|
pub(crate) use atomic_float::AtomicF64;
|
||||||
pub extern crate jack;
|
//pub extern crate jack;
|
||||||
pub(crate) use ::jack::{*, contrib::{*, ClosureProcessHandler}};
|
//pub(crate) use ::jack::{*, contrib::{*, ClosureProcessHandler}};
|
||||||
pub extern crate midly;
|
pub extern crate midly;
|
||||||
pub(crate) use ::midly::{Smf, TrackEventKind, MidiMessage, Error as MidiError, num::*, live::*};
|
pub(crate) use ::midly::{Smf, TrackEventKind, MidiMessage, Error as MidiError, num::*, live::*};
|
||||||
pub extern crate tengri;
|
pub extern crate tengri;
|
||||||
|
|
@ -301,10 +301,10 @@ pub fn tek_print_status (project: &Arrangement) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return boxed iterator of MIDI events
|
/// 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<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,
|
time as usize,
|
||||||
LiveEvent::parse(bytes).unwrap(),
|
LiveEvent::parse(bytes).unwrap(),
|
||||||
bytes
|
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()
|
//take!(DeviceCommand|state: Arrangement, iter|state.selected_device().as_ref()
|
||||||
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
||||||
|
|
||||||
|
|
||||||
#[macro_export] macro_rules! has_clip {
|
#[macro_export] macro_rules! has_clip {
|
||||||
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
||||||
impl $(<$($L),*$($T $(: $U)?),*>)? HasMidiClip for $Struct $(<$($L),*$($T),*>)? {
|
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 {
|
impl_audio!(App: tek_jack_process, tek_jack_event);
|
||||||
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!() }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "vst2")] impl<E: Engine> ::vst::host::Host for Plugin<E> {}
|
#[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<T: TracksView + ScenesView + Send + Sync> ClipsView for T {}
|
||||||
impl HasClipsSize for App { fn clips_size (&self) -> &Measure<TuiOut> { &self.project.size_inner } }
|
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 MidiInput { fn jack (&self) -> &Jack<'static> { &self.jack } }
|
||||||
impl HasJack<'static> for MidiOutput { 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 } }
|
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<MidiInput>: |self: Sequencer|self.midi_ins);
|
||||||
#[cfg(feature = "port")] impl_has!(Vec<MidiOutput>: |self: Sequencer|self.midi_outs);
|
#[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!(Jack<'static>: |self: App|self.jack);
|
||||||
impl_has!(Dialog: |self: App|self.dialog);
|
impl_has!(Dialog: |self: App|self.dialog);
|
||||||
impl_has!(Measure<TuiOut>: |self: App|self.size);
|
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 {
|
mod time {
|
||||||
use crate::*;
|
use crate::*;
|
||||||
impl Moment {
|
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 {
|
#[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 {
|
pub(crate) fn sampler_jack_process (state: &mut Sampler, _: &Client, scope: &ProcessScope) -> Control {
|
||||||
if let Some(midi_in) = &state.midi_in {
|
if let Some(midi_in) = &state.midi_in {
|
||||||
for midi in midi_in.port().iter(scope) {
|
for midi in midi_in.port().iter(scope) {
|
||||||
|
|
@ -3619,7 +3414,7 @@ mod audio {
|
||||||
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
audio!(Lv2: lv2_jack_process);
|
impl_audio!(Lv2: lv2_jack_process);
|
||||||
|
|
||||||
impl Lv2 {
|
impl Lv2 {
|
||||||
const INPUT_BUFFER: usize = 1024;
|
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 clap::{self, Parser, Subcommand};
|
||||||
use builder_pattern::Builder;
|
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
|
/// Total state
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|
|
||||||
123
app/tek_trait.rs
123
app/tek_trait.rs
|
|
@ -1,125 +1,6 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use std::sync::atomic::Ordering;
|
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> {
|
pub trait Gettable<T> {
|
||||||
/// Returns current value
|
/// Returns current value
|
||||||
fn get (&self) -> T;
|
fn get (&self) -> T;
|
||||||
|
|
@ -249,7 +130,7 @@ pub trait HasClock: AsRef<Clock> + AsMut<Clock> {
|
||||||
}
|
}
|
||||||
pub trait HasDevices: AsRef<Vec<Device>> + AsMut<Vec<Device>> {
|
pub trait HasDevices: AsRef<Vec<Device>> + AsMut<Vec<Device>> {
|
||||||
fn devices_mut (&mut self) -> &mut Vec<Device> { self.as_mut() }
|
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> {
|
pub trait HasSelection: AsRef<Selection> + AsMut<Selection> {
|
||||||
fn selection_mut (&mut self) -> &mut Selection { self.as_mut() }
|
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() }
|
fn scene (&self) -> Option<&Scene> { self.as_ref() }
|
||||||
}
|
}
|
||||||
pub trait HasScenes: AsRef<Vec<Scene>> + AsMut<Vec<Scene>> {
|
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() }
|
fn scenes_mut (&mut self) -> &mut Vec<Scene> { self.as_mut() }
|
||||||
/// Generate the default name for a new scene
|
/// Generate the default name for a new scene
|
||||||
fn scene_default_name (&self) -> Arc<str> { format!("s{:3>}", self.scenes().len() + 1).into() }
|
fn scene_default_name (&self) -> Arc<str> { format!("s{:3>}", self.scenes().len() + 1).into() }
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,5 @@
|
||||||
use crate::*;
|
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 =
|
pub type MidiData =
|
||||||
Vec<Vec<MidiMessage>>;
|
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