mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-04-03 21:00:44 +02:00
Compare commits
4 commits
b6559fc904
...
4f5c332f48
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f5c332f48 | ||
|
|
ab90129f4c | ||
|
|
6295f2e601 | ||
|
|
9ae35830c3 |
14 changed files with 1456 additions and 1941 deletions
4
.gitmodules
vendored
4
.gitmodules
vendored
|
|
@ -6,8 +6,8 @@
|
|||
path = tengri
|
||||
url = https://codeberg.org/unspeaker/tengri
|
||||
[submodule "deps/rust-jack"]
|
||||
path = rust-jack
|
||||
path = tengri/rust-jack
|
||||
url = https://codeberg.org/unspeaker/rust-jack
|
||||
[submodule "deps/dizzle"]
|
||||
path = dizzle
|
||||
path = tengri/dizzle
|
||||
url = ssh://git@codeberg.org/unspeaker/dizzle.git
|
||||
|
|
|
|||
3
Cargo.lock
generated
3
Cargo.lock
generated
|
|
@ -2992,9 +2992,7 @@ dependencies = [
|
|||
"builder-pattern",
|
||||
"bumpalo",
|
||||
"clap",
|
||||
"dizzle",
|
||||
"gtk",
|
||||
"jack",
|
||||
"konst",
|
||||
"livi",
|
||||
"midly",
|
||||
|
|
@ -3035,6 +3033,7 @@ dependencies = [
|
|||
"better-panic",
|
||||
"crossterm 0.29.0",
|
||||
"dizzle",
|
||||
"jack",
|
||||
"palette",
|
||||
"quanta",
|
||||
"rand 0.8.5",
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@ rustflags = ["-C", "link-arg=-fuse-ld=mold"]
|
|||
|
||||
[dependencies]
|
||||
tengri = { path = "./tengri", features = [ "tui", "dsl" ] }
|
||||
dizzle = { path = "./dizzle" }
|
||||
jack = { path = "./rust-jack" }
|
||||
|
||||
ansi_term = { version = "0.12.1" }
|
||||
atomic_float = { version = "1.0.0" }
|
||||
|
|
@ -52,7 +50,6 @@ proptest-derive = { version = "^0.5.1" }
|
|||
|
||||
[features]
|
||||
default = ["cli", "arranger", "sampler"]
|
||||
|
||||
arranger = ["port", "editor", "sequencer", "track", "scene", "clip", "select"]
|
||||
browse = []
|
||||
clap = []
|
||||
|
|
|
|||
8
Justfile
8
Justfile
|
|
@ -2,7 +2,10 @@ export RUSTFLAGS := "--cfg procmacro2_semver_exempt -Zmacro-backtrace -Clink-arg
|
|||
export RUST_BACKTRACE := "1"
|
||||
|
||||
default +ARGS="new":
|
||||
target/debug/tek {{ARGS}}
|
||||
cargo run -- {{ARGS}}
|
||||
|
||||
doc +ARGS="":
|
||||
cargo doc --open -j4 --document-private-items {{ARGS}}
|
||||
|
||||
cloc:
|
||||
for src in {cli,edn/src,input/src,jack/src,midi/src,output/src,plugin/src,sampler/src,tek/src,time/src,tui/src}; do echo; echo $src; cloc --quiet $src; done
|
||||
|
|
@ -43,9 +46,6 @@ run-init:
|
|||
prof:
|
||||
CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph --
|
||||
|
||||
doc:
|
||||
cargo doc -j4 --workspace --document-private-items
|
||||
|
||||
release := "reset && cargo run --release --"
|
||||
release:
|
||||
{{release}}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,50 @@
|
|||
|
||||
//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 view_h2 (&self) -> impl Content<TuiOut> {
|
||||
//let cache = self.project.clock.view_cache.clone();
|
||||
//let cache = cache.read().unwrap();
|
||||
//add(&Fixed::x(15, Align::w(Bsp::s(
|
||||
//FieldH(theme, "Beat", cache.beat.view.clone()),
|
||||
//FieldH(theme, "Time", cache.time.view.clone()),
|
||||
//))));
|
||||
//add(&Fixed::x(13, Align::w(Bsp::s(
|
||||
//Fill::X(Align::w(FieldH(theme, "BPM", cache.bpm.view.clone()))),
|
||||
//Fill::X(Align::w(FieldH(theme, "SR ", cache.sr.view.clone()))),
|
||||
//))));
|
||||
//add(&Fixed::x(12, Align::w(Bsp::s(
|
||||
//Fill::X(Align::w(FieldH(theme, "Buf", cache.buf.view.clone()))),
|
||||
//Fill::X(Align::w(FieldH(theme, "Lat", cache.lat.view.clone()))),
|
||||
//))));
|
||||
//add(&Bsp::s(
|
||||
//Fill::X(Align::w(FieldH(theme, "Selected", Align::w(self.selection().describe(
|
||||
//self.tracks(),
|
||||
//self.scenes()
|
||||
//))))),
|
||||
//Fill::X(Align::w(FieldH(theme, format!("History ({})", self.history.len()),
|
||||
//self.history.last().map(|last|Fill::X(Align::w(format!("{:?}", last.0)))))))
|
||||
//));
|
||||
////if let Some(last) = self.history.last() {
|
||||
////add(&FieldV(theme, format!("History ({})", self.history.len()),
|
||||
////Fill::X(Align::w(format!("{:?}", last.0)))));
|
||||
////}
|
||||
//}
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//pub fn view_nil (_: &App) -> TuiCb {
|
||||
|
|
@ -1137,3 +1183,6 @@
|
|||
//cols
|
||||
//Thunk::new(|to: &mut TuiOut|{
|
||||
//})
|
||||
//take!(ClipCommand |state: Arrangement, iter|state.selected_clip().as_ref()
|
||||
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
||||
|
||||
|
|
|
|||
112
app/tek.rs
112
app/tek.rs
|
|
@ -13,8 +13,8 @@ extern crate xdg;
|
|||
pub(crate) use ::xdg::BaseDirectories;
|
||||
pub extern crate atomic_float;
|
||||
pub(crate) use atomic_float::AtomicF64;
|
||||
pub extern crate jack;
|
||||
pub(crate) use ::jack::{*, contrib::{*, ClosureProcessHandler}};
|
||||
//pub extern crate jack;
|
||||
//pub(crate) use ::jack::{*, contrib::{*, ClosureProcessHandler}};
|
||||
pub extern crate midly;
|
||||
pub(crate) use ::midly::{Smf, TrackEventKind, MidiMessage, Error as MidiError, num::*, live::*};
|
||||
pub extern crate tengri;
|
||||
|
|
@ -59,7 +59,6 @@ pub(crate) use tengri::{
|
|||
pub(crate) use ConnectName::*;
|
||||
pub(crate) use ConnectScope::*;
|
||||
pub(crate) use ConnectStatus::*;
|
||||
pub(crate) use JackState::*;
|
||||
|
||||
/// Command-line entrypoint.
|
||||
#[cfg(feature = "cli")] pub fn main () -> Usually<()> {
|
||||
|
|
@ -70,7 +69,7 @@ pub(crate) use JackState::*;
|
|||
/// Create a new application from a backend, project, config, and mode
|
||||
///
|
||||
/// ```
|
||||
/// let jack = tek::Jack::new(&"test_tek").expect("failed to connect to jack");
|
||||
/// let jack = tek::tengri::Jack::new(&"test_tek").expect("failed to connect to jack");
|
||||
/// let proj = tek::Arrangement::default();
|
||||
/// let mut conf = tek::Config::default();
|
||||
/// conf.add("(mode hello)");
|
||||
|
|
@ -301,10 +300,10 @@ pub fn tek_print_status (project: &Arrangement) {
|
|||
}
|
||||
|
||||
/// Return boxed iterator of MIDI events
|
||||
pub fn parse_midi_input <'a> (input: ::jack::MidiIter<'a>)
|
||||
pub fn parse_midi_input <'a> (input: ::tengri::jack::MidiIter<'a>)
|
||||
-> Box<dyn Iterator<Item=(usize, LiveEvent<'a>, &'a [u8])> + 'a>
|
||||
{
|
||||
Box::new(input.map(|::jack::RawMidi { time, bytes }|(
|
||||
Box::new(input.map(|::tengri::jack::RawMidi { time, bytes }|(
|
||||
time as usize,
|
||||
LiveEvent::parse(bytes).unwrap(),
|
||||
bytes
|
||||
|
|
@ -384,12 +383,12 @@ pub fn device_kinds () -> &'static [&'static str] {
|
|||
]
|
||||
}
|
||||
|
||||
impl<T: Has<Vec<Device>>> HasDevices for T {
|
||||
impl<T: AsRef<Vec<Device>> + AsMut<Vec<Device>>> HasDevices for T {
|
||||
fn devices (&self) -> &Vec<Device> {
|
||||
self.get()
|
||||
self.as_ref()
|
||||
}
|
||||
fn devices_mut (&mut self) -> &mut Vec<Device> {
|
||||
self.get_mut()
|
||||
self.as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -426,24 +425,10 @@ impl Device {
|
|||
}
|
||||
}
|
||||
|
||||
audio!(|self: DeviceAudio<'a>, client, scope|{
|
||||
use Device::*;
|
||||
match self.0 {
|
||||
Mute => { Control::Continue },
|
||||
Bypass => { /*TODO*/ Control::Continue },
|
||||
#[cfg(feature = "sampler")] Sampler(sampler) => sampler.process(client, scope),
|
||||
#[cfg(feature = "lv2")] Lv2(lv2) => lv2.process(client, scope),
|
||||
#[cfg(feature = "vst2")] Vst2 => { todo!() }, // TODO
|
||||
#[cfg(feature = "vst3")] Vst3 => { todo!() }, // TODO
|
||||
#[cfg(feature = "clap")] Clap => { todo!() }, // TODO
|
||||
#[cfg(feature = "sf2")] Sf2 => { todo!() }, // TODO
|
||||
}
|
||||
});
|
||||
|
||||
//take!(DeviceCommand|state: Arrangement, iter|state.selected_device().as_ref()
|
||||
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));
|
||||
|
||||
|
||||
#[macro_export] macro_rules! has_clip {
|
||||
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
||||
impl $(<$($L),*$($T $(: $U)?),*>)? HasMidiClip for $Struct $(<$($L),*$($T),*>)? {
|
||||
|
|
@ -1127,26 +1112,6 @@ mod view {
|
|||
Fixed::XY(20, 1 + ins, frame.enclose(Fixed::XY(20, 1 + ins, field)))
|
||||
}
|
||||
|
||||
pub fn per_track_top <'a, T: Content<TuiOut> + 'a, U: TracksSizes<'a>> (
|
||||
tracks: impl Fn() -> U + Send + Sync + 'a,
|
||||
callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a
|
||||
) -> impl Content<TuiOut> + 'a {
|
||||
Align::x(Tui::bg(Reset, Map::new(tracks,
|
||||
move|(index, track, x1, x2): (usize, &'a Track, usize, usize), _|{
|
||||
let width = (x2 - x1) as u16;
|
||||
map_east(x1 as u16, width, Fixed::X(width, Tui::fg_bg(
|
||||
track.color.lightest.rgb,
|
||||
track.color.base.rgb,
|
||||
callback(index, track))))})))
|
||||
}
|
||||
|
||||
pub fn per_track <'a, T: Content<TuiOut> + 'a, U: TracksSizes<'a>> (
|
||||
tracks: impl Fn() -> U + Send + Sync + 'a,
|
||||
callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a
|
||||
) -> impl Content<TuiOut> + 'a {
|
||||
per_track_top(tracks, move|index, track|Fill::Y(Align::y(callback(index, track))))
|
||||
}
|
||||
|
||||
pub fn io_ports <'a, T: PortsSizes<'a>> (
|
||||
fg: Color, bg: Color, iter: impl Fn()->T + Send + Sync + 'a
|
||||
) -> impl Content<TuiOut> + 'a {
|
||||
|
|
@ -1159,67 +1124,6 @@ mod view {
|
|||
Fill::Y(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg,
|
||||
&connect.info)))))))))
|
||||
}
|
||||
|
||||
#[cfg(feature = "lv2")] pub fn draw_header (state: &Lv2, to: &mut TuiOut, x: u16, y: u16, w: u16) {
|
||||
let style = Style::default().gray();
|
||||
let label1 = format!(" {}", state.name);
|
||||
to.blit(&label1, x + 1, y, Some(style.white().bold()));
|
||||
if let Some(ref path) = state.path {
|
||||
let label2 = format!("{}…", &path[..((w as usize - 10).min(path.len()))]);
|
||||
to.blit(&label2, x + 2 + label1.len() as u16, y, Some(style.not_dim()));
|
||||
}
|
||||
//Ok(Rect { x, y, width: w, height: 1 })
|
||||
}
|
||||
|
||||
pub fn draw_sample (
|
||||
to: &mut TuiOut, x: u16, y: u16, note: Option<&u7>, sample: &Sample, focus: bool
|
||||
) -> Usually<usize> {
|
||||
let style = if focus { Style::default().green() } else { Style::default() };
|
||||
if focus {
|
||||
to.blit(&"🬴", x+1, y, Some(style.bold()));
|
||||
}
|
||||
let label1 = format!("{:3} {:12}",
|
||||
note.map(|n|n.to_string()).unwrap_or(String::default()),
|
||||
sample.name);
|
||||
let label2 = format!("{:>6} {:>6} +0.0",
|
||||
sample.start,
|
||||
sample.end);
|
||||
to.blit(&label1, x+2, y, Some(style.bold()));
|
||||
to.blit(&label2, x+3+label1.len()as u16, y, Some(style));
|
||||
Ok(label1.len() + label2.len() + 4)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn sampler_jack_process (
|
||||
state: &mut Sampler, _: &Client, scope: &ProcessScope
|
||||
) -> Control {
|
||||
if let Some(midi_in) = &state.midi_in {
|
||||
for midi in midi_in.port().iter(scope) {
|
||||
sampler_midi_in(&state.samples, &state.voices, midi)
|
||||
}
|
||||
}
|
||||
state.process_audio_out(scope);
|
||||
state.process_audio_in(scope);
|
||||
Control::Continue
|
||||
}
|
||||
|
||||
/// Create [Voice]s from [Sample]s in response to MIDI input.
|
||||
pub(crate) fn sampler_midi_in (
|
||||
samples: &SampleKit<128>, voices: &Arc<RwLock<Vec<Voice>>>, RawMidi { time, bytes }: RawMidi
|
||||
) {
|
||||
if let Ok(LiveEvent::Midi { message, .. }) = LiveEvent::parse(bytes) {
|
||||
match message {
|
||||
MidiMessage::NoteOn { ref key, ref vel } => {
|
||||
if let Some(sample) = samples.get(key.as_int() as usize) {
|
||||
voices.write().unwrap().push(Sample::play(sample, time as usize, vel));
|
||||
}
|
||||
},
|
||||
MidiMessage::Controller { controller: _, value: _ } => {
|
||||
// TODO
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)] mod test_view_meter {
|
||||
|
|
|
|||
1922
app/tek_impls.rs
1922
app/tek_impls.rs
File diff suppressed because it is too large
Load diff
|
|
@ -2,62 +2,10 @@ use crate::*;
|
|||
use clap::{self, Parser, Subcommand};
|
||||
use builder_pattern::Builder;
|
||||
|
||||
/// Wraps [JackState], and through it [jack::Client] when connected.
|
||||
///
|
||||
/// ```
|
||||
/// let jack = tek::Jack::default();
|
||||
/// ```
|
||||
#[derive(Clone, Debug, Default)] pub struct Jack<'j> (
|
||||
pub(crate) Arc<RwLock<JackState<'j>>>
|
||||
);
|
||||
|
||||
/// This is a connection which may be [Inactive], [Activating], or [Active].
|
||||
/// In the [Active] and [Inactive] states, [JackState::client] returns a
|
||||
/// [jack::Client], which you can use to talk to the JACK API.
|
||||
///
|
||||
/// ```
|
||||
/// let state = tek::JackState::default();
|
||||
/// ```
|
||||
#[derive(Debug, Default)] pub enum JackState<'j> {
|
||||
/// Unused
|
||||
#[default] Inert,
|
||||
/// Before activation.
|
||||
Inactive(Client),
|
||||
/// During activation.
|
||||
Activating,
|
||||
/// After activation. Must not be dropped for JACK thread to persist.
|
||||
Active(DynamicAsyncClient<'j>),
|
||||
}
|
||||
|
||||
/// Event enum for JACK events.
|
||||
///
|
||||
/// ```
|
||||
/// let event = tek::JackEvent::XRun;
|
||||
/// ```
|
||||
#[derive(Debug, Clone, PartialEq)] pub enum JackEvent {
|
||||
ThreadInit,
|
||||
Shutdown(ClientStatus, Arc<str>),
|
||||
Freewheel(bool),
|
||||
SampleRate(Frames),
|
||||
ClientRegistration(Arc<str>, bool),
|
||||
PortRegistration(PortId, bool),
|
||||
PortRename(PortId, Arc<str>, Arc<str>),
|
||||
PortsConnected(PortId, PortId, bool),
|
||||
GraphReorder,
|
||||
XRun,
|
||||
}
|
||||
|
||||
/// Generic notification handler that emits [JackEvent]
|
||||
///
|
||||
/// ```
|
||||
/// let notify = tek::JackNotify(|_|{});
|
||||
/// ```
|
||||
pub struct JackNotify<T: Fn(JackEvent) + Send>(pub T);
|
||||
|
||||
/// Total state
|
||||
///
|
||||
/// ```
|
||||
/// use tek::{TracksView, ScenesView, AddScene};
|
||||
/// use tek::{HasTracks, HasScenes, TracksView, ScenesView};
|
||||
/// let mut app = tek::App::default();
|
||||
/// let _ = app.scene_add(None, None).unwrap();
|
||||
/// let _ = app.update_clock();
|
||||
|
|
|
|||
334
app/tek_trait.rs
334
app/tek_trait.rs
|
|
@ -1,129 +1,6 @@
|
|||
use crate::*;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
/// Things that can provide a [jack::Client] reference.
|
||||
///
|
||||
/// ```
|
||||
/// use tek::{Jack, HasJack};
|
||||
///
|
||||
/// let jack: &Jack = Jacked::default().jack();
|
||||
///
|
||||
/// #[derive(Default)] struct Jacked<'j>(Jack<'j>);
|
||||
///
|
||||
/// impl<'j> tek::HasJack<'j> for Jacked<'j> {
|
||||
/// fn jack (&self) -> &Jack<'j> { &self.0 }
|
||||
/// }
|
||||
/// ```
|
||||
pub trait HasJack<'j>: Send + Sync {
|
||||
|
||||
/// Return the internal [jack::Client] handle
|
||||
/// that lets you call the JACK API.
|
||||
fn jack (&self) -> &Jack<'j>;
|
||||
|
||||
fn with_client <T> (&self, op: impl FnOnce(&Client)->T) -> T {
|
||||
self.jack().with_client(op)
|
||||
}
|
||||
|
||||
fn port_by_name (&self, name: &str) -> Option<Port<Unowned>> {
|
||||
self.with_client(|client|client.port_by_name(name))
|
||||
}
|
||||
|
||||
fn port_by_id (&self, id: u32) -> Option<Port<Unowned>> {
|
||||
self.with_client(|c|c.port_by_id(id))
|
||||
}
|
||||
|
||||
fn register_port <PS: PortSpec + Default> (&self, name: impl AsRef<str>) -> Usually<Port<PS>> {
|
||||
self.with_client(|client|Ok(client.register_port(name.as_ref(), PS::default())?))
|
||||
}
|
||||
|
||||
fn sync_lead (&self, enable: bool, callback: impl Fn(TimebaseInfo)->Position) -> Usually<()> {
|
||||
if enable {
|
||||
self.with_client(|client|match client.register_timebase_callback(false, callback) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(e)
|
||||
})?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn sync_follow (&self, _enable: bool) -> Usually<()> {
|
||||
// TODO: sync follow
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for thing that has a JACK process callback.
|
||||
pub trait Audio {
|
||||
|
||||
/// Handle a JACK event.
|
||||
fn handle (&mut self, _event: JackEvent) {}
|
||||
|
||||
/// Projecss a JACK chunk.
|
||||
fn process (&mut self, _: &Client, _: &ProcessScope) -> Control {
|
||||
Control::Continue
|
||||
}
|
||||
|
||||
/// The JACK process callback function passed to the server.
|
||||
fn callback (
|
||||
state: &Arc<RwLock<Self>>, client: &Client, scope: &ProcessScope
|
||||
) -> Control where Self: Sized {
|
||||
if let Ok(mut state) = state.write() {
|
||||
state.process(client, scope)
|
||||
} else {
|
||||
Control::Quit
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Implement [Audio]: provide JACK callbacks.
|
||||
#[macro_export] macro_rules! audio {
|
||||
|
||||
(|
|
||||
$self1:ident:
|
||||
$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?,$c:ident,$s:ident
|
||||
|$cb:expr$(;|$self2:ident,$e:ident|$cb2:expr)?) => {
|
||||
impl $(<$($L),*$($T $(: $U)?),*>)? Audio for $Struct $(<$($L),*$($T),*>)? {
|
||||
#[inline] fn process (&mut $self1, $c: &Client, $s: &ProcessScope) -> Control { $cb }
|
||||
$(#[inline] fn handle (&mut $self2, $e: JackEvent) { $cb2 })?
|
||||
}
|
||||
};
|
||||
|
||||
($Struct:ident: $process:ident, $handle:ident) => {
|
||||
impl Audio for $Struct {
|
||||
#[inline] fn process (&mut self, c: &Client, s: &ProcessScope) -> Control {
|
||||
$process(self, c, s)
|
||||
}
|
||||
#[inline] fn handle (&mut self, e: JackEvent) {
|
||||
$handle(self, e)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
($Struct:ident: $process:ident) => {
|
||||
impl Audio for $Struct {
|
||||
#[inline] fn process (&mut self, c: &Client, s: &ProcessScope) -> Control {
|
||||
$process(self, c, s)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
pub trait JackPerfModel {
|
||||
fn update_from_jack_scope (&self, t0: Option<u64>, scope: &ProcessScope);
|
||||
}
|
||||
|
||||
|
||||
//pub trait MaybeHas<T>: Send + Sync {
|
||||
//fn get (&self) -> Option<&T>;
|
||||
//}
|
||||
|
||||
pub trait HasN<T>: Send + Sync {
|
||||
fn get_nth (&self, key: usize) -> &T;
|
||||
fn get_nth_mut (&mut self, key: usize) -> &mut T;
|
||||
}
|
||||
|
||||
pub trait Gettable<T> {
|
||||
/// Returns current value
|
||||
fn get (&self) -> T;
|
||||
|
|
@ -242,37 +119,93 @@ pub trait MidiRange: TimeRange + NoteRange {}
|
|||
/// can be clocked in microseconds with f64 without losing precision.
|
||||
pub trait TimeUnit: InteriorMutable<f64> {}
|
||||
|
||||
pub trait HasSceneScroll: HasScenes {
|
||||
fn scene_scroll (&self) -> usize;
|
||||
}
|
||||
|
||||
pub trait HasTrackScroll: HasTracks {
|
||||
fn track_scroll (&self) -> usize;
|
||||
}
|
||||
pub trait HasClipsSize { fn clips_size (&self) -> &Measure<TuiOut>; }
|
||||
|
||||
pub trait HasMidiClip {
|
||||
fn clip (&self) -> Option<Arc<RwLock<MidiClip>>>;
|
||||
}
|
||||
|
||||
pub trait HasClipsSize {
|
||||
fn clips_size (&self) -> &Measure<TuiOut>;
|
||||
pub trait HasClock: AsRef<Clock> + AsMut<Clock> {
|
||||
fn clock (&self) -> &Clock { self.as_ref() }
|
||||
fn clock_mut (&mut self) -> &mut Clock { self.as_mut() }
|
||||
}
|
||||
|
||||
pub trait HasClock: Send + Sync {
|
||||
fn clock (&self) -> &Clock;
|
||||
fn clock_mut (&mut self) -> &mut Clock;
|
||||
pub trait HasDevices: AsRef<Vec<Device>> + AsMut<Vec<Device>> {
|
||||
fn devices (&self) -> &Vec<Device> { self.as_ref() }
|
||||
fn devices_mut (&mut self) -> &mut Vec<Device> { self.as_mut() }
|
||||
}
|
||||
|
||||
pub trait HasDevices {
|
||||
fn devices (&self) -> &Vec<Device>;
|
||||
fn devices_mut (&mut self) -> &mut Vec<Device>;
|
||||
pub trait HasSequencer: AsRef<Sequencer> + AsMut<Sequencer> {
|
||||
fn sequencer_mut (&mut self) -> &mut Sequencer { self.as_mut() }
|
||||
fn sequencer (&self) -> &Sequencer { self.as_ref() }
|
||||
}
|
||||
|
||||
pub trait HasSelection: Has<Selection> {
|
||||
fn selection (&self) -> &Selection { self.get() }
|
||||
fn selection_mut (&mut self) -> &mut Selection { self.get_mut() }
|
||||
pub trait HasSceneScroll: HasScenes { fn scene_scroll (&self) -> usize; }
|
||||
pub trait HasTrackScroll: HasTracks { fn track_scroll (&self) -> usize; }
|
||||
pub trait HasScene: AsRefOpt<Scene> + AsMutOpt<Scene> {
|
||||
fn scene_mut (&mut self) -> Option<&mut Scene> { self.as_mut_opt() }
|
||||
fn scene (&self) -> Option<&Scene> { self.as_ref_opt() }
|
||||
}
|
||||
pub trait HasSelection: AsRef<Selection> + AsMut<Selection> {
|
||||
fn selection (&self) -> &Selection { self.as_ref() }
|
||||
fn selection_mut (&mut self) -> &mut Selection { self.as_mut() }
|
||||
/// Get the active track
|
||||
#[cfg(feature = "track")]
|
||||
fn selected_track (&self) -> Option<&Track> where Self: HasTracks {
|
||||
let index = self.selection().track()?;
|
||||
self.tracks().get(index)
|
||||
}
|
||||
/// Get a mutable reference to the active track
|
||||
#[cfg(feature = "track")]
|
||||
fn selected_track_mut (&mut self) -> Option<&mut Track> where Self: HasTracks {
|
||||
let index = self.selection().track()?;
|
||||
self.tracks_mut().get_mut(index)
|
||||
}
|
||||
/// Get the active scene
|
||||
#[cfg(feature = "scene")]
|
||||
fn selected_scene (&self) -> Option<&Scene> where Self: HasScenes {
|
||||
let index = self.selection().scene()?;
|
||||
self.scenes().get(index)
|
||||
}
|
||||
/// Get a mutable reference to the active scene
|
||||
#[cfg(feature = "scene")]
|
||||
fn selected_scene_mut (&mut self) -> Option<&mut Scene> where Self: HasScenes {
|
||||
let index = self.selection().scene()?;
|
||||
self.scenes_mut().get_mut(index)
|
||||
}
|
||||
/// Get the active clip
|
||||
#[cfg(feature = "clip")]
|
||||
fn selected_clip (&self) -> Option<Arc<RwLock<MidiClip>>> where Self: HasScenes + HasTracks {
|
||||
self.selected_scene()?.clips.get(self.selection().track()?)?.clone()
|
||||
}
|
||||
}
|
||||
pub trait HasScenes: AsRef<Vec<Scene>> + AsMut<Vec<Scene>> {
|
||||
fn scenes (&self) -> &Vec<Scene> { self.as_ref() }
|
||||
fn scenes_mut (&mut self) -> &mut Vec<Scene> { self.as_mut() }
|
||||
/// Generate the default name for a new scene
|
||||
fn scene_default_name (&self) -> Arc<str> { format!("s{:3>}", self.scenes().len() + 1).into() }
|
||||
fn scene_longest_name (&self) -> usize { self.scenes().iter().map(|s|s.name.len()).fold(0, usize::max) }
|
||||
/// Add multiple scenes
|
||||
fn scenes_add (&mut self, n: usize) -> Usually<()> where Self: HasTracks {
|
||||
let scene_color_1 = ItemColor::random();
|
||||
let scene_color_2 = ItemColor::random();
|
||||
for i in 0..n {
|
||||
let _ = self.scene_add(None, Some(
|
||||
scene_color_1.mix(scene_color_2, i as f32 / n as f32).into()
|
||||
))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// Add a scene
|
||||
fn scene_add (&mut self, name: Option<&str>, color: Option<ItemTheme>)
|
||||
-> Usually<(usize, &mut Scene)> where Self: HasTracks
|
||||
{
|
||||
let scene = Scene {
|
||||
name: name.map_or_else(||self.scene_default_name(), |x|x.to_string().into()),
|
||||
clips: vec![None;self.tracks().len()],
|
||||
color: color.unwrap_or_else(ItemTheme::random),
|
||||
};
|
||||
self.scenes_mut().push(scene);
|
||||
let index = self.scenes().len() - 1;
|
||||
Ok((index, &mut self.scenes_mut()[index]))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HasWidth {
|
||||
const MIN_WIDTH: usize;
|
||||
/// Increment track width.
|
||||
|
|
@ -280,76 +213,32 @@ pub trait HasWidth {
|
|||
/// Decrement track width, down to a hardcoded minimum of [Self::MIN_WIDTH].
|
||||
fn width_dec (&mut self);
|
||||
}
|
||||
|
||||
pub trait HasMidiBuffers {
|
||||
fn note_buf_mut (&mut self) -> &mut Vec<u8>;
|
||||
fn midi_buf_mut (&mut self) -> &mut Vec<Vec<Vec<u8>>>;
|
||||
}
|
||||
|
||||
pub trait HasSequencer {
|
||||
fn sequencer (&self) -> &Sequencer;
|
||||
fn sequencer_mut (&mut self) -> &mut Sequencer;
|
||||
}
|
||||
|
||||
pub trait HasScene: Has<Option<Scene>> + Send + Sync {
|
||||
fn scene (&self) -> Option<&Scene> {
|
||||
Has::<Option<Scene>>::get(self).as_ref()
|
||||
}
|
||||
fn scene_mut (&mut self) -> &mut Option<Scene> {
|
||||
Has::<Option<Scene>>::get_mut(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HasScenes: Has<Vec<Scene>> + Send + Sync {
|
||||
fn scenes (&self) -> &Vec<Scene> {
|
||||
Has::<Vec<Scene>>::get(self)
|
||||
}
|
||||
fn scenes_mut (&mut self) -> &mut Vec<Scene> {
|
||||
Has::<Vec<Scene>>::get_mut(self)
|
||||
}
|
||||
/// Generate the default name for a new scene
|
||||
fn scene_default_name (&self) -> Arc<str> {
|
||||
format!("s{:3>}", self.scenes().len() + 1).into()
|
||||
}
|
||||
fn scene_longest_name (&self) -> usize {
|
||||
self.scenes().iter().map(|s|s.name.len()).fold(0, usize::max)
|
||||
}
|
||||
}
|
||||
|
||||
/// ```
|
||||
/// use tek::{MidiEditor, HasEditor, tengri::Has};
|
||||
/// use tek::{*, tengri::*};
|
||||
///
|
||||
/// let mut host = TestEditorHost(Some(MidiEditor::default()));
|
||||
/// struct TestEditorHost(Option<MidiEditor>);
|
||||
/// impl Has<Option<MidiEditor>> for TestEditorHost {
|
||||
/// fn get (&self) -> &Option<MidiEditor> { &self.0 }
|
||||
/// fn get_mut (&mut self) -> &mut Option<MidiEditor> { &mut self.0 }
|
||||
/// }
|
||||
/// struct Test(Option<MidiEditor>);
|
||||
/// impl_as_ref_opt!(MidiEditor: |self: Test|self.0.as_ref());
|
||||
/// impl_as_mut_opt!(MidiEditor: |self: Test|self.0.as_mut());
|
||||
///
|
||||
/// let mut host = Test(Some(MidiEditor::default()));
|
||||
/// let _ = host.editor();
|
||||
/// let _ = host.editor_mut();
|
||||
/// let _ = host.is_editing();
|
||||
/// let _ = host.editor_w();
|
||||
/// let _ = host.editor_h();
|
||||
/// ```
|
||||
pub trait HasEditor: Has<Option<MidiEditor>> {
|
||||
fn editor (&self) -> Option<&MidiEditor> {
|
||||
self.get().as_ref()
|
||||
}
|
||||
fn editor_mut (&mut self) -> Option<&mut MidiEditor> {
|
||||
self.get_mut().as_mut()
|
||||
}
|
||||
fn is_editing (&self) -> bool {
|
||||
self.editor().is_some()
|
||||
}
|
||||
fn editor_w (&self) -> usize {
|
||||
self.editor().map(|e|e.size.w()).unwrap_or(0) as usize
|
||||
}
|
||||
fn editor_h (&self) -> usize {
|
||||
self.editor().map(|e|e.size.h()).unwrap_or(0) as usize
|
||||
}
|
||||
pub trait HasEditor: AsRefOpt<MidiEditor> + AsMutOpt<MidiEditor> {
|
||||
fn editor (&self) -> Option<&MidiEditor> { self.as_ref_opt() }
|
||||
fn editor_mut (&mut self) -> Option<&mut MidiEditor> { self.as_mut_opt() }
|
||||
fn is_editing (&self) -> bool { self.editor().is_some() }
|
||||
fn editor_w (&self) -> usize { self.editor().map(|e|e.size.w()).unwrap_or(0) as usize }
|
||||
fn editor_h (&self) -> usize { self.editor().map(|e|e.size.h()).unwrap_or(0) as usize }
|
||||
}
|
||||
|
||||
pub trait HasClips {
|
||||
fn clips <'a> (&'a self) -> std::sync::RwLockReadGuard<'a, ClipPool>;
|
||||
fn clips_mut <'a> (&'a self) -> std::sync::RwLockWriteGuard<'a, ClipPool>;
|
||||
|
|
@ -359,7 +248,6 @@ pub trait HasClips {
|
|||
(self.clips().len() - 1, clip)
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for thing that may receive MIDI.
|
||||
pub trait HasMidiIns {
|
||||
fn midi_ins (&self) -> &Vec<MidiInput>;
|
||||
|
|
@ -384,7 +272,6 @@ pub trait HasMidiIns {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for thing that may output MIDI.
|
||||
pub trait HasMidiOuts {
|
||||
fn midi_outs (&self) -> &Vec<MidiOutput>;
|
||||
|
|
@ -406,10 +293,9 @@ pub trait HasMidiOuts {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HasTracks: Has<Vec<Track>> + Send + Sync {
|
||||
fn tracks (&self) -> &Vec<Track> { Has::<Vec<Track>>::get(self) }
|
||||
fn tracks_mut (&mut self) -> &mut Vec<Track> { Has::<Vec<Track>>::get_mut(self) }
|
||||
pub trait HasTracks: AsRef<Vec<Track>> + AsMut<Vec<Track>> {
|
||||
fn tracks (&self) -> &Vec<Track> { self.as_ref() }
|
||||
fn tracks_mut (&mut self) -> &mut Vec<Track> { self.as_mut() }
|
||||
/// Run audio callbacks for every track and every device
|
||||
fn process_tracks (&mut self, client: &Client, scope: &ProcessScope) -> Control {
|
||||
for track in self.tracks_mut().iter_mut() {
|
||||
|
|
@ -438,10 +324,9 @@ pub trait HasTracks: Has<Vec<Track>> + Send + Sync {
|
|||
/// Spacing between tracks.
|
||||
const TRACK_SPACING: usize = 0;
|
||||
}
|
||||
|
||||
pub trait HasTrack {
|
||||
fn track (&self) -> Option<&Track>;
|
||||
fn track_mut (&mut self) -> Option<&mut Track>;
|
||||
pub trait HasTrack: AsRefOpt<Track> + AsMutOpt<Track> {
|
||||
fn track (&self) -> Option<&Track> { self.as_ref_opt() }
|
||||
fn track_mut (&mut self) -> Option<&mut Track> { self.as_mut_opt() }
|
||||
#[cfg(feature = "port")] fn view_midi_ins_status <'a> (&'a self, theme: ItemTheme) -> impl Content<TuiOut> + 'a {
|
||||
self.track().map(move|track|view_ports_status(theme, "MIDI ins: ", &track.sequencer.midi_ins))
|
||||
}
|
||||
|
|
@ -669,33 +554,6 @@ pub trait MidiViewer: Measured<TuiOut> + MidiRange + MidiPoint + Debug + Send +
|
|||
}
|
||||
}
|
||||
|
||||
pub trait AddScene: HasScenes + HasTracks {
|
||||
/// Add multiple scenes
|
||||
fn scenes_add (&mut self, n: usize) -> Usually<()> {
|
||||
let scene_color_1 = ItemColor::random();
|
||||
let scene_color_2 = ItemColor::random();
|
||||
for i in 0..n {
|
||||
let _ = self.scene_add(None, Some(
|
||||
scene_color_1.mix(scene_color_2, i as f32 / n as f32).into()
|
||||
))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// Add a scene
|
||||
fn scene_add (&mut self, name: Option<&str>, color: Option<ItemTheme>)
|
||||
-> Usually<(usize, &mut Scene)>
|
||||
{
|
||||
let scene = Scene {
|
||||
name: name.map_or_else(||self.scene_default_name(), |x|x.to_string().into()),
|
||||
clips: vec![None;self.tracks().len()],
|
||||
color: color.unwrap_or_else(ItemTheme::random),
|
||||
};
|
||||
self.scenes_mut().push(scene);
|
||||
let index = self.scenes().len() - 1;
|
||||
Ok((index, &mut self.scenes_mut()[index]))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ClipsView: TracksView + ScenesView {
|
||||
|
||||
fn view_scenes_clips <'a> (&'a self)
|
||||
|
|
|
|||
|
|
@ -1,31 +1,5 @@
|
|||
use crate::*;
|
||||
|
||||
/// Running JACK [AsyncClient] with maximum type erasure.
|
||||
///
|
||||
/// One [Box] contains function that handles [JackEvent]s.
|
||||
///
|
||||
/// Another [Box] containing a function that handles realtime IO.
|
||||
///
|
||||
/// That's all it knows about them.
|
||||
pub type DynamicAsyncClient<'j>
|
||||
= AsyncClient<DynamicNotifications<'j>, DynamicAudioHandler<'j>>;
|
||||
|
||||
/// Notification handler wrapper for [BoxedAudioHandler].
|
||||
pub type DynamicAudioHandler<'j> =
|
||||
ClosureProcessHandler<(), BoxedAudioHandler<'j>>;
|
||||
|
||||
/// Boxed realtime callback.
|
||||
pub type BoxedAudioHandler<'j> =
|
||||
Box<dyn FnMut(&Client, &ProcessScope) -> Control + Send + Sync + 'j>;
|
||||
|
||||
/// Notification handler wrapper for [BoxedJackEventHandler].
|
||||
pub type DynamicNotifications<'j> =
|
||||
JackNotify<BoxedJackEventHandler<'j>>;
|
||||
|
||||
/// Boxed [JackEvent] callback.
|
||||
pub type BoxedJackEventHandler<'j> =
|
||||
Box<dyn Fn(JackEvent) + Send + Sync + 'j>;
|
||||
|
||||
pub type MidiData =
|
||||
Vec<Vec<MidiMessage>>;
|
||||
|
||||
|
|
|
|||
1
dizzle
1
dizzle
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 7d1fbe3fbe53699a3e12eb5a3d55db79653d72d8
|
||||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 764a38a880ab4749ea60aa7e53cd814b858e606c
|
||||
2
tengri
2
tengri
|
|
@ -1 +1 @@
|
|||
Subproject commit 5d0dc40fdcd7cc022d1e468d9bf59de722949ace
|
||||
Subproject commit f1dda6af07b94928481d062c3d3fda5b9e969633
|
||||
Loading…
Add table
Add a link
Reference in a new issue