mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
Compare commits
2 commits
447638ee71
...
0192d85a19
| Author | SHA1 | Date | |
|---|---|---|---|
| 0192d85a19 | |||
| cb7e4f7a95 |
33 changed files with 793 additions and 1055 deletions
|
|
@ -1,5 +1,9 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
impl HasJack<'static> for App {
|
||||||
|
fn jack (&self) -> &Jack<'static> {
|
||||||
|
&self.jack
|
||||||
|
}
|
||||||
|
}
|
||||||
audio!(
|
audio!(
|
||||||
|self: App, client, scope|{
|
|self: App, client, scope|{
|
||||||
let t0 = self.perf.get_t0();
|
let t0 = self.perf.get_t0();
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct App {
|
pub struct App {
|
||||||
/// Must not be dropped for the duration of the process
|
/// 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
|
/// Display size
|
||||||
pub size: Measure<TuiOut>,
|
pub size: Measure<TuiOut>,
|
||||||
/// Performance counter
|
/// Performance counter
|
||||||
|
|
@ -21,13 +18,10 @@ pub struct App {
|
||||||
pub history: Vec<(AppCommand, Option<AppCommand>)>,
|
pub history: Vec<(AppCommand, Option<AppCommand>)>,
|
||||||
// Dialog overlay
|
// Dialog overlay
|
||||||
pub dialog: Option<Dialog>,
|
pub dialog: Option<Dialog>,
|
||||||
// Cache of formatted strings
|
|
||||||
pub view_cache: Arc<RwLock<ViewCache>>,
|
|
||||||
/// Base color.
|
/// Base color.
|
||||||
pub color: ItemTheme,
|
pub color: ItemTheme,
|
||||||
}
|
}
|
||||||
|
has!(Jack<'static>: |self: App|self.jack);
|
||||||
has!(Jack: |self: App|self.jack);
|
|
||||||
has!(Pool: |self: App|self.pool);
|
has!(Pool: |self: App|self.pool);
|
||||||
has!(Option<Dialog>: |self: App|self.dialog);
|
has!(Option<Dialog>: |self: App|self.dialog);
|
||||||
has!(Clock: |self: App|self.project.clock);
|
has!(Clock: |self: App|self.project.clock);
|
||||||
|
|
@ -41,31 +35,19 @@ 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(&self.project) };
|
||||||
{ MaybeHas::<Track>::get_mut(&mut self.project) });
|
{ MaybeHas::<Track>::get_mut(&mut self.project) });
|
||||||
impl HasTrackScroll for App {
|
impl HasTrackScroll for App { fn track_scroll (&self) -> usize { self.project.track_scroll() } }
|
||||||
fn track_scroll (&self) -> usize { self.project.track_scroll() }
|
|
||||||
}
|
|
||||||
maybe_has!(Scene: |self: App|
|
maybe_has!(Scene: |self: App|
|
||||||
{ MaybeHas::<Scene>::get(&self.project) };
|
{ MaybeHas::<Scene>::get(&self.project) };
|
||||||
{ MaybeHas::<Scene>::get_mut(&mut self.project) });
|
{ MaybeHas::<Scene>::get_mut(&mut self.project) });
|
||||||
impl HasSceneScroll for App {
|
impl HasSceneScroll for App { fn scene_scroll (&self) -> usize { self.project.scene_scroll() } }
|
||||||
fn scene_scroll (&self) -> usize { self.project.scene_scroll() }
|
|
||||||
}
|
|
||||||
has_clips!(|self: App|self.pool.clips);
|
has_clips!(|self: App|self.pool.clips);
|
||||||
impl HasClipsSize for App {
|
impl HasClipsSize for App { fn clips_size (&self) -> &Measure<TuiOut> { &self.project.inner_size } }
|
||||||
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!(ClockCommand:
|
from_dsl!(PoolCommand: |state: App, iter|Namespace::take_from(&state.pool, iter));
|
||||||
|state: App, iter|FromDsl::take_from(state.clock(), iter));
|
from_dsl!(SamplerCommand: |state: App, iter|Ok(state.project.sampler().map(|x|Namespace::take_from(x, iter)).transpose()?.flatten()));
|
||||||
from_dsl!(MidiEditCommand:
|
from_dsl!(ArrangementCommand: |state: App, iter|Namespace::take_from(&state.project, iter));
|
||||||
|state: App, iter|Ok(state.editor().map(|x|FromDsl::take_from(x, iter)).transpose()?.flatten()));
|
from_dsl!(DialogCommand: |state: App, iter|Namespace::take_from(&state.dialog, iter));
|
||||||
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|{
|
//has_editor!(|self: App|{
|
||||||
//editor = self.editor;
|
//editor = self.editor;
|
||||||
//editor_w = {
|
//editor_w = {
|
||||||
|
|
@ -81,7 +63,7 @@ from_dsl!(DialogCommand:
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub fn update_clock (&self) {
|
pub fn update_clock (&self) {
|
||||||
ViewCache::update_clock(&self.view_cache, self.clock(), self.size.w() > 80)
|
ViewCache::update_clock(&self.project.clock.view_cache, self.clock(), self.size.w() > 80)
|
||||||
}
|
}
|
||||||
pub fn toggle_dialog (&mut self, mut dialog: Option<Dialog>) -> Option<Dialog> {
|
pub fn toggle_dialog (&mut self, mut dialog: Option<Dialog>) -> Option<Dialog> {
|
||||||
std::mem::swap(&mut self.dialog, &mut dialog);
|
std::mem::swap(&mut self.dialog, &mut dialog);
|
||||||
|
|
@ -118,10 +100,10 @@ impl App {
|
||||||
}
|
}
|
||||||
fn device_add_sampler (&mut self) -> Usually<()> {
|
fn device_add_sampler (&mut self) -> Usually<()> {
|
||||||
let name = self.jack.with_client(|c|c.name().to_string());
|
let name = self.jack.with_client(|c|c.name().to_string());
|
||||||
let midi = self.project.track().expect("no active track").sequencer.midi_outs[0].name();
|
let midi = self.project.track().expect("no active track").sequencer.midi_outs[0].port_name();
|
||||||
let track = self.track().expect("no active track");
|
let track = self.track().expect("no active track");
|
||||||
let port = format!("{}/Sampler", &track.name);
|
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(
|
let sampler = if let Ok(sampler) = Sampler::new(
|
||||||
&self.jack, &port, &[connect], &[&[], &[]], &[&[], &[]]
|
&self.jack, &port, &[connect], &[&[], &[]], &[&[], &[]]
|
||||||
) {
|
) {
|
||||||
|
|
@ -464,7 +446,7 @@ impl Configuration {
|
||||||
let cond = cond.unwrap();
|
let cond = cond.unwrap();
|
||||||
println!("ok");
|
println!("ok");
|
||||||
map.add_layer_if(
|
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(),
|
state, &mut exp.clone(),
|
||||||
format!("missing input layer conditional")
|
format!("missing input layer conditional")
|
||||||
)), keys
|
)), keys
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ pub(crate) use ::tengri::tui::ratatui::prelude::Position;
|
||||||
impl App {
|
impl App {
|
||||||
pub fn view (&self) -> impl Content<TuiOut> + '_ {
|
pub fn view (&self) -> impl Content<TuiOut> + '_ {
|
||||||
let view: Perhaps<Box<dyn Render<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(),
|
Either(view.is_ok(),
|
||||||
ThunkRender::new(move|to|if let Some(view) = view.as_ref().unwrap().as_ref() {
|
ThunkRender::new(move|to|if let Some(view) = view.as_ref().unwrap().as_ref() {
|
||||||
Content::render(view, to)
|
Content::render(view, to)
|
||||||
|
|
@ -60,7 +60,7 @@ impl App {
|
||||||
)));
|
)));
|
||||||
add(&" ");
|
add(&" ");
|
||||||
{
|
{
|
||||||
let cache = self.view_cache.read().unwrap();
|
let cache = self.project.clock.view_cache.read().unwrap();
|
||||||
add(&Fixed::x(15, Align::w(Bsp::s(
|
add(&Fixed::x(15, Align::w(Bsp::s(
|
||||||
FieldH(theme, "Beat", cache.beat.view.clone()),
|
FieldH(theme, "Beat", cache.beat.view.clone()),
|
||||||
FieldH(theme, "Time", cache.time.view.clone()),
|
FieldH(theme, "Time", cache.time.view.clone()),
|
||||||
|
|
@ -90,7 +90,7 @@ impl App {
|
||||||
}
|
}
|
||||||
pub fn view_status_v (&self) -> impl Content<TuiOut> + use<'_> {
|
pub fn view_status_v (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
self.update_clock();
|
self.update_clock();
|
||||||
let cache = self.view_cache.read().unwrap();
|
let cache = self.project.clock.view_cache.read().unwrap();
|
||||||
let theme = self.color;
|
let theme = self.color;
|
||||||
let playing = self.clock().is_rolling();
|
let playing = self.clock().is_rolling();
|
||||||
Tui::bg(theme.darker.rgb, Fixed::xy(20, 5, Outer(true, Style::default().fg(Tui::g(96))).enclose(
|
Tui::bg(theme.darker.rgb, Fixed::xy(20, 5, Outer(true, Style::default().fg(Tui::g(96))).enclose(
|
||||||
|
|
@ -120,13 +120,13 @@ impl App {
|
||||||
}
|
}
|
||||||
pub fn view_status (&self) -> impl Content<TuiOut> + use<'_> {
|
pub fn view_status (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
self.update_clock();
|
self.update_clock();
|
||||||
let cache = self.view_cache.read().unwrap();
|
let cache = self.project.clock.view_cache.read().unwrap();
|
||||||
view_status(Some(self.project.selection.describe(self.tracks(), self.scenes())),
|
view_status(Some(self.project.selection.describe(self.tracks(), self.scenes())),
|
||||||
cache.sr.view.clone(), cache.buf.view.clone(), cache.lat.view.clone())
|
cache.sr.view.clone(), cache.buf.view.clone(), cache.lat.view.clone())
|
||||||
}
|
}
|
||||||
pub fn view_transport (&self) -> impl Content<TuiOut> + use<'_> {
|
pub fn view_transport (&self) -> impl Content<TuiOut> + use<'_> {
|
||||||
self.update_clock();
|
self.update_clock();
|
||||||
let cache = self.view_cache.read().unwrap();
|
let cache = self.project.clock.view_cache.read().unwrap();
|
||||||
view_transport(self.project.clock.is_rolling(),
|
view_transport(self.project.clock.is_rolling(),
|
||||||
cache.bpm.view.clone(), cache.beat.view.clone(), cache.time.view.clone())
|
cache.bpm.view.clone(), cache.beat.view.clone(), cache.time.view.clone())
|
||||||
}
|
}
|
||||||
|
|
@ -225,122 +225,3 @@ impl ScenesView for App {
|
||||||
(self.width() as u16).saturating_sub(self.w_side())
|
(self.width() as u16).saturating_sub(self.w_side())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clear a pre-allocated buffer, then write into it.
|
|
||||||
#[macro_export] macro_rules! rewrite {
|
|
||||||
($buf:ident, $($rest:tt)*) => { |$buf,_,_|{ $buf.clear(); write!($buf, $($rest)*) } }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)] pub(crate) struct ViewMemo<T, U> {
|
|
||||||
pub(crate) value: T,
|
|
||||||
pub(crate) view: Arc<RwLock<U>>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: PartialEq, U> ViewMemo<T, U> {
|
|
||||||
fn new (value: T, view: U) -> Self {
|
|
||||||
Self { value, view: Arc::new(view.into()) }
|
|
||||||
}
|
|
||||||
pub(crate) fn update <R> (
|
|
||||||
&mut self,
|
|
||||||
newval: T,
|
|
||||||
render: impl Fn(&mut U, &T, &T)->R
|
|
||||||
) -> Option<R> {
|
|
||||||
if newval != self.value {
|
|
||||||
let result = render(&mut*self.view.write().unwrap(), &newval, &self.value);
|
|
||||||
self.value = newval;
|
|
||||||
return Some(result);
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)] pub struct ViewCache {
|
|
||||||
pub(crate) sr: ViewMemo<Option<(bool, f64)>, String>,
|
|
||||||
pub(crate) buf: ViewMemo<Option<f64>, String>,
|
|
||||||
pub(crate) lat: ViewMemo<Option<f64>, String>,
|
|
||||||
pub(crate) bpm: ViewMemo<Option<f64>, String>,
|
|
||||||
pub(crate) beat: ViewMemo<Option<f64>, String>,
|
|
||||||
pub(crate) time: ViewMemo<Option<f64>, String>,
|
|
||||||
pub(crate) scns: ViewMemo<Option<(usize, usize)>, String>,
|
|
||||||
pub(crate) trks: ViewMemo<Option<(usize, usize)>, String>,
|
|
||||||
pub(crate) stop: Arc<str>,
|
|
||||||
pub(crate) edit: Arc<str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ViewCache {
|
|
||||||
fn default () -> Self {
|
|
||||||
let mut beat = String::with_capacity(16);
|
|
||||||
write!(beat, "{}", Self::BEAT_EMPTY);
|
|
||||||
let mut time = String::with_capacity(16);
|
|
||||||
write!(time, "{}", Self::TIME_EMPTY);
|
|
||||||
let mut bpm = String::with_capacity(16);
|
|
||||||
write!(bpm, "{}", Self::BPM_EMPTY);
|
|
||||||
Self {
|
|
||||||
beat: ViewMemo::new(None, beat),
|
|
||||||
time: ViewMemo::new(None, time),
|
|
||||||
bpm: ViewMemo::new(None, bpm),
|
|
||||||
sr: ViewMemo::new(None, String::with_capacity(16)),
|
|
||||||
buf: ViewMemo::new(None, String::with_capacity(16)),
|
|
||||||
lat: ViewMemo::new(None, String::with_capacity(16)),
|
|
||||||
scns: ViewMemo::new(None, String::with_capacity(16)),
|
|
||||||
trks: ViewMemo::new(None, String::with_capacity(16)),
|
|
||||||
stop: "⏹".into(),
|
|
||||||
edit: "edit".into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ViewCache {
|
|
||||||
pub const BEAT_EMPTY: &'static str = "-.-.--";
|
|
||||||
pub const TIME_EMPTY: &'static str = "-.---s";
|
|
||||||
pub const BPM_EMPTY: &'static str = "---.---";
|
|
||||||
|
|
||||||
pub fn track_counter (cache: &Arc<RwLock<Self>>, track: usize, tracks: usize)
|
|
||||||
-> Arc<RwLock<String>>
|
|
||||||
{
|
|
||||||
let data = (track, tracks);
|
|
||||||
cache.write().unwrap().trks.update(Some(data), rewrite!(buf, "{}/{}", data.0, data.1));
|
|
||||||
cache.read().unwrap().trks.view.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scene_add (cache: &Arc<RwLock<Self>>, scene: usize, scenes: usize, is_editing: bool)
|
|
||||||
-> impl Content<TuiOut>
|
|
||||||
{
|
|
||||||
let data = (scene, scenes);
|
|
||||||
cache.write().unwrap().scns.update(Some(data), rewrite!(buf, "({}/{})", data.0, data.1));
|
|
||||||
button_3("S", "add scene", cache.read().unwrap().scns.view.clone(), is_editing)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_clock (cache: &Arc<RwLock<Self>>, clock: &Clock, compact: bool) {
|
|
||||||
let rate = clock.timebase.sr.get();
|
|
||||||
let chunk = clock.chunk.load(Relaxed) as f64;
|
|
||||||
let lat = chunk / rate * 1000.;
|
|
||||||
let delta = |start: &Moment|clock.global.usec.get() - start.usec.get();
|
|
||||||
let mut cache = cache.write().unwrap();
|
|
||||||
cache.buf.update(Some(chunk), rewrite!(buf, "{chunk}"));
|
|
||||||
cache.lat.update(Some(lat), rewrite!(buf, "{lat:.1}ms"));
|
|
||||||
cache.sr.update(Some((compact, rate)), |buf,_,_|{
|
|
||||||
buf.clear();
|
|
||||||
if compact {
|
|
||||||
write!(buf, "{:.1}kHz", rate / 1000.)
|
|
||||||
} else {
|
|
||||||
write!(buf, "{:.0}Hz", rate)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if let Some(now) = clock.started.read().unwrap().as_ref().map(delta) {
|
|
||||||
let pulse = clock.timebase.usecs_to_pulse(now);
|
|
||||||
let time = now/1000000.;
|
|
||||||
let bpm = clock.timebase.bpm.get();
|
|
||||||
cache.beat.update(Some(pulse), |buf, _, _|{
|
|
||||||
buf.clear();
|
|
||||||
clock.timebase.format_beats_1_to(buf, pulse)
|
|
||||||
});
|
|
||||||
cache.time.update(Some(time), rewrite!(buf, "{:.3}s", time));
|
|
||||||
cache.bpm.update(Some(bpm), rewrite!(buf, "{:.3}", bpm));
|
|
||||||
} else {
|
|
||||||
cache.beat.update(None, rewrite!(buf, "{}", ViewCache::BEAT_EMPTY));
|
|
||||||
cache.time.update(None, rewrite!(buf, "{}", ViewCache::TIME_EMPTY));
|
|
||||||
cache.bpm.update(None, rewrite!(buf, "{}", ViewCache::BPM_EMPTY));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -78,25 +78,23 @@ impl Cli {
|
||||||
let mut midi_outs = vec![];
|
let mut midi_outs = vec![];
|
||||||
let mut tracks = vec![];
|
let mut tracks = vec![];
|
||||||
let mut scenes = vec![];
|
let mut scenes = vec![];
|
||||||
let midi_froms = PortConnect::collect(&self.midi_from, empty, &self.midi_from_re);
|
let midi_froms = Connect::collect(&self.midi_from, empty, &self.midi_from_re);
|
||||||
let midi_tos = PortConnect::collect(&self.midi_to, empty, &self.midi_to_re);
|
let midi_tos = Connect::collect(&self.midi_to, empty, &self.midi_to_re);
|
||||||
let left_froms = PortConnect::collect(&self.left_from, empty, empty);
|
let left_froms = Connect::collect(&self.left_from, empty, empty);
|
||||||
let left_tos = PortConnect::collect(&self.left_to, empty, empty);
|
let left_tos = Connect::collect(&self.left_to, empty, empty);
|
||||||
let right_froms = PortConnect::collect(&self.right_from, empty, empty);
|
let right_froms = Connect::collect(&self.right_from, empty, empty);
|
||||||
let right_tos = PortConnect::collect(&self.right_to, empty, empty);
|
let right_tos = Connect::collect(&self.right_to, empty, empty);
|
||||||
let audio_froms = &[left_froms.as_slice(), right_froms.as_slice()];
|
let audio_froms = &[left_froms.as_slice(), right_froms.as_slice()];
|
||||||
let audio_tos = &[left_tos.as_slice(), right_tos.as_slice()];
|
let audio_tos = &[left_tos.as_slice(), right_tos.as_slice()];
|
||||||
let clip = Arc::new(RwLock::new(MidiClip::new(
|
let clip = Arc::new(RwLock::new(MidiClip::new(
|
||||||
"Clip", true, 384usize, None, Some(ItemColor::random().into())),
|
"Clip", true, 384usize, None, Some(ItemColor::random().into())),
|
||||||
));
|
));
|
||||||
Tui::new()?.run(&Jack::new(name)?.run(|jack|{
|
Tui::new()?.run(&Jack::new_run(&name, move|jack|{
|
||||||
for (index, connect) in midi_froms.iter().enumerate() {
|
for (index, connect) in midi_froms.iter().enumerate() {
|
||||||
let port = MidiInput::new(jack, &format!("M/{index}"), &[connect.clone()])?;
|
midi_ins.push(jack.midi_in(&format!("M/{index}"), &[connect.clone()])?);
|
||||||
midi_ins.push(port);
|
|
||||||
}
|
}
|
||||||
for (index, connect) in midi_tos.iter().enumerate() {
|
for (index, connect) in midi_tos.iter().enumerate() {
|
||||||
let port = MidiOutput::new(jack, &format!("{index}/M"), &[connect.clone()])?;
|
midi_outs.push(jack.midi_out(&format!("{index}/M"), &[connect.clone()])?);
|
||||||
midi_outs.push(port);
|
|
||||||
};
|
};
|
||||||
let config = Configuration::new(&match self.mode {
|
let config = Configuration::new(&match self.mode {
|
||||||
LaunchMode::Clock => "config/config_transport.edn",
|
LaunchMode::Clock => "config/config_transport.edn",
|
||||||
|
|
@ -106,14 +104,14 @@ impl Cli {
|
||||||
LaunchMode::Sampler => "config/config_sampler.edn",
|
LaunchMode::Sampler => "config/config_sampler.edn",
|
||||||
_ => todo!("{:?}", self.mode),
|
_ => todo!("{:?}", self.mode),
|
||||||
}, false)?;
|
}, false)?;
|
||||||
let clock = Clock::new(jack, self.bpm)?;
|
let clock = Clock::new(&jack, self.bpm)?;
|
||||||
match self.mode {
|
match self.mode {
|
||||||
LaunchMode::Sequencer => tracks.push(Track::new(
|
LaunchMode::Sequencer => tracks.push(Track::new(
|
||||||
&name, None, jack, Some(&clock), Some(&clip),
|
&name, None, &jack, Some(&clock), Some(&clip),
|
||||||
midi_froms.as_slice(), midi_tos.as_slice()
|
midi_froms.as_slice(), midi_tos.as_slice()
|
||||||
)?),
|
)?),
|
||||||
LaunchMode::Groovebox | LaunchMode::Sampler => tracks.push(Track::new_with_sampler(
|
LaunchMode::Groovebox | LaunchMode::Sampler => tracks.push(Track::new_with_sampler(
|
||||||
&name, None, jack, Some(&clock), Some(&clip),
|
&name, None, &jack, Some(&clock), Some(&clip),
|
||||||
midi_froms.as_slice(), midi_tos.as_slice(), audio_froms, audio_tos,
|
midi_froms.as_slice(), midi_tos.as_slice(), audio_froms, audio_tos,
|
||||||
)?),
|
)?),
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
@ -172,10 +170,5 @@ const HEADER: &'static str = r#"
|
||||||
#[cfg(test)] #[test] fn test_cli () {
|
#[cfg(test)] #[test] fn test_cli () {
|
||||||
use clap::CommandFactory;
|
use clap::CommandFactory;
|
||||||
Cli::command().debug_assert();
|
Cli::command().debug_assert();
|
||||||
let jack = Jack::default();
|
//let jack = Jack::default();
|
||||||
//TODO:
|
|
||||||
//let _ = App::new_clock(&jack, None, false, false, &[], &[]);
|
|
||||||
//let _ = App::new_sequencer(&jack, None, false, false, &[], &[]);
|
|
||||||
//let _ = App::new_groovebox(&jack, None, false, false, &[], &[], &[&[], &[]], &[&[], &[]]);
|
|
||||||
//let _ = App::new_arranger(&jack, None, false, false, &[], &[], &[&[], &[]], &[&[], &[]], 0, 0, 0);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,18 +16,20 @@ wavers = { workspace = true, optional = true }
|
||||||
winit = { workspace = true, optional = true }
|
winit = { workspace = true, optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = [ "browser", "clock", "editor", "sequencer", "sampler", "lv2", "arranger" ]
|
default = [ "arranger", "sampler", "lv2" ]
|
||||||
|
|
||||||
|
arranger = [ "port", "editor", "sequencer", "editor" ]
|
||||||
|
browser = []
|
||||||
|
clap = []
|
||||||
clock = []
|
clock = []
|
||||||
arranger = [ "editor" ]
|
|
||||||
editor = []
|
editor = []
|
||||||
|
lv2 = [ "port", "livi", "winit" ]
|
||||||
meter = []
|
meter = []
|
||||||
mixer = []
|
mixer = []
|
||||||
browser = []
|
|
||||||
pool = []
|
pool = []
|
||||||
sequencer = [ "clock", "uuid", "pool" ]
|
port = []
|
||||||
sampler = [ "meter", "mixer", "browser", "symphonia", "wavers" ]
|
sampler = [ "port", "meter", "mixer", "browser", "symphonia", "wavers" ]
|
||||||
lv2 = [ "livi", "winit" ]
|
sequencer = [ "port", "clock", "uuid", "pool" ]
|
||||||
|
sf2 = []
|
||||||
vst2 = []
|
vst2 = []
|
||||||
vst3 = []
|
vst3 = []
|
||||||
clap = []
|
|
||||||
sf2 = []
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,5 @@
|
||||||
use crate::*;
|
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_api; pub use self::arranger_api::*;
|
||||||
mod arranger_clip; pub use self::arranger_clip::*;
|
mod arranger_clip; pub use self::arranger_clip::*;
|
||||||
mod arranger_model; pub use self::arranger_model::*;
|
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!(TracksSizes => Track);
|
||||||
def_sizes_iter!(InputsSizes => MidiInput);
|
def_sizes_iter!(InputsSizes => MidiInput);
|
||||||
def_sizes_iter!(OutputsSizes => MidiOutput);
|
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> {
|
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("▐")));
|
let left = Tui::fg_bg(bg, Reset, Fixed::x(1, RepeatV("▐")));
|
||||||
|
|
|
||||||
|
|
@ -156,7 +156,7 @@ impl ArrangementCommand {
|
||||||
fn output_add (arranger: &mut Arrangement) -> Perhaps<Self> {
|
fn output_add (arranger: &mut Arrangement) -> Perhaps<Self> {
|
||||||
arranger.midi_outs.push(MidiOutput::new(
|
arranger.midi_outs.push(MidiOutput::new(
|
||||||
arranger.jack(),
|
arranger.jack(),
|
||||||
format!("/M{}", arranger.midi_outs.len() + 1),
|
&format!("/M{}", arranger.midi_outs.len() + 1),
|
||||||
&[]
|
&[]
|
||||||
)?);
|
)?);
|
||||||
Ok(None)
|
Ok(None)
|
||||||
|
|
@ -164,7 +164,7 @@ impl ArrangementCommand {
|
||||||
fn input_add (arranger: &mut Arrangement) -> Perhaps<Self> {
|
fn input_add (arranger: &mut Arrangement) -> Perhaps<Self> {
|
||||||
arranger.midi_ins.push(MidiInput::new(
|
arranger.midi_ins.push(MidiInput::new(
|
||||||
arranger.jack(),
|
arranger.jack(),
|
||||||
format!("M{}/", arranger.midi_ins.len() + 1),
|
&format!("M{}/", arranger.midi_ins.len() + 1),
|
||||||
&[]
|
&[]
|
||||||
)?);
|
)?);
|
||||||
Ok(None)
|
Ok(None)
|
||||||
|
|
|
||||||
|
|
@ -1,53 +1,11 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
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 {
|
#[derive(Default, Debug)] pub struct Arrangement {
|
||||||
/// Project name.
|
/// Project name.
|
||||||
pub name: Arc<str>,
|
pub name: Arc<str>,
|
||||||
/// Base color.
|
/// Base color.
|
||||||
pub color: ItemTheme,
|
pub color: ItemTheme,
|
||||||
/// Jack client handle
|
/// Jack client handle
|
||||||
pub jack: Jack,
|
pub jack: Jack<'static>,
|
||||||
/// Source of time
|
/// Source of time
|
||||||
pub clock: Clock,
|
pub clock: Clock,
|
||||||
/// Allows one MIDI clip to be edited
|
/// Allows one MIDI clip to be edited
|
||||||
|
|
@ -80,6 +38,53 @@ from_dsl!(ClipCommand: |state: Arrangement, iter|state.selected_clip().as_
|
||||||
/// Display size of clips area
|
/// Display size of clips area
|
||||||
pub inner_size: Measure<TuiOut>,
|
pub inner_size: Measure<TuiOut>,
|
||||||
}
|
}
|
||||||
|
impl HasJack<'static> for Arrangement {
|
||||||
|
fn jack (&self) -> &Jack<'static> {
|
||||||
|
&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<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|Namespace::take_from(t, iter)).transpose().map(|x|x.flatten()));
|
||||||
|
from_dsl!(MidiOutputCommand: |state: Arrangement, iter|state.selected_midi_out().as_ref()
|
||||||
|
.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 _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
|
||||||
|
}
|
||||||
|
}
|
||||||
impl Arrangement {
|
impl Arrangement {
|
||||||
/// Width of display
|
/// Width of display
|
||||||
pub fn w (&self) -> u16 {
|
pub fn w (&self) -> u16 {
|
||||||
|
|
@ -152,64 +157,6 @@ impl Arrangement {
|
||||||
clip.write().unwrap().toggle_loop()
|
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")]
|
#[cfg(feature = "sampler")]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,62 @@
|
||||||
use crate::*;
|
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 {}
|
impl<T: Has<Vec<Track>> + Send + Sync> HasTracks for T {}
|
||||||
|
|
||||||
pub trait HasTracks: Has<Vec<Track>> + Send + Sync {
|
pub trait HasTracks: Has<Vec<Track>> + Send + Sync {
|
||||||
|
|
@ -132,11 +189,11 @@ impl Track {
|
||||||
pub fn new (
|
pub fn new (
|
||||||
name: &impl AsRef<str>,
|
name: &impl AsRef<str>,
|
||||||
color: Option<ItemTheme>,
|
color: Option<ItemTheme>,
|
||||||
jack: &Jack,
|
jack: &Jack<'static>,
|
||||||
clock: Option<&Clock>,
|
clock: Option<&Clock>,
|
||||||
clip: Option<&Arc<RwLock<MidiClip>>>,
|
clip: Option<&Arc<RwLock<MidiClip>>>,
|
||||||
midi_from: &[PortConnect],
|
midi_from: &[Connect],
|
||||||
midi_to: &[PortConnect],
|
midi_to: &[Connect],
|
||||||
) -> Usually<Self> {
|
) -> Usually<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
name: name.as_ref().into(),
|
name: name.as_ref().into(),
|
||||||
|
|
@ -156,24 +213,20 @@ impl Track {
|
||||||
pub fn new_with_sampler (
|
pub fn new_with_sampler (
|
||||||
name: &impl AsRef<str>,
|
name: &impl AsRef<str>,
|
||||||
color: Option<ItemTheme>,
|
color: Option<ItemTheme>,
|
||||||
jack: &Jack,
|
jack: &Jack<'static>,
|
||||||
clock: Option<&Clock>,
|
clock: Option<&Clock>,
|
||||||
clip: Option<&Arc<RwLock<MidiClip>>>,
|
clip: Option<&Arc<RwLock<MidiClip>>>,
|
||||||
midi_from: &[PortConnect],
|
midi_from: &[Connect],
|
||||||
midi_to: &[PortConnect],
|
midi_to: &[Connect],
|
||||||
audio_from: &[&[PortConnect];2],
|
audio_from: &[&[Connect];2],
|
||||||
audio_to: &[&[PortConnect];2],
|
audio_to: &[&[Connect];2],
|
||||||
) -> Usually<Self> {
|
) -> Usually<Self> {
|
||||||
let mut track = Self::new(name, color, jack, clock, clip, midi_from, midi_to)?;
|
let mut track = Self::new(name, color, jack, clock, clip, midi_from, midi_to)?;
|
||||||
|
let client_name = jack.with_client(|c|c.name().to_string());
|
||||||
|
let port_name = track.sequencer.midi_outs[0].port_name();
|
||||||
|
let connect = [Connect::exact(format!("{client_name}:{}", port_name))];
|
||||||
track.devices.push(Device::Sampler(Sampler::new(
|
track.devices.push(Device::Sampler(Sampler::new(
|
||||||
jack,
|
jack, &format!("{}/sampler", name.as_ref()), &connect, audio_from, audio_to
|
||||||
&format!("{}/sampler", name.as_ref()),
|
|
||||||
&[PortConnect::exact(format!("{}:{}",
|
|
||||||
jack.with_client(|c|c.name().to_string()),
|
|
||||||
track.sequencer.midi_outs[0].name()
|
|
||||||
))],
|
|
||||||
audio_from,
|
|
||||||
audio_to
|
|
||||||
)?));
|
)?));
|
||||||
Ok(track)
|
Ok(track)
|
||||||
}
|
}
|
||||||
|
|
@ -216,7 +269,7 @@ pub trait HasTrack {
|
||||||
Fixed::xy(20, 1 + ins, Outer(true, Style::default().fg(Tui::g(96))).enclose(
|
Fixed::xy(20, 1 + ins, Outer(true, Style::default().fg(Tui::g(96))).enclose(
|
||||||
Fixed::xy(20, 1 + ins, FieldV(theme, format!("MIDI ins: "),
|
Fixed::xy(20, 1 + ins, FieldV(theme, format!("MIDI ins: "),
|
||||||
Map::south(1, ||track.sequencer.midi_ins.iter(),
|
Map::south(1, ||track.sequencer.midi_ins.iter(),
|
||||||
|port, index|Fill::x(Align::w(format!(" {index} {}", port.name()))))))))})
|
|port, index|Fill::x(Align::w(format!(" {index} {}", port.port_name()))))))))})
|
||||||
}
|
}
|
||||||
fn view_midi_outs_status (&self, theme: ItemTheme) -> impl Content<TuiOut> {
|
fn view_midi_outs_status (&self, theme: ItemTheme) -> impl Content<TuiOut> {
|
||||||
self.track().map(|track|{
|
self.track().map(|track|{
|
||||||
|
|
@ -224,7 +277,7 @@ pub trait HasTrack {
|
||||||
Fixed::xy(20, 1 + outs, Outer(true, Style::default().fg(Tui::g(96))).enclose(
|
Fixed::xy(20, 1 + outs, Outer(true, Style::default().fg(Tui::g(96))).enclose(
|
||||||
Fixed::xy(20, 1 + outs, FieldV(theme, format!("MIDI outs: "),
|
Fixed::xy(20, 1 + outs, FieldV(theme, format!("MIDI outs: "),
|
||||||
Map::south(1, ||track.sequencer.midi_outs.iter(),
|
Map::south(1, ||track.sequencer.midi_outs.iter(),
|
||||||
|port, index|Fill::x(Align::w(format!(" {index} {}", port.name()))))))))})
|
|port, index|Fill::x(Align::w(format!(" {index} {}", port.port_name()))))))))})
|
||||||
}
|
}
|
||||||
fn view_audio_ins_status (&self, theme: ItemTheme) -> impl Content<TuiOut> {
|
fn view_audio_ins_status (&self, theme: ItemTheme) -> impl Content<TuiOut> {
|
||||||
self.track().and_then(|track|track.devices.get(0)).map(|device|{
|
self.track().and_then(|track|track.devices.get(0)).map(|device|{
|
||||||
|
|
@ -232,7 +285,7 @@ pub trait HasTrack {
|
||||||
Fixed::xy(20, 1 + ins, Outer(true, Style::default().fg(Tui::g(96))).enclose(
|
Fixed::xy(20, 1 + ins, Outer(true, Style::default().fg(Tui::g(96))).enclose(
|
||||||
Fixed::xy(20, 1 + ins, FieldV(theme, format!("Audio ins: "),
|
Fixed::xy(20, 1 + ins, FieldV(theme, format!("Audio ins: "),
|
||||||
Map::south(1, ||device.audio_ins().iter(),
|
Map::south(1, ||device.audio_ins().iter(),
|
||||||
|port, index|Fill::x(Align::w(format!(" {index} {}", port.name()))))))))})
|
|port, index|Fill::x(Align::w(format!(" {index} {}", port.port_name()))))))))})
|
||||||
}
|
}
|
||||||
fn view_audio_outs_status (&self, theme: ItemTheme) -> impl Content<TuiOut> {
|
fn view_audio_outs_status (&self, theme: ItemTheme) -> impl Content<TuiOut> {
|
||||||
self.track().and_then(|track|track.devices.last()).map(|device|{
|
self.track().and_then(|track|track.devices.last()).map(|device|{
|
||||||
|
|
@ -240,7 +293,7 @@ pub trait HasTrack {
|
||||||
Fixed::xy(20, 1 + outs, Outer(true, Style::default().fg(Tui::g(96))).enclose(
|
Fixed::xy(20, 1 + outs, Outer(true, Style::default().fg(Tui::g(96))).enclose(
|
||||||
Fixed::xy(20, 1 + outs, FieldV(theme, format!("Audio outs: "),
|
Fixed::xy(20, 1 + outs, FieldV(theme, format!("Audio outs: "),
|
||||||
Map::south(1, ||device.audio_outs().iter(),
|
Map::south(1, ||device.audio_outs().iter(),
|
||||||
|port, index|Fill::x(Align::w(format!(" {index} {}", port.name()))))))))})
|
|port, index|Fill::x(Align::w(format!(" {index} {}", port.port_name()))))))))})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -284,10 +337,10 @@ pub(crate) fn io_ports <'a, T: PortsSizes<'a>> (
|
||||||
) -> impl Content<TuiOut> + 'a {
|
) -> impl Content<TuiOut> + 'a {
|
||||||
Map::new(iter, move|(
|
Map::new(iter, move|(
|
||||||
index, name, connections, y, y2
|
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(
|
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))))),
|
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,
|
Fill::x(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg,
|
||||||
&connect.info)))))))))
|
&connect.info)))))))))
|
||||||
}
|
}
|
||||||
|
|
@ -297,7 +350,7 @@ pub(crate) fn io_conns <'a, T: PortsSizes<'a>> (
|
||||||
) -> impl Content<TuiOut> + 'a {
|
) -> impl Content<TuiOut> + 'a {
|
||||||
Map::new(iter, move|(
|
Map::new(iter, move|(
|
||||||
index, name, connections, y, y2
|
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(
|
map_south(y as u16, (y2-y) as u16, Bsp::s(
|
||||||
Fill::x(Tui::bold(true, wrap(bg, fg, Fill::x(Align::w("▞▞▞▞ ▞▞▞▞"))))),
|
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,
|
Map::new(||connections.iter(), move|connect, index|map_south(index as u16, 1,
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,8 @@ impl Arrangement {
|
||||||
})))));
|
})))));
|
||||||
for (index, port) in self.midi_ins().iter().enumerate() {
|
for (index, port) in self.midi_ins().iter().enumerate() {
|
||||||
add(&Fixed::y(1, Bsp::e(
|
add(&Fixed::y(1, Bsp::e(
|
||||||
Fixed::x(20, Align::w(Bsp::e(" ● ", Tui::bold(true, Tui::fg(Rgb(255,255,255),port.name()))))),
|
Fixed::x(20, Align::w(Bsp::e(" ● ",
|
||||||
|
Tui::bold(true, Tui::fg(Rgb(255,255,255), port.port_name()))))),
|
||||||
Bsp::w(Fixed::x(4, ()),
|
Bsp::w(Fixed::x(4, ()),
|
||||||
Stack::east(move|add: &mut dyn FnMut(&dyn Render<TuiOut>)|{
|
Stack::east(move|add: &mut dyn FnMut(&dyn Render<TuiOut>)|{
|
||||||
for (index, track, x1, x2) in self.tracks_with_sizes() {
|
for (index, track, x1, x2) in self.tracks_with_sizes() {
|
||||||
|
|
@ -39,7 +40,7 @@ impl Arrangement {
|
||||||
pub fn view_outputs <'a> (&'a self, theme: ItemTheme) -> impl Content<TuiOut> + 'a {
|
pub fn view_outputs <'a> (&'a self, theme: ItemTheme) -> impl Content<TuiOut> + 'a {
|
||||||
let mut h = 1;
|
let mut h = 1;
|
||||||
for output in self.midi_outs().iter() {
|
for output in self.midi_outs().iter() {
|
||||||
h += 1 + output.conn().len();
|
h += 1 + output.connections.len();
|
||||||
}
|
}
|
||||||
let h = h as u16;
|
let h = h as u16;
|
||||||
let list = Bsp::s(
|
let list = Bsp::s(
|
||||||
|
|
@ -47,11 +48,11 @@ impl Arrangement {
|
||||||
Fixed::y(h - 1, Fill::xy(Align::nw(Stack::south(|add: &mut dyn FnMut(&dyn Render<TuiOut>)|{
|
Fixed::y(h - 1, Fill::xy(Align::nw(Stack::south(|add: &mut dyn FnMut(&dyn Render<TuiOut>)|{
|
||||||
for (index, port) in self.midi_outs().iter().enumerate() {
|
for (index, port) in self.midi_outs().iter().enumerate() {
|
||||||
add(&Fixed::y(1,Fill::x(Bsp::e(
|
add(&Fixed::y(1,Fill::x(Bsp::e(
|
||||||
Align::w(Bsp::e(" ● ", Tui::fg(Rgb(255,255,255),Tui::bold(true, port.name())))),
|
Align::w(Bsp::e(" ● ", Tui::fg(Rgb(255,255,255),Tui::bold(true, port.port_name())))),
|
||||||
Fill::x(Align::e(format!("{}/{} ",
|
Fill::x(Align::e(format!("{}/{} ",
|
||||||
port.port().get_connections().len(),
|
port.port().get_connections().len(),
|
||||||
port.conn().len())))))));
|
port.connections.len())))))));
|
||||||
for (index, conn) in port.conn().iter().enumerate() {
|
for (index, conn) in port.connections.iter().enumerate() {
|
||||||
add(&Fixed::y(1, Fill::x(Align::w(format!(" c{index:02}{}", conn.info())))));
|
add(&Fixed::y(1, Fill::x(Align::w(format!(" c{index:02}{}", conn.info())))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -72,7 +73,7 @@ impl Arrangement {
|
||||||
Either(true, Tui::fg(Green, " ● "), " · "),
|
Either(true, Tui::fg(Green, " ● "), " · "),
|
||||||
Either(false, Tui::fg(Yellow, " ● "), " · "),
|
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("")));
|
add(&Fixed::y(1, Fill::x("")));
|
||||||
}
|
}
|
||||||
}})))}}))))))
|
}})))}}))))))
|
||||||
|
|
@ -195,7 +196,7 @@ pub trait TracksView:
|
||||||
Fill::x(Align::w(button_2("o", "utput", false))),
|
Fill::x(Align::w(button_2("o", "utput", false))),
|
||||||
Fill::xy(Stack::south(|add: &mut dyn FnMut(&dyn Render<TuiOut>)|{
|
Fill::xy(Stack::south(|add: &mut dyn FnMut(&dyn Render<TuiOut>)|{
|
||||||
for port in self.midi_outs().iter() {
|
for port in self.midi_outs().iter() {
|
||||||
add(&Fill::x(Align::w(port.name())));
|
add(&Fill::x(Align::w(port.port_name())));
|
||||||
}
|
}
|
||||||
}))),
|
}))),
|
||||||
button_2("O", "+", false),
|
button_2("O", "+", false),
|
||||||
|
|
@ -206,7 +207,7 @@ pub trait TracksView:
|
||||||
Align::nw(Fill::y(Map::south(1, ||track.sequencer.midi_outs.iter(),
|
Align::nw(Fill::y(Map::south(1, ||track.sequencer.midi_outs.iter(),
|
||||||
|port, index|Tui::fg(Rgb(255, 255, 255),
|
|port, index|Tui::fg(Rgb(255, 255, 255),
|
||||||
Fixed::y(1, Tui::bg(track.color.dark.rgb, Fill::x(Align::w(
|
Fixed::y(1, Tui::bg(track.color.dark.rgb, Fill::x(Align::w(
|
||||||
format!("·o{index:02} {}", port.name())))))))))));
|
format!("·o{index:02} {}", port.port_name())))))))))));
|
||||||
}
|
}
|
||||||
})))))
|
})))))
|
||||||
}
|
}
|
||||||
|
|
@ -232,7 +233,7 @@ pub trait TracksView:
|
||||||
)))),
|
)))),
|
||||||
Map::south(1, ||track.sequencer.midi_ins.iter(),
|
Map::south(1, ||track.sequencer.midi_ins.iter(),
|
||||||
|port, index|Tui::fg_bg(Rgb(255, 255, 255), track.color.dark.rgb,
|
|port, index|Tui::fg_bg(Rgb(255, 255, 255), track.color.dark.rgb,
|
||||||
Fill::x(Align::w(format!("·i{index:02} {}", port.name())))))))));
|
Fill::x(Align::w(format!("·i{index:02} {}", port.port_name())))))))));
|
||||||
}})))))
|
}})))))
|
||||||
}
|
}
|
||||||
fn track_width (&self, index: usize, track: &Track) -> u16 {
|
fn track_width (&self, index: usize, track: &Track) -> u16 {
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@ pub struct Clock {
|
||||||
pub midi_out: Arc<RwLock<Option<MidiOutput>>>,
|
pub midi_out: Arc<RwLock<Option<MidiOutput>>>,
|
||||||
/// For emitting a metronome
|
/// For emitting a metronome
|
||||||
pub click_out: Arc<RwLock<Option<AudioOutput>>>,
|
pub click_out: Arc<RwLock<Option<AudioOutput>>>,
|
||||||
|
// Cache of formatted strings
|
||||||
|
pub view_cache: Arc<RwLock<ViewCache>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for Clock {
|
impl std::fmt::Debug for Clock {
|
||||||
|
|
@ -43,7 +45,7 @@ impl std::fmt::Debug for Clock {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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 (chunk, transport) = jack.with_client(|c|(c.buffer_size(), c.transport()));
|
||||||
let timebase = Arc::new(Timebase::default());
|
let timebase = Arc::new(Timebase::default());
|
||||||
let clock = Self {
|
let clock = Self {
|
||||||
|
|
@ -56,9 +58,10 @@ impl Clock {
|
||||||
offset: Arc::new(Moment::zero(&timebase)),
|
offset: Arc::new(Moment::zero(&timebase)),
|
||||||
started: RwLock::new(None).into(),
|
started: RwLock::new(None).into(),
|
||||||
timebase,
|
timebase,
|
||||||
midi_in: Arc::new(RwLock::new(Some(MidiInput::new(jack, "M/clock", &[])?))),
|
midi_in: Arc::new(RwLock::new(Some(MidiInput::new(jack, &"M/clock", &[])?))),
|
||||||
midi_out: Arc::new(RwLock::new(Some(MidiOutput::new(jack, "clock/M", &[])?))),
|
midi_out: Arc::new(RwLock::new(Some(MidiOutput::new(jack, &"clock/M", &[])?))),
|
||||||
click_out: Arc::new(RwLock::new(Some(AudioOutput::new(jack, "click", &[])?))),
|
click_out: Arc::new(RwLock::new(Some(AudioOutput::new(jack, &"click", &[])?))),
|
||||||
|
..Default::default()
|
||||||
};
|
};
|
||||||
if let Some(bpm) = bpm {
|
if let Some(bpm) = bpm {
|
||||||
clock.timebase.bpm.set(bpm);
|
clock.timebase.bpm.set(bpm);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
pub fn view_transport (
|
pub fn view_transport (
|
||||||
play: bool,
|
play: bool,
|
||||||
|
|
@ -49,3 +50,86 @@ pub(crate) fn button_play_pause (playing: bool) -> impl Content<TuiOut> {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)] pub struct ViewCache {
|
||||||
|
pub sr: Memo<Option<(bool, f64)>, String>,
|
||||||
|
pub buf: Memo<Option<f64>, String>,
|
||||||
|
pub lat: Memo<Option<f64>, String>,
|
||||||
|
pub bpm: Memo<Option<f64>, String>,
|
||||||
|
pub beat: Memo<Option<f64>, String>,
|
||||||
|
pub time: Memo<Option<f64>, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ViewCache {
|
||||||
|
fn default () -> Self {
|
||||||
|
let mut beat = String::with_capacity(16);
|
||||||
|
write!(beat, "{}", Self::BEAT_EMPTY);
|
||||||
|
let mut time = String::with_capacity(16);
|
||||||
|
write!(time, "{}", Self::TIME_EMPTY);
|
||||||
|
let mut bpm = String::with_capacity(16);
|
||||||
|
write!(bpm, "{}", Self::BPM_EMPTY);
|
||||||
|
Self {
|
||||||
|
beat: Memo::new(None, beat),
|
||||||
|
time: Memo::new(None, time),
|
||||||
|
bpm: Memo::new(None, bpm),
|
||||||
|
sr: Memo::new(None, String::with_capacity(16)),
|
||||||
|
buf: Memo::new(None, String::with_capacity(16)),
|
||||||
|
lat: Memo::new(None, String::with_capacity(16)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ViewCache {
|
||||||
|
pub const BEAT_EMPTY: &'static str = "-.-.--";
|
||||||
|
pub const TIME_EMPTY: &'static str = "-.---s";
|
||||||
|
pub const BPM_EMPTY: &'static str = "---.---";
|
||||||
|
|
||||||
|
//pub fn track_counter (cache: &Arc<RwLock<Self>>, track: usize, tracks: usize)
|
||||||
|
//-> Arc<RwLock<String>>
|
||||||
|
//{
|
||||||
|
//let data = (track, tracks);
|
||||||
|
//cache.write().unwrap().trks.update(Some(data), rewrite!(buf, "{}/{}", data.0, data.1));
|
||||||
|
//cache.read().unwrap().trks.view.clone()
|
||||||
|
//}
|
||||||
|
|
||||||
|
//pub fn scene_add (cache: &Arc<RwLock<Self>>, scene: usize, scenes: usize, is_editing: bool)
|
||||||
|
//-> impl Content<TuiOut>
|
||||||
|
//{
|
||||||
|
//let data = (scene, scenes);
|
||||||
|
//cache.write().unwrap().scns.update(Some(data), rewrite!(buf, "({}/{})", data.0, data.1));
|
||||||
|
//button_3("S", "add scene", cache.read().unwrap().scns.view.clone(), is_editing)
|
||||||
|
//}
|
||||||
|
|
||||||
|
pub fn update_clock (cache: &Arc<RwLock<Self>>, clock: &Clock, compact: bool) {
|
||||||
|
let rate = clock.timebase.sr.get();
|
||||||
|
let chunk = clock.chunk.load(Relaxed) as f64;
|
||||||
|
let lat = chunk / rate * 1000.;
|
||||||
|
let delta = |start: &Moment|clock.global.usec.get() - start.usec.get();
|
||||||
|
let mut cache = cache.write().unwrap();
|
||||||
|
cache.buf.update(Some(chunk), rewrite!(buf, "{chunk}"));
|
||||||
|
cache.lat.update(Some(lat), rewrite!(buf, "{lat:.1}ms"));
|
||||||
|
cache.sr.update(Some((compact, rate)), |buf,_,_|{
|
||||||
|
buf.clear();
|
||||||
|
if compact {
|
||||||
|
write!(buf, "{:.1}kHz", rate / 1000.)
|
||||||
|
} else {
|
||||||
|
write!(buf, "{:.0}Hz", rate)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if let Some(now) = clock.started.read().unwrap().as_ref().map(delta) {
|
||||||
|
let pulse = clock.timebase.usecs_to_pulse(now);
|
||||||
|
let time = now/1000000.;
|
||||||
|
let bpm = clock.timebase.bpm.get();
|
||||||
|
cache.beat.update(Some(pulse), |buf, _, _|{
|
||||||
|
buf.clear();
|
||||||
|
clock.timebase.format_beats_1_to(buf, pulse)
|
||||||
|
});
|
||||||
|
cache.time.update(Some(time), rewrite!(buf, "{:.3}s", time));
|
||||||
|
cache.bpm.update(Some(bpm), rewrite!(buf, "{:.3}", bpm));
|
||||||
|
} else {
|
||||||
|
cache.beat.update(None, rewrite!(buf, "{}", ViewCache::BEAT_EMPTY));
|
||||||
|
cache.time.update(None, rewrite!(buf, "{}", ViewCache::TIME_EMPTY));
|
||||||
|
cache.bpm.update(None, rewrite!(buf, "{}", ViewCache::BPM_EMPTY));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,20 +24,29 @@ pub(crate) use Color::*;
|
||||||
mod device; pub use self::device::*;
|
mod device; pub use self::device::*;
|
||||||
mod dialog; pub use self::dialog::*;
|
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 = "arranger")] mod arranger; #[cfg(feature = "arranger")] pub use self::arranger::*;
|
||||||
#[cfg(feature = "browser")] mod browser; #[cfg(feature = "browser")] pub use self::browser::*;
|
#[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 = "clock")] mod clock; #[cfg(feature = "clock")] pub use self::clock::*;
|
||||||
#[cfg(feature = "editor")] mod editor; #[cfg(feature = "editor")] pub use self::editor::*;
|
#[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 = "lv2")] mod lv2; #[cfg(feature = "lv2")] pub use self::lv2::*;
|
||||||
#[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 = "meter")] mod meter; #[cfg(feature = "meter")] pub use self::meter::*;
|
#[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 = "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 = "sf2")] mod sf2; #[cfg(feature = "sf2")] pub use self::sf2::*;
|
||||||
#[cfg(feature = "vst2")] mod vst2; #[cfg(feature = "vst2")] pub use self::vst2::*;
|
#[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 = "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> (
|
pub fn button_2 <'a> (
|
||||||
key: impl Content<TuiOut> + 'a, label: impl Content<TuiOut> + 'a, editing: bool,
|
key: impl Content<TuiOut> + 'a, label: impl Content<TuiOut> + 'a, editing: bool,
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use crate::*;
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Lv2 {
|
pub struct Lv2 {
|
||||||
/// JACK client handle (needs to not be dropped for standalone mode to work).
|
/// 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 name: Arc<str>,
|
||||||
pub path: Option<Arc<str>>,
|
pub path: Option<Arc<str>>,
|
||||||
pub selected: usize,
|
pub selected: usize,
|
||||||
|
|
@ -26,7 +26,7 @@ pub struct Lv2 {
|
||||||
impl Lv2 {
|
impl Lv2 {
|
||||||
|
|
||||||
pub fn new (
|
pub fn new (
|
||||||
jack: &Jack,
|
jack: &Jack<'static>,
|
||||||
name: &str,
|
name: &str,
|
||||||
uri: &str,
|
uri: &str,
|
||||||
) -> Usually<Self> {
|
) -> Usually<Self> {
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ pub struct Pool {
|
||||||
}
|
}
|
||||||
from_dsl!(BrowserCommand: |state: Pool, iter|Ok(state.browser
|
from_dsl!(BrowserCommand: |state: Pool, iter|Ok(state.browser
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|p|FromDsl::take_from(p, iter))
|
.map(|p|Namespace::take_from(p, iter))
|
||||||
.transpose()?
|
.transpose()?
|
||||||
.flatten()));
|
.flatten()));
|
||||||
impl Default for Pool {
|
impl Default for Pool {
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,57 @@
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use super::*;
|
|
||||||
|
|
||||||
pub trait JackPort<'j>: HasJack<'j> {
|
mod port_audio_out; pub use self::port_audio_out::*;
|
||||||
type Port: PortSpec;
|
mod port_audio_in; pub use self::port_audio_in::*;
|
||||||
type Pair: PortSpec;
|
mod port_midi_out; pub use self::port_midi_out::*;
|
||||||
fn port (&self) -> &Port<Self::Port>;
|
mod port_midi_in; pub use self::port_midi_in::*;
|
||||||
|
pub(crate) use ConnectName::*;
|
||||||
|
pub(crate) use ConnectScope::*;
|
||||||
|
pub(crate) use ConnectStatus::*;
|
||||||
|
|
||||||
|
pub trait RegisterPorts: HasJack<'static> {
|
||||||
|
/// Register a MIDI input port.
|
||||||
|
fn midi_in (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<MidiInput>;
|
||||||
|
/// Register a MIDI output port.
|
||||||
|
fn midi_out (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<MidiOutput>;
|
||||||
|
/// Register an audio input port.
|
||||||
|
fn audio_in (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<AudioInput>;
|
||||||
|
/// Register an audio output port.
|
||||||
|
fn audio_out (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<AudioOutput>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ConnectTo<'j, T>: JackPort<'j> {
|
impl<J: HasJack<'static>> RegisterPorts for J {
|
||||||
fn connect_to (&'j self, to: &T) -> Usually<ConnectStatus>;
|
fn midi_in (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<MidiInput> {
|
||||||
|
MidiInput::new(self.jack(), name, connect)
|
||||||
|
}
|
||||||
|
fn midi_out (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<MidiOutput> {
|
||||||
|
MidiOutput::new(self.jack(), name, connect)
|
||||||
|
}
|
||||||
|
fn audio_in (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<AudioInput> {
|
||||||
|
AudioInput::new(self.jack(), name, connect)
|
||||||
|
}
|
||||||
|
fn audio_out (&self, name: &impl AsRef<str>, connect: &[Connect]) -> Usually<AudioOutput> {
|
||||||
|
AudioOutput::new(self.jack(), name, connect)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export] macro_rules! connect_to {
|
pub trait JackPort: HasJack<'static> {
|
||||||
(<$lt:lifetime>|$self:ident:$Self:ty, $port:ident:$Port:ty|$expr:expr) => {
|
type Port: PortSpec + Default;
|
||||||
impl<$lt> ConnectTo<$lt, &$Port> for $Self {
|
type Pair: PortSpec + Default;
|
||||||
fn connect_to (&$self, $port: &$Port) -> Usually<ConnectStatus> {
|
fn new (jack: &Jack<'static>, name: &impl AsRef<str>, connect: &[Connect])
|
||||||
$expr
|
-> Usually<Self> where Self: Sized;
|
||||||
|
fn register (jack: &Jack<'static>, name: &impl AsRef<str>) -> Usually<Port<Self::Port>> {
|
||||||
|
jack.with_client(|c|c.register_port::<Self::Port>(name.as_ref(), Default::default()))
|
||||||
|
.map_err(|e|e.into())
|
||||||
}
|
}
|
||||||
}
|
fn port_name (&self) -> &Arc<str>;
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait ConnectAuto<'j>: JackPort<'j> + ConnectTo<'j, &'j Port<Unowned>> {
|
|
||||||
fn connections (&self) -> &[Connect];
|
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> {
|
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))
|
self.with_client(|c|c.ports(re_name, re_type, flags))
|
||||||
}
|
}
|
||||||
|
|
@ -32,7 +61,7 @@ pub trait ConnectAuto<'j>: JackPort<'j> + ConnectTo<'j, &'j Port<Unowned>> {
|
||||||
fn port_by_name (&self, name: impl AsRef<str>) -> Option<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()))
|
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() {
|
for connect in self.connections().iter() {
|
||||||
//panic!("{connect:?}");
|
//panic!("{connect:?}");
|
||||||
let status = match &connect.name {
|
let status = match &connect.name {
|
||||||
|
|
@ -43,15 +72,15 @@ pub trait ConnectAuto<'j>: JackPort<'j> + ConnectTo<'j, &'j Port<Unowned>> {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn connect_exact (&self, name: &str) ->
|
fn connect_exact <'k> (&'k self, name: &str) ->
|
||||||
Usually<Vec<(Port<Unowned>, Arc<str>, ConnectStatus)>>
|
Usually<Vec<(Port<Unowned>, Arc<str>, ConnectStatus)>>
|
||||||
{
|
{
|
||||||
self.with_client(|c|{
|
self.with_client(move|c|{
|
||||||
let mut status = vec![];
|
let mut status = vec![];
|
||||||
for port in c.ports(None, None, PortFlags::empty()).iter() {
|
for port in c.ports(None, None, PortFlags::empty()).iter() {
|
||||||
if port.as_str() == &*name {
|
if port.as_str() == &*name {
|
||||||
if let Some(port) = c.port_by_name(port.as_str()) {
|
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();
|
let name = port.name()?.into();
|
||||||
status.push((port, name, port_status));
|
status.push((port, name, port_status));
|
||||||
if port_status == Connected {
|
if port_status == Connected {
|
||||||
|
|
@ -63,15 +92,15 @@ pub trait ConnectAuto<'j>: JackPort<'j> + ConnectTo<'j, &'j Port<Unowned>> {
|
||||||
Ok(status)
|
Ok(status)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
fn connect_regexp (
|
fn connect_regexp <'k> (
|
||||||
&self, re: &str, scope: ConnectScope
|
&'k self, re: &str, scope: ConnectScope
|
||||||
) -> Usually<Vec<(Port<Unowned>, Arc<str>, ConnectStatus)>> {
|
) -> Usually<Vec<(Port<Unowned>, Arc<str>, ConnectStatus)>> {
|
||||||
self.with_client(|c|{
|
self.with_client(move|c|{
|
||||||
let mut status = vec![];
|
let mut status = vec![];
|
||||||
let ports = c.ports(Some(&re), None, PortFlags::empty());
|
let ports = c.ports(Some(&re), None, PortFlags::empty());
|
||||||
for port in ports.iter() {
|
for port in ports.iter() {
|
||||||
if let Some(port) = c.port_by_name(port.as_str()) {
|
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();
|
let name = port.name()?.into();
|
||||||
status.push((port, name, port_status));
|
status.push((port, name, port_status));
|
||||||
if port_status == Connected && scope == One {
|
if port_status == Connected && scope == One {
|
||||||
|
|
@ -82,6 +111,34 @@ pub trait ConnectAuto<'j>: JackPort<'j> + ConnectTo<'j, &'j Port<Unowned>> {
|
||||||
Ok(status)
|
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)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
48
crates/device/src/port/port_audio_in.rs
Normal file
48
crates/device/src/port/port_audio_in.rs
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
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>,
|
||||||
|
}
|
||||||
|
impl HasJack<'static> for AudioInput {
|
||||||
|
fn jack (&self) -> &Jack<'static> {
|
||||||
|
&self.jack
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl JackPort for AudioInput {
|
||||||
|
type Port = AudioIn;
|
||||||
|
type Pair = AudioOut;
|
||||||
|
fn port_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)
|
||||||
|
}
|
||||||
|
}
|
||||||
48
crates/device/src/port/port_audio_out.rs
Normal file
48
crates/device/src/port/port_audio_out.rs
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
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>,
|
||||||
|
}
|
||||||
|
impl HasJack<'static> for AudioOutput {
|
||||||
|
fn jack (&self) -> &Jack<'static> {
|
||||||
|
&self.jack
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl JackPort for AudioOutput {
|
||||||
|
type Port = AudioOut;
|
||||||
|
type Pair = AudioIn;
|
||||||
|
fn port_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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,99 +2,60 @@ use crate::*;
|
||||||
|
|
||||||
//impl_port!(MidiInput: MidiOut -> MidiIn |j, n|j.register_port::<MidiOut>(n));
|
//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.
|
/// Handle to JACK client, for receiving reconnect events.
|
||||||
jack: Jack<'j>,
|
jack: Jack<'static>,
|
||||||
/// Port name
|
/// Port name
|
||||||
name: Arc<str>,
|
name: Arc<str>,
|
||||||
/// Port handle.
|
/// Port handle.
|
||||||
port: Port<MidiOut>,
|
port: Port<MidiIn>,
|
||||||
|
/// List of currently held notes.
|
||||||
|
held: Arc<RwLock<[bool;128]>>,
|
||||||
/// List of ports to connect to.
|
/// List of ports to connect to.
|
||||||
connections: Vec<Connect>
|
pub connections: Vec<Connect>,
|
||||||
}
|
}
|
||||||
impl<'j> AsRef<Port<MidiOut>> for MidiInput<'j> {
|
impl HasJack<'static> for MidiInput {
|
||||||
fn as_ref (&self) -> &Port<MidiOut> { &self.port }
|
fn jack (&self) -> &Jack<'static> {
|
||||||
|
&self.jack
|
||||||
}
|
}
|
||||||
impl<'j> MidiInput<'j> {
|
}
|
||||||
pub fn new (jack: &Jack, name: impl AsRef<str>, connect: &[Connect])
|
impl JackPort for MidiInput {
|
||||||
-> Usually<Self>
|
type Port = MidiIn;
|
||||||
|
type Pair = MidiOut;
|
||||||
|
fn port_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 {
|
let port = Self {
|
||||||
port: jack.register_port::<MidiIn>(name.as_ref())?,
|
port: Self::register(jack, name)?,
|
||||||
jack,
|
jack: jack.clone(),
|
||||||
name: name.into(),
|
name: name.as_ref().into(),
|
||||||
connections: connect.to_vec()
|
connections: connect.to_vec(),
|
||||||
|
held: Arc::new(RwLock::new([false;128]))
|
||||||
};
|
};
|
||||||
port.connect_to_matching()?;
|
port.connect_to_matching()?;
|
||||||
Ok(port)
|
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])> {
|
pub fn parsed <'a> (&'a self, scope: &'a ProcessScope) -> impl Iterator<Item=(usize, LiveEvent<'a>, &'a [u8])> {
|
||||||
parse_midi_input(self.port().iter(scope))
|
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)]
|
#[tengri_proc::command(MidiInput)]
|
||||||
impl MidiInputCommand {
|
impl MidiInputCommand {
|
||||||
|
|
@ -127,8 +88,8 @@ pub trait HasMidiIns {
|
||||||
{
|
{
|
||||||
let mut y = 0;
|
let mut y = 0;
|
||||||
self.midi_ins().iter().enumerate().map(move|(i, input)|{
|
self.midi_ins().iter().enumerate().map(move|(i, input)|{
|
||||||
let height = 1 + input.conn().len();
|
let height = 1 + input.connections().len();
|
||||||
let data = (i, input.name(), input.conn(), y, y + height);
|
let data = (i, input.port_name(), input.connections(), y, y + height);
|
||||||
y += height;
|
y += height;
|
||||||
data
|
data
|
||||||
})
|
})
|
||||||
|
|
@ -137,7 +98,7 @@ pub trait HasMidiIns {
|
||||||
|
|
||||||
pub type CollectedMidiInput<'a> = Vec<Vec<(u32, Result<LiveEvent<'a>, MidiError>)>>;
|
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<()> {
|
fn midi_in_add (&mut self) -> Usually<()> {
|
||||||
let index = self.midi_ins().len();
|
let index = self.midi_ins().len();
|
||||||
let port = MidiInput::new(self.jack(), &format!("M/{index}"), &[])?;
|
let port = MidiInput::new(self.jack(), &format!("M/{index}"), &[])?;
|
||||||
|
|
@ -8,7 +8,7 @@ use crate::*;
|
||||||
/// Port handle.
|
/// Port handle.
|
||||||
port: Port<MidiOut>,
|
port: Port<MidiOut>,
|
||||||
/// List of ports to connect to.
|
/// List of ports to connect to.
|
||||||
conn: Vec<Connect>,
|
pub connections: Vec<Connect>,
|
||||||
/// List of currently held notes.
|
/// List of currently held notes.
|
||||||
held: Arc<RwLock<[bool;128]>>,
|
held: Arc<RwLock<[bool;128]>>,
|
||||||
/// Buffer
|
/// Buffer
|
||||||
|
|
@ -16,22 +16,41 @@ use crate::*;
|
||||||
/// Buffer
|
/// Buffer
|
||||||
output_buffer: Vec<Vec<Vec<u8>>>,
|
output_buffer: Vec<Vec<Vec<u8>>>,
|
||||||
}
|
}
|
||||||
|
impl HasJack<'static> for MidiOutput {
|
||||||
has!(Jack<'static>: |self: MidiOutput|self.jack);
|
fn jack (&self) -> &Jack<'static> {
|
||||||
|
&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 port_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 jack = jack.clone();
|
||||||
let port = jack.register_port::<MidiOut>(name.as_ref())?;
|
|
||||||
let name = name.as_ref().into();
|
let name = name.as_ref().into();
|
||||||
let conn = connect.to_vec();
|
let connections = connect.to_vec();
|
||||||
let port = Self {
|
let port = Self {
|
||||||
jack,
|
jack,
|
||||||
port,
|
port,
|
||||||
name,
|
name,
|
||||||
conn,
|
connections,
|
||||||
held: Arc::new([false;128].into()),
|
held: Arc::new([false;128].into()),
|
||||||
note_buffer: vec![0;8],
|
note_buffer: vec![0;8],
|
||||||
output_buffer: vec![vec![];65536],
|
output_buffer: vec![vec![];65536],
|
||||||
|
|
@ -39,22 +58,8 @@ impl MidiOutput {
|
||||||
port.connect_to_matching()?;
|
port.connect_to_matching()?;
|
||||||
Ok(port)
|
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,
|
/// Clear the section of the output buffer that we will be using,
|
||||||
/// emitting "all notes off" at start of buffer if requested.
|
/// emitting "all notes off" at start of buffer if requested.
|
||||||
pub fn buffer_clear (&mut self, scope: &ProcessScope, reset: bool) {
|
pub fn buffer_clear (&mut self, scope: &ProcessScope, reset: bool) {
|
||||||
|
|
@ -94,58 +99,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)]
|
#[tengri_proc::command(MidiOutput)]
|
||||||
impl MidiOutputCommand {
|
impl MidiOutputCommand {
|
||||||
fn _todo_ (_port: &mut MidiOutput) -> Perhaps<Self> { Ok(None) }
|
fn _todo_ (_port: &mut MidiOutput) -> Perhaps<Self> { Ok(None) }
|
||||||
|
|
@ -170,8 +123,8 @@ pub trait HasMidiOuts {
|
||||||
{
|
{
|
||||||
let mut y = 0;
|
let mut y = 0;
|
||||||
self.midi_outs().iter().enumerate().map(move|(i, output)|{
|
self.midi_outs().iter().enumerate().map(move|(i, output)|{
|
||||||
let height = 1 + output.conn().len();
|
let height = 1 + output.connections().len();
|
||||||
let data = (i, output.name(), output.conn(), y, y + height);
|
let data = (i, output.port_name(), output.connections(), y, y + height);
|
||||||
y += height;
|
y += height;
|
||||||
data
|
data
|
||||||
})
|
})
|
||||||
|
|
@ -184,7 +137,7 @@ pub trait HasMidiOuts {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trail for thing that may gain new MIDI ports.
|
/// 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<()> {
|
fn midi_out_add (&mut self) -> Usually<()> {
|
||||||
let index = self.midi_outs().len();
|
let index = self.midi_outs().len();
|
||||||
let port = MidiOutput::new(self.jack(), &format!("{index}/M"), &[])?;
|
let port = MidiOutput::new(self.jack(), &format!("{index}/M"), &[])?;
|
||||||
|
|
@ -49,16 +49,16 @@ pub struct Sampler {
|
||||||
|
|
||||||
impl Sampler {
|
impl Sampler {
|
||||||
pub fn new (
|
pub fn new (
|
||||||
jack: &Jack,
|
jack: &Jack<'static>,
|
||||||
name: impl AsRef<str>,
|
name: impl AsRef<str>,
|
||||||
midi_from: &[PortConnect],
|
midi_from: &[Connect],
|
||||||
audio_from: &[&[PortConnect];2],
|
audio_from: &[&[Connect];2],
|
||||||
audio_to: &[&[PortConnect];2],
|
audio_to: &[&[Connect];2],
|
||||||
) -> Usually<Self> {
|
) -> Usually<Self> {
|
||||||
let name = name.as_ref();
|
let name = name.as_ref();
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
name: name.into(),
|
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![
|
audio_ins: vec![
|
||||||
AudioInput::new(jack, &format!("L/{name}"), audio_from[0])?,
|
AudioInput::new(jack, &format!("L/{name}"), audio_from[0])?,
|
||||||
AudioInput::new(jack, &format!("R/{name}"), audio_from[1])?,
|
AudioInput::new(jack, &format!("R/{name}"), audio_from[1])?,
|
||||||
|
|
|
||||||
|
|
@ -70,17 +70,17 @@ impl Default for Sequencer {
|
||||||
impl Sequencer {
|
impl Sequencer {
|
||||||
pub fn new (
|
pub fn new (
|
||||||
name: impl AsRef<str>,
|
name: impl AsRef<str>,
|
||||||
jack: &Jack,
|
jack: &Jack<'static>,
|
||||||
clock: Option<&Clock>,
|
clock: Option<&Clock>,
|
||||||
clip: Option<&Arc<RwLock<MidiClip>>>,
|
clip: Option<&Arc<RwLock<MidiClip>>>,
|
||||||
midi_from: &[PortConnect],
|
midi_from: &[Connect],
|
||||||
midi_to: &[PortConnect],
|
midi_to: &[Connect],
|
||||||
) -> Usually<Self> {
|
) -> Usually<Self> {
|
||||||
let _name = name.as_ref();
|
let _name = name.as_ref();
|
||||||
let clock = clock.cloned().unwrap_or_default();
|
let clock = clock.cloned().unwrap_or_default();
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
midi_ins: vec![MidiInput::new(jack, format!("M/{}", name.as_ref()), midi_from)?,],
|
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_outs: vec![MidiOutput::new(jack, &format!("{}/M", name.as_ref()), midi_to)?, ],
|
||||||
play_clip: clip.map(|clip|(Moment::zero(&clock.timebase), Some(clip.clone()))),
|
play_clip: clip.map(|clip|(Moment::zero(&clock.timebase), Some(clip.clone()))),
|
||||||
clock,
|
clock,
|
||||||
reset: true,
|
reset: true,
|
||||||
|
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
mod audio_in; pub use self::audio_in::*;
|
|
||||||
mod audio_out; pub use self::audio_out::*;
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +1,222 @@
|
||||||
|
use crate::*;
|
||||||
pub use ::jack::{*, contrib::{*, ClosureProcessHandler}};
|
pub use ::jack::{*, contrib::{*, ClosureProcessHandler}};
|
||||||
//contrib::ClosureProcessHandler,
|
/// Wraps [JackState] and through it [jack::Client] when connected.
|
||||||
//NotificationHandler,
|
#[derive(Clone, Debug, Default)]
|
||||||
//Client, AsyncClient, ClientOptions, ClientStatus,
|
pub struct Jack<'j>(Arc<RwLock<JackState<'j>>>);
|
||||||
//ProcessScope, Control, Frames,
|
/// Implement [Jack] constructor and methods
|
||||||
//Port, PortId, PortSpec, PortFlags,
|
impl<'j> Jack<'j> {
|
||||||
//Unowned, MidiIn, MidiOut, AudioIn, AudioOut,
|
/// Register new [Client] and wrap it for shared use.
|
||||||
//};
|
pub fn new_run <T: HasJack<'j> + Audio + Send + Sync + 'static> (
|
||||||
|
name: &impl AsRef<str>,
|
||||||
|
init: impl FnOnce(Jack<'j>)->Usually<T>
|
||||||
|
) -> Usually<Arc<RwLock<T>>> {
|
||||||
|
Jack::new(name)?.run(init)
|
||||||
|
}
|
||||||
|
pub fn new (name: &impl AsRef<str>) -> Usually<Self> {
|
||||||
|
let client = Client::new(name.as_ref(), ClientOptions::NO_START_SERVER)?.0;
|
||||||
|
Ok(Jack(Arc::new(RwLock::new(JackState::Inactive(client)))))
|
||||||
|
}
|
||||||
|
pub fn run <T: HasJack<'j> + Audio + Send + Sync + 'static>
|
||||||
|
(self, init: impl FnOnce(Self)->Usually<T>) -> Usually<Arc<RwLock<T>>>
|
||||||
|
{
|
||||||
|
let client_state = self.0.clone();
|
||||||
|
let app: Arc<RwLock<T>> = Arc::new(RwLock::new(init(self)?));
|
||||||
|
let mut state = Activating;
|
||||||
|
std::mem::swap(&mut*client_state.write().unwrap(), &mut state);
|
||||||
|
if let Inactive(client) = state {
|
||||||
|
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|(&mut*app.write().unwrap()).handle(event)
|
||||||
|
}) as BoxedJackEventHandler),
|
||||||
|
// This is the main processing handler. It's a struct that wraps a [Box]
|
||||||
|
// which performs type erasure on a callback that takes [Client] and [ProcessScope]
|
||||||
|
// and passes them down to the `app`'s `process` callback, which in turn
|
||||||
|
// implements audio and MIDI input and output on a realtime basis.
|
||||||
|
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),
|
||||||
|
)?;
|
||||||
|
*client_state.write().unwrap() = Active(client);
|
||||||
|
} else {
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
Ok(app)
|
||||||
|
}
|
||||||
|
/// Run something with the client.
|
||||||
|
pub fn with_client <T> (&self, op: impl FnOnce(&Client)->T) -> T {
|
||||||
|
match &*self.0.read().unwrap() {
|
||||||
|
Inert => panic!("jack client not activated"),
|
||||||
|
Inactive(ref client) => op(client),
|
||||||
|
Activating => panic!("jack client has not finished activation"),
|
||||||
|
Active(ref client) => op(client.as_client()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// 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>),
|
||||||
|
}
|
||||||
|
/// Things that can provide a [jack::Client] reference.
|
||||||
|
pub trait HasJack<'j>: Send + Sync {
|
||||||
|
/// Return the internal [jack::Client] handle
|
||||||
|
/// that lets you call the JACK API.
|
||||||
|
fn jack (&self) -> &Jack<'j>;
|
||||||
|
fn with_client <T> (&self, op: impl FnOnce(&Client)->T) -> T {
|
||||||
|
self.jack().with_client(op)
|
||||||
|
}
|
||||||
|
fn port_by_name (&self, name: &str) -> Option<Port<Unowned>> {
|
||||||
|
self.with_client(|client|client.port_by_name(name))
|
||||||
|
}
|
||||||
|
fn port_by_id (&self, id: u32) -> Option<Port<Unowned>> {
|
||||||
|
self.with_client(|c|c.port_by_id(id))
|
||||||
|
}
|
||||||
|
fn register_port <PS: PortSpec + Default> (&self, name: impl AsRef<str>) -> Usually<Port<PS>> {
|
||||||
|
self.with_client(|client|Ok(client.register_port(name.as_ref(), PS::default())?))
|
||||||
|
}
|
||||||
|
fn sync_lead (&self, enable: bool, callback: impl Fn(TimebaseInfo)->Position) -> Usually<()> {
|
||||||
|
if enable {
|
||||||
|
self.with_client(|client|match client.register_timebase_callback(false, callback) {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(e) => Err(e)
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn sync_follow (&self, _enable: bool) -> Usually<()> {
|
||||||
|
// TODO: sync follow
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Trait for thing that has a JACK process callback.
|
||||||
|
pub trait Audio {
|
||||||
|
/// Handle a JACK event.
|
||||||
|
fn handle (&mut self, _event: JackEvent) {}
|
||||||
|
/// Projecss a JACK chunk.
|
||||||
|
fn process (&mut self, _: &Client, _: &ProcessScope) -> Control {
|
||||||
|
Control::Continue
|
||||||
|
}
|
||||||
|
/// The JACK process callback function passed to the server.
|
||||||
|
fn callback (
|
||||||
|
state: &Arc<RwLock<Self>>, client: &Client, scope: &ProcessScope
|
||||||
|
) -> Control where Self: Sized {
|
||||||
|
if let Ok(mut state) = state.write() {
|
||||||
|
state.process(client, scope)
|
||||||
|
} else {
|
||||||
|
Control::Quit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Implement [Audio]: provide JACK callbacks.
|
||||||
|
#[macro_export] macro_rules! audio {
|
||||||
|
(|
|
||||||
|
$self1:ident:
|
||||||
|
$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?,$c:ident,$s:ident
|
||||||
|
|$cb:expr$(;|$self2:ident,$e:ident|$cb2:expr)?) => {
|
||||||
|
impl $(<$($L),*$($T $(: $U)?),*>)? Audio for $Struct $(<$($L),*$($T),*>)? {
|
||||||
|
#[inline] fn process (&mut $self1, $c: &Client, $s: &ProcessScope) -> Control { $cb }
|
||||||
|
$(#[inline] fn handle (&mut $self2, $e: JackEvent) { $cb2 })?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// 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);
|
||||||
|
|
||||||
pub(crate) use ConnectName::*;
|
impl<T: Fn(JackEvent) + Send> NotificationHandler for Notifications<T> {
|
||||||
pub(crate) use ConnectScope::*;
|
fn thread_init(&self, _: &Client) {
|
||||||
pub(crate) use ConnectStatus::*;
|
self.0(JackEvent::ThreadInit);
|
||||||
pub(crate) use std::sync::{Arc, RwLock};
|
}
|
||||||
|
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::*;
|
||||||
|
|
||||||
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::*;
|
|
||||||
|
|
|
||||||
|
|
@ -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)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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)?)
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
|
|
@ -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>;
|
|
||||||
|
|
@ -88,9 +88,8 @@ mod time; pub use self::time::*;
|
||||||
mod note; pub use self::note::*;
|
mod note; pub use self::note::*;
|
||||||
pub mod jack; pub use self::jack::*;
|
pub mod jack; pub use self::jack::*;
|
||||||
pub mod midi; pub use self::midi::*;
|
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::sync::{Arc, RwLock, atomic::{AtomicUsize, AtomicBool, Ordering::Relaxed}};
|
||||||
pub(crate) use std::fmt::Debug;
|
pub(crate) use std::fmt::Debug;
|
||||||
pub(crate) use std::ops::{Add, Sub, Mul, Div, Rem};
|
pub(crate) use std::ops::{Add, Sub, Mul, Div, Rem};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,6 @@ pub use ::midly::{
|
||||||
live::*,
|
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
|
/// 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> {
|
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 }|(
|
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();
|
evt.write(&mut buf).unwrap();
|
||||||
output[0].push(buf);
|
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; },
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
2
deps/tengri
vendored
|
|
@ -1 +1 @@
|
||||||
Subproject commit 455d6d00d5f91e7f9f6b9d3711aa47e09900ad46
|
Subproject commit 2048dd2263fc4389c8f510bfd12c851efe34026f
|
||||||
Loading…
Add table
Add a link
Reference in a new issue