mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
i dont know why that worked
This commit is contained in:
parent
4ae62c5bc2
commit
b73aa8a0dc
17 changed files with 552 additions and 481 deletions
53
README.md
53
README.md
|
|
@ -1,53 +0,0 @@
|
||||||
# That's It!
|
|
||||||
|
|
||||||
Minimal, cross-environment user interface framework.
|
|
||||||
|
|
||||||
## Definitions
|
|
||||||
|
|
||||||
### User interface
|
|
||||||
|
|
||||||
A **user interface** is a program which, repeatedly:
|
|
||||||
* **renders** information to be displayed to the user;
|
|
||||||
* **handles** input from the user;
|
|
||||||
thus interactively performing tasks until an exit condition is met.
|
|
||||||
|
|
||||||
### Engine
|
|
||||||
|
|
||||||
An **engine** is the underlying platform responsible for:
|
|
||||||
* Displaying your program's `render`ed output to the user
|
|
||||||
* Reading user input to be `handle`d by your program.
|
|
||||||
|
|
||||||
For example, the `tui` engine is based on `crossterm`,
|
|
||||||
a library for rendering text user interfaces.
|
|
||||||
|
|
||||||
### Widget
|
|
||||||
|
|
||||||
A **widget** is any struct that implements the `Input<E, I>` and `Output<E, O>`
|
|
||||||
traits for a given engine `E`. This enables it to act as a component of the
|
|
||||||
user interface. Widgets may contain arbitrary state -- including other widgets.
|
|
||||||
We provide a set of basic widgets that allow you to define standard hierarchical
|
|
||||||
UI layouts. It's the implementor's responsibility to define
|
|
||||||
`render` and `handle` behaviors for custom widgets.
|
|
||||||
|
|
||||||
### Input
|
|
||||||
|
|
||||||
**To respond to user input**, implement the trait `Input`.
|
|
||||||
It has a single method, `handle`, which takes an input
|
|
||||||
event, and returns an engine-specific value.
|
|
||||||
|
|
||||||
In the case of the `tui` engine, the returned value is a
|
|
||||||
`bool` corresponding to whether the input event was captured
|
|
||||||
by the current widget. Returning `true` from `render` terminates
|
|
||||||
the handling of the current event; returning `false` "bubbles" it
|
|
||||||
to the parent widget.
|
|
||||||
|
|
||||||
### Output
|
|
||||||
|
|
||||||
**To display data to the user**, implement the trait `Output`.
|
|
||||||
It has a single method, `render`, which takes a mutable instance
|
|
||||||
of the engine, and returns and engine-specific value.
|
|
||||||
|
|
||||||
In the case of the `tui` engine, the returned value is `[u16;2]`,
|
|
||||||
corresponding to the size requested by the widget. This allows
|
|
||||||
the layout components to implement dynamic, responsive layouts in the
|
|
||||||
terminal.
|
|
||||||
106
src/device.rs
106
src/device.rs
|
|
@ -16,8 +16,6 @@ pub use self::mixer::Mixer;
|
||||||
pub use self::looper::Looper;
|
pub use self::looper::Looper;
|
||||||
pub use self::plugin::Plugin;
|
pub use self::plugin::Plugin;
|
||||||
|
|
||||||
use crate::prelude::*;
|
|
||||||
use std::sync::mpsc;
|
|
||||||
use crossterm::event;
|
use crossterm::event;
|
||||||
|
|
||||||
pub fn run (device: impl Device + Send + Sync + 'static) -> Result<(), Box<dyn Error>> {
|
pub fn run (device: impl Device + Send + Sync + 'static) -> Result<(), Box<dyn Error>> {
|
||||||
|
|
@ -43,7 +41,7 @@ pub fn run (device: impl Device + Send + Sync + 'static) -> Result<(), Box<dyn E
|
||||||
}) => {
|
}) => {
|
||||||
exited.store(true, Ordering::Relaxed);
|
exited.store(true, Ordering::Relaxed);
|
||||||
},
|
},
|
||||||
_ => if device.lock().unwrap().handle(&EngineEvent::Input(event)).is_err() {
|
_ => if device.lock().unwrap().handle(&AppEvent::Input(event)).is_err() {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -90,7 +88,7 @@ pub fn run (device: impl Device + Send + Sync + 'static) -> Result<(), Box<dyn E
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Device: Send + Sync {
|
pub trait Device: Send + Sync {
|
||||||
fn handle (&mut self, _event: &EngineEvent) -> Usually<()> {
|
fn handle (&mut self, _event: &AppEvent) -> Usually<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn render (&self, _buffer: &mut Buffer, _area: Rect) -> Usually<Rect> {
|
fn render (&self, _buffer: &mut Buffer, _area: Rect) -> Usually<Rect> {
|
||||||
|
|
@ -100,28 +98,32 @@ pub trait Device: Send + Sync {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DynamicDevice<T> {
|
pub struct DynamicDevice<T> {
|
||||||
pub state: Mutex<T>,
|
pub state: Arc<Mutex<T>>,
|
||||||
pub render: Mutex<Box<dyn FnMut(&T, &mut Buffer, Rect)->Usually<Rect> + Send>>,
|
pub render: Mutex<Box<dyn FnMut(&T, &mut Buffer, Rect)->Usually<Rect> + Send>>,
|
||||||
pub handle: Mutex<Box<dyn FnMut(&mut T, &EngineEvent)->Usually<()> + Send>>,
|
pub handle: Arc<Mutex<Box<dyn FnMut(&mut T, &AppEvent)->Usually<()> + Send>>>,
|
||||||
pub process: Mutex<Box<dyn FnMut(&mut T) + Send>>
|
pub process: Arc<Mutex<Box<dyn FnMut(&mut T, &Client, &ProcessScope)->Control + Send>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Send + Sync> Device for DynamicDevice<T> {
|
||||||
|
fn handle (&mut self, event: &AppEvent) -> Usually<()> {
|
||||||
|
self.handle.lock().unwrap()(&mut *self.state.lock().unwrap(), event)
|
||||||
|
}
|
||||||
|
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||||
|
self.render.lock().unwrap()(&*self.state.lock().unwrap(), buf, area)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> DynamicDevice<T> {
|
impl<T> DynamicDevice<T> {
|
||||||
fn new <'a, R, H, P> (
|
fn new <'a, R, H, P> (render: R, handle: H, process: P, state: T) -> Self where
|
||||||
render: R,
|
|
||||||
handle: H,
|
|
||||||
process: P,
|
|
||||||
state: T
|
|
||||||
) -> Self where
|
|
||||||
R: FnMut(&T, &mut Buffer, Rect)->Usually<Rect> + Send + 'static,
|
R: FnMut(&T, &mut Buffer, Rect)->Usually<Rect> + Send + 'static,
|
||||||
H: FnMut(&mut T, &EngineEvent) -> Result<(), Box<dyn Error>> + Send + 'static,
|
H: FnMut(&mut T, &AppEvent) -> Result<(), Box<dyn Error>> + Send + 'static,
|
||||||
P: FnMut(&mut T) + Send + 'static
|
P: FnMut(&mut T, &Client, &ProcessScope)->Control + Send + 'static,
|
||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
|
state: Arc::new(Mutex::new(state)),
|
||||||
render: Mutex::new(Box::new(render)),
|
render: Mutex::new(Box::new(render)),
|
||||||
handle: Mutex::new(Box::new(handle)),
|
handle: Arc::new(Mutex::new(Box::new(handle))),
|
||||||
process: Mutex::new(Box::new(process)),
|
process: Arc::new(Mutex::new(Box::new(process))),
|
||||||
state: Mutex::new(state),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn state (&self) -> std::sync::MutexGuard<'_, T> {
|
fn state (&self) -> std::sync::MutexGuard<'_, T> {
|
||||||
|
|
@ -129,12 +131,29 @@ impl<T> DynamicDevice<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Send + Sync> Device for DynamicDevice<T> {
|
impl<T: NotificationHandler + 'static> DynamicDevice<T> {
|
||||||
fn handle (&mut self, event: &EngineEvent) -> Usually<()> {
|
fn activate (&self, client: Client) -> Usually<()> {
|
||||||
self.handle.lock().unwrap()(&mut *self.state.lock().unwrap(), event)
|
let notifications = {
|
||||||
|
let state = self.state.clone();
|
||||||
|
let handle = self.handle.clone();
|
||||||
|
move|event|{
|
||||||
|
let mut state = state.lock().unwrap();
|
||||||
|
let mut handle = handle.lock().unwrap();
|
||||||
|
handle(&mut state, &event).unwrap()
|
||||||
}
|
}
|
||||||
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
};
|
||||||
self.render.lock().unwrap()(&*self.state.lock().unwrap(), buf, area)
|
let notifications = Notifications(Box::new(notifications));
|
||||||
|
let handler = ClosureProcessHandler::new(Box::new({
|
||||||
|
let state = self.state.clone();
|
||||||
|
let process = self.process.clone();
|
||||||
|
move|client: &Client, scope: &ProcessScope|{
|
||||||
|
let mut state = state.lock().unwrap();
|
||||||
|
let mut process = process.lock().unwrap();
|
||||||
|
(process)(&mut state, client, scope)
|
||||||
|
}
|
||||||
|
}) as BoxedProcessHandler);
|
||||||
|
client.activate_async(notifications, handler)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -150,16 +169,9 @@ impl WidgetRef for dyn Device {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Engine {
|
|
||||||
exited: Arc<AtomicBool>,
|
|
||||||
sender: Sender<Event>,
|
|
||||||
receiver: Receiver<Event>,
|
|
||||||
pub jack_client: Jack<Notifications>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum EngineEvent {
|
pub enum AppEvent {
|
||||||
/// An input event that must be handled.
|
/// Terminal input
|
||||||
Input(::crossterm::event::Event),
|
Input(::crossterm::event::Event),
|
||||||
/// Update values but not the whole form.
|
/// Update values but not the whole form.
|
||||||
Update,
|
Update,
|
||||||
|
|
@ -169,6 +181,8 @@ pub enum EngineEvent {
|
||||||
Focus,
|
Focus,
|
||||||
/// Device loses focus
|
/// Device loses focus
|
||||||
Blur,
|
Blur,
|
||||||
|
/// JACK notification
|
||||||
|
Jack(JackEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn activate_jack_client <N: NotificationHandler + Sync + 'static> (
|
pub fn activate_jack_client <N: NotificationHandler + Sync + 'static> (
|
||||||
|
|
@ -191,40 +205,64 @@ fn panic_hook (info: &std::panic::PanicInfo) {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Notifications;//(mpsc::Sender<self::Event>);
|
#[derive(Debug)]
|
||||||
|
pub enum JackEvent {
|
||||||
|
ThreadInit,
|
||||||
|
Shutdown,
|
||||||
|
Freewheel,
|
||||||
|
SampleRate,
|
||||||
|
ClientRegistration,
|
||||||
|
PortRegistration,
|
||||||
|
PortRename,
|
||||||
|
PortsConnected,
|
||||||
|
GraphReorder,
|
||||||
|
XRun,
|
||||||
|
}
|
||||||
|
|
||||||
impl NotificationHandler for Notifications {
|
pub struct Notifications<T: Fn(AppEvent) + Send>(T);
|
||||||
|
|
||||||
|
impl<T: Fn(AppEvent) + Send> NotificationHandler for Notifications<T> {
|
||||||
fn thread_init (&self, _: &Client) {
|
fn thread_init (&self, _: &Client) {
|
||||||
|
self.0(AppEvent::Jack(JackEvent::ThreadInit));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn shutdown (&mut self, status: ClientStatus, reason: &str) {
|
fn shutdown (&mut self, status: ClientStatus, reason: &str) {
|
||||||
|
self.0(AppEvent::Jack(JackEvent::Shutdown));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn freewheel (&mut self, _: &Client, is_enabled: bool) {
|
fn freewheel (&mut self, _: &Client, is_enabled: bool) {
|
||||||
|
self.0(AppEvent::Jack(JackEvent::Freewheel));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sample_rate (&mut self, _: &Client, _: Frames) -> Control {
|
fn sample_rate (&mut self, _: &Client, _: Frames) -> Control {
|
||||||
|
self.0(AppEvent::Jack(JackEvent::SampleRate));
|
||||||
Control::Quit
|
Control::Quit
|
||||||
}
|
}
|
||||||
|
|
||||||
fn client_registration (&mut self, _: &Client, name: &str, is_reg: bool) {
|
fn client_registration (&mut self, _: &Client, name: &str, is_reg: bool) {
|
||||||
|
self.0(AppEvent::Jack(JackEvent::ClientRegistration));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn port_registration (&mut self, _: &Client, port_id: PortId, is_reg: bool) {
|
fn port_registration (&mut self, _: &Client, port_id: PortId, is_reg: bool) {
|
||||||
|
self.0(AppEvent::Jack(JackEvent::PortRegistration));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn port_rename (&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control {
|
fn port_rename (&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control {
|
||||||
|
self.0(AppEvent::Jack(JackEvent::PortRename));
|
||||||
Control::Continue
|
Control::Continue
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ports_connected (&mut self, _: &Client, id_a: PortId, id_b: PortId, are: bool) {
|
fn ports_connected (&mut self, _: &Client, id_a: PortId, id_b: PortId, are: bool) {
|
||||||
|
self.0(AppEvent::Jack(JackEvent::PortsConnected));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn graph_reorder (&mut self, _: &Client) -> Control {
|
fn graph_reorder (&mut self, _: &Client) -> Control {
|
||||||
|
self.0(AppEvent::Jack(JackEvent::GraphReorder));
|
||||||
Control::Continue
|
Control::Continue
|
||||||
}
|
}
|
||||||
|
|
||||||
fn xrun (&mut self, _: &Client) -> Control {
|
fn xrun (&mut self, _: &Client) -> Control {
|
||||||
|
self.0(AppEvent::Jack(JackEvent::XRun));
|
||||||
Control::Continue
|
Control::Continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,17 +7,14 @@ pub struct Chain {
|
||||||
|
|
||||||
impl Chain {
|
impl Chain {
|
||||||
pub fn new (name: &str, devices: Vec<Box<dyn Device>>) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
|
pub fn new (name: &str, devices: Vec<Box<dyn Device>>) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
|
||||||
Ok(DynamicDevice::new(render, handle, |_|{}, Self {
|
Ok(DynamicDevice::new(render, handle, process, Self {
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
devices
|
devices
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process (
|
pub fn process (state: &mut Chain, client: &Client, scope: &ProcessScope) -> Control {
|
||||||
client: &Client,
|
|
||||||
scope: &ProcessScope
|
|
||||||
) -> Control {
|
|
||||||
Control::Continue
|
Control::Continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,6 +51,6 @@ pub fn render (state: &Chain, buf: &mut Buffer, area: Rect)
|
||||||
Ok(Rect { x: area.x, y: area.y, width: x, height: y })
|
Ok(Rect { x: area.x, y: area.y, width: x, height: y })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle (state: &mut Chain, event: &EngineEvent) -> Result<(), Box<dyn Error>> {
|
pub fn handle (state: &mut Chain, event: &AppEvent) -> Result<(), Box<dyn Error>> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ pub struct Launcher {
|
||||||
|
|
||||||
impl Launcher {
|
impl Launcher {
|
||||||
pub fn new (name: &str) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
|
pub fn new (name: &str) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
|
||||||
Ok(DynamicDevice::new(render, handle, |_|{}, Self {
|
Ok(DynamicDevice::new(render, handle, Self {
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,14 @@ pub struct Looper {
|
||||||
|
|
||||||
impl Looper {
|
impl Looper {
|
||||||
pub fn new (name: &str) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
|
pub fn new (name: &str) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
|
||||||
Ok(DynamicDevice::new(render, handle, |_|{}, Self {
|
Ok(DynamicDevice::new(render, handle, process, Self {
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process (
|
pub fn process (
|
||||||
|
state: &mut Looper,
|
||||||
client: &Client,
|
client: &Client,
|
||||||
scope: &ProcessScope
|
scope: &ProcessScope
|
||||||
) -> Control {
|
) -> Control {
|
||||||
|
|
@ -32,7 +33,7 @@ pub fn render (state: &Looper, buf: &mut Buffer, area: Rect)
|
||||||
Ok(Rect::default())
|
Ok(Rect::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle (state: &mut Looper, event: &EngineEvent) -> Result<(), Box<dyn Error>> {
|
pub fn handle (state: &mut Looper, event: &AppEvent) -> Result<(), Box<dyn Error>> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ use crate::prelude::*;
|
||||||
|
|
||||||
pub struct Mixer {
|
pub struct Mixer {
|
||||||
name: String,
|
name: String,
|
||||||
jack: Jack<MixerJack>,
|
|
||||||
tracks: Vec<Track>,
|
tracks: Vec<Track>,
|
||||||
selected_track: usize,
|
selected_track: usize,
|
||||||
selected_column: usize,
|
selected_column: usize,
|
||||||
|
|
@ -10,33 +9,27 @@ pub struct Mixer {
|
||||||
|
|
||||||
impl Mixer {
|
impl Mixer {
|
||||||
pub fn new (name: &str) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
|
pub fn new (name: &str) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
|
||||||
let (client, status) = Client::new(
|
let (client, _status) = Client::new(name, ClientOptions::NO_START_SERVER)?;
|
||||||
name,
|
Ok(DynamicDevice::new(render, handle, process, Self {
|
||||||
ClientOptions::NO_START_SERVER
|
|
||||||
)?;
|
|
||||||
let jack = MixerJack.activate(client, ClosureProcessHandler::new(Box::new(
|
|
||||||
move|client: &Client, scope: &ProcessScope|process(client, scope)
|
|
||||||
) as BoxedProcessHandler))?;
|
|
||||||
Ok(DynamicDevice::new(render, handle, |_|{}, Self {
|
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
selected_column: 0,
|
selected_column: 0,
|
||||||
selected_track: 1,
|
selected_track: 1,
|
||||||
tracks: vec![
|
tracks: vec![
|
||||||
Track::new(&jack.as_client(), 1, "Mono 1")?,
|
Track::new(&client, 1, "Mono 1")?,
|
||||||
Track::new(&jack.as_client(), 1, "Mono 2")?,
|
Track::new(&client, 1, "Mono 2")?,
|
||||||
Track::new(&jack.as_client(), 2, "Stereo 1")?,
|
Track::new(&client, 2, "Stereo 1")?,
|
||||||
Track::new(&jack.as_client(), 2, "Stereo 2")?,
|
Track::new(&client, 2, "Stereo 2")?,
|
||||||
Track::new(&jack.as_client(), 2, "Stereo 3")?,
|
Track::new(&client, 2, "Stereo 3")?,
|
||||||
Track::new(&jack.as_client(), 2, "Bus 1")?,
|
Track::new(&client, 2, "Bus 1")?,
|
||||||
Track::new(&jack.as_client(), 2, "Bus 2")?,
|
Track::new(&client, 2, "Bus 2")?,
|
||||||
Track::new(&jack.as_client(), 2, "Mix")?,
|
Track::new(&client, 2, "Mix")?,
|
||||||
],
|
],
|
||||||
jack,
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process (
|
pub fn process (
|
||||||
|
mixer: &mut Mixer,
|
||||||
client: &Client,
|
client: &Client,
|
||||||
scope: &ProcessScope
|
scope: &ProcessScope
|
||||||
) -> Control {
|
) -> Control {
|
||||||
|
|
@ -107,8 +100,8 @@ pub fn render (state: &Mixer, buf: &mut Buffer, mut area: Rect)
|
||||||
Ok(area)
|
Ok(area)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle (state: &mut Mixer, event: &EngineEvent) -> Result<(), Box<dyn Error>> {
|
pub fn handle (state: &mut Mixer, event: &AppEvent) -> Result<(), Box<dyn Error>> {
|
||||||
if let EngineEvent::Input(crossterm::event::Event::Key(event)) = event {
|
if let AppEvent::Input(crossterm::event::Event::Key(event)) = event {
|
||||||
|
|
||||||
match event.code {
|
match event.code {
|
||||||
//KeyCode::Char('c') => {
|
//KeyCode::Char('c') => {
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,16 @@ pub struct Plugin {
|
||||||
|
|
||||||
impl Plugin {
|
impl Plugin {
|
||||||
pub fn new (name: &str) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
|
pub fn new (name: &str) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
|
||||||
Ok(DynamicDevice::new(render, handle, |_|{}, Self {
|
Ok(DynamicDevice::new(render, handle, process, Self {
|
||||||
name: name.into()
|
name: name.into()
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn process (state: &mut Plugin, client: &Client, scope: &ProcessScope) -> Control {
|
||||||
|
Control::Continue
|
||||||
|
}
|
||||||
|
|
||||||
pub fn render (state: &Plugin, buf: &mut Buffer, Rect { x, y, width, height }: Rect)
|
pub fn render (state: &Plugin, buf: &mut Buffer, Rect { x, y, width, height }: Rect)
|
||||||
-> Usually<Rect>
|
-> Usually<Rect>
|
||||||
{
|
{
|
||||||
|
|
@ -27,6 +31,6 @@ pub fn render (state: &Plugin, buf: &mut Buffer, Rect { x, y, width, height }: R
|
||||||
Ok(Rect { x, y, width: 40, height: 7 })
|
Ok(Rect { x, y, width: 40, height: 7 })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle (state: &mut Plugin, event: &EngineEvent) -> Result<(), Box<dyn Error>> {
|
pub fn handle (state: &mut Plugin, event: &AppEvent) -> Result<(), Box<dyn Error>> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ pub const ACTIONS: [(&'static str, &'static str);2] = [
|
||||||
|
|
||||||
pub struct Sampler {
|
pub struct Sampler {
|
||||||
name: String,
|
name: String,
|
||||||
jack: Jack<SamplerJack>,
|
input: ::jack::Port<::jack::MidiIn>,
|
||||||
samples: Arc<Mutex<Vec<Sample>>>,
|
samples: Arc<Mutex<Vec<Sample>>>,
|
||||||
selected_sample: usize,
|
selected_sample: usize,
|
||||||
selected_column: usize,
|
selected_column: usize,
|
||||||
|
|
@ -23,36 +23,23 @@ impl Sampler {
|
||||||
];
|
];
|
||||||
let samples = Arc::new(Mutex::new(samples));
|
let samples = Arc::new(Mutex::new(samples));
|
||||||
let input = client.register_port("trigger", ::jack::MidiIn::default())?;
|
let input = client.register_port("trigger", ::jack::MidiIn::default())?;
|
||||||
let jack = SamplerJack.activate(client, ClosureProcessHandler::new({
|
Ok(DynamicDevice::new(render, handle, process, Self {
|
||||||
let exited = exited.clone();
|
|
||||||
let samples = samples.clone();
|
|
||||||
Box::new(move |client: &Client, scope: &ProcessScope|{
|
|
||||||
process(client, scope, &exited, &samples, &input);
|
|
||||||
Control::Continue
|
|
||||||
}) as BoxedProcessHandler
|
|
||||||
}))?;
|
|
||||||
Ok(DynamicDevice::new(render, handle, |_|{}, Self {
|
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
|
input,
|
||||||
selected_sample: 0,
|
selected_sample: 0,
|
||||||
selected_column: 0,
|
selected_column: 0,
|
||||||
samples,
|
samples,
|
||||||
jack,
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process (
|
pub fn process (
|
||||||
|
state: &mut Sampler,
|
||||||
client: &Client,
|
client: &Client,
|
||||||
scope: &ProcessScope,
|
scope: &ProcessScope,
|
||||||
exited: &Arc<AtomicBool>,
|
|
||||||
samples: &Arc<Mutex<Vec<Sample>>>,
|
|
||||||
input: &Port<MidiIn>
|
|
||||||
) -> Control {
|
) -> Control {
|
||||||
if exited.fetch_and(true, Ordering::Relaxed) {
|
let mut samples = state.samples.lock().unwrap();
|
||||||
return Control::Quit
|
for event in state.input.iter(scope) {
|
||||||
}
|
|
||||||
let mut samples = samples.lock().unwrap();
|
|
||||||
for event in input.iter(scope) {
|
|
||||||
let len = 3.min(event.bytes.len());
|
let len = 3.min(event.bytes.len());
|
||||||
let mut data = [0; 3];
|
let mut data = [0; 3];
|
||||||
data[..len].copy_from_slice(&event.bytes[..len]);
|
data[..len].copy_from_slice(&event.bytes[..len]);
|
||||||
|
|
@ -183,7 +170,7 @@ pub fn render (state: &Sampler, buf: &mut Buffer, Rect { x, y, width, height }:
|
||||||
//Ok(())
|
//Ok(())
|
||||||
//}
|
//}
|
||||||
|
|
||||||
pub fn handle (state: &mut Sampler, event: &EngineEvent) -> Result<(), Box<dyn Error>> {
|
pub fn handle (state: &mut Sampler, event: &AppEvent) -> Result<(), Box<dyn Error>> {
|
||||||
//if let Event::Input(crossterm::event::Event::Key(event)) = event {
|
//if let Event::Input(crossterm::event::Event::Key(event)) = event {
|
||||||
//match event.code {
|
//match event.code {
|
||||||
//KeyCode::Char('c') => {
|
//KeyCode::Char('c') => {
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,15 @@ use ratatui::style::Stylize;
|
||||||
|
|
||||||
pub struct Sequencer {
|
pub struct Sequencer {
|
||||||
name: String,
|
name: String,
|
||||||
jack: Jack<SequencerJack>,
|
|
||||||
playing: Arc<AtomicBool>,
|
|
||||||
recording: Arc<AtomicBool>,
|
|
||||||
overdub: Arc<AtomicBool>,
|
|
||||||
inputs_open: Arc<AtomicBool>,
|
|
||||||
outputs_open: Arc<AtomicBool>,
|
|
||||||
cursor: (u16, u16, u16),
|
|
||||||
timesig: (f32, f32),
|
|
||||||
sequence: Arc<Mutex<Vec<Vec<Option<SequencerEvent>>>>>,
|
|
||||||
sequences: Arc<Mutex<Vec<MIDISequence>>>,
|
|
||||||
mode: SequencerMode,
|
mode: SequencerMode,
|
||||||
|
cursor: (u16, u16, u16),
|
||||||
|
rate: Hz,
|
||||||
|
tempo: Tempo,
|
||||||
|
sequences: Vec<Sequence>,
|
||||||
|
input_port: ::jack::Port<::jack::MidiIn>,
|
||||||
|
input_connect: Vec<String>,
|
||||||
|
output_port: ::jack::Port<::jack::MidiOut>,
|
||||||
|
output_connect: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum SequencerMode {
|
pub enum SequencerMode {
|
||||||
|
|
@ -24,120 +22,111 @@ pub enum SequencerMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sequencer {
|
impl Sequencer {
|
||||||
|
|
||||||
pub fn new (name: &str) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
|
pub fn new (name: &str) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
|
||||||
let beats = 4;
|
let (client, _status) = Client::new(name, ClientOptions::NO_START_SERVER)?;
|
||||||
let steps = 16;
|
let mut input = client.register_port("input", ::jack::MidiIn::default())?;
|
||||||
let bpm = 120.0;
|
let mut output = client.register_port("output", ::jack::MidiOut::default())?;
|
||||||
let rate = 44100; // Hz
|
let device = DynamicDevice::new(render, handle, process, Self {
|
||||||
let frame = 1f64 / rate as f64; // msec
|
|
||||||
let buf = 512; // frames
|
|
||||||
let t_beat = 60.0 / bpm; // msec
|
|
||||||
let t_loop = t_beat * beats as f64; // msec
|
|
||||||
let t_step = t_beat / steps as f64; // msec
|
|
||||||
let exited = Arc::new(AtomicBool::new(false));
|
|
||||||
let playing = Arc::new(AtomicBool::new(true));
|
|
||||||
let recording = Arc::new(AtomicBool::new(true));
|
|
||||||
let overdub = Arc::new(AtomicBool::new(false));
|
|
||||||
let sequence: Arc<Mutex<Vec<Vec<Option<SequencerEvent>>>>> = Arc::new(
|
|
||||||
Mutex::new(vec![vec![None;64];128])
|
|
||||||
);
|
|
||||||
let mut step_frames = vec![];
|
|
||||||
for step in 0..beats*steps {
|
|
||||||
let step_index = (step as f64 * t_step / frame) as usize;
|
|
||||||
step_frames.push(step_index);
|
|
||||||
}
|
|
||||||
let loop_frames = (t_loop*rate as f64) as usize;
|
|
||||||
let mut frame_steps: Vec<Option<usize>> = vec![None;loop_frames];
|
|
||||||
for (index, frame) in step_frames.iter().enumerate() {
|
|
||||||
frame_steps[*frame] = Some(index);
|
|
||||||
}
|
|
||||||
let (client, _status) = Client::new(
|
|
||||||
name, ClientOptions::NO_START_SERVER
|
|
||||||
)?;
|
|
||||||
let mut input = client.register_port(
|
|
||||||
"input", ::jack::MidiIn::default()
|
|
||||||
)?;
|
|
||||||
let mut output = client.register_port(
|
|
||||||
"output", ::jack::MidiOut::default()
|
|
||||||
)?;
|
|
||||||
Ok(DynamicDevice::new(render, handle, |_|{}, Self {
|
|
||||||
mode: SequencerMode::Vertical,
|
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
playing: playing.clone(),
|
mode: SequencerMode::Vertical,
|
||||||
recording: recording.clone(),
|
|
||||||
overdub: overdub.clone(),
|
|
||||||
sequence: sequence.clone(),
|
|
||||||
inputs_open: Arc::new(AtomicBool::new(false)),
|
|
||||||
outputs_open: Arc::new(AtomicBool::new(false)),
|
|
||||||
sequences: Arc::new(Mutex::new(vec![
|
|
||||||
])),
|
|
||||||
cursor: (11, 0, 0),
|
cursor: (11, 0, 0),
|
||||||
timesig: (4.0, 4.0),
|
rate: Hz(client.sample_rate() as u32),
|
||||||
jack: SequencerJack.activate(client, ClosureProcessHandler::new(
|
tempo: Tempo(120000),
|
||||||
Box::new(move |client: &Client, scope: &ProcessScope| process(client, scope)) as BoxedProcessHandler
|
sequences: vec![
|
||||||
))?
|
Sequence::new(&client, "Rhythm#000",)?,
|
||||||
}))
|
Sequence::new(&client, "Rhythm#001",)?,
|
||||||
|
Sequence::new(&client, "Rhythm#002",)?,
|
||||||
|
Sequence::new(&client, "Rhythm#003",)?,
|
||||||
|
],
|
||||||
|
input_port: client.register_port("in", ::jack::MidiIn::default())?,
|
||||||
|
input_connect: vec![],
|
||||||
|
output_port: client.register_port("out", ::jack::MidiOut::default())?,
|
||||||
|
output_connect: vec![],
|
||||||
|
});
|
||||||
|
device.activate(client)?;
|
||||||
|
Ok(device)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process (
|
pub fn process (state: &mut Sequencer, client: &Client, scope: &ProcessScope) -> Control {
|
||||||
client: &Client,
|
for seq in state.sequences.iter() {
|
||||||
scope: &ProcessScope
|
seq.chunk_out(scope, &mut state.output_port);
|
||||||
) -> Control {
|
seq.chunk_in(scope, &state.input_port);
|
||||||
//if exited.fetch_and(true, Ordering::Relaxed) {
|
}
|
||||||
//return Control::Quit
|
|
||||||
//}
|
|
||||||
//if client.transport().query_state().unwrap() == TransportState::Rolling {
|
|
||||||
//let chunk_start = scope.last_frame_time();
|
|
||||||
//let chunk_size = scope.n_frames();
|
|
||||||
//let chunk_end = chunk_start + chunk_size;
|
|
||||||
//let start_looped = chunk_start as usize % loop_frames;
|
|
||||||
//let end_looped = chunk_end as usize % loop_frames;
|
|
||||||
|
|
||||||
//// Write MIDI notes from input to sequence
|
|
||||||
//if recording.fetch_and(true, Ordering::Relaxed) {
|
|
||||||
////let overdub = overdub.fetch_and(true, Ordering::Relaxed);
|
|
||||||
//for event in input.iter(scope) {
|
|
||||||
//let ::jack::RawMidi { time, bytes } = event;
|
|
||||||
//println!("\n{time} {bytes:?}");
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
|
|
||||||
//// Write MIDI notes from sequence to output
|
|
||||||
//if playing.fetch_and(true, Ordering::Relaxed) {
|
|
||||||
//let mut writer = output.writer(scope);
|
|
||||||
//let sequence = sequence.lock().unwrap();
|
|
||||||
//for frame in 0..chunk_size {
|
|
||||||
//let value = frame_steps[(start_looped + frame as usize) % loop_frames];
|
|
||||||
//if let Some(step) = value {
|
|
||||||
//for track in sequence.iter() {
|
|
||||||
//for event in track[step].iter() {
|
|
||||||
//writer.write(&::jack::RawMidi {
|
|
||||||
//time: frame as u32,
|
|
||||||
//bytes: &match event {
|
|
||||||
//SequencerEvent::NoteOn(pitch, velocity) => [
|
|
||||||
//0b10010000,
|
|
||||||
//*pitch,
|
|
||||||
//*velocity
|
|
||||||
//],
|
|
||||||
//SequencerEvent::NoteOff(pitch) => [
|
|
||||||
//0b10000000,
|
|
||||||
//*pitch,
|
|
||||||
//0b00000000
|
|
||||||
//],
|
|
||||||
//}
|
|
||||||
//}).unwrap()
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
Control::Continue
|
Control::Continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Sequence {
|
||||||
|
is_recording: bool,
|
||||||
|
overdub: bool,
|
||||||
|
ppq: u32,
|
||||||
|
buffer: std::collections::BTreeMap<u32, Option<Vec<Vec<u8>>>>,
|
||||||
|
loop_points: Option<(u32, u32)>,
|
||||||
|
is_playing: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sequence {
|
||||||
|
fn new (client: &Client, name: &str) -> Usually<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
is_recording: false,
|
||||||
|
overdub: false,
|
||||||
|
ppq: 96,
|
||||||
|
buffer: std::collections::BTreeMap::new(),
|
||||||
|
loop_points: None,
|
||||||
|
is_playing: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn chunk_in (
|
||||||
|
&self,
|
||||||
|
scope: &ProcessScope,
|
||||||
|
port: &::jack::Port<::jack::MidiIn>,
|
||||||
|
) {
|
||||||
|
if self.is_recording {
|
||||||
|
for event in port.iter(scope) {
|
||||||
|
let ::jack::RawMidi { time, bytes } = event;
|
||||||
|
println!("\n{time} {bytes:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn chunk_out (
|
||||||
|
&self,
|
||||||
|
scope: &ProcessScope,
|
||||||
|
port: &mut ::jack::Port<::jack::MidiOut>,
|
||||||
|
) {
|
||||||
|
if self.is_playing {
|
||||||
|
let size = scope.n_frames();
|
||||||
|
let start = scope.last_frame_time();
|
||||||
|
let end = start + size;
|
||||||
|
let mut writer = port.writer(scope);
|
||||||
|
for time in 0..size {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl NotificationHandler for Sequencer {
|
||||||
|
fn thread_init (&self, _: &Client) {}
|
||||||
|
fn shutdown (&mut self, status: ClientStatus, reason: &str) {}
|
||||||
|
fn freewheel (&mut self, _: &Client, is_enabled: bool) {}
|
||||||
|
fn sample_rate (&mut self, _: &Client, _: Frames) -> Control {
|
||||||
|
Control::Quit
|
||||||
|
}
|
||||||
|
fn client_registration (&mut self, _: &Client, name: &str, is_reg: bool) {}
|
||||||
|
fn port_registration (&mut self, _: &Client, port_id: PortId, is_reg: bool) {}
|
||||||
|
fn port_rename (&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control {
|
||||||
|
Control::Continue
|
||||||
|
}
|
||||||
|
fn ports_connected (&mut self, _: &Client, id_a: PortId, id_b: PortId, are: bool) {}
|
||||||
|
fn graph_reorder (&mut self, _: &Client) -> Control {
|
||||||
|
Control::Continue
|
||||||
|
}
|
||||||
|
fn xrun (&mut self, _: &Client) -> Control {
|
||||||
|
Control::Continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub const ACTIONS: [(&'static str, &'static str);4] = [
|
pub const ACTIONS: [(&'static str, &'static str);4] = [
|
||||||
("+/-", "Zoom"),
|
("+/-", "Zoom"),
|
||||||
("A/D", "Add/delete note"),
|
("A/D", "Add/delete note"),
|
||||||
|
|
@ -145,12 +134,12 @@ pub const ACTIONS: [(&'static str, &'static str);4] = [
|
||||||
("CapsLock", "Auto advance"),
|
("CapsLock", "Auto advance"),
|
||||||
];
|
];
|
||||||
|
|
||||||
pub fn handle (state: &mut Sequencer, event: &EngineEvent) -> Result<(), Box<dyn Error>> {
|
pub fn handle (state: &mut Sequencer, event: &AppEvent) -> Result<(), Box<dyn Error>> {
|
||||||
|
|
||||||
if let EngineEvent::Focus = event {
|
if let AppEvent::Focus = event {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let EngineEvent::Input(Event::Key(event)) = event {
|
if let AppEvent::Input(Event::Key(event)) = event {
|
||||||
match event.code {
|
match event.code {
|
||||||
KeyCode::Down => {
|
KeyCode::Down => {
|
||||||
state.cursor.0 = if state.cursor.0 >= 23 {
|
state.cursor.0 = if state.cursor.0 >= 23 {
|
||||||
|
|
@ -189,20 +178,20 @@ pub fn handle (state: &mut Sequencer, event: &EngineEvent) -> Result<(), Box<dyn
|
||||||
state.cursor.2 = state.cursor.2 + 1
|
state.cursor.2 = state.cursor.2 + 1
|
||||||
},
|
},
|
||||||
KeyCode::Char('i') => {
|
KeyCode::Char('i') => {
|
||||||
state.inputs_open.fetch_xor(true, Ordering::Relaxed);
|
//state.inputs_open.fetch_xor(true, Ordering::Relaxed);
|
||||||
},
|
},
|
||||||
KeyCode::Char('o') => {
|
KeyCode::Char('o') => {
|
||||||
state.outputs_open.fetch_xor(true, Ordering::Relaxed);
|
//state.outputs_open.fetch_xor(true, Ordering::Relaxed);
|
||||||
},
|
},
|
||||||
KeyCode::Char('a') => {
|
KeyCode::Char('a') => {
|
||||||
let row = state.cursor.0 as usize;
|
let row = state.cursor.0 as usize;
|
||||||
let step = state.cursor.1 as usize;
|
let step = state.cursor.1 as usize;
|
||||||
let duration = state.cursor.2 as usize;
|
let duration = state.cursor.2 as usize;
|
||||||
let mut sequence = state.sequence.lock().unwrap();
|
//let mut sequence = state.sequence.lock().unwrap();
|
||||||
sequence[row][step] = Some(SequencerEvent::NoteOn(48 - row as u8, 128));
|
//sequence[row][step] = Some(SequencerEvent::NoteOn(48 - row as u8, 128));
|
||||||
if state.cursor.2 > 0 {
|
//if state.cursor.2 > 0 {
|
||||||
sequence[row][step + duration] = Some(SequencerEvent::NoteOff(35));
|
//sequence[row][step + duration] = Some(SequencerEvent::NoteOff(35));
|
||||||
}
|
//}
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
println!("{event:?}");
|
println!("{event:?}");
|
||||||
|
|
@ -518,186 +507,83 @@ fn draw_sequence_cursor (
|
||||||
buf.set_string(area.x + cursor.1 + 3, area.y + 1 + cursor_y, cursor_text, style);
|
buf.set_string(area.x + cursor.1 + 3, area.y + 1 + cursor_y, cursor_text, style);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SequencerJack;
|
//pub type MIDISequenceVoice = std::collections::BTreeMap<u32, NoteEvent>;
|
||||||
|
|
||||||
impl SequencerJack {
|
//#[derive(Clone)]
|
||||||
fn activate (self, client: Client, handler: ClosureProcessHandler<BoxedProcessHandler>)
|
//pub enum NoteEvent {
|
||||||
-> Usually<AsyncClient<Self, ClosureProcessHandler<BoxedProcessHandler>>>
|
//On(u8),
|
||||||
{
|
//Off(u8),
|
||||||
Ok(client.activate_async(self, handler)?)
|
//}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NotificationHandler for SequencerJack {
|
//const VOICE_EMPTY: MIDISequenceVoice = MIDISequenceVoice::new();
|
||||||
fn thread_init (&self, _: &Client) {
|
|
||||||
}
|
|
||||||
|
|
||||||
fn shutdown (&mut self, status: ClientStatus, reason: &str) {
|
//impl MIDISequence {
|
||||||
}
|
//fn new () -> Self {
|
||||||
|
//Self {
|
||||||
|
//channels: [
|
||||||
|
//MIDISequenceChannel::new(1),
|
||||||
|
//MIDISequenceChannel::new(2),
|
||||||
|
//MIDISequenceChannel::new(3),
|
||||||
|
//MIDISequenceChannel::new(4),
|
||||||
|
//MIDISequenceChannel::new(5),
|
||||||
|
//MIDISequenceChannel::new(6),
|
||||||
|
//MIDISequenceChannel::new(7),
|
||||||
|
//MIDISequenceChannel::new(8),
|
||||||
|
//MIDISequenceChannel::new(9),
|
||||||
|
//MIDISequenceChannel::new(10),
|
||||||
|
//MIDISequenceChannel::new(11),
|
||||||
|
//MIDISequenceChannel::new(12),
|
||||||
|
//MIDISequenceChannel::new(13),
|
||||||
|
//MIDISequenceChannel::new(14),
|
||||||
|
//MIDISequenceChannel::new(15),
|
||||||
|
//MIDISequenceChannel::new(16),
|
||||||
|
//]
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
|
||||||
fn freewheel (&mut self, _: &Client, is_enabled: bool) {
|
//pub struct MIDISequence {
|
||||||
}
|
//channels: [MIDISequenceChannel;16],
|
||||||
|
//}
|
||||||
|
//pub struct MIDISequenceChannel {
|
||||||
|
//number: u8,
|
||||||
|
//notes: [MIDISequenceVoice;128],
|
||||||
|
//}
|
||||||
|
//impl MIDISequenceChannel {
|
||||||
|
//fn new (number: u8) -> Self {
|
||||||
|
//Self {
|
||||||
|
//number,
|
||||||
|
//notes: [VOICE_EMPTY;128]
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
|
||||||
fn sample_rate (&mut self, _: &Client, _: Frames) -> Control {
|
//#[derive(Clone)]
|
||||||
Control::Quit
|
//pub enum SequencerEvent {
|
||||||
}
|
//NoteOn(u8, u8),
|
||||||
|
//NoteOff(u8)
|
||||||
|
//}
|
||||||
|
|
||||||
fn client_registration (&mut self, _: &Client, name: &str, is_reg: bool) {
|
//let buffer_index = self.to_buffer_index(chunk_start + frame, scope, bpm);
|
||||||
}
|
//let value = frame_steps[(start_looped + frame as usize) % loop_frames];
|
||||||
|
//if let Some(step) = value {
|
||||||
fn port_registration (&mut self, _: &Client, port_id: PortId, is_reg: bool) {
|
//for track in sequence.iter() {
|
||||||
}
|
//for event in track[step].iter() {
|
||||||
|
//writer.write(&::jack::RawMidi {
|
||||||
fn port_rename (&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control {
|
//time: frame as u32,
|
||||||
Control::Continue
|
//bytes: &match event {
|
||||||
}
|
//SequencerEvent::NoteOn(pitch, velocity) => [
|
||||||
|
//0b10010000,
|
||||||
fn ports_connected (&mut self, _: &Client, id_a: PortId, id_b: PortId, are: bool) {
|
//*pitch,
|
||||||
}
|
//*velocity
|
||||||
|
//],
|
||||||
fn graph_reorder (&mut self, _: &Client) -> Control {
|
//SequencerEvent::NoteOff(pitch) => [
|
||||||
Control::Continue
|
//0b10000000,
|
||||||
}
|
//*pitch,
|
||||||
|
//0b00000000
|
||||||
fn xrun (&mut self, _: &Client) -> Control {
|
//],
|
||||||
Control::Continue
|
//}
|
||||||
}
|
//}).unwrap()
|
||||||
}
|
//}
|
||||||
|
//}
|
||||||
pub struct MIDISequence {
|
//}
|
||||||
channels: [MIDISequenceChannel;16],
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct MIDISequenceChannel {
|
|
||||||
number: u8,
|
|
||||||
notes: [MIDISequenceVoice;128],
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type MIDISequenceVoice = std::collections::BTreeMap<u32, NoteEvent>;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub enum NoteEvent {
|
|
||||||
On(u8),
|
|
||||||
Off(u8),
|
|
||||||
}
|
|
||||||
|
|
||||||
const VOICE_EMPTY: MIDISequenceVoice = MIDISequenceVoice::new();
|
|
||||||
|
|
||||||
impl MIDISequence {
|
|
||||||
fn new () -> Self {
|
|
||||||
Self {
|
|
||||||
channels: [
|
|
||||||
MIDISequenceChannel::new(1),
|
|
||||||
MIDISequenceChannel::new(2),
|
|
||||||
MIDISequenceChannel::new(3),
|
|
||||||
MIDISequenceChannel::new(4),
|
|
||||||
MIDISequenceChannel::new(5),
|
|
||||||
MIDISequenceChannel::new(6),
|
|
||||||
MIDISequenceChannel::new(7),
|
|
||||||
MIDISequenceChannel::new(8),
|
|
||||||
MIDISequenceChannel::new(9),
|
|
||||||
MIDISequenceChannel::new(10),
|
|
||||||
MIDISequenceChannel::new(11),
|
|
||||||
MIDISequenceChannel::new(12),
|
|
||||||
MIDISequenceChannel::new(13),
|
|
||||||
MIDISequenceChannel::new(14),
|
|
||||||
MIDISequenceChannel::new(15),
|
|
||||||
MIDISequenceChannel::new(16),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MIDISequenceChannel {
|
|
||||||
fn new (number: u8) -> Self {
|
|
||||||
Self {
|
|
||||||
number,
|
|
||||||
notes: [VOICE_EMPTY;128]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub enum SequencerEvent {
|
|
||||||
NoteOn(u8, u8),
|
|
||||||
NoteOff(u8)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
#[test]
|
|
||||||
fn test_midi_frames () {
|
|
||||||
let beats = 4;
|
|
||||||
let steps = 16;
|
|
||||||
let bpm = 120;
|
|
||||||
let rate = 44100; // Hz
|
|
||||||
let frame = 1f64 / rate as f64; // msec
|
|
||||||
let buf = 512; // frames
|
|
||||||
let t_beat = 60.0 / bpm as f64; // msec
|
|
||||||
let t_loop = t_beat * beats as f64; // msec
|
|
||||||
let t_step = t_beat / steps as f64; // msec
|
|
||||||
|
|
||||||
let assign = |chunk: usize| {
|
|
||||||
let start = chunk * buf; // frames
|
|
||||||
let end = (chunk + 1) * buf; // frames
|
|
||||||
println!("{chunk}: {start} .. {end}");
|
|
||||||
let mut steps: Vec<(usize, usize, f64)> = vec![];
|
|
||||||
for frame_index in start..end {
|
|
||||||
let frame_msec = frame_index as f64 * frame;
|
|
||||||
let offset = (frame_msec * 1000.0) % (t_step * 1000.0);
|
|
||||||
if offset < 0.1 {
|
|
||||||
let time = frame_index - start;
|
|
||||||
let step_index = (frame_msec % t_loop / t_step) as usize;
|
|
||||||
println!("{chunk}: {frame_index} ({time}) -> {step_index} ({frame_msec} % {t_step} = {offset})");
|
|
||||||
steps.push((time, step_index, offset));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
steps
|
|
||||||
};
|
|
||||||
|
|
||||||
for chunk in 0..10 {
|
|
||||||
let chunk = assign(chunk);
|
|
||||||
//println!("{chunk} {:#?}", assign(chunk));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_midi_frames_2 () {
|
|
||||||
let beats = 4;
|
|
||||||
let steps = 16;
|
|
||||||
let bpm = 120;
|
|
||||||
let rate = 44100; // Hz
|
|
||||||
let frame = 1f64 / rate as f64; // msec
|
|
||||||
let buf = 512; // frames
|
|
||||||
let t_beat = 60.0 / bpm as f64; // msec
|
|
||||||
let t_loop = t_beat * beats as f64; // msec
|
|
||||||
let t_step = t_beat / steps as f64; // msec
|
|
||||||
let mut step_frames = vec![];
|
|
||||||
for step in 0..beats*steps {
|
|
||||||
let step_index = (step as f64 * t_step / frame) as usize;
|
|
||||||
step_frames.push(step_index);
|
|
||||||
}
|
|
||||||
let loop_frames = (t_loop*rate as f64) as usize;
|
|
||||||
let mut frame_steps: Vec<Option<usize>> = vec![None;loop_frames];
|
|
||||||
for (index, frame) in step_frames.iter().enumerate() {
|
|
||||||
println!("{index} {frame}");
|
|
||||||
frame_steps[*frame] = Some(index);
|
|
||||||
}
|
|
||||||
let assign = |chunk: usize| {
|
|
||||||
let (start, end) = (chunk * buf, (chunk + 1) * buf); // frames
|
|
||||||
let (start_looped, end_looped) = (start % loop_frames, end % loop_frames);
|
|
||||||
println!("{chunk}: {start} .. {end} ({start_looped} .. {end_looped})");
|
|
||||||
let mut steps: Vec<Option<usize>> = vec![None;buf];
|
|
||||||
for frame in 0..buf {
|
|
||||||
let value = frame_steps[(start_looped + frame) as usize % loop_frames];
|
|
||||||
if value.is_some() { println!("{frame:03} = {value:?}, ") };
|
|
||||||
steps[frame as usize] = value;
|
|
||||||
}
|
|
||||||
steps
|
|
||||||
};
|
|
||||||
for chunk in 0..1000 {
|
|
||||||
let chunk = assign(chunk);
|
|
||||||
//println!("{chunk} {:#?}", assign(chunk));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
77
src/device/sequencer_test.rs
Normal file
77
src/device/sequencer_test.rs
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
#![cfg(test)]
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_midi_frames () {
|
||||||
|
let beats = 4;
|
||||||
|
let steps = 16;
|
||||||
|
let bpm = 120;
|
||||||
|
let rate = 44100; // Hz
|
||||||
|
let frame = 1f64 / rate as f64; // msec
|
||||||
|
let buf = 512; // frames
|
||||||
|
let t_beat = 60.0 / bpm as f64; // msec
|
||||||
|
let t_loop = t_beat * beats as f64; // msec
|
||||||
|
let t_step = t_beat / steps as f64; // msec
|
||||||
|
|
||||||
|
let assign = |chunk: usize| {
|
||||||
|
let start = chunk * buf; // frames
|
||||||
|
let end = (chunk + 1) * buf; // frames
|
||||||
|
println!("{chunk}: {start} .. {end}");
|
||||||
|
let mut steps: Vec<(usize, usize, f64)> = vec![];
|
||||||
|
for frame_index in start..end {
|
||||||
|
let frame_msec = frame_index as f64 * frame;
|
||||||
|
let offset = (frame_msec * 1000.0) % (t_step * 1000.0);
|
||||||
|
if offset < 0.1 {
|
||||||
|
let time = frame_index - start;
|
||||||
|
let step_index = (frame_msec % t_loop / t_step) as usize;
|
||||||
|
println!("{chunk}: {frame_index} ({time}) -> {step_index} ({frame_msec} % {t_step} = {offset})");
|
||||||
|
steps.push((time, step_index, offset));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
steps
|
||||||
|
};
|
||||||
|
|
||||||
|
for chunk in 0..10 {
|
||||||
|
let chunk = assign(chunk);
|
||||||
|
//println!("{chunk} {:#?}", assign(chunk));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_midi_frames_2 () {
|
||||||
|
let beats = 4;
|
||||||
|
let steps = 16;
|
||||||
|
let bpm = 120;
|
||||||
|
let rate = 44100; // Hz
|
||||||
|
let frame = 1f64 / rate as f64; // msec
|
||||||
|
let buf = 512; // frames
|
||||||
|
let t_beat = 60.0 / bpm as f64; // msec
|
||||||
|
let t_loop = t_beat * beats as f64; // msec
|
||||||
|
let t_step = t_beat / steps as f64; // msec
|
||||||
|
let mut step_frames = vec![];
|
||||||
|
for step in 0..beats*steps {
|
||||||
|
let step_index = (step as f64 * t_step / frame) as usize;
|
||||||
|
step_frames.push(step_index);
|
||||||
|
}
|
||||||
|
let loop_frames = (t_loop*rate as f64) as usize;
|
||||||
|
let mut frame_steps: Vec<Option<usize>> = vec![None;loop_frames];
|
||||||
|
for (index, frame) in step_frames.iter().enumerate() {
|
||||||
|
println!("{index} {frame}");
|
||||||
|
frame_steps[*frame] = Some(index);
|
||||||
|
}
|
||||||
|
let assign = |chunk: usize| {
|
||||||
|
let (start, end) = (chunk * buf, (chunk + 1) * buf); // frames
|
||||||
|
let (start_looped, end_looped) = (start % loop_frames, end % loop_frames);
|
||||||
|
println!("{chunk}: {start} .. {end} ({start_looped} .. {end_looped})");
|
||||||
|
let mut steps: Vec<Option<usize>> = vec![None;buf];
|
||||||
|
for frame in 0..buf {
|
||||||
|
let value = frame_steps[(start_looped + frame) as usize % loop_frames];
|
||||||
|
if value.is_some() { println!("{frame:03} = {value:?}, ") };
|
||||||
|
steps[frame as usize] = value;
|
||||||
|
}
|
||||||
|
steps
|
||||||
|
};
|
||||||
|
for chunk in 0..1000 {
|
||||||
|
let chunk = assign(chunk);
|
||||||
|
//println!("{chunk} {:#?}", assign(chunk));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,21 +2,19 @@ use crate::prelude::*;
|
||||||
|
|
||||||
pub struct Transport {
|
pub struct Transport {
|
||||||
name: String,
|
name: String,
|
||||||
transport: Option<::jack::Transport>,
|
transport: ::jack::Transport,
|
||||||
bpm: f64,
|
|
||||||
timesig: (f32, f32),
|
timesig: (f32, f32),
|
||||||
|
bpm: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Transport {
|
impl Transport {
|
||||||
pub fn new ()
|
pub fn new (name: &str) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
|
||||||
-> Result<DynamicDevice<Self>, Box<dyn Error>>
|
let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?;
|
||||||
{
|
Ok(DynamicDevice::new(render, handle, process, Self {
|
||||||
//let transport = client.transport();
|
name: name.into(),
|
||||||
Ok(DynamicDevice::new(render, handle, |_|{}, Self {
|
transport: client.transport(),
|
||||||
name: "Transport".into(),
|
|
||||||
bpm: 113.0,
|
|
||||||
timesig: (4.0, 4.0),
|
timesig: (4.0, 4.0),
|
||||||
transport: None,
|
bpm: 113.0,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -24,7 +22,7 @@ impl Transport {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn play_or_pause (&mut self) -> Result<(), Box<dyn Error>> {
|
pub fn play_or_pause (&mut self) -> Result<(), Box<dyn Error>> {
|
||||||
match self.transport.as_ref().unwrap().query_state()? {
|
match self.transport.query_state()? {
|
||||||
TransportState::Stopped => self.play(),
|
TransportState::Stopped => self.play(),
|
||||||
TransportState::Rolling => self.stop(),
|
TransportState::Rolling => self.stop(),
|
||||||
_ => Ok(())
|
_ => Ok(())
|
||||||
|
|
@ -32,14 +30,18 @@ impl Transport {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn play (&mut self) -> Result<(), Box<dyn Error>> {
|
pub fn play (&mut self) -> Result<(), Box<dyn Error>> {
|
||||||
Ok(self.transport.as_ref().unwrap().start()?)
|
Ok(self.transport.start()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stop (&mut self) -> Result<(), Box<dyn Error>> {
|
pub fn stop (&mut self) -> Result<(), Box<dyn Error>> {
|
||||||
Ok(self.transport.as_ref().unwrap().stop()?)
|
Ok(self.transport.stop()?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn process (state: &mut Transport, client: &Client, scope: &ProcessScope) -> Control {
|
||||||
|
Control::Continue
|
||||||
|
}
|
||||||
|
|
||||||
pub fn render (state: &Transport, buf: &mut Buffer, mut area: Rect)
|
pub fn render (state: &Transport, buf: &mut Buffer, mut area: Rect)
|
||||||
-> Usually<Rect>
|
-> Usually<Rect>
|
||||||
{
|
{
|
||||||
|
|
@ -220,7 +222,7 @@ pub fn render (state: &Transport, buf: &mut Buffer, mut area: Rect)
|
||||||
//Ok(())
|
//Ok(())
|
||||||
//}
|
//}
|
||||||
|
|
||||||
pub fn handle (state: &mut Transport, event: &EngineEvent) -> Result<(), Box<dyn Error>> {
|
pub fn handle (state: &mut Transport, event: &AppEvent) -> Result<(), Box<dyn Error>> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -231,9 +233,9 @@ pub const ACTIONS: [(&'static str, &'static str);4] = [
|
||||||
("(Shift-)Space", "⯈ Play/pause"),
|
("(Shift-)Space", "⯈ Play/pause"),
|
||||||
];
|
];
|
||||||
|
|
||||||
struct Notifications;
|
struct TransportJack;
|
||||||
|
|
||||||
impl NotificationHandler for Notifications {
|
impl NotificationHandler for TransportJack {
|
||||||
fn thread_init (&self, _: &Client) {
|
fn thread_init (&self, _: &Client) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,19 +13,19 @@ impl Rows {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Device for Rows {
|
impl Device for Rows {
|
||||||
fn handle (&mut self, event: &EngineEvent) -> Usually<()> {
|
fn handle (&mut self, event: &AppEvent) -> Usually<()> {
|
||||||
if !self.focused {
|
if !self.focused {
|
||||||
match event {
|
match event {
|
||||||
EngineEvent::Input(Event::Key(KeyEvent { code: KeyCode::Esc, .. })) => {
|
AppEvent::Input(Event::Key(KeyEvent { code: KeyCode::Esc, .. })) => {
|
||||||
self.focused = true;
|
self.focused = true;
|
||||||
self.items[self.focus].handle(&EngineEvent::Blur)?;
|
self.items[self.focus].handle(&AppEvent::Blur)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
_ => self.items[self.focus].handle(event)
|
_ => self.items[self.focus].handle(event)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match event {
|
match event {
|
||||||
EngineEvent::Input(event) => match event {
|
AppEvent::Input(event) => match event {
|
||||||
Event::Key(KeyEvent { code: KeyCode::Up, .. }) => {
|
Event::Key(KeyEvent { code: KeyCode::Up, .. }) => {
|
||||||
if self.focus == 0 {
|
if self.focus == 0 {
|
||||||
self.focus = self.items.len();
|
self.focus = self.items.len();
|
||||||
|
|
@ -40,11 +40,11 @@ impl Device for Rows {
|
||||||
},
|
},
|
||||||
Event::Key(KeyEvent { code: KeyCode::Enter, .. }) => {
|
Event::Key(KeyEvent { code: KeyCode::Enter, .. }) => {
|
||||||
self.focused = false;
|
self.focused = false;
|
||||||
self.items[self.focus].handle(&EngineEvent::Focus)?;
|
self.items[self.focus].handle(&AppEvent::Focus)?;
|
||||||
},
|
},
|
||||||
Event::Key(KeyEvent { code: KeyCode::Esc, .. }) => {
|
Event::Key(KeyEvent { code: KeyCode::Esc, .. }) => {
|
||||||
self.focused = true;
|
self.focused = true;
|
||||||
self.items[self.focus].handle(&EngineEvent::Blur)?;
|
self.items[self.focus].handle(&AppEvent::Blur)?;
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
println!("{event:?}");
|
println!("{event:?}");
|
||||||
|
|
@ -93,7 +93,7 @@ impl Columns {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Device for Columns {
|
impl Device for Columns {
|
||||||
fn handle (&mut self, event: &EngineEvent) -> Usually<()> {
|
fn handle (&mut self, event: &AppEvent) -> Usually<()> {
|
||||||
if self.focused {
|
if self.focused {
|
||||||
self.items[self.focus].handle(event)
|
self.items[self.focus].handle(event)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,13 @@ use std::error::Error;
|
||||||
pub mod cli;
|
pub mod cli;
|
||||||
pub mod device;
|
pub mod device;
|
||||||
pub mod prelude;
|
pub mod prelude;
|
||||||
pub mod render;
|
pub mod draw;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
|
pub mod time;
|
||||||
|
|
||||||
use crate::device::{Chain, Sequencer, Sampler, Plugin, Mixer, Transport};
|
use crate::device::{Sequencer, Transport};
|
||||||
use crate::layout::{Rows, Columns};
|
use crate::layout::Rows;
|
||||||
|
|
||||||
fn main () -> Result<(), Box<dyn Error>> {
|
fn main () -> Result<(), Box<dyn Error>> {
|
||||||
let cli = cli::Cli::parse();
|
let cli = cli::Cli::parse();
|
||||||
|
|
@ -21,7 +22,7 @@ fn main () -> Result<(), Box<dyn Error>> {
|
||||||
crate::config::create_dirs(&xdg)?;
|
crate::config::create_dirs(&xdg)?;
|
||||||
crate::device::run(Rows::new(true, vec![
|
crate::device::run(Rows::new(true, vec![
|
||||||
Box::new(Sequencer::new("Rhythm#000")?),
|
Box::new(Sequencer::new("Rhythm#000")?),
|
||||||
Box::new(Transport::new()?),
|
Box::new(Transport::new("Transport0")?),
|
||||||
]))
|
]))
|
||||||
//crate::device::run(Rows::new(true, vec![
|
//crate::device::run(Rows::new(true, vec![
|
||||||
//Box::new(Columns::new(false, vec![
|
//Box::new(Columns::new(false, vec![
|
||||||
|
|
|
||||||
26
src/note.rs
Normal file
26
src/note.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
pub const C_4: u8 = 0;
|
||||||
|
pub const C_3: u8 = 12;
|
||||||
|
pub const C_2: u8 = 24;
|
||||||
|
pub const C_1: u8 = 36;
|
||||||
|
pub const C0: u8 = 48;
|
||||||
|
pub const C1: u8 = 60;
|
||||||
|
pub const C2: u8 = 72;
|
||||||
|
pub const C3: u8 = 84;
|
||||||
|
pub const C4: u8 = 96;
|
||||||
|
pub const C5: u8 = 108;
|
||||||
|
pub const C6: u8 = 120;
|
||||||
|
|
||||||
|
pub enum Pitch {
|
||||||
|
C = 0,
|
||||||
|
Cs,
|
||||||
|
D,
|
||||||
|
Ds,
|
||||||
|
E,
|
||||||
|
F,
|
||||||
|
Fs,
|
||||||
|
G,
|
||||||
|
Gs,
|
||||||
|
A,
|
||||||
|
As,
|
||||||
|
B
|
||||||
|
}
|
||||||
|
|
@ -1,20 +1,42 @@
|
||||||
|
pub type Usually<T> = Result<T, Box<dyn Error>>;
|
||||||
|
|
||||||
|
pub use crate::draw::*;
|
||||||
|
|
||||||
|
pub use crate::device::{
|
||||||
|
Device,
|
||||||
|
DynamicDevice,
|
||||||
|
AppEvent,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub use crate::time::*;
|
||||||
|
|
||||||
|
pub use crate::layout::{
|
||||||
|
Rows,
|
||||||
|
Columns
|
||||||
|
};
|
||||||
|
|
||||||
pub use std::error::Error;
|
pub use std::error::Error;
|
||||||
|
|
||||||
pub use std::io::{
|
pub use std::io::{
|
||||||
stdout,
|
stdout,
|
||||||
Stdout,
|
Stdout,
|
||||||
Write
|
Write
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use std::thread::{
|
pub use std::thread::{
|
||||||
spawn,
|
spawn,
|
||||||
JoinHandle
|
JoinHandle
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use std::time::Duration;
|
pub use std::time::Duration;
|
||||||
|
|
||||||
pub use std::sync::{
|
pub use std::sync::{
|
||||||
Arc,
|
Arc,
|
||||||
Mutex,
|
Mutex,
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
mpsc::{self, channel, Sender, Receiver}
|
mpsc::{self, channel, Sender, Receiver}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use crossterm::{
|
pub use crossterm::{
|
||||||
ExecutableCommand, QueueableCommand,
|
ExecutableCommand, QueueableCommand,
|
||||||
event::{Event, KeyEvent, KeyCode, KeyModifiers},
|
event::{Event, KeyEvent, KeyCode, KeyModifiers},
|
||||||
|
|
@ -25,11 +47,16 @@ pub use crossterm::{
|
||||||
enable_raw_mode, disable_raw_mode
|
enable_raw_mode, disable_raw_mode
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use ratatui::{
|
pub use ratatui::{
|
||||||
prelude::*,
|
prelude::{
|
||||||
widgets::WidgetRef,
|
Buffer, Rect, Style, Color, CrosstermBackend, Layout, Stylize, Direction,
|
||||||
|
Line, Constraint
|
||||||
|
},
|
||||||
|
widgets::{Widget, WidgetRef},
|
||||||
//style::Stylize,
|
//style::Stylize,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use jack::{
|
pub use jack::{
|
||||||
AsyncClient,
|
AsyncClient,
|
||||||
AudioIn,
|
AudioIn,
|
||||||
|
|
@ -51,12 +78,7 @@ pub use jack::{
|
||||||
TransportState,
|
TransportState,
|
||||||
TransportStatePosition
|
TransportStatePosition
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type BoxedProcessHandler = Box<dyn FnMut(&Client, &ProcessScope)-> Control + Send>;
|
pub type BoxedProcessHandler = Box<dyn FnMut(&Client, &ProcessScope)-> Control + Send>;
|
||||||
|
|
||||||
pub type Jack<N> = AsyncClient<N, ClosureProcessHandler<BoxedProcessHandler>>;
|
pub type Jack<N> = AsyncClient<N, ClosureProcessHandler<BoxedProcessHandler>>;
|
||||||
pub use crate::render::*;
|
|
||||||
pub use crate::device::{
|
|
||||||
Device,
|
|
||||||
DynamicDevice,
|
|
||||||
EngineEvent,
|
|
||||||
};
|
|
||||||
pub type Usually<T> = Result<T, Box<dyn Error>>;
|
|
||||||
|
|
|
||||||
90
src/time.rs
Normal file
90
src/time.rs
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
/// Number of data frames in a second.
|
||||||
|
pub struct Hz(pub u32);
|
||||||
|
|
||||||
|
/// One data frame.
|
||||||
|
pub struct Frame(pub u32);
|
||||||
|
|
||||||
|
/// One microsecond.
|
||||||
|
pub struct Usec(pub u32);
|
||||||
|
|
||||||
|
/// Beats per minute as `120000` = 120.000BPM
|
||||||
|
pub struct Tempo(pub u32);
|
||||||
|
|
||||||
|
/// Time signature: N/Mths per bar.
|
||||||
|
pub struct Signature(pub u32, pub u32);
|
||||||
|
|
||||||
|
/// NoteDuration in musical terms. Has definite usec value
|
||||||
|
/// for given bpm and sample rate.
|
||||||
|
pub enum NoteDuration {
|
||||||
|
Nth(u16, u16),
|
||||||
|
Dotted(Box<Self>),
|
||||||
|
Tuplet(u16, Box<Self>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Frame {
|
||||||
|
#[inline]
|
||||||
|
pub fn to_usec (&self, rate: &Hz) -> Usec {
|
||||||
|
Usec((self.0 * 1000) / rate.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Usec {
|
||||||
|
#[inline]
|
||||||
|
pub fn to_frame (&self, rate: &Hz) -> Frame {
|
||||||
|
Frame((self.0 * rate.0) / 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tempo {
|
||||||
|
#[inline]
|
||||||
|
pub fn to_usec_per_beat (&self) -> Usec {
|
||||||
|
Usec((60_000_000_000 / self.0 as usize) as u32)
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub fn to_usec_per_quarter (&self) -> Usec {
|
||||||
|
Usec(self.to_usec_per_quarter().0 / 4)
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub fn to_usec_per_tick (&self, ppq: u32) -> Usec {
|
||||||
|
Usec(self.to_usec_per_quarter().0 / ppq)
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub fn quantize (&self, step: &NoteDuration, time: Usec) -> Usec {
|
||||||
|
let step = step.to_usec(self);
|
||||||
|
let t = time.0 / step.0;
|
||||||
|
Usec(step.0 * t)
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub fn quantize_into <T, U> (&self, step: &NoteDuration, events: U)
|
||||||
|
-> Vec<(Usec, T)>
|
||||||
|
where U: std::iter::Iterator<Item=(Usec, T)> + Sized
|
||||||
|
{
|
||||||
|
events
|
||||||
|
.map(|(time, event)|(self.quantize(step, time), event))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NoteDuration {
|
||||||
|
fn to_usec (&self, bpm: &Tempo) -> Usec {
|
||||||
|
Usec(match self {
|
||||||
|
Self::Nth(time, flies) =>
|
||||||
|
bpm.to_usec_per_beat().0 * *time as u32 / *flies as u32,
|
||||||
|
Self::Dotted(duration) =>
|
||||||
|
duration.to_usec(bpm).0 * 3 / 2,
|
||||||
|
Self::Tuplet(n, duration) =>
|
||||||
|
duration.to_usec(bpm).0 * 2 / *n as u32,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn to_frame (&self, bpm: &Tempo, rate: &Hz) -> Frame {
|
||||||
|
self.to_usec(bpm).to_frame(rate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Loop<T> {
|
||||||
|
repeat: bool,
|
||||||
|
pre_start: T,
|
||||||
|
start: T,
|
||||||
|
end: T,
|
||||||
|
post_end: T,
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue