wip: port: make device

This commit is contained in:
🪞👃🪞 2025-05-21 00:07:35 +03:00
parent 447638ee71
commit cb7e4f7a95
31 changed files with 602 additions and 865 deletions

View file

@ -4,7 +4,7 @@ use std::path::PathBuf;
#[derive(Default, Debug)]
pub struct App {
/// Must not be dropped for the duration of the process
pub jack: Jack,
pub jack: Jack<'static>,
/// Port handles
pub ports: std::collections::BTreeMap<u32, Port<Unowned>>,
/// Display size
@ -27,45 +27,33 @@ pub struct App {
pub color: ItemTheme,
}
has!(Jack: |self: App|self.jack);
has!(Jack<'static>: |self: App|self.jack);
has!(Pool: |self: App|self.pool);
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<MidiInput>: |self: App|self.project.midi_ins);
has!(Vec<MidiOutput>: |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);
maybe_has!(Track: |self: App|
maybe_has!(Track: |self: App|
{ MaybeHas::<Track>::get(&self.project) };
{ MaybeHas::<Track>::get_mut(&mut self.project) });
impl HasTrackScroll for App {
fn track_scroll (&self) -> usize { self.project.track_scroll() }
}
maybe_has!(Scene: |self: App|
impl HasTrackScroll for App { fn track_scroll (&self) -> usize { self.project.track_scroll() } }
maybe_has!(Scene: |self: App|
{ MaybeHas::<Scene>::get(&self.project) };
{ MaybeHas::<Scene>::get_mut(&mut self.project) });
impl HasSceneScroll for App {
fn scene_scroll (&self) -> usize { self.project.scene_scroll() }
}
impl HasSceneScroll for App { fn scene_scroll (&self) -> usize { self.project.scene_scroll() } }
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));
impl HasClipsSize for App { fn clips_size (&self) -> &Measure<TuiOut> { &self.project.inner_size } }
from_dsl!(ClockCommand: |state: App, iter|Namespace::take_from(state.clock(), iter));
from_dsl!(MidiEditCommand: |state: App, iter|Ok(state.editor().map(|x|Namespace::take_from(x, iter)).transpose()?.flatten()));
from_dsl!(PoolCommand: |state: App, iter|Namespace::take_from(&state.pool, iter));
from_dsl!(SamplerCommand: |state: App, iter|Ok(state.project.sampler().map(|x|Namespace::take_from(x, iter)).transpose()?.flatten()));
from_dsl!(ArrangementCommand: |state: App, iter|Namespace::take_from(&state.project, iter));
from_dsl!(DialogCommand: |state: App, iter|Namespace::take_from(&state.dialog, iter));
//has_editor!(|self: App|{
//editor = self.editor;
//editor_w = {
@ -121,7 +109,7 @@ impl App {
let midi = self.project.track().expect("no active track").sequencer.midi_outs[0].name();
let track = self.track().expect("no active track");
let port = format!("{}/Sampler", &track.name);
let connect = PortConnect::exact(format!("{name}:{midi}"));
let connect = Connect::exact(format!("{name}:{midi}"));
let sampler = if let Ok(sampler) = Sampler::new(
&self.jack, &port, &[connect], &[&[], &[]], &[&[], &[]]
) {
@ -464,7 +452,7 @@ impl Configuration {
let cond = cond.unwrap();
println!("ok");
map.add_layer_if(
Box::new(move |state: &App|FromDsl::take_from_or_fail(
Box::new(move |state: &App|Namespace::take_from_or_fail(
state, &mut exp.clone(),
format!("missing input layer conditional")
)), keys

View file

@ -5,7 +5,7 @@ pub(crate) use ::tengri::tui::ratatui::prelude::Position;
impl App {
pub fn view (&self) -> impl Content<TuiOut> + '_ {
let view: Perhaps<Box<dyn Render<TuiOut>>> =
FromDsl::take_from(self, &mut self.config.view.clone());
Namespace::take_from(self, &mut self.config.view.clone());
Either(view.is_ok(),
ThunkRender::new(move|to|if let Some(view) = view.as_ref().unwrap().as_ref() {
Content::render(view, to)

View file

@ -16,18 +16,20 @@ wavers = { workspace = true, optional = true }
winit = { workspace = true, optional = true }
[features]
default = [ "browser", "clock", "editor", "sequencer", "sampler", "lv2", "arranger" ]
default = [ "arranger", "sampler", "lv2" ]
arranger = [ "port", "editor", "sequencer", "editor" ]
browser = []
clap = []
clock = []
arranger = [ "editor" ]
editor = []
lv2 = [ "port", "livi", "winit" ]
meter = []
mixer = []
browser = []
pool = []
sequencer = [ "clock", "uuid", "pool" ]
sampler = [ "meter", "mixer", "browser", "symphonia", "wavers" ]
lv2 = [ "livi", "winit" ]
port = []
sampler = [ "port", "meter", "mixer", "browser", "symphonia", "wavers" ]
sequencer = [ "port", "clock", "uuid", "pool" ]
sf2 = []
vst2 = []
vst3 = []
clap = []
sf2 = []

View file

@ -1,13 +1,5 @@
use crate::*;
/// Define a type alias for iterators of sized items (columns).
macro_rules! def_sizes_iter {
($Type:ident => $($Item:ty),+) => {
pub trait $Type<'a> =
Iterator<Item=(usize, $(&'a $Item,)+ usize, usize)> + Send + Sync + 'a;
}
}
mod arranger_api; pub use self::arranger_api::*;
mod arranger_clip; pub use self::arranger_clip::*;
mod arranger_model; pub use self::arranger_model::*;
@ -20,7 +12,7 @@ def_sizes_iter!(ScenesSizes => Scene);
def_sizes_iter!(TracksSizes => Track);
def_sizes_iter!(InputsSizes => MidiInput);
def_sizes_iter!(OutputsSizes => MidiOutput);
def_sizes_iter!(PortsSizes => Arc<str>, [PortConnect]);
def_sizes_iter!(PortsSizes => Arc<str>, [Connect]);
pub(crate) fn wrap (bg: Color, fg: Color, content: impl Content<TuiOut>) -> impl Content<TuiOut> {
let left = Tui::fg_bg(bg, Reset, Fixed::x(1, RepeatV("")));

View file

@ -156,7 +156,7 @@ impl ArrangementCommand {
fn output_add (arranger: &mut Arrangement) -> Perhaps<Self> {
arranger.midi_outs.push(MidiOutput::new(
arranger.jack(),
format!("/M{}", arranger.midi_outs.len() + 1),
&format!("/M{}", arranger.midi_outs.len() + 1),
&[]
)?);
Ok(None)
@ -164,7 +164,7 @@ impl ArrangementCommand {
fn input_add (arranger: &mut Arrangement) -> Perhaps<Self> {
arranger.midi_ins.push(MidiInput::new(
arranger.jack(),
format!("M{}/", arranger.midi_ins.len() + 1),
&format!("M{}/", arranger.midi_ins.len() + 1),
&[]
)?);
Ok(None)

View file

@ -1,9 +1,9 @@
use crate::*;
has!(Jack: |self: Arrangement|self.jack);
has!(Jack<'static>: |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<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);
@ -15,24 +15,24 @@ 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()));
.map(|t|Namespace::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()));
.map(|t|Namespace::take_from(t, iter)).transpose().map(|x|x.flatten()));
from_dsl!(DeviceCommand:|state: Arrangement, iter|state.selected_device().as_ref()
.map(|t|Namespace::take_from(t, iter)).transpose().map(|x|x.flatten()));
from_dsl!(TrackCommand: |state: Arrangement, iter|state.selected_track().as_ref()
.map(|t|Namespace::take_from(t, iter)).transpose().map(|x|x.flatten()));
from_dsl!(SceneCommand: |state: Arrangement, iter|state.selected_scene().as_ref()
.map(|t|Namespace::take_from(t, iter)).transpose().map(|x|x.flatten()));
from_dsl!(ClipCommand: |state: Arrangement, iter|state.selected_clip().as_ref()
.map(|t|Namespace::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 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!() }
@ -47,7 +47,7 @@ from_dsl!(ClipCommand: |state: Arrangement, iter|state.selected_clip().as_
/// Base color.
pub color: ItemTheme,
/// Jack client handle
pub jack: Jack,
pub jack: Jack<'static>,
/// Source of time
pub clock: Clock,
/// Allows one MIDI clip to be edited
@ -152,64 +152,6 @@ impl Arrangement {
clip.write().unwrap().toggle_loop()
}
}
/// Add multiple tracks
pub fn tracks_add (
&mut self,
count: usize,
width: Option<usize>,
mins: &[PortConnect],
mouts: &[PortConnect],
) -> Usually<()> {
let jack = self.jack().clone();
let track_color_1 = ItemColor::random();
let track_color_2 = ItemColor::random();
for i in 0..count {
let color = track_color_1.mix(track_color_2, i as f32 / count as f32).into();
let mut track = self.track_add(None, Some(color), mins, mouts)?.1;
if let Some(width) = width {
track.width = width;
}
}
Ok(())
}
/// Add a track
pub fn track_add (
&mut self,
name: Option<&str>,
color: Option<ItemTheme>,
mins: &[PortConnect],
mouts: &[PortConnect],
) -> Usually<(usize, &mut Track)> {
let name: Arc<str> = name.map_or_else(
||format!("trk{:02}", self.track_last).into(),
|x|x.to_string().into()
);
self.track_last += 1;
let mut track = Track {
width: (name.len() + 2).max(12),
color: color.unwrap_or_else(ItemTheme::random),
sequencer: Sequencer::new(
&format!("{name}"),
self.jack(),
Some(self.clock()),
None,
mins,
mouts
)?,
name,
..Default::default()
};
self.tracks_mut().push(track);
let len = self.tracks().len();
let index = len - 1;
for scene in self.scenes_mut().iter_mut() {
while scene.clips.len() < len {
scene.clips.push(None);
}
}
Ok((index, &mut self.tracks_mut()[index]))
}
}
#[cfg(feature = "sampler")]

View file

@ -1,5 +1,62 @@
use crate::*;
impl Arrangement {
/// Add multiple tracks
pub fn tracks_add (
&mut self,
count: usize, width: Option<usize>,
mins: &[Connect], mouts: &[Connect],
) -> Usually<()> {
let jack = self.jack().clone();
let track_color_1 = ItemColor::random();
let track_color_2 = ItemColor::random();
for i in 0..count {
let color = track_color_1.mix(track_color_2, i as f32 / count as f32).into();
let mut track = self.track_add(None, Some(color), mins, mouts)?.1;
if let Some(width) = width {
track.width = width;
}
}
Ok(())
}
/// Add a track
pub fn track_add (
&mut self,
name: Option<&str>, color: Option<ItemTheme>,
mins: &[Connect], mouts: &[Connect],
) -> Usually<(usize, &mut Track)> {
let name: Arc<str> = name.map_or_else(
||format!("trk{:02}", self.track_last).into(),
|x|x.to_string().into()
);
self.track_last += 1;
let mut track = Track {
width: (name.len() + 2).max(12),
color: color.unwrap_or_else(ItemTheme::random),
sequencer: Sequencer::new(
&format!("{name}"),
self.jack(),
Some(self.clock()),
None,
mins,
mouts
)?,
name,
..Default::default()
};
self.tracks_mut().push(track);
let len = self.tracks().len();
let index = len - 1;
for scene in self.scenes_mut().iter_mut() {
while scene.clips.len() < len {
scene.clips.push(None);
}
}
Ok((index, &mut self.tracks_mut()[index]))
}
}
impl<T: Has<Vec<Track>> + Send + Sync> HasTracks for T {}
pub trait HasTracks: Has<Vec<Track>> + Send + Sync {
@ -132,11 +189,11 @@ impl Track {
pub fn new (
name: &impl AsRef<str>,
color: Option<ItemTheme>,
jack: &Jack,
jack: &Jack<'static>,
clock: Option<&Clock>,
clip: Option<&Arc<RwLock<MidiClip>>>,
midi_from: &[PortConnect],
midi_to: &[PortConnect],
midi_from: &[Connect],
midi_to: &[Connect],
) -> Usually<Self> {
Ok(Self {
name: name.as_ref().into(),
@ -156,19 +213,19 @@ impl Track {
pub fn new_with_sampler (
name: &impl AsRef<str>,
color: Option<ItemTheme>,
jack: &Jack,
jack: &Jack<'static>,
clock: Option<&Clock>,
clip: Option<&Arc<RwLock<MidiClip>>>,
midi_from: &[PortConnect],
midi_to: &[PortConnect],
audio_from: &[&[PortConnect];2],
audio_to: &[&[PortConnect];2],
midi_from: &[Connect],
midi_to: &[Connect],
audio_from: &[&[Connect];2],
audio_to: &[&[Connect];2],
) -> Usually<Self> {
let mut track = Self::new(name, color, jack, clock, clip, midi_from, midi_to)?;
track.devices.push(Device::Sampler(Sampler::new(
jack,
&format!("{}/sampler", name.as_ref()),
&[PortConnect::exact(format!("{}:{}",
&[Connect::exact(format!("{}:{}",
jack.with_client(|c|c.name().to_string()),
track.sequencer.midi_outs[0].name()
))],
@ -284,10 +341,10 @@ pub(crate) fn io_ports <'a, T: PortsSizes<'a>> (
) -> impl Content<TuiOut> + 'a {
Map::new(iter, move|(
index, name, connections, y, y2
): (usize, &'a Arc<str>, &'a [PortConnect], usize, usize), _|
): (usize, &'a Arc<str>, &'a [Connect], usize, usize), _|
map_south(y as u16, (y2-y) as u16, Bsp::s(
Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(Bsp::e(" 󰣲 ", name))))),
Map::new(||connections.iter(), move|connect: &'a PortConnect, index|map_south(index as u16, 1,
Map::new(||connections.iter(), move|connect: &'a Connect, index|map_south(index as u16, 1,
Fill::x(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg,
&connect.info)))))))))
}
@ -297,7 +354,7 @@ pub(crate) fn io_conns <'a, T: PortsSizes<'a>> (
) -> impl Content<TuiOut> + 'a {
Map::new(iter, move|(
index, name, connections, y, y2
): (usize, &'a Arc<str>, &'a [PortConnect], usize, usize), _|
): (usize, &'a Arc<str>, &'a [Connect], usize, usize), _|
map_south(y as u16, (y2-y) as u16, Bsp::s(
Fill::x(Tui::bold(true, wrap(bg, fg, Fill::x(Align::w("▞▞▞▞ ▞▞▞▞"))))),
Map::new(||connections.iter(), move|connect, index|map_south(index as u16, 1,

View file

@ -39,7 +39,7 @@ impl Arrangement {
pub fn view_outputs <'a> (&'a self, theme: ItemTheme) -> impl Content<TuiOut> + 'a {
let mut h = 1;
for output in self.midi_outs().iter() {
h += 1 + output.conn().len();
h += 1 + output.connections.len();
}
let h = h as u16;
let list = Bsp::s(
@ -50,8 +50,8 @@ impl Arrangement {
Align::w(Bsp::e("", Tui::fg(Rgb(255,255,255),Tui::bold(true, port.name())))),
Fill::x(Align::e(format!("{}/{} ",
port.port().get_connections().len(),
port.conn().len())))))));
for (index, conn) in port.conn().iter().enumerate() {
port.connections.len())))))));
for (index, conn) in port.connections.iter().enumerate() {
add(&Fixed::y(1, Fill::x(Align::w(format!(" c{index:02}{}", conn.info())))));
}
}
@ -72,7 +72,7 @@ impl Arrangement {
Either(true, Tui::fg(Green, ""), " · "),
Either(false, Tui::fg(Yellow, ""), " · "),
))));
for (index, conn) in port.conn().iter().enumerate() {
for (index, conn) in port.connections.iter().enumerate() {
add(&Fixed::y(1, Fill::x("")));
}
}})))}}))))))

