refactor: jack proto-lib

This commit is contained in:
🪞👃🪞 2024-07-04 15:32:41 +03:00
parent 4aadc712b8
commit fe6ffea5df
12 changed files with 467 additions and 441 deletions

View file

@ -1,30 +1,27 @@
pub use std::error::Error;
pub use std::io::{stdout, Stdout, Write};
pub use std::thread::{spawn, JoinHandle};
pub use std::time::Duration;
pub use std::collections::BTreeMap;
pub use std::sync::{Arc, Mutex, MutexGuard};
pub use std::sync::atomic::{Ordering, AtomicBool, AtomicUsize};
pub use std::sync::mpsc::{channel, Sender, Receiver};
/// Define and reexport submodules.
#[macro_export] macro_rules! submod {
($($name:ident)*) => { $(mod $name; pub use self::$name::*;)* };
}
pub use microxdg::XdgApp;
pub use ratatui::prelude::*;
pub use midly::{MidiMessage, live::LiveEvent, num::u7};
pub use crossterm::{ExecutableCommand, QueueableCommand};
pub use crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers};
macro_rules! submod { ($($name:ident)*) => { $(mod $name; pub use self::$name::*;)* }; }
submod!( handle jack keymap midi port render run time );
submod!( handle keymap midi render run time );
/// Standard result type.
pub type Usually<T> = Result<T, Box<dyn Error>>;
pub trait Component: Render + Handle {}
/// A UI component.
pub trait Component: Render + Handle {
/// Perform type erasure for collecting heterogeneous components.
fn boxed (self) -> Box<dyn Component> where Self: Sized + 'static {
Box::new(self)
}
}
/// Anything that implements `Render` + `Handle` can be used as a UI component.
impl<T: Render + Handle> Component for T {}
/// A UI component that may have presence on the JACK grap.
/// A UI component that may be associated with a JACK client by the `Jack` factory.
pub trait Device: Render + Handle + Process + Send + Sync {
/// Perform type erasure for collecting heterogeneous devices.
fn boxed (self) -> Box<dyn Device> where Self: Sized + 'static {
Box::new(self)
}
@ -34,12 +31,24 @@ pub trait Device: Render + Handle + Process + Send + Sync {
impl<T: Render + Handle + Process + Send + Sync> Device for T {}
// Reexport macros:
pub use crate::{
render,
handle,
process,
phrase,
keymap,
key,
ports
};
pub use crate::{submod, render, handle, process, phrase, keymap, key, ports};
// Reexport JACK proto-lib:
pub use crate::jack::*;
// Various stdlib dependencies:
pub use std::error::Error;
pub use std::io::{stdout, Stdout, Write};
pub use std::thread::{spawn, JoinHandle};
pub use std::time::Duration;
pub use std::collections::BTreeMap;
pub use std::sync::{Arc, Mutex, MutexGuard};
pub use std::sync::atomic::{Ordering, AtomicBool, AtomicUsize};
pub use std::sync::mpsc::{channel, Sender, Receiver};
// Various non-stdlib dependencies:
pub use microxdg::XdgApp;
pub use ratatui::prelude::*;
pub use midly::{MidiMessage, live::LiveEvent, num::u7};
pub use crossterm::{ExecutableCommand, QueueableCommand};
pub use crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers};

View file

@ -1,271 +0,0 @@
use crate::core::*;
pub struct Jack {
pub client: Client,
pub ports: JackPorts,
}
pub struct JackDevice {
pub client: DynamicAsyncClient,
pub state: Arc<Mutex<Box<dyn Device>>>,
pub ports: UnownedJackPorts,
}
pub enum JackClient {
Active(DynamicAsyncClient),
Inactive(Client),
}
ports!(JackDevice {
audio: {
ins: |s|Ok(s.ports.audio_ins.values().collect()),
outs: |s|Ok(s.ports.audio_outs.values().collect()),
}
midi: {
ins: |s|Ok(s.ports.midi_ins.values().collect()),
outs: |s|Ok(s.ports.midi_outs.values().collect()),
}
});
impl Jack {
pub fn new (name: &str) -> Usually<Self> {
let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?;
Ok(Self { client, ports: JackPorts::default() })
}
pub fn run <T: Device + Process + Sized + 'static> (
mut self, state: impl FnOnce(JackPorts)->Box<T>
)
-> Usually<JackDevice>
{
let mut owned_ports = JackPorts::default();
std::mem::swap(&mut self.ports, &mut owned_ports);
let unowned_ports = owned_ports.clone_unowned(&self.client);
let state = Arc::new(Mutex::new(state(owned_ports) as Box<dyn Device>));
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(AppEvent) + Send + Sync>),
ClosureProcessHandler::new(Box::new({
let state = state.clone();
move|c: &Client, s: &ProcessScope|{
state.lock().unwrap().process(c, s)
}
}) as BoxedProcessHandler)
)?;
Ok(JackDevice { client, state, ports: unowned_ports })
}
pub fn ports_from_lv2 (self, plugin: &::livi::Plugin) -> Usually<Self> {
let counts = plugin.port_counts();
let mut jack = self;
for i in 0..counts.atom_sequence_inputs {
jack = jack.register_midi_in(&format!("midi-in-{i}"))?
}
for i in 0..counts.atom_sequence_outputs {
jack = jack.register_midi_out(&format!("midi-out-{i}"))?;
}
for i in 0..counts.audio_inputs {
jack = jack.register_audio_in(&format!("audio-in-{i}"))?
}
for i in 0..counts.audio_outputs {
jack = jack.register_audio_out(&format!("audio-out-{i}"))?;
}
Ok(jack)
}
pub fn register_midi_out (mut self, name: &str) -> Usually<Self> {
let port = self.client.register_port(name, MidiOut::default())?;
self.ports.midi_outs.insert(name.to_string(), port);
Ok(self)
}
pub fn register_midi_in (mut self, name: &str) -> Usually<Self> {
let port = self.client.register_port(name, MidiIn::default())?;
self.ports.midi_ins.insert(name.to_string(), port);
Ok(self)
}
pub fn register_audio_out (mut self, name: &str) -> Usually<Self> {
let port = self.client.register_port(name, AudioOut::default())?;
self.ports.audio_outs.insert(name.to_string(), port);
Ok(self)
}
pub fn register_audio_in (mut self, name: &str) -> Usually<Self> {
let port = self.client.register_port(name, AudioIn::default())?;
self.ports.audio_ins.insert(name.to_string(), port);
Ok(self)
}
}
pub fn jack_run <T> (name: &str, app: &Arc<Mutex<T>>) -> Usually<DynamicAsyncClient>
where T: Handle + Process + Send + 'static
{
let options = ClientOptions::NO_START_SERVER;
let (client, _status) = Client::new(name, options)?;
Ok(client.activate_async(
Notifications(Box::new({
let _app = app.clone();
move|_event|{
// FIXME: this deadlocks
//app.lock().unwrap().handle(&event).unwrap();
}
}) as Box<dyn Fn(AppEvent) + Send + Sync>),
ClosureProcessHandler::new(Box::new({
let app = app.clone();
move|c: &Client, s: &ProcessScope|{
app.lock().unwrap().process(c, s)
//Control::Continue
}
}) as BoxedProcessHandler)
)?)
}
pub trait Process {
fn process (&mut self, _: &Client, _: &ProcessScope) -> Control {
Control::Continue
}
}
#[macro_export] macro_rules! process {
($T:ty) => {
impl Process for $T {}
};
($T:ty |$self:ident, $c:ident, $s:ident|$block:tt) => {
impl Process for $T {
fn process (&mut $self, $c: &Client, $s: &ProcessScope) -> Control {
$block
}
}
};
($T:ty = $process:path) => {
impl Process for $T {
fn process (&mut self, c: &Client, s: &ProcessScope) -> Control {
$process(self, c, s)
}
}
}
}
pub type DynamicAsyncClient =
AsyncClient<DynamicNotifications, DynamicProcessHandler>;
pub type DynamicNotifications =
Notifications<Box<dyn Fn(AppEvent) + Send + Sync>>;
pub type DynamicProcessHandler =
ClosureProcessHandler<BoxedProcessHandler>;
pub type BoxedProcessHandler =
Box<dyn FnMut(&Client, &ProcessScope)-> Control + Send>;
pub use ::jack::{
AsyncClient,
AudioIn,
AudioOut,
Client,
ClientOptions,
ClientStatus,
ClosureProcessHandler,
Control,
Frames,
MidiIn,
MidiOut,
NotificationHandler,
Port,
PortFlags,
PortId,
PortSpec,
ProcessHandler,
ProcessScope,
RawMidi,
Transport,
TransportState,
TransportStatePosition,
Unowned
};
#[derive(Debug)]
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,
}
pub struct Notifications<T: Fn(AppEvent) + Send>(pub T);
impl<T: Fn(AppEvent) + Send> NotificationHandler for Notifications<T> {
fn thread_init (&self, _: &Client) {
self.0(AppEvent::Jack(JackEvent::ThreadInit));
}
fn shutdown (&mut self, status: ClientStatus, reason: &str) {
self.0(AppEvent::Jack(JackEvent::Shutdown(status, reason.into())));
}
fn freewheel (&mut self, _: &Client, enabled: bool) {
self.0(AppEvent::Jack(JackEvent::Freewheel(enabled)));
}
fn sample_rate (&mut self, _: &Client, frames: Frames) -> Control {
self.0(AppEvent::Jack(JackEvent::SampleRate(frames)));
Control::Quit
}
fn client_registration (&mut self, _: &Client, name: &str, reg: bool) {
self.0(AppEvent::Jack(JackEvent::ClientRegistration(name.into(), reg)));
}
fn port_registration (&mut self, _: &Client, id: PortId, reg: bool) {
self.0(AppEvent::Jack(JackEvent::PortRegistration(id, reg)));
}
fn port_rename (&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control {
self.0(AppEvent::Jack(JackEvent::PortRename(id, old.into(), new.into())));
Control::Continue
}
fn ports_connected (&mut self, _: &Client, a: PortId, b: PortId, are: bool) {
self.0(AppEvent::Jack(JackEvent::PortsConnected(a, b, are)));
}
fn graph_reorder (&mut self, _: &Client) -> Control {
self.0(AppEvent::Jack(JackEvent::GraphReorder));
Control::Continue
}
fn xrun (&mut self, _: &Client) -> Control {
self.0(AppEvent::Jack(JackEvent::XRun));
Control::Continue
}
}
/// Add "all notes off" to the start of a buffer.
pub fn all_notes_off (output: &mut MIDIChunk) {
output[0] = Some(vec![]);
if let Some(Some(frame)) = output.get_mut(0) {
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();
frame.push(buf);
}
}
/// Write to JACK port from output buffer (containing notes from sequence and/or monitor)
pub fn write_output (writer: &mut ::jack::MidiWriter, output: &mut MIDIChunk, frames: usize) {
for time in 0..frames {
if let Some(Some(frame)) = output.get_mut(time ) {
for event in frame.iter() {
writer.write(&::jack::RawMidi { time: time as u32, bytes: &event })
.expect(&format!("{event:?}"));
}
}
}
}

View file

@ -9,28 +9,26 @@ pub type MIDIMessage =
pub type MIDIChunk =
[Option<Vec<MIDIMessage>>];
pub const KEY_WHITE: Style = Style {
fg: Some(Color::Gray),
bg: None,
underline_color: None,
add_modifier: ::ratatui::style::Modifier::empty(),
sub_modifier: ::ratatui::style::Modifier::empty(),
};
pub const KEY_BLACK: Style = Style {
fg: Some(Color::Black),
bg: None,
underline_color: None,
add_modifier: ::ratatui::style::Modifier::empty(),
sub_modifier: ::ratatui::style::Modifier::empty(),
};
pub const KEY_STYLE: [Style;12] = [
KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE,
KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE,
];
pub const KEYS_VERTICAL: [&'static str; 6] = [
"", "", "", "", "", "",
];
/// Add "all notes off" to the start of a buffer.
pub fn all_notes_off (output: &mut MIDIChunk) {
output[0] = Some(vec![]);
if let Some(Some(frame)) = output.get_mut(0) {
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();
frame.push(buf);
}
}
/// Write to JACK port from output buffer (containing notes from sequence and/or monitor)
pub fn write_output (writer: &mut ::jack::MidiWriter, output: &mut MIDIChunk, frames: usize) {
for time in 0..frames {
if let Some(Some(frame)) = output.get_mut(time ) {
for event in frame.iter() {
writer.write(&::jack::RawMidi { time: time as u32, bytes: &event })
.expect(&format!("{event:?}"));
}
}
}
}

65
src/jack.rs Normal file
View file

@ -0,0 +1,65 @@
use crate::core::*;
submod!( device event factory ports process );
pub type DynamicAsyncClient =
AsyncClient<DynamicNotifications, DynamicProcessHandler>;
pub type DynamicNotifications =
Notifications<Box<dyn Fn(AppEvent) + Send + Sync>>;
pub type DynamicProcessHandler =
ClosureProcessHandler<BoxedProcessHandler>;
pub type BoxedProcessHandler =
Box<dyn FnMut(&Client, &ProcessScope)-> Control + Send>;
pub use ::_jack::{
AsyncClient,
AudioIn,
AudioOut,
Client,
ClientOptions,
ClientStatus,
ClosureProcessHandler,
Control,
Frames,
MidiIn,
MidiOut,
NotificationHandler,
Port,
PortFlags,
PortId,
PortSpec,
ProcessHandler,
ProcessScope,
RawMidi,
Transport,
TransportState,
TransportStatePosition,
Unowned
};
/// Just run thing with JACK. Returns the activated client.
pub fn jack_run <T> (name: &str, app: &Arc<Mutex<T>>) -> Usually<DynamicAsyncClient>
where T: Handle + Process + Send + 'static
{
let options = ClientOptions::NO_START_SERVER;
let (client, _status) = Client::new(name, options)?;
Ok(client.activate_async(
Notifications(Box::new({
let _app = app.clone();
move|_event|{
// FIXME: this deadlocks
//app.lock().unwrap().handle(&event).unwrap();
}
}) as Box<dyn Fn(AppEvent) + Send + Sync>),
ClosureProcessHandler::new(Box::new({
let app = app.clone();
move|c: &Client, s: &ProcessScope|{
app.lock().unwrap().process(c, s)
//Control::Continue
}
}) as BoxedProcessHandler)
)?)
}

29
src/jack/device.rs Normal file
View file

@ -0,0 +1,29 @@
use super::*;
/// A Device bound to a JACK client and a set of ports.
pub struct JackDevice {
/// The active JACK client of this device.
pub client: DynamicAsyncClient,
/// The device state, encapsulated for sharing between threads.
pub state: Arc<Mutex<Box<dyn Device>>>,
/// 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,
}
ports!(JackDevice {
audio: {
ins: |s|Ok(s.ports.audio_ins.values().collect()),
outs: |s|Ok(s.ports.audio_outs.values().collect()),
}
midi: {
ins: |s|Ok(s.ports.midi_ins.values().collect()),
outs: |s|Ok(s.ports.midi_outs.values().collect()),
}
});
impl JackDevice {
/// Returns a locked mutex of the state's contents.
pub fn state (&self) -> MutexGuard<Box<dyn Device>> {
self.state.lock().unwrap()
}
}

64
src/jack/event.rs Normal file
View file

@ -0,0 +1,64 @@
use super::*;
#[derive(Debug)]
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,
}
pub struct Notifications<T: Fn(AppEvent) + Send>(pub T);
impl<T: Fn(AppEvent) + Send> NotificationHandler for Notifications<T> {
fn thread_init (&self, _: &Client) {
self.0(AppEvent::Jack(JackEvent::ThreadInit));
}
fn shutdown (&mut self, status: ClientStatus, reason: &str) {
self.0(AppEvent::Jack(JackEvent::Shutdown(status, reason.into())));
}
fn freewheel (&mut self, _: &Client, enabled: bool) {
self.0(AppEvent::Jack(JackEvent::Freewheel(enabled)));
}
fn sample_rate (&mut self, _: &Client, frames: Frames) -> Control {
self.0(AppEvent::Jack(JackEvent::SampleRate(frames)));
Control::Quit
}
fn client_registration (&mut self, _: &Client, name: &str, reg: bool) {
self.0(AppEvent::Jack(JackEvent::ClientRegistration(name.into(), reg)));
}
fn port_registration (&mut self, _: &Client, id: PortId, reg: bool) {
self.0(AppEvent::Jack(JackEvent::PortRegistration(id, reg)));
}
fn port_rename (&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control {
self.0(AppEvent::Jack(JackEvent::PortRename(id, old.into(), new.into())));
Control::Continue
}
fn ports_connected (&mut self, _: &Client, a: PortId, b: PortId, are: bool) {
self.0(AppEvent::Jack(JackEvent::PortsConnected(a, b, are)));
}
fn graph_reorder (&mut self, _: &Client) -> Control {
self.0(AppEvent::Jack(JackEvent::GraphReorder));
Control::Continue
}
fn xrun (&mut self, _: &Client) -> Control {
self.0(AppEvent::Jack(JackEvent::XRun));
Control::Continue
}
}

78
src/jack/factory.rs Normal file
View file

@ -0,0 +1,78 @@
use super::*;
/// `JackDevice` factory. Creates JACK `Client`s, performs port registration
/// and activation, and encapsulates a `Device` into a `JackDevice`.
pub struct Jack {
pub client: Client,
pub ports: JackPorts,
}
impl Jack {
pub fn new (name: &str) -> Usually<Self> {
let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?;
Ok(Self { client, ports: JackPorts::default() })
}
pub fn run <T: Device + Process + Sized + 'static> (
mut self, state: impl FnOnce(JackPorts)->Box<T>
)
-> Usually<JackDevice>
{
let mut owned_ports = JackPorts::default();
std::mem::swap(&mut self.ports, &mut owned_ports);
let unowned_ports = owned_ports.clone_unowned(&self.client);
let state = Arc::new(Mutex::new(state(owned_ports) as Box<dyn Device>));
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(AppEvent) + Send + Sync>),
ClosureProcessHandler::new(Box::new({
let state = state.clone();
move|c: &Client, s: &ProcessScope|{
state.lock().unwrap().process(c, s)
}
}) as BoxedProcessHandler)
)?;
Ok(JackDevice { client, state, ports: unowned_ports })
}
pub fn ports_from_lv2 (self, plugin: &::livi::Plugin) -> Usually<Self> {
let counts = plugin.port_counts();
let mut jack = self;
for i in 0..counts.atom_sequence_inputs {
jack = jack.register_midi_in(&format!("midi-in-{i}"))?
}
for i in 0..counts.atom_sequence_outputs {
jack = jack.register_midi_out(&format!("midi-out-{i}"))?;
}
for i in 0..counts.audio_inputs {
jack = jack.register_audio_in(&format!("audio-in-{i}"))?
}
for i in 0..counts.audio_outputs {
jack = jack.register_audio_out(&format!("audio-out-{i}"))?;
}
Ok(jack)
}
pub fn register_midi_out (mut self, name: &str) -> Usually<Self> {
let port = self.client.register_port(name, MidiOut::default())?;
self.ports.midi_outs.insert(name.to_string(), port);
Ok(self)
}
pub fn register_midi_in (mut self, name: &str) -> Usually<Self> {
let port = self.client.register_port(name, MidiIn::default())?;
self.ports.midi_ins.insert(name.to_string(), port);
Ok(self)
}
pub fn register_audio_out (mut self, name: &str) -> Usually<Self> {
let port = self.client.register_port(name, AudioOut::default())?;
self.ports.audio_outs.insert(name.to_string(), port);
Ok(self)
}
pub fn register_audio_in (mut self, name: &str) -> Usually<Self> {
let port = self.client.register_port(name, AudioIn::default())?;
self.ports.audio_ins.insert(name.to_string(), port);
Ok(self)
}
}

View file

@ -1,4 +1,4 @@
use crate::core::*;
use super::*;
#[derive(Default)]
pub struct JackPorts {
@ -95,25 +95,4 @@ pub trait Ports {
)?
)?}
};
}
pub struct DevicePort<T: PortSpec> {
pub name: String,
pub port: Port<T>,
pub connect: Vec<String>,
}
impl<T: PortSpec + Default> DevicePort<T> {
pub fn new (client: &Client, name: &str, connect: &[&str]) -> Usually<Self> {
let mut connects = vec![];
for port in connect.iter() {
connects.push(port.to_string());
}
Ok(Self {
name: name.to_string(),
port: client.register_port(name, T::default())?,
connect: connects,
})
}
}

30
src/jack/process.rs Normal file
View file

@ -0,0 +1,30 @@
use super::*;
/// Trait for things that have a JACK process callback.
pub trait Process {
fn process (&mut self, _: &Client, _: &ProcessScope) -> Control {
Control::Continue
}
}
/// Define the JACK process callback associated with a struct.
#[macro_export] macro_rules! process {
($T:ty) => {
impl Process for $T {}
};
($T:ty |$self:ident, $c:ident, $s:ident|$block:tt) => {
impl Process for $T {
fn process (&mut $self, $c: &Client, $s: &ProcessScope) -> Control {
$block
}
}
};
($T:ty = $process:path) => {
impl Process for $T {
fn process (&mut self, c: &Client, s: &ProcessScope) -> Control {
$process(self, c, s)
}
}
}
}

