mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
This commit is contained in:
parent
573534a9a6
commit
447638ee71
30 changed files with 824 additions and 548 deletions
|
|
@ -112,10 +112,3 @@ impl AppCommand {
|
|||
//Ok(None)
|
||||
//}
|
||||
}
|
||||
|
||||
dsl!(ClockCommand: |self: App, iter|self.clock().take(iter));
|
||||
dsl!(MidiEditCommand: |self: App, iter|Ok(self.editor().map(|x|x.take(iter)).transpose()?.flatten()));
|
||||
dsl!(PoolCommand: |self: App, iter|self.pool.take(iter));
|
||||
dsl!(SamplerCommand: |self: App, iter|Ok(self.project.sampler().map(|x|x.take(iter)).transpose()?.flatten()));
|
||||
dsl!(ArrangementCommand: |self: App, iter|self.project.take(iter));
|
||||
dsl!(DialogCommand: |self: App, iter|Dsl::take(&self.dialog, iter));
|
||||
|
|
|
|||
|
|
@ -33,8 +33,8 @@ has!(Option<Dialog>: |self: App|self.dialog);
|
|||
has!(Clock: |self: App|self.project.clock);
|
||||
has!(Option<MidiEditor>: |self: App|self.project.editor);
|
||||
has!(Selection: |self: App|self.project.selection);
|
||||
has!(Vec<JackMidiIn>: |self: App|self.project.midi_ins);
|
||||
has!(Vec<JackMidiOut>: |self: App|self.project.midi_outs);
|
||||
has!(Vec<MidiInput>: |self: App|self.project.midi_ins);
|
||||
has!(Vec<MidiOutput>: |self: App|self.project.midi_outs);
|
||||
has!(Vec<Scene>: |self: App|self.project.scenes);
|
||||
has!(Vec<Track>: |self: App|self.project.tracks);
|
||||
has!(Measure<TuiOut>: |self: App|self.size);
|
||||
|
|
@ -54,6 +54,18 @@ has_clips!(|self: App|self.pool.clips);
|
|||
impl HasClipsSize for App {
|
||||
fn clips_size (&self) -> &Measure<TuiOut> { &self.project.inner_size }
|
||||
}
|
||||
from_dsl!(ClockCommand:
|
||||
|state: App, iter|FromDsl::take_from(state.clock(), iter));
|
||||
from_dsl!(MidiEditCommand:
|
||||
|state: App, iter|Ok(state.editor().map(|x|FromDsl::take_from(x, iter)).transpose()?.flatten()));
|
||||
from_dsl!(PoolCommand:
|
||||
|state: App, iter|FromDsl::take_from(&state.pool, iter));
|
||||
from_dsl!(SamplerCommand:
|
||||
|state: App, iter|Ok(state.project.sampler().map(|x|FromDsl::take_from(x, iter)).transpose()?.flatten()));
|
||||
from_dsl!(ArrangementCommand:
|
||||
|state: App, iter|FromDsl::take_from(&state.project, iter));
|
||||
from_dsl!(DialogCommand:
|
||||
|state: App, iter|FromDsl::take_from(&state.dialog, iter));
|
||||
//has_editor!(|self: App|{
|
||||
//editor = self.editor;
|
||||
//editor_w = {
|
||||
|
|
@ -332,7 +344,7 @@ pub struct Configuration {
|
|||
/// View definition
|
||||
pub view: TokenIter<'static>,
|
||||
// Input keymap
|
||||
pub keys: InputMap<App, AppCommand, TuiIn, TokenIter<'static>>,
|
||||
pub keys: InputMap<'static, App, AppCommand, TuiIn, TokenIter<'static>>,
|
||||
}
|
||||
|
||||
impl Configuration {
|
||||
|
|
@ -406,7 +418,7 @@ impl Configuration {
|
|||
}
|
||||
|
||||
fn parse_keys (base: &impl AsRef<Path>, iter: Option<TokenIter<'static>>)
|
||||
-> Usually<InputMap<App, AppCommand, TuiIn, TokenIter<'static>>>
|
||||
-> Usually<InputMap<'static, App, AppCommand, TuiIn, TokenIter<'static>>>
|
||||
{
|
||||
if iter.is_none() {
|
||||
return Err(format!("missing keys definition").into())
|
||||
|
|
@ -452,7 +464,7 @@ impl Configuration {
|
|||
let cond = cond.unwrap();
|
||||
println!("ok");
|
||||
map.add_layer_if(
|
||||
Box::new(move |state: &App|Dsl::take_or_fail(
|
||||
Box::new(move |state: &App|FromDsl::take_from_or_fail(
|
||||
state, &mut exp.clone(),
|
||||
format!("missing input layer conditional")
|
||||
)), keys
|
||||
|
|
|
|||
|
|
@ -4,22 +4,30 @@ pub(crate) use ::tengri::tui::ratatui::prelude::Position;
|
|||
|
||||
impl App {
|
||||
pub fn view (&self) -> impl Content<TuiOut> + '_ {
|
||||
let view: Perhaps<RenderBox<TuiOut>> =
|
||||
let view: Perhaps<Box<dyn Render<TuiOut>>> =
|
||||
FromDsl::take_from(self, &mut self.config.view.clone());
|
||||
self.size.of(lay! {
|
||||
When(matches!(&view, Ok(None)), Fill::y(Bsp::s(
|
||||
Fixed::y(1, "no view"),
|
||||
Fill::y(format!("{:?}", &self.config.view)),
|
||||
))),
|
||||
When(matches!(&view, Err(_)), Fill::y(Bsp::s(
|
||||
Fixed::y(1, "view error"),
|
||||
Fill::y(Bsp::s(
|
||||
view.as_ref().err().map(|e|format!("{e}")),
|
||||
format!("{:?}", &self.config.view),
|
||||
))
|
||||
))),
|
||||
When(matches!(&view, Ok(Some(_))), Fill::y(view)),
|
||||
})
|
||||
Either(view.is_ok(),
|
||||
ThunkRender::new(move|to|if let Some(view) = view.as_ref().unwrap().as_ref() {
|
||||
Content::render(view, to)
|
||||
}),
|
||||
"error?!")
|
||||
//Fill::xy(self.size.of(col! {
|
||||
//Tui::bg(Rgb(72,72,0), When(matches!(&view, Err(_)), Bsp::s(
|
||||
//Fixed::y(1, "view error"),
|
||||
//Bsp::s(
|
||||
//view.as_ref().err().map(|e|format!("{e}")),
|
||||
//format!("{}", &self.config.view.0.0))))),
|
||||
//When(matches!(&view, Ok(None)), Bsp::s(
|
||||
//Tui::bg(Rgb(96,48,0),
|
||||
//Fixed::y(1, Fill::x(Align::w("no view returned, as defined by:")))),
|
||||
//Tui::bg(Rgb(72,32,0),
|
||||
//Fill::x(Stack::south(|add: &mut dyn FnMut(&dyn Render<TuiOut>)|{
|
||||
//for line in self.config.view.0.0.split('\n') {
|
||||
//add(&Fill::x(Align::w(line)));
|
||||
//}
|
||||
//}))))),
|
||||
//When(matches!(&view, Ok(Some(_))), &view.unwrap().unwrap()),
|
||||
//}))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -91,11 +91,11 @@ impl Cli {
|
|||
));
|
||||
Tui::new()?.run(&Jack::new(name)?.run(|jack|{
|
||||
for (index, connect) in midi_froms.iter().enumerate() {
|
||||
let port = JackMidiIn::new(jack, &format!("M/{index}"), &[connect.clone()])?;
|
||||
let port = MidiInput::new(jack, &format!("M/{index}"), &[connect.clone()])?;
|
||||
midi_ins.push(port);
|
||||
}
|
||||
for (index, connect) in midi_tos.iter().enumerate() {
|
||||
let port = JackMidiOut::new(jack, &format!("{index}/M"), &[connect.clone()])?;
|
||||
let port = MidiOutput::new(jack, &format!("{index}/M"), &[connect.clone()])?;
|
||||
midi_outs.push(port);
|
||||
};
|
||||
let config = Configuration::new(&match self.mode {
|
||||
|
|
|
|||
|
|
@ -18,8 +18,8 @@ mod arranger_view; pub use self::arranger_view::*;
|
|||
|
||||
def_sizes_iter!(ScenesSizes => Scene);
|
||||
def_sizes_iter!(TracksSizes => Track);
|
||||
def_sizes_iter!(InputsSizes => JackMidiIn);
|
||||
def_sizes_iter!(OutputsSizes => JackMidiOut);
|
||||
def_sizes_iter!(InputsSizes => MidiInput);
|
||||
def_sizes_iter!(OutputsSizes => MidiOutput);
|
||||
def_sizes_iter!(PortsSizes => Arc<str>, [PortConnect]);
|
||||
|
||||
pub(crate) fn wrap (bg: Color, fg: Color, content: impl Content<TuiOut>) -> impl Content<TuiOut> {
|
||||
|
|
|
|||
|
|
@ -1,23 +1,4 @@
|
|||
use crate::*;
|
||||
|
||||
#[tengri_proc::expose]
|
||||
impl Arrangement {
|
||||
fn _todo_usize_stub_ (&self) -> usize { todo!() }
|
||||
fn _todo_arc_str_stub_ (&self) -> Arc<str> { todo!() }
|
||||
fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() }
|
||||
fn _todo_opt_item_theme_stub (&self) -> Option<ItemTheme> { todo!() }
|
||||
fn select_nothing (&self) -> Selection {
|
||||
Selection::Nothing
|
||||
}
|
||||
}
|
||||
|
||||
dsl!(TrackCommand: |self: Arrangement, iter|self.take(iter));
|
||||
dsl!(MidiInputCommand: |self: Arrangement, iter|self.take(iter));
|
||||
dsl!(MidiOutputCommand: |self: Arrangement, iter|self.take(iter));
|
||||
dsl!(DeviceCommand: |self: Arrangement, iter|self.take(iter));
|
||||
dsl!(SceneCommand: |self: Arrangement, iter|self.take(iter));
|
||||
dsl!(ClipCommand: |self: Arrangement, iter|self.take(iter));
|
||||
|
||||
#[tengri_proc::command(Arrangement)]
|
||||
impl ArrangementCommand {
|
||||
fn home (arranger: &mut Arrangement) -> Perhaps<Self> {
|
||||
|
|
@ -173,7 +154,7 @@ impl ArrangementCommand {
|
|||
Ok(None)
|
||||
}
|
||||
fn output_add (arranger: &mut Arrangement) -> Perhaps<Self> {
|
||||
arranger.midi_outs.push(JackMidiOut::new(
|
||||
arranger.midi_outs.push(MidiOutput::new(
|
||||
arranger.jack(),
|
||||
format!("/M{}", arranger.midi_outs.len() + 1),
|
||||
&[]
|
||||
|
|
@ -181,7 +162,7 @@ impl ArrangementCommand {
|
|||
Ok(None)
|
||||
}
|
||||
fn input_add (arranger: &mut Arrangement) -> Perhaps<Self> {
|
||||
arranger.midi_ins.push(JackMidiIn::new(
|
||||
arranger.midi_ins.push(MidiInput::new(
|
||||
arranger.jack(),
|
||||
format!("M{}/", arranger.midi_ins.len() + 1),
|
||||
&[]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,47 @@
|
|||
use crate::*;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Arrangement {
|
||||
has!(Jack: |self: Arrangement|self.jack);
|
||||
has!(Clock: |self: Arrangement|self.clock);
|
||||
has!(Selection: |self: Arrangement|self.selection);
|
||||
has!(Vec<MidiInput>: |self: Arrangement|self.midi_ins);
|
||||
has!(Vec<MidiOutput>: |self: Arrangement|self.midi_outs);
|
||||
has!(Vec<Scene>: |self: Arrangement|self.scenes);
|
||||
has!(Vec<Track>: |self: Arrangement|self.tracks);
|
||||
has!(Measure<TuiOut>: |self: Arrangement|self.size);
|
||||
has!(Option<MidiEditor>: |self: Arrangement|self.editor);
|
||||
maybe_has!(Track: |self: Arrangement|
|
||||
{ Has::<Selection>::get(self).track().map(|index|Has::<Vec<Track>>::get(self).get(index)).flatten() };
|
||||
{ Has::<Selection>::get(self).track().map(|index|Has::<Vec<Track>>::get_mut(self).get_mut(index)).flatten() });
|
||||
maybe_has!(Scene: |self: Arrangement|
|
||||
{ Has::<Selection>::get(self).track().map(|index|Has::<Vec<Scene>>::get(self).get(index)).flatten() };
|
||||
{ Has::<Selection>::get(self).track().map(|index|Has::<Vec<Scene>>::get_mut(self).get_mut(index)).flatten() });
|
||||
from_dsl!(MidiInputCommand: |state: Arrangement, iter|state.selected_midi_in().as_ref()
|
||||
.map(|t|FromDsl::take_from(t, iter)).transpose().map(|x|x.flatten()));
|
||||
from_dsl!(MidiOutputCommand: |state: Arrangement, iter|state.selected_midi_out().as_ref()
|
||||
.map(|t|FromDsl::take_from(t, iter)).transpose().map(|x|x.flatten()));
|
||||
from_dsl!(DeviceCommand: |state: Arrangement, iter|state.selected_device().as_ref()
|
||||
.map(|t|FromDsl::take_from(t, iter)).transpose().map(|x|x.flatten()));
|
||||
from_dsl!(TrackCommand: |state: Arrangement, iter|state.selected_track().as_ref()
|
||||
.map(|t|FromDsl::take_from(t, iter)).transpose().map(|x|x.flatten()));
|
||||
from_dsl!(SceneCommand: |state: Arrangement, iter|state.selected_scene().as_ref()
|
||||
.map(|t|FromDsl::take_from(t, iter)).transpose().map(|x|x.flatten()));
|
||||
from_dsl!(ClipCommand: |state: Arrangement, iter|state.selected_clip().as_ref()
|
||||
.map(|t|FromDsl::take_from(t, iter)).transpose().map(|x|x.flatten()));
|
||||
#[tengri_proc::expose] impl Arrangement {
|
||||
fn selected_midi_in (&self) -> Option<MidiInput> { todo!() }
|
||||
fn selected_midi_out (&self) -> Option<MidiOutput> { todo!() }
|
||||
fn selected_device (&self) -> Option<Device> { todo!() }
|
||||
fn selected_track (&self) -> Option<Track> { todo!() }
|
||||
fn selected_scene (&self) -> Option<Scene> { todo!() }
|
||||
fn selected_clip (&self) -> Option<MidiClip> { todo!() }
|
||||
fn _todo_usize_stub_ (&self) -> usize { todo!() }
|
||||
fn _todo_arc_str_stub_ (&self) -> Arc<str> { todo!() }
|
||||
fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() }
|
||||
fn _todo_opt_item_theme_stub (&self) -> Option<ItemTheme> { todo!() }
|
||||
fn select_nothing (&self) -> Selection {
|
||||
Selection::Nothing
|
||||
}
|
||||
}
|
||||
#[derive(Default, Debug)] pub struct Arrangement {
|
||||
/// Project name.
|
||||
pub name: Arc<str>,
|
||||
/// Base color.
|
||||
|
|
@ -13,13 +53,13 @@ pub struct Arrangement {
|
|||
/// Allows one MIDI clip to be edited
|
||||
pub editor: Option<MidiEditor>,
|
||||
/// List of global midi inputs
|
||||
pub midi_ins: Vec<JackMidiIn>,
|
||||
pub midi_ins: Vec<MidiInput>,
|
||||
/// List of global midi outputs
|
||||
pub midi_outs: Vec<JackMidiOut>,
|
||||
pub midi_outs: Vec<MidiOutput>,
|
||||
/// List of global audio inputs
|
||||
pub audio_ins: Vec<JackAudioIn>,
|
||||
pub audio_ins: Vec<AudioInput>,
|
||||
/// List of global audio outputs
|
||||
pub audio_outs: Vec<JackAudioOut>,
|
||||
pub audio_outs: Vec<AudioOutput>,
|
||||
/// Last track number (to avoid duplicate port names)
|
||||
pub track_last: usize,
|
||||
/// List of tracks
|
||||
|
|
@ -40,23 +80,6 @@ pub struct Arrangement {
|
|||
/// Display size of clips area
|
||||
pub inner_size: Measure<TuiOut>,
|
||||
}
|
||||
|
||||
has!(Jack: |self: Arrangement|self.jack);
|
||||
has!(Clock: |self: Arrangement|self.clock);
|
||||
has!(Selection: |self: Arrangement|self.selection);
|
||||
has!(Vec<JackMidiIn>: |self: Arrangement|self.midi_ins);
|
||||
has!(Vec<JackMidiOut>: |self: Arrangement|self.midi_outs);
|
||||
has!(Vec<Scene>: |self: Arrangement|self.scenes);
|
||||
has!(Vec<Track>: |self: Arrangement|self.tracks);
|
||||
has!(Measure<TuiOut>: |self: Arrangement|self.size);
|
||||
has!(Option<MidiEditor>: |self: Arrangement|self.editor);
|
||||
maybe_has!(Track: |self: Arrangement|
|
||||
{ Has::<Selection>::get(self).track().map(|index|Has::<Vec<Track>>::get(self).get(index)).flatten() };
|
||||
{ Has::<Selection>::get(self).track().map(|index|Has::<Vec<Track>>::get_mut(self).get_mut(index)).flatten() });
|
||||
maybe_has!(Scene: |self: Arrangement|
|
||||
{ Has::<Selection>::get(self).track().map(|index|Has::<Vec<Scene>>::get(self).get(index)).flatten() };
|
||||
{ Has::<Selection>::get(self).track().map(|index|Has::<Vec<Scene>>::get_mut(self).get_mut(index)).flatten() });
|
||||
|
||||
impl Arrangement {
|
||||
/// Width of display
|
||||
pub fn w (&self) -> u16 {
|
||||
|
|
|
|||
|
|
@ -21,11 +21,11 @@ pub struct Clock {
|
|||
/// Size of buffer in samples
|
||||
pub chunk: Arc<AtomicUsize>,
|
||||
/// For syncing the clock to an external source
|
||||
pub midi_in: Arc<RwLock<Option<JackMidiIn>>>,
|
||||
pub midi_in: Arc<RwLock<Option<MidiInput>>>,
|
||||
/// For syncing other devices to this clock
|
||||
pub midi_out: Arc<RwLock<Option<JackMidiOut>>>,
|
||||
pub midi_out: Arc<RwLock<Option<MidiOutput>>>,
|
||||
/// For emitting a metronome
|
||||
pub click_out: Arc<RwLock<Option<JackAudioOut>>>,
|
||||
pub click_out: Arc<RwLock<Option<AudioOutput>>>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Clock {
|
||||
|
|
@ -56,9 +56,9 @@ impl Clock {
|
|||
offset: Arc::new(Moment::zero(&timebase)),
|
||||
started: RwLock::new(None).into(),
|
||||
timebase,
|
||||
midi_in: Arc::new(RwLock::new(Some(JackMidiIn::new(jack, "M/clock", &[])?))),
|
||||
midi_out: Arc::new(RwLock::new(Some(JackMidiOut::new(jack, "clock/M", &[])?))),
|
||||
click_out: Arc::new(RwLock::new(Some(JackAudioOut::new(jack, "click", &[])?))),
|
||||
midi_in: Arc::new(RwLock::new(Some(MidiInput::new(jack, "M/clock", &[])?))),
|
||||
midi_out: Arc::new(RwLock::new(Some(MidiOutput::new(jack, "clock/M", &[])?))),
|
||||
click_out: Arc::new(RwLock::new(Some(AudioOutput::new(jack, "click", &[])?))),
|
||||
};
|
||||
if let Some(bpm) = bpm {
|
||||
clock.timebase.bpm.set(bpm);
|
||||
|
|
|
|||
|
|
@ -44,25 +44,25 @@ impl Device {
|
|||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
pub fn midi_ins (&self) -> &[JackMidiIn] {
|
||||
pub fn midi_ins (&self) -> &[MidiInput] {
|
||||
match self {
|
||||
//Self::Sampler(Sampler { midi_in, .. }) => &[midi_in],
|
||||
_ => todo!()
|
||||
}
|
||||
}
|
||||
pub fn midi_outs (&self) -> &[JackMidiOut] {
|
||||
pub fn midi_outs (&self) -> &[MidiOutput] {
|
||||
match self {
|
||||
Self::Sampler(_) => &[],
|
||||
_ => todo!()
|
||||
}
|
||||
}
|
||||
pub fn audio_ins (&self) -> &[JackAudioIn] {
|
||||
pub fn audio_ins (&self) -> &[AudioInput] {
|
||||
match self {
|
||||
Self::Sampler(Sampler { audio_ins, .. }) => audio_ins.as_slice(),
|
||||
_ => todo!()
|
||||
}
|
||||
}
|
||||
pub fn audio_outs (&self) -> &[JackAudioOut] {
|
||||
pub fn audio_outs (&self) -> &[AudioOutput] {
|
||||
match self {
|
||||
Self::Sampler(Sampler { audio_outs, .. }) => audio_outs.as_slice(),
|
||||
_ => todo!()
|
||||
|
|
|
|||
|
|
@ -1,19 +1,24 @@
|
|||
use crate::*;
|
||||
|
||||
content!(TuiOut: |self: Dialog| match self {
|
||||
Self::Menu(_) =>
|
||||
self.view_dialog_menu().boxed(),
|
||||
Self::Help(offset) =>
|
||||
self.view_dialog_help(*offset).boxed(),
|
||||
Self::Browser(target, browser) =>
|
||||
self.view_dialog_browser(target, browser).boxed(),
|
||||
Self::Options =>
|
||||
self.view_dialog_options().boxed(),
|
||||
Self::Device(index) =>
|
||||
self.view_dialog_device(*index).boxed(),
|
||||
Self::Message(message) =>
|
||||
self.view_dialog_message(message).boxed(),
|
||||
});
|
||||
impl Content<TuiOut> for Dialog {
|
||||
fn content (&self) -> impl Render<TuiOut> + '_ {
|
||||
Some(match self {
|
||||
Self::Menu(_) => self.view_dialog_menu().boxed(),
|
||||
_ => "kyp".boxed()
|
||||
})
|
||||
//Self::Help(offset) =>
|
||||
//self.view_dialog_help(*offset).boxed(),
|
||||
//Self::Browser(target, browser) =>
|
||||
//self.view_dialog_browser(target, browser).boxed(),
|
||||
//Self::Options =>
|
||||
//self.view_dialog_options().boxed(),
|
||||
//Self::Device(index) =>
|
||||
//self.view_dialog_device(*index).boxed(),
|
||||
//Self::Message(message) =>
|
||||
//self.view_dialog_message(message).boxed(),
|
||||
//})
|
||||
}
|
||||
}
|
||||
|
||||
content!(TuiOut: |self: Message| match self {
|
||||
Self::FailedToAddDevice => "Failed to add device."
|
||||
|
|
@ -25,7 +30,7 @@ impl Dialog {
|
|||
let option = |a,i|Tui::fg(Rgb(255,255,255), format!("{}", a));
|
||||
Bsp::s(Tui::bold(true, "tek!"), Bsp::s("", Map::south(1, options, option)))
|
||||
}
|
||||
pub fn view_dialog_help <'a> (&'a self, offset: usize) -> impl Content<TuiOut> + use<'a> {
|
||||
pub fn view_dialog_help <'a> (&'a self, offset: usize) -> impl Content<TuiOut> + 'a {
|
||||
Bsp::s(Tui::bold(true, "Help"), "FIXME")
|
||||
//Bsp::s(Tui::bold(true, "Help"), Bsp::s("", Map::south(1,
|
||||
//move||self.config.keys.layers.iter()
|
||||
|
|
|
|||
|
|
@ -81,9 +81,6 @@ impl PoolCommand {
|
|||
|
||||
}
|
||||
|
||||
dsl!(BrowserCommand: |self: Pool, iter|Ok(self.browser
|
||||
.as_ref().map(|p|p.take(iter)).transpose()?.flatten()));
|
||||
|
||||
#[tengri_proc::command(Pool)]
|
||||
impl PoolClipCommand {
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,11 @@ pub struct Pool {
|
|||
/// Embedded file browser
|
||||
pub browser: Option<Browser>,
|
||||
}
|
||||
|
||||
from_dsl!(BrowserCommand: |state: Pool, iter|Ok(state.browser
|
||||
.as_ref()
|
||||
.map(|p|FromDsl::take_from(p, iter))
|
||||
.transpose()?
|
||||
.flatten()));
|
||||
impl Default for Pool {
|
||||
fn default () -> Self {
|
||||
use PoolMode::*;
|
||||
|
|
|
|||
|
|
@ -63,21 +63,17 @@ impl SamplerCommand {
|
|||
fn record_begin (sampler: &mut Sampler, slot: usize) -> Perhaps<Self> {
|
||||
sampler.recording = Some((
|
||||
slot,
|
||||
Arc::new(RwLock::new(Sample::new(
|
||||
Some(Arc::new(RwLock::new(Sample::new(
|
||||
"Sample", 0, 0, vec![vec![];sampler.audio_ins.len()]
|
||||
)))
|
||||
))))
|
||||
));
|
||||
Ok(None)
|
||||
}
|
||||
fn record_finish (sampler: &mut Sampler) -> Perhaps<Self> {
|
||||
let recording = sampler.recording.take();
|
||||
let _sample = if let Some((index, sample)) = recording {
|
||||
let old = sampler.mapped[index].clone();
|
||||
sampler.mapped[index] = Some(sample);
|
||||
old
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let _prev_sample = sampler.recording.as_mut().map(|(index, sample)|{
|
||||
std::mem::swap(sample, &mut sampler.mapped[*index]);
|
||||
sample
|
||||
}); // TODO: undo
|
||||
Ok(None)
|
||||
}
|
||||
fn record_cancel (sampler: &mut Sampler) -> Perhaps<Self> {
|
||||
|
|
|
|||
|
|
@ -28,9 +28,8 @@ impl Sampler {
|
|||
|
||||
/// Record from inputs to sample
|
||||
fn record_into (&mut self, scope: &ProcessScope) {
|
||||
let mut sample = self.recording
|
||||
.as_mut().expect("no recording sample").1
|
||||
.write().unwrap();
|
||||
if let Some(ref sample) = self.recording.as_ref().expect("no recording sample").1 {
|
||||
let mut sample = sample.write().unwrap();
|
||||
if sample.channels.len() != self.audio_ins.len() {
|
||||
panic!("channel count mismatch");
|
||||
}
|
||||
|
|
@ -45,6 +44,9 @@ impl Sampler {
|
|||
channel.extend_from_slice(slice);
|
||||
}
|
||||
sample.end += length;
|
||||
} else {
|
||||
panic!("tried to record into the void")
|
||||
}
|
||||
}
|
||||
|
||||
/// Update input meters
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ pub struct Sampler {
|
|||
/// Device color.
|
||||
pub color: ItemTheme,
|
||||
/// Audio input ports. Samples get recorded here.
|
||||
pub audio_ins: Vec<JackAudioIn>,
|
||||
pub audio_ins: Vec<AudioInput>,
|
||||
/// Audio input meters.
|
||||
pub input_meters: Vec<f32>,
|
||||
/// Sample currently being recorded.
|
||||
pub recording: Option<(usize, Arc<RwLock<Sample>>)>,
|
||||
pub recording: Option<(usize, Option<Arc<RwLock<Sample>>>)>,
|
||||
/// Recording buffer.
|
||||
pub buffer: Vec<Vec<f32>>,
|
||||
/// Samples mapped to MIDI notes.
|
||||
|
|
@ -22,11 +22,11 @@ pub struct Sampler {
|
|||
/// Sample currently being edited.
|
||||
pub editing: Option<Arc<RwLock<Sample>>>,
|
||||
/// MIDI input port. Triggers sample playback.
|
||||
pub midi_in: JackMidiIn,
|
||||
pub midi_in: MidiInput,
|
||||
/// Collection of currently playing instances of samples.
|
||||
pub voices: Arc<RwLock<Vec<Voice>>>,
|
||||
/// Audio output ports. Voices get played here.
|
||||
pub audio_outs: Vec<JackAudioOut>,
|
||||
pub audio_outs: Vec<AudioOutput>,
|
||||
/// Audio output meters.
|
||||
pub output_meters: Vec<f32>,
|
||||
/// How to mix the voices.
|
||||
|
|
@ -58,14 +58,14 @@ impl Sampler {
|
|||
let name = name.as_ref();
|
||||
Ok(Self {
|
||||
name: name.into(),
|
||||
midi_in: JackMidiIn::new(jack, format!("M/{name}"), midi_from)?,
|
||||
midi_in: MidiInput::new(jack, format!("M/{name}"), midi_from)?,
|
||||
audio_ins: vec![
|
||||
JackAudioIn::new(jack, &format!("L/{name}"), audio_from[0])?,
|
||||
JackAudioIn::new(jack, &format!("R/{name}"), audio_from[1])?,
|
||||
AudioInput::new(jack, &format!("L/{name}"), audio_from[0])?,
|
||||
AudioInput::new(jack, &format!("R/{name}"), audio_from[1])?,
|
||||
],
|
||||
audio_outs: vec![
|
||||
JackAudioOut::new(jack, &format!("{name}/L"), audio_to[0])?,
|
||||
JackAudioOut::new(jack, &format!("{name}/R"), audio_to[1])?,
|
||||
AudioOutput::new(jack, &format!("{name}/L"), audio_to[0])?,
|
||||
AudioOutput::new(jack, &format!("{name}/R"), audio_to[1])?,
|
||||
],
|
||||
input_meters: vec![0.0;2],
|
||||
output_meters: vec![0.0;2],
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ impl Sampler {
|
|||
|
||||
pub fn view_sample (&self, note_pt: usize) -> impl Content<TuiOut> + use<'_> {
|
||||
Outer(true, Style::default().fg(Tui::g(96)))
|
||||
.enclose(Fill::xy(draw_viewer(if let Some((_, sample)) = &self.recording {
|
||||
.enclose(Fill::xy(draw_viewer(if let Some((_, Some(sample))) = &self.recording {
|
||||
Some(sample)
|
||||
} else if let Some(sample) = &self.mapped[note_pt] {
|
||||
Some(sample)
|
||||
|
|
@ -109,7 +109,7 @@ impl Sampler {
|
|||
}
|
||||
|
||||
pub fn view_sample_info (&self, note_pt: usize) -> impl Content<TuiOut> + use<'_> {
|
||||
Fill::x(Fixed::y(1, draw_info(if let Some((_, sample)) = &self.recording {
|
||||
Fill::x(Fixed::y(1, draw_info(if let Some((_, Some(sample))) = &self.recording {
|
||||
Some(sample)
|
||||
} else if let Some(sample) = &self.mapped[note_pt] {
|
||||
Some(sample)
|
||||
|
|
@ -119,7 +119,7 @@ impl Sampler {
|
|||
}
|
||||
|
||||
pub fn view_sample_status (&self, note_pt: usize) -> impl Content<TuiOut> + use<'_> {
|
||||
Fixed::x(20, draw_info_v(if let Some((_, sample)) = &self.recording {
|
||||
Fixed::x(20, draw_info_v(if let Some((_, Some(sample))) = &self.recording {
|
||||
Some(sample)
|
||||
} else if let Some(sample) = &self.mapped[note_pt] {
|
||||
Some(sample)
|
||||
|
|
|
|||
|
|
@ -32,9 +32,9 @@ pub struct Sequencer {
|
|||
/// Send all notes off
|
||||
pub reset: bool, // TODO?: after Some(nframes)
|
||||
/// Record from MIDI ports to current sequence.
|
||||
pub midi_ins: Vec<JackMidiIn>,
|
||||
pub midi_ins: Vec<MidiInput>,
|
||||
/// Play from current sequence to MIDI ports
|
||||
pub midi_outs: Vec<JackMidiOut>,
|
||||
pub midi_outs: Vec<MidiOutput>,
|
||||
/// Notes currently held at input
|
||||
pub notes_in: Arc<RwLock<[bool; 128]>>,
|
||||
/// Notes currently held at output
|
||||
|
|
@ -79,8 +79,8 @@ impl Sequencer {
|
|||
let _name = name.as_ref();
|
||||
let clock = clock.cloned().unwrap_or_default();
|
||||
Ok(Self {
|
||||
midi_ins: vec![JackMidiIn::new(jack, format!("M/{}", name.as_ref()), midi_from)?,],
|
||||
midi_outs: vec![JackMidiOut::new(jack, format!("{}/M", name.as_ref()), midi_to)?, ],
|
||||
midi_ins: vec![MidiInput::new(jack, format!("M/{}", name.as_ref()), midi_from)?,],
|
||||
midi_outs: vec![MidiOutput::new(jack, format!("{}/M", name.as_ref()), midi_to)?, ],
|
||||
play_clip: clip.map(|clip|(Moment::zero(&clock.timebase), Some(clip.clone()))),
|
||||
clock,
|
||||
reset: true,
|
||||
|
|
@ -102,8 +102,8 @@ impl std::fmt::Debug for Sequencer {
|
|||
}
|
||||
|
||||
has!(Clock: |self: Sequencer|self.clock);
|
||||
has!(Vec<JackMidiIn>: |self:Sequencer| self.midi_ins);
|
||||
has!(Vec<JackMidiOut>: |self:Sequencer| self.midi_outs);
|
||||
has!(Vec<MidiInput>: |self:Sequencer| self.midi_ins);
|
||||
has!(Vec<MidiOutput>: |self:Sequencer| self.midi_outs);
|
||||
|
||||
impl MidiMonitor for Sequencer {
|
||||
fn notes_in (&self) -> &Arc<RwLock<[bool; 128]>> {
|
||||
|
|
|
|||
2
crates/engine/src/audio.rs
Normal file
2
crates/engine/src/audio.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
mod audio_in; pub use self::audio_in::*;
|
||||
mod audio_out; pub use self::audio_out::*;
|
||||
86
crates/engine/src/audio/audio_in.rs
Normal file
86
crates/engine/src/audio/audio_in.rs
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
use crate::*;
|
||||
|
||||
//impl_port!(AudioInput: AudioOut -> AudioIn |j, n|j.register_port::<AudioOut>(n));
|
||||
|
||||
#[derive(Debug)] pub struct AudioInput<'j> {
|
||||
/// Handle to JACK client, for receiving reconnect events.
|
||||
jack: Jack<'j>,
|
||||
/// Port name
|
||||
name: Arc<str>,
|
||||
/// Port handle.
|
||||
port: Port<AudioOut>,
|
||||
/// List of ports to connect to.
|
||||
connections: Vec<PortConnect>
|
||||
}
|
||||
impl<'j> AsRef<Port<AudioOut>> for AudioInput<'j> {
|
||||
fn as_ref (&self) -> &Port<AudioOut> { &self.port }
|
||||
}
|
||||
impl<'j> AudioInput<'j> {
|
||||
pub fn new (jack: &Jack, name: impl AsRef<str>, connect: &[PortConnect])
|
||||
-> Usually<Self>
|
||||
{
|
||||
let port = Self {
|
||||
port: jack.register_port::<AudioIn>(name.as_ref())?,
|
||||
jack,
|
||||
name: name.into(),
|
||||
connections: connect.to_vec()
|
||||
};
|
||||
port.connect_to_matching()?;
|
||||
Ok(port)
|
||||
}
|
||||
pub fn name (&self) -> &Arc<str> { &self.name }
|
||||
pub fn port (&self) -> &Port<AudioOut> { &self.port }
|
||||
pub fn port_mut (&mut self) -> &mut Port<AudioOut> { &mut self.port }
|
||||
pub fn into_port (self) -> Port<AudioOut> { self.port }
|
||||
pub fn close (self) -> Usually<()> {
|
||||
let Self { jack, port, .. } = self;
|
||||
Ok(jack.with_client(|client|client.unregister_port(port))?)
|
||||
}
|
||||
}
|
||||
impl<'j> HasJack<'j> for AudioInput<'j> {
|
||||
fn jack (&self) -> &'j Jack<'j> { &self.jack }
|
||||
}
|
||||
impl<'j> JackPort<'j> for AudioInput<'j> {
|
||||
type Port = AudioIn;
|
||||
type Pair = AudioOut;
|
||||
fn port (&self) -> &Port<AudioOut> { &self.port }
|
||||
}
|
||||
//impl<'j, T: AsRef<str>> ConnectTo<'j, T> for AudioInput<'j> {
|
||||
//fn connect_to (&self, to: &T) -> Usually<PortConnectStatus> {
|
||||
//self.with_client(|c|if let Some(ref port) = c.port_by_name(to.as_ref()) {
|
||||
//self.connect_to(port)
|
||||
//} else {
|
||||
//Ok(Missing)
|
||||
//})
|
||||
//}
|
||||
//}
|
||||
connect_to!(<'j>|self: AudioInput<'j>, port: &str|{
|
||||
self.with_client(|c|if let Some(ref port) = c.port_by_name(port.as_ref()) {
|
||||
self.connect_to(port)
|
||||
} else {
|
||||
Ok(Missing)
|
||||
})
|
||||
});
|
||||
connect_to!(<'j>|self: AudioInput<'j>, port: Port<AudioOut>|{
|
||||
self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(&self.port, port) {
|
||||
Connected
|
||||
} else if let Ok(_) = c.connect_ports(port, &self.port) {
|
||||
Connected
|
||||
} else {
|
||||
Mismatch
|
||||
}))
|
||||
});
|
||||
connect_to!(<'j>|self: AudioInput<'j>, port: Port<Unowned>|{
|
||||
self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(&self.port, port) {
|
||||
Connected
|
||||
} else if let Ok(_) = c.connect_ports(port, &self.port) {
|
||||
Connected
|
||||
} else {
|
||||
Mismatch
|
||||
}))
|
||||
});
|
||||
impl<'j> ConnectAuto<'j> for AudioInput<'j> {
|
||||
fn connections (&self) -> &[PortConnect] {
|
||||
&self.connections
|
||||
}
|
||||
}
|
||||
77
crates/engine/src/audio/audio_out.rs
Normal file
77
crates/engine/src/audio/audio_out.rs
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
use crate::*;
|
||||
|
||||
//impl_port!(AudioOutput: AudioOut -> AudioIn |j, n|j.register_port::<AudioOut>(n));
|
||||
|
||||
#[derive(Debug)] pub struct AudioOutput<'j> {
|
||||
/// Handle to JACK client, for receiving reconnect events.
|
||||
jack: Jack<'j>,
|
||||
/// Port name
|
||||
name: Arc<str>,
|
||||
/// Port handle.
|
||||
port: Port<AudioOut>,
|
||||
/// List of ports to connect to.
|
||||
connections: Vec<PortConnect>
|
||||
}
|
||||
impl<'j> AsRef<Port<AudioOut>> for AudioOutput<'j> {
|
||||
fn as_ref (&self) -> &Port<AudioOut> { &self.port }
|
||||
}
|
||||
impl<'j> AudioOutput<'j> {
|
||||
pub fn new (jack: &Jack, name: impl AsRef<str>, connect: &[PortConnect])
|
||||
-> Usually<Self>
|
||||
{
|
||||
let port = Self {
|
||||
port: jack.register_port::<AudioOut>(name.as_ref())?,
|
||||
jack,
|
||||
name: name.into(),
|
||||
connections: connect.to_vec()
|
||||
};
|
||||
port.connect_to_matching()?;
|
||||
Ok(port)
|
||||
}
|
||||
pub fn name (&self) -> &Arc<str> { &self.name }
|
||||
pub fn port (&self) -> &Port<AudioOut> { &self.port }
|
||||
pub fn port_mut (&mut self) -> &mut Port<AudioOut> { &mut self.port }
|
||||
pub fn into_port (self) -> Port<AudioOut> { self.port }
|
||||
pub fn close (self) -> Usually<()> {
|
||||
let Self { jack, port, .. } = self;
|
||||
Ok(jack.with_client(|client|client.unregister_port(port))?)
|
||||
}
|
||||
}
|
||||
impl<'j> HasJack<'j> for AudioOutput<'j> {
|
||||
fn jack (&self) -> &'j Jack<'j> { &self.jack }
|
||||
}
|
||||
impl<'j> JackPort<'j> for AudioOutput<'j> {
|
||||
type Port = AudioOut;
|
||||
type Pair = AudioIn;
|
||||
fn port (&self) -> &Port<AudioOut> { &self.port }
|
||||
}
|
||||
connect_to!(<'j>|self: AudioOutput<'j>, port: &str|{
|
||||
self.with_client(|c|if let Some(ref port) = c.port_by_name(port.as_ref()) {
|
||||
self.connect_to(port)
|
||||
} else {
|
||||
Ok(Missing)
|
||||
})
|
||||
});
|
||||
connect_to!(<'j>|self: AudioOutput<'j>, port: Port<AudioIn>|{
|
||||
self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(&self.port, port) {
|
||||
Connected
|
||||
} else if let Ok(_) = c.connect_ports(port, &self.port) {
|
||||
Connected
|
||||
} else {
|
||||
Mismatch
|
||||
}))
|
||||
});
|
||||
connect_to!(<'j>|self: AudioOutput<'j>, port: Port<Unowned>|{
|
||||
self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(&self.port, port) {
|
||||
Connected
|
||||
} else if let Ok(_) = c.connect_ports(port, &self.port) {
|
||||
Connected
|
||||
} else {
|
||||
Mismatch
|
||||
}))
|
||||
});
|
||||
impl<'j> ConnectAuto<'j> for AudioOutput<'j> {
|
||||
fn connections (&self) -> &[PortConnect] {
|
||||
&self.connections
|
||||
}
|
||||
}
|
||||
|
|
@ -7,11 +7,12 @@ pub use ::jack::{*, contrib::{*, ClosureProcessHandler}};
|
|||
//Unowned, MidiIn, MidiOut, AudioIn, AudioOut,
|
||||
//};
|
||||
|
||||
pub(crate) use PortConnectName::*;
|
||||
pub(crate) use PortConnectScope::*;
|
||||
pub(crate) use PortConnectStatus::*;
|
||||
pub(crate) use ConnectName::*;
|
||||
pub(crate) use ConnectScope::*;
|
||||
pub(crate) use ConnectStatus::*;
|
||||
pub(crate) use std::sync::{Arc, RwLock};
|
||||
|
||||
mod jack_client; pub use self::jack_client::*;
|
||||
mod jack_event; pub use self::jack_event::*;
|
||||
mod jack_device; pub use self::jack_device::*;
|
||||
mod jack_handler; pub use self::jack_handler::*;
|
||||
mod jack_port; pub use self::jack_port::*;
|
||||
|
|
|
|||
|
|
@ -2,17 +2,57 @@ use crate::*;
|
|||
use super::*;
|
||||
use self::JackState::*;
|
||||
|
||||
impl<T: Has<Jack>> HasJack for T {
|
||||
fn jack (&self) -> &Jack {
|
||||
self.get()
|
||||
}
|
||||
impl<'j, T: Has<Jack<'j>>> HasJack<'j> for T {
|
||||
fn jack (&'j self) -> &'j Jack<'j> { self.get() }
|
||||
}
|
||||
impl<'j> HasJack<'j> for Jack<'j> {
|
||||
fn jack (&'j self) -> &'j Jack<'j> { self }
|
||||
}
|
||||
impl<'j> HasJack<'j> for &Jack<'j> {
|
||||
fn jack (&'j self) -> &'j Jack<'j> { self }
|
||||
}
|
||||
|
||||
/// Things that can provide a [jack::Client] reference.
|
||||
pub trait HasJack {
|
||||
pub trait HasJack<'j> {
|
||||
/// Return the internal [jack::Client] handle
|
||||
/// that lets you call the JACK API.
|
||||
fn jack (&self) -> &Jack;
|
||||
fn jack (&'j self) -> &'j Jack<'j>;
|
||||
/// Run the JACK thread.
|
||||
fn run <T: Audio + Send + Sync + 'j> (
|
||||
&self, cb: impl FnOnce(&Jack)->Usually<T>
|
||||
) -> Usually<Arc<RwLock<T>>> {
|
||||
let jack = self.jack();
|
||||
let app = Arc::new(RwLock::new(cb(jack)?));
|
||||
let mut state = Activating;
|
||||
std::mem::swap(&mut*jack.state.write().unwrap(), &mut state);
|
||||
if let Inactive(client) = state {
|
||||
let client = 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({
|
||||
let app = app.clone();
|
||||
move|event|app.write().unwrap().handle(event)
|
||||
}) as BoxedJackEventHandler<'j>),
|
||||
// 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.
|
||||
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),
|
||||
)?;
|
||||
*jack.state.write().unwrap() = Active(client);
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
Ok(app)
|
||||
}
|
||||
/// Run something with the client.
|
||||
fn with_client <T> (&self, op: impl FnOnce(&Client)->T) -> T {
|
||||
match &*self.jack().state.read().unwrap() {
|
||||
|
|
@ -46,71 +86,25 @@ pub trait HasJack {
|
|||
}
|
||||
}
|
||||
|
||||
impl HasJack for Jack {
|
||||
fn jack (&self) -> &Jack {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl HasJack for &Jack {
|
||||
fn jack (&self) -> &Jack {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Wraps [JackState] and through it [jack::Client].
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Jack {
|
||||
pub state: Arc<RwLock<JackState>>
|
||||
pub struct Jack<'j> {
|
||||
pub state: Arc<RwLock<JackState<'j>>>
|
||||
}
|
||||
|
||||
impl Jack {
|
||||
impl<'j> Jack<'j> {
|
||||
pub fn new (name: &str) -> Usually<Self> {
|
||||
Ok(Self {
|
||||
state: JackState::new(Client::new(name, ClientOptions::NO_START_SERVER)?.0)
|
||||
})
|
||||
}
|
||||
pub fn run <'j: 'static, T: Audio + 'j> (
|
||||
&self, cb: impl FnOnce(&Jack)->Usually<T>
|
||||
) -> Usually<Arc<RwLock<T>>> {
|
||||
let app = Arc::new(RwLock::new(cb(self)?));
|
||||
let mut state = Activating;
|
||||
std::mem::swap(&mut*self.state.write().unwrap(), &mut state);
|
||||
if let Inactive(client) = state {
|
||||
let client = 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({
|
||||
let app = app.clone();
|
||||
move|event|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.
|
||||
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<'j>),
|
||||
)?;
|
||||
*self.state.write().unwrap() = Active(client);
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
Ok(app)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[derive(Debug, Default)]
|
||||
pub enum JackState {
|
||||
pub enum JackState<'j> {
|
||||
/// Unused
|
||||
#[default] Inert,
|
||||
/// Before activation.
|
||||
|
|
@ -118,64 +112,11 @@ pub enum JackState {
|
|||
/// During activation.
|
||||
Activating,
|
||||
/// After activation. Must not be dropped for JACK thread to persist.
|
||||
Active(DynamicAsyncClient<'static>),
|
||||
Active(DynamicAsyncClient<'j>),
|
||||
}
|
||||
|
||||
impl JackState {
|
||||
impl<'j> JackState<'j> {
|
||||
fn new (client: Client) -> Arc<RwLock<Self>> {
|
||||
Arc::new(RwLock::new(Self::Inactive(client)))
|
||||
}
|
||||
}
|
||||
|
||||
/// This is a boxed realtime callback.
|
||||
pub type BoxedAudioHandler<'j> =
|
||||
Box<dyn FnMut(&Client, &ProcessScope) -> Control + Send + 'j>;
|
||||
|
||||
/// This is the notification handler wrapper for a boxed realtime callback.
|
||||
pub type DynamicAudioHandler<'j> =
|
||||
ClosureProcessHandler<(), BoxedAudioHandler<'j>>;
|
||||
|
||||
/// This is a boxed [JackEvent] callback.
|
||||
pub type BoxedJackEventHandler<'j> =
|
||||
Box<dyn Fn(JackEvent) + Send + Sync + 'j>;
|
||||
|
||||
/// This is the notification handler wrapper for a boxed [JackEvent] callback.
|
||||
pub type DynamicNotifications<'j> =
|
||||
Notifications<BoxedJackEventHandler<'j>>;
|
||||
|
||||
/// 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<'j>
|
||||
= AsyncClient<DynamicNotifications<'j>, DynamicAudioHandler<'j>>;
|
||||
|
||||
/// 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 })?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for thing that has a JACK process callback.
|
||||
pub trait Audio: Send + Sync {
|
||||
fn handle (&mut self, _event: JackEvent) {}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,86 +1,86 @@
|
|||
use crate::*
|
||||
use crate::*;
|
||||
|
||||
/// 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,
|
||||
}
|
||||
///// 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> 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> 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> 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> 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)?)
|
||||
}
|
||||
}
|
||||
//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)?)
|
||||
//}
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -1,56 +0,0 @@
|
|||
use crate::*;
|
||||
use super::*;
|
||||
|
||||
/// Event enum for JACK events.
|
||||
#[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]
|
||||
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
|
||||
}
|
||||
}
|
||||
105
crates/engine/src/jack/jack_handler.rs
Normal file
105
crates/engine/src/jack/jack_handler.rs
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
use crate::*;
|
||||
use super::*;
|
||||
|
||||
/// Trait for thing that has a JACK process callback.
|
||||
pub trait Audio {
|
||||
fn handle (&mut self, _event: JackEvent) {}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 })?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Event enum for JACK events.
|
||||
#[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]
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<'j>
|
||||
= AsyncClient<DynamicNotifications<'j>, DynamicAudioHandler<'j>>;
|
||||
/// This is the notification handler wrapper for a boxed realtime callback.
|
||||
pub type DynamicAudioHandler<'j> =
|
||||
ClosureProcessHandler<(), BoxedAudioHandler<'j>>;
|
||||
/// This is a boxed realtime callback.
|
||||
pub type BoxedAudioHandler<'j> =
|
||||
Box<dyn FnMut(&Client, &ProcessScope) -> Control + Send + Sync + 'j>;
|
||||
/// This is the notification handler wrapper for a boxed [JackEvent] callback.
|
||||
pub type DynamicNotifications<'j> =
|
||||
Notifications<BoxedJackEventHandler<'j>>;
|
||||
/// This is a boxed [JackEvent] callback.
|
||||
pub type BoxedJackEventHandler<'j> =
|
||||
Box<dyn Fn(JackEvent) + Send + Sync + 'j>;
|
||||
|
|
@ -1,18 +1,28 @@
|
|||
use crate::*;
|
||||
use super::*;
|
||||
|
||||
pub trait JackPort: HasJack {
|
||||
pub trait JackPort<'j>: HasJack<'j> {
|
||||
type Port: PortSpec;
|
||||
type Pair: PortSpec;
|
||||
fn port (&self) -> &Port<Self::Port>;
|
||||
}
|
||||
|
||||
pub trait JackPortConnect<T>: JackPort {
|
||||
fn connect_to (&self, to: T) -> Usually<PortConnectStatus>;
|
||||
pub trait ConnectTo<'j, T>: JackPort<'j> {
|
||||
fn connect_to (&'j self, to: &T) -> Usually<ConnectStatus>;
|
||||
}
|
||||
|
||||
pub trait JackPortAutoconnect: JackPort + for<'a>JackPortConnect<&'a Port<Unowned>> {
|
||||
fn conn (&self) -> &[PortConnect];
|
||||
#[macro_export] macro_rules! connect_to {
|
||||
(<$lt:lifetime>|$self:ident:$Self:ty, $port:ident:$Port:ty|$expr:expr) => {
|
||||
impl<$lt> ConnectTo<$lt, &$Port> for $Self {
|
||||
fn connect_to (&$self, $port: &$Port) -> Usually<ConnectStatus> {
|
||||
$expr
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub trait ConnectAuto<'j>: JackPort<'j> + ConnectTo<'j, &'j Port<Unowned>> {
|
||||
fn connections (&self) -> &[Connect];
|
||||
fn ports (&self, re_name: Option<&str>, re_type: Option<&str>, flags: PortFlags) -> Vec<String> {
|
||||
self.with_client(|c|c.ports(re_name, re_type, flags))
|
||||
}
|
||||
|
|
@ -23,7 +33,7 @@ pub trait JackPortAutoconnect: JackPort + for<'a>JackPortConnect<&'a Port<Unowne
|
|||
self.with_client(|c|c.port_by_name(name.as_ref()))
|
||||
}
|
||||
fn connect_to_matching (&self) -> Usually<()> {
|
||||
for connect in self.conn().iter() {
|
||||
for connect in self.connections().iter() {
|
||||
//panic!("{connect:?}");
|
||||
let status = match &connect.name {
|
||||
Exact(name) => self.connect_exact(name),
|
||||
|
|
@ -33,9 +43,9 @@ pub trait JackPortAutoconnect: JackPort + for<'a>JackPortConnect<&'a Port<Unowne
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
fn connect_exact (
|
||||
&self, name: &str
|
||||
) -> Usually<Vec<(Port<Unowned>, Arc<str>, PortConnectStatus)>> {
|
||||
fn connect_exact (&self, name: &str) ->
|
||||
Usually<Vec<(Port<Unowned>, Arc<str>, ConnectStatus)>>
|
||||
{
|
||||
self.with_client(|c|{
|
||||
let mut status = vec![];
|
||||
for port in c.ports(None, None, PortFlags::empty()).iter() {
|
||||
|
|
@ -54,8 +64,8 @@ pub trait JackPortAutoconnect: JackPort + for<'a>JackPortConnect<&'a Port<Unowne
|
|||
})
|
||||
}
|
||||
fn connect_regexp (
|
||||
&self, re: &str, scope: PortConnectScope
|
||||
) -> Usually<Vec<(Port<Unowned>, Arc<str>, PortConnectStatus)>> {
|
||||
&self, re: &str, scope: ConnectScope
|
||||
) -> Usually<Vec<(Port<Unowned>, Arc<str>, ConnectStatus)>> {
|
||||
self.with_client(|c|{
|
||||
let mut status = vec![];
|
||||
let ports = c.ports(Some(&re), None, PortFlags::empty());
|
||||
|
|
@ -75,33 +85,33 @@ pub trait JackPortAutoconnect: JackPort + for<'a>JackPortConnect<&'a Port<Unowne
|
|||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum PortConnectName {
|
||||
pub enum ConnectName {
|
||||
/** Exact match */
|
||||
Exact(Arc<str>),
|
||||
/** Match regular expression */
|
||||
RegExp(Arc<str>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)] pub enum PortConnectScope {
|
||||
#[derive(Clone, Copy, Debug, PartialEq)] pub enum ConnectScope {
|
||||
One,
|
||||
All
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)] pub enum PortConnectStatus {
|
||||
#[derive(Clone, Copy, Debug, PartialEq)] pub enum ConnectStatus {
|
||||
Missing,
|
||||
Disconnected,
|
||||
Connected,
|
||||
Mismatch,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)] pub struct PortConnect {
|
||||
pub name: PortConnectName,
|
||||
pub scope: PortConnectScope,
|
||||
pub status: Arc<RwLock<Vec<(Port<Unowned>, Arc<str>, PortConnectStatus)>>>,
|
||||
#[derive(Clone, Debug)] pub struct Connect {
|
||||
pub name: ConnectName,
|
||||
pub scope: ConnectScope,
|
||||
pub status: Arc<RwLock<Vec<(Port<Unowned>, Arc<str>, ConnectStatus)>>>,
|
||||
pub info: Arc<String>,
|
||||
}
|
||||
|
||||
impl PortConnect {
|
||||
impl Connect {
|
||||
pub fn collect (exact: &[impl AsRef<str>], re: &[impl AsRef<str>], re_all: &[impl AsRef<str>])
|
||||
-> Vec<Self>
|
||||
{
|
||||
|
|
@ -147,89 +157,3 @@ impl PortConnect {
|
|||
format!(" ({}) {} {}", status, scope, name).into()
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_port {
|
||||
($Name:ident : $Spec:ident -> $Pair:ident |$jack:ident, $name:ident|$port:expr) => {
|
||||
#[derive(Debug)] pub struct $Name {
|
||||
/// Handle to JACK client, for receiving reconnect events.
|
||||
jack: Jack,
|
||||
/// Port name
|
||||
name: Arc<str>,
|
||||
/// Port handle.
|
||||
port: Port<$Spec>,
|
||||
/// List of ports to connect to.
|
||||
conn: Vec<PortConnect>
|
||||
}
|
||||
impl AsRef<Port<$Spec>> for $Name { fn as_ref (&self) -> &Port<$Spec> { &self.port } }
|
||||
impl $Name {
|
||||
pub fn new ($jack: &Jack, name: impl AsRef<str>, connect: &[PortConnect])
|
||||
-> Usually<Self>
|
||||
{
|
||||
let $name = name.as_ref();
|
||||
let jack = $jack.clone();
|
||||
let port = $port?;
|
||||
let name = $name.into();
|
||||
let conn = connect.to_vec();
|
||||
let port = Self { jack, port, name, conn };
|
||||
port.connect_to_matching()?;
|
||||
Ok(port)
|
||||
}
|
||||
pub fn name (&self) -> &Arc<str> { &self.name }
|
||||
pub fn port (&self) -> &Port<$Spec> { &self.port }
|
||||
pub fn port_mut (&mut self) -> &mut Port<$Spec> { &mut self.port }
|
||||
pub fn into_port (self) -> Port<$Spec> { self.port }
|
||||
pub fn close (self) -> Usually<()> {
|
||||
let Self { jack, port, .. } = self;
|
||||
Ok(jack.with_client(|client|client.unregister_port(port))?)
|
||||
}
|
||||
}
|
||||
impl HasJack for $Name { fn jack (&self) -> &Jack { &self.jack } }
|
||||
impl JackPort for $Name {
|
||||
type Port = $Spec;
|
||||
type Pair = $Pair;
|
||||
fn port (&self) -> &Port<$Spec> { &self.port }
|
||||
}
|
||||
impl JackPortConnect<&str> for $Name {
|
||||
fn connect_to (&self, to: &str) -> Usually<PortConnectStatus> {
|
||||
self.with_client(|c|if let Some(ref port) = c.port_by_name(to.as_ref()) {
|
||||
self.connect_to(port)
|
||||
} else {
|
||||
Ok(Missing)
|
||||
})
|
||||
}
|
||||
}
|
||||
impl JackPortConnect<&Port<Unowned>> for $Name {
|
||||
fn connect_to (&self, port: &Port<Unowned>) -> Usually<PortConnectStatus> {
|
||||
self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(&self.port, port) {
|
||||
Connected
|
||||
} else if let Ok(_) = c.connect_ports(port, &self.port) {
|
||||
Connected
|
||||
} else {
|
||||
Mismatch
|
||||
}))
|
||||
}
|
||||
}
|
||||
impl JackPortConnect<&Port<$Pair>> for $Name {
|
||||
fn connect_to (&self, port: &Port<$Pair>) -> Usually<PortConnectStatus> {
|
||||
self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(&self.port, port) {
|
||||
Connected
|
||||
} else if let Ok(_) = c.connect_ports(port, &self.port) {
|
||||
Connected
|
||||
} else {
|
||||
Mismatch
|
||||
}))
|
||||
}
|
||||
}
|
||||
impl JackPortAutoconnect for $Name {
|
||||
fn conn (&self) -> &[PortConnect] {
|
||||
&self.conn
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_port!(JackAudioIn: AudioIn -> AudioOut |j, n|j.register_port::<AudioIn>(n));
|
||||
|
||||
impl_port!(JackAudioOut: AudioOut -> AudioIn |j, n|j.register_port::<AudioOut>(n));
|
||||
|
||||
impl_port!(JackMidiIn: MidiIn -> MidiOut |j, n|j.register_port::<MidiIn>(n));
|
||||
|
|
|
|||
|
|
@ -1,9 +1,94 @@
|
|||
#![feature(type_alias_impl_trait)]
|
||||
|
||||
//macro_rules! impl_port {
|
||||
//($Name:ident : $Spec:ident -> $Pair:ident |$jack:ident, $name:ident|$port:expr) => {
|
||||
//#[derive(Debug)] pub struct $Name {
|
||||
///// Handle to JACK client, for receiving reconnect events.
|
||||
//jack: Jack<'static>,
|
||||
///// Port name
|
||||
//name: Arc<str>,
|
||||
///// Port handle.
|
||||
//port: Port<$Spec>,
|
||||
///// List of ports to connect to.
|
||||
//conn: Vec<PortConnect>
|
||||
//}
|
||||
//impl AsRef<Port<$Spec>> for $Name {
|
||||
//fn as_ref (&self) -> &Port<$Spec> { &self.port }
|
||||
//}
|
||||
//impl $Name {
|
||||
//pub fn new ($jack: &Jack, name: impl AsRef<str>, connect: &[PortConnect])
|
||||
//-> Usually<Self>
|
||||
//{
|
||||
//let $name = name.as_ref();
|
||||
//let jack = $jack.clone();
|
||||
//let port = $port?;
|
||||
//let name = $name.into();
|
||||
//let conn = connect.to_vec();
|
||||
//let port = Self { jack, port, name, conn };
|
||||
//port.connect_to_matching()?;
|
||||
//Ok(port)
|
||||
//}
|
||||
//pub fn name (&self) -> &Arc<str> { &self.name }
|
||||
//pub fn port (&self) -> &Port<$Spec> { &self.port }
|
||||
//pub fn port_mut (&mut self) -> &mut Port<$Spec> { &mut self.port }
|
||||
//pub fn into_port (self) -> Port<$Spec> { self.port }
|
||||
//pub fn close (self) -> Usually<()> {
|
||||
//let Self { jack, port, .. } = self;
|
||||
//Ok(jack.with_client(|client|client.unregister_port(port))?)
|
||||
//}
|
||||
//}
|
||||
//impl HasJack<'static> for $Name {
|
||||
//fn jack (&self) -> &'static Jack<'static> { &self.jack }
|
||||
//}
|
||||
//impl JackPort<'static> for $Name {
|
||||
//type Port = $Spec;
|
||||
//type Pair = $Pair;
|
||||
//fn port (&self) -> &Port<$Spec> { &self.port }
|
||||
//}
|
||||
//impl ConnectTo<'static, &str> for $Name {
|
||||
//fn connect_to (&self, to: &str) -> Usually<PortConnectStatus> {
|
||||
//self.with_client(|c|if let Some(ref port) = c.port_by_name(to.as_ref()) {
|
||||
//self.connect_to(port)
|
||||
//} else {
|
||||
//Ok(Missing)
|
||||
//})
|
||||
//}
|
||||
//}
|
||||
//impl ConnectTo<'static, &Port<Unowned>> for $Name {
|
||||
//fn connect_to (&self, port: &Port<Unowned>) -> Usually<PortConnectStatus> {
|
||||
//self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(&self.port, port) {
|
||||
//Connected
|
||||
//} else if let Ok(_) = c.connect_ports(port, &self.port) {
|
||||
//Connected
|
||||
//} else {
|
||||
//Mismatch
|
||||
//}))
|
||||
//}
|
||||
//}
|
||||
//impl ConnectTo<'static, &Port<$Pair>> for $Name {
|
||||
//fn connect_to (&self, port: &Port<$Pair>) -> Usually<PortConnectStatus> {
|
||||
//self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(&self.port, port) {
|
||||
//Connected
|
||||
//} else if let Ok(_) = c.connect_ports(port, &self.port) {
|
||||
//Connected
|
||||
//} else {
|
||||
//Mismatch
|
||||
//}))
|
||||
//}
|
||||
//}
|
||||
//impl ConnectAuto<'static> for $Name {
|
||||
//fn connections (&self) -> &[PortConnect] {
|
||||
//&self.conn
|
||||
//}
|
||||
//}
|
||||
//};
|
||||
//}
|
||||
|
||||
mod time; pub use self::time::*;
|
||||
mod note; pub use self::note::*;
|
||||
pub mod jack; pub use self::jack::*;
|
||||
pub mod midi; pub use self::midi::*;
|
||||
pub mod audio; pub use self::audio::*;
|
||||
|
||||
pub(crate) use std::sync::{Arc, atomic::{AtomicUsize, AtomicBool, Ordering::Relaxed}};
|
||||
pub(crate) use std::fmt::Debug;
|
||||
|
|
|
|||
|
|
@ -1,29 +1,119 @@
|
|||
use crate::*;
|
||||
|
||||
impl JackMidiIn {
|
||||
//impl_port!(MidiInput: MidiOut -> MidiIn |j, n|j.register_port::<MidiOut>(n));
|
||||
|
||||
#[derive(Debug)] pub struct MidiInput<'j> {
|
||||
/// Handle to JACK client, for receiving reconnect events.
|
||||
jack: Jack<'j>,
|
||||
/// Port name
|
||||
name: Arc<str>,
|
||||
/// Port handle.
|
||||
port: Port<MidiOut>,
|
||||
/// List of ports to connect to.
|
||||
connections: Vec<Connect>
|
||||
}
|
||||
impl<'j> AsRef<Port<MidiOut>> for MidiInput<'j> {
|
||||
fn as_ref (&self) -> &Port<MidiOut> { &self.port }
|
||||
}
|
||||
impl<'j> MidiInput<'j> {
|
||||
pub fn new (jack: &Jack, name: impl AsRef<str>, connect: &[Connect])
|
||||
-> Usually<Self>
|
||||
{
|
||||
let port = Self {
|
||||
port: jack.register_port::<MidiIn>(name.as_ref())?,
|
||||
jack,
|
||||
name: name.into(),
|
||||
connections: connect.to_vec()
|
||||
};
|
||||
port.connect_to_matching()?;
|
||||
Ok(port)
|
||||
}
|
||||
pub fn name (&self) -> &Arc<str> {
|
||||
&self.name
|
||||
}
|
||||
pub fn port (&self) -> &Port<MidiOut> {
|
||||
&self.port
|
||||
}
|
||||
pub fn port_mut (&mut self) -> &mut Port<MidiOut> {
|
||||
&mut self.port
|
||||
}
|
||||
pub fn into_port (self) -> Port<MidiOut> {
|
||||
self.port
|
||||
}
|
||||
pub fn close (self) -> Usually<()> {
|
||||
let Self { jack, port, .. } = self;
|
||||
Ok(jack.with_client(|client|client.unregister_port(port))?)
|
||||
}
|
||||
pub fn parsed <'a> (&'a self, scope: &'a ProcessScope) -> impl Iterator<Item=(usize, LiveEvent<'a>, &'a [u8])> {
|
||||
parse_midi_input(self.port().iter(scope))
|
||||
}
|
||||
}
|
||||
|
||||
#[tengri_proc::command(JackMidiIn)]
|
||||
impl MidiInputCommand {
|
||||
fn _todo_ (_port: &mut JackMidiIn) -> Perhaps<Self> { Ok(None) }
|
||||
impl<'j> HasJack<'j> for MidiInput<'j> {
|
||||
fn jack (&self) -> &'j Jack<'j> { &self.jack }
|
||||
}
|
||||
impl<'j> JackPort<'j> for MidiInput<'j> {
|
||||
type Port = MidiIn;
|
||||
type Pair = MidiOut;
|
||||
fn port (&self) -> &Port<MidiOut> { &self.port }
|
||||
}
|
||||
//impl<'j, T: AsRef<str>> ConnectTo<'j, T> for MidiInput<'j> {
|
||||
//fn connect_to (&self, to: &T) -> Usually<ConnectStatus> {
|
||||
//self.with_client(|c|if let Some(ref port) = c.port_by_name(to.as_ref()) {
|
||||
//self.connect_to(port)
|
||||
//} else {
|
||||
//Ok(Missing)
|
||||
//})
|
||||
//}
|
||||
//}
|
||||
connect_to!(<'j>|self: MidiInput<'j>, port: &str|{
|
||||
self.with_client(|c|if let Some(ref port) = c.port_by_name(port.as_ref()) {
|
||||
self.connect_to(port)
|
||||
} else {
|
||||
Ok(Missing)
|
||||
})
|
||||
});
|
||||
connect_to!(<'j>|self: MidiInput<'j>, port: Port<MidiOut>|{
|
||||
self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(&self.port, port) {
|
||||
Connected
|
||||
} else if let Ok(_) = c.connect_ports(port, &self.port) {
|
||||
Connected
|
||||
} else {
|
||||
Mismatch
|
||||
}))
|
||||
});
|
||||
connect_to!(<'j>|self: MidiInput<'j>, port: Port<Unowned>|{
|
||||
self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(&self.port, port) {
|
||||
Connected
|
||||
} else if let Ok(_) = c.connect_ports(port, &self.port) {
|
||||
Connected
|
||||
} else {
|
||||
Mismatch
|
||||
}))
|
||||
});
|
||||
impl<'j> ConnectAuto<'j> for MidiInput<'j> {
|
||||
fn connections (&self) -> &[Connect] {
|
||||
&self.connections
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Has<Vec<JackMidiIn>>> HasMidiIns for T {
|
||||
fn midi_ins (&self) -> &Vec<JackMidiIn> {
|
||||
#[tengri_proc::command(MidiInput)]
|
||||
impl MidiInputCommand {
|
||||
//fn _todo_ (_port: &mut MidiInput) -> Perhaps<Self> { Ok(None) }
|
||||
}
|
||||
|
||||
impl<T: Has<Vec<MidiInput>>> HasMidiIns for T {
|
||||
fn midi_ins (&self) -> &Vec<MidiInput> {
|
||||
self.get()
|
||||
}
|
||||
fn midi_ins_mut (&mut self) -> &mut Vec<JackMidiIn> {
|
||||
fn midi_ins_mut (&mut self) -> &mut Vec<MidiInput> {
|
||||
self.get_mut()
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for thing that may receive MIDI.
|
||||
pub trait HasMidiIns {
|
||||
fn midi_ins (&self) -> &Vec<JackMidiIn>;
|
||||
fn midi_ins_mut (&mut self) -> &mut Vec<JackMidiIn>;
|
||||
fn midi_ins (&self) -> &Vec<MidiInput>;
|
||||
fn midi_ins_mut (&mut self) -> &mut Vec<MidiInput>;
|
||||
/// Collect MIDI input from app ports (TODO preallocate large buffers)
|
||||
fn midi_input_collect <'a> (&'a self, scope: &'a ProcessScope) -> CollectedMidiInput<'a> {
|
||||
self.midi_ins().iter()
|
||||
|
|
@ -33,7 +123,7 @@ pub trait HasMidiIns {
|
|||
.collect::<Vec<_>>()
|
||||
}
|
||||
fn midi_ins_with_sizes <'a> (&'a self) ->
|
||||
impl Iterator<Item=(usize, &Arc<str>, &[PortConnect], usize, usize)> + Send + Sync + 'a
|
||||
impl Iterator<Item=(usize, &Arc<str>, &[Connect], usize, usize)> + Send + Sync + 'a
|
||||
{
|
||||
let mut y = 0;
|
||||
self.midi_ins().iter().enumerate().map(move|(i, input)|{
|
||||
|
|
@ -47,10 +137,10 @@ pub trait HasMidiIns {
|
|||
|
||||
pub type CollectedMidiInput<'a> = Vec<Vec<(u32, Result<LiveEvent<'a>, MidiError>)>>;
|
||||
|
||||
impl<T: HasMidiIns + HasJack> AddMidiIn for T {
|
||||
impl<'j, T: HasMidiIns + HasJack<'j>> AddMidiIn for T {
|
||||
fn midi_in_add (&mut self) -> Usually<()> {
|
||||
let index = self.midi_ins().len();
|
||||
let port = JackMidiIn::new(self.jack(), &format!("M/{index}"), &[])?;
|
||||
let port = MidiInput::new(self.jack(), &format!("M/{index}"), &[])?;
|
||||
self.midi_ins_mut().push(port);
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
use crate::*;
|
||||
|
||||
#[derive(Debug)] pub struct JackMidiOut {
|
||||
#[derive(Debug)] pub struct MidiOutput {
|
||||
/// Handle to JACK client, for receiving reconnect events.
|
||||
jack: Jack,
|
||||
jack: Jack<'static>,
|
||||
/// Port name
|
||||
name: Arc<str>,
|
||||
/// Port handle.
|
||||
port: Port<MidiOut>,
|
||||
/// List of ports to connect to.
|
||||
conn: Vec<PortConnect>,
|
||||
conn: Vec<Connect>,
|
||||
/// List of currently held notes.
|
||||
held: Arc<RwLock<[bool;128]>>,
|
||||
/// Buffer
|
||||
|
|
@ -17,10 +17,10 @@ use crate::*;
|
|||
output_buffer: Vec<Vec<Vec<u8>>>,
|
||||
}
|
||||
|
||||
has!(Jack: |self: JackMidiOut|self.jack);
|
||||
has!(Jack<'static>: |self: MidiOutput|self.jack);
|
||||
|
||||
impl JackMidiOut {
|
||||
pub fn new (jack: &Jack, name: impl AsRef<str>, connect: &[PortConnect])
|
||||
impl MidiOutput {
|
||||
pub fn new (jack: &Jack, name: impl AsRef<str>, connect: &[Connect])
|
||||
-> Usually<Self>
|
||||
{
|
||||
let jack = jack.clone();
|
||||
|
|
@ -94,20 +94,20 @@ impl JackMidiOut {
|
|||
}
|
||||
}
|
||||
|
||||
impl AsRef<Port<MidiOut>> for JackMidiOut {
|
||||
impl AsRef<Port<MidiOut>> for MidiOutput {
|
||||
fn as_ref (&self) -> &Port<MidiOut> {
|
||||
&self.port
|
||||
}
|
||||
}
|
||||
|
||||
impl JackPort for JackMidiOut {
|
||||
impl JackPort<'static> for MidiOutput {
|
||||
type Port = MidiOut;
|
||||
type Pair = MidiIn;
|
||||
fn port (&self) -> &Port<MidiOut> { &self.port }
|
||||
}
|
||||
|
||||
impl JackPortConnect<&str> for JackMidiOut {
|
||||
fn connect_to (&self, to: &str) -> Usually<PortConnectStatus> {
|
||||
impl ConnectTo<'static, &str> for MidiOutput {
|
||||
fn connect_to (&self, to: &str) -> Usually<ConnectStatus> {
|
||||
self.with_client(|c|if let Some(ref port) = c.port_by_name(to.as_ref()) {
|
||||
self.connect_to(port)
|
||||
} else {
|
||||
|
|
@ -116,8 +116,8 @@ impl JackPortConnect<&str> for JackMidiOut {
|
|||
}
|
||||
}
|
||||
|
||||
impl JackPortConnect<&Port<Unowned>> for JackMidiOut {
|
||||
fn connect_to (&self, port: &Port<Unowned>) -> Usually<PortConnectStatus> {
|
||||
impl ConnectTo<'static, &Port<Unowned>> for MidiOutput {
|
||||
fn connect_to (&self, port: &Port<Unowned>) -> Usually<ConnectStatus> {
|
||||
self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(&self.port, port) {
|
||||
Connected
|
||||
} else if let Ok(_) = c.connect_ports(port, &self.port) {
|
||||
|
|
@ -128,8 +128,8 @@ impl JackPortConnect<&Port<Unowned>> for JackMidiOut {
|
|||
}
|
||||
}
|
||||
|
||||
impl JackPortConnect<&Port<MidiIn>> for JackMidiOut {
|
||||
fn connect_to (&self, port: &Port<MidiIn>) -> Usually<PortConnectStatus> {
|
||||
impl ConnectTo<'static, &Port<MidiIn>> for MidiOutput {
|
||||
fn connect_to (&self, port: &Port<MidiIn>) -> Usually<ConnectStatus> {
|
||||
self.with_client(|c|Ok(if let Ok(_) = c.connect_ports(&self.port, port) {
|
||||
Connected
|
||||
} else if let Ok(_) = c.connect_ports(port, &self.port) {
|
||||
|
|
@ -140,22 +140,22 @@ impl JackPortConnect<&Port<MidiIn>> for JackMidiOut {
|
|||
}
|
||||
}
|
||||
|
||||
impl JackPortAutoconnect for JackMidiOut {
|
||||
fn conn (&self) -> &[PortConnect] {
|
||||
impl ConnectAuto<'static> for MidiOutput {
|
||||
fn connections (&self) -> &[Connect] {
|
||||
&self.conn
|
||||
}
|
||||
}
|
||||
|
||||
#[tengri_proc::command(JackMidiOut)]
|
||||
#[tengri_proc::command(MidiOutput)]
|
||||
impl MidiOutputCommand {
|
||||
fn _todo_ (_port: &mut JackMidiOut) -> Perhaps<Self> { Ok(None) }
|
||||
fn _todo_ (_port: &mut MidiOutput) -> Perhaps<Self> { Ok(None) }
|
||||
}
|
||||
|
||||
impl<T: Has<Vec<JackMidiOut>>> HasMidiOuts for T {
|
||||
fn midi_outs (&self) -> &Vec<JackMidiOut> {
|
||||
impl<T: Has<Vec<MidiOutput>>> HasMidiOuts for T {
|
||||
fn midi_outs (&self) -> &Vec<MidiOutput> {
|
||||
self.get()
|
||||
}
|
||||
fn midi_outs_mut (&mut self) -> &mut Vec<JackMidiOut> {
|
||||
fn midi_outs_mut (&mut self) -> &mut Vec<MidiOutput> {
|
||||
self.get_mut()
|
||||
}
|
||||
}
|
||||
|
|
@ -163,10 +163,10 @@ impl<T: Has<Vec<JackMidiOut>>> HasMidiOuts for T {
|
|||
|
||||
/// Trait for thing that may output MIDI.
|
||||
pub trait HasMidiOuts {
|
||||
fn midi_outs (&self) -> &Vec<JackMidiOut>;
|
||||
fn midi_outs_mut (&mut self) -> &mut Vec<JackMidiOut>;
|
||||
fn midi_outs (&self) -> &Vec<MidiOutput>;
|
||||
fn midi_outs_mut (&mut self) -> &mut Vec<MidiOutput>;
|
||||
fn midi_outs_with_sizes <'a> (&'a self) ->
|
||||
impl Iterator<Item=(usize, &Arc<str>, &[PortConnect], usize, usize)> + Send + Sync + 'a
|
||||
impl Iterator<Item=(usize, &Arc<str>, &[Connect], usize, usize)> + Send + Sync + 'a
|
||||
{
|
||||
let mut y = 0;
|
||||
self.midi_outs().iter().enumerate().map(move|(i, output)|{
|
||||
|
|
@ -184,10 +184,10 @@ pub trait HasMidiOuts {
|
|||
}
|
||||
|
||||
/// Trail for thing that may gain new MIDI ports.
|
||||
impl<T: HasMidiOuts + HasJack> AddMidiOut for T {
|
||||
impl<'j, T: HasMidiOuts + HasJack<'j>> AddMidiOut for T {
|
||||
fn midi_out_add (&mut self) -> Usually<()> {
|
||||
let index = self.midi_outs().len();
|
||||
let port = JackMidiOut::new(self.jack(), &format!("{index}/M"), &[])?;
|
||||
let port = MidiOutput::new(self.jack(), &format!("{index}/M"), &[])?;
|
||||
self.midi_outs_mut().push(port);
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
2
deps/tengri
vendored
2
deps/tengri
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit f08593f0f8c3dc03a734d922d2442848a4205ad6
|
||||
Subproject commit 455d6d00d5f91e7f9f6b9d3711aa47e09900ad46
|
||||
Loading…
Add table
Add a link
Reference in a new issue