View file

@ -43,7 +43,7 @@ impl std::fmt::Debug for Clock {
}
impl Clock {
pub fn new (jack: &Jack, bpm: Option<f64>) -> Usually<Self> {
pub fn new (jack: &Jack<'static>, bpm: Option<f64>) -> Usually<Self> {
let (chunk, transport) = jack.with_client(|c|(c.buffer_size(), c.transport()));
let timebase = Arc::new(Timebase::default());
let clock = Self {
@ -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(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", &[])?))),
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);

View file

@ -24,20 +24,29 @@ pub(crate) use Color::*;
mod device; pub use self::device::*;
mod dialog; pub use self::dialog::*;
/// Define a type alias for iterators of sized items (columns).
macro_rules! def_sizes_iter {
($Type:ident => $($Item:ty),+) => {
pub trait $Type<'a> =
Iterator<Item=(usize, $(&'a $Item,)+ usize, usize)> + Send + Sync + 'a;
}
}
#[cfg(feature = "arranger")] mod arranger; #[cfg(feature = "arranger")] pub use self::arranger::*;
#[cfg(feature = "browser")] mod browser; #[cfg(feature = "browser")] pub use self::browser::*;
#[cfg(feature = "clap")] mod clap; #[cfg(feature = "clap")] pub use self::clap::*;
#[cfg(feature = "clock")] mod clock; #[cfg(feature = "clock")] pub use self::clock::*;
#[cfg(feature = "editor")] mod editor; #[cfg(feature = "editor")] pub use self::editor::*;
#[cfg(feature = "pool")] mod pool; #[cfg(feature = "pool")] pub use self::pool::*;
#[cfg(feature = "sequencer")] mod sequencer; #[cfg(feature = "sequencer")] pub use self::sequencer::*;
#[cfg(feature = "sampler")] mod sampler; #[cfg(feature = "sampler")] pub use self::sampler::*;
#[cfg(feature = "lv2")] mod lv2; #[cfg(feature = "lv2")] pub use self::lv2::*;
#[cfg(feature = "meter")] mod meter; #[cfg(feature = "meter")] pub use self::meter::*;
#[cfg(feature = "mixer")] mod mixer; #[cfg(feature = "mixer")] pub use self::mixer::*;
#[cfg(feature = "lv2")] mod lv2; #[cfg(feature = "lv2")] pub use self::lv2::*;
#[cfg(feature = "pool")] mod pool; #[cfg(feature = "pool")] pub use self::pool::*;
#[cfg(feature = "port")] mod port; #[cfg(feature = "port")] pub use self::port::*;
#[cfg(feature = "sampler")] mod sampler; #[cfg(feature = "sampler")] pub use self::sampler::*;
#[cfg(feature = "sequencer")] mod sequencer; #[cfg(feature = "sequencer")] pub use self::sequencer::*;
#[cfg(feature = "sf2")] mod sf2; #[cfg(feature = "sf2")] pub use self::sf2::*;
#[cfg(feature = "vst2")] mod vst2; #[cfg(feature = "vst2")] pub use self::vst2::*;
#[cfg(feature = "vst3")] mod vst3; #[cfg(feature = "vst3")] pub use self::vst3::*;
#[cfg(feature = "clap")] mod clap; #[cfg(feature = "clap")] pub use self::clap::*;
pub fn button_2 <'a> (
key: impl Content<TuiOut> + 'a, label: impl Content<TuiOut> + 'a, editing: bool,

View file

@ -4,7 +4,7 @@ use crate::*;
#[derive(Debug)]
pub struct Lv2 {
/// JACK client handle (needs to not be dropped for standalone mode to work).
pub jack: Jack,
pub jack: Jack<'static>,
pub name: Arc<str>,
pub path: Option<Arc<str>>,
pub selected: usize,
@ -26,7 +26,7 @@ pub struct Lv2 {
impl Lv2 {
pub fn new (
jack: &Jack,
jack: &Jack<'static>,
name: &str,
uri: &str,
) -> Usually<Self> {

View file

@ -14,7 +14,7 @@ pub struct Pool {
}
from_dsl!(BrowserCommand: |state: Pool, iter|Ok(state.browser
.as_ref()
.map(|p|FromDsl::take_from(p, iter))
.map(|p|Namespace::take_from(p, iter))
.transpose()?
.flatten()));
impl Default for Pool {

View file

@ -0,0 +1,8 @@
mod port_audio_out; pub use self::port_audio_out::*;
mod port_audio_in; pub use self::port_audio_in::*;
mod port_connect; pub use self::port_connect::*;
mod port_midi_out; pub use self::port_midi_out::*;
mod port_midi_in; pub use self::port_midi_in::*;
pub(crate) use ConnectName::*;
pub(crate) use ConnectScope::*;
pub(crate) use ConnectStatus::*;

View file

@ -0,0 +1,44 @@
use crate::*;
#[derive(Debug)] pub struct AudioInput {
/// Handle to JACK client, for receiving reconnect events.
jack: Jack<'static>,
/// Port name
name: Arc<str>,
/// Port handle.
port: Port<AudioIn>,
/// List of ports to connect to.
pub connections: Vec<Connect>,
}
has!(Jack<'static>: |self: AudioInput|self.jack);
impl JackPort for AudioInput {
type Port = AudioIn;
type Pair = AudioOut;
fn name (&self) -> &Arc<str> {
&self.name
}
fn port (&self) -> &Port<Self::Port> {
&self.port
}
fn port_mut (&mut self) -> &mut Port<Self::Port> {
&mut self.port
}
fn into_port (self) -> Port<Self::Port> {
self.port
}
fn connections (&self) -> &[Connect] {
self.connections.as_slice()
}
fn new (jack: &Jack<'static>, name: &impl AsRef<str>, connect: &[Connect])
-> Usually<Self> where Self: Sized
{
let port = Self {
port: Self::register(jack, name)?,
jack: jack.clone(),
name: name.as_ref().into(),
connections: connect.to_vec()
};
port.connect_to_matching()?;
Ok(port)
}
}

View file

@ -0,0 +1,44 @@
use crate::*;
#[derive(Debug)] pub struct AudioOutput {
/// Handle to JACK client, for receiving reconnect events.
jack: Jack<'static>,
/// Port name
name: Arc<str>,
/// Port handle.
port: Port<AudioOut>,
/// List of ports to connect to.
pub connections: Vec<Connect>,
}
has!(Jack<'static>: |self: AudioOutput|self.jack);
impl JackPort for AudioOutput {
type Port = AudioOut;
type Pair = AudioIn;
fn name (&self) -> &Arc<str> {
&self.name
}
fn port (&self) -> &Port<Self::Port> {
&self.port
}
fn port_mut (&mut self) -> &mut Port<Self::Port> {
&mut self.port
}
fn into_port (self) -> Port<Self::Port> {
self.port
}
fn connections (&self) -> &[Connect] {
self.connections.as_slice()
}
fn new (jack: &Jack<'static>, name: &impl AsRef<str>, connect: &[Connect])
-> Usually<Self> where Self: Sized
{
let port = Self {
port: Self::register(jack, name)?,
jack: jack.clone(),
name: name.as_ref().into(),
connections: connect.to_vec()
};
port.connect_to_matching()?;
Ok(port)
}
}

View file

@ -1,28 +1,22 @@
use crate::*;
use super::*;
pub trait JackPort<'j>: HasJack<'j> {
type Port: PortSpec;
type Pair: PortSpec;
fn port (&self) -> &Port<Self::Port>;
}
pub trait ConnectTo<'j, T>: JackPort<'j> {
fn connect_to (&'j self, to: &T) -> Usually<ConnectStatus>;
}
#[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>> {
pub trait JackPort: HasJack<'static> {
type Port: PortSpec + Default;
type Pair: PortSpec + Default;
fn new (jack: &Jack<'static>, name: &impl AsRef<str>, connect: &[Connect])
-> Usually<Self> where Self: Sized;
fn register (jack: &Jack<'static>, name: &impl AsRef<str>) -> Usually<Port<Self::Port>> {
Ok(jack.register_port::<Self::Port>(name.as_ref())?)
}
fn name (&self) -> &Arc<str>;
fn connections (&self) -> &[Connect];
fn port (&self) -> &Port<Self::Port>;
fn port_mut (&mut self) -> &mut Port<Self::Port>;
fn into_port (self) -> Port<Self::Port> where Self: Sized;
fn close (self) -> Usually<()> where Self: Sized {
let jack = self.jack().clone();
Ok(jack.with_client(|c|c.unregister_port(self.into_port()))?)
}
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))
}
@ -32,7 +26,7 @@ pub trait ConnectAuto<'j>: JackPort<'j> + ConnectTo<'j, &'j Port<Unowned>> {
fn port_by_name (&self, name: impl AsRef<str>) -> Option<Port<Unowned>> {
self.with_client(|c|c.port_by_name(name.as_ref()))
}
fn connect_to_matching (&self) -> Usually<()> {
fn connect_to_matching <'k> (&'k self) -> Usually<()> {
for connect in self.connections().iter() {
//panic!("{connect:?}");
let status = match &connect.name {
@ -43,15 +37,15 @@ pub trait ConnectAuto<'j>: JackPort<'j> + ConnectTo<'j, &'j Port<Unowned>> {
}
Ok(())
}
fn connect_exact (&self, name: &str) ->
fn connect_exact <'k> (&'k self, name: &str) ->
Usually<Vec<(Port<Unowned>, Arc<str>, ConnectStatus)>>
{
self.with_client(|c|{
self.with_client(move|c|{
let mut status = vec![];
for port in c.ports(None, None, PortFlags::empty()).iter() {
if port.as_str() == &*name {
if let Some(port) = c.port_by_name(port.as_str()) {
let port_status = self.connect_to(&port)?;
let port_status = self.connect_to_unowned(&port)?;
let name = port.name()?.into();
status.push((port, name, port_status));
if port_status == Connected {
@ -63,15 +57,15 @@ pub trait ConnectAuto<'j>: JackPort<'j> + ConnectTo<'j, &'j Port<Unowned>> {
Ok(status)
})
}
fn connect_regexp (
&self, re: &str, scope: ConnectScope
fn connect_regexp <'k> (
&'k self, re: &str, scope: ConnectScope
) -> Usually<Vec<(Port<Unowned>, Arc<str>, ConnectStatus)>> {
self.with_client(|c|{
self.with_client(move|c|{
let mut status = vec![];
let ports = c.ports(Some(&re), None, PortFlags::empty());
for port in ports.iter() {
if let Some(port) = c.port_by_name(port.as_str()) {
let port_status = self.connect_to(&port)?;
let port_status = self.connect_to_unowned(&port)?;
let name = port.name()?.into();
status.push((port, name, port_status));
if port_status == Connected && scope == One {
@ -82,6 +76,34 @@ pub trait ConnectAuto<'j>: JackPort<'j> + ConnectTo<'j, &'j Port<Unowned>> {
Ok(status)
})
}
/** Connect to a matching port by name. */
fn connect_to_name (&self, name: impl AsRef<str>) -> Usually<ConnectStatus> {
self.with_client(|c|if let Some(ref port) = c.port_by_name(name.as_ref()) {
self.connect_to_unowned(port)
} else {
Ok(Missing)
})
}
/** Connect to a matching port by reference. */
fn connect_to_unowned (&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()) {
Connected
} else {
Mismatch
}))
}
/** Connect to an owned matching port by reference. */
fn connect_to_owned (&self, port: &Port<Self::Pair>) -> 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()) {
Connected
} else {
Mismatch
}))
}
}
#[derive(Clone, Debug, PartialEq)]

View file

@ -2,99 +2,56 @@ use crate::*;
//impl_port!(MidiInput: MidiOut -> MidiIn |j, n|j.register_port::<MidiOut>(n));
#[derive(Debug)] pub struct MidiInput<'j> {
#[derive(Debug)] pub struct MidiInput {
/// Handle to JACK client, for receiving reconnect events.
jack: Jack<'j>,
jack: Jack<'static>,
/// Port name
name: Arc<str>,
/// Port handle.
port: Port<MidiOut>,
port: Port<MidiIn>,
/// List of currently held notes.
held: Arc<RwLock<[bool;128]>>,
/// List of ports to connect to.
connections: Vec<Connect>
pub 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>
has!(Jack<'static>: |self: MidiInput|self.jack);
impl JackPort for MidiInput {
type Port = MidiIn;
type Pair = MidiOut;
fn name (&self) -> &Arc<str> {
&self.name
}
fn port (&self) -> &Port<Self::Port> {
&self.port
}
fn port_mut (&mut self) -> &mut Port<Self::Port> {
&mut self.port
}
fn into_port (self) -> Port<Self::Port> {
self.port
}
fn connections (&self) -> &[Connect] {
self.connections.as_slice()
}
fn new (jack: &Jack<'static>, name: &impl AsRef<str>, connect: &[Connect])
-> Usually<Self> where Self: Sized
{
let port = Self {
port: jack.register_port::<MidiIn>(name.as_ref())?,
jack,
name: name.into(),
connections: connect.to_vec()
port: Self::register(jack, name)?,
jack: jack.clone(),
name: name.as_ref().into(),
connections: connect.to_vec(),
held: Arc::new(RwLock::new([false;128]))
};
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))?)
}
}
impl MidiInput {
pub fn parsed <'a> (&'a self, scope: &'a ProcessScope) -> impl Iterator<Item=(usize, LiveEvent<'a>, &'a [u8])> {
parse_midi_input(self.port().iter(scope))
}
}
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
}
}
#[tengri_proc::command(MidiInput)]
impl MidiInputCommand {
@ -127,8 +84,8 @@ pub trait HasMidiIns {
{
let mut y = 0;
self.midi_ins().iter().enumerate().map(move|(i, input)|{
let height = 1 + input.conn().len();
let data = (i, input.name(), input.conn(), y, y + height);
let height = 1 + input.connections().len();
let data = (i, input.name(), input.connections(), y, y + height);
y += height;
data
})
@ -137,7 +94,7 @@ pub trait HasMidiIns {
pub type CollectedMidiInput<'a> = Vec<Vec<(u32, Result<LiveEvent<'a>, MidiError>)>>;
impl<'j, T: HasMidiIns + HasJack<'j>> AddMidiIn for T {
impl<T: HasMidiIns + HasJack<'static>> AddMidiIn for T {
fn midi_in_add (&mut self) -> Usually<()> {
let index = self.midi_ins().len();
let port = MidiInput::new(self.jack(), &format!("M/{index}"), &[])?;

View file

@ -8,7 +8,7 @@ use crate::*;
/// Port handle.
port: Port<MidiOut>,
/// List of ports to connect to.
conn: Vec<Connect>,
pub connections: Vec<Connect>,
/// List of currently held notes.
held: Arc<RwLock<[bool;128]>>,
/// Buffer
@ -16,22 +16,37 @@ use crate::*;
/// Buffer
output_buffer: Vec<Vec<Vec<u8>>>,
}
has!(Jack<'static>: |self: MidiOutput|self.jack);
impl MidiOutput {
pub fn new (jack: &Jack, name: impl AsRef<str>, connect: &[Connect])
-> Usually<Self>
impl JackPort for MidiOutput {
type Port = MidiOut;
type Pair = MidiIn;
fn name (&self) -> &Arc<str> {
&self.name
}
fn port (&self) -> &Port<Self::Port> {
&self.port
}
fn port_mut (&mut self) -> &mut Port<Self::Port> {
&mut self.port
}
fn into_port (self) -> Port<Self::Port> {
self.port
}
fn connections (&self) -> &[Connect] {
self.connections.as_slice()
}
fn new (jack: &Jack<'static>, name: &impl AsRef<str>, connect: &[Connect])
-> Usually<Self> where Self: Sized
{
let port = Self::register(jack, name)?;
let jack = jack.clone();
let port = jack.register_port::<MidiOut>(name.as_ref())?;
let name = name.as_ref().into();
let conn = connect.to_vec();
let connections = connect.to_vec();
let port = Self {
jack,
port,
name,
conn,
connections,
held: Arc::new([false;128].into()),
note_buffer: vec![0;8],
output_buffer: vec![vec![];65536],
@ -39,22 +54,8 @@ impl MidiOutput {
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))?)
}
}
impl MidiOutput {
/// Clear the section of the output buffer that we will be using,
/// emitting "all notes off" at start of buffer if requested.
pub fn buffer_clear (&mut self, scope: &ProcessScope, reset: bool) {
@ -94,58 +95,6 @@ impl MidiOutput {
}
}
impl AsRef<Port<MidiOut>> for MidiOutput {
fn as_ref (&self) -> &Port<MidiOut> {
&self.port
}
}
impl JackPort<'static> for MidiOutput {
type Port = MidiOut;
type Pair = MidiIn;
fn port (&self) -> &Port<MidiOut> { &self.port }
}
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 {
Ok(Missing)
})
}
}
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) {
Connected
} else {
Mismatch
}))
}
}
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) {
Connected
} else {
Mismatch
}))
}
}
impl ConnectAuto<'static> for MidiOutput {
fn connections (&self) -> &[Connect] {
&self.conn
}
}
#[tengri_proc::command(MidiOutput)]
impl MidiOutputCommand {
fn _todo_ (_port: &mut MidiOutput) -> Perhaps<Self> { Ok(None) }
@ -170,8 +119,8 @@ pub trait HasMidiOuts {
{
let mut y = 0;
self.midi_outs().iter().enumerate().map(move|(i, output)|{
let height = 1 + output.conn().len();
let data = (i, output.name(), output.conn(), y, y + height);
let height = 1 + output.connections().len();
let data = (i, output.name(), output.connections(), y, y + height);
y += height;
data
})
@ -184,7 +133,7 @@ pub trait HasMidiOuts {
}
/// Trail for thing that may gain new MIDI ports.
impl<'j, T: HasMidiOuts + HasJack<'j>> AddMidiOut for T {
impl<T: HasMidiOuts + HasJack<'static>> AddMidiOut for T {
fn midi_out_add (&mut self) -> Usually<()> {
let index = self.midi_outs().len();
let port = MidiOutput::new(self.jack(), &format!("{index}/M"), &[])?;

View file

@ -49,16 +49,16 @@ pub struct Sampler {
impl Sampler {
pub fn new (
jack: &Jack,
jack: &Jack<'static>,
name: impl AsRef<str>,
midi_from: &[PortConnect],
audio_from: &[&[PortConnect];2],
audio_to: &[&[PortConnect];2],
midi_from: &[Connect],
audio_from: &[&[Connect];2],
audio_to: &[&[Connect];2],
) -> Usually<Self> {
let name = name.as_ref();
Ok(Self {
name: name.into(),
midi_in: MidiInput::new(jack, format!("M/{name}"), midi_from)?,
midi_in: MidiInput::new(jack, &format!("M/{name}"), midi_from)?,
audio_ins: vec![
AudioInput::new(jack, &format!("L/{name}"), audio_from[0])?,
AudioInput::new(jack, &format!("R/{name}"), audio_from[1])?,

View file

@ -70,17 +70,17 @@ impl Default for Sequencer {
impl Sequencer {
pub fn new (
name: impl AsRef<str>,
jack: &Jack,
jack: &Jack<'static>,
clock: Option<&Clock>,
clip: Option<&Arc<RwLock<MidiClip>>>,
midi_from: &[PortConnect],
midi_to: &[PortConnect],
midi_from: &[Connect],
midi_to: &[Connect],
) -> Usually<Self> {
let _name = name.as_ref();
let clock = clock.cloned().unwrap_or_default();
Ok(Self {
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)?, ],
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,
@ -101,9 +101,9 @@ impl std::fmt::Debug for Sequencer {
}
}
has!(Clock: |self: Sequencer|self.clock);
has!(Vec<MidiInput>: |self:Sequencer| self.midi_ins);
has!(Vec<MidiOutput>: |self:Sequencer| self.midi_outs);
has!(Clock: |self:Sequencer|self.clock);
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]>> {

View file

@ -1,2 +0,0 @@
mod audio_in; pub use self::audio_in::*;
mod audio_out; pub use self::audio_out::*;

View file

@ -1,86 +0,0 @@
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
}
}

View file

@ -1,77 +0,0 @@
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
}
}

View file

@ -1,18 +1,225 @@
use crate::*;
pub use ::jack::{*, contrib::{*, ClosureProcessHandler}};
//contrib::ClosureProcessHandler,
//NotificationHandler,
//Client, AsyncClient, ClientOptions, ClientStatus,
//ProcessScope, Control, Frames,
//Port, PortId, PortSpec, PortFlags,
//Unowned, MidiIn, MidiOut, AudioIn, AudioOut,
//};
pub(crate) use ConnectName::*;
pub(crate) use ConnectScope::*;
pub(crate) use ConnectStatus::*;
pub(crate) use std::sync::{Arc, RwLock};
/// 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
}
}
}
mod jack_client; pub use self::jack_client::*;
mod jack_device; pub use self::jack_device::*;
mod jack_handler; pub use self::jack_handler::*;
mod jack_port; pub use self::jack_port::*;
/// 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>;
use self::JackState::*;
impl<'j, T: Has<Jack<'j>>> HasJack<'j> for T {
fn jack (&self) -> &Jack<'j> { self.get() }
}
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 }
}
/// Things that can provide a [jack::Client] reference.
pub trait HasJack<'j> {
/// Return the internal [jack::Client] handle
/// that lets you call the JACK API.
fn jack (&self) -> &Jack<'j>;
/// Run the JACK thread.
fn run <T: Audio + Send + Sync + 'static> (
&self, callback: impl FnOnce(&Jack)->Usually<T>
) -> Usually<Arc<RwLock<T>>> {
let jack = self.jack();
let app = Arc::new(RwLock::new(callback(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),
// 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() {
Inert => panic!("jack client not activated"),
Inactive(ref client) => op(client),
Activating => panic!("jack client has not finished activation"),
Active(ref client) => op(client.as_client()),
}
}
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(())
}
}
/// Wraps [JackState] and through it [jack::Client].
#[derive(Clone, Debug, Default)]
pub struct Jack<'j> {
pub state: Arc<RwLock<JackState<'j>>>
}
impl<'j> Jack<'j> {
pub fn new (name: &str) -> Usually<Self> {
Ok(Self {
state: JackState::new(Client::new(name, ClientOptions::NO_START_SERVER)?.0)
})
}
}
/// 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<'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>),
}
impl<'j> JackState<'j> {
fn new (client: Client) -> Arc<RwLock<Self>> {
Arc::new(RwLock::new(Self::Inactive(client)))
}
}

View file

@ -1,122 +0,0 @@
use crate::*;
use super::*;
use self::JackState::*;
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<'j> {
/// Return the internal [jack::Client] handle
/// that lets you call the JACK API.
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() {
Inert => panic!("jack client not activated"),
Inactive(ref client) => op(client),
Activating => panic!("jack client has not finished activation"),
Active(ref client) => op(client.as_client()),
}
}
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, cb: impl Fn(TimebaseInfo)->Position) -> Usually<()> {
if enable {
self.with_client(|client|match client.register_timebase_callback(false, cb) {
Ok(_) => Ok(()),
Err(e) => Err(e)
})?
}
Ok(())
}
fn sync_follow (&self, _enable: bool) -> Usually<()> {
// TODO: sync follow
Ok(())
}
}
/// Wraps [JackState] and through it [jack::Client].
#[derive(Clone, Debug, Default)]
pub struct Jack<'j> {
pub state: Arc<RwLock<JackState<'j>>>
}
impl<'j> Jack<'j> {
pub fn new (name: &str) -> Usually<Self> {
Ok(Self {
state: JackState::new(Client::new(name, ClientOptions::NO_START_SERVER)?.0)
})
}
}
/// 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<'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>),
}
impl<'j> JackState<'j> {
fn new (client: Client) -> Arc<RwLock<Self>> {
Arc::new(RwLock::new(Self::Inactive(client)))
}
}

View file

@ -1,86 +0,0 @@
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,
//}
//impl<E: Engine> std::fmt::Debug for JackDevice<E> {
//fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
//f.debug_struct("JackDevice")
//.field("ports", &self.ports)
//.finish()
//}
//}
//impl<E: Engine> Render for JackDevice<E> {
//type Engine = E;
//fn min_size(&self, to: E::Size) -> Perhaps<E::Size> {
//self.state.read().unwrap().layout(to)
//}
//fn render(&self, to: &mut E::Output) -> Usually<()> {
//self.state.read().unwrap().render(to)
//}
//}
//impl<E: Engine> Handle<E> for JackDevice<E> {
//fn handle(&mut self, from: &E::Input) -> Perhaps<E::Handled> {
//self.state.write().unwrap().handle(from)
//}
//}
//impl<E: Engine> Ports for JackDevice<E> {
//fn audio_ins (&self) -> Usually<Vec<&Port<Unowned>>> {
//Ok(self.ports.audio_ins.values().collect())
//}
//fn audio_outs (&self) -> Usually<Vec<&Port<Unowned>>> {
//Ok(self.ports.audio_outs.values().collect())
//}
//fn midi_ins (&self) -> Usually<Vec<&Port<Unowned>>> {
//Ok(self.ports.midi_ins.values().collect())
//}
//fn midi_outs (&self) -> Usually<Vec<&Port<Unowned>>> {
//Ok(self.ports.midi_outs.values().collect())
//}
//}
//impl<E: Engine> JackDevice<E> {
///// Returns a locked mutex of the state's contents.
//pub fn state(&self) -> LockResult<RwLockReadGuard<Box<dyn AudioComponent<E>>>> {
//self.state.read()
//}
///// Returns a locked mutex of the state's contents.
//pub fn state_mut(&self) -> LockResult<RwLockWriteGuard<Box<dyn AudioComponent<E>>>> {
//self.state.write()
//}
//pub fn connect_midi_in(&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
//Ok(self
//.client
//.as_client()
//.connect_ports(port, self.midi_ins()?[index])?)
//}
//pub fn connect_midi_out(&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
//Ok(self
//.client
//.as_client()
//.connect_ports(self.midi_outs()?[index], port)?)
//}
//pub fn connect_audio_in(&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
//Ok(self
//.client
//.as_client()
//.connect_ports(port, self.audio_ins()?[index])?)
//}
//pub fn connect_audio_out(&self, index: usize, port: &Port<Unowned>) -> Usually<()> {
//Ok(self
//.client
//.as_client()
//.connect_ports(self.audio_outs()?[index], port)?)
//}
//}

View file

@ -1,105 +0,0 @@
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>;

View file

@ -88,7 +88,6 @@ 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;

View file

@ -9,10 +9,6 @@ pub use ::midly::{
live::*,
};
mod midi_in; pub use self::midi_in::*;
mod midi_out; pub use self::midi_out::*;
mod midi_hold; pub use self::midi_hold::*;
/// Return boxed iterator of MIDI events
pub fn parse_midi_input <'a> (input: ::jack::MidiIter<'a>) -> Box<dyn Iterator<Item=(usize, LiveEvent<'a>, &'a [u8])> + 'a> {
Box::new(input.map(|::jack::RawMidi { time, bytes }|(
@ -30,3 +26,12 @@ pub fn all_notes_off (output: &mut [Vec<Vec<u8>>]) {
evt.write(&mut buf).unwrap();
output[0].push(buf);
}
/// Update notes_in array
pub fn update_keys (keys: &mut[bool;128], message: &MidiMessage) {
match message {
MidiMessage::NoteOn { key, .. } => { keys[key.as_int() as usize] = true; }
MidiMessage::NoteOff { key, .. } => { keys[key.as_int() as usize] = false; },
_ => {}
}
}

View file

@ -1,10 +0,0 @@
use crate::*;
/// Update notes_in array
pub fn update_keys (keys: &mut[bool;128], message: &MidiMessage) {
match message {
MidiMessage::NoteOn { key, .. } => { keys[key.as_int() as usize] = true; }
MidiMessage::NoteOff { key, .. } => { keys[key.as_int() as usize] = false; },
_ => {}
}
}

2
deps/tengri vendored

@ -1 +1 @@
Subproject commit 455d6d00d5f91e7f9f6b9d3711aa47e09900ad46
Subproject commit f714302f21d64ef5f6dcbee09aa913e8e3b9fbc5