flatten workspace into 1 crate

This commit is contained in:
🪞👃🪞 2024-12-29 00:10:30 +01:00
parent 7c4e1e2166
commit d926422c67
147 changed files with 66 additions and 126 deletions

View file

@ -1,40 +0,0 @@
[package]
name = "tek_cli"
edition = "2021"
version = "0.2.0"
[dependencies]
tek = { path = "../tek" }
clap = { version = "4.5.4", features = [ "derive" ] }
[[bin]]
name = "tek_arranger"
path = "src/cli_arranger.rs"
[[bin]]
name = "tek_sequencer"
path = "src/cli_sequencer.rs"
[[bin]]
name = "tek_groovebox"
path = "src/cli_groovebox.rs"
[[bin]]
name = "tek_transport"
path = "src/cli_transport.rs"
[[bin]]
name = "tek_sampler"
path = "src/cli_sampler.rs"
#[[bin]]
#name = "tek_mixer"
#path = "src/cli_mixer.rs"
#[[bin]]
#name = "tek_track"
#path = "src/cli_track.rs"
#[[bin]]
#name = "tek_plugin"
#path = "src/cli_plugin.rs"

View file

@ -1,133 +0,0 @@
include!("./lib.rs");
use tek::ArrangerTui;
pub fn main () -> Usually<()> { ArrangerCli::parse().run() }
/// Launches an interactive MIDI arranger.
#[derive(Debug, Parser)]
#[command(version, about, long_about = None)]
pub struct ArrangerCli {
/// Name of JACK client
#[arg(short, long)]
name: Option<String>,
/// Whether to include a transport toolbar (default: true)
#[arg(short, long, default_value_t = true)]
transport: bool,
/// Number of tracks
#[arg(short = 'x', long, default_value_t = 4)]
tracks: usize,
/// Number of scenes
#[arg(short, long, default_value_t = 8)]
scenes: usize,
/// MIDI outs to connect each track to.
#[arg(short='i', long)]
midi_from: Vec<String>,
/// MIDI ins to connect each track to.
#[arg(short='o', long)]
midi_to: Vec<String>,
}
impl ArrangerCli {
/// Run the arranger TUI from CLI arguments.
fn run (&self) -> Usually<()> {
let mut client_name = String::from("tek_arranger");
if let Some(name) = self.name.as_ref() {
client_name = name.clone();
}
Tui::run(JackClient::new(client_name.as_str())?.activate_with(|jack|{
let mut app = ArrangerTui::try_from(jack)?;
let jack = jack.read().unwrap();
app.color = ItemPalette::random();
add_tracks(&jack, &mut app, self)?;
add_scenes(&mut app, self.scenes)?;
Ok(app)
})?)?;
Ok(())
}
}
fn add_tracks (jack: &JackClient, app: &mut ArrangerTui, cli: &ArrangerCli) -> Usually<()> {
let n = cli.tracks;
let track_color_1 = ItemColor::random();
let track_color_2 = ItemColor::random();
for i in 0..n {
let track = app.track_add(None, Some(
track_color_1.mix(track_color_2, i as f32 / n as f32).into()
))?;
track.width = 8;
let name = track.name.read().unwrap();
track.player.midi_ins.push(
jack.register_port(&format!("{}I", &name), MidiIn::default())?
);
track.player.midi_outs.push(
jack.register_port(&format!("{}O", &name), MidiOut::default())?
);
}
for connection in cli.midi_from.iter() {
let mut split = connection.split("=");
let number = split.next().unwrap().trim();
if let Ok(track) = number.parse::<usize>() {
if track < 1 {
panic!("Tracks are zero-indexed")
}
if track > n {
panic!("Tried to connect track {track} or {n}. Pass -t {track} to increase number of tracks.")
}
if let Some(port) = split.next() {
if let Some(port) = jack.port_by_name(port).as_ref() {
jack.client().connect_ports(port, &app.tracks[track-1].player.midi_ins[0])?;
} else {
panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names.");
}
} else {
panic!("No port specified for track {track}. Format is TRACK_NUMBER=PORT_NAME")
}
} else {
panic!("Failed to parse track number: {number}")
}
}
for connection in cli.midi_to.iter() {
let mut split = connection.split("=");
let number = split.next().unwrap().trim();
if let Ok(track) = number.parse::<usize>() {
if track < 1 {
panic!("Tracks are zero-indexed")
}
if track > n {
panic!("Tried to connect track {track} or {n}. Pass -t {track} to increase number of tracks.")
}
if let Some(port) = split.next() {
if let Some(port) = jack.port_by_name(port).as_ref() {
jack.client().connect_ports(&app.tracks[track-1].player.midi_outs[0], port)?;
} else {
panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names.");
}
} else {
panic!("No port specified for track {track}. Format is TRACK_NUMBER=PORT_NAME")
}
} else {
panic!("Failed to parse track number: {number}")
}
}
Ok(())
}
fn add_scenes (app: &mut ArrangerTui, n: usize) -> Usually<()> {
let scene_color_1 = ItemColor::random();
let scene_color_2 = ItemColor::random();
for i in 0..n {
let _scene = app.scene_add(None, Some(
scene_color_1.mix(scene_color_2, i as f32 / n as f32).into()
))?;
}
Ok(())
}
#[test] fn verify_arranger_cli () {
use clap::CommandFactory;
ArrangerCli::command().debug_assert();
}

View file

@ -1,52 +0,0 @@
include!("./lib.rs");
pub fn main () -> Usually<()> { GrooveboxCli::parse().run() }
#[derive(Debug, Parser)]
#[command(version, about, long_about = None)]
pub struct GrooveboxCli {
/// Name of JACK client
#[arg(short, long)]
name: Option<String>,
/// Whether to include a transport toolbar (default: true)
#[arg(short, long, default_value_t = true)]
transport: bool,
/// MIDI outs to connect to MIDI input
#[arg(short='i', long)]
midi_from: Vec<String>,
/// MIDI ins to connect from MIDI output
#[arg(short='o', long)]
midi_to: Vec<String>,
/// Audio outs to connect to left input
#[arg(short='l', long)]
l_from: Vec<String>,
/// Audio outs to connect to right input
#[arg(short='r', long)]
r_from: Vec<String>,
/// Audio ins to connect from left output
#[arg(short='L', long)]
l_to: Vec<String>,
/// Audio ins to connect from right output
#[arg(short='R', long)]
r_to: Vec<String>,
}
impl GrooveboxCli {
fn run (&self) -> Usually<()> {
Tui::run(JackClient::new("tek_groovebox")?.activate_with(|jack|{
let app = tek::GrooveboxTui::try_from(jack)?;
jack.read().unwrap().client().connect_ports(&app.player.midi_outs[0], &app.sampler.midi_in)?;
jack.connect_midi_from(&app.player.midi_ins[0], &self.midi_from)?;
jack.connect_midi_from(&app.sampler.midi_in, &self.midi_from)?;
jack.connect_midi_to(&app.player.midi_outs[0], &self.midi_to)?;
jack.connect_audio_from(&app.sampler.audio_ins[0], &self.l_from)?;
jack.connect_audio_from(&app.sampler.audio_ins[1], &self.r_from)?;
jack.connect_audio_to(&app.sampler.audio_outs[0], &self.l_to)?;
jack.connect_audio_to(&app.sampler.audio_outs[1], &self.r_to)?;
Ok(app)
})?)?;
Ok(())
}
}
#[test] fn verify_groovebox_cli () {
use clap::CommandFactory;
GrooveboxCli::command().debug_assert();
}

View file

@ -1,17 +0,0 @@
include!("./lib.rs");
pub fn main () -> Usually<()> { SamplerCli::parse().run() }
#[derive(Debug, Parser)] #[command(version, about, long_about = None)] pub struct SamplerCli {
/// Name of JACK client
#[arg(short, long)] name: Option<String>,
/// Path to plugin
#[arg(short, long)] path: Option<String>,
}
impl SamplerCli {
fn run (&self) -> Usually<()> {
Tui::run(JackClient::new("tek_sampler")?.activate_with(|x|{
let sampler = tek::SamplerTui::try_from(x)?;
Ok(sampler)
})?)?;
Ok(())
}
}

View file

@ -1,46 +0,0 @@
include!("./lib.rs");
pub fn main () -> Usually<()> {
SequencerCli::parse().run()
}
/// Launches a single interactive MIDI sequencer.
#[derive(Debug, Parser)]
#[command(version, about, long_about = None)]
pub struct SequencerCli {
/// Name of JACK client
#[arg(short, long)]
name: Option<String>,
/// Whether to include a transport toolbar (default: true)
#[arg(short, long, default_value_t = true)]
transport: bool,
/// MIDI outs to connect to (multiple instances accepted)
#[arg(short='i', long)]
midi_from: Vec<String>,
/// MIDI ins to connect to (multiple instances accepted)
#[arg(short='o', long)]
midi_to: Vec<String>,
}
impl SequencerCli {
fn run (&self) -> Usually<()> {
let name = self.name.as_deref().unwrap_or("tek_sequencer");
Tui::run(JackClient::new(name)?.activate_with(|jack|{
let mut app = SequencerTui::try_from(jack)?;
let jack = jack.read().unwrap();
let midi_in = jack.register_port("i", MidiIn::default())?;
let midi_out = jack.register_port("o", MidiOut::default())?;
connect_from(&jack, &midi_in, &self.midi_from)?;
connect_to(&jack, &midi_out, &self.midi_to)?;
app.player.midi_ins.push(midi_in);
app.player.midi_outs.push(midi_out);
Ok(app)
})?)?;
Ok(())
}
}
#[test] fn verify_sequencer_cli () {
use clap::CommandFactory;
SequencerCli::command().debug_assert();
}

View file

@ -1,9 +0,0 @@
include!("./lib.rs");
/// Application entrypoint.
pub fn main () -> Usually<()> {
Tui::run(JackClient::new("tek_transport")?.activate_with(|jack|{
TransportTui::try_from(jack)
})?)?;
Ok(())
}

View file

@ -1,51 +0,0 @@
#[allow(unused_imports)] use std::sync::Arc;
#[allow(unused_imports)] use clap::{self, Parser};
#[allow(unused_imports)] use tek::{*, jack::*};
#[allow(unused)]
fn connect_from (jack: &JackClient, input: &Port<MidiIn>, ports: &[String]) -> Usually<()> {
for port in ports.iter() {
if let Some(port) = jack.port_by_name(port).as_ref() {
jack.client().connect_ports(port, input)?;
} else {
panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names.");
}
}
Ok(())
}
#[allow(unused)]
fn connect_to (jack: &JackClient, output: &Port<MidiOut>, ports: &[String]) -> Usually<()> {
for port in ports.iter() {
if let Some(port) = jack.port_by_name(port).as_ref() {
jack.client().connect_ports(output, port)?;
} else {
panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names.");
}
}
Ok(())
}
#[allow(unused)]
fn connect_audio_from (jack: &JackClient, input: &Port<AudioIn>, ports: &[String]) -> Usually<()> {
for port in ports.iter() {
if let Some(port) = jack.port_by_name(port).as_ref() {
jack.client().connect_ports(port, input)?;
} else {
panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names.");
}
}
Ok(())
}
#[allow(unused)]
fn connect_audio_to (jack: &JackClient, output: &Port<AudioOut>, ports: &[String]) -> Usually<()> {
for port in ports.iter() {
if let Some(port) = jack.port_by_name(port).as_ref() {
jack.client().connect_ports(output, port)?;
} else {
panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names.");
}
}
Ok(())
}

View file

@ -1,25 +0,0 @@
include!("./lib.rs");
pub fn main () -> Usually<()> {
MixerCli::parse().run()
}
#[derive(Debug, Parser)] #[command(version, about, long_about = None)] pub struct MixerCli {
/// Name of JACK client
#[arg(short, long)] name: Option<String>,
/// Number of tracks
#[arg(short, long)] channels: Option<usize>,
}
impl MixerCli {
fn run (&self) -> Usually<()> {
Tui::run(JackClient::new("tek_mixer")?.activate_with(|jack|{
let mut mixer = Mixer::new(jack, self.name.as_ref().map(|x|x.as_str()).unwrap_or("mixer"))?;
for channel in 0..self.channels.unwrap_or(8) {
mixer.track_add(&format!("Track {}", channel + 1), 1)?;
}
Ok(mixer)
})?)?;
Ok(())
}
}

View file

@ -1,26 +0,0 @@
include!("./lib.rs");
pub fn main () -> Usually<()> {
PluginCli::parse().run()
}
#[derive(Debug, Parser)] #[command(version, about, long_about = None)] pub struct PluginCli {
/// Name of JACK client
#[arg(short, long)] name: Option<String>,
/// Path to plugin
#[arg(short, long)] path: Option<String>,
}
impl PluginCli {
fn run (&self) -> Usually<()> {
Tui::run(JackClient::new("tek_plugin")?.activate_with(|jack|{
let mut plugin = Plugin::new_lv2(
jack,
self.name.as_ref().map(|x|x.as_str()).unwrap_or("mixer"),
self.path.as_ref().expect("pass --path /to/lv2/plugin.so")
)?;
Ok(plugin)
})?)?;
Ok(())
}
}

View file

@ -1,26 +0,0 @@
include!("./lib.rs");
pub fn main () -> Usually<()> {
SamplerCli::parse().run()
}
#[derive(Debug, Parser)] #[command(version, about, long_about = None)] pub struct SamplerCli {
/// Name of JACK client
#[arg(short, long)] name: Option<String>,
/// Path to plugin
#[arg(short, long)] path: Option<String>,
}
impl SamplerCli {
fn run (&self) -> Usually<()> {
Tui::run(JackClient::new("tek_sampler")?.activate_with(|jack|{
let mut plugin = Sampler::new(
jack,
self.name.as_ref().map(|x|x.as_str()).unwrap_or("mixer"),
None,
)?;
Ok(plugin)
})?)?;
Ok(())
}
}

View file

@ -1,8 +0,0 @@
[package]
name = "tek_edn"
edition = "2021"
version = "0.2.0"
[dependencies]
tek = { path = "../tek" }
clojure-reader = "0.1.0"

View file

@ -1,202 +0,0 @@
#[allow(unused_imports)] use tek::{*, jack::*, plugin::*};
use std::sync::{Arc, RwLock};
use std::collections::BTreeMap;
pub use clojure_reader::edn::Edn;
//pub use clojure_reader::{edn::{read, Edn}, error::Error as EdnError};
/// EDN parsing helper.
#[macro_export] macro_rules! edn {
($edn:ident { $($pat:pat => $expr:expr),* $(,)? }) => {
match $edn { $($pat => $expr),* }
};
($edn:ident in $args:ident { $($pat:pat => $expr:expr),* $(,)? }) => {
for $edn in $args {
edn!($edn { $($pat => $expr),* })
}
};
}
pub trait FromEdn<C>: Sized {
const ID: &'static str;
fn from_edn (context: C, expr: &[Edn<'_>]) -> Usually<Self>;
}
/// Implements the [FromEdn] trait.
#[macro_export] macro_rules! from_edn {
($id:expr => |$context:tt:$Context:ty, $args:ident| -> $T:ty $body:block) => {
impl FromEdn<$Context> for $T {
const ID: &'static str = $id;
fn from_edn <'e> ($context: $Context, $args: &[Edn<'e>]) -> Usually<Self> {
$body
}
}
}
}
from_edn!("sampler" => |jack: &Arc<RwLock<JackClient>>, args| -> crate::Sampler {
let mut name = String::new();
let mut dir = String::new();
let mut samples = BTreeMap::new();
edn!(edn in args {
Edn::Map(map) => {
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
name = String::from(*n);
}
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":dir")) {
dir = String::from(*n);
}
},
Edn::List(args) => match args.first() {
Some(Edn::Symbol("sample")) => {
let (midi, sample) = MidiSample::from_edn((jack, &dir), &args[1..])?;
if let Some(midi) = midi {
samples.insert(midi, sample);
} else {
panic!("sample without midi binding: {}", sample.read().unwrap().name);
}
},
_ => panic!("unexpected in sampler {name}: {args:?}")
},
_ => panic!("unexpected in sampler {name}: {edn:?}")
});
Self::new(jack, &name)
});
type MidiSample = (Option<u7>, Arc<RwLock<crate::Sample>>);
from_edn!("sample" => |(_jack, dir): (&Arc<RwLock<JackClient>>, &str), args| -> MidiSample {
let mut name = String::new();
let mut file = String::new();
let mut midi = None;
let mut start = 0usize;
edn!(edn in args {
Edn::Map(map) => {
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
name = String::from(*n);
}
if let Some(Edn::Str(f)) = map.get(&Edn::Key(":file")) {
file = String::from(*f);
}
if let Some(Edn::Int(i)) = map.get(&Edn::Key(":start")) {
start = *i as usize;
}
if let Some(Edn::Int(m)) = map.get(&Edn::Key(":midi")) {
midi = Some(u7::from(*m as u8));
}
},
_ => panic!("unexpected in sample {name}"),
});
let (end, data) = Sample::read_data(&format!("{dir}/{file}"))?;
Ok((midi, Arc::new(RwLock::new(crate::Sample {
name,
start,
end,
channels: data,
rate: None,
gain: 1.0
}))))
});
from_edn!("plugin/lv2" => |jack: &Arc<RwLock<JackClient>>, args| -> Plugin {
let mut name = String::new();
let mut path = String::new();
edn!(edn in args {
Edn::Map(map) => {
if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) {
name = String::from(*n);
}
if let Some(Edn::Str(p)) = map.get(&Edn::Key(":path")) {
path = String::from(*p);
}
},
_ => panic!("unexpected in lv2 '{name}'"),
});
Plugin::new_lv2(jack, &name, &path)
});
const SYM_NAME: &str = ":name";
const SYM_GAIN: &str = ":gain";
const SYM_SAMPLER: &str = "sampler";
const SYM_LV2: &str = "lv2";
from_edn!("mixer/track" => |jack: &Arc<RwLock<JackClient>>, args| -> MixerTrack {
let mut _gain = 0.0f64;
let mut track = MixerTrack {
name: String::new(),
audio_ins: vec![],
audio_outs: vec![],
devices: vec![],
};
edn!(edn in args {
Edn::Map(map) => {
if let Some(Edn::Str(n)) = map.get(&Edn::Key(SYM_NAME)) {
track.name = n.to_string();
}
if let Some(Edn::Double(g)) = map.get(&Edn::Key(SYM_GAIN)) {
_gain = f64::from(*g);
}
},
Edn::List(args) => match args.first() {
// Add a sampler device to the track
Some(Edn::Symbol(SYM_SAMPLER)) => {
track.devices.push(
Box::new(Sampler::from_edn(jack, &args[1..])?) as Box<dyn MixerTrackDevice>
);
panic!(
"unsupported in track {}: {:?}; tek_mixer not compiled with feature \"sampler\"",
&track.name,
args.first().unwrap()
)
},
// Add a LV2 plugin to the track.
Some(Edn::Symbol(SYM_LV2)) => {
track.devices.push(
Box::new(Plugin::from_edn(jack, &args[1..])?) as Box<dyn MixerTrackDevice>
);
panic!(
"unsupported in track {}: {:?}; tek_mixer not compiled with feature \"plugin\"",
&track.name,
args.first().unwrap()
)
},
None =>
panic!("empty list track {}", &track.name),
_ =>
panic!("unexpected in track {}: {:?}", &track.name, args.first().unwrap())
},
_ => {}
});
Ok(track)
});
//impl ArrangerScene {
////TODO
////pub fn from_edn <'a, 'e> (args: &[Edn<'e>]) -> Usually<Self> {
////let mut name = None;
////let mut clips = vec![];
////edn!(edn in args {
////Edn::Map(map) => {
////let key = map.get(&Edn::Key(":name"));
////if let Some(Edn::Str(n)) = key {
////name = Some(*n);
////} else {
////panic!("unexpected key in scene '{name:?}': {key:?}")
////}
////},
////Edn::Symbol("_") => {
////clips.push(None);
////},
////Edn::Int(i) => {
////clips.push(Some(*i as usize));
////},
////_ => panic!("unexpected in scene '{name:?}': {edn:?}")
////});
////Ok(ArrangerScene {
////name: Arc::new(name.unwrap_or("").to_string().into()),
////color: ItemColor::random(),
////clips,
////})
////}
//}

View file

@ -1,15 +0,0 @@
[package]
name = "tek_suil"
version = "0.1.0"
edition = "2021"
[dependencies]
gtk = "0.18.1"
livi = "0.7.4"
#winit = { version = "0.30.4", features = [ "x11" ] }
[build-dependencies]
bindgen = "0.69.4"
[dev-dependencies]
winit = { version = "0.30.4", features = [ "x11" ] }

View file

@ -1,18 +0,0 @@
use std::path::PathBuf;
use std::env::var;
use bindgen::{Builder, CargoCallbacks};
fn main() {
//println!("cargo:rustc-link-lib=lv2");
println!("cargo:rustc-link-lib=suil-0");
let bindings = Builder::default()
.header("wrapper.h")
//.clang_arg("-Ilv2/include/lv2")
.clang_arg("-Isuil/include/suil")
.parse_callbacks(Box::new(CargoCallbacks::new()))
.generate()
.expect("Unable to generate bindings");
let out_path = PathBuf::from(var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");
}

View file

@ -1,5 +0,0 @@
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

View file

@ -1,37 +0,0 @@
struct SuilX11Wrapper {
socket: GtkSocket,
plug: GtkPlug,
wrapper: SuilWrapper,
instance: SuilInstance,
idle_iface: LV2UI_Idle_Interface,
idle_id: usize,
idle_ms: usize,
idle_size_req_id: usize,
initial_width: usize,
initial_height: usize,
req_width: usize,
req_height: usize,
}
struct SuilX11WrapperClass {
parent_class: GtkSocketClass,
}
impl SuilX11Wrapper {
fn x_window_is_valid (&self) {
let window: GdkWindow = gtk_widget_get_window(self.plug);
let root: X11Window = Some(0);
let parent: X11Window = Some(0);
let children: [X11Window] = None;
let child_count: usize = 0;
x_query_tree(window.xdisplay, window.xid, root, parent, children, &mut childcount);
for i in 0..child_count {
if children[i] == self.instance.ui_widget {
x_free(children);
return true;
}
}
x_free(children);
return false;
}
}

View file

@ -1,113 +0,0 @@
use std::ffi::{CString, c_void};
pub mod bound;
#[cfg(test)] mod test;
pub struct Host(*mut self::bound::SuilHost);
impl Drop for Host {
fn drop (&mut self) {
unsafe {
bound::suil_host_free(self.0)
}
}
}
impl Host {
pub fn new () -> Self {
Self(unsafe {
let mut argv = [];
bound::suil_init(
&mut 0,
&mut argv as *mut *mut *mut i8,
0
);
bound::suil_host_new(
Some(write),
Some(index),
None,
None,
)
})
}
fn set_touch_func (&self) {
unimplemented!();
}
fn instance <T> (
&self,
controller: &mut T,
container_type_uri: &str,
plugin_uri: &str,
ui_uri: &str,
ui_type_uri: &str,
ui_bundle_path: &str,
ui_binary_path: &str,
features: &[*const bound::LV2_Feature],
) -> Result<Instance, Box<dyn std::error::Error>> {
let container_type_uri = CString::new(container_type_uri)?;
let plugin_uri = CString::new(plugin_uri)?;
let ui_uri = CString::new(ui_uri)?;
let ui_type_uri = CString::new(ui_type_uri)?;
let ui_bundle_path = CString::new(ui_bundle_path)?;
let ui_binary_path = CString::new(ui_binary_path)?;
Ok(Instance(unsafe {
bound::suil_instance_new(
self.0,
controller as *mut _ as *mut std::ffi::c_void,
container_type_uri.into_raw(),
plugin_uri.into_raw(),
ui_uri.into_raw(),
ui_type_uri.into_raw(),
ui_bundle_path.into_raw(),
ui_binary_path.into_raw(),
std::ptr::null_mut(),
)
}))
}
}
unsafe extern "C" fn write (
_controller: *mut c_void, _: u32, _: u32, _: u32, _: *const c_void
) {
panic!("write")
}
unsafe extern "C" fn index (
_controller: *mut c_void, _: *const i8
) -> u32 {
0
}
//unsafe extern "C" fn subscribe (
//_: *mut c_void, _: u32, _: u32, _: *const *const bound::LV2_Feature
//) -> u32 {
//panic!("subscribe");
//0
//}
//unsafe extern "C" fn unsubscribe (
//_: *mut c_void, _: u32, _: u32, _: *const *const bound::LV2_Feature
//) -> u32 {
//panic!("unsubscribe");
//0
//}
pub struct Instance(*mut self::bound::SuilInstance);
impl Drop for Instance {
fn drop (&mut self) -> () {
unsafe {
bound::suil_instance_free(self.0)
}
}
}
unsafe impl Send for Instance {}
unsafe impl Sync for Instance {}
impl Instance {
fn get_widget (&self) -> *mut c_void {
unsafe { bound::suil_instance_get_widget(self.0) }
}
}

View file

@ -1,173 +0,0 @@
use crate::*;
use std::sync::Arc;
#[test]
fn test_lv2_ui () {
let mut features = [];
std::mem::forget(features);
//gtk::init().unwrap();
use gtk::prelude::{ApplicationExt, ApplicationExtManual};
let app = gtk::Application::builder().application_id("lol.tek").build();
app.connect_activate(move |app| {
let host = Host::new();
let mut plugin = plugin::LV2Plugin::new("file:///home/user/.lv2/Odin2.lv2").unwrap();
//let mut plugin = plugin::LV2Plugin::new("file:///home/user/.lv2/ChowKick.lv2").unwrap();
for ui in plugin.plugin.raw().uis().unwrap().iter() {
println!("{:?}", ui.uri());
println!("{:?}", ui.classes());
}
let handle = plugin.instance.raw_mut().instance_mut().handle();
let instance_access = crate::bound::LV2_Feature {
URI: std::ffi::CString::new("http://lv2plug.in/ns/ext/instance-access").unwrap().into_raw(),
data: handle as *mut _ as *mut std::ffi::c_void
};
let ui_instance = Arc::new(host.instance(
&mut plugin,
"http://lv2plug.in/ns/extensions/ui#Gtk3UI",
"https://thewavewarden.com/odin2",
"https://thewavewarden.com/odin2#ParentUI",
"http://lv2plug.in/ns/extensions/ui#X11UI",
"/home/user/.lv2/Odin2.lv2/Odin2.so",
"/home/user/.lv2/Odin2.lv2/Odin2.so",
&features,
).unwrap());
//let instance = Arc::new(host.instance(
//&mut plugin,
//"http://lv2plug.in/ns/extensions/ui#Gtk3UI",
//"http://tytel.org/helm",
//"http://tytel.org/helm#ParentUI",
//"http://lv2plug.in/ns/extensions/ui#X11UI",
//"/home/user/.lv2/Helm.lv2/helm.so",
//"/home/user/.lv2/Helm.lv2/helm.so",
//&features,
//).unwrap());
//let instance = Arc::new(host.instance(
//&mut plugin,
//"http://lv2plug.in/ns/extensions/ui#Gtk3UI",
//"http://github.com/Chowdhury-DSP/ChowKick",
//"http://github.com/Chowdhury-DSP/ChowKick:UI",
//"http://lv2plug.in/ns/extensions/ui#X11UI",
//"/home/user/.lv2/ChowKick.lv2/libChowKick.so",
//"/home/user/.lv2/ChowKick.lv2/libChowKick.so",
//&features,
//).unwrap());
//println!("{:?}", instance.get_widget());
});
app.run();
//self::ui::UI::run(&instance).join();
}
mod ui {
use std::sync::Arc;
use std::thread::{spawn, JoinHandle};
use ::winit::{
application::ApplicationHandler,
event::WindowEvent,
event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
window::{Window, WindowId},
platform::x11::EventLoopBuilderExtX11
};
use crate::{Host, Instance};
pub struct UI {
host: Arc<Instance>,
window: Option<Window>
}
impl UI {
pub fn new (host: &Arc<Instance>) -> Self {
Self {
host: host.clone(),
window: None
}
}
pub fn run (host: &Arc<Instance>) -> JoinHandle<()> {
let mut ui = Self::new(host);
spawn(move||{
let event_loop = EventLoop::builder().with_x11().with_any_thread(true).build().unwrap();
event_loop.set_control_flow(ControlFlow::Wait);
event_loop.run_app(&mut ui).unwrap()
})
}
}
impl ApplicationHandler for UI {
fn resumed (&mut self, event_loop: &ActiveEventLoop) {
self.window = Some(event_loop.create_window(Window::default_attributes()).unwrap());
}
fn window_event (&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) {
match event {
WindowEvent::CloseRequested => {
self.window.as_ref().unwrap().set_visible(false);
event_loop.exit();
},
WindowEvent::RedrawRequested => {
self.window.as_ref().unwrap().request_redraw();
}
_ => (),
}
}
}
}
mod plugin {
use std::thread::JoinHandle;
use std::sync::Arc;
use ::livi::{
World,
Instance,
Plugin as LiviPlugin,
Features,
FeaturesBuilder,
Port,
event::LV2AtomSequence,
};
/// A LV2 plugin.
pub struct LV2Plugin {
pub world: World,
pub instance: Instance,
pub plugin: LiviPlugin,
pub features: Arc<Features>,
pub port_list: Vec<Port>,
pub input_buffer: Vec<LV2AtomSequence>,
pub ui_thread: Option<JoinHandle<()>>,
}
impl LV2Plugin {
pub fn new (uri: &str) -> Result<Self, Box<dyn std::error::Error>> {
// Get 1st plugin at URI
let world = World::with_load_bundle(&uri);
let features = FeaturesBuilder { min_block_length: 1, max_block_length: 65536 };
let features = world.build_features(features);
let mut plugin = None;
#[allow(clippy::never_loop)]
for p in world.iter_plugins() {
plugin = Some(p);
break
}
if plugin.is_none() {
return Err(format!("plugin not found: {uri}").into())
}
let plugin = plugin.unwrap();
let err = &format!("init {uri}");
// Instantiate
Ok(Self {
world,
instance: unsafe {
plugin
.instantiate(features.clone(), 48000.0)
.expect(&err)
},
port_list: {
let mut port_list = vec![];
for port in plugin.ports() {
port_list.push(port);
}
port_list
},
plugin,
features,
input_buffer: Vec::with_capacity(1024),
ui_thread: None
})
}
}
}

View file

@ -1,39 +0,0 @@
/*===---- stdbool.h - Standard header for booleans -------------------------===
*
* Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
* See https://llvm.org/LICENSE.txt for license information.
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
*
*===-----------------------------------------------------------------------===
*/
#ifndef __STDBOOL_H
#define __STDBOOL_H
#define __bool_true_false_are_defined 1
#if defined(__MVS__) && __has_include_next(<stdbool.h>)
#include_next <stdbool.h>
#else
#if defined(__STDC_VERSION__) && __STDC_VERSION__ > 201710L
/* FIXME: We should be issuing a deprecation warning here, but cannot yet due
* to system headers which include this header file unconditionally.
*/
#elif !defined(__cplusplus)
#define bool _Bool
#define true 1
#define false 0
#elif defined(__GNUC__) && !defined(__STRICT_ANSI__)
/* Define _Bool as a GNU extension. */
#define _Bool bool
#if defined(__cplusplus) && __cplusplus < 201103L
/* For C++98, define bool, false, true as a GNU extension. */
#define bool bool
#define false false
#define true true
#endif
#endif
#endif /* __MVS__ */
#endif /* __STDBOOL_H */

View file

@ -1,955 +0,0 @@
/*===---- stdint.h - Standard header for sized integer types --------------===*\
*
* Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
* See https://llvm.org/LICENSE.txt for license information.
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
*
\*===----------------------------------------------------------------------===*/
#ifndef __CLANG_STDINT_H
// AIX system headers need stdint.h to be re-enterable while _STD_TYPES_T
// is defined until an inclusion of it without _STD_TYPES_T occurs, in which
// case the header guard macro is defined.
#if !defined(_AIX) || !defined(_STD_TYPES_T) || !defined(__STDC_HOSTED__)
#define __CLANG_STDINT_H
#endif
#if defined(__MVS__) && __has_include_next(<stdint.h>)
#include_next <stdint.h>
#else
/* If we're hosted, fall back to the system's stdint.h, which might have
* additional definitions.
*/
#if __STDC_HOSTED__ && __has_include_next(<stdint.h>)
// C99 7.18.3 Limits of other integer types
//
// Footnote 219, 220: C++ implementations should define these macros only when
// __STDC_LIMIT_MACROS is defined before <stdint.h> is included.
//
// Footnote 222: C++ implementations should define these macros only when
// __STDC_CONSTANT_MACROS is defined before <stdint.h> is included.
//
// C++11 [cstdint.syn]p2:
//
// The macros defined by <cstdint> are provided unconditionally. In particular,
// the symbols __STDC_LIMIT_MACROS and __STDC_CONSTANT_MACROS (mentioned in
// footnotes 219, 220, and 222 in the C standard) play no role in C++.
//
// C11 removed the problematic footnotes.
//
// Work around this inconsistency by always defining those macros in C++ mode,
// so that a C library implementation which follows the C99 standard can be
// used in C++.
# ifdef __cplusplus
# if !defined(__STDC_LIMIT_MACROS)
# define __STDC_LIMIT_MACROS
# define __STDC_LIMIT_MACROS_DEFINED_BY_CLANG
# endif
# if !defined(__STDC_CONSTANT_MACROS)
# define __STDC_CONSTANT_MACROS
# define __STDC_CONSTANT_MACROS_DEFINED_BY_CLANG
# endif
# endif
# include_next <stdint.h>
# ifdef __STDC_LIMIT_MACROS_DEFINED_BY_CLANG
# undef __STDC_LIMIT_MACROS
# undef __STDC_LIMIT_MACROS_DEFINED_BY_CLANG
# endif
# ifdef __STDC_CONSTANT_MACROS_DEFINED_BY_CLANG
# undef __STDC_CONSTANT_MACROS
# undef __STDC_CONSTANT_MACROS_DEFINED_BY_CLANG
# endif
#else
/* C99 7.18.1.1 Exact-width integer types.
* C99 7.18.1.2 Minimum-width integer types.
* C99 7.18.1.3 Fastest minimum-width integer types.
*
* The standard requires that exact-width type be defined for 8-, 16-, 32-, and
* 64-bit types if they are implemented. Other exact width types are optional.
* This implementation defines an exact-width types for every integer width
* that is represented in the standard integer types.
*
* The standard also requires minimum-width types be defined for 8-, 16-, 32-,
* and 64-bit widths regardless of whether there are corresponding exact-width
* types.
*
* To accommodate targets that are missing types that are exactly 8, 16, 32, or
* 64 bits wide, this implementation takes an approach of cascading
* redefinitions, redefining __int_leastN_t to successively smaller exact-width
* types. It is therefore important that the types are defined in order of
* descending widths.
*
* We currently assume that the minimum-width types and the fastest
* minimum-width types are the same. This is allowed by the standard, but is
* suboptimal.
*
* In violation of the standard, some targets do not implement a type that is
* wide enough to represent all of the required widths (8-, 16-, 32-, 64-bit).
* To accommodate these targets, a required minimum-width type is only
* defined if there exists an exact-width type of equal or greater width.
*/
#ifdef __INT64_TYPE__
# ifndef __int8_t_defined /* glibc sys/types.h also defines int64_t*/
typedef __INT64_TYPE__ int64_t;
# endif /* __int8_t_defined */
typedef __UINT64_TYPE__ uint64_t;
# undef __int_least64_t
# define __int_least64_t int64_t
# undef __uint_least64_t
# define __uint_least64_t uint64_t
# undef __int_least32_t
# define __int_least32_t int64_t
# undef __uint_least32_t
# define __uint_least32_t uint64_t
# undef __int_least16_t
# define __int_least16_t int64_t
# undef __uint_least16_t
# define __uint_least16_t uint64_t
# undef __int_least8_t
# define __int_least8_t int64_t
# undef __uint_least8_t
# define __uint_least8_t uint64_t
#endif /* __INT64_TYPE__ */
#ifdef __int_least64_t
typedef __int_least64_t int_least64_t;
typedef __uint_least64_t uint_least64_t;
typedef __int_least64_t int_fast64_t;
typedef __uint_least64_t uint_fast64_t;
#endif /* __int_least64_t */
#ifdef __INT56_TYPE__
typedef __INT56_TYPE__ int56_t;
typedef __UINT56_TYPE__ uint56_t;
typedef int56_t int_least56_t;
typedef uint56_t uint_least56_t;
typedef int56_t int_fast56_t;
typedef uint56_t uint_fast56_t;
# undef __int_least32_t
# define __int_least32_t int56_t
# undef __uint_least32_t
# define __uint_least32_t uint56_t
# undef __int_least16_t
# define __int_least16_t int56_t
# undef __uint_least16_t
# define __uint_least16_t uint56_t
# undef __int_least8_t
# define __int_least8_t int56_t
# undef __uint_least8_t
# define __uint_least8_t uint56_t
#endif /* __INT56_TYPE__ */
#ifdef __INT48_TYPE__
typedef __INT48_TYPE__ int48_t;
typedef __UINT48_TYPE__ uint48_t;
typedef int48_t int_least48_t;
typedef uint48_t uint_least48_t;
typedef int48_t int_fast48_t;
typedef uint48_t uint_fast48_t;
# undef __int_least32_t
# define __int_least32_t int48_t
# undef __uint_least32_t
# define __uint_least32_t uint48_t
# undef __int_least16_t
# define __int_least16_t int48_t
# undef __uint_least16_t
# define __uint_least16_t uint48_t
# undef __int_least8_t
# define __int_least8_t int48_t
# undef __uint_least8_t
# define __uint_least8_t uint48_t
#endif /* __INT48_TYPE__ */
#ifdef __INT40_TYPE__
typedef __INT40_TYPE__ int40_t;
typedef __UINT40_TYPE__ uint40_t;
typedef int40_t int_least40_t;
typedef uint40_t uint_least40_t;
typedef int40_t int_fast40_t;
typedef uint40_t uint_fast40_t;
# undef __int_least32_t
# define __int_least32_t int40_t
# undef __uint_least32_t
# define __uint_least32_t uint40_t
# undef __int_least16_t
# define __int_least16_t int40_t
# undef __uint_least16_t
# define __uint_least16_t uint40_t
# undef __int_least8_t
# define __int_least8_t int40_t
# undef __uint_least8_t
# define __uint_least8_t uint40_t
#endif /* __INT40_TYPE__ */
#ifdef __INT32_TYPE__
# ifndef __int8_t_defined /* glibc sys/types.h also defines int32_t*/
typedef __INT32_TYPE__ int32_t;
# endif /* __int8_t_defined */
# ifndef __uint32_t_defined /* more glibc compatibility */
# define __uint32_t_defined
typedef __UINT32_TYPE__ uint32_t;
# endif /* __uint32_t_defined */
# undef __int_least32_t
# define __int_least32_t int32_t
# undef __uint_least32_t
# define __uint_least32_t uint32_t
# undef __int_least16_t
# define __int_least16_t int32_t
# undef __uint_least16_t
# define __uint_least16_t uint32_t
# undef __int_least8_t
# define __int_least8_t int32_t
# undef __uint_least8_t
# define __uint_least8_t uint32_t
#endif /* __INT32_TYPE__ */
#ifdef __int_least32_t
typedef __int_least32_t int_least32_t;
typedef __uint_least32_t uint_least32_t;
typedef __int_least32_t int_fast32_t;
typedef __uint_least32_t uint_fast32_t;
#endif /* __int_least32_t */
#ifdef __INT24_TYPE__
typedef __INT24_TYPE__ int24_t;
typedef __UINT24_TYPE__ uint24_t;
typedef int24_t int_least24_t;
typedef uint24_t uint_least24_t;
typedef int24_t int_fast24_t;
typedef uint24_t uint_fast24_t;
# undef __int_least16_t
# define __int_least16_t int24_t
# undef __uint_least16_t
# define __uint_least16_t uint24_t
# undef __int_least8_t
# define __int_least8_t int24_t
# undef __uint_least8_t
# define __uint_least8_t uint24_t
#endif /* __INT24_TYPE__ */
#ifdef __INT16_TYPE__
#ifndef __int8_t_defined /* glibc sys/types.h also defines int16_t*/
typedef __INT16_TYPE__ int16_t;
#endif /* __int8_t_defined */
typedef __UINT16_TYPE__ uint16_t;
# undef __int_least16_t
# define __int_least16_t int16_t
# undef __uint_least16_t
# define __uint_least16_t uint16_t
# undef __int_least8_t
# define __int_least8_t int16_t
# undef __uint_least8_t
# define __uint_least8_t uint16_t
#endif /* __INT16_TYPE__ */
#ifdef __int_least16_t
typedef __int_least16_t int_least16_t;
typedef __uint_least16_t uint_least16_t;
typedef __int_least16_t int_fast16_t;
typedef __uint_least16_t uint_fast16_t;
#endif /* __int_least16_t */
#ifdef __INT8_TYPE__
#ifndef __int8_t_defined /* glibc sys/types.h also defines int8_t*/
typedef __INT8_TYPE__ int8_t;
#endif /* __int8_t_defined */
typedef __UINT8_TYPE__ uint8_t;
# undef __int_least8_t
# define __int_least8_t int8_t
# undef __uint_least8_t
# define __uint_least8_t uint8_t
#endif /* __INT8_TYPE__ */
#ifdef __int_least8_t
typedef __int_least8_t int_least8_t;
typedef __uint_least8_t uint_least8_t;
typedef __int_least8_t int_fast8_t;
typedef __uint_least8_t uint_fast8_t;
#endif /* __int_least8_t */
/* prevent glibc sys/types.h from defining conflicting types */
#ifndef __int8_t_defined
# define __int8_t_defined
#endif /* __int8_t_defined */
/* C99 7.18.1.4 Integer types capable of holding object pointers.
*/
#define __stdint_join3(a,b,c) a ## b ## c
#ifndef _INTPTR_T
#ifndef __intptr_t_defined
typedef __INTPTR_TYPE__ intptr_t;
#define __intptr_t_defined
#define _INTPTR_T
#endif
#endif
#ifndef _UINTPTR_T
typedef __UINTPTR_TYPE__ uintptr_t;
#define _UINTPTR_T
#endif
/* C99 7.18.1.5 Greatest-width integer types.
*/
typedef __INTMAX_TYPE__ intmax_t;
typedef __UINTMAX_TYPE__ uintmax_t;
/* C99 7.18.4 Macros for minimum-width integer constants.
*
* The standard requires that integer constant macros be defined for all the
* minimum-width types defined above. As 8-, 16-, 32-, and 64-bit minimum-width
* types are required, the corresponding integer constant macros are defined
* here. This implementation also defines minimum-width types for every other
* integer width that the target implements, so corresponding macros are
* defined below, too.
*
* These macros are defined using the same successive-shrinking approach as
* the type definitions above. It is likewise important that macros are defined
* in order of decending width.
*
* Note that C++ should not check __STDC_CONSTANT_MACROS here, contrary to the
* claims of the C standard (see C++ 18.3.1p2, [cstdint.syn]).
*/
#define __int_c_join(a, b) a ## b
#define __int_c(v, suffix) __int_c_join(v, suffix)
#define __uint_c(v, suffix) __int_c_join(v##U, suffix)
#ifdef __INT64_TYPE__
# undef __int64_c_suffix
# undef __int32_c_suffix
# undef __int16_c_suffix
# undef __int8_c_suffix
# ifdef __INT64_C_SUFFIX__
# define __int64_c_suffix __INT64_C_SUFFIX__
# define __int32_c_suffix __INT64_C_SUFFIX__
# define __int16_c_suffix __INT64_C_SUFFIX__
# define __int8_c_suffix __INT64_C_SUFFIX__
# endif /* __INT64_C_SUFFIX__ */
#endif /* __INT64_TYPE__ */
#ifdef __int_least64_t
# ifdef __int64_c_suffix
# define INT64_C(v) __int_c(v, __int64_c_suffix)
# define UINT64_C(v) __uint_c(v, __int64_c_suffix)
# else
# define INT64_C(v) v
# define UINT64_C(v) v ## U
# endif /* __int64_c_suffix */
#endif /* __int_least64_t */
#ifdef __INT56_TYPE__
# undef __int32_c_suffix
# undef __int16_c_suffix
# undef __int8_c_suffix
# ifdef __INT56_C_SUFFIX__
# define INT56_C(v) __int_c(v, __INT56_C_SUFFIX__)
# define UINT56_C(v) __uint_c(v, __INT56_C_SUFFIX__)
# define __int32_c_suffix __INT56_C_SUFFIX__
# define __int16_c_suffix __INT56_C_SUFFIX__
# define __int8_c_suffix __INT56_C_SUFFIX__
# else
# define INT56_C(v) v
# define UINT56_C(v) v ## U
# endif /* __INT56_C_SUFFIX__ */
#endif /* __INT56_TYPE__ */
#ifdef __INT48_TYPE__
# undef __int32_c_suffix
# undef __int16_c_suffix
# undef __int8_c_suffix
# ifdef __INT48_C_SUFFIX__
# define INT48_C(v) __int_c(v, __INT48_C_SUFFIX__)
# define UINT48_C(v) __uint_c(v, __INT48_C_SUFFIX__)
# define __int32_c_suffix __INT48_C_SUFFIX__
# define __int16_c_suffix __INT48_C_SUFFIX__
# define __int8_c_suffix __INT48_C_SUFFIX__
# else
# define INT48_C(v) v
# define UINT48_C(v) v ## U
# endif /* __INT48_C_SUFFIX__ */
#endif /* __INT48_TYPE__ */
#ifdef __INT40_TYPE__
# undef __int32_c_suffix
# undef __int16_c_suffix
# undef __int8_c_suffix
# ifdef __INT40_C_SUFFIX__
# define INT40_C(v) __int_c(v, __INT40_C_SUFFIX__)
# define UINT40_C(v) __uint_c(v, __INT40_C_SUFFIX__)
# define __int32_c_suffix __INT40_C_SUFFIX__
# define __int16_c_suffix __INT40_C_SUFFIX__
# define __int8_c_suffix __INT40_C_SUFFIX__
# else
# define INT40_C(v) v
# define UINT40_C(v) v ## U
# endif /* __INT40_C_SUFFIX__ */
#endif /* __INT40_TYPE__ */
#ifdef __INT32_TYPE__
# undef __int32_c_suffix
# undef __int16_c_suffix
# undef __int8_c_suffix
# ifdef __INT32_C_SUFFIX__
# define __int32_c_suffix __INT32_C_SUFFIX__
# define __int16_c_suffix __INT32_C_SUFFIX__
# define __int8_c_suffix __INT32_C_SUFFIX__
# endif /* __INT32_C_SUFFIX__ */
#endif /* __INT32_TYPE__ */
#ifdef __int_least32_t
# ifdef __int32_c_suffix
# define INT32_C(v) __int_c(v, __int32_c_suffix)
# define UINT32_C(v) __uint_c(v, __int32_c_suffix)
# else
# define INT32_C(v) v
# define UINT32_C(v) v ## U
# endif /* __int32_c_suffix */
#endif /* __int_least32_t */
#ifdef __INT24_TYPE__
# undef __int16_c_suffix
# undef __int8_c_suffix
# ifdef __INT24_C_SUFFIX__
# define INT24_C(v) __int_c(v, __INT24_C_SUFFIX__)
# define UINT24_C(v) __uint_c(v, __INT24_C_SUFFIX__)
# define __int16_c_suffix __INT24_C_SUFFIX__
# define __int8_c_suffix __INT24_C_SUFFIX__
# else
# define INT24_C(v) v
# define UINT24_C(v) v ## U
# endif /* __INT24_C_SUFFIX__ */
#endif /* __INT24_TYPE__ */
#ifdef __INT16_TYPE__
# undef __int16_c_suffix
# undef __int8_c_suffix
# ifdef __INT16_C_SUFFIX__
# define __int16_c_suffix __INT16_C_SUFFIX__
# define __int8_c_suffix __INT16_C_SUFFIX__
# endif /* __INT16_C_SUFFIX__ */
#endif /* __INT16_TYPE__ */
#ifdef __int_least16_t
# ifdef __int16_c_suffix
# define INT16_C(v) __int_c(v, __int16_c_suffix)
# define UINT16_C(v) __uint_c(v, __int16_c_suffix)
# else
# define INT16_C(v) v
# define UINT16_C(v) v ## U
# endif /* __int16_c_suffix */
#endif /* __int_least16_t */
#ifdef __INT8_TYPE__
# undef __int8_c_suffix
# ifdef __INT8_C_SUFFIX__
# define __int8_c_suffix __INT8_C_SUFFIX__
# endif /* __INT8_C_SUFFIX__ */
#endif /* __INT8_TYPE__ */
#ifdef __int_least8_t
# ifdef __int8_c_suffix
# define INT8_C(v) __int_c(v, __int8_c_suffix)
# define UINT8_C(v) __uint_c(v, __int8_c_suffix)
# else
# define INT8_C(v) v
# define UINT8_C(v) v ## U
# endif /* __int8_c_suffix */
#endif /* __int_least8_t */
/* C99 7.18.2.1 Limits of exact-width integer types.
* C99 7.18.2.2 Limits of minimum-width integer types.
* C99 7.18.2.3 Limits of fastest minimum-width integer types.
*
* The presence of limit macros are completely optional in C99. This
* implementation defines limits for all of the types (exact- and
* minimum-width) that it defines above, using the limits of the minimum-width
* type for any types that do not have exact-width representations.
*
* As in the type definitions, this section takes an approach of
* successive-shrinking to determine which limits to use for the standard (8,
* 16, 32, 64) bit widths when they don't have exact representations. It is
* therefore important that the definitions be kept in order of decending
* widths.
*
* Note that C++ should not check __STDC_LIMIT_MACROS here, contrary to the
* claims of the C standard (see C++ 18.3.1p2, [cstdint.syn]).
*/
#ifdef __INT64_TYPE__
# define INT64_MAX INT64_C( 9223372036854775807)
# define INT64_MIN (-INT64_C( 9223372036854775807)-1)
# define UINT64_MAX UINT64_C(18446744073709551615)
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
# define UINT64_WIDTH 64
# define INT64_WIDTH UINT64_WIDTH
# define __UINT_LEAST64_WIDTH UINT64_WIDTH
# undef __UINT_LEAST32_WIDTH
# define __UINT_LEAST32_WIDTH UINT64_WIDTH
# undef __UINT_LEAST16_WIDTH
# define __UINT_LEAST16_WIDTH UINT64_WIDTH
# undef __UINT_LEAST8_MAX
# define __UINT_LEAST8_MAX UINT64_MAX
#endif /* __STDC_VERSION__ */
# define __INT_LEAST64_MIN INT64_MIN
# define __INT_LEAST64_MAX INT64_MAX
# define __UINT_LEAST64_MAX UINT64_MAX
# undef __INT_LEAST32_MIN
# define __INT_LEAST32_MIN INT64_MIN
# undef __INT_LEAST32_MAX
# define __INT_LEAST32_MAX INT64_MAX
# undef __UINT_LEAST32_MAX
# define __UINT_LEAST32_MAX UINT64_MAX
# undef __INT_LEAST16_MIN
# define __INT_LEAST16_MIN INT64_MIN
# undef __INT_LEAST16_MAX
# define __INT_LEAST16_MAX INT64_MAX
# undef __UINT_LEAST16_MAX
# define __UINT_LEAST16_MAX UINT64_MAX
# undef __INT_LEAST8_MIN
# define __INT_LEAST8_MIN INT64_MIN
# undef __INT_LEAST8_MAX
# define __INT_LEAST8_MAX INT64_MAX
# undef __UINT_LEAST8_MAX
# define __UINT_LEAST8_MAX UINT64_MAX
#endif /* __INT64_TYPE__ */
#ifdef __INT_LEAST64_MIN
# define INT_LEAST64_MIN __INT_LEAST64_MIN
# define INT_LEAST64_MAX __INT_LEAST64_MAX
# define UINT_LEAST64_MAX __UINT_LEAST64_MAX
# define INT_FAST64_MIN __INT_LEAST64_MIN
# define INT_FAST64_MAX __INT_LEAST64_MAX
# define UINT_FAST64_MAX __UINT_LEAST64_MAX
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
# define UINT_LEAST64_WIDTH __UINT_LEAST64_WIDTH
# define INT_LEAST64_WIDTH UINT_LEAST64_WIDTH
# define UINT_FAST64_WIDTH __UINT_LEAST64_WIDTH
# define INT_FAST64_WIDTH UINT_FAST64_WIDTH
#endif /* __STDC_VERSION__ */
#endif /* __INT_LEAST64_MIN */
#ifdef __INT56_TYPE__
# define INT56_MAX INT56_C(36028797018963967)
# define INT56_MIN (-INT56_C(36028797018963967)-1)
# define UINT56_MAX UINT56_C(72057594037927935)
# define INT_LEAST56_MIN INT56_MIN
# define INT_LEAST56_MAX INT56_MAX
# define UINT_LEAST56_MAX UINT56_MAX
# define INT_FAST56_MIN INT56_MIN
# define INT_FAST56_MAX INT56_MAX
# define UINT_FAST56_MAX UINT56_MAX
# undef __INT_LEAST32_MIN
# define __INT_LEAST32_MIN INT56_MIN
# undef __INT_LEAST32_MAX
# define __INT_LEAST32_MAX INT56_MAX
# undef __UINT_LEAST32_MAX
# define __UINT_LEAST32_MAX UINT56_MAX
# undef __INT_LEAST16_MIN
# define __INT_LEAST16_MIN INT56_MIN
# undef __INT_LEAST16_MAX
# define __INT_LEAST16_MAX INT56_MAX
# undef __UINT_LEAST16_MAX
# define __UINT_LEAST16_MAX UINT56_MAX
# undef __INT_LEAST8_MIN
# define __INT_LEAST8_MIN INT56_MIN
# undef __INT_LEAST8_MAX
# define __INT_LEAST8_MAX INT56_MAX
# undef __UINT_LEAST8_MAX
# define __UINT_LEAST8_MAX UINT56_MAX
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
# define UINT56_WIDTH 56
# define INT56_WIDTH UINT56_WIDTH
# define UINT_LEAST56_WIDTH UINT56_WIDTH
# define INT_LEAST56_WIDTH UINT_LEAST56_WIDTH
# define UINT_FAST56_WIDTH UINT56_WIDTH
# define INT_FAST56_WIDTH UINT_FAST56_WIDTH
# undef __UINT_LEAST32_WIDTH
# define __UINT_LEAST32_WIDTH UINT56_WIDTH
# undef __UINT_LEAST16_WIDTH
# define __UINT_LEAST16_WIDTH UINT56_WIDTH
# undef __UINT_LEAST8_WIDTH
# define __UINT_LEAST8_WIDTH UINT56_WIDTH
#endif /* __STDC_VERSION__ */
#endif /* __INT56_TYPE__ */
#ifdef __INT48_TYPE__
# define INT48_MAX INT48_C(140737488355327)
# define INT48_MIN (-INT48_C(140737488355327)-1)
# define UINT48_MAX UINT48_C(281474976710655)
# define INT_LEAST48_MIN INT48_MIN
# define INT_LEAST48_MAX INT48_MAX
# define UINT_LEAST48_MAX UINT48_MAX
# define INT_FAST48_MIN INT48_MIN
# define INT_FAST48_MAX INT48_MAX
# define UINT_FAST48_MAX UINT48_MAX
# undef __INT_LEAST32_MIN
# define __INT_LEAST32_MIN INT48_MIN
# undef __INT_LEAST32_MAX
# define __INT_LEAST32_MAX INT48_MAX
# undef __UINT_LEAST32_MAX
# define __UINT_LEAST32_MAX UINT48_MAX
# undef __INT_LEAST16_MIN
# define __INT_LEAST16_MIN INT48_MIN
# undef __INT_LEAST16_MAX
# define __INT_LEAST16_MAX INT48_MAX
# undef __UINT_LEAST16_MAX
# define __UINT_LEAST16_MAX UINT48_MAX
# undef __INT_LEAST8_MIN
# define __INT_LEAST8_MIN INT48_MIN
# undef __INT_LEAST8_MAX
# define __INT_LEAST8_MAX INT48_MAX
# undef __UINT_LEAST8_MAX
# define __UINT_LEAST8_MAX UINT48_MAX
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
#define UINT48_WIDTH 48
#define INT48_WIDTH UINT48_WIDTH
#define UINT_LEAST48_WIDTH UINT48_WIDTH
#define INT_LEAST48_WIDTH UINT_LEAST48_WIDTH
#define UINT_FAST48_WIDTH UINT48_WIDTH
#define INT_FAST48_WIDTH UINT_FAST48_WIDTH
#undef __UINT_LEAST32_WIDTH
#define __UINT_LEAST32_WIDTH UINT48_WIDTH
# undef __UINT_LEAST16_WIDTH
#define __UINT_LEAST16_WIDTH UINT48_WIDTH
# undef __UINT_LEAST8_WIDTH
#define __UINT_LEAST8_WIDTH UINT48_WIDTH
#endif /* __STDC_VERSION__ */
#endif /* __INT48_TYPE__ */
#ifdef __INT40_TYPE__
# define INT40_MAX INT40_C(549755813887)
# define INT40_MIN (-INT40_C(549755813887)-1)
# define UINT40_MAX UINT40_C(1099511627775)
# define INT_LEAST40_MIN INT40_MIN
# define INT_LEAST40_MAX INT40_MAX
# define UINT_LEAST40_MAX UINT40_MAX
# define INT_FAST40_MIN INT40_MIN
# define INT_FAST40_MAX INT40_MAX
# define UINT_FAST40_MAX UINT40_MAX
# undef __INT_LEAST32_MIN
# define __INT_LEAST32_MIN INT40_MIN
# undef __INT_LEAST32_MAX
# define __INT_LEAST32_MAX INT40_MAX
# undef __UINT_LEAST32_MAX
# define __UINT_LEAST32_MAX UINT40_MAX
# undef __INT_LEAST16_MIN
# define __INT_LEAST16_MIN INT40_MIN
# undef __INT_LEAST16_MAX
# define __INT_LEAST16_MAX INT40_MAX
# undef __UINT_LEAST16_MAX
# define __UINT_LEAST16_MAX UINT40_MAX
# undef __INT_LEAST8_MIN
# define __INT_LEAST8_MIN INT40_MIN
# undef __INT_LEAST8_MAX
# define __INT_LEAST8_MAX INT40_MAX
# undef __UINT_LEAST8_MAX
# define __UINT_LEAST8_MAX UINT40_MAX
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
# define UINT40_WIDTH 40
# define INT40_WIDTH UINT40_WIDTH
# define UINT_LEAST40_WIDTH UINT40_WIDTH
# define INT_LEAST40_WIDTH UINT_LEAST40_WIDTH
# define UINT_FAST40_WIDTH UINT40_WIDTH
# define INT_FAST40_WIDTH UINT_FAST40_WIDTH
# undef __UINT_LEAST32_WIDTH
# define __UINT_LEAST32_WIDTH UINT40_WIDTH
# undef __UINT_LEAST16_WIDTH
# define __UINT_LEAST16_WIDTH UINT40_WIDTH
# undef __UINT_LEAST8_WIDTH
# define __UINT_LEAST8_WIDTH UINT40_WIDTH
#endif /* __STDC_VERSION__ */
#endif /* __INT40_TYPE__ */
#ifdef __INT32_TYPE__
# define INT32_MAX INT32_C(2147483647)
# define INT32_MIN (-INT32_C(2147483647)-1)
# define UINT32_MAX UINT32_C(4294967295)
# undef __INT_LEAST32_MIN
# define __INT_LEAST32_MIN INT32_MIN
# undef __INT_LEAST32_MAX
# define __INT_LEAST32_MAX INT32_MAX
# undef __UINT_LEAST32_MAX
# define __UINT_LEAST32_MAX UINT32_MAX
# undef __INT_LEAST16_MIN
# define __INT_LEAST16_MIN INT32_MIN
# undef __INT_LEAST16_MAX
# define __INT_LEAST16_MAX INT32_MAX
# undef __UINT_LEAST16_MAX
# define __UINT_LEAST16_MAX UINT32_MAX
# undef __INT_LEAST8_MIN
# define __INT_LEAST8_MIN INT32_MIN
# undef __INT_LEAST8_MAX
# define __INT_LEAST8_MAX INT32_MAX
# undef __UINT_LEAST8_MAX
# define __UINT_LEAST8_MAX UINT32_MAX
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
# define UINT32_WIDTH 32
# define INT32_WIDTH UINT32_WIDTH
# undef __UINT_LEAST32_WIDTH
# define __UINT_LEAST32_WIDTH UINT32_WIDTH
# undef __UINT_LEAST16_WIDTH
# define __UINT_LEAST16_WIDTH UINT32_WIDTH
# undef __UINT_LEAST8_WIDTH
# define __UINT_LEAST8_WIDTH UINT32_WIDTH
#endif /* __STDC_VERSION__ */
#endif /* __INT32_TYPE__ */
#ifdef __INT_LEAST32_MIN
# define INT_LEAST32_MIN __INT_LEAST32_MIN
# define INT_LEAST32_MAX __INT_LEAST32_MAX
# define UINT_LEAST32_MAX __UINT_LEAST32_MAX
# define INT_FAST32_MIN __INT_LEAST32_MIN
# define INT_FAST32_MAX __INT_LEAST32_MAX
# define UINT_FAST32_MAX __UINT_LEAST32_MAX
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
# define UINT_LEAST32_WIDTH __UINT_LEAST32_WIDTH
# define INT_LEAST32_WIDTH UINT_LEAST32_WIDTH
# define UINT_FAST32_WIDTH __UINT_LEAST32_WIDTH
# define INT_FAST32_WIDTH UINT_FAST32_WIDTH
#endif /* __STDC_VERSION__ */
#endif /* __INT_LEAST32_MIN */
#ifdef __INT24_TYPE__
# define INT24_MAX INT24_C(8388607)
# define INT24_MIN (-INT24_C(8388607)-1)
# define UINT24_MAX UINT24_C(16777215)
# define INT_LEAST24_MIN INT24_MIN
# define INT_LEAST24_MAX INT24_MAX
# define UINT_LEAST24_MAX UINT24_MAX
# define INT_FAST24_MIN INT24_MIN
# define INT_FAST24_MAX INT24_MAX
# define UINT_FAST24_MAX UINT24_MAX
# undef __INT_LEAST16_MIN
# define __INT_LEAST16_MIN INT24_MIN
# undef __INT_LEAST16_MAX
# define __INT_LEAST16_MAX INT24_MAX
# undef __UINT_LEAST16_MAX
# define __UINT_LEAST16_MAX UINT24_MAX
# undef __INT_LEAST8_MIN
# define __INT_LEAST8_MIN INT24_MIN
# undef __INT_LEAST8_MAX
# define __INT_LEAST8_MAX INT24_MAX
# undef __UINT_LEAST8_MAX
# define __UINT_LEAST8_MAX UINT24_MAX
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
# define UINT24_WIDTH 24
# define INT24_WIDTH UINT24_WIDTH
# define UINT_LEAST24_WIDTH UINT24_WIDTH
# define INT_LEAST24_WIDTH UINT_LEAST24_WIDTH
# define UINT_FAST24_WIDTH UINT24_WIDTH
# define INT_FAST24_WIDTH UINT_FAST24_WIDTH
# undef __UINT_LEAST16_WIDTH
# define __UINT_LEAST16_WIDTH UINT24_WIDTH
# undef __UINT_LEAST8_WIDTH
# define __UINT_LEAST8_WIDTH UINT24_WIDTH
#endif /* __STDC_VERSION__ */
#endif /* __INT24_TYPE__ */
#ifdef __INT16_TYPE__
#define INT16_MAX INT16_C(32767)
#define INT16_MIN (-INT16_C(32767)-1)
#define UINT16_MAX UINT16_C(65535)
# undef __INT_LEAST16_MIN
# define __INT_LEAST16_MIN INT16_MIN
# undef __INT_LEAST16_MAX
# define __INT_LEAST16_MAX INT16_MAX
# undef __UINT_LEAST16_MAX
# define __UINT_LEAST16_MAX UINT16_MAX
# undef __INT_LEAST8_MIN
# define __INT_LEAST8_MIN INT16_MIN
# undef __INT_LEAST8_MAX
# define __INT_LEAST8_MAX INT16_MAX
# undef __UINT_LEAST8_MAX
# define __UINT_LEAST8_MAX UINT16_MAX
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
# define UINT16_WIDTH 16
# define INT16_WIDTH UINT16_WIDTH
# undef __UINT_LEAST16_WIDTH
# define __UINT_LEAST16_WIDTH UINT16_WIDTH
# undef __UINT_LEAST8_WIDTH
# define __UINT_LEAST8_WIDTH UINT16_WIDTH
#endif /* __STDC_VERSION__ */
#endif /* __INT16_TYPE__ */
#ifdef __INT_LEAST16_MIN
# define INT_LEAST16_MIN __INT_LEAST16_MIN
# define INT_LEAST16_MAX __INT_LEAST16_MAX
# define UINT_LEAST16_MAX __UINT_LEAST16_MAX
# define INT_FAST16_MIN __INT_LEAST16_MIN
# define INT_FAST16_MAX __INT_LEAST16_MAX
# define UINT_FAST16_MAX __UINT_LEAST16_MAX
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
# define UINT_LEAST16_WIDTH __UINT_LEAST16_WIDTH
# define INT_LEAST16_WIDTH UINT_LEAST16_WIDTH
# define UINT_FAST16_WIDTH __UINT_LEAST16_WIDTH
# define INT_FAST16_WIDTH UINT_FAST16_WIDTH
#endif /* __STDC_VERSION__ */
#endif /* __INT_LEAST16_MIN */
#ifdef __INT8_TYPE__
# define INT8_MAX INT8_C(127)
# define INT8_MIN (-INT8_C(127)-1)
# define UINT8_MAX UINT8_C(255)
# undef __INT_LEAST8_MIN
# define __INT_LEAST8_MIN INT8_MIN
# undef __INT_LEAST8_MAX
# define __INT_LEAST8_MAX INT8_MAX
# undef __UINT_LEAST8_MAX
# define __UINT_LEAST8_MAX UINT8_MAX
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
# define UINT8_WIDTH 8
# define INT8_WIDTH UINT8_WIDTH
# undef __UINT_LEAST8_WIDTH
# define __UINT_LEAST8_WIDTH UINT8_WIDTH
#endif /* __STDC_VERSION__ */
#endif /* __INT8_TYPE__ */
#ifdef __INT_LEAST8_MIN
# define INT_LEAST8_MIN __INT_LEAST8_MIN
# define INT_LEAST8_MAX __INT_LEAST8_MAX
# define UINT_LEAST8_MAX __UINT_LEAST8_MAX
# define INT_FAST8_MIN __INT_LEAST8_MIN
# define INT_FAST8_MAX __INT_LEAST8_MAX
# define UINT_FAST8_MAX __UINT_LEAST8_MAX
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
# define UINT_LEAST8_WIDTH __UINT_LEAST8_WIDTH
# define INT_LEAST8_WIDTH UINT_LEAST8_WIDTH
# define UINT_FAST8_WIDTH __UINT_LEAST8_WIDTH
# define INT_FAST8_WIDTH UINT_FAST8_WIDTH
#endif /* __STDC_VERSION__ */
#endif /* __INT_LEAST8_MIN */
/* Some utility macros */
#define __INTN_MIN(n) __stdint_join3( INT, n, _MIN)
#define __INTN_MAX(n) __stdint_join3( INT, n, _MAX)
#define __UINTN_MAX(n) __stdint_join3(UINT, n, _MAX)
#define __INTN_C(n, v) __stdint_join3( INT, n, _C(v))
#define __UINTN_C(n, v) __stdint_join3(UINT, n, _C(v))
/* C99 7.18.2.4 Limits of integer types capable of holding object pointers. */
/* C99 7.18.3 Limits of other integer types. */
#define INTPTR_MIN (-__INTPTR_MAX__-1)
#define INTPTR_MAX __INTPTR_MAX__
#define UINTPTR_MAX __UINTPTR_MAX__
#define PTRDIFF_MIN (-__PTRDIFF_MAX__-1)
#define PTRDIFF_MAX __PTRDIFF_MAX__
#define SIZE_MAX __SIZE_MAX__
/* C23 7.22.2.4 Width of integer types capable of holding object pointers. */
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
/* NB: The C standard requires that these be the same value, but the compiler
exposes separate internal width macros. */
#define INTPTR_WIDTH __INTPTR_WIDTH__
#define UINTPTR_WIDTH __UINTPTR_WIDTH__
#endif
/* ISO9899:2011 7.20 (C11 Annex K): Define RSIZE_MAX if __STDC_WANT_LIB_EXT1__
* is enabled. */
#if defined(__STDC_WANT_LIB_EXT1__) && __STDC_WANT_LIB_EXT1__ >= 1
#define RSIZE_MAX (SIZE_MAX >> 1)
#endif
/* C99 7.18.2.5 Limits of greatest-width integer types. */
#define INTMAX_MIN (-__INTMAX_MAX__-1)
#define INTMAX_MAX __INTMAX_MAX__
#define UINTMAX_MAX __UINTMAX_MAX__
/* C23 7.22.2.5 Width of greatest-width integer types. */
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
/* NB: The C standard requires that these be the same value, but the compiler
exposes separate internal width macros. */
#define INTMAX_WIDTH __INTMAX_WIDTH__
#define UINTMAX_WIDTH __UINTMAX_WIDTH__
#endif
/* C99 7.18.3 Limits of other integer types. */
#define SIG_ATOMIC_MIN __INTN_MIN(__SIG_ATOMIC_WIDTH__)
#define SIG_ATOMIC_MAX __INTN_MAX(__SIG_ATOMIC_WIDTH__)
#ifdef __WINT_UNSIGNED__
# define WINT_MIN __UINTN_C(__WINT_WIDTH__, 0)
# define WINT_MAX __UINTN_MAX(__WINT_WIDTH__)
#else
# define WINT_MIN __INTN_MIN(__WINT_WIDTH__)
# define WINT_MAX __INTN_MAX(__WINT_WIDTH__)
#endif
#ifndef WCHAR_MAX
# define WCHAR_MAX __WCHAR_MAX__
#endif
#ifndef WCHAR_MIN
# if __WCHAR_MAX__ == __INTN_MAX(__WCHAR_WIDTH__)
# define WCHAR_MIN __INTN_MIN(__WCHAR_WIDTH__)
# else
# define WCHAR_MIN __UINTN_C(__WCHAR_WIDTH__, 0)
# endif
#endif
/* 7.18.4.2 Macros for greatest-width integer constants. */
#define INTMAX_C(v) __int_c(v, __INTMAX_C_SUFFIX__)
#define UINTMAX_C(v) __int_c(v, __UINTMAX_C_SUFFIX__)
/* C23 7.22.3.x Width of other integer types. */
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
#define PTRDIFF_WIDTH __PTRDIFF_WIDTH__
#define SIG_ATOMIC_WIDTH __SIG_ATOMIC_WIDTH__
#define SIZE_WIDTH __SIZE_WIDTH__
#define WCHAR_WIDTH __WCHAR_WIDTH__
#define WINT_WIDTH __WINT_WIDTH__
#endif
#endif /* __STDC_HOSTED__ */
#endif /* __MVS__ */
#endif /* __CLANG_STDINT_H */

View file

@ -1,302 +0,0 @@
// Copyright 2011-2017 David Robillard <d@drobilla.net>
// SPDX-License-Identifier: ISC
/// @file suil.h Public API for Suil
#ifndef SUIL_SUIL_H
#define SUIL_SUIL_H
typedef struct { const char *URI; void *data; } LV2_Feature;
#include "stdbool.h"
#include "stdint.h"
// SUIL_LIB_IMPORT and SUIL_LIB_EXPORT mark the entry points of shared libraries
#ifdef _WIN32
# define SUIL_LIB_IMPORT __declspec(dllimport)
# define SUIL_LIB_EXPORT __declspec(dllexport)
#else
# define SUIL_LIB_IMPORT __attribute__((visibility("default")))
# define SUIL_LIB_EXPORT __attribute__((visibility("default")))
#endif
// SUIL_API exposes symbols in the public API
#ifndef SUIL_API
# ifdef SUIL_STATIC
# define SUIL_API
# elif defined(SUIL_INTERNAL)
# define SUIL_API SUIL_LIB_EXPORT
# else
# define SUIL_API SUIL_LIB_IMPORT
# endif
#endif
#ifdef __cplusplus
extern "C" {
#endif
/**
@defgroup suil Suil C API
@{
*/
/**
@defgroup suil_callbacks Callbacks
@{
*/
/**
UI controller.
This is an opaque pointer passed by the user which is passed to the various
UI control functions (e.g. SuilPortWriteFunc). It is typically used to pass
a pointer to some controller object the host uses to communicate with
plugins.
*/
typedef void* SuilController;
/// Function to write/send a value to a port
typedef void (*SuilPortWriteFunc)( //
SuilController controller,
uint32_t port_index,
uint32_t buffer_size,
uint32_t protocol,
void const* buffer);
/// Function to return the index for a port by symbol
typedef uint32_t (*SuilPortIndexFunc)( //
SuilController controller,
const char* port_symbol);
/// Function to subscribe to notifications for a port
typedef uint32_t (*SuilPortSubscribeFunc)( //
SuilController controller,
uint32_t port_index,
uint32_t protocol,
const LV2_Feature* const* features);
/// Function to unsubscribe from notifications for a port
typedef uint32_t (*SuilPortUnsubscribeFunc)( //
SuilController controller,
uint32_t port_index,
uint32_t protocol,
const LV2_Feature* const* features);
/// Function called when a control is grabbed or released
typedef void (*SuilTouchFunc)( //
SuilController controller,
uint32_t port_index,
bool grabbed);
/**
@}
@defgroup suil_library Library
@{
*/
/// Initialization argument
typedef enum { SUIL_ARG_NONE } SuilArg;
/**
Initialize suil.
This function should be called as early as possible, before any other GUI
toolkit functions. The variable argument list is a sequence of SuilArg keys
and corresponding value pairs for passing any necessary platform-specific
information. It must be terminated with SUIL_ARG_NONE.
*/
SUIL_API
void
suil_init(int* argc, char*** argv, SuilArg key, ...);
/**
Check if suil can wrap a UI type.
@param host_type_uri The URI of the desired widget type of the host,
corresponding to the `type_uri` parameter of suil_instance_new().
@param ui_type_uri The URI of the UI widget type.
@return 0 if wrapping is unsupported, otherwise the quality of the wrapping
where 1 is the highest quality (direct native embedding with no wrapping)
and increasing values are of a progressively lower quality and/or stability.
*/
SUIL_API
unsigned
suil_ui_supported(const char* host_type_uri, const char* ui_type_uri);
/**
@}
@defgroup suil_host Host
@{
*/
/**
UI host descriptor.
This contains the various functions that a plugin UI may use to communicate
with the plugin. It is passed to suil_instance_new() to provide these
functions to the UI.
*/
typedef struct SuilHostImpl SuilHost;
/**
Create a new UI host descriptor.
@param write_func Function to send a value to a plugin port.
@param index_func Function to get the index for a port by symbol.
@param subscribe_func Function to subscribe to port updates.
@param unsubscribe_func Function to unsubscribe from port updates.
*/
SUIL_API
SuilHost*
suil_host_new(SuilPortWriteFunc write_func,
SuilPortIndexFunc index_func,
SuilPortSubscribeFunc subscribe_func,
SuilPortUnsubscribeFunc unsubscribe_func);
/**
Set a touch function for a host descriptor.
Note this function will only be called if the UI supports it.
*/
SUIL_API
void
suil_host_set_touch_func(SuilHost* host, SuilTouchFunc touch_func);
/**
Free `host`.
*/
SUIL_API
void
suil_host_free(SuilHost* host);
/**
@}
*/
/**
@defgroup suil_instance Instance
@{
*/
/// An instance of an LV2 plugin UI
typedef struct SuilInstanceImpl SuilInstance;
/// Opaque pointer to a UI handle
typedef void* SuilHandle;
/// Opaque pointer to a UI widget
typedef void* SuilWidget;
/**
Instantiate a UI for an LV2 plugin.
This funcion may load a suil module to adapt the UI to the desired toolkit.
Suil is configured at compile time to load modules from the appropriate
place, but this can be changed at run-time via the environment variable
SUIL_MODULE_DIR. This makes it possible to bundle suil with an application.
Note that some situations (Gtk in Qt, Windows in Gtk) require a parent
container to be passed as a feature with URI LV2_UI__parent
(http://lv2plug.in/ns/extensions/ui#ui) in order to work correctly. The
data must point to a single child container of the host widget set.
@param host Host descriptor.
@param controller Opaque host controller pointer.
@param container_type_uri URI of the desired host container widget type.
@param plugin_uri URI of the plugin to instantiate this UI for.
@param ui_uri URI of the specifically desired UI.
@param ui_type_uri URI of the actual UI widget type.
@param ui_bundle_path Path of the UI bundle.
@param ui_binary_path Path of the UI binary.
@param features NULL-terminated array of supported features, or NULL.
@return A new UI instance, or NULL if instantiation failed.
*/
SUIL_API
SuilInstance*
suil_instance_new(SuilHost* host,
SuilController controller,
const char* container_type_uri,
const char* plugin_uri,
const char* ui_uri,
const char* ui_type_uri,
const char* ui_bundle_path,
const char* ui_binary_path,
const LV2_Feature* const* features);
/**
Free a plugin UI instance.
The caller must ensure all references to the UI have been dropped before
calling this function (e.g. it has been removed from its parent).
*/
SUIL_API
void
suil_instance_free(SuilInstance* instance);
/**
Get the handle for a UI instance.
Returns the handle to the UI instance. The returned handle has opaque type
to insulate the Suil API from LV2 extensions, but in pactice it is currently
of type `LV2UI_Handle`. This should not normally be needed.
The returned handle is shared and must not be deleted.
*/
SUIL_API
SuilHandle
suil_instance_get_handle(SuilInstance* instance);
/**
Get the widget for a UI instance.
Returns an opaque pointer to a widget, the type of which matches the
`container_type_uri` parameter of suil_instance_new(). Note this may be a
wrapper widget created by Suil, and not necessarily the widget directly
implemented by the UI.
*/
SUIL_API
SuilWidget
suil_instance_get_widget(SuilInstance* instance);
/**
Notify the UI about a change in a plugin port.
This function can be used to notify the UI about any port change, but in the
simplest case is used to set the value of lv2:ControlPort ports. For
simplicity, this is a special case where `format` is 0, `buffer_size` is 4,
and `buffer` should point to a single float.
The `buffer` must be valid only for the duration of this call, the UI must
not keep a reference to it.
@param instance UI instance.
@param port_index Index of the port which has changed.
@param buffer_size Size of `buffer` in bytes.
@param format Format of `buffer` (mapped URI, or 0 for float).
@param buffer Change data, e.g. the new port value.
*/
SUIL_API
void
suil_instance_port_event(SuilInstance* instance,
uint32_t port_index,
uint32_t buffer_size,
uint32_t format,
const void* buffer);
/// Return a data structure defined by some LV2 extension URI
SUIL_API
const void*
suil_instance_extension_data(SuilInstance* instance, const char* uri);
/**
@}
@}
*/
#ifdef __cplusplus
} // extern "C"
#endif
#endif /* SUIL_SUIL_H */

View file

@ -1,35 +0,0 @@
[package]
name = "tek"
edition = "2021"
version = "0.1.0"
[dependencies]
clojure-reader = "0.1.0"
microxdg = "0.1.2"
tek_core = { path = "../tek_core" }
#tek_sequencer = { path = "../tek_sequencer" }
#tek_mixer = { path = "../tek_mixer" }
#jack = "0.10"
#crossterm = "0.27"
#ratatui = { version = "0.26.3", features = [ "unstable-widget-ref", "underline-color" ] }
#backtrace = "0.3.72"
#toml = "0.8.12"
#better-panic = "0.3.0"
#midly = "0.5"
#vst = "0.4.0"
##vst3 = "0.1.0"
#livi = "0.7.4"
##atomic_enum = "0.3.0"
#wavers = "1.4.3"
#music-math = "0.1.1"
#fraction = "0.15.3"
#rlsf = "0.2.1"
#r8brain-rs = "0.3.5"
#symphonia = { version = "0.5.4", features = [ "all" ] }
#dasp = { version = "0.11.0", features = [ "all" ] }
#rubato = "0.15.0"

View file

@ -1,3 +0,0 @@
# `tek`
This crate unifies the `tek_*` subcrates into a single application.

View file

@ -1,139 +0,0 @@
;(bpm 150)
;(midi-in "nanoKEY Studio.*capture.*")
;(midi-in "nanoKEY Studio.*capture.*")
;(audio-out "Built-.+:playback_FL", "Built-.+:playback_FR")
;(scene { :name "Intro" } 0 0 _ _)
;(scene { :name "Hook" } 1 1 0 _)
;(scene { :name "Verse" } 2 2 1 _)
;(scene { :name "Chorus" } 3 3 2 _)
;(scene { :name "Bridge" } _ 4 3 _)
;(scene { :name "Outro" } 4 1 4 _)
;(track { :name "Drums" :gain +0.0 }
;(phrase { :name "4 kicks" :beats 4 :steps 16 }
;(:00 (36 128))
;(:04 (36 100))
;(:08 (36 100))
;(:12 (36 100)))
;(phrase { :name "5 kicks" :beats 4 :steps 16 }
;(:00 (36 128))
;(:04 (36 100))
;(:08 (36 128))
;(:12 (36 100))
;(:14 (36 110)))
;(phrase { :name "D Beat" :beats 8 :steps 32 }
;(:00 (44 70) (36 128) (49 110))
;(:02 (44 30))
;(:04 (44 80) (40 100))
;(:06 (44 50))
;(:08 (44 30) (36 100))
;(:10 (44 50) (36 100))
;(:12 (44 80) (40 100))
;(:14 (44 50))
;(:15 (36 50))
;(:16 (44 60) (36 80))
;(:18 (44 60) (36 80))
;(:20 (44 60) (40 80))
;(:22 (44 60))
;(:24 (44 60))
;(:26 (44 30) (36 80))
;(:27 (44 60))
;(:28 (44 60) (40 80))
;(:30 (44 60)))
;(phrase { :name "Garage" :beats 4 :steps 16 }
;(:00 (44 100) (36 100) (35 100))
;(:01 (44 100))
;(:02 (44 100) (35 100))
;(:03 (44 100))
;(:04 (44 100) (40 100))
;(:06 (44 100))
;(:07 (44 100) (34 100))
;(:09 (44 100))
;(:10 (44 100))
;(:11 (35 100) (36 100))
;(:12 (44 100) (40 100))
;(:14 (44 100)))
;(phrase { :name "Trap Pinging" :beats 8 :steps 96 }
;(:00 (42 100) (36 100) (34 120) (49 100))
;(:01 (42 100))
;(:02 (42 100))
;(:06 (42 100) (35 80) (36 80) (49 100))
;(:07 (42 100))
;(:08 (42 100))
;(:12 (42 100))
;(:15 (39 100) (34 100))
;(:18 (42 100))
;(:24 (42 100) (38 50) (40 50))
;(:27 (42 100) (36 50))
;(:30 (42 100))
;(:33 (42 100) (36 50) (34 100))
;(:36 (42 90))
;(:39 (42 80))
;(:42 (42 70))
;(:45 (42 60))
;(:48 (42 100) (36 100) (34 100))
;(:50 (42 100))
;(:52 (42 110))
;(:54 (46 50) (42 120))
;(:56 (42 90))
;(:58 (42 100))
;(:60 (42 100) (35 100))
;(:64 (39 100))
;(:66 (42 100) (34 100))
;(:70 (42 100))
;(:71 (42 100))
;(:72 (42 100) (38 50) (40 50))
;(:75 (42 100) (36 50) (34 80))
;(:78 (42 100))
;(:81 (42 100) (36 50))
;(:84 (38 40) (40 50) (34 90))
;(:87 (42 90) (35 40))
;(:90 (42 70)))
;(sampler { :name "DrumKit1" :dir "/home/user/Lab/Music/pak" }
;(sample { :midi 34 :name "808 D" :file "808.wav" })
;(sample { :midi 35 :name "Kick 1" :file "kik.wav" })
;(sample { :midi 36 :name "Kick 2" :file "kik2.wav" })
;(sample { :midi 37 :name "Rim" :file "rim.wav" })
;(sample { :midi 38 :name "Snare 1" :file "sna.wav" })
;(sample { :midi 39 :name "Shaker" :file "shk.wav" })
;(sample { :midi 40 :name "Snare 2" :file "sna2.wav" })
;(sample { :midi 42 :name "Closed HH 1" :file "chh.wav" })
;(sample { :midi 44 :name "Closed HH 2" :file "chh2.wav" })
;(sample { :midi 45 :name "Open HH 0" :file "ohh.wav" })
;(sample { :midi 46 :name "Open HH 1" :file "ohh1.wav" })
;(sample { :midi 47 :name "Open HH 2" :file "ohh2.wav" })
;(sample { :midi 49 :name "Crash" :file "crs.wav" })))
;(track { :name "Bass" :gain +0.0 }
;(phrase { :name "Bass 1" :beats 4 })
;(phrase { :name "Bass 2" :beats 4 })
;(phrase { :name "Bass 3" :beats 4 })
;(phrase { :name "Bass 4" :beats 4 })
;(phrase { :name "Bass 5" :beats 4 })
;(phrase { :name "Bass 6" :beats 4 })
;(phrase { :name "Bass 7" :beats 4 })
;(phrase { :name "Bass 8" :beats 4 })
;(lv2 {
;:name "Odin2"
;:path "file:///home/user/.lv2/Odin2.lv2"
;}))
;(track { :name "Lead" :gain +0.0 }
;(phrase { :name "Lead 1" :beats 4 })
;(phrase { :name "Lead 2" :beats 4 })
;(phrase { :name "Lead 3" :beats 4 })
;(phrase { :name "Lead 4" :beats 4 })
;(phrase { :name "Lead 5" :beats 4 })
;(phrase { :name "Lead 6" :beats 4 })
;(phrase { :name "Lead 7" :beats 4 })
;(phrase { :name "Lead 8" :beats 4 })
;(lv2 {
;:name "Odin2"
;:path "file:///home/user/.lv2/Odin2.lv2"
;}))

View file

@ -1,151 +0,0 @@
use crate::*;
use tek_core::Direction;
use tek_mixer::Mixer;
use tek_sequencer::{Arranger, TransportToolbar};
/// Root of application state.
pub struct App {
/// Whether the currently focused section has input priority
pub entered: bool,
/// Currently focused section
pub section: AppFocus,
/// Transport model and view.
pub transport: TransportToolbar,
/// Arranger (contains sequencers)
pub arranger: Arranger,
/// Mixer (contains tracks)
pub mixer: Mixer,
/// Main JACK client.
pub jack: Option<JackClientt>,
/// Map of external MIDI outs in the jack graph
/// to internal MIDI ins of this app.
pub midi_in: Option<Arc<Port<MidiIn>>>,
/// Names of ports to connect to main MIDI IN.
pub midi_ins: Vec<String>,
/// Display mode of chain section
pub chain_mode: bool,
/// Main audio outputs.
pub audio_outs: Vec<Arc<Port<Unowned>>>,
/// Number of frames requested by process callback
chunk_size: usize,
/// Paths to user directories
_xdg: Option<Arc<XdgApp>>,
}
impl App {
pub fn new() -> Usually<Self> {
let xdg = Arc::new(XdgApp::new("tek")?);
let first_run = AppPaths::new(&xdg)?.should_create();
let jack = JackClient::Inactive(Client::new("tek", ClientOptions::NO_START_SERVER)?.0);
*MODAL.lock().unwrap() =
first_run.then(|| ExitableComponent::boxed(SetupModal(Some(xdg.clone()), false)));
Ok(Self {
entered: true,
section: AppFocus::default(),
transport: TransportToolbar::new(Some(jack.transport())),
arranger: Arranger::new(""),
mixer: Mixer::new("")?,
jack: Some(jack),
audio_outs: vec![],
chain_mode: false,
chunk_size: 0,
midi_in: None,
midi_ins: vec![],
_xdg: Some(xdg),
})
}
pub fn client(&self) -> &Client {
self.jack.as_ref().unwrap().client()
}
pub fn audio_out(&self, index: usize) -> Option<Arc<Port<Unowned>>> {
self.audio_outs.get(index).map(|x| x.clone())
}
pub fn with_midi_ins(mut self, names: &[&str]) -> Usually<Self> {
self.midi_ins = names.iter().map(|x| x.to_string()).collect();
Ok(self)
}
pub fn with_audio_outs(mut self, names: &[&str]) -> Usually<Self> {
let client = self.client();
self.audio_outs = names
.iter()
.map(|name| {
client
.ports(Some(name), None, PortFlags::empty())
.get(0)
.map(|name| client.port_by_name(name))
})
.flatten()
.filter_map(|x| x)
.map(Arc::new)
.collect();
Ok(self)
}
pub fn activate(
mut self,
init: Option<impl FnOnce(&Arc<RwLock<Self>>) -> Usually<()>>,
) -> Usually<Arc<RwLock<Self>>> {
let jack = self.jack.take().expect("no jack client");
let app = Arc::new(RwLock::new(self));
app.write().unwrap().jack = Some(jack.activate(&app.clone(), |state, client, scope| {
state.write().unwrap().process(client, scope)
})?);
if let Some(init) = init {
init(&app)?;
}
Ok(app)
}
}
render!(
App | self,
buf,
area | {
Stack::down()
.add_ref(&self.transport)
.add_ref(&self.arranger)
.add(If(
self.arranger.selected.is_clip(),
&Stack::right()
.add(tek_mixer::TrackView {
direction: Direction::Down,
entered: self.entered,
focused: self.section == AppFocus::Chain,
chain: self.mixer.track(),
})
.add_ref(&self.arranger.sequencer()),
))
.render(buf, area)?;
if let Some(ref modal) = *MODAL.lock().unwrap() {
modal.render(buf, area)?;
}
Ok(area)
}
);
process!(
App | self,
_client,
scope | {
let (reset, current_frames, chunk_size, current_usecs, next_usecs, period_usecs) =
self.transport.update(&scope);
self.chunk_size = chunk_size;
for track in self.arranger.tracks.iter_mut() {
track.process(
self.midi_in.as_ref().map(|p| p.iter(&scope)),
&self.transport.timebase,
self.transport.playing,
self.transport.started,
self.transport.quant as usize,
reset,
&scope,
(current_frames as usize, self.chunk_size),
(
current_usecs as usize,
next_usecs.saturating_sub(current_usecs) as usize,
),
period_usecs as f64,
);
}
Control::Continue
}
);

View file

@ -1,37 +0,0 @@
use crate::*;
/// Different sections of the UI that may be focused.
#[derive(PartialEq, Clone, Copy)]
pub enum AppFocus {
/// The transport is selected.
Transport,
/// The arranger is selected.
Arranger,
/// The sequencer is selected.
Sequencer,
/// The device chain is selected.
Chain,
}
impl Default for AppFocus {
fn default () -> Self { Self::Arranger }
}
impl AppFocus {
pub fn prev (&mut self) {
*self = match self {
Self::Transport => Self::Chain,
Self::Arranger => Self::Transport,
Self::Sequencer => Self::Arranger,
Self::Chain => Self::Sequencer,
}
}
pub fn next (&mut self) {
*self = match self {
Self::Transport => Self::Arranger,
Self::Arranger => Self::Sequencer,
Self::Sequencer => Self::Chain,
Self::Chain => Self::Transport,
}
}
}

View file

@ -1,52 +0,0 @@
//! Global settings.
use crate::*;
use std::path::{Path, PathBuf};
use std::fs::{File, create_dir_all};
const CONFIG_FILE_NAME: &str = "tek.toml";
const PROJECT_FILE_NAME: &str = "project.toml";
/// Filesystem locations of things.
pub struct AppPaths {
config_dir: PathBuf,
config_file: PathBuf,
data_dir: PathBuf,
project_file: PathBuf,
}
impl AppPaths {
pub fn new (xdg: &XdgApp) -> Usually<Self> {
let config_dir = PathBuf::from(xdg.app_config()?);
let config_file = PathBuf::from(config_dir.join(CONFIG_FILE_NAME));
let data_dir = PathBuf::from(xdg.app_data()?);
let project_file = PathBuf::from(data_dir.join(PROJECT_FILE_NAME));
Ok(Self { config_dir, config_file, data_dir, project_file })
}
pub fn should_create (&self) -> bool {
for path in [
&self.config_dir,
&self.config_file,
&self.data_dir,
&self.project_file,
].iter() {
if !Path::new(path).exists() {
return true
}
}
false
}
pub fn create (&self) -> Usually<()> {
for dir in [&self.config_dir, &self.data_dir].iter() {
if !Path::new(dir).exists() {
create_dir_all(&dir)?;
}
}
for file in [&self.config_file, &self.project_file].iter() {
if !Path::new(file).exists() {
File::create_new(&file)?;
}
}
Ok(())
}
}

View file

@ -1,29 +0,0 @@
//! Command line option parser.
use tek_core::clap::{self, Parser, Subcommand};
#[derive(Debug, Parser)]
#[command(version, about, long_about = None)]
pub struct Cli {
#[command(subcommand)]
pub command: Option<Command>
}
#[derive(Debug, Clone, Subcommand)]
pub enum Command {
/// Launch or control a master transport
Transport,
/// Launch or control a sequencer
Sequencer {
#[arg(long="input")]
inputs: Vec<Option<String>>,
#[arg(long="output")]
outputs: Vec<Option<String>>,
},
/// Launch or control a sampler
Sampler,
/// Launch or control a mixer
Mixer,
/// Launch or control a looper
Looper,
}

View file

@ -1,184 +0,0 @@
//! Handling of input events.
use crate::*;
handle!{
App |self, e| {
if handle_modal(e)? {
return Ok(true)
}
Ok(if self.entered {
handle_focused(self, e)?
|| handle_keymap(self, e, KEYMAP_GLOBAL)?
|| handle_keymap(self, e, crate::control::KEYMAP_FOCUS)?
} else {
handle_keymap(self, e, KEYMAP_GLOBAL)?
|| handle_keymap(self, e, crate::control::KEYMAP_FOCUS)?
|| handle_focused(self, e)?
})
}
}
fn handle_modal (e: &AppEvent) -> Usually<bool> {
let mut handled = false;
let mut close = false;
if let Some(ref mut modal) = *MODAL.lock().unwrap() {
if modal.handle(e)? {
handled = true;
if modal.exited() {
close = true;
}
};
}
if close {
*MODAL.lock().unwrap() = None;
}
Ok(handled)
}
fn handle_focused (state: &mut App, e: &AppEvent) -> Usually<bool> {
unimplemented!()
//match state.section {
//AppFocus::Transport => state.transport.handle(e),
//AppFocus::Arranger => state.arranger.sequencer_mut().map(|s|s.handle(e)),
//AppFocus::Sequencer => state.arranger.sequencer_mut().map(|s|s.handle(e)),
//AppFocus::Chain => Ok(false)[>if state.entered {
//handle_device(state, e)? ||
//handle_keymap(state, e, crate::control::KEYMAP_CHAIN)?
//} else {
//handle_keymap(state, e, crate::control::KEYMAP_CHAIN)? || handle_device(state, e)?
//})*/
//}
}
fn handle_device (state: &mut App, e: &AppEvent) -> Usually<bool> {
state.mixer.track()
.and_then(|track|track.device_mut())
.map(|mut device|device.handle(e))
.transpose()
.map(|x|x.unwrap_or(false))
}
/// Global key bindings.
pub const KEYMAP_GLOBAL: &'static [KeyBinding<App>] = keymap!(App {
[Char(' '), NONE, "play_toggle", "play or pause", |app: &mut App| {
app.transport.toggle_play()?;
Ok(true)
}],
[Char('r'), NONE, "record_toggle", "toggle recording", |app: &mut App| {
app.arranger.track_mut().map(|t|t.toggle_record());
Ok(true)
}],
[Char('o'), NONE, "overdub_toggle", "toggle overdub", |app: &mut App| {
app.arranger.track_mut().map(|t|t.toggle_overdub());
Ok(true)
}],
[Char('m'), NONE, "monitor_toggle", "toggle monitor", |app: &mut App| {
app.arranger.track_mut().map(|t|t.toggle_monitor());
Ok(true)
}],
[Char('+'), NONE, "quant_inc", "quantize coarser", |app: &mut App| {
app.transport.quant = Note::next(app.transport.quant);
Ok(true)
}],
[Char('_'), NONE, "quant_dec", "quantize finer", |app: &mut App| {
app.transport.quant = Note::prev(app.transport.quant);
Ok(true)
}],
[Char('='), NONE, "zoom_in", "show fewer ticks per block", |app: &mut App| {
app.arranger.sequencer_mut().map(|s|s.time_axis.scale_mut(&Note::prev));
Ok(true)
}],
[Char('-'), NONE, "zoom_out", "show more ticks per block", |app: &mut App| {
app.arranger.sequencer_mut().map(|s|s.time_axis.scale_mut(&Note::next));
Ok(true)
}],
[Char('x'), NONE, "extend", "double the current clip", |app: &mut App| {
app.arranger.phrase().map(|x|x.write().unwrap()).map(|mut phrase|{
let mut notes = phrase.notes.clone();
notes.extend_from_slice(&mut phrase.notes);
phrase.notes = notes;
phrase.length = phrase.length * 2;
});
//app.arranger.show_phrase()?;
Ok(true)
}],
[Char('l'), NONE, "loop_toggle", "toggle looping", |_app: &mut App| {
// TODO: This toggles the loop flag for the clip under the cursor.
Ok(true)
}],
[Char('['), NONE, "loop_start_dec", "move loop start back", |_app: &mut App| {
// TODO: This moves the loop start to the previous quant.
Ok(true)
}],
[Char(']'), NONE, "loop_start_inc", "move loop start forward", |_app: &mut App| {
// TODO: This moves the loop start to the next quant.
Ok(true)
}],
[Char('{'), NONE, "loop_end_dec", "move loop end back", |_app: &mut App| {
// TODO: This moves the loop end to the previous quant.
Ok(true)
}],
[Char('}'), NONE, "loop_end_inc", "move loop end forward", |_app: &mut App| {
// TODO: This moves the loop end to the next quant.
Ok(true)
}],
[Char('a'), CONTROL, "scene_add", "add a new scene", |app: &mut App| {
app.arranger.scene_add(None)?;
Ok(true)
}],
[Char('t'), CONTROL, "track_add", "add a new track", |app: &mut App| {
app.arranger.track_add(None)?;
Ok(true)
}],
});
/// Generic key bindings for views that support focus.
pub const KEYMAP_FOCUS: &'static [KeyBinding<App>] = keymap!(App {
[Char(';'), NONE, "command", "open command palette", |_: &mut App| {
*MODAL.lock().unwrap() = Some(Box::new(HelpModal::new()));
Ok(true)
}],
[Tab, NONE, "focus_next", "focus next area", focus_next],
[Tab, SHIFT, "focus_prev", "focus previous area", focus_prev],
[Esc, NONE, "focus_exit", "unfocus", |app: &mut App|{
app.entered = false;
app.transport.entered = app.entered;
//app.arranger.entered = app.entered;
app.arranger.sequencer_mut().map(|s|s.entered = app.entered);
Ok(true)
}],
[Enter, NONE, "focus_enter", "activate item at cursor", |app: &mut App|{
app.entered = true;
app.transport.entered = app.entered;
//app.arranger.entered = app.entered;
app.arranger.sequencer_mut().map(|s|s.entered = app.entered);
Ok(true)
}],
});
pub fn focus_next (app: &mut App) -> Usually<bool> {
app.section.next();
app.transport.focused = app.section == AppFocus::Transport;
app.transport.entered = app.entered;
//app.arranger.focused = app.section == AppFocus::Arranger;
//app.arranger.entered = app.entered;
app.arranger.sequencer_mut().map(|s|{
s.focused = app.section == AppFocus::Sequencer;
s.entered = app.entered;
});
Ok(true)
}
pub fn focus_prev (app: &mut App) -> Usually<bool> {
app.section.prev();
app.transport.focused = app.section == AppFocus::Transport;
app.transport.entered = app.entered;
//app.arranger.focused = app.section == AppFocus::Arranger;
//app.arranger.entered = app.entered;
app.arranger.sequencer_mut().map(|s|{
s.focused = app.section == AppFocus::Sequencer;
s.entered = app.entered;
});
Ok(true)
}

View file

@ -1,99 +0,0 @@
//! Project file format.
//!
//! This module `impl`s the `from_edn`, `load_edn`, etc. methods
//! of structs that are defined in other modules. See:
//!
//! * [App::from_edn]
//! * [App::load_edn]
//! * [App::load_edn_one]
//! * [Scene::load_edn]
//! * [Track::load_edn]
//! * [Phrase::load_edn]
//! * [Sampler::load_edn]
//! * [Sample::load_edn]
//! * [LV2Plugin::load_edn]
use crate::*;
impl App {
pub fn from_edn (src: &str) -> Usually<Self> {
let mut app = Self::new()?;
app.load_edn(src)?;
Ok(app)
}
pub fn load_edn (&mut self, mut src: &str) -> Usually<&mut Self> {
loop {
match read(src) {
Ok((edn, rest)) => {
self.load_edn_one(edn)?;
if rest.len() > 0 {
src = rest;
} else {
break
}
},
Err(EdnError { ptr: None, .. }) => {
break
},
Err(e) => {
panic!("{e:?}");
},
}
}
Ok(self)
}
fn load_edn_one <'e> (&mut self, edn: Edn<'e>) -> Usually<()> {
match edn {
Edn::List(items) => {
match items.get(0) {
Some(Edn::Symbol("bpm")) => {
match items.get(1) {
Some(Edn::Int(b)) =>
self.transport.timebase.set_bpm(*b as f64),
Some(Edn::Double(b)) =>
self.transport.timebase.set_bpm(f64::from(*b)),
_ => panic!("unspecified bpm")
}
},
Some(Edn::Symbol("scene")) => {
tek_sequencer::Scene::from_edn(&items[1..])?;
},
Some(Edn::Symbol("track")) => {
tek_mixer::Track::from_edn(&items[1..])?;
},
Some(Edn::Symbol("midi-in")) => {
self.midi_ins = items[1..].iter().map(|x|match x {
Edn::Str(n) => n.to_string(),
_ => panic!("unexpected midi-in")
}).collect::<Vec<_>>();
},
Some(Edn::Symbol("audio-out")) => {
let client = self.client();
self.audio_outs = items[1..].iter().map(|x|match x {
Edn::Str(n) => n.to_string(),
_ => panic!("unexpected midi-in")
}).collect::<Vec<_>>()
.iter()
.map(|name|client
.ports(Some(name), None, PortFlags::empty())
.get(0)
.map(|name|client.port_by_name(name)))
.flatten()
.filter_map(|x|x)
.map(Arc::new)
.collect();
},
_ => panic!("unexpected edn: {:?}", items.get(0))
}
},
_ => {
panic!("unexpected edn: {edn:?}");
}
}
Ok(())
}
}

View file

@ -1,96 +0,0 @@
//! Help modal / command palette.
use crate::*;
/// Command palette.
pub struct HelpModal {
cursor: usize,
search: Option<String>,
exited: bool,
}
impl HelpModal {
pub fn new () -> Self {
Self { cursor: 0, search: None, exited: false }
}
}
impl Exit for HelpModal {
fn exited (&self) -> bool {
self.exited
}
fn exit (&mut self) {
self.exited = true
}
}
render!(HelpModal |self, buf, area|{
make_dim(buf);
let area = center_box(area, 64, 20);
fill_fg(buf, area, Color::Reset);
fill_bg(buf, area, Nord::bg_lo(true, true));
fill_char(buf, area, ' ');
let x = area.x + 2;
let y = area.y + 1;
"Command:"
.blit(buf, x, y, Some(Style::default().bold()))?;
" ".repeat(area.width as usize - 13)
.blit(buf, x + 9, y, Some(Style::default().bg(Color::Reset)))?;
if let Some(search) = self.search.as_ref() {
search.blit(buf, x + 9, y, Some(Style::default().not_dim()))?;
}
let y = y + 1;
fill_char(buf, Rect { y, height: 1, ..area }, '-');
let y = y + 1;
for i in 0..area.height-3 {
let y = y + i;
if let Some(command) = crate::control::KEYMAP_FOCUS.get(i as usize) {
format!("{:?}", command.0).blit(buf, x, y, Some(Style::default().white().bold()))?;
command.2.blit(buf, x + 11, y, Some(Style::default().white().bold()))?;
command.3.blit(buf, x + 26, y, Some(Style::default().white().dim()))?;
} else if let Some(command) = crate::control::KEYMAP_GLOBAL.get((i as usize) - crate::control::KEYMAP_FOCUS.len()) {
format!("{:?}", command.0).blit(buf, x, y, Some(Style::default().white().bold()))?;
command.2.blit(buf, x + 11, y, Some(Style::default().white().bold()))?;
command.3.blit(buf, x + 26, y, Some(Style::default().white().dim()))?;
} else {
break
}
}
let hi_area = Rect { x: area.x + 1, width: area.width - 2, y: area.y + 3 + self.cursor as u16, height: 1 };
fill_bg(buf, hi_area, Nord::bg_hi(true, true));
fill_fg(buf, hi_area, Color::White);
Lozenge(Style::default()).draw(buf, area)
});
handle!(HelpModal |self, e| {
if handle_keymap(self, e, KEYMAP_HELP)? {
return Ok(true)
}
Ok(match e {
AppEvent::Input(Event::Key(KeyEvent {
code: KeyCode::Char(c),
modifiers: KeyModifiers::NONE, ..
})) => {
if self.search.is_none() {
self.search = Some(String::new());
}
self.search.as_mut().unwrap().push(*c);
true
},
_ => true
})
});
pub const KEYMAP_HELP: &'static [KeyBinding<HelpModal>] = keymap!(HelpModal {
[Esc, NONE, "help_close", "close help dialog", |modal: &mut HelpModal|{
modal.exit();
Ok(true)
}],
[Up, NONE, "help_prev", "select previous command", |modal: &mut HelpModal|{
modal.cursor = modal.cursor.saturating_sub(1);
Ok(true)
}],
[Down, NONE, "help_next", "select next command", |modal: &mut HelpModal|{
modal.cursor = modal.cursor + 1;
Ok(true)
}],
});

View file

@ -1,65 +0,0 @@
//! ***Tek*** is a MIDI sequencer, sampler, and audio plugin host for the Linux terminal.
//#[global_allocator]
//static A: rlsf::SmallGlobalTlsf = rlsf::SmallGlobalTlsf::new();
//#![feature(fn_traits)]
//#![feature(unboxed_closures)]
#![allow(macro_expanded_macro_exports_accessed_by_absolute_paths)]
#![allow(ambiguous_glob_reexports)]
pub(crate) use tek_core::{*, jack::*};
pub(crate) use tek_sequencer::*;
pub(crate) use tek_mixer::*;
pub(crate) use microxdg::XdgApp;
submod! {
app
app_focus
app_paths
cli
control
edn
help
setup
}
/// Global modal dialog
pub static MODAL: Lazy<Arc<Mutex<Option<Box<dyn ExitableComponent>>>>> =
Lazy::new(||Arc::new(Mutex::new(None)));
/// Application entrypoint.
pub fn main () -> Usually<()> {
run(App::from_edn(include_str!("../example.edn"))?
.activate(Some(|app: &Arc<RwLock<App>>|Ok({
let (midi_in, mut midi_outs) = {
let app = app.read().unwrap();
let jack = app.jack.as_ref().unwrap();
let midi_in = jack.register_port("midi-in", MidiIn)?;
let midi_outs = app.arranger.tracks.iter()
.map(|t|Some(jack.register_port(&t.name.read().unwrap(), MidiOut).unwrap()))
.collect::<Vec<_>>();
(midi_in, midi_outs)
};
{
let mut app = app.write().unwrap();
let jack = app.jack.as_ref().unwrap();
for name in app.midi_ins.iter() {
let ports = jack.client().ports(Some(name), None, PortFlags::empty());
for port in ports.iter() {
if let Some(port) = jack.client().port_by_name(port) {
jack.client().connect_ports(&port, &midi_in)?;
}
}
}
app.midi_in = Some(Arc::new(midi_in));
for (index, track) in app.arranger.tracks.iter_mut().enumerate() {
track.midi_out = midi_outs[index].take();
}
//for track in app.arranger.tracks.iter() {
//track.connect_first_device()?;
//track.connect_last_device(&app)?;
//}
};
})))?)?;
Ok(())
}

View file

@ -1,55 +0,0 @@
//! Inital setup dialog (TODO: make this the options dialog too?)
use crate::*;
/// Appears on first run (i.e. if state dir is missing).
pub struct SetupModal(pub Option<Arc<XdgApp>>, pub bool);
render!(SetupModal |self, buf, area| {
for cell in buf.content.iter_mut() {
cell.fg = ratatui::style::Color::Gray;
cell.modifier = ratatui::style::Modifier::DIM;
}
let lines = [
(" ", Style::default().white().on_black().not_dim().bold()),
(" Welcome to TEK! ", Style::default().white().on_black().not_dim().bold()),
(" ", Style::default().white().on_black().not_dim().bold()),
(" Press ENTER to create the ", Style::default().white().on_black().not_dim()),
(" following directories: ", Style::default().white().on_black().not_dim()),
(" ", Style::default().white().on_black().not_dim().bold()),
(" Configuration directory: ", Style::default().white().on_black().not_dim()),
(" ~/.config/tek ", Style::default().white().on_black().not_dim().bold()),
(" ", Style::default().white().on_black().not_dim()),
(" Data directory: ", Style::default().white().on_black().not_dim()),
(" ~/.local/share/tek ", Style::default().white().on_black().not_dim().bold()),
(" ", Style::default().white().on_black().not_dim().bold()),
(" Or press CTRL-C to exit. ", Style::default().white().on_black().not_dim()),
(" ", Style::default().white().on_black().not_dim()),
];
let width = lines[0].0.len() as u16;
let x = area.x + (area.width - width) / 2;
for (i, (line, style)) in lines.iter().enumerate() {
line.blit(buf, x, area.y + area.height / 2 - (lines.len() / 2) as u16 + i as u16, Some(*style))?;
}
Ok(area)
});
handle!(SetupModal |self, e| {
if let AppEvent::Input(Event::Key(KeyEvent {
code: KeyCode::Enter,
..
})) = e {
AppPaths::new(&self.0.as_ref().unwrap())?.create()?;
self.exit();
Ok(true)
} else {
Ok(false)
}
});
impl Exit for SetupModal {
fn exited (&self) -> bool {
self.1
}
fn exit (&mut self) {
self.1 = true
}
}

View file

@ -1,31 +0,0 @@
[package]
name = "tek"
edition = "2021"
version = "0.2.0"
[dependencies]
#no_deadlocks = "1.3.2"
#vst3 = "0.1.0"
atomic_float = "1.0.0"
backtrace = "0.3.72"
better-panic = "0.3.0"
clojure-reader = "0.1.0"
crossterm = "0.27"
jack = { path = "../../rust-jack" }
livi = "0.7.4"
midly = "0.5"
once_cell = "1.19.0"
palette = { version = "0.7.6", features = [ "random" ] }
quanta = "0.12.3"
rand = "0.8.5"
ratatui = { version = "0.26.3", features = [ "unstable-widget-ref", "underline-color" ] }
#suil-rs = { path = "../suil" }
symphonia = { version = "0.5.4", features = [ "all" ] }
toml = "0.8.12"
uuid = { version = "1.10.0", features = [ "v4" ] }
#vst = "0.4.0"
wavers = "1.4.3"
#winit = { version = "0.30.4", features = [ "x11" ] }
[lib]
path = "src/lib.rs"

View file

@ -1,65 +0,0 @@
# `tek`
This crate implements several musical utilities.
## `tek_sequencer`
A single-track, multi-pattern MIDI sequencer with properly tempo-synced pattern switch.
---
## `tek_arranger`
A multi-track, multi-pattern MIDI sequencer translating the familiar clip launching workflow
into the TUI medium.
---
## `tek_groovebox`
TODO: A single-track, multi-pattern MIDI sequencer,
attached to a sampler or plugin (see `tek_plugin`).
---
## `tek_timer`
TODO: This crate implements time sync and JACK transport control.
* Warning: If transport is set rolling by qjackctl, this program can't pause it
* Todo: bpm: shift +/- 0.001
* Todo: quant/sync: shift = next/prev value of same type (normal, triplet, dotted)
* Or: use shift to switch between inc/dec top/bottom value?
* Todo: focus play button
* Todo: focus time position
* Todo: edit numeric values
* Todo: jump to time/bbt markers
* Todo: count xruns
---
## `tek_mixer`
TODO:
// - Meters: propagate clipping:
// - If one stage clips, all stages after it are marked red
// - If one track clips, all tracks that feed from it are marked red?
---
## `tek_track`
TODO:
---
## `tek_sampler`
TODO: This crate implements a sampler device which plays audio files
in response to MIDI notes.
---
## `tek_plugin`
TODO:

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 25 KiB

View file

@ -1,144 +0,0 @@
use tek::*;
fn main () -> Usually<()> {
Tui::run(Arc::new(RwLock::new(Demo::new())))?;
Ok(())
}
pub struct Demo<E: Engine> {
index: usize,
items: Vec<Box<dyn Render<Engine = E>>>
}
impl Demo<Tui> {
fn new () -> Self {
Self {
index: 0,
items: vec![
//Box::new(tek_sequencer::TransportPlayPauseButton {
//_engine: Default::default(),
//transport: None,
//value: Some(TransportState::Stopped),
//focused: true
//}),
//Box::new(tek_sequencer::TransportPlayPauseButton {
//_engine: Default::default(),
//transport: None,
//value: Some(TransportState::Rolling),
//focused: false
//}),
]
}
}
}
impl Content for Demo<Tui> {
type Engine = Tui;
fn content (&self) -> dyn Render<Engine = Tui> {
let border_style = Style::default().fg(Color::Rgb(0,0,0));
Align::Center(Layers::new(move|add|{
add(&Background(Color::Rgb(0,128,128)))?;
add(&Outset::XY(1, 1, Stack::down(|add|{
add(&Layers::new(|add|{
add(&Background(Color::Rgb(128,96,0)))?;
add(&Border(Square(border_style)))?;
add(&Outset::XY(2, 1, "..."))?;
Ok(())
}).debug())?;
add(&Layers::new(|add|{
add(&Background(Color::Rgb(128,64,0)))?;
add(&Border(Lozenge(border_style)))?;
add(&Outset::XY(4, 2, "---"))?;
Ok(())
}).debug())?;
add(&Layers::new(|add|{
add(&Background(Color::Rgb(96,64,0)))?;
add(&Border(SquareBold(border_style)))?;
add(&Outset::XY(6, 3, "~~~"))?;
Ok(())
}).debug())?;
Ok(())
})).debug())?;
Ok(())
}))
//Align::Center(Outset::X(1, Layers::new(|add|{
//add(&Background(Color::Rgb(128,0,0)))?;
//add(&Stack::down(|add|{
//add(&Outset::Y(1, Layers::new(|add|{
//add(&Background(Color::Rgb(0,128,0)))?;
//add(&Align::Center("12345"))?;
//add(&Align::Center("FOO"))
//})))?;
//add(&Outset::XY(1, 1, Layers::new(|add|{
//add(&Align::Center("1234567"))?;
//add(&Align::Center("BAR"))?;
//add(&Background(Color::Rgb(0,0,128)))
//})))
//}))
//})))
//Align::Y(Layers::new(|add|{
//add(&Background(Color::Rgb(128,0,0)))?;
//add(&Outset::X(1, Align::Center(Stack::down(|add|{
//add(&Align::X(Outset::Y(1, Layers::new(|add|{
//add(&Background(Color::Rgb(0,128,0)))?;
//add(&Align::Center("12345"))?;
//add(&Align::Center("FOO"))
//})))?;
//add(&Outset::XY(1, 1, Layers::new(|add|{
//add(&Align::Center("1234567"))?;
//add(&Align::Center("BAR"))?;
//add(&Background(Color::Rgb(0,0,128)))
//})))?;
//Ok(())
//})))))
//}))
}
}
impl Handle<Tui> for Demo<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
use KeyCode::{PageUp, PageDown};
match from.event() {
key_expr!(PageUp) => {
self.index = (self.index + 1) % self.items.len();
},
key_expr!(PageDown) => {
self.index = if self.index > 1 {
self.index - 1
} else {
self.items.len() - 1
};
},
_ => return Ok(None)
}
Ok(Some(true))
}
}
//lisp!(CONTENT Demo (LET
//(BORDER-STYLE (STYLE (FG (RGB 0 0 0))))
//(BG-COLOR-0 (RGB 0 128 128))
//(BG-COLOR-1 (RGB 128 96 0))
//(BG-COLOR-2 (RGB 128 64 0))
//(BG-COLOR-3 (RGB 96 64 0))
//(CENTER (LAYERS
//(BACKGROUND BG-COLOR-0)
//(OUTSET-XY 1 1 (SPLIT-DOWN
//(LAYERS (BACKGROUND BG-COLOR-1)
//(BORDER SQUARE BORDER-STYLE)
//(OUTSET-XY 2 1 "..."))
//(LAYERS (BACKGROUND BG-COLOR-2)
//(BORDER LOZENGE BORDER-STYLE)
//(OUTSET-XY 4 2 "---"))
//(LAYERS (BACKGROUND BG-COLOR-3)
//(BORDER SQUARE-BOLD BORDER-STYLE)
//(OUTSET-XY 2 1 "~~~"))))))))

View file

@ -1,19 +0,0 @@
use tek::*;
use std::sync::{Arc, RwLock};
fn main () -> Usually<()> {
Tui::run(Arc::new(RwLock::new(BspDemo(Default::default()))))?;
Ok(())
}
pub struct BspDemo<E: Engine>(std::marker::PhantomData<E>);
render!(<Tui>|self:BspDemo<Tui>|Fill::wh(Align::c(
Bsp::n(Bsp::s(Bsp::e(Bsp::w("00", "11"), "22"), "33"), "44")
)));
impl Handle<Tui> for BspDemo<Tui> {
fn handle (&mut self, from: &TuiInput) -> Perhaps<bool> {
Ok(None)
}
}

View file

@ -1,18 +0,0 @@
use tek::*;
struct ExamplePhrases(Vec<Arc<RwLock<Phrase>>>);
impl HasPhrases for ExamplePhrases {
fn phrases (&self) -> &Vec<Arc<RwLock<Phrase>>> {
&self.0
}
fn phrases_mut (&mut self) -> &mut Vec<Arc<RwLock<Phrase>>> {
&mut self.0
}
}
fn main () -> Usually<()> {
let mut phrases = ExamplePhrases(vec![]);
PhrasePoolCommand::Import(0, String::from("./example.mid")).execute(&mut phrases)?;
Ok(())
}

View file

@ -1,12 +0,0 @@
(mixer
(track
(name "Drums")
(sampler
(dir "/home/user/Lab/Music/pak")
(sample (midi 34) (name "808 D") (file "808.wav"))))
(track
(name "Lead")
(lv2
(name "Odin2")
(path "file:///home/user/.lv2/Odin2.lv2"))
(gain 0.0)))

View file

@ -1,18 +0,0 @@
(arranger
(track
(name "Drums")
(phrase
(name "4 kicks")
(beats 4)
(steps 16)
(:00 (36 128))
(:04 (36 100))
(:08 (36 100))
(:12 (36 100))))
(track
(name "Bass")
(phrase
(beats 4)
(steps 16)
(:04 (36 100))
(:12 (36 100)))))

View file

@ -1 +0,0 @@
mod sampler; pub(crate) use sampler::*;

View file

@ -1,162 +0,0 @@
use crate::*;
mod arranger_command; pub(crate) use self::arranger_command::*;
mod arranger_scene; pub(crate) use self::arranger_scene::*;
mod arranger_select; pub(crate) use self::arranger_select::*;
mod arranger_track; pub(crate) use self::arranger_track::*;
mod arranger_mode; pub(crate) use self::arranger_mode::*;
mod arranger_v; #[allow(unused)] pub(crate) use self::arranger_v::*;
mod arranger_h;
/// Root view for standalone `tek_arranger`
pub struct ArrangerTui {
jack: Arc<RwLock<JackClient>>,
pub clock: ClockModel,
pub phrases: PoolModel,
pub tracks: Vec<ArrangerTrack>,
pub scenes: Vec<ArrangerScene>,
pub splits: [u16;2],
pub selected: ArrangerSelection,
pub mode: ArrangerMode,
pub color: ItemPalette,
pub size: Measure<Tui>,
pub note_buf: Vec<u8>,
pub midi_buf: Vec<Vec<Vec<u8>>>,
pub editor: MidiEditorModel,
pub perf: PerfModel,
}
impl ArrangerTui {
pub fn selected (&self) -> ArrangerSelection {
self.selected
}
pub fn selected_mut (&mut self) -> &mut ArrangerSelection {
&mut self.selected
}
pub fn activate (&mut self) -> Usually<()> {
if let ArrangerSelection::Scene(s) = self.selected {
for (t, track) in self.tracks.iter_mut().enumerate() {
let phrase = self.scenes[s].clips[t].clone();
if track.player.play_phrase.is_some() || phrase.is_some() {
track.player.enqueue_next(phrase.as_ref());
}
}
if self.clock().is_stopped() {
self.clock().play_from(Some(0))?;
}
} else if let ArrangerSelection::Clip(t, s) = self.selected {
let phrase = self.scenes[s].clips[t].clone();
self.tracks[t].player.enqueue_next(phrase.as_ref());
};
Ok(())
}
pub fn selected_phrase (&self) -> Option<Arc<RwLock<MidiClip>>> {
self.selected_scene()?.clips.get(self.selected.track()?)?.clone()
}
pub fn toggle_loop (&mut self) {
if let Some(phrase) = self.selected_phrase() {
phrase.write().unwrap().toggle_loop()
}
}
pub fn randomize_color (&mut self) {
match self.selected {
ArrangerSelection::Mix => { self.color = ItemPalette::random() },
ArrangerSelection::Track(t) => { self.tracks[t].color = ItemPalette::random() },
ArrangerSelection::Scene(s) => { self.scenes[s].color = ItemPalette::random() },
ArrangerSelection::Clip(t, s) => if let Some(phrase) = &self.scenes[s].clips[t] {
phrase.write().unwrap().color = ItemPalette::random();
}
}
}
}
from_jack!(|jack| ArrangerTui {
let clock = ClockModel::from(jack);
let phrase = Arc::new(RwLock::new(MidiClip::new(
"New", true, 4 * clock.timebase.ppq.get() as usize,
None, Some(ItemColor::random().into())
)));
Self {
clock,
phrases: (&phrase).into(),
editor: (&phrase).into(),
selected: ArrangerSelection::Clip(0, 0),
scenes: vec![],
tracks: vec![],
color: TuiTheme::bg().into(),
mode: ArrangerMode::V(1),
size: Measure::new(),
splits: [12, 20],
midi_buf: vec![vec![];65536],
note_buf: vec![],
perf: PerfModel::default(),
jack: jack.clone(),
}
});
impl ArrangerTui {
fn render_mode (state: &Self) -> impl Render<Tui> + use<'_> {
match state.mode {
ArrangerMode::H => todo!("horizontal arranger"),
ArrangerMode::V(factor) => Self::render_mode_v(state, factor),
}
}
}
render!(<Tui>|self: ArrangerTui|{
let play = Fixed::wh(5, 2, PlayPause(self.clock.is_rolling()));
let transport = TransportView::from((self, Some(ItemPalette::from(TuiTheme::g(96))), true));
let with_transport = |x|col!([row!(![&play, &transport]), &x]);
let pool_size = if self.phrases.visible { self.splits[1] } else { 0 };
let with_pool = |x|Split::left(false, pool_size, PoolView(&self.phrases), x);
let status = ArrangerStatus::from(self);
let with_editbar = |x|Tui::split_n(false, 1, MidiEditStatus(&self.editor), x);
let with_status = |x|Tui::split_n(false, 2, status, x);
let with_size = |x|lay!([&self.size, x]);
let arranger = ||lay!(|add|{
let color = self.color;
add(&Fill::wh(Tui::bg(color.darkest.rgb, ())))?;
add(&Fill::wh(Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb))))?;
add(&Self::render_mode(self))
});
with_size(with_status(with_editbar(with_pool(with_transport(col!([
Fill::w(Fixed::h(20, arranger())),
Fill::wh(&self.editor),
]))))))
});
audio!(|self: ArrangerTui, client, scope|{
// Start profiling cycle
let t0 = self.perf.get_t0();
// Update transport clock
if Control::Quit == ClockAudio(self).process(client, scope) {
return Control::Quit
}
// Update MIDI sequencers
let tracks = &mut self.tracks;
let note_buf = &mut self.note_buf;
let midi_buf = &mut self.midi_buf;
if Control::Quit == TracksAudio(tracks, note_buf, midi_buf).process(client, scope) {
return Control::Quit
}
// FIXME: one of these per playing track
//self.now.set(0.);
//if let ArrangerSelection::Clip(t, s) = self.selected {
//let phrase = self.scenes.get(s).map(|scene|scene.clips.get(t));
//if let Some(Some(Some(phrase))) = phrase {
//if let Some(track) = self.tracks().get(t) {
//if let Some((ref started_at, Some(ref playing))) = track.player.play_phrase {
//let phrase = phrase.read().unwrap();
//if *playing.read().unwrap() == *phrase {
//let pulse = self.current().pulse.get();
//let start = started_at.pulse.get();
//let now = (pulse - start) % phrase.length as f64;
//self.now.set(now);
//}
//}
//}
//}
//}
// End profiling cycle
self.perf.update(t0, scope);
return Control::Continue
});
has_clock!(|self: ArrangerTui|&self.clock);
has_phrases!(|self: ArrangerTui|self.phrases.phrases);
has_editor!(|self: ArrangerTui|self.editor);
handle!(<Tui>|self: ArrangerTui, input|ArrangerCommand::execute_with_state(self, input));

View file

@ -1,339 +0,0 @@
use crate::*;
use ClockCommand::{Play, Pause};
use KeyCode::{Char, Delete, Tab, Up, Down, Left, Right};
#[derive(Clone, Debug)] pub enum ArrangerCommand {
History(isize),
Color(ItemPalette),
Clock(ClockCommand),
Scene(ArrangerSceneCommand),
Track(ArrangerTrackCommand),
Clip(ArrangerClipCommand),
Select(ArrangerSelection),
Zoom(usize),
Phrases(PoolCommand),
Editor(PhraseCommand),
StopAll,
Clear,
}
#[derive(Clone, Debug)]
pub enum ArrangerTrackCommand {
Add,
Delete(usize),
Stop(usize),
Swap(usize, usize),
SetSize(usize),
SetZoom(usize),
SetColor(usize, ItemPalette),
}
#[derive(Clone, Debug)]
pub enum ArrangerSceneCommand {
Enqueue(usize),
Add,
Delete(usize),
Swap(usize, usize),
SetSize(usize),
SetZoom(usize),
SetColor(usize, ItemPalette),
}
#[derive(Clone, Debug)]
pub enum ArrangerClipCommand {
Get(usize, usize),
Put(usize, usize, Option<Arc<RwLock<MidiClip>>>),
Enqueue(usize, usize),
Edit(Option<Arc<RwLock<MidiClip>>>),
SetLoop(usize, usize, bool),
SetColor(usize, usize, ItemPalette),
}
input_to_command!(ArrangerCommand: <Tui>|state: ArrangerTui, input|match input.event() {
key_pat!(Char('u')) => Self::History(-1),
key_pat!(Char('U')) => Self::History(1),
// TODO: k: toggle on-screen keyboard
key_pat!(Ctrl-Char('k')) => { todo!("keyboard") },
// Transport: Play/pause
key_pat!(Char(' ')) =>
Self::Clock(if state.clock().is_stopped() { Play(None) } else { Pause(None) }),
// Transport: Play from start or rewind to start
key_pat!(Shift-Char(' ')) =>
Self::Clock(if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }),
key_pat!(Char('e')) =>
Self::Editor(PhraseCommand::Show(Some(state.phrases.phrase().clone()))),
key_pat!(Ctrl-Left) =>
Self::Scene(ArrangerSceneCommand::Add),
key_pat!(Ctrl-Char('t')) =>
Self::Track(ArrangerTrackCommand::Add),
// Tab: Toggle visibility of phrase pool column
key_pat!(Tab) =>
Self::Phrases(PoolCommand::Show(!state.phrases.visible)),
_ => {
use ArrangerCommand as Cmd;
use ArrangerSelection as Selected;
use ArrangerSceneCommand as Scene;
use ArrangerTrackCommand as Track;
use ArrangerClipCommand as Clip;
let t_len = state.tracks.len();
let s_len = state.scenes.len();
match state.selected() {
Selected::Clip(t, s) => match input.event() {
key_pat!(Char('g')) => Some(Cmd::Phrases(PoolCommand::Select(0))),
key_pat!(Char('q')) => Some(Cmd::Clip(Clip::Enqueue(t, s))),
key_pat!(Char(',')) => Some(Cmd::Clip(Clip::Put(t, s, None))),
key_pat!(Char('.')) => Some(Cmd::Clip(Clip::Put(t, s, None))),
key_pat!(Char('<')) => Some(Cmd::Clip(Clip::Put(t, s, None))),
key_pat!(Char('>')) => Some(Cmd::Clip(Clip::Put(t, s, None))),
key_pat!(Char('p')) => Some(Cmd::Clip(Clip::Put(t, s, Some(state.phrases.phrase().clone())))),
key_pat!(Char('l')) => Some(Cmd::Clip(ArrangerClipCommand::SetLoop(t, s, false))),
key_pat!(Delete) => Some(Cmd::Clip(Clip::Put(t, s, None))),
key_pat!(Up) => Some(Cmd::Select(
if s > 0 { Selected::Clip(t, s - 1) } else { Selected::Track(t) })),
key_pat!(Down) => Some(Cmd::Select(
Selected::Clip(t, (s + 1).min(s_len.saturating_sub(1))))),
key_pat!(Left) => Some(Cmd::Select(
if t > 0 { Selected::Clip(t - 1, s) } else { Selected::Scene(s) })),
key_pat!(Right) => Some(Cmd::Select(
Selected::Clip((t + 1).min(t_len.saturating_sub(1)), s))),
_ => None
},
Selected::Scene(s) => match input.event() {
key_pat!(Char(',')) => Some(Cmd::Scene(Scene::Swap(s, s - 1))),
key_pat!(Char('.')) => Some(Cmd::Scene(Scene::Swap(s, s + 1))),
key_pat!(Char('<')) => Some(Cmd::Scene(Scene::Swap(s, s - 1))),
key_pat!(Char('>')) => Some(Cmd::Scene(Scene::Swap(s, s + 1))),
key_pat!(Char('q')) => Some(Cmd::Scene(Scene::Enqueue(s))),
key_pat!(Delete) => Some(Cmd::Scene(Scene::Delete(s))),
key_pat!(Char('c')) => Some(Cmd::Scene(Scene::SetColor(s, ItemPalette::random()))),
key_pat!(Up) => Some(
Cmd::Select(if s > 0 { Selected::Scene(s - 1) } else { Selected::Mix })),
key_pat!(Down) => Some(
Cmd::Select(Selected::Scene((s + 1).min(s_len.saturating_sub(1))))),
key_pat!(Left) =>
return None,
key_pat!(Right) => Some(
Cmd::Select(Selected::Clip(0, s))),
_ => None
},
Selected::Track(t) => match input.event() {
key_pat!(Char(',')) => Some(Cmd::Track(Track::Swap(t, t - 1))),
key_pat!(Char('.')) => Some(Cmd::Track(Track::Swap(t, t + 1))),
key_pat!(Char('<')) => Some(Cmd::Track(Track::Swap(t, t - 1))),
key_pat!(Char('>')) => Some(Cmd::Track(Track::Swap(t, t + 1))),
key_pat!(Delete) => Some(Cmd::Track(Track::Delete(t))),
key_pat!(Char('c')) => Some(Cmd::Track(Track::SetColor(t, ItemPalette::random()))),
key_pat!(Up) =>
return None,
key_pat!(Down) => Some(
Cmd::Select(Selected::Clip(t, 0))),
key_pat!(Left) => Some(
Cmd::Select(if t > 0 { Selected::Track(t - 1) } else { Selected::Mix })),
key_pat!(Right) => Some(
Cmd::Select(Selected::Track((t + 1).min(t_len.saturating_sub(1))))),
_ => None
},
Selected::Mix => match input.event() {
key_pat!(Delete) => Some(Cmd::Clear),
key_pat!(Char('0')) => Some(Cmd::StopAll),
key_pat!(Char('c')) => Some(Cmd::Color(ItemPalette::random())),
key_pat!(Up) =>
return None,
key_pat!(Down) => Some(
Cmd::Select(Selected::Scene(0))),
key_pat!(Left) =>
return None,
key_pat!(Right) => Some(
Cmd::Select(Selected::Track(0))),
_ => None
},
}
}.or_else(||if let Some(command) = PhraseCommand::input_to_command(&state.editor, input) {
Some(Self::Editor(command))
} else if let Some(command) = PoolCommand::input_to_command(&state.phrases, input) {
Some(Self::Phrases(command))
} else {
None
})?
});
fn to_arrangement_command (state: &ArrangerTui, input: &TuiInput) -> Option<ArrangerCommand> {
use ArrangerCommand as Cmd;
use ArrangerSelection as Selected;
use ArrangerSceneCommand as Scene;
use ArrangerTrackCommand as Track;
use ArrangerClipCommand as Clip;
let t_len = state.tracks.len();
let s_len = state.scenes.len();
match state.selected() {
Selected::Clip(t, s) => match input.event() {
key_pat!(Char('g')) => Some(Cmd::Phrases(PoolCommand::Select(0))),
key_pat!(Char('q')) => Some(Cmd::Clip(Clip::Enqueue(t, s))),
key_pat!(Char(',')) => Some(Cmd::Clip(Clip::Put(t, s, None))),
key_pat!(Char('.')) => Some(Cmd::Clip(Clip::Put(t, s, None))),
key_pat!(Char('<')) => Some(Cmd::Clip(Clip::Put(t, s, None))),
key_pat!(Char('>')) => Some(Cmd::Clip(Clip::Put(t, s, None))),
key_pat!(Char('p')) => Some(Cmd::Clip(Clip::Put(t, s, Some(state.phrases.phrase().clone())))),
key_pat!(Char('l')) => Some(Cmd::Clip(ArrangerClipCommand::SetLoop(t, s, false))),
key_pat!(Delete) => Some(Cmd::Clip(Clip::Put(t, s, None))),
key_pat!(Up) => Some(Cmd::Select(
if s > 0 { Selected::Clip(t, s - 1) } else { Selected::Track(t) })),
key_pat!(Down) => Some(Cmd::Select(
Selected::Clip(t, (s + 1).min(s_len.saturating_sub(1))))),
key_pat!(Left) => Some(Cmd::Select(
if t > 0 { Selected::Clip(t - 1, s) } else { Selected::Scene(s) })),
key_pat!(Right) => Some(Cmd::Select(
Selected::Clip((t + 1).min(t_len.saturating_sub(1)), s))),
_ => None
},
Selected::Scene(s) => match input.event() {
key_pat!(Char(',')) => Some(Cmd::Scene(Scene::Swap(s, s - 1))),
key_pat!(Char('.')) => Some(Cmd::Scene(Scene::Swap(s, s + 1))),
key_pat!(Char('<')) => Some(Cmd::Scene(Scene::Swap(s, s - 1))),
key_pat!(Char('>')) => Some(Cmd::Scene(Scene::Swap(s, s + 1))),
key_pat!(Char('q')) => Some(Cmd::Scene(Scene::Enqueue(s))),
key_pat!(Delete) => Some(Cmd::Scene(Scene::Delete(s))),
key_pat!(Char('c')) => Some(Cmd::Scene(Scene::SetColor(s, ItemPalette::random()))),
key_pat!(Up) => Some(
Cmd::Select(if s > 0 { Selected::Scene(s - 1) } else { Selected::Mix })),
key_pat!(Down) => Some(
Cmd::Select(Selected::Scene((s + 1).min(s_len.saturating_sub(1))))),
key_pat!(Left) =>
None,
key_pat!(Right) => Some(
Cmd::Select(Selected::Clip(0, s))),
_ => None
},
Selected::Track(t) => match input.event() {
key_pat!(Char(',')) => Some(Cmd::Track(Track::Swap(t, t - 1))),
key_pat!(Char('.')) => Some(Cmd::Track(Track::Swap(t, t + 1))),
key_pat!(Char('<')) => Some(Cmd::Track(Track::Swap(t, t - 1))),
key_pat!(Char('>')) => Some(Cmd::Track(Track::Swap(t, t + 1))),
key_pat!(Delete) => Some(Cmd::Track(Track::Delete(t))),
key_pat!(Char('c')) => Some(Cmd::Track(Track::SetColor(t, ItemPalette::random()))),
key_pat!(Up) =>
None,
key_pat!(Down) => Some(
Cmd::Select(Selected::Clip(t, 0))),
key_pat!(Left) => Some(
Cmd::Select(if t > 0 { Selected::Track(t - 1) } else { Selected::Mix })),
key_pat!(Right) => Some(
Cmd::Select(Selected::Track((t + 1).min(t_len.saturating_sub(1))))),
_ => None
},
Selected::Mix => match input.event() {
key_pat!(Delete) => Some(Cmd::Clear),
key_pat!(Char('0')) => Some(Cmd::StopAll),
key_pat!(Char('c')) => Some(Cmd::Color(ItemPalette::random())),
key_pat!(Up) =>
None,
key_pat!(Down) => Some(
Cmd::Select(Selected::Scene(0))),
key_pat!(Left) =>
None,
key_pat!(Right) => Some(
Cmd::Select(Selected::Track(0))),
_ => None
},
}
}
command!(|self: ArrangerCommand, state: ArrangerTui|match self {
Self::Scene(cmd) => cmd.execute(state)?.map(Self::Scene),
Self::Track(cmd) => cmd.execute(state)?.map(Self::Track),
Self::Clip(cmd) => cmd.execute(state)?.map(Self::Clip),
Self::Editor(cmd) => cmd.execute(&mut state.editor)?.map(Self::Editor),
Self::Clock(cmd) => cmd.execute(state)?.map(Self::Clock),
Self::Zoom(_) => { todo!(); },
Self::Select(selected) => {
*state.selected_mut() = selected;
None
},
Self::Color(palette) => {
let old = state.color;
state.color = palette;
Some(Self::Color(old))
},
Self::Phrases(cmd) => {
let mut default = |cmd: PoolCommand|{
cmd.execute(&mut state.phrases).map(|x|x.map(Self::Phrases))
};
match cmd {
// autoselect: automatically load selected phrase in editor
PoolCommand::Select(_) => {
let undo = default(cmd)?;
state.editor.set_phrase(Some(state.phrases.phrase()));
undo
},
// reload phrase in editor to update color
PoolCommand::Phrase(PhrasePoolCommand::SetColor(index, _)) => {
let undo = default(cmd)?;
state.editor.set_phrase(Some(state.phrases.phrase()));
undo
},
_ => default(cmd)?
}
},
Self::History(_) => { todo!() },
Self::StopAll => {
for track in 0..state.tracks.len() {
state.tracks[track].player.enqueue_next(None);
}
None
},
Self::Clear => { todo!() },
});
command!(|self: ArrangerTrackCommand, state: ArrangerTui|match self {
Self::SetColor(index, color) => {
let old = state.tracks[index].color;
state.tracks[index].color = color;
Some(Self::SetColor(index, old))
},
Self::Stop(track) => {
state.tracks[track].player.enqueue_next(None);
None
},
_ => None
});
command!(|self: ArrangerSceneCommand, state: ArrangerTui|match self {
Self::Delete(index) => {
state.scene_del(index);
None
},
Self::SetColor(index, color) => {
let old = state.scenes[index].color;
state.scenes[index].color = color;
Some(Self::SetColor(index, old))
},
Self::Enqueue(scene) => {
for track in 0..state.tracks.len() {
state.tracks[track].player.enqueue_next(state.scenes[scene].clips[track].as_ref());
}
None
},
_ => None
});
command!(|self: ArrangerClipCommand, state: ArrangerTui|match self {
Self::Get(track, scene) => { todo!() },
Self::Put(track, scene, phrase) => {
let old = state.scenes[scene].clips[track].clone();
state.scenes[scene].clips[track] = phrase;
Some(Self::Put(track, scene, old))
},
Self::Enqueue(track, scene) => {
state.tracks[track].player.enqueue_next(state.scenes[scene].clips[track].as_ref());
None
},
_ => None
});

View file

@ -1 +0,0 @@
// TODO

View file

@ -1,26 +0,0 @@
use crate::*;
/// Display mode of arranger
#[derive(Clone, PartialEq)]
pub enum ArrangerMode {
/// Tracks are columns
V(usize),
/// Tracks are rows
H,
}
render!(<Tui>|self: ArrangerMode|{});
/// Arranger display mode can be cycled
impl ArrangerMode {
/// Cycle arranger display mode
pub fn next (&mut self) {
*self = match self {
Self::H => Self::V(1),
Self::V(1) => Self::V(2),
Self::V(2) => Self::V(2),
Self::V(0) => Self::H,
Self::V(_) => Self::V(0),
}
}
}
fn any_size <E: Engine> (_: E::Size) -> Perhaps<E::Size>{
Ok(Some([0.into(),0.into()].into()))
}

View file

@ -1,91 +0,0 @@
use crate::*;
impl ArrangerTui {
pub fn scene_add (&mut self, name: Option<&str>, color: Option<ItemPalette>)
-> Usually<&mut ArrangerScene>
{
let name = name.map_or_else(||self.scene_default_name(), |x|x.to_string());
let scene = ArrangerScene {
name: Arc::new(name.into()),
clips: vec![None;self.tracks.len()],
color: color.unwrap_or_else(ItemPalette::random),
};
self.scenes.push(scene);
let index = self.scenes.len() - 1;
Ok(&mut self.scenes[index])
}
pub fn scene_del (&mut self, index: usize) {
todo!("delete scene");
}
fn scene_default_name (&self) -> String {
format!("S{:3>}", self.scenes.len() + 1)
}
pub fn selected_scene (&self) -> Option<&ArrangerScene> {
self.selected.scene().and_then(|s|self.scenes.get(s))
}
pub fn selected_scene_mut (&mut self) -> Option<&mut ArrangerScene> {
self.selected.scene().and_then(|s|self.scenes.get_mut(s))
}
}
#[derive(Default, Debug, Clone)] pub struct ArrangerScene {
/// Name of scene
pub(crate) name: Arc<RwLock<String>>,
/// Clips in scene, one per track
pub(crate) clips: Vec<Option<Arc<RwLock<MidiClip>>>>,
/// Identifying color of scene
pub(crate) color: ItemPalette,
}
impl ArrangerScene {
pub fn name (&self) -> &Arc<RwLock<String>> {
&self.name
}
pub fn clips (&self) -> &Vec<Option<Arc<RwLock<MidiClip>>>> {
&self.clips
}
pub fn color (&self) -> ItemPalette {
self.color
}
pub fn ppqs (scenes: &[Self], factor: usize) -> Vec<(usize, usize)> {
let mut total = 0;
if factor == 0 {
scenes.iter().map(|scene|{
let pulses = scene.pulses().max(PPQ);
total += pulses;
(pulses, total - pulses)
}).collect()
} else {
(0..=scenes.len()).map(|i|{
(factor*PPQ, factor*PPQ*i)
}).collect()
}
}
pub fn longest_name (scenes: &[Self]) -> usize {
scenes.iter().map(|s|s.name().read().unwrap().len()).fold(0, usize::max)
}
/// Returns the pulse length of the longest phrase in the scene
pub fn pulses (&self) -> usize {
self.clips().iter().fold(0, |a, p|{
a.max(p.as_ref().map(|q|q.read().unwrap().length).unwrap_or(0))
})
}
/// Returns true if all phrases in the scene are
/// currently playing on the given collection of tracks.
pub fn is_playing (&self, tracks: &[ArrangerTrack]) -> bool {
self.clips().iter().any(|clip|clip.is_some()) && self.clips().iter().enumerate()
.all(|(track_index, clip)|match clip {
Some(clip) => tracks
.get(track_index)
.map(|track|{
if let Some((_, Some(phrase))) = track.player().play_phrase() {
*phrase.read().unwrap() == *clip.read().unwrap()
} else {
false
}
})
.unwrap_or(false),
None => true
})
}
pub fn clip (&self, index: usize) -> Option<&Arc<RwLock<MidiClip>>> {
match self.clips().get(index) { Some(Some(clip)) => Some(clip), _ => None }
}
}

View file

@ -1,60 +0,0 @@
use crate::*;
#[derive(PartialEq, Clone, Copy, Debug)]
/// Represents the current user selection in the arranger
pub enum ArrangerSelection {
/// The whole mix is selected
Mix,
/// A track is selected.
Track(usize),
/// A scene is selected.
Scene(usize),
/// A clip (track × scene) is selected.
Clip(usize, usize),
}
/// Focus identification methods
impl ArrangerSelection {
pub fn is_mix (&self) -> bool { matches!(self, Self::Mix) }
pub fn is_track (&self) -> bool { matches!(self, Self::Track(_)) }
pub fn is_scene (&self) -> bool { matches!(self, Self::Scene(_)) }
pub fn is_clip (&self) -> bool { matches!(self, Self::Clip(_, _)) }
pub fn description (
&self,
tracks: &[ArrangerTrack],
scenes: &[ArrangerScene],
) -> String {
format!("Selected: {}", match self {
Self::Mix => "Everything".to_string(),
Self::Track(t) => match tracks.get(*t) {
Some(track) => format!("T{t}: {}", &track.name.read().unwrap()),
None => "T??".into(),
},
Self::Scene(s) => match scenes.get(*s) {
Some(scene) => format!("S{s}: {}", &scene.name.read().unwrap()),
None => "S??".into(),
},
Self::Clip(t, s) => match (tracks.get(*t), scenes.get(*s)) {
(Some(_), Some(scene)) => match scene.clip(*t) {
Some(clip) => format!("T{t} S{s} C{}", &clip.read().unwrap().name),
None => format!("T{t} S{s}: Empty")
},
_ => format!("T{t} S{s}: Empty"),
}
})
}
pub fn track (&self) -> Option<usize> {
use ArrangerSelection::*;
match self {
Clip(t, _) => Some(*t),
Track(t) => Some(*t),
_ => None
}
}
pub fn scene (&self) -> Option<usize> {
use ArrangerSelection::*;
match self {
Clip(_, s) => Some(*s),
Scene(s) => Some(*s),
_ => None
}
}
}

View file

@ -1,111 +0,0 @@
use crate::*;
impl ArrangerTui {
pub fn track_next_name (&self) -> String {
format!("T{}", self.tracks.len() + 1)
}
pub fn track_add (&mut self, name: Option<&str>, color: Option<ItemPalette>)
-> Usually<&mut ArrangerTrack>
{
let name = name.map_or_else(||self.track_next_name(), |x|x.to_string());
let track = ArrangerTrack {
width: name.len() + 2,
name: Arc::new(name.into()),
color: color.unwrap_or_else(ItemPalette::random),
player: MidiPlayer::from(&self.clock),
};
self.tracks.push(track);
let index = self.tracks.len() - 1;
Ok(&mut self.tracks[index])
}
pub fn track_del (&mut self, index: usize) {
self.tracks.remove(index);
for scene in self.scenes.iter_mut() {
scene.clips.remove(index);
}
}
}
#[derive(Debug)] pub struct ArrangerTrack {
/// Name of track
pub name: Arc<RwLock<String>>,
/// Preferred width of track column
pub width: usize,
/// Identifying color of track
pub color: ItemPalette,
/// MIDI player state
pub player: MidiPlayer,
}
has_clock!(|self:ArrangerTrack|self.player.clock());
has_player!(|self:ArrangerTrack|self.player);
impl ArrangerTrack {
pub fn widths (tracks: &[Self]) -> Vec<(usize, usize)> {
let mut widths = vec![];
let mut total = 0;
for track in tracks.iter() {
let width = track.width;
widths.push((width, total));
total += width;
}
widths.push((0, total));
widths
}
pub fn with_widths (tracks: &[ArrangerTrack])
-> impl Iterator<Item = (usize, &ArrangerTrack, usize, usize)>
{
let mut x = 0;
tracks.iter().enumerate().map(move |(index, track)|{
let data = (index, track, x, x + track.width);
x += track.width;
data
})
}
/// Name of track
pub fn name (&self) -> &Arc<RwLock<String>> {
&self.name
}
/// Preferred width of track column
fn width (&self) -> usize {
self.width
}
/// Preferred width of track column
fn width_mut (&mut self) -> &mut usize {
&mut self.width
}
/// Identifying color of track
pub fn color (&self) -> ItemPalette {
self.color
}
fn longest_name (tracks: &[Self]) -> usize {
tracks.iter().map(|s|s.name().read().unwrap().len()).fold(0, usize::max)
}
pub const MIN_WIDTH: usize = 6;
fn width_inc (&mut self) {
*self.width_mut() += 1;
}
fn width_dec (&mut self) {
if self.width() > Self::MIN_WIDTH {
*self.width_mut() -= 1;
}
}
}
/// Hosts the JACK callback for a collection of tracks
pub struct TracksAudio<'a>(
// Track collection
pub &'a mut [ArrangerTrack],
/// Note buffer
pub &'a mut Vec<u8>,
/// Note chunk buffer
pub &'a mut Vec<Vec<Vec<u8>>>,
);
impl Audio for TracksAudio<'_> {
#[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
let model = &mut self.0;
let note_buffer = &mut self.1;
let output_buffer = &mut self.2;
for track in model.iter_mut() {
if PlayerAudio(track.player_mut(), note_buffer, output_buffer).process(client, scope) == Control::Quit {
return Control::Quit
}
}
Control::Continue
}
}

View file

@ -1,23 +0,0 @@
use crate::*;
mod v_clips; pub(crate) use self::v_clips::*;
mod v_cursor; pub(crate) use self::v_cursor::*;
mod v_head; pub(crate) use self::v_head::*;
mod v_io; pub(crate) use self::v_io::*;
mod v_sep; pub(crate) use self::v_sep::*;
const HEADER_H: u16 = 5;
const SCENES_W_OFFSET: u16 = 3;
impl ArrangerTui {
pub fn render_mode_v (state: &ArrangerTui, factor: usize) -> impl Render<Tui> + use<'_> {
lay!([
ArrangerVColSep::from(state),
ArrangerVRowSep::from((state, factor)),
col!([
ArrangerVHead::from(state),
ArrangerVIns::from(state),
ArrangerVClips::from((state, factor)),
ArrangerVOuts::from(state),
]),
ArrangerVCursor::from((state, factor)),
])
}
}

View file

@ -1,62 +0,0 @@
use crate::*;
use super::*;
pub struct ArrangerVClips<'a> {
size: &'a Measure<Tui>,
scenes: &'a Vec<ArrangerScene>,
tracks: &'a Vec<ArrangerTrack>,
rows: Vec<(usize, usize)>,
}
from!(<'a>|args:(&'a ArrangerTui, usize)|ArrangerVClips<'a> = Self {
size: &args.0.size,
scenes: &args.0.scenes,
tracks: &args.0.tracks,
rows: ArrangerScene::ppqs(&args.0.scenes, args.1),
});
render!(<Tui>|self: ArrangerVClips<'a>|Fill::wh(
col!((scene, pulses) in self.scenes.iter().zip(self.rows.iter().map(|row|row.0)) => {
Self::format_scene(self.tracks, scene, pulses)
})
));
impl<'a> ArrangerVClips<'a> {
fn format_scene (
tracks: &'a [ArrangerTrack], scene: &'a ArrangerScene, pulses: usize
) -> impl Render<Tui> + use<'a> {
let height = 1.max((pulses / PPQ) as u16);
let playing = scene.is_playing(tracks);
Fixed::h(height, row!([
Tui::bg(scene.color.base.rgb,
if playing { "" } else { " " }),
Tui::fg_bg(scene.color.lightest.rgb, scene.color.base.rgb,
Tui::grow_x(1, Tui::bold(true, scene.name.read().unwrap().as_str()))),
row!((index, track, x1, x2) in ArrangerTrack::with_widths(tracks) => {
Self::format_clip(scene, index, track, (x2 - x1) as u16, height)
})])
)
}
fn format_clip (
scene: &'a ArrangerScene, index: usize, track: &'a ArrangerTrack, w: u16, h: u16
) -> impl Render<Tui> + use<'a> {
Fixed::wh(w, h, Layers::new(move |add|{
if let Some(Some(phrase)) = scene.clips.get(index) {
let mut bg = TuiTheme::border_bg();
let name = &(phrase as &Arc<RwLock<MidiClip>>).read().unwrap().name.to_string();
let max_w = name.len().min((w as usize).saturating_sub(2));
let color = phrase.read().unwrap().color;
bg = color.dark.rgb;
if let Some((_, Some(ref playing))) = track.player.play_phrase() {
if *playing.read().unwrap() == *phrase.read().unwrap() {
bg = color.light.rgb
}
};
add(&Tui::bg(bg,
Tui::push_x(1, Fixed::w(w, &name.as_str()[0..max_w])))
)?;
}
Ok(())
}))
}
}

View file

@ -1,81 +0,0 @@
use crate::*;
use super::*;
pub struct ArrangerVCursor {
cols: Vec<(usize, usize)>,
rows: Vec<(usize, usize)>,
color: ItemPalette,
reticle: Reticle,
selected: ArrangerSelection,
scenes_w: u16,
}
from!(|args:(&ArrangerTui, usize)|ArrangerVCursor = Self {
cols: ArrangerTrack::widths(&args.0.tracks),
rows: ArrangerScene::ppqs(&args.0.scenes, args.1),
selected: args.0.selected(),
scenes_w: SCENES_W_OFFSET + ArrangerScene::longest_name(&args.0.scenes) as u16,
color: args.0.color,
reticle: Reticle(Style {
fg: Some(args.0.color.lighter.rgb),
bg: None,
underline_color: None,
add_modifier: Modifier::empty(),
sub_modifier: Modifier::DIM
}),
});
render!(<Tui>|self: ArrangerVCursor|render(move|to: &mut TuiOutput|{
let area = to.area();
let focused = true;
let selected = self.selected;
let get_track_area = |t: usize| [
self.scenes_w + area.x() + self.cols[t].1 as u16, area.y(),
self.cols[t].0 as u16, area.h(),
];
let get_scene_area = |s: usize| [
area.x(), HEADER_H + area.y() + (self.rows[s].1 / PPQ) as u16,
area.w(), (self.rows[s].0 / PPQ) as u16
];
let get_clip_area = |t: usize, s: usize| [
(self.scenes_w + area.x() + self.cols[t].1 as u16).saturating_sub(1),
HEADER_H + area.y() + (self.rows[s].1/PPQ) as u16,
self.cols[t].0 as u16 + 2,
(self.rows[s].0 / PPQ) as u16
];
let mut track_area: Option<[u16;4]> = None;
let mut scene_area: Option<[u16;4]> = None;
let mut clip_area: Option<[u16;4]> = None;
let area = match selected {
ArrangerSelection::Mix => area,
ArrangerSelection::Track(t) => {
track_area = Some(get_track_area(t));
area
},
ArrangerSelection::Scene(s) => {
scene_area = Some(get_scene_area(s));
area
},
ArrangerSelection::Clip(t, s) => {
track_area = Some(get_track_area(t));
scene_area = Some(get_scene_area(s));
clip_area = Some(get_clip_area(t, s));
area
},
};
let bg = self.color.lighter.rgb;//Color::Rgb(0, 255, 0);
if let Some([x, y, width, height]) = track_area {
to.fill_fg([x, y, 1, height], bg);
to.fill_fg([x + width, y, 1, height], bg);
}
if let Some([_, y, _, height]) = scene_area {
to.fill_ul([area.x(), y - 1, area.w(), 1], bg);
to.fill_ul([area.x(), y + height - 1, area.w(), 1], bg);
}
Ok(if focused {
to.render_in(if let Some(clip_area) = clip_area { clip_area }
else if let Some(track_area) = track_area { track_area.clip_h(HEADER_H) }
else if let Some(scene_area) = scene_area { scene_area.clip_w(self.scenes_w) }
else { area.clip_w(self.scenes_w).clip_h(HEADER_H) }, &self.reticle)?
})
}));

View file

@ -1,83 +0,0 @@
use crate::*;
use super::*;
pub struct ArrangerVHead<'a> {
scenes_w: u16,
timebase: &'a Arc<Timebase>,
current: &'a Arc<Moment>,
tracks: &'a [ArrangerTrack],
}
from!(<'a>|state: &'a ArrangerTui|ArrangerVHead<'a> = Self { // A
tracks: &state.tracks,
timebase: state.clock().timebase(),
current: &state.clock().playhead,
scenes_w: SCENES_W_OFFSET + ArrangerScene::longest_name(&state.scenes) as u16,
});
render!(<Tui>|self: ArrangerVHead<'a>|Tui::push_x(self.scenes_w, row!(
(_, track, x1, x2) in ArrangerTrack::with_widths(self.tracks) => {
let (w, h) = (ArrangerTrack::MIN_WIDTH.max(x2 - x1), HEADER_H);
let color = track.color();
fn row <T: Render<Tui>> (color: ItemPalette, field: &T) -> impl Render<Tui> + use<'_, T> {
row!([
Tui::fg(color.light.rgb, ""),
Tui::fg(color.lightest.rgb, field)
])
}
Tui::bg(color.base.rgb, Tui::min_xy(w as u16, h, Fixed::wh(w as u16, 5, col!([
row(color, &Self::format_name(track, w)),
row(color, &Self::format_input(track)?),
row(color, &Self::format_output(track)?),
row(color, &Self::format_elapsed(track, self.timebase)),
row(color, &Self::format_until_next(track, self.current)),
]))))
}
)));
impl ArrangerVHead<'_> {
/// name and width of track
fn format_name (track: &ArrangerTrack, _w: usize) -> impl Render<Tui> {
let name = track.name().read().unwrap().clone();
Tui::bold(true, Tui::fg(track.color.lightest.rgb, name))
}
/// input port
fn format_input (track: &ArrangerTrack) -> Usually<impl Render<Tui>> {
Ok(format!(">{}", track.player.midi_ins().first().map(|port|port.short_name())
.transpose()?.unwrap_or("?".into())))
}
/// output port
fn format_output (track: &ArrangerTrack) -> Usually<impl Render<Tui>> {
Ok(format!("<{}", track.player.midi_outs().first().map(|port|port.short_name())
.transpose()?.unwrap_or("?".into())))
}
/// beats elapsed
fn format_elapsed (track: &ArrangerTrack, timebase: &Arc<Timebase>) -> impl Render<Tui> {
let mut result = String::new();
if let Some((_, Some(phrase))) = track.player.play_phrase().as_ref() {
let length = phrase.read().unwrap().length;
let elapsed = track.player.pulses_since_start().unwrap();
let elapsed = timebase.format_beats_1_short(
(elapsed as usize % length) as f64
);
result = format!("+{elapsed:>}")
}
result
}
/// beats until switchover
fn format_until_next (track: &ArrangerTrack, current: &Arc<Moment>)
-> Option<impl Render<Tui>>
{
let timebase = &current.timebase;
let mut result = String::new();
if let Some((t, _)) = track.player.next_phrase().as_ref() {
let target = t.pulse.get();
let current = current.pulse.get();
if target > current {
let remaining = target - current;
result = format!("-{:>}", timebase.format_beats_0_short(remaining))
}
}
Some(result)
}
}

View file

@ -1,26 +0,0 @@
use crate::*;
use super::*;
pub struct ArrangerVIns<'a> {
size: &'a Measure<Tui>,
tracks: &'a Vec<ArrangerTrack>,
}
from!(<'a>|args: &'a ArrangerTui|ArrangerVIns<'a> = Self {
size: &args.size,
tracks: &args.tracks,
});
render!(<Tui>|self: ArrangerVIns<'a>|());
pub struct ArrangerVOuts<'a> {
size: &'a Measure<Tui>,
tracks: &'a Vec<ArrangerTrack>,
}
from!(<'a>|args: &'a ArrangerTui|ArrangerVOuts<'a> = Self {
size: &args.size,
tracks: &args.tracks,
});
render!(<Tui>|self: ArrangerVOuts<'a>|());

View file

@ -1,48 +0,0 @@
use crate::*;
use super::*;
pub struct ArrangerVColSep {
fg: Color,
cols: Vec<(usize, usize)>,
scenes_w: u16
}
from!(|state:&ArrangerTui|ArrangerVColSep = Self {
fg: TuiTheme::separator_fg(false),
cols: ArrangerTrack::widths(&state.tracks),
scenes_w: SCENES_W_OFFSET + ArrangerScene::longest_name(&state.scenes) as u16,
});
render!(<Tui>|self: ArrangerVColSep|render(move|to: &mut TuiOutput|{
let style = Some(Style::default().fg(self.fg));
Ok(for x in self.cols.iter().map(|col|col.1) {
let x = self.scenes_w + to.area().x() + x as u16;
for y in to.area().y()..to.area().y2() {
to.blit(&"", x, y, style);
}
})
}));
pub struct ArrangerVRowSep {
fg: Color,
rows: Vec<(usize, usize)>,
}
from!(|args:(&ArrangerTui, usize)|ArrangerVRowSep = Self {
fg: TuiTheme::separator_fg(false),
rows: ArrangerScene::ppqs(&args.0.scenes, args.1),
});
render!(<Tui>|self: ArrangerVRowSep|render(move|to: &mut TuiOutput|{
Ok(for y in self.rows.iter().map(|row|row.1) {
let y = to.area().y() + (y / PPQ) as u16 + 1;
if y >= to.buffer.area.height { break }
for x in to.area().x()..to.area().x2().saturating_sub(2) {
if x < to.buffer.area.x && y < to.buffer.area.y {
let cell = to.buffer.get_mut(x, y);
cell.modifier = Modifier::UNDERLINED;
cell.underline_color = self.fg;
}
}
})
}));

View file

@ -1 +0,0 @@
use crate::*;

View file

@ -1,69 +0,0 @@
pub(crate) use std::error::Error;
pub(crate) mod color;
pub(crate) use color::*;
pub use color::*;
pub(crate) mod command; pub(crate) use command::*;
pub(crate) mod engine; pub(crate) use engine::*;
pub(crate) mod focus; pub(crate) use focus::*;
pub(crate) mod input; pub(crate) use input::*;
pub(crate) mod output; pub(crate) use output::*;
pub(crate) use std::sync::atomic::{Ordering, AtomicBool, AtomicUsize};
pub(crate) use Ordering::Relaxed;
pub use self::{
engine::Engine,
input::Handle,
output::Render
};
/// Standard result type.
pub type Usually<T> = Result<T, Box<dyn Error>>;
/// Standard optional result type.
pub type Perhaps<T> = Result<Option<T>, Box<dyn Error>>;
/// Define test modules.
#[macro_export] macro_rules! testmod {
($($name:ident)*) => { $(#[cfg(test)] mod $name;)* };
}
/// Prototypal case of implementor macro.
/// Saves 4loc per data pats.
#[macro_export] macro_rules! from {
($(<$($lt:lifetime),+>)?|$state:ident:$Source:ty|$Target:ty=$cb:expr) => {
impl $(<$($lt),+>)? From<$Source> for $Target {
fn from ($state:$Source) -> Self { $cb }
}
};
}
pub trait Gettable<T> {
/// Returns current value
fn get (&self) -> T;
}
pub trait Mutable<T>: Gettable<T> {
/// Sets new value, returns old
fn set (&mut self, value: T) -> T;
}
pub trait InteriorMutable<T>: Gettable<T> {
/// Sets new value, returns old
fn set (&self, value: T) -> T;
}
impl Gettable<bool> for AtomicBool {
fn get (&self) -> bool { self.load(Relaxed) }
}
impl InteriorMutable<bool> for AtomicBool {
fn set (&self, value: bool) -> bool { self.swap(value, Relaxed) }
}
impl Gettable<usize> for AtomicUsize {
fn get (&self) -> usize { self.load(Relaxed) }
}
impl InteriorMutable<usize> for AtomicUsize {
fn set (&self, value: usize) -> usize { self.swap(value, Relaxed) }
}

View file

@ -1,105 +0,0 @@
use crate::*;
use rand::{thread_rng, distributions::uniform::UniformSampler};
pub use ratatui::prelude::Color;
pub trait HasColor {
fn color (&self) -> ItemColor;
}
#[macro_export] macro_rules! has_color {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? HasColor for $Struct $(<$($L),*$($T),*>)? {
fn color (&$self) -> ItemColor { $cb }
}
}
}
/// A color in OKHSL and RGB representations.
#[derive(Debug, Default, Copy, Clone, PartialEq)]
pub struct ItemColor {
pub okhsl: Okhsl<f32>,
pub rgb: Color,
}
/// A color in OKHSL and RGB with lighter and darker variants.
#[derive(Debug, Default, Copy, Clone, PartialEq)]
pub struct ItemPalette {
pub base: ItemColor,
pub light: ItemColor,
pub lighter: ItemColor,
pub lightest: ItemColor,
pub dark: ItemColor,
pub darker: ItemColor,
pub darkest: ItemColor,
}
from!(|okhsl: Okhsl<f32>|ItemColor = Self { okhsl, rgb: okhsl_to_rgb(okhsl) });
from!(|rgb: Color|ItemColor = Self { rgb, okhsl: rgb_to_okhsl(rgb) });
// A single color within item theme parameters, in OKHSL and RGB representations.
impl ItemColor {
pub fn random () -> Self {
let mut rng = thread_rng();
let lo = Okhsl::new(-180.0, 0.01, 0.25);
let hi = Okhsl::new( 180.0, 0.9, 0.5);
UniformOkhsl::new(lo, hi).sample(&mut rng).into()
}
pub fn random_dark () -> Self {
let mut rng = thread_rng();
let lo = Okhsl::new(-180.0, 0.025, 0.075);
let hi = Okhsl::new( 180.0, 0.5, 0.150);
UniformOkhsl::new(lo, hi).sample(&mut rng).into()
}
pub fn random_near (color: Self, distance: f32) -> Self {
color.mix(Self::random(), distance)
}
pub fn mix (&self, other: Self, distance: f32) -> Self {
if distance > 1.0 { panic!("color mixing takes distance between 0.0 and 1.0"); }
self.okhsl.mix(other.okhsl, distance).into()
}
}
from!(|base: Color|ItemPalette = Self::from(ItemColor::from(base)));
from!(|base: ItemColor|ItemPalette = {
let mut light = base.okhsl;
light.lightness = (light.lightness * 1.3).min(Okhsl::<f32>::max_lightness());
let mut lighter = light;
lighter.lightness = (lighter.lightness * 1.3).min(Okhsl::<f32>::max_lightness());
let mut lightest = lighter;
lightest.lightness = (lightest.lightness * 1.3).min(Okhsl::<f32>::max_lightness());
let mut dark = base.okhsl;
dark.lightness = (dark.lightness * 0.75).max(Okhsl::<f32>::min_lightness());
dark.saturation = (dark.saturation * 0.75).max(Okhsl::<f32>::min_saturation());
let mut darker = dark;
darker.lightness = (darker.lightness * 0.66).max(Okhsl::<f32>::min_lightness());
darker.saturation = (darker.saturation * 0.66).max(Okhsl::<f32>::min_saturation());
let mut darkest = darker;
darkest.lightness = (darkest.lightness * 0.50).max(Okhsl::<f32>::min_lightness());
darkest.saturation = (darkest.saturation * 0.50).max(Okhsl::<f32>::min_saturation());
Self {
base,
light: light.into(),
lighter: lighter.into(),
lightest: lightest.into(),
dark: dark.into(),
darker: darker.into(),
darkest: darkest.into(),
}
});
impl ItemPalette {
pub fn random () -> Self {
ItemColor::random().into()
}
pub fn random_near (color: Self, distance: f32) -> Self {
color.base.mix(ItemColor::random(), distance).into()
}
}
pub fn okhsl_to_rgb (color: Okhsl<f32>) -> Color {
let Srgb { red, green, blue, .. }: Srgb<f32> = Srgb::from_color_unclamped(color);
Color::Rgb((red * 255.0) as u8, (green * 255.0) as u8, (blue * 255.0) as u8,)
}
pub fn rgb_to_okhsl (color: Color) -> Okhsl<f32> {
if let Color::Rgb(r, g, b) = color {
Okhsl::from_color(Srgb::new(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0))
} else {
unreachable!("only Color::Rgb is supported")
}
}

View file

@ -1,116 +0,0 @@
use crate::*;
#[macro_export] macro_rules! command {
(|$self:ident:$Command:ty,$state:ident:$State:ty|$handler:expr) => {
impl Command<$State> for $Command {
fn execute ($self, $state: &mut $State) -> Perhaps<Self> {
Ok($handler)
}
}
}
}
#[macro_export] macro_rules! input_to_command {
($Command:ty: <$Engine:ty>|$state:ident:$State:ty,$input:ident|$handler:expr) => {
impl InputToCommand<$Engine, $State> for $Command {
fn input_to_command ($state: &$State, $input: &<$Engine as Engine>::Input) -> Option<Self> {
Some($handler)
}
}
}
}
#[derive(Clone)]
pub enum NextPrev {
Next,
Prev,
}
pub trait Execute<T> {
fn command (&mut self, command: T) -> Perhaps<T>;
}
pub trait Command<S>: Send + Sync + Sized {
fn execute (self, state: &mut S) -> Perhaps<Self>;
}
pub fn delegate <B, C: Command<S>, S> (
cmd: C,
wrap: impl Fn(C)->B,
state: &mut S,
) -> Perhaps<B> {
Ok(cmd.execute(state)?.map(wrap))
}
pub trait InputToCommand<E: Engine, S>: Command<S> + Sized {
fn input_to_command (state: &S, input: &E::Input) -> Option<Self>;
fn execute_with_state (state: &mut S, input: &E::Input) -> Perhaps<bool> {
Ok(if let Some(command) = Self::input_to_command(state, input) {
let _undo = command.execute(state)?;
Some(true)
} else {
None
})
}
}
pub struct MenuBar<E: Engine, S, C: Command<S>> {
pub menus: Vec<Menu<E, S, C>>,
pub index: usize,
}
impl<E: Engine, S, C: Command<S>> MenuBar<E, S, C> {
pub fn new () -> Self { Self { menus: vec![], index: 0 } }
pub fn add (mut self, menu: Menu<E, S, C>) -> Self {
self.menus.push(menu);
self
}
}
pub struct Menu<E: Engine, S, C: Command<S>> {
pub title: String,
pub items: Vec<MenuItem<E, S, C>>,
pub index: Option<usize>,
}
impl<E: Engine, S, C: Command<S>> Menu<E, S, C> {
pub fn new (title: impl AsRef<str>) -> Self {
Self {
title: title.as_ref().to_string(),
items: vec![],
index: None,
}
}
pub fn add (mut self, item: MenuItem<E, S, C>) -> Self {
self.items.push(item);
self
}
pub fn sep (mut self) -> Self {
self.items.push(MenuItem::sep());
self
}
pub fn cmd (mut self, hotkey: &'static str, text: &'static str, command: C) -> Self {
self.items.push(MenuItem::cmd(hotkey, text, command));
self
}
pub fn off (mut self, hotkey: &'static str, text: &'static str) -> Self {
self.items.push(MenuItem::off(hotkey, text));
self
}
}
pub enum MenuItem<E: Engine, S, C: Command<S>> {
/// Unused.
__(PhantomData<E>, PhantomData<S>),
/// A separator. Skip it.
Separator,
/// A menu item with command, description and hotkey.
Command(&'static str, &'static str, C),
/// A menu item that can't be activated but has description and hotkey
Disabled(&'static str, &'static str)
}
impl<E: Engine, S, C: Command<S>> MenuItem<E, S, C> {
pub fn sep () -> Self {
Self::Separator
}
pub fn cmd (hotkey: &'static str, text: &'static str, command: C) -> Self {
Self::Command(hotkey, text, command)
}
pub fn off (hotkey: &'static str, text: &'static str) -> Self {
Self::Disabled(hotkey, text)
}
}

View file

@ -1,49 +0,0 @@
use crate::*;
/// Platform backend.
pub trait Engine: Send + Sync + Sized {
/// Input event type
type Input: Input<Self>;
/// Result of handling input
type Handled;
/// Render target
type Output: Output<Self>;
/// Unit of length
type Unit: Coordinate;
/// Rectangle without offset
type Size: Size<Self::Unit> + From<[Self::Unit;2]> + Debug + Copy;
/// Rectangle with offset
type Area: Area<Self::Unit> + From<[Self::Unit;4]> + Debug + Copy;
/// Prepare before run
fn setup (&mut self) -> Usually<()> { Ok(()) }
/// True if done
fn exited (&self) -> bool;
/// Clean up after run
fn teardown (&mut self) -> Usually<()> { Ok(()) }
}
/// A UI component that can render itself as a [Render], and [Handle] input.
pub trait Component<E: Engine>: Render<E> + Handle<E> {}
/// Everything that implements [Render] and [Handle] is a [Component].
impl<E: Engine, C: Render<E> + Handle<E>> Component<E> for C {}
/// A component that can exit.
pub trait Exit: Send {
fn exited (&self) -> bool;
fn exit (&mut self);
fn boxed (self) -> Box<dyn Exit> where Self: Sized + 'static {
Box::new(self)
}
}
/// Marker trait for [Component]s that can [Exit].
pub trait ExitableComponent<E>: Exit + Component<E> where E: Engine {
/// Perform type erasure for collecting heterogeneous components.
fn boxed (self) -> Box<dyn ExitableComponent<E>> where Self: Sized + 'static {
Box::new(self)
}
}
/// All [Components]s that implement [Exit] implement [ExitableComponent].
impl<E: Engine, C: Component<E> + Exit> ExitableComponent<E> for C {}

View file

@ -1,308 +0,0 @@
use crate::*;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum FocusState<T: Copy + Debug + PartialEq> {
Focused(T),
Entered(T),
}
impl<T: Copy + Debug + PartialEq> FocusState<T> {
pub fn inner (&self) -> T {
match self {
Self::Focused(inner) => *inner,
Self::Entered(inner) => *inner,
}
}
pub fn set_inner (&mut self, inner: T) {
*self = match self {
Self::Focused(_) => Self::Focused(inner),
Self::Entered(_) => Self::Entered(inner),
}
}
pub fn is_focused (&self) -> bool { matches!(self, Self::Focused(_)) }
pub fn is_entered (&self) -> bool { matches!(self, Self::Entered(_)) }
pub fn focus (&mut self) { *self = Self::Focused(self.inner()) }
pub fn enter (&mut self) { *self = Self::Entered(self.inner()) }
}
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum FocusCommand<T: Send + Sync> {
Up,
Down,
Left,
Right,
Next,
Prev,
Enter,
Exit,
Set(T)
}
impl<F: HasFocus + HasEnter + FocusGrid + FocusOrder> Command<F> for FocusCommand<F::Item> {
fn execute (self, state: &mut F) -> Perhaps<FocusCommand<F::Item>> {
use FocusCommand::*;
match self {
Next => { state.focus_next(); },
Prev => { state.focus_prev(); },
Up => { state.focus_up(); },
Down => { state.focus_down(); },
Left => { state.focus_left(); },
Right => { state.focus_right(); },
Enter => { state.focus_enter(); },
Exit => { state.focus_exit(); },
Set(to) => { state.set_focused(to); },
}
Ok(None)
}
}
/// Trait for things that have focusable subparts.
pub trait HasFocus {
type Item: Copy + PartialEq + Debug + Send + Sync;
/// Get the currently focused item.
fn focused (&self) -> Self::Item;
/// Get the currently focused item.
fn set_focused (&mut self, to: Self::Item);
/// Loop forward until a specific item is focused.
fn focus_to (&mut self, to: Self::Item) {
self.set_focused(to);
self.focus_updated();
}
/// Run this on focus update
fn focus_updated (&mut self) {}
}
/// Trait for things that have enterable subparts.
pub trait HasEnter: HasFocus {
/// Get the currently focused item.
fn entered (&self) -> bool;
/// Get the currently focused item.
fn set_entered (&mut self, entered: bool);
/// Enter into the currently focused component
fn focus_enter (&mut self) {
self.set_entered(true);
self.focus_updated();
}
/// Exit the currently entered component
fn focus_exit (&mut self) {
self.set_entered(false);
self.focus_updated();
}
}
/// Trait for things that implement directional navigation between focusable elements.
pub trait FocusGrid: HasFocus {
fn focus_layout (&self) -> &[&[Self::Item]];
fn focus_cursor (&self) -> (usize, usize);
fn focus_cursor_mut (&mut self) -> &mut (usize, usize);
fn focus_current (&self) -> Self::Item {
let (x, y) = self.focus_cursor();
self.focus_layout()[y][x]
}
fn focus_update (&mut self) {
self.focus_to(self.focus_current());
self.focus_updated()
}
fn focus_up (&mut self) {
let original_focused = self.focused();
let (_, original_y) = self.focus_cursor();
loop {
let (x, y) = self.focus_cursor();
let next_y = if y == 0 {
self.focus_layout().len().saturating_sub(1)
} else {
y - 1
};
if next_y == original_y {
break
}
let next_x = if self.focus_layout()[y].len() == self.focus_layout()[next_y].len() {
x
} else {
((x as f32 / self.focus_layout()[original_y].len() as f32)
* self.focus_layout()[next_y].len() as f32) as usize
};
*self.focus_cursor_mut() = (next_x, next_y);
if self.focus_current() != original_focused {
break
}
}
self.focus_update();
}
fn focus_down (&mut self) {
let original_focused = self.focused();
let (_, original_y) = self.focus_cursor();
loop {
let (x, y) = self.focus_cursor();
let next_y = if y >= self.focus_layout().len().saturating_sub(1) {
0
} else {
y + 1
};
if next_y == original_y {
break
}
let next_x = if self.focus_layout()[y].len() == self.focus_layout()[next_y].len() {
x
} else {
((x as f32 / self.focus_layout()[original_y].len() as f32)
* self.focus_layout()[next_y].len() as f32) as usize
};
*self.focus_cursor_mut() = (next_x, next_y);
if self.focus_current() != original_focused {
break
}
}
self.focus_update();
}
fn focus_left (&mut self) {
let original_focused = self.focused();
let (original_x, y) = self.focus_cursor();
loop {
let x = self.focus_cursor().0;
let next_x = if x == 0 {
self.focus_layout()[y].len().saturating_sub(1)
} else {
x - 1
};
if next_x == original_x {
break
}
*self.focus_cursor_mut() = (next_x, y);
if self.focus_current() != original_focused {
break
}
}
self.focus_update();
}
fn focus_right (&mut self) {
let original_focused = self.focused();
let (original_x, y) = self.focus_cursor();
loop {
let x = self.focus_cursor().0;
let next_x = if x >= self.focus_layout()[y].len().saturating_sub(1) {
0
} else {
x + 1
};
if next_x == original_x {
break
}
self.focus_cursor_mut().0 = next_x;
if self.focus_current() != original_focused {
break
}
}
self.focus_update();
}
}
/// Trait for things that implement next/prev navigation between focusable elements.
pub trait FocusOrder {
/// Focus the next item.
fn focus_next (&mut self);
/// Focus the previous item.
fn focus_prev (&mut self);
}
/// Next/prev navigation for directional focusables works in the given way.
impl<T: FocusGrid + HasEnter> FocusOrder for T {
/// Focus the next item.
fn focus_next (&mut self) {
let current = self.focused();
let (x, y) = self.focus_cursor();
if x < self.focus_layout()[y].len().saturating_sub(1) {
self.focus_right();
} else {
self.focus_down();
self.focus_cursor_mut().0 = 0;
}
if self.focused() == current { // FIXME: prevent infinite loop
self.focus_next()
}
self.focus_exit();
self.focus_update();
}
/// Focus the previous item.
fn focus_prev (&mut self) {
let current = self.focused();
let (x, _) = self.focus_cursor();
if x > 0 {
self.focus_left();
} else {
self.focus_up();
let (_, y) = self.focus_cursor();
let next_x = self.focus_layout()[y].len().saturating_sub(1);
self.focus_cursor_mut().0 = next_x;
}
if self.focused() == current { // FIXME: prevent infinite loop
self.focus_prev()
}
self.focus_exit();
self.focus_update();
}
}
pub trait FocusWrap<T> {
fn wrap <W: Render<Tui>> (self, focus: T, content: &'_ W) -> impl Render<Tui> + '_;
}
pub fn to_focus_command <T: Send + Sync> (input: &TuiInput) -> Option<FocusCommand<T>> {
use KeyCode::{Tab, BackTab, Up, Down, Left, Right, Enter, Esc};
Some(match input.event() {
key_pat!(Tab) => FocusCommand::Next,
key_pat!(Shift-Tab) => FocusCommand::Prev,
key_pat!(BackTab) => FocusCommand::Prev,
key_pat!(Shift-BackTab) => FocusCommand::Prev,
key_pat!(Up) => FocusCommand::Up,
key_pat!(Down) => FocusCommand::Down,
key_pat!(Left) => FocusCommand::Left,
key_pat!(Right) => FocusCommand::Right,
key_pat!(Enter) => FocusCommand::Enter,
key_pat!(Esc) => FocusCommand::Exit,
_ => return None
})
}
#[macro_export] macro_rules! impl_focus {
($Struct:ident $Focus:ident $Grid:expr $(=> [$self:ident : $update_focus:expr])?) => {
impl HasFocus for $Struct {
type Item = $Focus;
/// Get the currently focused item.
fn focused (&self) -> Self::Item {
self.focus.inner()
}
/// Get the currently focused item.
fn set_focused (&mut self, to: Self::Item) {
self.focus.set_inner(to)
}
$(fn focus_updated (&mut $self) { $update_focus })?
}
impl HasEnter for $Struct {
/// Get the currently focused item.
fn entered (&self) -> bool {
self.focus.is_entered()
}
/// Get the currently focused item.
fn set_entered (&mut self, entered: bool) {
if entered {
self.focus.to_entered()
} else {
self.focus.to_focused()
}
}
}
impl FocusGrid for $Struct {
fn focus_cursor (&self) -> (usize, usize) {
self.cursor
}
fn focus_cursor_mut (&mut self) -> &mut (usize, usize) {
&mut self.cursor
}
fn focus_layout (&self) -> &[&[$Focus]] {
use $Focus::*;
&$Grid
}
}
}
}

View file

@ -1,68 +0,0 @@
use crate::*;
/// Current input state
pub trait Input<E: Engine> {
/// Type of input event
type Event;
/// Currently handled event
fn event (&self) -> &Self::Event;
/// Whether component should exit
fn is_done (&self) -> bool;
/// Mark component as done
fn done (&self);
}
/// Handle input
pub trait Handle<E: Engine>: Send + Sync {
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled>;
}
#[macro_export] macro_rules! handle {
(<$E:ty>|$self:ident:$Struct:ty,$input:ident|$handler:expr) => {
impl Handle<$E> for $Struct {
fn handle (&mut $self, $input: &<$E as Engine>::Input) -> Perhaps<<$E as Engine>::Handled> {
$handler
}
}
}
}
impl<E: Engine, H: Handle<E>> Handle<E> for &mut H {
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled> {
(*self).handle(context)
}
}
impl<E: Engine, H: Handle<E>> Handle<E> for Option<H> {
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled> {
if let Some(ref mut handle) = self {
handle.handle(context)
} else {
Ok(None)
}
}
}
impl<H, E: Engine> Handle<E> for Mutex<H> where H: Handle<E> {
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled> {
self.get_mut().unwrap().handle(context)
}
}
impl<H, E: Engine> Handle<E> for Arc<Mutex<H>> where H: Handle<E> {
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled> {
self.lock().unwrap().handle(context)
}
}
impl<H, E: Engine> Handle<E> for RwLock<H> where H: Handle<E> {
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled> {
self.write().unwrap().handle(context)
}
}
impl<H, E: Engine> Handle<E> for Arc<RwLock<H>> where H: Handle<E> {
fn handle (&mut self, context: &E::Input) -> Perhaps<E::Handled> {
self.write().unwrap().handle(context)
}
}

View file

@ -1,160 +0,0 @@
use crate::*;
#[macro_export] macro_rules! render {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*E$(,$T:ident$(:$U:path)?)*$(,)?>)?|$cb:expr) => {
impl<E: Engine, $($($L),*$($T $(: $U)?),*)?> Render<E> for $Struct $(<$($L,)* E, $($T),*>)? {
fn min_size (&$self, to: <E as Engine>::Size) -> Perhaps<<E as Engine>::Size> {
$cb.min_size(to)
}
fn render (&$self, to: &mut <E as Engine>::Output) -> Usually<()> {
$cb.render(to)
}
}
};
(<$E:ty>|$self:ident:$Struct:ident$(<
$($($L:lifetime),+)?
$($($T:ident$(:$U:path)?),+)?
>)?|$cb:expr) => {
impl $(<
$($($L),+)?
$($($T$(:$U)?),+)?
>)? Render<$E> for $Struct $(<
$($($L),+)?
$($($T),+)?
>)? {
fn min_size (&$self, to: <$E as Engine>::Size) -> Perhaps<<$E as Engine>::Size> {
$cb.min_size(to)
}
fn render (&$self, to: &mut <$E as Engine>::Output) -> Usually<()> {
$cb.render(to)
}
}
}
}
/// Rendering target
pub trait Output<E: Engine> {
/// Current output area
fn area (&self) -> E::Area;
/// Mutable pointer to area
fn area_mut (&mut self) -> &mut E::Area;
/// Render widget in area
fn render_in (&mut self, area: E::Area, widget: &dyn Render<E>) -> Usually<()>;
}
/// Cast to dynamic pointer
pub fn widget <E: Engine, T: Render<E>> (w: &T) -> &dyn Render<E> {
w as &dyn Render<E>
}
/// A renderable component
pub trait Render<E: Engine>: Send + Sync {
/// Minimum size to use
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
Ok(Some(to))
}
/// Draw to output render target
fn render (&self, to: &mut E::Output) -> Usually<()>;
}
impl<E: Engine, R: Render<E>> Render<E> for &R {
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
(*self).min_size(to)
}
fn render (&self, to: &mut E::Output) -> Usually<()> {
(*self).render(to)
}
}
impl<E: Engine> Render<E> for &dyn Render<E> {
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
(*self).min_size(to)
}
fn render (&self, to: &mut E::Output) -> Usually<()> {
(*self).render(to)
}
}
//impl<E: Engine> Render<E> for &mut dyn Render<E> {
//fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
//(*self).min_size(to)
//}
//fn render (&self, to: &mut E::Output) -> Usually<()> {
//(*self).render(to)
//}
//}
impl<E: Engine> Render<E> for Box<dyn Render<E> + '_> {
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
(**self).min_size(to)
}
fn render (&self, to: &mut E::Output) -> Usually<()> {
(**self).render(to)
}
}
impl<E: Engine, W: Render<E>> Render<E> for Arc<W> {
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
self.as_ref().min_size(to)
}
fn render (&self, to: &mut E::Output) -> Usually<()> {
self.as_ref().render(to)
}
}
impl<E: Engine, W: Render<E>> Render<E> for Mutex<W> {
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
self.lock().unwrap().min_size(to)
}
fn render (&self, to: &mut E::Output) -> Usually<()> {
self.lock().unwrap().render(to)
}
}
impl<E: Engine, W: Render<E>> Render<E> for RwLock<W> {
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
self.read().unwrap().min_size(to)
}
fn render (&self, to: &mut E::Output) -> Usually<()> {
self.read().unwrap().render(to)
}
}
impl<E: Engine, W: Render<E>> Render<E> for Option<W> {
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
Ok(self.as_ref().map(|widget|widget.min_size(to)).transpose()?.flatten())
}
fn render (&self, to: &mut E::Output) -> Usually<()> {
self.as_ref().map(|widget|widget.render(to)).unwrap_or(Ok(()))
}
}
/// A custom [Render] defined by passing layout and render closures in place.
pub struct Widget<
E: Engine,
L: Send + Sync + Fn(E::Size)->Perhaps<E::Size>,
R: Send + Sync + Fn(&mut E::Output)->Usually<()>
>(L, R, PhantomData<E>);
impl<
E: Engine,
L: Send + Sync + Fn(E::Size)->Perhaps<E::Size>,
R: Send + Sync + Fn(&mut E::Output)->Usually<()>
> Widget<E, L, R> {
pub fn new (layout: L, render: R) -> Self {
Self(layout, render, Default::default())
}
}
impl<
E: Engine,
L: Send + Sync + Fn(E::Size)->Perhaps<E::Size>,
R: Send + Sync + Fn(&mut E::Output)->Usually<()>
> Render<E> for Widget<E, L, R> {
fn min_size (&self, to: E::Size) -> Perhaps<E::Size> {
self.0(to)
}
fn render (&self, to: &mut E::Output) -> Usually<()> {
self.1(to)
}
}

View file

@ -1,49 +0,0 @@
#[cfg(test)] mod test_focus {
use super::focus::*;
#[test] fn test_focus () {
struct FocusTest {
focused: char,
cursor: (usize, usize)
}
impl HasFocus for FocusTest {
type Item = char;
fn focused (&self) -> Self::Item {
self.focused
}
fn set_focused (&mut self, to: Self::Item) {
self.focused = to
}
}
impl FocusGrid for FocusTest {
fn focus_cursor (&self) -> (usize, usize) {
self.cursor
}
fn focus_cursor_mut (&mut self) -> &mut (usize, usize) {
&mut self.cursor
}
fn focus_layout (&self) -> &[&[Self::Item]] {
&[
&['a', 'a', 'a', 'b', 'b', 'd'],
&['a', 'a', 'a', 'b', 'b', 'd'],
&['a', 'a', 'a', 'c', 'c', 'd'],
&['a', 'a', 'a', 'c', 'c', 'd'],
&['e', 'e', 'e', 'e', 'e', 'e'],
]
}
}
let mut tester = FocusTest { focused: 'a', cursor: (0, 0) };
tester.focus_right();
assert_eq!(tester.cursor.0, 3);
assert_eq!(tester.focused, 'b');
tester.focus_down();
assert_eq!(tester.cursor.1, 2);
assert_eq!(tester.focused, 'c');
}
}

View file

@ -1,268 +0,0 @@
use crate::*;
use super::*;
use KeyCode::{Char, Delete, Tab, Up, Down, Left, Right};
use ClockCommand::{Play, Pause};
use GrooveboxCommand::*;
use PhraseCommand::*;
use PhrasePoolCommand::*;
pub struct GrooveboxTui {
_jack: Arc<RwLock<JackClient>>,
pub player: MidiPlayer,
pub pool: PoolModel,
pub editor: MidiEditorModel,
pub sampler: crate::sampler::Sampler,
pub size: Measure<Tui>,
pub status: bool,
pub note_buf: Vec<u8>,
pub midi_buf: Vec<Vec<Vec<u8>>>,
pub perf: PerfModel,
}
from_jack!(|jack|GrooveboxTui {
let mut player = MidiPlayer::new(jack, "sequencer")?;
let phrase = Arc::new(RwLock::new(MidiClip::new(
"New", true, 4 * player.clock.timebase.ppq.get() as usize,
None, Some(ItemColor::random().into())
)));
player.play_phrase = Some((Moment::zero(&player.clock.timebase), Some(phrase.clone())));
let pool = crate::pool::PoolModel::from(&phrase);
let editor = crate::midi::MidiEditorModel::from(&phrase);
let sampler = crate::sampler::Sampler::new(jack, "sampler")?;
Self {
_jack: jack.clone(),
player,
pool,
editor,
sampler,
size: Measure::new(),
midi_buf: vec![vec![];65536],
note_buf: vec![],
perf: PerfModel::default(),
status: true,
}
});
audio!(|self: GrooveboxTui, client, scope|{
let t0 = self.perf.get_t0();
if Control::Quit == ClockAudio(&mut self.player).process(client, scope) {
return Control::Quit
}
if Control::Quit == PlayerAudio(
&mut self.player, &mut self.note_buf, &mut self.midi_buf
).process(client, scope) {
return Control::Quit
}
if Control::Quit == SamplerAudio(&mut self.sampler).process(client, scope) {
return Control::Quit
}
for RawMidi { time, bytes } in self.player.midi_ins[0].iter(scope) {
if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() {
match message {
MidiMessage::NoteOn { ref key, .. } => {
self.editor.set_note_point(key.as_int() as usize);
},
MidiMessage::Controller { controller, value } => {
if let Some(sample) = &self.sampler.mapped[self.editor.note_point()] {
let mut sample = sample.write().unwrap();
let percentage = value.as_int() as f64 / 127.;
match controller.as_int() {
20 => {
sample.start = (percentage * sample.end as f64) as usize;
},
21 => {
let length = sample.channels[0].len();
sample.end = sample.start + (percentage * (length as f64 - sample.start as f64)) as usize;
sample.end = sample.end.min(length);
},
24 => {
sample.gain = percentage as f32 * 2.0;
},
_ => {}
}
}
}
_ => {}
}
}
}
self.perf.update(t0, scope);
Control::Continue
});
has_clock!(|self:GrooveboxTui|&self.player.clock);
render!(<Tui>|self:GrooveboxTui|{
let w = self.size.w();
let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 };
let pool_w = if self.pool.visible { phrase_w } else { 0 };
let sampler_w = 11;
let note_pt = self.editor.note_point();
Fill::wh(lay!([
&self.size,
Fill::wh(Align::s(Fixed::h(2, GrooveboxStatus::from(self)))),
Tui::shrink_y(2, col!([
Fixed::h(2, row!([
Fixed::wh(5, 2, PlayPause(self.clock().is_rolling())),
Fixed::h(2, TransportView::from((self, self.player.play_phrase().as_ref().map(|(_,p)|
p.as_ref().map(|p|p.read().unwrap().color)
).flatten().clone(), true))),
])),
Tui::push_x(sampler_w, Fixed::h(1, row!([
PhraseSelector::play_phrase(&self.player),
PhraseSelector::next_phrase(&self.player),
]))),
row!([
Tui::split_n(false, 9,
col!([
row!(|add|{
if let Some(sample) = &self.sampler.mapped[note_pt] {
add(&format!("Sample {}", sample.read().unwrap().end))?;
}
add(&MidiEditStatus(&self.editor))?;
Ok(())
}),
lay!([
Outer(Style::default().fg(TuiTheme::g(128))),
Fill::w(Fixed::h(8, if let Some((_, sample)) = &self.sampler.recording {
SampleViewer(Some(sample.clone()))
} else if let Some(sample) = &self.sampler.mapped[note_pt] {
SampleViewer(Some(sample.clone()))
} else {
SampleViewer(None)
})),
]),
]),
Tui::split_w(false, pool_w,
Tui::pull_y(1, Fill::h(Align::e(PoolView(&self.pool)))),
Tui::split_e(false, sampler_w, Fill::wh(col!([
Meters(self.sampler.input_meter.as_ref()),
GrooveboxSamples(self),
])), Fill::h(&self.editor))
)
),
]),
]))
]))
});
struct GrooveboxSamples<'a>(&'a GrooveboxTui);
render!(<Tui>|self: GrooveboxSamples<'a>|{
let note_lo = self.0.editor.note_lo().load(Relaxed);
let note_pt = self.0.editor.note_point();
let note_hi = self.0.editor.note_hi();
Fill::wh(col!(note in (note_lo..=note_hi).rev() => {
let mut bg = if note == note_pt { TuiTheme::g(64) } else { Color::Reset };
let mut fg = TuiTheme::g(160);
if let Some((index, _)) = self.0.sampler.recording {
if note == index {
bg = Color::Rgb(64,16,0);
fg = Color::Rgb(224,64,32)
}
} else if self.0.sampler.mapped[note].is_some() {
fg = TuiTheme::g(224);
}
Tui::bg(bg, if let Some(sample) = &self.0.sampler.mapped[note] {
Tui::fg(fg, format!("{note:3} ?????? "))
} else {
Tui::fg(fg, format!("{note:3} (none) "))
})
}))
});
pub enum GrooveboxCommand {
History(isize),
Clock(ClockCommand),
Pool(PoolCommand),
Editor(PhraseCommand),
Enqueue(Option<Arc<RwLock<MidiClip>>>),
Sampler(SamplerCommand),
}
handle!(<Tui>|self: GrooveboxTui, input|GrooveboxCommand::execute_with_state(self, input));
input_to_command!(GrooveboxCommand: <Tui>|state: GrooveboxTui, input|match input.event() {
// TODO: k: toggle on-screen keyboard
key_pat!(Ctrl-Char('k')) => {
todo!("keyboard")
},
// Transport: Play from start or rewind to start
key_pat!(Char(' ')) => Clock(
if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) }
),
// Tab: Toggle visibility of phrase pool column
key_pat!(Tab) => Pool(PoolCommand::Show(!state.pool.visible)),
// q: Enqueue currently edited phrase
key_pat!(Char('q')) => Enqueue(Some(state.pool.phrase().clone())),
// 0: Enqueue phrase 0 (stop all)
key_pat!(Char('0')) => Enqueue(Some(state.pool.phrases()[0].clone())),
key_pat!(Shift-Char('R')) => Sampler(if state.sampler.recording.is_some() {
SamplerCommand::RecordFinish
} else {
SamplerCommand::RecordBegin(u7::from(state.editor.note_point() as u8))
}),
// e: Toggle between editing currently playing or other phrase
key_pat!(Char('e')) => if let Some((_, Some(playing))) = state.player.play_phrase() {
let editing = state.editor.phrase().as_ref().map(|p|p.read().unwrap().clone());
let selected = state.pool.phrase().clone();
Editor(Show(Some(if Some(selected.read().unwrap().clone()) != editing {
selected
} else {
playing.clone()
})))
} else {
return None
},
// For the rest, use the default keybindings of the components.
// The ones defined above supersede them.
_ => if let Some(command) = PhraseCommand::input_to_command(&state.editor, input) {
Editor(command)
} else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) {
Pool(command)
} else {
return None
}
});
command!(|self:GrooveboxCommand,state:GrooveboxTui|match self {
Self::Pool(cmd) => {
let mut default = |cmd: PoolCommand|cmd
.execute(&mut state.pool)
.map(|x|x.map(Pool));
match cmd {
// autoselect: automatically load selected phrase in editor
PoolCommand::Select(_) => {
let undo = default(cmd)?;
state.editor.set_phrase(Some(state.pool.phrase()));
undo
},
// update color in all places simultaneously
PoolCommand::Phrase(SetColor(index, _)) => {
let undo = default(cmd)?;
state.editor.set_phrase(Some(state.pool.phrase()));
undo
},
_ => default(cmd)?
}
},
Self::Editor(cmd) => {
let default = ||cmd.execute(&mut state.editor).map(|x|x.map(Editor));
match cmd {
_ => default()?
}
},
Self::Clock(cmd) => cmd.execute(state)?.map(Clock),
Self::Sampler(cmd) => cmd.execute(&mut state.sampler)?.map(Sampler),
Self::Enqueue(phrase) => {
state.player.enqueue_next(phrase.as_ref());
None
},
Self::History(delta) => {
todo!("undo/redo")
},
});

View file

@ -1,377 +0,0 @@
use crate::*;
pub use ::jack as libjack;
pub use ::jack::{
contrib::ClosureProcessHandler, NotificationHandler,
Client, AsyncClient, ClientOptions, ClientStatus,
ProcessScope, Control, CycleTimes, Frames,
Port, PortId, PortSpec, Unowned, MidiIn, MidiOut, AudioIn, AudioOut,
Transport, TransportState, MidiIter, MidiWriter, RawMidi,
};
pub mod activate;
pub(crate) use self::activate::*;
pub use self::activate::JackActivate;
pub mod client;
pub(crate) use self::client::*;
pub use self::client::JackClient;
pub mod jack_event;
pub(crate) use self::jack_event::*;
pub mod ports;
pub(crate) use self::ports::*;
pub use self::ports::RegisterPort;
/// Implement [TryFrom<&Arc<RwLock<JackClient>>>]: create app state from wrapped JACK handle.
#[macro_export] macro_rules! from_jack {
(|$jack:ident|$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)? $cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? TryFrom<&Arc<RwLock<JackClient>>> for $Struct $(<$($L),*$($T),*>)? {
type Error = Box<dyn std::error::Error>;
fn try_from ($jack: &Arc<RwLock<JackClient>>) -> Usually<Self> {
Ok($cb)
}
}
};
}
/// Implement [Audio]: provide JACK callbacks.
#[macro_export] macro_rules! audio {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?,$c:ident,$s:ident|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? Audio for $Struct $(<$($L),*$($T),*>)? {
#[inline] fn process (&mut $self, $c: &Client, $s: &ProcessScope) -> Control { $cb }
}
}
}
/// Trait for thing that has a JACK process callback.
pub trait Audio: Send + Sync {
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
}
}
}
/// Trait for things that wrap a JACK client.
pub trait AudioEngine {
fn transport (&self) -> Transport {
self.client().transport()
}
fn port_by_name (&self, name: &str) -> Option<Port<Unowned>> {
self.client().port_by_name(name)
}
fn register_port <PS: PortSpec> (&self, name: &str, spec: PS) -> Usually<Port<PS>> {
Ok(self.client().register_port(name, spec)?)
}
fn client (&self) -> &Client;
fn activate (
self,
process: impl FnMut(&Arc<RwLock<Self>>, &Client, &ProcessScope) -> Control + Send + 'static
) -> Usually<Arc<RwLock<Self>>> where Self: Send + Sync + 'static;
fn thread_init (&self, _: &Client) {}
unsafe fn shutdown (&mut self, _status: ClientStatus, _reason: &str) {}
fn freewheel (&mut self, _: &Client, _enabled: bool) {}
fn client_registration (&mut self, _: &Client, _name: &str, _reg: bool) {}
fn port_registration (&mut self, _: &Client, _id: PortId, _reg: bool) {}
fn ports_connected (&mut self, _: &Client, _a: PortId, _b: PortId, _are: bool) {}
fn sample_rate (&mut self, _: &Client, _frames: Frames) -> Control {
Control::Continue
}
fn port_rename (&mut self, _: &Client, _id: PortId, _old: &str, _new: &str) -> Control {
Control::Continue
}
fn graph_reorder (&mut self, _: &Client) -> Control {
Control::Continue
}
fn xrun (&mut self, _: &Client) -> Control {
Control::Continue
}
}
////////////////////////////////////////////////////////////////////////////////////
///// 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)?)
//}
//}
///// Collection of JACK ports as [AudioIn]/[AudioOut]/[MidiIn]/[MidiOut].
//#[derive(Default, Debug)]
//pub struct JackPorts {
//pub audio_ins: BTreeMap<String, Port<AudioIn>>,
//pub midi_ins: BTreeMap<String, Port<MidiIn>>,
//pub audio_outs: BTreeMap<String, Port<AudioOut>>,
//pub midi_outs: BTreeMap<String, Port<MidiOut>>,
//}
///// Collection of JACK ports as [Unowned].
//#[derive(Default, Debug)]
//pub struct UnownedJackPorts {
//pub audio_ins: BTreeMap<String, Port<Unowned>>,
//pub midi_ins: BTreeMap<String, Port<Unowned>>,
//pub audio_outs: BTreeMap<String, Port<Unowned>>,
//pub midi_outs: BTreeMap<String, Port<Unowned>>,
//}
//impl JackPorts {
//pub fn clone_unowned(&self) -> UnownedJackPorts {
//let mut unowned = UnownedJackPorts::default();
//for (name, port) in self.midi_ins.iter() {
//unowned.midi_ins.insert(name.clone(), port.clone_unowned());
//}
//for (name, port) in self.midi_outs.iter() {
//unowned.midi_outs.insert(name.clone(), port.clone_unowned());
//}
//for (name, port) in self.audio_ins.iter() {
//unowned.audio_ins.insert(name.clone(), port.clone_unowned());
//}
//for (name, port) in self.audio_outs.iter() {
//unowned
//.audio_outs
//.insert(name.clone(), port.clone_unowned());
//}
//unowned
//}
//}
///// Implement the `Ports` trait.
//#[macro_export]
//macro_rules! ports {
//($T:ty $({ $(audio: {
//$(ins: |$ai_arg:ident|$ai_impl:expr,)?
//$(outs: |$ao_arg:ident|$ao_impl:expr,)?
//})? $(midi: {
//$(ins: |$mi_arg:ident|$mi_impl:expr,)?
//$(outs: |$mo_arg:ident|$mo_impl:expr,)?
//})?})?) => {
//impl Ports for $T {$(
//$(
//$(fn audio_ins <'a> (&'a self) -> Usually<Vec<&'a Port<Unowned>>> {
//let cb = |$ai_arg:&'a Self|$ai_impl;
//cb(self)
//})?
//)?
//$(
//$(fn audio_outs <'a> (&'a self) -> Usually<Vec<&'a Port<Unowned>>> {
//let cb = (|$ao_arg:&'a Self|$ao_impl);
//cb(self)
//})?
//)?
//)? $(
//$(
//$(fn midi_ins <'a> (&'a self) -> Usually<Vec<&'a Port<Unowned>>> {
//let cb = (|$mi_arg:&'a Self|$mi_impl);
//cb(self)
//})?
//)?
//$(
//$(fn midi_outs <'a> (&'a self) -> Usually<Vec<&'a Port<Unowned>>> {
//let cb = (|$mo_arg:&'a Self|$mo_impl);
//cb(self)
//})?
//)?
//)?}
//};
//}
///// `JackDevice` factory. Creates JACK `Client`s, performs port registration
///// and activation, and encapsulates a `AudioComponent` into a `JackDevice`.
//pub struct Jack {
//pub client: Client,
//pub midi_ins: Vec<String>,
//pub audio_ins: Vec<String>,
//pub midi_outs: Vec<String>,
//pub audio_outs: Vec<String>,
//}
//impl Jack {
//pub fn new(name: &str) -> Usually<Self> {
//Ok(Self {
//midi_ins: vec![],
//audio_ins: vec![],
//midi_outs: vec![],
//audio_outs: vec![],
//client: Client::new(name, ClientOptions::NO_START_SERVER)?.0,
//})
//}
//pub fn run<'a: 'static, D, E>(
//self,
//state: impl FnOnce(JackPorts) -> Box<D>,
//) -> Usually<JackDevice<E>>
//where
//D: AudioComponent<E> + Sized + 'static,
//E: Engine + 'static,
//{
//let owned_ports = JackPorts {
//audio_ins: register_ports(&self.client, self.audio_ins, AudioIn::default())?,
//audio_outs: register_ports(&self.client, self.audio_outs, AudioOut::default())?,
//midi_ins: register_ports(&self.client, self.midi_ins, MidiIn::default())?,
//midi_outs: register_ports(&self.client, self.midi_outs, MidiOut::default())?,
//};
//let midi_outs = owned_ports
//.midi_outs
//.values()
//.map(|p| Ok(p.name()?))
//.collect::<Usually<Vec<_>>>()?;
//let midi_ins = owned_ports
//.midi_ins
//.values()
//.map(|p| Ok(p.name()?))
//.collect::<Usually<Vec<_>>>()?;
//let audio_outs = owned_ports
//.audio_outs
//.values()
//.map(|p| Ok(p.name()?))
//.collect::<Usually<Vec<_>>>()?;
//let audio_ins = owned_ports
//.audio_ins
//.values()
//.map(|p| Ok(p.name()?))
//.collect::<Usually<Vec<_>>>()?;
//let state = Arc::new(RwLock::new(state(owned_ports) as Box<dyn AudioComponent<E>>));
//let client = self.client.activate_async(
//Notifications(Box::new({
//let _state = state.clone();
//move |_event| {
//// FIXME: this deadlocks
////state.lock().unwrap().handle(&event).unwrap();
//}
//}) as Box<dyn Fn(JackEvent) + Send + Sync>),
//ClosureProcessHandler::new(Box::new({
//let state = state.clone();
//move |c: &Client, s: &ProcessScope| state.write().unwrap().process(c, s)
//}) as BoxedAudioHandler),
//)?;
//Ok(JackDevice {
//ports: UnownedJackPorts {
//audio_ins: query_ports(&client.as_client(), audio_ins),
//audio_outs: query_ports(&client.as_client(), audio_outs),
//midi_ins: query_ports(&client.as_client(), midi_ins),
//midi_outs: query_ports(&client.as_client(), midi_outs),
//},
//client,
//state,
//})
//}
//pub fn audio_in(mut self, name: &str) -> Self {
//self.audio_ins.push(name.to_string());
//self
//}
//pub fn audio_out(mut self, name: &str) -> Self {
//self.audio_outs.push(name.to_string());
//self
//}
//pub fn midi_in(mut self, name: &str) -> Self {
//self.midi_ins.push(name.to_string());
//self
//}
//pub fn midi_out(mut self, name: &str) -> Self {
//self.midi_outs.push(name.to_string());
//self
//}
//}

View file

@ -1,50 +0,0 @@
use crate::*;
pub trait JackActivate: Sized {
fn activate_with <T: Audio + 'static> (
self,
init: impl FnOnce(&Arc<RwLock<JackClient>>)->Usually<T>
)
-> Usually<Arc<RwLock<T>>>;
}
impl JackActivate for JackClient {
fn activate_with <T: Audio + 'static> (
self,
init: impl FnOnce(&Arc<RwLock<JackClient>>)->Usually<T>
)
-> Usually<Arc<RwLock<T>>>
{
let client = Arc::new(RwLock::new(self));
let target = Arc::new(RwLock::new(init(&client)?));
let event = Box::new(move|_|{/*TODO*/}) as Box<dyn Fn(JackEvent) + Send + Sync>;
let events = Notifications(event);
let frame = Box::new({
let target = target.clone();
move|c: &_, s: &_|if let Ok(mut target) = target.write() {
target.process(c, s)
} else {
Control::Quit
}
});
let frames = ClosureProcessHandler::new(frame as BoxedAudioHandler);
let mut buffer = Self::Activating;
std::mem::swap(&mut*client.write().unwrap(), &mut buffer);
*client.write().unwrap() = Self::Active(Client::from(buffer).activate_async(events, frames)?);
Ok(target)
}
}
/// A UI component that may be associated with a JACK client by the `Jack` factory.
pub trait AudioComponent<E: Engine>: Component<E> + Audio {
/// Perform type erasure for collecting heterogeneous devices.
fn boxed(self) -> Box<dyn AudioComponent<E>>
where
Self: Sized + 'static,
{
Box::new(self)
}
}
/// All things that implement the required traits can be treated as `AudioComponent`.
impl<E: Engine, W: Component<E> + Audio> AudioComponent<E> for W {}

View file

@ -1,50 +0,0 @@
use crate::*;
pub type DynamicAsyncClient = AsyncClient<DynamicNotifications, DynamicAudioHandler>;
pub type DynamicAudioHandler = ClosureProcessHandler<(), BoxedAudioHandler>;
pub type BoxedAudioHandler = Box<dyn FnMut(&Client, &ProcessScope) -> Control + Send>;
/// Wraps [Client] or [DynamicAsyncClient] in place.
#[derive(Debug)]
pub enum JackClient {
/// Before activation.
Inactive(Client),
/// During activation.
Activating,
/// After activation. Must not be dropped for JACK thread to persist.
Active(DynamicAsyncClient),
}
from!(|jack: JackClient|Client = match jack {
JackClient::Inactive(client) => client,
JackClient::Activating => panic!("jack client still activating"),
JackClient::Active(_) => panic!("jack client already activated"),
});
impl JackClient {
pub fn new (name: &str) -> Usually<Self> {
let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?;
Ok(Self::Inactive(client))
}
}
impl AudioEngine for JackClient {
fn client(&self) -> &Client {
match self {
Self::Inactive(ref client) => client,
Self::Activating => panic!("jack client has not finished activation"),
Self::Active(ref client) => client.as_client(),
}
}
fn activate(
self,
mut cb: impl FnMut(&Arc<RwLock<Self>>, &Client, &ProcessScope) -> Control + Send + 'static,
) -> Usually<Arc<RwLock<Self>>>
where
Self: Send + Sync + 'static
{
let client = Client::from(self);
let state = Arc::new(RwLock::new(Self::Activating));
let event = Box::new(move|_|{/*TODO*/}) as Box<dyn Fn(JackEvent) + Send + Sync>;
let events = Notifications(event);
let frame = Box::new({let state = state.clone(); move|c: &_, s: &_|cb(&state, c, s)});
let frames = ClosureProcessHandler::new(frame as BoxedAudioHandler);
*state.write().unwrap() = Self::Active(client.activate_async(events, frames)?);
Ok(state)
}
}

View file

@ -1,69 +0,0 @@
use crate::*;
#[derive(Debug, Clone, PartialEq)]
/// Event enum for JACK events.
pub enum JackEvent {
ThreadInit,
Shutdown(ClientStatus, String),
Freewheel(bool),
SampleRate(Frames),
ClientRegistration(String, bool),
PortRegistration(PortId, bool),
PortRename(PortId, String, String),
PortsConnected(PortId, PortId, bool),
GraphReorder,
XRun,
}
/// Notification handler used by the [Jack] factory
/// when constructing [JackDevice]s.
pub type DynamicNotifications = Notifications<Box<dyn Fn(JackEvent) + Send + Sync>>;
/// 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
}
}

View file

@ -1,109 +0,0 @@
use crate::*;
pub trait RegisterPort {
fn midi_in (&self, name: &str) -> Usually<Port<MidiIn>>;
fn midi_out (&self, name: &str) -> Usually<Port<MidiOut>>;
fn audio_in (&self, name: &str) -> Usually<Port<AudioIn>>;
fn audio_out (&self, name: &str) -> Usually<Port<AudioOut>>;
fn connect_midi_from (&self, my_input: &Port<MidiIn>, ports: &[String]) -> Usually<()>;
fn connect_midi_to (&self, my_output: &Port<MidiOut>, ports: &[String]) -> Usually<()>;
fn connect_audio_from (&self, my_input: &Port<AudioIn>, ports: &[String]) -> Usually<()>;
fn connect_audio_to (&self, my_output: &Port<AudioOut>, ports: &[String]) -> Usually<()>;
}
impl RegisterPort for Arc<RwLock<JackClient>> {
fn midi_in (&self, name: &str) -> Usually<Port<MidiIn>> {
Ok(self.read().unwrap().client().register_port(name, MidiIn::default())?)
}
fn midi_out (&self, name: &str) -> Usually<Port<MidiOut>> {
Ok(self.read().unwrap().client().register_port(name, MidiOut::default())?)
}
fn audio_out (&self, name: &str) -> Usually<Port<AudioOut>> {
Ok(self.read().unwrap().client().register_port(name, AudioOut::default())?)
}
fn audio_in (&self, name: &str) -> Usually<Port<AudioIn>> {
Ok(self.read().unwrap().client().register_port(name, AudioIn::default())?)
}
fn connect_midi_from (&self, input: &Port<MidiIn>, ports: &[String]) -> Usually<()> {
let jack = self.read().unwrap();
for port in ports.iter() {
if let Some(port) = jack.port_by_name(port).as_ref() {
jack.client().connect_ports(port, input)?;
} else {
panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names.");
}
}
Ok(())
}
fn connect_midi_to (&self, output: &Port<MidiOut>, ports: &[String]) -> Usually<()> {
let jack = self.read().unwrap();
for port in ports.iter() {
if let Some(port) = jack.port_by_name(port).as_ref() {
jack.client().connect_ports(output, port)?;
} else {
panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names.");
}
}
Ok(())
}
fn connect_audio_from (&self, input: &Port<AudioIn>, ports: &[String]) -> Usually<()> {
let jack = self.read().unwrap();
for port in ports.iter() {
if let Some(port) = jack.port_by_name(port).as_ref() {
jack.client().connect_ports(port, input)?;
} else {
panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names.");
}
}
Ok(())
}
fn connect_audio_to (&self, output: &Port<AudioOut>, ports: &[String]) -> Usually<()> {
let jack = self.read().unwrap();
for port in ports.iter() {
if let Some(port) = jack.port_by_name(port).as_ref() {
jack.client().connect_ports(output, port)?;
} else {
panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names.");
}
}
Ok(())
}
}
/// Trait for things that may expose JACK ports.
pub trait Ports {
fn audio_ins(&self) -> Usually<Vec<&Port<Unowned>>> {
Ok(vec![])
}
fn audio_outs(&self) -> Usually<Vec<&Port<Unowned>>> {
Ok(vec![])
}
fn midi_ins(&self) -> Usually<Vec<&Port<Unowned>>> {
Ok(vec![])
}
fn midi_outs(&self) -> Usually<Vec<&Port<Unowned>>> {
Ok(vec![])
}
}
fn register_ports<T: PortSpec + Copy>(
client: &Client,
names: Vec<String>,
spec: T,
) -> Usually<BTreeMap<String, Port<T>>> {
names
.into_iter()
.try_fold(BTreeMap::new(), |mut ports, name| {
let port = client.register_port(&name, spec)?;
ports.insert(name, port);
Ok(ports)
})
}
fn query_ports(client: &Client, names: Vec<String>) -> BTreeMap<String, Port<Unowned>> {
names.into_iter().fold(BTreeMap::new(), |mut ports, name| {
let port = client.port_by_name(&name).unwrap();
ports.insert(name, port);
ports
})
}

View file

@ -1,90 +0,0 @@
#![allow(unused)]
#![allow(clippy::unit_arg)]
pub mod core; pub use self::core::*;
pub mod time; pub(crate) use self::time::*;
pub mod space; pub(crate) use self::space::*;
pub mod tui; pub(crate) use self::tui::*;
pub use tui::*;
pub mod jack; pub(crate) use self::jack::*;
pub use self::jack::*;
pub mod midi; pub(crate) use self::midi::*;
pub mod meter; pub(crate) use self::meter::*;
pub mod piano_h; pub(crate) use self::piano_h::*;
pub mod transport; pub(crate) use self::transport::*;
pub use self::transport::TransportTui;
pub mod sequencer; pub(crate) use self::sequencer::*;
pub use self::sequencer::SequencerTui;
pub mod arranger; pub(crate) use self::arranger::*;
pub use self::arranger::ArrangerTui;
pub mod sampler; pub(crate) use self::sampler::*;
pub use self::sampler::{SamplerTui, Sampler, Sample, Voice};
pub mod mixer; pub(crate) use self::mixer::*;
pub use self::mixer::{Mixer, MixerTrack, MixerTrackDevice};
pub mod plugin; pub(crate) use self::plugin::*;
pub use self::plugin::*;
pub mod groovebox; pub(crate) use self::groovebox::*;
pub use self::groovebox::GrooveboxTui;
pub mod pool; pub(crate) use self::pool::*;
pub use self::pool::PoolModel;
pub mod status; pub(crate) use self::status::*;
pub use ::better_panic; pub(crate) use better_panic::{Settings, Verbosity};
pub use ::atomic_float; pub(crate) use atomic_float::*;
pub(crate) use std::sync::{Arc, Mutex, RwLock};
pub(crate) use std::sync::atomic::Ordering;
pub(crate) use std::sync::atomic::{AtomicBool, AtomicUsize};
pub(crate) use std::collections::BTreeMap;
pub(crate) use std::marker::PhantomData;
pub(crate) use std::thread::{spawn, JoinHandle};
pub(crate) use std::path::PathBuf;
pub(crate) use std::ffi::OsString;
pub(crate) use std::time::Duration;
pub(crate) use std::io::{Stdout, stdout};
pub(crate) use std::ops::{Add, Sub, Mul, Div, Rem};
pub(crate) use std::cmp::{Ord, Eq, PartialEq};
pub(crate) use std::fmt::{Debug, Display, Formatter};
pub use ::crossterm;
pub(crate) use crossterm::{ExecutableCommand};
pub(crate) use crossterm::terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode};
pub(crate) use crossterm::event::{KeyCode, KeyModifiers, KeyEvent, KeyEventKind, KeyEventState};
pub use ::ratatui; pub(crate) use ratatui::{
prelude::{Style, Buffer},
style::{Stylize, Modifier},
backend::{Backend, CrosstermBackend, ClearType}
};
pub use ::midly::{self, num::u7}; pub(crate) use ::midly::{
Smf,
MidiMessage,
TrackEventKind,
live::LiveEvent,
};
pub use ::palette; pub(crate) use ::palette::{
*,
convert::*,
okhsl::*
};
testmod! { test }

View file

@ -1,8 +0,0 @@
use crate::*;
pub struct Meters<'a>(pub &'a[f32]);
render!(<Tui>|self: Meters<'a>|col!([
&format!("L/{:>+9.3}", self.0[0]),
&format!("R/{:>+9.3}", self.0[1]),
]));

View file

@ -1,352 +0,0 @@
use crate::*;
pub(crate) mod midi_pool; pub(crate) use midi_pool::*;
pub(crate) mod midi_clip; pub(crate) use midi_clip::*;
pub(crate) mod midi_launch; pub(crate) use midi_launch::*;
pub(crate) mod midi_play; pub(crate) use midi_play::*;
pub(crate) mod midi_rec; pub(crate) use midi_rec::*;
pub(crate) mod midi_note; pub(crate) use midi_note::*;
pub(crate) mod midi_range; pub(crate) use midi_range::*;
pub(crate) mod midi_point; pub(crate) use midi_point::*;
pub(crate) mod midi_view; pub(crate) use midi_view::*;
pub(crate) mod midi_editor; pub(crate) use midi_editor::*;
/// Trait for thing that may receive MIDI.
pub trait HasMidiIns {
fn midi_ins (&self) -> &Vec<Port<MidiIn>>;
fn midi_ins_mut (&mut self) -> &mut Vec<Port<MidiIn>>;
fn has_midi_ins (&self) -> bool {
!self.midi_ins().is_empty()
}
}
/// Trait for thing that may output MIDI.
pub trait HasMidiOuts {
fn midi_outs (&self) -> &Vec<Port<MidiOut>>;
fn midi_outs_mut (&mut self) -> &mut Vec<Port<MidiOut>>;
fn has_midi_outs (&self) -> bool {
!self.midi_outs().is_empty()
}
/// Buffer for serializing a MIDI event. FIXME rename
fn midi_note (&mut self) -> &mut Vec<u8>;
}
/// Add "all notes off" to the start of a buffer.
pub fn all_notes_off (output: &mut [Vec<Vec<u8>>]) {
let mut buf = vec![];
let msg = MidiMessage::Controller { controller: 123.into(), value: 0.into() };
let evt = LiveEvent::Midi { channel: 0.into(), message: msg };
evt.write(&mut buf).unwrap();
output[0].push(buf);
}
/// Return boxed iterator of MIDI events
pub fn parse_midi_input (input: MidiIter) -> Box<dyn Iterator<Item=(usize, LiveEvent, &[u8])> + '_> {
Box::new(input.map(|RawMidi { time, bytes }|(
time as usize,
LiveEvent::parse(bytes).unwrap(),
bytes
)))
}
/// 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; },
_ => {}
}
}
pub fn to_note_name (n: usize) -> &'static str {
if n > 127 {
panic!("to_note_name({n}): must be 0-127");
}
MIDI_NOTE_NAMES[n]
}
pub const MIDI_NOTE_NAMES: [&str; 128] = [
"C0", "C#0", "D0", "D#0", "E0", "F0", "F#0", "G0", "G#0", "A0", "A#0", "B0",
"C1", "C#1", "D1", "D#1", "E1", "F1", "F#1", "G1", "G#1", "A1", "A#1", "B1",
"C2", "C#2", "D2", "D#2", "E2", "F2", "F#2", "G2", "G#2", "A2", "A#2", "B2",
"C3", "C#3", "D3", "D#3", "E3", "F3", "F#3", "G3", "G#3", "A3", "A#3", "B3",
"C4", "C#4", "D4", "D#4", "E4", "F4", "F#4", "G4", "G#4", "A4", "A#4", "B4",
"C5", "C#5", "D5", "D#5", "E5", "F5", "F#5", "G5", "G#5", "A5", "A#5", "B5",
"C6", "C#6", "D6", "D#6", "E6", "F6", "F#6", "G6", "G#6", "A6", "A#6", "B6",
"C7", "C#7", "D7", "D#7", "E7", "F7", "F#7", "G7", "G#7", "A7", "A#7", "B7",
"C8", "C#8", "D8", "D#8", "E8", "F8", "F#8", "G8", "G#8", "A8", "A#8", "B8",
"C9", "C#9", "D9", "D#9", "E9", "F9", "F#9", "G9", "G#9", "A9", "A#9", "B9",
"C10", "C#10", "D10", "D#10", "E10", "F10", "F#10", "G10",
];
pub trait MidiPlayerApi: MidiRecordApi + MidiPlaybackApi + Send + Sync {}
impl MidiPlayerApi for MidiPlayer {}
pub trait HasPlayer {
fn player (&self) -> &impl MidiPlayerApi;
fn player_mut (&mut self) -> &mut impl MidiPlayerApi;
}
#[macro_export] macro_rules! has_player {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? HasPlayer for $Struct $(<$($L),*$($T),*>)? {
fn player (&$self) -> &impl MidiPlayerApi { &$cb }
fn player_mut (&mut $self) -> &mut impl MidiPlayerApi { &mut$cb }
}
}
}
/// Contains state for playing a phrase
pub struct MidiPlayer {
/// State of clock and playhead
pub(crate) clock: ClockModel,
/// Start time and phrase being played
pub(crate) play_phrase: Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>,
/// Start time and next phrase
pub(crate) next_phrase: Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>,
/// Play input through output.
pub(crate) monitoring: bool,
/// Write input to sequence.
pub(crate) recording: bool,
/// Overdub input to sequence.
pub(crate) overdub: bool,
/// Send all notes off
pub(crate) reset: bool, // TODO?: after Some(nframes)
/// Record from MIDI ports to current sequence.
pub midi_ins: Vec<Port<MidiIn>>,
/// Play from current sequence to MIDI ports
pub midi_outs: Vec<Port<MidiOut>>,
/// Notes currently held at input
pub(crate) notes_in: Arc<RwLock<[bool; 128]>>,
/// Notes currently held at output
pub(crate) notes_out: Arc<RwLock<[bool; 128]>>,
/// MIDI output buffer
pub note_buf: Vec<u8>,
}
impl MidiPlayer {
pub fn new (jack: &Arc<RwLock<JackClient>>, name: &str) -> Usually<Self> {
Ok(Self {
clock: ClockModel::from(jack),
play_phrase: None,
next_phrase: None,
recording: false,
monitoring: false,
overdub: false,
notes_in: RwLock::new([false;128]).into(),
midi_ins: vec![
jack.midi_in(&format!("M/{name}"))?,
],
midi_outs: vec![
jack.midi_out(&format!("{name}/M"))?,
],
notes_out: RwLock::new([false;128]).into(),
reset: true,
note_buf: vec![0;8],
})
}
}
impl std::fmt::Debug for MidiPlayer {
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.debug_struct("MidiPlayer")
.field("clock", &self.clock)
.field("play_phrase", &self.play_phrase)
.field("next_phrase", &self.next_phrase)
.finish()
}
}
from!(|clock: &ClockModel| MidiPlayer = Self {
clock: clock.clone(),
midi_ins: vec![],
midi_outs: vec![],
note_buf: vec![0;8],
reset: true,
recording: false,
monitoring: false,
overdub: false,
play_phrase: None,
next_phrase: None,
notes_in: RwLock::new([false;128]).into(),
notes_out: RwLock::new([false;128]).into(),
});
from!(|state: (&ClockModel, &Arc<RwLock<MidiClip>>)|MidiPlayer = {
let (clock, phrase) = state;
let mut model = Self::from(clock);
model.play_phrase = Some((Moment::zero(&clock.timebase), Some(phrase.clone())));
model
});
has_clock!(|self: MidiPlayer|&self.clock);
impl HasMidiIns for MidiPlayer {
fn midi_ins (&self) -> &Vec<Port<MidiIn>> {
&self.midi_ins
}
fn midi_ins_mut (&mut self) -> &mut Vec<Port<MidiIn>> {
&mut self.midi_ins
}
}
impl HasMidiOuts for MidiPlayer {
fn midi_outs (&self) -> &Vec<Port<MidiOut>> {
&self.midi_outs
}
fn midi_outs_mut (&mut self) -> &mut Vec<Port<MidiOut>> {
&mut self.midi_outs
}
fn midi_note (&mut self) -> &mut Vec<u8> {
&mut self.note_buf
}
}
/// Hosts the JACK callback for a single MIDI player
pub struct PlayerAudio<'a, T: MidiPlayerApi>(
/// Player
pub &'a mut T,
/// Note buffer
pub &'a mut Vec<u8>,
/// Note chunk buffer
pub &'a mut Vec<Vec<Vec<u8>>>,
);
/// JACK process callback for a sequencer's phrase player/recorder.
impl<T: MidiPlayerApi> Audio for PlayerAudio<'_, T> {
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
let model = &mut self.0;
let note_buf = &mut self.1;
let midi_buf = &mut self.2;
// Clear output buffer(s)
model.clear(scope, midi_buf, false);
// Write chunk of phrase to output, handle switchover
if model.play(scope, note_buf, midi_buf) {
model.switchover(scope, note_buf, midi_buf);
}
if model.has_midi_ins() {
if model.recording() || model.monitoring() {
// Record and/or monitor input
model.record(scope, midi_buf)
} else if model.has_midi_outs() && model.monitoring() {
// Monitor input to output
model.monitor(scope, midi_buf)
}
}
// Write to output port(s)
model.write(scope, midi_buf);
Control::Continue
}
}
impl MidiRecordApi for MidiPlayer {
fn recording (&self) -> bool {
self.recording
}
fn recording_mut (&mut self) -> &mut bool {
&mut self.recording
}
fn monitoring (&self) -> bool {
self.monitoring
}
fn monitoring_mut (&mut self) -> &mut bool {
&mut self.monitoring
}
fn overdub (&self) -> bool {
self.overdub
}
fn overdub_mut (&mut self) -> &mut bool {
&mut self.overdub
}
fn notes_in (&self) -> &Arc<RwLock<[bool; 128]>> {
&self.notes_in
}
}
impl MidiPlaybackApi for MidiPlayer {
fn notes_out (&self) -> &Arc<RwLock<[bool; 128]>> {
&self.notes_out
}
}
impl HasPlayPhrase for MidiPlayer {
fn reset (&self) -> bool {
self.reset
}
fn reset_mut (&mut self) -> &mut bool {
&mut self.reset
}
fn play_phrase (&self) -> &Option<(Moment, Option<Arc<RwLock<MidiClip>>>)> {
&self.play_phrase
}
fn play_phrase_mut (&mut self) -> &mut Option<(Moment, Option<Arc<RwLock<MidiClip>>>)> {
&mut self.play_phrase
}
fn next_phrase (&self) -> &Option<(Moment, Option<Arc<RwLock<MidiClip>>>)> {
&self.next_phrase
}
fn next_phrase_mut (&mut self) -> &mut Option<(Moment, Option<Arc<RwLock<MidiClip>>>)> {
&mut self.next_phrase
}
}
//#[derive(Debug)]
//pub struct MIDIPlayer {
///// Global timebase
//pub clock: Arc<Clock>,
///// Start time and phrase being played
//pub play_phrase: Option<(Moment, Option<Arc<RwLock<Phrase>>>)>,
///// Start time and next phrase
//pub next_phrase: Option<(Moment, Option<Arc<RwLock<Phrase>>>)>,
///// Play input through output.
//pub monitoring: bool,
///// Write input to sequence.
//pub recording: bool,
///// Overdub input to sequence.
//pub overdub: bool,
///// Send all notes off
//pub reset: bool, // TODO?: after Some(nframes)
///// Record from MIDI ports to current sequence.
//pub midi_inputs: Vec<Port<MidiIn>>,
///// Play from current sequence to MIDI ports
//pub midi_outputs: Vec<Port<MidiOut>>,
///// MIDI output buffer
//pub midi_note: Vec<u8>,
///// MIDI output buffer
//pub midi_chunk: Vec<Vec<Vec<u8>>>,
///// Notes currently held at input
//pub notes_in: Arc<RwLock<[bool; 128]>>,
///// Notes currently held at output
//pub notes_out: Arc<RwLock<[bool; 128]>>,
//}
///// Methods used primarily by the process callback
//impl MIDIPlayer {
//pub fn new (
//jack: &Arc<RwLock<JackClient>>,
//clock: &Arc<Clock>,
//name: &str
//) -> Usually<Self> {
//let jack = jack.read().unwrap();
//Ok(Self {
//clock: clock.clone(),
//phrase: None,
//next_phrase: None,
//notes_in: Arc::new(RwLock::new([false;128])),
//notes_out: Arc::new(RwLock::new([false;128])),
//monitoring: false,
//recording: false,
//overdub: true,
//reset: true,
//midi_note: Vec::with_capacity(8),
//midi_chunk: vec![Vec::with_capacity(16);16384],
//midi_outputs: vec![
//jack.client().register_port(format!("{name}_out0").as_str(), MidiOut::default())?
//],
//midi_inputs: vec![
//jack.client().register_port(format!("{name}_in0").as_str(), MidiIn::default())?
//],
//})
//}
//}

View file

@ -1,109 +0,0 @@
use crate::*;
pub trait HasMidiClip {
fn phrase (&self) -> &Arc<RwLock<MidiClip>>;
}
#[macro_export] macro_rules! has_phrase {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? HasMidiClip for $Struct $(<$($L),*$($T),*>)? {
fn phrase (&$self) -> &Arc<RwLock<MidiClip>> { &$cb }
}
}
}
/// A MIDI sequence.
#[derive(Debug, Clone)]
pub struct MidiClip {
pub uuid: uuid::Uuid,
/// Name of phrase
pub name: String,
/// Temporal resolution in pulses per quarter note
pub ppq: usize,
/// Length of phrase in pulses
pub length: usize,
/// Notes in phrase
pub notes: MidiData,
/// Whether to loop the phrase or play it once
pub looped: bool,
/// Start of loop
pub loop_start: usize,
/// Length of loop
pub loop_length: usize,
/// All notes are displayed with minimum length
pub percussive: bool,
/// Identifying color of phrase
pub color: ItemPalette,
}
/// MIDI message structural
pub type MidiData = Vec<Vec<MidiMessage>>;
impl MidiClip {
pub fn new (
name: impl AsRef<str>,
looped: bool,
length: usize,
notes: Option<MidiData>,
color: Option<ItemPalette>,
) -> Self {
Self {
uuid: uuid::Uuid::new_v4(),
name: name.as_ref().to_string(),
ppq: PPQ,
length,
notes: notes.unwrap_or(vec![Vec::with_capacity(16);length]),
looped,
loop_start: 0,
loop_length: length,
percussive: true,
color: color.unwrap_or_else(ItemPalette::random)
}
}
pub fn set_length (&mut self, length: usize) {
self.length = length;
self.notes = vec![Vec::with_capacity(16);length];
}
pub fn duplicate (&self) -> Self {
let mut clone = self.clone();
clone.uuid = uuid::Uuid::new_v4();
clone
}
pub fn toggle_loop (&mut self) { self.looped = !self.looped; }
pub fn record_event (&mut self, pulse: usize, message: MidiMessage) {
if pulse >= self.length { panic!("extend phrase first") }
self.notes[pulse].push(message);
}
/// Check if a range `start..end` contains MIDI Note On `k`
pub fn contains_note_on (&self, k: u7, start: usize, end: usize) -> bool {
for events in self.notes[start.max(0)..end.min(self.notes.len())].iter() {
for event in events.iter() {
if let MidiMessage::NoteOn {key,..} = event { if *key == k { return true } }
}
}
false
}
}
impl Default for MidiClip {
fn default () -> Self {
Self::new(
"Stop",
false,
1,
Some(vec![vec![MidiMessage::Controller {
controller: 123.into(),
value: 0.into()
}]]),
Some(ItemColor::from(Color::Rgb(32, 32, 32)).into())
)
}
}
impl PartialEq for MidiClip {
fn eq (&self, other: &Self) -> bool {
self.uuid == other.uuid
}
}
impl Eq for MidiClip {}

View file

@ -1,225 +0,0 @@
use crate::*;
use KeyCode::{Char, Up, Down, Left, Right, Enter};
use PhraseCommand::*;
pub trait HasEditor {
fn editor (&self) -> &MidiEditorModel;
}
#[macro_export] macro_rules! has_editor {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? HasEditor for $Struct $(<$($L),*$($T),*>)? {
fn editor (&$self) -> &MidiEditorModel { &$cb }
}
}
}
#[derive(Clone, Debug)]
pub enum PhraseCommand {
// TODO: 1-9 seek markers that by default start every 8th of the phrase
AppendNote,
PutNote,
SetNoteCursor(usize),
SetNoteLength(usize),
SetNoteScroll(usize),
SetTimeCursor(usize),
SetTimeScroll(usize),
SetTimeZoom(usize),
SetTimeLock(bool),
Show(Option<Arc<RwLock<MidiClip>>>),
}
event_map_input_to_command!(Tui: MidiEditorModel: PhraseCommand: MidiEditorModel::KEYS);
impl MidiEditorModel {
const KEYS: KeyMapping<31, Self> = [
(kexp!(Ctrl-Alt-Up), &|s: &Self|SetNoteScroll(s.note_point() + 3)),
(kexp!(Ctrl-Alt-Down), &|s: &Self|SetNoteScroll(s.note_point().saturating_sub(3))),
(kexp!(Ctrl-Alt-Left), &|s: &Self|SetTimeScroll(s.time_point().saturating_sub(s.time_zoom().get()))),
(kexp!(Ctrl-Alt-Right), &|s: &Self|SetTimeScroll((s.time_point() + s.time_zoom().get()) % s.phrase_length())),
(kexp!(Ctrl-Up), &|s: &Self|SetNoteScroll(s.note_lo().get() + 1)),
(kexp!(Ctrl-Down), &|s: &Self|SetNoteScroll(s.note_lo().get().saturating_sub(1))),
(kexp!(Ctrl-Left), &|s: &Self|SetTimeScroll(s.time_start().get().saturating_sub(s.note_len()))),
(kexp!(Ctrl-Right), &|s: &Self|SetTimeScroll(s.time_start().get() + s.note_len())),
(kexp!(Alt-Up), &|s: &Self|SetNoteCursor(s.note_point() + 3)),
(kexp!(Alt-Down), &|s: &Self|SetNoteCursor(s.note_point().saturating_sub(3))),
(kexp!(Alt-Left), &|s: &Self|SetTimeCursor(s.time_point().saturating_sub(s.time_zoom().get()))),
(kexp!(Alt-Right), &|s: &Self|SetTimeCursor((s.time_point() + s.time_zoom().get()) % s.phrase_length())),
(kexp!(Up), &|s: &Self|SetNoteCursor(s.note_point() + 1)),
(kexp!(Char('w')), &|s: &Self|SetNoteCursor(s.note_point() + 1)),
(kexp!(Down), &|s: &Self|SetNoteCursor(s.note_point().saturating_sub(1))),
(kexp!(Char('s')), &|s: &Self|SetNoteCursor(s.note_point().saturating_sub(1))),
(kexp!(Left), &|s: &Self|SetTimeCursor(s.time_point().saturating_sub(s.note_len()))),
(kexp!(Char('a')), &|s: &Self|SetTimeCursor(s.time_point().saturating_sub(s.note_len()))),
(kexp!(Right), &|s: &Self|SetTimeCursor((s.time_point() + s.note_len()) % s.phrase_length())),
(kexp!(Char('d')), &|s: &Self|SetTimeCursor((s.time_point() + s.note_len()) % s.phrase_length())),
(kexp!(Char('z')), &|s: &Self|SetTimeLock(!s.time_lock().get())),
(kexp!(Char('-')), &|s: &Self|SetTimeZoom(Note::next(s.time_zoom().get()))),
(kexp!(Char('_')), &|s: &Self|SetTimeZoom(Note::next(s.time_zoom().get()))),
(kexp!(Char('=')), &|s: &Self|SetTimeZoom(Note::prev(s.time_zoom().get()))),
(kexp!(Char('+')), &|s: &Self|SetTimeZoom(Note::prev(s.time_zoom().get()))),
(kexp!(Enter), &|s: &Self|PutNote),
(kexp!(Ctrl-Enter), &|s: &Self|AppendNote),
(kexp!(Char(',')), &|s: &Self|SetNoteLength(Note::prev(s.note_len()))), // TODO: no 3plet
(kexp!(Char('.')), &|s: &Self|SetNoteLength(Note::next(s.note_len()))),
(kexp!(Char('<')), &|s: &Self|SetNoteLength(Note::prev(s.note_len()))), // TODO: 3plet
(kexp!(Char('>')), &|s: &Self|SetNoteLength(Note::next(s.note_len()))),
//// TODO: key_pat!(Char('/')) => // toggle 3plet
//// TODO: key_pat!(Char('?')) => // toggle dotted
];
fn phrase_length (&self) -> usize {
self.phrase().as_ref().map(|p|p.read().unwrap().length).unwrap_or(1)
}
}
impl Command<MidiEditorModel> for PhraseCommand {
fn execute (self, state: &mut MidiEditorModel) -> Perhaps<Self> {
use PhraseCommand::*;
match self {
Show(phrase) => { state.set_phrase(phrase.as_ref()); },
PutNote => { state.put_note(false); },
AppendNote => { state.put_note(true); },
SetTimeZoom(x) => { state.time_zoom().set(x); state.redraw(); },
SetTimeLock(x) => { state.time_lock().set(x); },
SetTimeScroll(x) => { state.time_start().set(x); },
SetNoteScroll(x) => { state.note_lo().set(x.min(127)); },
SetNoteLength(x) => { state.set_note_len(x); },
SetTimeCursor(x) => { state.set_time_point(x); },
SetNoteCursor(note) => { state.set_note_point(note.min(127)); },
_ => todo!("{:?}", self)
}
Ok(None)
}
}
/// Contains state for viewing and editing a phrase
pub struct MidiEditorModel {
/// Renders the phrase
pub mode: Box<dyn PhraseViewMode>,
pub size: Measure<Tui>
}
impl Default for MidiEditorModel {
fn default () -> Self {
let mut mode = Box::new(PianoHorizontal::new(None));
mode.redraw();
Self { mode, size: Measure::new() }
}
}
has_size!(<Tui>|self:MidiEditorModel|&self.size);
render!(<Tui>|self: MidiEditorModel|{
self.autoscroll();
self.autozoom();
&self.mode
});
//render!(<Tui>|self: MidiEditorModel|lay!(|add|{add(&self.size)?;add(self.mode)}));//bollocks
pub trait PhraseViewMode: Render<Tui> + HasSize<Tui> + MidiRange + MidiPoint + Debug + Send + Sync {
fn buffer_size (&self, phrase: &MidiClip) -> (usize, usize);
fn redraw (&mut self);
fn phrase (&self) -> &Option<Arc<RwLock<MidiClip>>>;
fn phrase_mut (&mut self) -> &mut Option<Arc<RwLock<MidiClip>>>;
fn set_phrase (&mut self, phrase: Option<&Arc<RwLock<MidiClip>>>) {
*self.phrase_mut() = phrase.cloned();
self.redraw();
}
}
impl MidiView<Tui> for MidiEditorModel {}
impl TimeRange for MidiEditorModel {
fn time_len (&self) -> &AtomicUsize { self.mode.time_len() }
fn time_zoom (&self) -> &AtomicUsize { self.mode.time_zoom() }
fn time_lock (&self) -> &AtomicBool { self.mode.time_lock() }
fn time_start (&self) -> &AtomicUsize { self.mode.time_start() }
fn time_axis (&self) -> &AtomicUsize { self.mode.time_axis() }
}
impl NoteRange for MidiEditorModel {
fn note_lo (&self) -> &AtomicUsize { self.mode.note_lo() }
fn note_axis (&self) -> &AtomicUsize { self.mode.note_axis() }
}
impl NotePoint for MidiEditorModel {
fn note_len (&self) -> usize { self.mode.note_len() }
fn set_note_len (&self, x: usize) { self.mode.set_note_len(x) }
fn note_point (&self) -> usize { self.mode.note_point() }
fn set_note_point (&self, x: usize) { self.mode.set_note_point(x) }
}
impl TimePoint for MidiEditorModel {
fn time_point (&self) -> usize { self.mode.time_point() }
fn set_time_point (&self, x: usize) { self.mode.set_time_point(x) }
}
impl PhraseViewMode for MidiEditorModel {
fn buffer_size (&self, phrase: &MidiClip) -> (usize, usize) {
self.mode.buffer_size(phrase)
}
fn redraw (&mut self) {
self.mode.redraw()
}
fn phrase (&self) -> &Option<Arc<RwLock<MidiClip>>> {
self.mode.phrase()
}
fn phrase_mut (&mut self) -> &mut Option<Arc<RwLock<MidiClip>>> {
self.mode.phrase_mut()
}
fn set_phrase (&mut self, phrase: Option<&Arc<RwLock<MidiClip>>>) {
self.mode.set_phrase(phrase)
}
}
impl MidiEditorModel {
/// Put note at current position
pub fn put_note (&mut self, advance: bool) {
let mut redraw = false;
if let Some(phrase) = self.phrase() {
let mut phrase = phrase.write().unwrap();
let note_start = self.time_point();
let note_point = self.note_point();
let note_len = self.note_len();
let note_end = note_start + (note_len.saturating_sub(1));
let key: u7 = u7::from(note_point as u8);
let vel: u7 = 100.into();
let length = phrase.length;
let note_end = note_end % length;
let note_on = MidiMessage::NoteOn { key, vel };
if !phrase.notes[note_start].iter().any(|msg|*msg == note_on) {
phrase.notes[note_start].push(note_on);
}
let note_off = MidiMessage::NoteOff { key, vel };
if !phrase.notes[note_end].iter().any(|msg|*msg == note_off) {
phrase.notes[note_end].push(note_off);
}
if advance {
self.set_time_point(note_end);
}
redraw = true;
}
if redraw {
self.mode.redraw();
}
}
}
from!(|phrase: &Arc<RwLock<MidiClip>>|MidiEditorModel = {
let mut model = Self::from(Some(phrase.clone()));
model.redraw();
model
});
from!(|phrase: Option<Arc<RwLock<MidiClip>>>|MidiEditorModel = {
let mut model = Self::default();
*model.phrase_mut() = phrase;
model.redraw();
model
});
impl std::fmt::Debug for MidiEditorModel {
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.debug_struct("MidiEditorModel")
.field("mode", &self.mode)
.finish()
}
}

View file

@ -1,34 +0,0 @@
use crate::*;
pub trait HasPlayPhrase: HasClock {
fn reset (&self) -> bool;
fn reset_mut (&mut self) -> &mut bool;
fn play_phrase (&self) -> &Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>;
fn play_phrase_mut (&mut self) -> &mut Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>;
fn next_phrase (&self) -> &Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>;
fn next_phrase_mut (&mut self) -> &mut Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>;
fn pulses_since_start (&self) -> Option<f64> {
if let Some((started, Some(_))) = self.play_phrase().as_ref() {
let elapsed = self.clock().playhead.pulse.get() - started.pulse.get();
Some(elapsed)
} else {
None
}
}
fn pulses_since_start_looped (&self) -> Option<f64> {
if let Some((started, Some(phrase))) = self.play_phrase().as_ref() {
let elapsed = self.clock().playhead.pulse.get() - started.pulse.get();
let length = phrase.read().unwrap().length.max(1); // prevent div0 on empty phrase
let elapsed = (elapsed as usize % length) as f64;
Some(elapsed)
} else {
None
}
}
fn enqueue_next (&mut self, phrase: Option<&Arc<RwLock<MidiClip>>>) {
let start = self.clock().next_launch_pulse() as f64;
let instant = Moment::from_pulse(self.clock().timebase(), start);
*self.next_phrase_mut() = Some((instant, phrase.cloned()));
*self.reset_mut() = true;
}
}

View file

@ -1,36 +0,0 @@
use crate::*;
pub struct Note;
impl Note {
/// (pulses, name), assuming 96 PPQ
pub const DURATIONS: [(usize, &str);26] = [
(1, "1/384"), (2, "1/192"),
(3, "1/128"), (4, "1/96"),
(6, "1/64"), (8, "1/48"),
(12, "1/32"), (16, "1/24"),
(24, "1/16"), (32, "1/12"),
(48, "1/8"), (64, "1/6"),
(96, "1/4"), (128, "1/3"),
(192, "1/2"), (256, "2/3"),
(384, "1/1"), (512, "4/3"),
(576, "3/2"), (768, "2/1"),
(1152, "3/1"), (1536, "4/1"),
(2304, "6/1"), (3072, "8/1"),
(3456, "9/1"), (6144, "16/1"),
];
/// Returns the next shorter length
pub fn prev (pulses: usize) -> usize {
for i in 1..=16 { let length = Note::DURATIONS[16-i].0; if length < pulses { return length } }
pulses
}
/// Returns the next longer length
pub fn next (pulses: usize) -> usize {
for (length, _) in &Note::DURATIONS { if *length > pulses { return *length } }
pulses
}
pub fn pulses_to_name (pulses: usize) -> &'static str {
for (length, name) in &Note::DURATIONS { if *length == pulses { return name } }
""
}
}

View file

@ -1,149 +0,0 @@
use crate::*;
pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts {
fn notes_out (&self) -> &Arc<RwLock<[bool;128]>>;
/// Clear the section of the output buffer that we will be using,
/// emitting "all notes off" at start of buffer if requested.
fn clear (
&mut self, scope: &ProcessScope, out: &mut [Vec<Vec<u8>>], reset: bool
) {
for frame in &mut out[0..scope.n_frames() as usize] {
frame.clear();
}
if reset {
all_notes_off(out);
}
}
/// Output notes from phrase to MIDI output ports.
fn play (
&mut self, scope: &ProcessScope, note_buf: &mut Vec<u8>, out: &mut [Vec<Vec<u8>>]
) -> bool {
if !self.clock().is_rolling() {
return false
}
// If a phrase is playing, write a chunk of MIDI events from it to the output buffer.
// If no phrase is playing, prepare for switchover immediately.
self.play_phrase().as_ref().map_or(true, |(started, phrase)|{
self.play_chunk(scope, note_buf, out, started, phrase)
})
}
/// Handle switchover from current to next playing phrase.
fn switchover (
&mut self, scope: &ProcessScope, note_buf: &mut Vec<u8>, out: &mut [Vec<Vec<u8>>]
) {
if !self.clock().is_rolling() {
return
}
let sample0 = scope.last_frame_time() as usize;
//let samples = scope.n_frames() as usize;
if let Some((start_at, phrase)) = &self.next_phrase() {
let start = start_at.sample.get() as usize;
let sample = self.clock().started.read().unwrap()
.as_ref().unwrap().sample.get() as usize;
// If it's time to switch to the next phrase:
if start <= sample0.saturating_sub(sample) {
// Samples elapsed since phrase was supposed to start
let _skipped = sample0 - start;
// Switch over to enqueued phrase
let started = Moment::from_sample(self.clock().timebase(), start as f64);
// Launch enqueued phrase
*self.play_phrase_mut() = Some((started, phrase.clone()));
// Unset enqueuement (TODO: where to implement looping?)
*self.next_phrase_mut() = None;
// Fill in remaining ticks of chunk from next phrase.
self.play(scope, note_buf, out);
}
}
}
fn play_chunk (
&self,
scope: &ProcessScope,
note_buf: &mut Vec<u8>,
out: &mut [Vec<Vec<u8>>],
started: &Moment,
phrase: &Option<Arc<RwLock<MidiClip>>>
) -> bool {
// First sample to populate. Greater than 0 means that the first
// pulse of the phrase falls somewhere in the middle of the chunk.
let sample = (scope.last_frame_time() as usize).saturating_sub(
started.sample.get() as usize +
self.clock().started.read().unwrap().as_ref().unwrap().sample.get() as usize
);
// Iterator that emits sample (index into output buffer at which to write MIDI event)
// paired with pulse (index into phrase from which to take the MIDI event) for each
// sample of the output buffer that corresponds to a MIDI pulse.
let pulses = self.clock().timebase().pulses_between_samples(sample, sample + scope.n_frames() as usize);
// Notes active during current chunk.
let notes = &mut self.notes_out().write().unwrap();
let length = phrase.as_ref().map_or(0, |p|p.read().unwrap().length);
for (sample, pulse) in pulses {
// If a next phrase is enqueued, and we're past the end of the current one,
// break the loop here (FIXME count pulse correctly)
let past_end = if phrase.is_some() { pulse >= length } else { true };
if self.next_phrase().is_some() && past_end {
return true
}
// If there's a currently playing phrase, output notes from it to buffer:
if let Some(ref phrase) = phrase {
Self::play_pulse(phrase, pulse, sample, note_buf, out, notes)
}
}
false
}
fn play_pulse (
phrase: &RwLock<MidiClip>,
pulse: usize,
sample: usize,
note_buf: &mut Vec<u8>,
out: &mut [Vec<Vec<u8>>],
notes: &mut [bool;128]
) {
// Source phrase from which the MIDI events will be taken.
let phrase = phrase.read().unwrap();
// Phrase with zero length is not processed
if phrase.length > 0 {
// Current pulse index in source phrase
let pulse = pulse % phrase.length;
// Output each MIDI event from phrase at appropriate frames of output buffer:
for message in phrase.notes[pulse].iter() {
// Clear output buffer for this MIDI event.
note_buf.clear();
// TODO: support MIDI channels other than CH1.
let channel = 0.into();
// Serialize MIDI event into message buffer.
LiveEvent::Midi { channel, message: *message }
.write(note_buf)
.unwrap();
// Append serialized message to output buffer.
out[sample].push(note_buf.clone());
// Update the list of currently held notes.
update_keys(&mut*notes, message);
}
}
}
/// Write a chunk of MIDI data from the output buffer to all assigned output ports.
fn write (&mut self, scope: &ProcessScope, out: &[Vec<Vec<u8>>]) {
let samples = scope.n_frames() as usize;
for port in self.midi_outs_mut().iter_mut() {
Self::write_port(&mut port.writer(scope), samples, out)
}
}
/// Write a chunk of MIDI data from the output buffer to an output port.
fn write_port (writer: &mut MidiWriter, samples: usize, out: &[Vec<Vec<u8>>]) {
for (time, events) in out.iter().enumerate().take(samples) {
for bytes in events.iter() {
writer.write(&RawMidi { time: time as u32, bytes }).unwrap_or_else(|_|{
panic!("Failed to write MIDI data: {bytes:?}");
});
}
}
}
}

View file

@ -1,50 +0,0 @@
use crate::*;
#[derive(Debug, Clone)]
pub struct MidiPointModel {
/// Time coordinate of cursor
pub time_point: Arc<AtomicUsize>,
/// Note coordinate of cursor
pub note_point: Arc<AtomicUsize>,
/// Length of note that will be inserted, in pulses
pub note_len: Arc<AtomicUsize>,
}
impl Default for MidiPointModel {
fn default () -> Self {
Self {
time_point: Arc::new(0.into()),
note_point: Arc::new(36.into()),
note_len: Arc::new(24.into()),
}
}
}
pub trait NotePoint {
fn note_len (&self) -> usize;
fn set_note_len (&self, x: usize);
fn note_point (&self) -> usize;
fn set_note_point (&self, x: usize);
fn note_end (&self) -> usize { self.note_point() + self.note_len() }
}
pub trait TimePoint {
fn time_point (&self) -> usize;
fn set_time_point (&self, x: usize);
}
pub trait MidiPoint: NotePoint + TimePoint {}
impl<T: NotePoint + TimePoint> MidiPoint for T {}
impl NotePoint for MidiPointModel {
fn note_len (&self) -> usize { self.note_len.load(Relaxed)}
fn set_note_len (&self, x: usize) { self.note_len.store(x, Relaxed) }
fn note_point (&self) -> usize { self.note_point.load(Relaxed).min(127) }
fn set_note_point (&self, x: usize) { self.note_point.store(x.min(127), Relaxed) }
}
impl TimePoint for MidiPointModel {
fn time_point (&self) -> usize { self.time_point.load(Relaxed) }
fn set_time_point (&self, x: usize) { self.time_point.store(x, Relaxed) }
}

View file

@ -1,93 +0,0 @@
use crate::*;
pub trait HasPhrases {
fn phrases (&self) -> &Vec<Arc<RwLock<MidiClip>>>;
fn phrases_mut (&mut self) -> &mut Vec<Arc<RwLock<MidiClip>>>;
}
#[macro_export] macro_rules! has_phrases {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? HasPhrases for $Struct $(<$($L),*$($T),*>)? {
fn phrases (&$self) -> &Vec<Arc<RwLock<MidiClip>>> { &$cb }
fn phrases_mut (&mut $self) -> &mut Vec<Arc<RwLock<MidiClip>>> { &mut$cb }
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum PhrasePoolCommand {
Add(usize, MidiClip),
Delete(usize),
Swap(usize, usize),
Import(usize, PathBuf),
Export(usize, PathBuf),
SetName(usize, String),
SetLength(usize, usize),
SetColor(usize, ItemColor),
}
impl<T: HasPhrases> Command<T> for PhrasePoolCommand {
fn execute (self, model: &mut T) -> Perhaps<Self> {
use PhrasePoolCommand::*;
Ok(match self {
Add(mut index, phrase) => {
let phrase = Arc::new(RwLock::new(phrase));
let phrases = model.phrases_mut();
if index >= phrases.len() {
index = phrases.len();
phrases.push(phrase)
} else {
phrases.insert(index, phrase);
}
Some(Self::Delete(index))
},
Delete(index) => {
let phrase = model.phrases_mut().remove(index).read().unwrap().clone();
Some(Self::Add(index, phrase))
},
Swap(index, other) => {
model.phrases_mut().swap(index, other);
Some(Self::Swap(index, other))
},
Import(index, path) => {
let bytes = std::fs::read(&path)?;
let smf = Smf::parse(bytes.as_slice())?;
let mut t = 0u32;
let mut events = vec![];
for track in smf.tracks.iter() {
for event in track.iter() {
t += event.delta.as_int();
if let TrackEventKind::Midi { channel, message } = event.kind {
events.push((t, channel.as_int(), message));
}
}
}
let mut phrase = MidiClip::new("imported", true, t as usize + 1, None, None);
for event in events.iter() {
phrase.notes[event.0 as usize].push(event.2);
}
Self::Add(index, phrase).execute(model)?
},
Export(_index, _path) => {
todo!("export phrase to midi file");
},
SetName(index, name) => {
let mut phrase = model.phrases()[index].write().unwrap();
let old_name = phrase.name.clone();
phrase.name = name;
Some(Self::SetName(index, old_name))
},
SetLength(index, length) => {
let mut phrase = model.phrases()[index].write().unwrap();
let old_len = phrase.length;
phrase.length = length;
Some(Self::SetLength(index, old_len))
},
SetColor(index, color) => {
let mut color = ItemPalette::from(color);
std::mem::swap(&mut color, &mut model.phrases()[index].write().unwrap().color);
Some(Self::SetColor(index, color.base))
},
})
}
}

View file

@ -1,64 +0,0 @@
use crate::*;
#[derive(Debug, Clone)]
pub struct MidiRangeModel {
pub time_len: Arc<AtomicUsize>,
/// Length of visible time axis
pub time_axis: Arc<AtomicUsize>,
/// Earliest time displayed
pub time_start: Arc<AtomicUsize>,
/// Time step
pub time_zoom: Arc<AtomicUsize>,
/// Auto rezoom to fit in time axis
pub time_lock: Arc<AtomicBool>,
/// Length of visible note axis
pub note_axis: Arc<AtomicUsize>,
// Lowest note displayed
pub note_lo: Arc<AtomicUsize>,
}
from!(|data:(usize, bool)|MidiRangeModel = Self {
time_len: Arc::new(0.into()),
note_axis: Arc::new(0.into()),
note_lo: Arc::new(0.into()),
time_axis: Arc::new(0.into()),
time_start: Arc::new(0.into()),
time_zoom: Arc::new(data.0.into()),
time_lock: Arc::new(data.1.into()),
});
pub trait TimeRange {
fn time_len (&self) -> &AtomicUsize;
fn time_zoom (&self) -> &AtomicUsize;
fn time_lock (&self) -> &AtomicBool;
fn time_start (&self) -> &AtomicUsize;
fn time_axis (&self) -> &AtomicUsize;
fn time_end (&self) -> usize {
self.time_start().get() + self.time_axis().get() * self.time_zoom().get()
}
}
pub trait NoteRange {
fn note_lo (&self) -> &AtomicUsize;
fn note_axis (&self) -> &AtomicUsize;
fn note_hi (&self) -> usize {
(self.note_lo().get() + self.note_axis().get().saturating_sub(1)).min(127)
}
}
pub trait MidiRange: TimeRange + NoteRange {}
impl<T: TimeRange + NoteRange> MidiRange for T {}
impl TimeRange for MidiRangeModel {
fn time_len (&self) -> &AtomicUsize { &self.time_len }
fn time_zoom (&self) -> &AtomicUsize { &self.time_zoom }
fn time_lock (&self) -> &AtomicBool { &self.time_lock }
fn time_start (&self) -> &AtomicUsize { &self.time_start }
fn time_axis (&self) -> &AtomicUsize { &self.time_axis }
}
impl NoteRange for MidiRangeModel {
fn note_lo (&self) -> &AtomicUsize { &self.note_lo }
fn note_axis (&self) -> &AtomicUsize { &self.note_axis }
}

View file

@ -1,82 +0,0 @@
use crate::*;
pub trait MidiRecordApi: HasClock + HasPlayPhrase + HasMidiIns {
fn notes_in (&self) -> &Arc<RwLock<[bool;128]>>;
fn recording (&self) -> bool;
fn recording_mut (&mut self) -> &mut bool;
fn toggle_record (&mut self) {
*self.recording_mut() = !self.recording();
}
fn monitoring (&self) -> bool;
fn monitoring_mut (&mut self) -> &mut bool;
fn toggle_monitor (&mut self) {
*self.monitoring_mut() = !self.monitoring();
}
fn monitor (&mut self, scope: &ProcessScope, midi_buf: &mut Vec<Vec<Vec<u8>>>) {
// For highlighting keys and note repeat
let notes_in = self.notes_in().clone();
let monitoring = self.monitoring();
for input in self.midi_ins_mut().iter() {
for (sample, event, bytes) in parse_midi_input(input.iter(scope)) {
if let LiveEvent::Midi { message, .. } = event {
if monitoring {
midi_buf[sample].push(bytes.to_vec());
}
update_keys(&mut notes_in.write().unwrap(), &message);
}
}
}
}
fn record (&mut self, scope: &ProcessScope, midi_buf: &mut Vec<Vec<Vec<u8>>>) {
if self.monitoring() {
self.monitor(scope, midi_buf);
}
if !self.clock().is_rolling() {
return
}
if let Some((started, ref clip)) = self.play_phrase().clone() {
self.record_clip(scope, started, clip, midi_buf);
}
if let Some((start_at, phrase)) = &self.next_phrase() {
self.record_next();
}
}
fn record_clip (
&mut self,
scope: &ProcessScope,
started: Moment,
phrase: &Option<Arc<RwLock<MidiClip>>>,
midi_buf: &mut Vec<Vec<Vec<u8>>>
) {
if let Some(phrase) = phrase {
let sample0 = scope.last_frame_time() as usize;
let start = started.sample.get() as usize;
let recording = self.recording();
let timebase = self.clock().timebase().clone();
let quant = self.clock().quant.get();
let mut phrase = phrase.write().unwrap();
let length = phrase.length;
for input in self.midi_ins_mut().iter() {
for (sample, event, bytes) in parse_midi_input(input.iter(scope)) {
if let LiveEvent::Midi { message, .. } = event {
phrase.record_event({
let sample = (sample0 + sample - start) as f64;
let pulse = timebase.samples_to_pulse(sample);
let quantized = (pulse / quant).round() * quant;
quantized as usize % length
}, message);
}
}
}
}
}
fn record_next (&mut self) {
// TODO switch to next clip and record into it
}
fn overdub (&self) -> bool;
fn overdub_mut (&mut self) -> &mut bool;
fn toggle_overdub (&mut self) {
*self.overdub_mut() = !self.overdub();
}
}

View file

@ -1,26 +0,0 @@
use crate::*;
pub trait MidiView<E: Engine>: MidiRange + MidiPoint + HasSize<E> {
/// Make sure cursor is within range
fn autoscroll (&self) {
let note_point = self.note_point().min(127);
let note_lo = self.note_lo().get();
let note_hi = self.note_hi();
if note_point < note_lo {
self.note_lo().set(note_point);
} else if note_point > note_hi {
self.note_lo().set((note_lo + note_point).saturating_sub(note_hi));
}
}
/// Make sure range is within display
fn autozoom (&self) {
let time_len = self.time_len().get();
let time_axis = self.time_axis().get();
let time_zoom = self.time_zoom().get();
//while time_len.div_ceil(time_zoom) > time_axis {
//println!("\r{time_len} {time_zoom} {time_axis}");
//time_zoom = Note::next(time_zoom);
//}
//self.time_zoom().set(time_zoom);
}
}

View file

@ -1,263 +0,0 @@
use crate::*;
#[derive(Debug)]
pub struct Mixer {
/// JACK client handle (needs to not be dropped for standalone mode to work).
pub jack: Arc<RwLock<JackClient>>,
pub name: String,
pub tracks: Vec<MixerTrack>,
pub selected_track: usize,
pub selected_column: usize,
}
/// A mixer track.
#[derive(Debug)]
pub struct MixerTrack {
pub name: String,
/// Inputs of 1st device
pub audio_ins: Vec<Port<AudioIn>>,
/// Outputs of last device
pub audio_outs: Vec<Port<AudioOut>>,
/// Device chain
pub devices: Vec<Box<dyn MixerTrackDevice>>,
}
audio!(|self: Mixer, _client, _scope|Control::Continue);
impl Mixer {
pub fn new (jack: &Arc<RwLock<JackClient>>, name: &str) -> Usually<Self> {
Ok(Self {
jack: jack.clone(),
name: name.into(),
selected_column: 0,
selected_track: 1,
tracks: vec![],
})
}
pub fn track_add (&mut self, name: &str, channels: usize) -> Usually<&mut Self> {
let track = MixerTrack::new(name)?;
self.tracks.push(track);
Ok(self)
}
pub fn track (&self) -> Option<&MixerTrack> {
self.tracks.get(self.selected_track)
}
}
impl MixerTrack {
pub fn new (name: &str) -> Usually<Self> {
Ok(Self {
name: name.to_string(),
audio_ins: vec![],
audio_outs: vec![],
devices: vec![],
//ports: JackPorts::default(),
//devices: vec![],
//device: 0,
})
}
//fn get_device_mut (&self, i: usize) -> Option<RwLockWriteGuard<Box<dyn AudioComponent<E>>>> {
//self.devices.get(i).map(|d|d.state.write().unwrap())
//}
//pub fn device_mut (&self) -> Option<RwLockWriteGuard<Box<dyn AudioComponent<E>>>> {
//self.get_device_mut(self.device)
//}
///// Add a device to the end of the chain.
//pub fn append_device (&mut self, device: JackDevice<E>) -> Usually<&mut JackDevice<E>> {
//self.devices.push(device);
//let index = self.devices.len() - 1;
//Ok(&mut self.devices[index])
//}
//pub fn add_device (&mut self, device: JackDevice<E>) {
//self.devices.push(device);
//}
//pub fn connect_first_device (&self) -> Usually<()> {
//if let (Some(port), Some(device)) = (&self.midi_out, self.devices.get(0)) {
//device.client.as_client().connect_ports(&port, &device.midi_ins()?[0])?;
//}
//Ok(())
//}
//pub fn connect_last_device (&self, app: &Track) -> Usually<()> {
//Ok(match self.devices.get(self.devices.len().saturating_sub(1)) {
//Some(device) => {
//app.audio_out(0).map(|left|device.connect_audio_out(0, &left)).transpose()?;
//app.audio_out(1).map(|right|device.connect_audio_out(1, &right)).transpose()?;
//()
//},
//None => ()
//})
//}
}
pub struct TrackView<'a> {
pub chain: Option<&'a MixerTrack>,
pub direction: Direction,
pub focused: bool,
pub entered: bool,
}
impl<'a> Render<Tui> for TrackView<'a> {
fn min_size (&self, area: [u16;2]) -> Perhaps<[u16;2]> {
todo!()
}
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
todo!();
//let mut area = to.area();
//if let Some(chain) = self.chain {
//match self.direction {
//Direction::Down => area.width = area.width.min(40),
//Direction::Right => area.width = area.width.min(10),
//_ => { unimplemented!() },
//}
//to.fill_bg(to.area(), Nord::bg_lo(self.focused, self.entered));
//let mut split = Stack::new(self.direction);
//for device in chain.devices.as_slice().iter() {
//split = split.add_ref(device);
//}
//let (area, areas) = split.render_areas(to)?;
//if self.focused && self.entered && areas.len() > 0 {
//Corners(Style::default().green().not_dim()).draw(to.with_rect(areas[0]))?;
//}
//Ok(Some(area))
//} else {
//let [x, y, width, height] = area;
//let label = "No chain selected";
//let x = x + (width - label.len() as u16) / 2;
//let y = y + height / 2;
//to.blit(&label, x, y, Some(Style::default().dim().bold()))?;
//Ok(Some(area))
//}
}
}
//impl Content<Tui> for Mixer<Tui> {
//fn content (&self) -> impl Render<Tui> {
//Stack::right(|add| {
//for channel in self.tracks.iter() {
//add(channel)?;
//}
//Ok(())
//})
//}
//}
//impl Content<Tui> for Track<Tui> {
//fn content (&self) -> impl Render<Tui> {
//TrackView {
//chain: Some(&self),
//direction: tek_core::Direction::Right,
//focused: true,
//entered: true,
////pub channels: u8,
////pub input_ports: Vec<Port<AudioIn>>,
////pub pre_gain_meter: f64,
////pub gain: f64,
////pub insert_ports: Vec<Port<AudioOut>>,
////pub return_ports: Vec<Port<AudioIn>>,
////pub post_gain_meter: f64,
////pub post_insert_meter: f64,
////pub level: f64,
////pub pan: f64,
////pub output_ports: Vec<Port<AudioOut>>,
////pub post_fader_meter: f64,
////pub route: String,
//}
//}
//}
handle!(<Tui>|self:Mixer,engine|{
if let TuiEvent::Input(crossterm::event::Event::Key(event)) = engine.event() {
match event.code {
//KeyCode::Char('c') => {
//if event.modifiers == KeyModifiers::CONTROL {
//self.exit();
//}
//},
KeyCode::Down => {
self.selected_track = (self.selected_track + 1) % self.tracks.len();
println!("{}", self.selected_track);
return Ok(Some(true))
},
KeyCode::Up => {
if self.selected_track == 0 {
self.selected_track = self.tracks.len() - 1;
} else {
self.selected_track -= 1;
}
println!("{}", self.selected_track);
return Ok(Some(true))
},
KeyCode::Left => {
if self.selected_column == 0 {
self.selected_column = 6
} else {
self.selected_column -= 1;
}
return Ok(Some(true))
},
KeyCode::Right => {
if self.selected_column == 6 {
self.selected_column = 0
} else {
self.selected_column += 1;
}
return Ok(Some(true))
},
_ => {
println!("\n{event:?}");
}
}
}
Ok(None)
});
handle!(<Tui>|self:MixerTrack,from|{
match from.event() {
//, NONE, "chain_cursor_up", "move cursor up", || {
key_pat!(KeyCode::Up) => {
Ok(Some(true))
},
// , NONE, "chain_cursor_down", "move cursor down", || {
key_pat!(KeyCode::Down) => {
Ok(Some(true))
},
// Left, NONE, "chain_cursor_left", "move cursor left", || {
key_pat!(KeyCode::Left) => {
//if let Some(track) = app.arranger.track_mut() {
//track.device = track.device.saturating_sub(1);
//return Ok(true)
//}
Ok(Some(true))
},
// , NONE, "chain_cursor_right", "move cursor right", || {
key_pat!(KeyCode::Right) => {
//if let Some(track) = app.arranger.track_mut() {
//track.device = (track.device + 1).min(track.devices.len().saturating_sub(1));
//return Ok(true)
//}
Ok(Some(true))
},
// , NONE, "chain_mode_switch", "switch the display mode", || {
key_pat!(KeyCode::Char('`')) => {
//app.chain_mode = !app.chain_mode;
Ok(Some(true))
},
_ => Ok(None)
}
});
pub enum MixerTrackCommand {}
//impl MixerTrackDevice for LV2Plugin {}
pub trait MixerTrackDevice: Debug + Send + Sync {
fn boxed (self) -> Box<dyn MixerTrackDevice> where Self: Sized + 'static {
Box::new(self)
}
}
impl MixerTrackDevice for Sampler {}
impl MixerTrackDevice for Plugin {}

View file

@ -1,244 +0,0 @@
use crate::*;
use super::*;
mod piano_h_cursor;
use self::piano_h_cursor::*;
mod piano_h_keys;
pub(crate) use self::piano_h_keys::*;
pub use self::piano_h_keys::render_keys_v;
mod piano_h_notes; use self::piano_h_notes::*;
mod piano_h_time; use self::piano_h_time::*;
pub(crate) fn note_y_iter (note_lo: usize, note_hi: usize, y0: u16) -> impl Iterator<Item=(usize, u16, usize)> {
(note_lo..=note_hi).rev().enumerate().map(move|(y, n)|(y, y0 + y as u16, n))
}
/// A phrase, rendered as a horizontal piano roll.
pub struct PianoHorizontal {
phrase: Option<Arc<RwLock<MidiClip>>>,
/// Buffer where the whole phrase is rerendered on change
buffer: BigBuffer,
/// Size of actual notes area
size: Measure<Tui>,
/// The display window
range: MidiRangeModel,
/// The note cursor
point: MidiPointModel,
/// The highlight color palette
color: ItemPalette,
/// Width of the keyboard
keys_width: u16,
}
impl PianoHorizontal {
pub fn new (phrase: Option<&Arc<RwLock<MidiClip>>>) -> Self {
let size = Measure::new();
let mut range = MidiRangeModel::from((24, true));
range.time_axis = size.x.clone();
range.note_axis = size.y.clone();
let color = phrase.as_ref()
.map(|p|p.read().unwrap().color)
.unwrap_or(ItemPalette::from(ItemColor::from(TuiTheme::g(64))));
Self {
buffer: Default::default(),
point: MidiPointModel::default(),
phrase: phrase.cloned(),
size,
range,
color,
keys_width: 5
}
}
}
render!(<Tui>|self: PianoHorizontal|{
let (color, name, length, looped) = if let Some(phrase) = self.phrase().as_ref().map(|p|p.read().unwrap()) {
(phrase.color, phrase.name.clone(), phrase.length, phrase.looped)
} else {
(ItemPalette::from(TuiTheme::g(64)), String::new(), 0, false)
};
let field = move|x, y|row!([
Tui::fg_bg(color.lighter.rgb, color.darker.rgb, Tui::bold(true, x)),
Tui::fg_bg(color.lighter.rgb, color.dark.rgb, &y),
]);
let keys_width = 5;
let keys = move||PianoHorizontalKeys(self);
let timeline = move||PianoHorizontalTimeline(self);
let notes = move||PianoHorizontalNotes(self);
let cursor = move||PianoHorizontalCursor(self);
let border = Fill::wh(Outer(Style::default().fg(self.color.dark.rgb).bg(self.color.darkest.rgb)));
let with_border = |x|lay!([border, Tui::inset_xy(0, 0, &x)]);
with_border(lay!([
Tui::push_x(0, row!(![
//" ",
field("Edit:", name.to_string()), " ",
field("Length:", length.to_string()), " ",
field("Loop:", looped.to_string())
])),
Tui::inset_xy(0, 1, Fill::wh(Bsp::s(
Fixed::h(1, Bsp::e(
Fixed::w(self.keys_width, ""),
Fill::w(timeline()),
)),
Bsp::e(
Fixed::w(self.keys_width, keys()),
Fill::wh(lay!([
&self.size,
Fill::wh(lay!([
Fill::wh(notes()),
Fill::wh(cursor()),
]))
])),
),
)))
]))
});
impl PianoHorizontal {
/// Draw the piano roll foreground using full blocks on note on and half blocks on legato: █▄ █▄ █▄
fn draw_bg (buf: &mut BigBuffer, phrase: &MidiClip, zoom: usize, note_len: usize) {
for (y, note) in (0..127).rev().enumerate() {
for (x, time) in (0..buf.width).map(|x|(x, x*zoom)) {
let cell = buf.get_mut(x, y).unwrap();
cell.set_bg(phrase.color.darkest.rgb);
cell.set_fg(phrase.color.darker.rgb);
cell.set_char(if time % 384 == 0 {
'│'
} else if time % 96 == 0 {
'╎'
} else if time % note_len == 0 {
'┊'
} else if (127 - note) % 12 == 1 {
'='
} else {
'·'
});
}
}
}
/// Draw the piano roll background using full blocks on note on and half blocks on legato: █▄ █▄ █▄
fn draw_fg (buf: &mut BigBuffer, phrase: &MidiClip, zoom: usize) {
let style = Style::default().fg(phrase.color.base.rgb);//.bg(Color::Rgb(0, 0, 0));
let mut notes_on = [false;128];
for (x, time_start) in (0..phrase.length).step_by(zoom).enumerate() {
for (y, note) in (0..127).rev().enumerate() {
if let Some(cell) = buf.get_mut(x, note) {
if notes_on[note] {
cell.set_char('▂');
cell.set_style(style);
}
}
}
let time_end = time_start + zoom;
for time in time_start..time_end.min(phrase.length) {
for event in phrase.notes[time].iter() {
match event {
MidiMessage::NoteOn { key, .. } => {
let note = key.as_int() as usize;
let cell = buf.get_mut(x, note).unwrap();
cell.set_char('█');
cell.set_style(style);
notes_on[note] = true
},
MidiMessage::NoteOff { key, .. } => {
notes_on[key.as_int() as usize] = false
},
_ => {}
}
}
}
}
}
}
has_size!(<Tui>|self:PianoHorizontal|&self.size);
impl TimeRange for PianoHorizontal {
fn time_len (&self) -> &AtomicUsize { self.range.time_len() }
fn time_zoom (&self) -> &AtomicUsize { self.range.time_zoom() }
fn time_lock (&self) -> &AtomicBool { self.range.time_lock() }
fn time_start (&self) -> &AtomicUsize { self.range.time_start() }
fn time_axis (&self) -> &AtomicUsize { self.range.time_axis() }
}
impl NoteRange for PianoHorizontal {
fn note_lo (&self) -> &AtomicUsize { self.range.note_lo() }
fn note_axis (&self) -> &AtomicUsize { self.range.note_axis() }
}
impl NotePoint for PianoHorizontal {
fn note_len (&self) -> usize { self.point.note_len() }
fn set_note_len (&self, x: usize) { self.point.set_note_len(x) }
fn note_point (&self) -> usize { self.point.note_point() }
fn set_note_point (&self, x: usize) { self.point.set_note_point(x) }
}
impl TimePoint for PianoHorizontal {
fn time_point (&self) -> usize { self.point.time_point() }
fn set_time_point (&self, x: usize) { self.point.set_time_point(x) }
}
impl PhraseViewMode for PianoHorizontal {
fn phrase (&self) -> &Option<Arc<RwLock<MidiClip>>> {
&self.phrase
}
fn phrase_mut (&mut self) -> &mut Option<Arc<RwLock<MidiClip>>> {
&mut self.phrase
}
/// Determine the required space to render the phrase.
fn buffer_size (&self, phrase: &MidiClip) -> (usize, usize) {
(phrase.length / self.range.time_zoom().get(), 128)
}
fn redraw (&mut self) {
let buffer = if let Some(phrase) = self.phrase.as_ref() {
let phrase = phrase.read().unwrap();
let buf_size = self.buffer_size(&phrase);
let mut buffer = BigBuffer::from(buf_size);
let note_len = self.note_len();
let time_zoom = self.time_zoom().get();
self.time_len().set(phrase.length);
PianoHorizontal::draw_bg(&mut buffer, &phrase, time_zoom, note_len);
PianoHorizontal::draw_fg(&mut buffer, &phrase, time_zoom);
buffer
} else {
Default::default()
};
self.buffer = buffer
}
fn set_phrase (&mut self, phrase: Option<&Arc<RwLock<MidiClip>>>) {
*self.phrase_mut() = phrase.cloned();
self.color = phrase.map(|p|p.read().unwrap().color)
.unwrap_or(ItemPalette::from(ItemColor::from(TuiTheme::g(64))));
self.redraw();
}
}
impl std::fmt::Debug for PianoHorizontal {
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
f.debug_struct("PianoHorizontal")
.field("time_zoom", &self.range.time_zoom)
.field("buffer", &format!("{}x{}", self.buffer.width, self.buffer.height))
.finish()
}
}
// Update sequencer playhead indicator
//self.now().set(0.);
//if let Some((ref started_at, Some(ref playing))) = self.player.play_phrase {
//let phrase = phrase.read().unwrap();
//if *playing.read().unwrap() == *phrase {
//let pulse = self.current().pulse.get();
//let start = started_at.pulse.get();
//let now = (pulse - start) % phrase.length as f64;
//self.now().set(now);
//}
//}

View file

@ -1,33 +0,0 @@
use crate::*;
use super::note_y_iter;
pub struct PianoHorizontalCursor<'a>(pub(crate) &'a PianoHorizontal);
render!(<Tui>|self: PianoHorizontalCursor<'a>|render(|to|Ok({
let style = Some(Style::default().fg(self.0.color.lightest.rgb));
let note_hi = self.0.note_hi();
let note_len = self.0.note_len();
let note_lo = self.0.note_lo().get();
let note_point = self.0.note_point();
let time_point = self.0.time_point();
let time_start = self.0.time_start().get();
let time_zoom = self.0.time_zoom().get();
let [x0, y0, w, _] = to.area().xywh();
for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) {
if note == note_point {
for x in 0..w {
let screen_x = x0 + x;
let time_1 = time_start + x as usize * time_zoom;
let time_2 = time_1 + time_zoom;
if time_1 <= time_point && time_point < time_2 {
to.blit(&"", screen_x, screen_y, style);
let tail = note_len as u16 / time_zoom as u16;
for x_tail in (screen_x + 1)..(screen_x + tail) {
to.blit(&"", x_tail, screen_y, style);
}
break
}
}
break
}
}
})));

View file

@ -1,56 +0,0 @@
use crate::*;
use super::note_y_iter;
pub struct PianoHorizontalKeys<'a>(pub(crate) &'a PianoHorizontal);
render!(<Tui>|self: PianoHorizontalKeys<'a>|render(|to|Ok(render_keys_v(to, self))));
has_color!(|self: PianoHorizontalKeys<'a>|self.0.color.base);
impl<'a> NoteRange for PianoHorizontalKeys<'a> {
fn note_lo (&self) -> &AtomicUsize { &self.0.note_lo() }
fn note_axis (&self) -> &AtomicUsize { &self.0.note_axis() }
}
impl<'a> NotePoint for PianoHorizontalKeys<'a> {
fn note_len (&self) -> usize { self.0.note_len() }
fn set_note_len (&self, x: usize) { self.0.set_note_len(x) }
fn note_point (&self) -> usize { self.0.note_point() }
fn set_note_point (&self, x: usize) { self.0.set_note_point(x) }
}
pub fn render_keys_v <T: HasColor + NoteRange + NotePoint> (to: &mut TuiOutput, state: &T) {
let color = state.color();
let note_lo = state.note_lo().get();
let note_hi = state.note_hi();
let note_point = state.note_point();
let [x, y0, w, h] = to.area().xywh();
let key_style = Some(Style::default().fg(Color::Rgb(192, 192, 192)).bg(Color::Rgb(0, 0, 0)));
let off_style = Some(Style::default().fg(TuiTheme::g(160)));
let on_style = Some(Style::default().fg(TuiTheme::g(255)).bg(color.rgb).bold());
for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) {
to.blit(&to_key(note), x, screen_y, key_style);
if note > 127 {
continue
}
if note == note_point {
to.blit(&format!("{:<5}", to_note_name(note)), x, screen_y, on_style)
} else {
to.blit(&to_note_name(note), x, screen_y, off_style)
};
}
}
fn to_key (note: usize) -> &'static str {
match note % 12 {
11 => "████▌",
10 => " ",
9 => "████▌",
8 => " ",
7 => "████▌",
6 => " ",
5 => "████▌",
4 => "████▌",
3 => " ",
2 => "████▌",
1 => " ",
0 => "████▌",
_ => unreachable!(),
}
}

View file

@ -1,46 +0,0 @@
use crate::*;
use super::note_y_iter;
pub struct PianoHorizontalNotes<'a>(pub(crate) &'a PianoHorizontal);
render!(<Tui>|self: PianoHorizontalNotes<'a>|render(|to|Ok({
let time_start = self.0.time_start().get();
let note_lo = self.0.note_lo().get();
let note_hi = self.0.note_hi();
let note_point = self.0.note_point();
let source = &self.0.buffer;
let [x0, y0, w, h] = to.area().xywh();
if h as usize != self.0.note_axis().get() {
panic!("area height mismatch");
}
for (area_x, screen_x) in (x0..x0+w).enumerate() {
for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) {
//if area_x % 10 == 0 {
//to.blit(&format!("{area_y} {note}"), screen_x, screen_y, None);
//}
let source_x = time_start + area_x;
let source_y = note_hi - area_y;
////// TODO: enable loop rollover:
//////let source_x = (time_start + area_x) % source.width.max(1);
//////let source_y = (note_hi - area_y) % source.height.max(1);
let is_in_x = source_x < source.width;
let is_in_y = source_y < source.height;
if is_in_x && is_in_y {
if let Some(source_cell) = source.get(source_x, source_y) {
*to.buffer.get_mut(screen_x, screen_y) = source_cell.clone();
}
}
}
}
//let debug = true;
//if debug {
//let x0=20+x0;
//to.blit(&format!("KYP "), x0, y0, None);
//to.blit(&format!("x0={x0} "), x0, y0+1, None);
//to.blit(&format!("y0={y0} "), x0, y0+2, None);
//to.blit(&format!("note_lo={note_lo}, {} ", to_note_name(note_lo)), x0, y0+3, None);
//to.blit(&format!("note_hi={note_hi}, {} ", to_note_name(note_hi)), x0, y0+4, None);
//to.blit(&format!("note_point={note_point}, {} ", to_note_name(note_point)), x0, y0+5, None);
//to.blit(&format!("time_start={time_start} "), x0, y0+6, None);
//return Ok(());
//}
})));

View file

@ -1,20 +0,0 @@
use crate::*;
pub struct PianoHorizontalTimeline<'a>(pub(crate) &'a PianoHorizontal);
render!(<Tui>|self: PianoHorizontalTimeline<'a>|render(|to|{
let [x, y, w, h] = to.area();
let style = Some(Style::default().dim());
let length = self.0.phrase.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1);
for (area_x, screen_x) in (0..w).map(|d|(d, d+x)) {
let t = area_x as usize * self.0.time_zoom().get();
if t < length {
to.blit(&"|", screen_x, y, style);
}
}
Ok(())
}));
//Tui::fg_bg(
//self.0.color.lightest.rgb,
//self.0.color.darkest.rgb,
//format!("{}*{}", self.0.time_start(), self.0.time_zoom()).as_str()
//));

View file

@ -1 +0,0 @@
// TODO

View file

@ -1,266 +0,0 @@
use crate::*;
pub mod lv2; pub(crate) use lv2::*;
pub use self::lv2::LV2Plugin;
/// A plugin device.
#[derive(Debug)]
pub struct Plugin {
/// JACK client handle (needs to not be dropped for standalone mode to work).
pub jack: Arc<RwLock<JackClient>>,
pub name: String,
pub path: Option<String>,
pub plugin: Option<PluginKind>,
pub selected: usize,
pub mapping: bool,
pub midi_ins: Vec<Port<MidiIn>>,
pub midi_outs: Vec<Port<MidiOut>>,
pub audio_ins: Vec<Port<AudioIn>>,
pub audio_outs: Vec<Port<AudioOut>>,
}
/// Supported plugin formats.
#[derive(Default)]
pub enum PluginKind {
#[default] None,
LV2(LV2Plugin),
VST2 { instance: () /*::vst::host::PluginInstance*/ },
VST3,
}
impl Debug for PluginKind {
fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
write!(f, "{}", match self {
Self::None => "(none)",
Self::LV2(_) => "LV2",
Self::VST2{..} => "VST2",
Self::VST3 => "VST3",
})
}
}
impl Plugin {
pub fn new_lv2 (
jack: &Arc<RwLock<JackClient>>,
name: &str,
path: &str,
) -> Usually<Self> {
Ok(Self {
jack: jack.clone(),
name: name.into(),
path: Some(String::from(path)),
plugin: Some(PluginKind::LV2(LV2Plugin::new(path)?)),
selected: 0,
mapping: false,
midi_ins: vec![],
midi_outs: vec![],
audio_ins: vec![],
audio_outs: vec![],
})
}
}
pub struct PluginAudio(Arc<RwLock<Plugin>>);
from!(|model: &Arc<RwLock<Plugin>>| PluginAudio = Self(model.clone()));
audio!(|self: PluginAudio, client, scope|{
let state = &mut*self.0.write().unwrap();
match state.plugin.as_mut() {
Some(PluginKind::LV2(LV2Plugin {
features,
ref mut instance,
ref mut input_buffer,
..
})) => {
let urid = features.midi_urid();
input_buffer.clear();
for port in state.midi_ins.iter() {
let mut atom = ::livi::event::LV2AtomSequence::new(
&features,
scope.n_frames() as usize
);
for event in port.iter(scope) {
match event.bytes.len() {
3 => atom.push_midi_event::<3>(
event.time as i64,
urid,
&event.bytes[0..3]
).unwrap(),
_ => {}
}
}
input_buffer.push(atom);
}
let mut outputs = vec![];
for _ in state.midi_outs.iter() {
outputs.push(::livi::event::LV2AtomSequence::new(
features,
scope.n_frames() as usize
));
}
let ports = ::livi::EmptyPortConnections::new()
.with_atom_sequence_inputs(input_buffer.iter())
.with_atom_sequence_outputs(outputs.iter_mut())
.with_audio_inputs(state.audio_ins.iter().map(|o|o.as_slice(scope)))
.with_audio_outputs(state.audio_outs.iter_mut().map(|o|o.as_mut_slice(scope)));
unsafe {
instance.run(scope.n_frames() as usize, ports).unwrap()
};
},
_ => todo!("only lv2 is supported")
}
Control::Continue
});
//fn jack_from_lv2 (name: &str, plugin: &::livi::Plugin) -> Usually<Jack> {
//let counts = plugin.port_counts();
//let mut jack = Jack::new(name)?;
//for i in 0..counts.atom_sequence_inputs {
//jack = jack.midi_in(&format!("midi-in-{i}"))
//}
//for i in 0..counts.atom_sequence_outputs {
//jack = jack.midi_out(&format!("midi-out-{i}"));
//}
//for i in 0..counts.audio_inputs {
//jack = jack.audio_in(&format!("audio-in-{i}"));
//}
//for i in 0..counts.audio_outputs {
//jack = jack.audio_out(&format!("audio-out-{i}"));
//}
//Ok(jack)
//}
impl Plugin {
/// Create a plugin host device.
pub fn new (
jack: &Arc<RwLock<JackClient>>,
name: &str,
) -> Usually<Self> {
Ok(Self {
//_engine: Default::default(),
jack: jack.clone(),
name: name.into(),
path: None,
plugin: None,
selected: 0,
mapping: false,
audio_ins: vec![],
audio_outs: vec![],
midi_ins: vec![],
midi_outs: vec![],
//ports: JackPorts::default()
})
}
}
impl Render<Tui> for Plugin {
fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> {
Ok(Some(to))
}
fn render (&self, to: &mut TuiOutput) -> Usually<()> {
let area = to.area();
let [x, y, _, height] = area;
let mut width = 20u16;
match &self.plugin {
Some(PluginKind::LV2(LV2Plugin { port_list, instance, .. })) => {
let start = self.selected.saturating_sub((height as usize / 2).saturating_sub(1));
let end = start + height as usize - 2;
//draw_box(buf, Rect { x, y, width, height });
for i in start..end {
if let Some(port) = port_list.get(i) {
let value = if let Some(value) = instance.control_input(port.index) {
value
} else {
port.default_value
};
//let label = &format!("C·· M·· {:25} = {value:.03}", port.name);
let label = &format!("{:25} = {value:.03}", port.name);
width = width.max(label.len() as u16 + 4);
let style = if i == self.selected {
Some(Style::default().green())
} else {
None
} ;
to.blit(&label, x + 2, y + 1 + i as u16 - start as u16, style);
} else {
break
}
}
},
_ => {}
};
draw_header(self, to, x, y, width)?;
Ok(())
}
}
fn draw_header (state: &Plugin, to: &mut TuiOutput, x: u16, y: u16, w: u16) -> Usually<()> {
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(())
//Ok(Rect { x, y, width: w, height: 1 })
}
handle!(<Tui>|self:Plugin, from|{
match from.event() {
key_pat!(KeyCode::Up) => {
self.selected = self.selected.saturating_sub(1);
Ok(Some(true))
},
key_pat!(KeyCode::Down) => {
self.selected = (self.selected + 1).min(match &self.plugin {
Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1,
_ => unimplemented!()
});
Ok(Some(true))
},
key_pat!(KeyCode::PageUp) => {
self.selected = self.selected.saturating_sub(8);
Ok(Some(true))
},
key_pat!(KeyCode::PageDown) => {
self.selected = (self.selected + 10).min(match &self.plugin {
Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1,
_ => unimplemented!()
});
Ok(Some(true))
},
key_pat!(KeyCode::Char(',')) => {
match self.plugin.as_mut() {
Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => {
let index = port_list[self.selected].index;
if let Some(value) = instance.control_input(index) {
instance.set_control_input(index, value - 0.01);
}
},
_ => {}
}
Ok(Some(true))
},
key_pat!(KeyCode::Char('.')) => {
match self.plugin.as_mut() {
Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => {
let index = port_list[self.selected].index;
if let Some(value) = instance.control_input(index) {
instance.set_control_input(index, value + 0.01);
}
},
_ => {}
}
Ok(Some(true))
},
key_pat!(KeyCode::Char('g')) => {
match self.plugin {
//Some(PluginKind::LV2(ref mut plugin)) => {
//plugin.ui_thread = Some(run_lv2_ui(LV2PluginUI::new()?)?);
//},
Some(_) => unreachable!(),
None => {}
}
Ok(Some(true))
},
_ => Ok(None)
}
});

View file

@ -1,40 +0,0 @@
use crate::*;
/// A LV2 plugin.
#[derive(Debug)]
pub struct LV2Plugin {
pub world: livi::World,
pub instance: livi::Instance,
pub plugin: livi::Plugin,
pub features: Arc<livi::Features>,
pub port_list: Vec<livi::Port>,
pub input_buffer: Vec<livi::event::LV2AtomSequence>,
pub ui_thread: Option<JoinHandle<()>>,
}
impl LV2Plugin {
const INPUT_BUFFER: usize = 1024;
pub fn new (uri: &str) -> Usually<Self> {
let world = livi::World::with_load_bundle(&uri);
let features = world
.build_features(livi::FeaturesBuilder {
min_block_length: 1,
max_block_length: 65536,
});
let plugin = world.iter_plugins().nth(0)
.unwrap_or_else(||panic!("plugin not found: {uri}"));
Ok(Self {
instance: unsafe {
plugin
.instantiate(features.clone(), 48000.0)
.expect(&format!("instantiate failed: {uri}"))
},
port_list: plugin.ports().collect::<Vec<_>>(),
input_buffer: Vec::with_capacity(Self::INPUT_BUFFER),
ui_thread: None,
world,
features,
plugin,
})
}
}

View file

@ -1,59 +0,0 @@
use crate::*;
use std::thread::{spawn, JoinHandle};
use ::winit::{
application::ApplicationHandler,
event::WindowEvent,
event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
window::{Window, WindowId},
platform::x11::EventLoopBuilderExtX11
};
//pub struct LV2PluginUI {
//write: (),
//controller: (),
//widget: (),
//features: (),
//transfer: (),
//}
pub fn run_lv2_ui (mut ui: LV2PluginUI) -> Usually<JoinHandle<()>> {
Ok(spawn(move||{
let event_loop = EventLoop::builder().with_x11().with_any_thread(true).build().unwrap();
event_loop.set_control_flow(ControlFlow::Wait);
event_loop.run_app(&mut ui).unwrap()
}))
}
/// A LV2 plugin's X11 UI.
pub struct LV2PluginUI {
pub window: Option<Window>
}
impl LV2PluginUI {
pub fn new () -> Usually<Self> {
Ok(Self { window: None })
}
}
impl ApplicationHandler for LV2PluginUI {
fn resumed (&mut self, event_loop: &ActiveEventLoop) {
self.window = Some(event_loop.create_window(Window::default_attributes()).unwrap());
}
fn window_event (&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) {
match event {
WindowEvent::CloseRequested => {
self.window.as_ref().unwrap().set_visible(false);
event_loop.exit();
},
WindowEvent::RedrawRequested => {
self.window.as_ref().unwrap().request_redraw();
}
_ => (),
}
}
}
fn lv2_ui_instantiate (kind: &str) {
//let host = Suil
}

View file

@ -1,47 +0,0 @@
use super::*;
use ::livi::{
World,
Instance,
Plugin as LiviPlugin,
Features,
FeaturesBuilder,
Port,
event::LV2AtomSequence,
};
use std::thread::JoinHandle;
/// A LV2 plugin.
pub struct LV2Plugin {
pub world: World,
pub instance: Instance,
pub plugin: LiviPlugin,
pub features: Arc<Features>,
pub port_list: Vec<Port>,
pub input_buffer: Vec<LV2AtomSequence>,
pub ui_thread: Option<JoinHandle<()>>,
}
impl LV2Plugin {
const INPUT_BUFFER: usize = 1024;
pub fn new (uri: &str) -> Usually<Self> {
// Get 1st plugin at URI
let world = World::with_load_bundle(&uri);
let features = FeaturesBuilder { min_block_length: 1, max_block_length: 65536 };
let features = world.build_features(features);
let mut plugin = None;
if let Some(p) = world.iter_plugins().next() { plugin = Some(p); }
let plugin = plugin.expect("plugin not found");
let err = &format!("init {uri}");
let instance = unsafe { plugin.instantiate(features.clone(), 48000.0).expect(&err) };
let mut port_list = vec![];
for port in plugin.ports() {
port_list.push(port);
}
let input_buffer = Vec::with_capacity(Self::INPUT_BUFFER);
// Instantiate
Ok(Self {
world, instance, port_list, plugin, features, input_buffer, ui_thread: None
})
}
}

View file

@ -1,14 +0,0 @@
use crate::*;
impl<E: Engine> ::vst::host::Host for Plugin<E> {}
fn set_vst_plugin <E: Engine> (host: &Arc<Mutex<Plugin<E>>>, _path: &str) -> Usually<PluginKind> {
let mut loader = ::vst::host::PluginLoader::load(
&std::path::Path::new("/nix/store/ij3sz7nqg5l7v2dygdvzy3w6cj62bd6r-helm-0.9.0/lib/lxvst/helm.so"),
host.clone()
)?;
Ok(PluginKind::VST2 {
instance: loader.instance()?
})
}

View file

@ -1,2 +0,0 @@
//! TODO

View file

@ -1,317 +0,0 @@
use super::*;
pub mod phrase_length;
pub(crate) use phrase_length::*;
pub mod phrase_rename;
pub(crate) use phrase_rename::*;
pub mod phrase_selector;
pub(crate) use phrase_selector::*;
#[derive(Debug)]
pub struct PoolModel {
pub(crate) visible: bool,
/// Collection of phrases
pub(crate) phrases: Vec<Arc<RwLock<MidiClip>>>,
/// Selected phrase
pub(crate) phrase: AtomicUsize,
/// Mode switch
pub(crate) mode: Option<PoolMode>,
/// Rendered size
size: Measure<Tui>,
/// Scroll offset
scroll: usize,
}
/// Modes for phrase pool
#[derive(Debug, Clone)]
pub enum PoolMode {
/// Renaming a pattern
Rename(usize, String),
/// Editing the length of a pattern
Length(usize, usize, PhraseLengthFocus),
/// Load phrase from disk
Import(usize, FileBrowser),
/// Save phrase to disk
Export(usize, FileBrowser),
}
#[derive(Clone, PartialEq, Debug)]
pub enum PoolCommand {
Show(bool),
/// Update the contents of the phrase pool
Phrase(PhrasePoolCommand),
/// Select a phrase from the phrase pool
Select(usize),
/// Rename a phrase
Rename(PhraseRenameCommand),
/// Change the length of a phrase
Length(PhraseLengthCommand),
/// Import from file
Import(FileBrowserCommand),
/// Export to file
Export(FileBrowserCommand),
}
command!(|self:PoolCommand, state: PoolModel|{
use PoolCommand::*;
match self {
Show(visible) => {
state.visible = visible;
Some(Self::Show(!visible))
}
Rename(command) => match command {
PhraseRenameCommand::Begin => {
let length = state.phrases()[state.phrase_index()].read().unwrap().length;
*state.phrases_mode_mut() = Some(
PoolMode::Length(state.phrase_index(), length, PhraseLengthFocus::Bar)
);
None
},
_ => command.execute(state)?.map(Rename)
},
Length(command) => match command {
PhraseLengthCommand::Begin => {
let name = state.phrases()[state.phrase_index()].read().unwrap().name.clone();
*state.phrases_mode_mut() = Some(
PoolMode::Rename(state.phrase_index(), name)
);
None
},
_ => command.execute(state)?.map(Length)
},
Import(command) => match command {
FileBrowserCommand::Begin => {
*state.phrases_mode_mut() = Some(
PoolMode::Import(state.phrase_index(), FileBrowser::new(None)?)
);
None
},
_ => command.execute(state)?.map(Import)
},
Export(command) => match command {
FileBrowserCommand::Begin => {
*state.phrases_mode_mut() = Some(
PoolMode::Export(state.phrase_index(), FileBrowser::new(None)?)
);
None
},
_ => command.execute(state)?.map(Export)
},
Select(phrase) => {
state.set_phrase_index(phrase);
None
},
Phrase(command) => command.execute(state)?.map(Phrase),
}
});
input_to_command!(PoolCommand:<Tui>|state: PoolModel,input|match state.phrases_mode() {
Some(PoolMode::Rename(..)) => Self::Rename(PhraseRenameCommand::input_to_command(state, input)?),
Some(PoolMode::Length(..)) => Self::Length(PhraseLengthCommand::input_to_command(state, input)?),
Some(PoolMode::Import(..)) => Self::Import(FileBrowserCommand::input_to_command(state, input)?),
Some(PoolMode::Export(..)) => Self::Export(FileBrowserCommand::input_to_command(state, input)?),
_ => to_phrases_command(state, input)?
});
fn to_phrases_command (state: &PoolModel, input: &TuiInput) -> Option<PoolCommand> {
use KeyCode::{Up, Down, Delete, Char};
use PoolCommand as Cmd;
let index = state.phrase_index();
let count = state.phrases().len();
Some(match input.event() {
key_pat!(Char('n')) => Cmd::Rename(PhraseRenameCommand::Begin),
key_pat!(Char('t')) => Cmd::Length(PhraseLengthCommand::Begin),
key_pat!(Char('m')) => Cmd::Import(FileBrowserCommand::Begin),
key_pat!(Char('x')) => Cmd::Export(FileBrowserCommand::Begin),
key_pat!(Char('c')) => Cmd::Phrase(PhrasePoolCommand::SetColor(index, ItemColor::random())),
key_pat!(Char('[')) | key_pat!(Up) => Cmd::Select(
index.overflowing_sub(1).0.min(state.phrases().len() - 1)
),
key_pat!(Char(']')) | key_pat!(Down) => Cmd::Select(
index.saturating_add(1) % state.phrases().len()
),
key_pat!(Char('<')) => if index > 1 {
state.set_phrase_index(state.phrase_index().saturating_sub(1));
Cmd::Phrase(PhrasePoolCommand::Swap(index - 1, index))
} else {
return None
},
key_pat!(Char('>')) => if index < count.saturating_sub(1) {
state.set_phrase_index(state.phrase_index() + 1);
Cmd::Phrase(PhrasePoolCommand::Swap(index + 1, index))
} else {
return None
},
key_pat!(Delete) => if index > 0 {
state.set_phrase_index(index.min(count.saturating_sub(1)));
Cmd::Phrase(PhrasePoolCommand::Delete(index))
} else {
return None
},
key_pat!(Char('a')) | key_pat!(Shift-Char('A')) => Cmd::Phrase(PhrasePoolCommand::Add(count, MidiClip::new(
String::from("(new)"), true, 4 * PPQ, None, Some(ItemPalette::random())
))),
key_pat!(Char('i')) => Cmd::Phrase(PhrasePoolCommand::Add(index + 1, MidiClip::new(
String::from("(new)"), true, 4 * PPQ, None, Some(ItemPalette::random())
))),
key_pat!(Char('d')) | key_pat!(Shift-Char('D')) => {
let mut phrase = state.phrases()[index].read().unwrap().duplicate();
phrase.color = ItemPalette::random_near(phrase.color, 0.25);
Cmd::Phrase(PhrasePoolCommand::Add(index + 1, phrase))
},
_ => return None
})
}
impl Default for PoolModel {
fn default () -> Self {
Self {
visible: true,
phrases: vec![RwLock::new(MidiClip::default()).into()],
phrase: 0.into(),
scroll: 0,
mode: None,
size: Measure::new(),
}
}
}
from!(|phrase:&Arc<RwLock<MidiClip>>|PoolModel = {
let mut model = Self::default();
model.phrases.push(phrase.clone());
model.phrase.store(1, Relaxed);
model
});
has_phrases!(|self: PoolModel|self.phrases);
has_phrase!(|self: PoolModel|self.phrases[self.phrase_index()]);
impl PoolModel {
pub(crate) fn phrase_index (&self) -> usize {
self.phrase.load(Relaxed)
}
pub(crate) fn set_phrase_index (&self, value: usize) {
self.phrase.store(value, Relaxed);
}
pub(crate) fn phrases_mode (&self) -> &Option<PoolMode> {
&self.mode
}
pub(crate) fn phrases_mode_mut (&mut self) -> &mut Option<PoolMode> {
&mut self.mode
}
}
pub struct PoolView<'a>(pub(crate) &'a PoolModel);
// TODO: Display phrases always in order of appearance
render!(<Tui>|self: PoolView<'a>|{
let PoolModel { phrases, mode, .. } = self.0;
let bg = TuiTheme::g(32);
let title_color = TuiTheme::ti1();
let upper_left = "Pool:";
let upper_right = format!("({})", phrases.len());
let color = self.0.phrase().read().unwrap().color;
Tui::bg(bg, lay!(move|add|{
add(&Fill::wh(Outer(Style::default().fg(color.base.rgb).bg(bg))))?;
//add(&Lozenge(Style::default().bg(border_bg).fg(border_color)))?;
add(&Tui::inset_xy(0, 1, Fill::wh(col!(move|add|match mode {
Some(PoolMode::Import(_, ref file_picker)) => add(file_picker),
Some(PoolMode::Export(_, ref file_picker)) => add(file_picker),
_ => Ok(for (i, phrase) in phrases.iter().enumerate() {
add(&lay!(|add|{
let MidiClip { ref name, color, length, .. } = *phrase.read().unwrap();
let mut length = PhraseLength::new(length, None);
if let Some(PoolMode::Length(phrase, new_length, focus)) = mode {
if i == *phrase {
length.pulses = *new_length;
length.focus = Some(*focus);
}
}
add(&Tui::bg(color.base.rgb, Fill::w(col!([
Fill::w(lay!(|add|{
add(&Fill::w(Align::w(format!(" {i}"))))?;
add(&Fill::w(Align::e(Tui::pull_x(1, length.clone()))))
})),
Tui::bold(true, {
let mut row2 = format!(" {name}");
if let Some(PoolMode::Rename(phrase, _)) = mode {
if i == *phrase {
row2 = format!("{row2}");
}
};
row2
}),
]))))?;
if i == self.0.phrase_index() {
add(&CORNERS)?;
}
Ok(())
}))?;
})
}))))?;
add(&Fill::w(Align::nw(Tui::push_x(1, Tui::fg(title_color, upper_left.to_string())))))?;
add(&Fill::w(Align::ne(Tui::pull_x(1, Tui::fg(title_color, upper_right.to_string())))))?;
add(&self.0.size)
}))
});
command!(|self: FileBrowserCommand, state: PoolModel|{
use PoolMode::*;
use FileBrowserCommand::*;
let mode = &mut state.mode;
match mode {
Some(Import(index, ref mut browser)) => match self {
Cancel => { *mode = None; },
Chdir(cwd) => { *mode = Some(Import(*index, FileBrowser::new(Some(cwd))?)); },
Select(index) => { browser.index = index; },
Confirm => if browser.is_file() {
let index = *index;
let path = browser.path();
*mode = None;
PhrasePoolCommand::Import(index, path).execute(state)?;
} else if browser.is_dir() {
*mode = Some(Import(*index, browser.chdir()?));
},
_ => todo!(),
},
Some(Export(index, ref mut browser)) => match self {
Cancel => { *mode = None; },
Chdir(cwd) => { *mode = Some(Export(*index, FileBrowser::new(Some(cwd))?)); },
Select(index) => { browser.index = index; },
_ => unreachable!()
},
_ => unreachable!(),
};
None
});
input_to_command!(FileBrowserCommand:<Tui>|state: PoolModel,from|{
use FileBrowserCommand::*;
use KeyCode::{Up, Down, Left, Right, Enter, Esc, Backspace, Char};
if let Some(PoolMode::Import(_index, browser)) = &state.mode {
match from.event() {
key_pat!(Up) => Select(browser.index.overflowing_sub(1).0
.min(browser.len().saturating_sub(1))),
key_pat!(Down) => Select(browser.index.saturating_add(1)
% browser.len()),
key_pat!(Right) => Chdir(browser.cwd.clone()),
key_pat!(Left) => Chdir(browser.cwd.clone()),
key_pat!(Enter) => Confirm,
key_pat!(Char(_)) => { todo!() },
key_pat!(Backspace) => { todo!() },
key_pat!(Esc) => Cancel,
_ => return None
}
} else if let Some(PoolMode::Export(_index, browser)) = &state.mode {
match from.event() {
key_pat!(Up) => Select(browser.index.overflowing_sub(1).0
.min(browser.len())),
key_pat!(Down) => Select(browser.index.saturating_add(1)
% browser.len()),
key_pat!(Right) => Chdir(browser.cwd.clone()),
key_pat!(Left) => Chdir(browser.cwd.clone()),
key_pat!(Enter) => Confirm,
key_pat!(Char(_)) => { todo!() },
key_pat!(Backspace) => { todo!() },
key_pat!(Esc) => Cancel,
_ => return None
}
} else {
unreachable!()
}
});

View file

@ -1,144 +0,0 @@
use crate::*;
use super::*;
use PhraseLengthFocus::*;
use PhraseLengthCommand::*;
use KeyCode::{Up, Down, Left, Right, Enter, Esc};
/// Displays and edits phrase length.
#[derive(Clone)]
pub struct PhraseLength {
/// Pulses per beat (quaver)
pub ppq: usize,
/// Beats per bar
pub bpb: usize,
/// Length of phrase in pulses
pub pulses: usize,
/// Selected subdivision
pub focus: Option<PhraseLengthFocus>,
}
impl PhraseLength {
pub fn new (pulses: usize, focus: Option<PhraseLengthFocus>) -> Self {
Self { ppq: PPQ, bpb: 4, pulses, focus }
}
pub fn bars (&self) -> usize {
self.pulses / (self.bpb * self.ppq)
}
pub fn beats (&self) -> usize {
(self.pulses % (self.bpb * self.ppq)) / self.ppq
}
pub fn ticks (&self) -> usize {
self.pulses % self.ppq
}
pub fn bars_string (&self) -> String {
format!("{}", self.bars())
}
pub fn beats_string (&self) -> String {
format!("{}", self.beats())
}
pub fn ticks_string (&self) -> String {
format!("{:>02}", self.ticks())
}
}
/// Focused field of `PhraseLength`
#[derive(Copy, Clone, Debug)]
pub enum PhraseLengthFocus {
/// Editing the number of bars
Bar,
/// Editing the number of beats
Beat,
/// Editing the number of ticks
Tick,
}
impl PhraseLengthFocus {
pub fn next (&mut self) {
*self = match self {
Self::Bar => Self::Beat,
Self::Beat => Self::Tick,
Self::Tick => Self::Bar,
}
}
pub fn prev (&mut self) {
*self = match self {
Self::Bar => Self::Tick,
Self::Beat => Self::Bar,
Self::Tick => Self::Beat,
}
}
}
render!(<Tui>|self: PhraseLength|{
let bars = ||self.bars_string();
let beats = ||self.beats_string();
let ticks = ||self.ticks_string();
row!(move|add|match self.focus {
None =>
add(&row!([" ", bars(), ".", beats(), ".", ticks()])),
Some(PhraseLengthFocus::Bar) =>
add(&row!(["[", bars(), "]", beats(), ".", ticks()])),
Some(PhraseLengthFocus::Beat) =>
add(&row!([" ", bars(), "[", beats(), "]", ticks()])),
Some(PhraseLengthFocus::Tick) =>
add(&row!([" ", bars(), ".", beats(), "[", ticks()])),
})
});
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum PhraseLengthCommand {
Begin,
Cancel,
Set(usize),
Next,
Prev,
Inc,
Dec,
}
command!(|self:PhraseLengthCommand,state:PoolModel|{
match state.phrases_mode_mut().clone() {
Some(PoolMode::Length(phrase, ref mut length, ref mut focus)) => match self {
Cancel => { *state.phrases_mode_mut() = None; },
Prev => { focus.prev() },
Next => { focus.next() },
Inc => match focus {
Bar => { *length += 4 * PPQ },
Beat => { *length += PPQ },
Tick => { *length += 1 },
},
Dec => match focus {
Bar => { *length = length.saturating_sub(4 * PPQ) },
Beat => { *length = length.saturating_sub(PPQ) },
Tick => { *length = length.saturating_sub(1) },
},
Set(length) => {
let mut phrase = state.phrases()[phrase].write().unwrap();
let old_length = phrase.length;
phrase.length = length;
std::mem::drop(phrase);
*state.phrases_mode_mut() = None;
return Ok(Some(Self::Set(old_length)))
},
_ => unreachable!()
},
_ => unreachable!()
};
None
});
input_to_command!(PhraseLengthCommand:<Tui>|state:PoolModel,from|{
if let Some(PoolMode::Length(_, length, _)) = state.phrases_mode() {
match from.event() {
key_pat!(Up) => Self::Inc,
key_pat!(Down) => Self::Dec,
key_pat!(Right) => Self::Next,
key_pat!(Left) => Self::Prev,
key_pat!(Enter) => Self::Set(*length),
key_pat!(Esc) => Self::Cancel,
_ => return None
}
} else {
unreachable!()
}
});

View file

@ -1,60 +0,0 @@
use crate::*;
use super::*;
#[derive(Clone, Debug, PartialEq)]
pub enum PhraseRenameCommand {
Begin,
Cancel,
Confirm,
Set(String),
}
impl Command<PoolModel> for PhraseRenameCommand {
fn execute (self, state: &mut PoolModel) -> Perhaps<Self> {
use PhraseRenameCommand::*;
match state.phrases_mode_mut().clone() {
Some(PoolMode::Rename(phrase, ref mut old_name)) => match self {
Set(s) => {
state.phrases()[phrase].write().unwrap().name = s;
return Ok(Some(Self::Set(old_name.clone())))
},
Confirm => {
let old_name = old_name.clone();
*state.phrases_mode_mut() = None;
return Ok(Some(Self::Set(old_name)))
},
Cancel => {
state.phrases()[phrase].write().unwrap().name = old_name.clone();
},
_ => unreachable!()
},
_ => unreachable!()
};
Ok(None)
}
}
impl InputToCommand<Tui, PoolModel> for PhraseRenameCommand {
fn input_to_command (state: &PoolModel, from: &TuiInput) -> Option<Self> {
use KeyCode::{Char, Backspace, Enter, Esc};
if let Some(PoolMode::Rename(_, ref old_name)) = state.phrases_mode() {
Some(match from.event() {
key_pat!(Char(c)) => {
let mut new_name = old_name.clone();
new_name.push(*c);
Self::Set(new_name)
},
key_pat!(Backspace) => {
let mut new_name = old_name.clone();
new_name.pop();
Self::Set(new_name)
},
key_pat!(Enter) => Self::Confirm,
key_pat!(Esc) => Self::Cancel,
_ => return None
})
} else {
unreachable!()
}
}
}

View file

@ -1,65 +0,0 @@
use crate::*;
pub struct PhraseSelector {
pub(crate) title: &'static str,
pub(crate) name: String,
pub(crate) color: ItemPalette,
pub(crate) time: String,
}
// TODO: Display phrases always in order of appearance
render!(<Tui>|self: PhraseSelector|Fixed::wh(24, 1, row!([
Tui::fg(self.color.lightest.rgb, Tui::bold(true, &self.title)),
Tui::fg_bg(self.color.lighter.rgb, self.color.base.rgb, row!([
format!("{:8}", &self.name[0..8.min(self.name.len())]),
Tui::bg(self.color.dark.rgb, &self.time),
])),
])));
impl PhraseSelector {
// beats elapsed
pub fn play_phrase <T: HasPlayPhrase + HasClock> (state: &T) -> Self {
let (name, color) = if let Some((_, Some(phrase))) = state.play_phrase() {
let MidiClip { ref name, color, .. } = *phrase.read().unwrap();
(name.clone(), color)
} else {
("".to_string(), ItemPalette::from(TuiTheme::g(64)))
};
let time = if let Some(elapsed) = state.pulses_since_start_looped() {
format!("+{:>}", state.clock().timebase.format_beats_0(elapsed))
} else {
String::from(" ")
};
Self { title: "Now:|", time, name, color, }
}
// beats until switchover
pub fn next_phrase <T: HasPlayPhrase> (state: &T) -> Self {
let (time, name, color) = if let Some((t, Some(phrase))) = state.next_phrase() {
let MidiClip { ref name, color, .. } = *phrase.read().unwrap();
let time = {
let target = t.pulse.get();
let current = state.clock().playhead.pulse.get();
if target > current {
let remaining = target - current;
format!("-{:>}", state.clock().timebase.format_beats_0(remaining))
} else {
String::new()
}
};
(time, name.clone(), color)
} else if let Some((_, Some(phrase))) = state.play_phrase() {
let phrase = phrase.read().unwrap();
if phrase.looped {
(" ".into(), phrase.name.clone(), phrase.color)
} else {
(" ".into(), " ".into(), TuiTheme::g(64).into())
}
} else {
(" ".into(), " ".into(), TuiTheme::g(64).into())
};
Self { title: " Next|", time, name, color, }
}
}

Some files were not shown because too many files have changed in this diff Show more