View file

@ -3,7 +3,7 @@
#![allow(macro_expanded_macro_exports_accessed_by_absolute_paths)]
extern crate clap;
extern crate jack;
extern crate jack as _jack;
extern crate crossterm;
pub mod cli;
@ -12,6 +12,7 @@ pub mod control;
pub mod core;
pub mod model;
pub mod view;
pub mod jack;
use crate::{core::*, model::*};
@ -35,48 +36,6 @@ pub fn main () -> Usually<()> {
let client = jack.as_client();
let timebase = &state.timebase;
let ppq = timebase.ppq() as usize;
state.tracks = vec![
Track::new("Drums", &jack.as_client(), Some(vec![
Phrase::new("4 kicks", ppq * 4, Some(phrase! {
00 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
04 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
08 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
12 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
})),
]), Some(vec![
Sampler::new("Sampler", Some(BTreeMap::from([
sample!(36, "Kick", "/home/user/Lab/Music/pak/kik.wav"),
sample!(40, "Snare", "/home/user/Lab/Music/pak/sna.wav"),
sample!(44, "Hihat", "/home/user/Lab/Music/pak/chh.wav"),
])))?,
Plugin::lv2(
"Panagement",
"file:///home/user/.lv2/Auburn Sounds Panagement 2.lv2"
)?,
]),)?,
Track::new("Bass", &jack.as_client(), Some(vec![
Phrase::new("Offbeat", ppq * 4, Some(phrase! {
00 * ppq/4 => MidiMessage::NoteOff { key: 40.into(), vel: 100.into() },
02 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
04 * ppq/4 => MidiMessage::NoteOff { key: 40.into(), vel: 100.into() },
06 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
08 * ppq/4 => MidiMessage::NoteOff { key: 40.into(), vel: 100.into() },
10 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
12 * ppq/4 => MidiMessage::NoteOff { key: 40.into(), vel: 100.into() },
14 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
})),
]), Some(vec![
Plugin::lv2("Odin2", "file:///home/user/.lv2/Odin2.lv2")?,
]))?,
];
//for track in state.tracks.iter() {
//if let Some(port) = track.midi_ins()?.get(0) {
//client.connect_ports(&track.midi_out, port)?;
//}
//}
state.tracks[0].sequence = Some(0);
state.tracks[1].sequence = Some(0);
state.track_cursor = 1;
@ -87,6 +46,51 @@ pub fn main () -> Usually<()> {
state.transport = Some(client.transport());
state.playing = Some(TransportState::Stopped);
state.jack = Some(jack);
let drums = state.add_track(Some("Drums"))?;
drums.add_device(Sampler::new("Sampler", Some(BTreeMap::from([
sample!(36, "Kick", "/home/user/Lab/Music/pak/kik.wav"),
sample!(40, "Snare", "/home/user/Lab/Music/pak/sna.wav"),
sample!(44, "Hihat", "/home/user/Lab/Music/pak/chh.wav"),
])))?);
drums.add_device(Plugin::lv2(
"Panagement",
"file:///home/user/.lv2/Auburn Sounds Panagement 2.lv2"
)?);
drums.add_phrase("4 kicks", ppq * 4, Some(phrase! {
00 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
04 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
08 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
12 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
}));
drums.add_phrase("D-Beat", ppq * 4, Some(phrase! {
00 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
04 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
08 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
10 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
12 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
}));
drums.add_phrase("Garage", ppq * 4, Some(phrase! {
00 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
04 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
11 * ppq/4 => MidiMessage::NoteOn { key: 36.into(), vel: 100.into() },
12 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
}));
let bass = state.add_track(Some("Bass"))?;
bass.add_device(Plugin::lv2("Odin2", "file:///home/user/.lv2/Odin2.lv2")?);
bass.add_phrase("Offbeat", ppq * 4, Some(phrase! {
00 * ppq/4 => MidiMessage::NoteOff { key: 40.into(), vel: 100.into() },
02 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
04 * ppq/4 => MidiMessage::NoteOff { key: 40.into(), vel: 100.into() },
06 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
08 * ppq/4 => MidiMessage::NoteOff { key: 40.into(), vel: 100.into() },
10 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
12 * ppq/4 => MidiMessage::NoteOff { key: 40.into(), vel: 100.into() },
14 * ppq/4 => MidiMessage::NoteOn { key: 40.into(), vel: 100.into() },
}));
Ok(())
}))
}
@ -205,6 +209,47 @@ impl App {
};
Ok(())
}
pub fn add_scene (&mut self, name: Option<&str>) -> Usually<&mut Scene> {
let name = name.ok_or_else(||format!("Scene {}", self.scenes.len() + 1))?;
self.scenes.push(Scene::new(&name, vec![]));
self.scene_cursor = self.scenes.len();
Ok(&mut self.scenes[self.scene_cursor - 1])
}
pub fn add_track (&mut self, name: Option<&str>) -> Usually<&mut Track> {
let name = name.ok_or_else(||format!("Track {}", self.tracks.len() + 1))?;
self.tracks.push(Track::new(&name, self.jack.as_ref().unwrap().as_client(), None, None)?);
self.track_cursor = self.tracks.len();
Ok(&mut self.tracks[self.track_cursor - 1])
}
pub fn track (&self) -> Option<(usize, &Track)> {
match self.track_cursor { 0 => None, _ => {
let id = self.track_cursor as usize - 1;
self.tracks.get(id).map(|t|(id, t))
} }
}
pub fn track_mut (&mut self) -> Option<(usize, &mut Track)> {
match self.track_cursor { 0 => None, _ => {
let id = self.track_cursor as usize - 1;
self.tracks.get_mut(id).map(|t|(id, t))
} }
}
pub fn scene (&self) -> Option<(usize, &Scene)> {
match self.scene_cursor { 0 => None, _ => {
let id = self.scene_cursor as usize - 1;
self.scenes.get(id).map(|t|(id, t))
} }
}
pub fn scene_mut (&mut self) -> Option<(usize, &mut Scene)> {
match self.scene_cursor { 0 => None, _ => {
let id = self.scene_cursor as usize - 1;
self.scenes.get_mut(id).map(|t|(id, t))
} }
}
pub fn phrase_id (&self) -> Option<usize> {
let (track_id, _) = self.track()?;
let (_, scene) = self.scene()?;
*scene.clips.get(track_id)?
}
pub fn connect_tracks (&self) -> Usually<()> {
//let (client, _status) = Client::new(
//&format!("{}-init", &self.name), ClientOptions::NO_START_SERVER
@ -233,45 +278,4 @@ impl App {
//}
Ok(())
}
pub fn add_scene (&mut self, name: Option<&str>) -> Usually<&mut Scene> {
let name = name.ok_or_else(||format!("Scene {}", self.scenes.len() + 1))?;
self.scenes.push(Scene::new(&name, vec![]));
self.scene_cursor = self.scenes.len();
Ok(&mut self.scenes[self.scene_cursor - 1])
}
pub fn add_track (&mut self, name: Option<&str>) -> Usually<&mut Track> {
let name = name.ok_or_else(||format!("Track {}", self.tracks.len() + 1))?;
self.tracks.push(Track::new(&name, self.jack.as_ref().unwrap().as_client(), None, None)?);
self.track_cursor = self.tracks.len();
Ok(&mut self.tracks[self.track_cursor - 1])
}
pub fn track (&self) -> Option<(usize, &Track)> {
match self.track_cursor { 0 => None, _ => {
let id = self.track_cursor as usize - 1;
self.tracks.get(id).map(|t|(id, t))
} }
}
pub fn track_mut (&mut self) -> Option<(usize, &mut Track)> {
match self.track_cursor { 0 => None, _ => {
let id = self.track_cursor as usize - 1;
self.tracks.get_mut(id).map(|t|(id, t))
} }
}
pub fn scene (&self) -> Option<(usize, &Scene)> {
match self.scene_cursor { 0 => None, _ => {
let id = self.scene_cursor as usize - 1;
self.scenes.get(id).map(|t|(id, t))
} }
}
pub fn scene_mut (&mut self) -> Option<(usize, &mut Scene)> {
match self.scene_cursor { 0 => None, _ => {
let id = self.scene_cursor as usize - 1;
self.scenes.get_mut(id).map(|t|(id, t))
} }
}
pub fn phrase_id (&self) -> Option<usize> {
let (track_id, _) = self.track()?;
let (_, scene) = self.scene()?;
*scene.clips.get(track_id)?
}
}

View file

@ -38,22 +38,29 @@ impl Track {
recording: false,
overdub: true,
sequence: None,
phrases: phrases.unwrap_or_else(||vec![]),
devices: devices.unwrap_or_else(||vec![]),
phrases: phrases.unwrap_or_else(||Vec::with_capacity(16)),
devices: devices.unwrap_or_else(||Vec::with_capacity(16)),
device: 0,
})
}
pub fn device (&self, i: usize) -> Option<MutexGuard<Box<dyn Device>>> {
pub fn add_device (
&mut self, device: JackDevice
) -> &mut JackDevice {
self.devices.push(device);
let index = self.devices.len() - 1;
&mut self.devices[index]
}
pub fn get_device (&self, i: usize) -> Option<MutexGuard<Box<dyn Device>>> {
self.devices.get(i).map(|d|d.state.lock().unwrap())
}
pub fn active_device (&self) -> Option<MutexGuard<Box<dyn Device>>> {
self.device(self.device)
pub fn device (&self) -> Option<MutexGuard<Box<dyn Device>>> {
self.get_device(self.device)
}
pub fn first_device (&self) -> Option<MutexGuard<Box<dyn Device>>> {
self.device(0)
self.get_device(0)
}
pub fn last_device (&self) -> Option<MutexGuard<Box<dyn Device>>> {
self.device(self.devices.len().saturating_sub(1))
self.get_device(self.devices.len().saturating_sub(1))
}
pub fn phrase (&self) -> Option<&Phrase> {
if let Some(phrase) = self.sequence {
@ -69,6 +76,13 @@ impl Track {
None
}
}
pub fn add_phrase (
&mut self, name: &str, length: usize, data: Option<PhraseData>
) -> &mut Phrase {
self.phrases.push(Phrase::new(name, length, data));
let index = self.phrases.len() - 1;
&mut self.phrases[index]
}
}
ports!(Track {

View file

@ -4,9 +4,11 @@ use crate::{core::*,model::*,view::*};
pub enum SequencerMode { Tiny, Compact, Horizontal, Vertical, }
pub struct SequencerView<'a> {
pub focused: bool,
pub phrase: Option<&'a Phrase>,
pub ppq: usize,
pub focused: bool,
/// Displayed phrase
pub phrase: Option<&'a Phrase>,
/// Resolution of MIDI sequence
pub ppq: usize,
/// Range of notes to display
pub note_start: usize,
/// Position of cursor within note range
@ -17,9 +19,8 @@ pub struct SequencerView<'a> {
pub time_start: usize,
/// Position of cursor within time range
pub time_cursor: usize,
/// Current time
pub now: usize
pub now: usize
}
impl<'a> Render for SequencerView<'a> {
@ -97,6 +98,10 @@ mod horizontal {
}
}
pub const KEYS_VERTICAL: [&'static str; 6] = [
"", "", "", "", "", "",
];
pub fn keys (buf: &mut Buffer, area: Rect, note0: usize, _notes: &[bool])
-> Usually<Rect>
{
@ -187,6 +192,7 @@ mod horizontal {
}
}
//pub fn footer (
//buf: &mut Buffer,
//area: Rect,
@ -293,6 +299,27 @@ mod horizontal {
//buf.set_string(x, y, c, Style::default());
//}
//pub const KEY_WHITE: Style = Style {
//fg: Some(Color::Gray),
//bg: None,
//underline_color: None,
//add_modifier: ::ratatui::style::Modifier::empty(),
//sub_modifier: ::ratatui::style::Modifier::empty(),
//};
//pub const KEY_BLACK: Style = Style {
//fg: Some(Color::Black),
//bg: None,
//underline_color: None,
//add_modifier: ::ratatui::style::Modifier::empty(),
//sub_modifier: ::ratatui::style::Modifier::empty(),
//};
//pub const KEY_STYLE: [Style;12] = [
//KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE,
//KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE,
//];
//pub fn keys (s: &Sequencer, buf: &mut Buffer, area: Rect, beat: usize) {
//let ppq = s.timebase.ppq() as usize;
//let Rect { x, y, .. } = area;