mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
dynamic device activation
This commit is contained in:
parent
b73aa8a0dc
commit
e4f3942757
9 changed files with 484 additions and 587 deletions
55
Cargo.lock
generated
55
Cargo.lock
generated
|
|
@ -137,6 +137,7 @@ dependencies = [
|
|||
"crossterm",
|
||||
"jack",
|
||||
"microxdg",
|
||||
"midly",
|
||||
"ratatui",
|
||||
"toml",
|
||||
]
|
||||
|
|
@ -239,6 +240,31 @@ dependencies = [
|
|||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
|
||||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
version = "0.27.0"
|
||||
|
|
@ -420,6 +446,15 @@ version = "0.1.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdcd3dc4ca0d1a9b1b80576639e93911a3e1db25a31ab6e6ba33027429adde5e"
|
||||
|
||||
[[package]]
|
||||
name = "midly"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "207d755f4cb882d20c4da58d707ca9130a0c9bc5061f657a4f299b8e36362b7a"
|
||||
dependencies = [
|
||||
"rayon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.3"
|
||||
|
|
@ -529,6 +564,26 @@ dependencies = [
|
|||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.1"
|
||||
|
|
|
|||
|
|
@ -11,3 +11,4 @@ backtrace = "0.3.72"
|
|||
microxdg = "0.1.2"
|
||||
toml = "0.8.12"
|
||||
better-panic = "0.3.0"
|
||||
midly = "0.5"
|
||||
|
|
|
|||
165
src/device.rs
165
src/device.rs
|
|
@ -102,6 +102,7 @@ pub struct DynamicDevice<T> {
|
|||
pub render: Mutex<Box<dyn FnMut(&T, &mut Buffer, Rect)->Usually<Rect> + Send>>,
|
||||
pub handle: Arc<Mutex<Box<dyn FnMut(&mut T, &AppEvent)->Usually<()> + Send>>>,
|
||||
pub process: Arc<Mutex<Box<dyn FnMut(&mut T, &Client, &ProcessScope)->Control + Send>>>,
|
||||
client: Option<DynamicAsyncClient>
|
||||
}
|
||||
|
||||
impl<T: Send + Sync> Device for DynamicDevice<T> {
|
||||
|
|
@ -113,7 +114,11 @@ impl<T: Send + Sync> Device for DynamicDevice<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> DynamicDevice<T> {
|
||||
type DynamicAsyncClient = AsyncClient<DynamicNotifications, DynamicProcessHandler>;
|
||||
type DynamicNotifications = Notifications<Box<dyn Fn(AppEvent) + Send + Sync>>;
|
||||
type DynamicProcessHandler = ClosureProcessHandler<BoxedProcessHandler>;
|
||||
|
||||
impl<T: Send + Sync + 'static> DynamicDevice<T> {
|
||||
fn new <'a, R, H, P> (render: R, handle: H, process: P, state: T) -> Self where
|
||||
R: FnMut(&T, &mut Buffer, Rect)->Usually<Rect> + Send + 'static,
|
||||
H: FnMut(&mut T, &AppEvent) -> Result<(), Box<dyn Error>> + Send + 'static,
|
||||
|
|
@ -124,16 +129,14 @@ impl<T> DynamicDevice<T> {
|
|||
render: Mutex::new(Box::new(render)),
|
||||
handle: Arc::new(Mutex::new(Box::new(handle))),
|
||||
process: Arc::new(Mutex::new(Box::new(process))),
|
||||
client: None,
|
||||
}
|
||||
}
|
||||
fn state (&self) -> std::sync::MutexGuard<'_, T> {
|
||||
self.state.lock().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: NotificationHandler + 'static> DynamicDevice<T> {
|
||||
fn activate (&self, client: Client) -> Usually<()> {
|
||||
let notifications = {
|
||||
fn activate (mut self, client: Client) -> Usually<Self> {
|
||||
self.client = Some(client.activate_async(Notifications(Box::new({
|
||||
let state = self.state.clone();
|
||||
let handle = self.handle.clone();
|
||||
move|event|{
|
||||
|
|
@ -141,9 +144,7 @@ impl<T: NotificationHandler + 'static> DynamicDevice<T> {
|
|||
let mut handle = handle.lock().unwrap();
|
||||
handle(&mut state, &event).unwrap()
|
||||
}
|
||||
};
|
||||
let notifications = Notifications(Box::new(notifications));
|
||||
let handler = ClosureProcessHandler::new(Box::new({
|
||||
}) as Box<dyn Fn(AppEvent) + Send + Sync>), ClosureProcessHandler::new(Box::new({
|
||||
let state = self.state.clone();
|
||||
let process = self.process.clone();
|
||||
move|client: &Client, scope: &ProcessScope|{
|
||||
|
|
@ -151,9 +152,8 @@ impl<T: NotificationHandler + 'static> DynamicDevice<T> {
|
|||
let mut process = process.lock().unwrap();
|
||||
(process)(&mut state, client, scope)
|
||||
}
|
||||
}) as BoxedProcessHandler);
|
||||
client.activate_async(notifications, handler)?;
|
||||
Ok(())
|
||||
}) as BoxedProcessHandler))?);
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -185,14 +185,6 @@ pub enum AppEvent {
|
|||
Jack(JackEvent)
|
||||
}
|
||||
|
||||
pub fn activate_jack_client <N: NotificationHandler + Sync + 'static> (
|
||||
client: Client,
|
||||
notifications: N,
|
||||
handler: BoxedProcessHandler
|
||||
) -> Result<Jack<N>, Box<dyn Error>> {
|
||||
Ok(client.activate_async(notifications, ClosureProcessHandler::new(handler))?)
|
||||
}
|
||||
|
||||
fn panic_hook (info: &std::panic::PanicInfo) {
|
||||
stdout()
|
||||
.execute(crossterm::terminal::LeaveAlternateScreen)
|
||||
|
|
@ -223,14 +215,17 @@ pub struct Notifications<T: Fn(AppEvent) + Send>(T);
|
|||
|
||||
impl<T: Fn(AppEvent) + Send> NotificationHandler for Notifications<T> {
|
||||
fn thread_init (&self, _: &Client) {
|
||||
println!("JACK: thread init");
|
||||
self.0(AppEvent::Jack(JackEvent::ThreadInit));
|
||||
}
|
||||
|
||||
fn shutdown (&mut self, status: ClientStatus, reason: &str) {
|
||||
println!("JACK: shutdown with status {:?} because \"{}\"", status, reason);
|
||||
self.0(AppEvent::Jack(JackEvent::Shutdown));
|
||||
}
|
||||
|
||||
fn freewheel (&mut self, _: &Client, is_enabled: bool) {
|
||||
println!("JACK: freewheel mode is {}", if is_enabled { "on" } else { "off" });
|
||||
self.0(AppEvent::Jack(JackEvent::Freewheel));
|
||||
}
|
||||
|
||||
|
|
@ -240,145 +235,41 @@ impl<T: Fn(AppEvent) + Send> NotificationHandler for Notifications<T> {
|
|||
}
|
||||
|
||||
fn client_registration (&mut self, _: &Client, name: &str, is_reg: bool) {
|
||||
println!("JACK: {} client with name \"{name}\"",
|
||||
if is_reg { "registered" } else { "unregistered" });
|
||||
self.0(AppEvent::Jack(JackEvent::ClientRegistration));
|
||||
}
|
||||
|
||||
fn port_registration (&mut self, _: &Client, port_id: PortId, is_reg: bool) {
|
||||
println!("JACK: {} port with id {port_id}",
|
||||
if is_reg { "registered" } else { "unregistered" });
|
||||
self.0(AppEvent::Jack(JackEvent::PortRegistration));
|
||||
}
|
||||
|
||||
fn port_rename (&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control {
|
||||
println!("JACK: port with id {id} renamed from {old} to {new}",);
|
||||
self.0(AppEvent::Jack(JackEvent::PortRename));
|
||||
Control::Continue
|
||||
}
|
||||
|
||||
fn ports_connected (&mut self, _: &Client, id_a: PortId, id_b: PortId, are: bool) {
|
||||
fn ports_connected (&mut self, _: &Client, a: PortId, b: PortId, are: bool) {
|
||||
println!("JACK: ports with id {a} and {b} are {}", if are {
|
||||
"connected"
|
||||
} else {
|
||||
"disconnected"
|
||||
});
|
||||
self.0(AppEvent::Jack(JackEvent::PortsConnected));
|
||||
}
|
||||
|
||||
fn graph_reorder (&mut self, _: &Client) -> Control {
|
||||
println!("JACK: graph reordered");
|
||||
self.0(AppEvent::Jack(JackEvent::GraphReorder));
|
||||
Control::Continue
|
||||
}
|
||||
|
||||
fn xrun (&mut self, _: &Client) -> Control {
|
||||
println!("JACK: xrun occurred");
|
||||
self.0(AppEvent::Jack(JackEvent::XRun));
|
||||
Control::Continue
|
||||
}
|
||||
}
|
||||
|
||||
//pub struct Jack {
|
||||
//client: Option<AsyncClient<
|
||||
//Notifications,
|
||||
//ClosureProcessHandler<Box<dyn FnMut(&Client, &ProcessScope)->Control + Send>>
|
||||
//>>,
|
||||
//pub transport: Option<Transport>,
|
||||
//audio_ins: BTreeMap<String, Port<AudioIn>>,
|
||||
//audio_outs: BTreeMap<String, Port<AudioOut>>,
|
||||
//midi_ins: BTreeMap<String, Port<MidiIn>>,
|
||||
//midi_outs: BTreeMap<String, Port<MidiOut>>,
|
||||
//}
|
||||
|
||||
//impl Jack {
|
||||
//pub fn init_from_cli (options: &Cli)
|
||||
//-> Result<Arc<Mutex<Self>>, Box<dyn Error>>
|
||||
//{
|
||||
//let jack = Self::init(&options.jack_client_name)?;
|
||||
//{
|
||||
//let jack = jack.clone();
|
||||
//let mut jack = jack.lock().unwrap();
|
||||
//for port in options.jack_audio_ins.iter() {
|
||||
//jack.add_audio_in(port)?;
|
||||
//}
|
||||
//for port in options.jack_audio_outs.iter() {
|
||||
//jack.add_audio_out(port)?;
|
||||
//}
|
||||
//for port in options.jack_midi_ins.iter() {
|
||||
//jack.add_midi_in(port)?;
|
||||
//}
|
||||
//for port in options.jack_midi_outs.iter() {
|
||||
//jack.add_midi_out(port)?;
|
||||
//}
|
||||
//}
|
||||
//Ok(jack.clone())
|
||||
//}
|
||||
//fn init (name: &str)
|
||||
//-> Result<Arc<Mutex<Self>>, Box<dyn Error>>
|
||||
//{
|
||||
//let jack = Arc::new(Mutex::new(Self {
|
||||
//client: None,
|
||||
//transport: None,
|
||||
//audio_ins: BTreeMap::new(),
|
||||
//audio_outs: BTreeMap::new(),
|
||||
//midi_ins: BTreeMap::new(),
|
||||
//midi_outs: BTreeMap::new(),
|
||||
//}));
|
||||
//let (client, status) = Client::new(name, ClientOptions::NO_START_SERVER)?;
|
||||
//println!("Client status: {status:?}");
|
||||
//let jack1 = jack.clone();
|
||||
//let mut jack1 = jack1.lock().unwrap();
|
||||
//let jack2 = jack.clone();
|
||||
//jack1.transport = Some(client.transport());
|
||||
//jack1.client = Some(client.activate_async(
|
||||
//Notifications, ClosureProcessHandler::new(Box::new(
|
||||
//move |_client: &Client, _ps: &ProcessScope| -> Control {
|
||||
//let jack = jack2.lock().expect("Failed to lock jack mutex");
|
||||
//jack.read_inputs();
|
||||
//jack.write_outputs();
|
||||
//Control::Continue
|
||||
//}
|
||||
//) as Box<dyn FnMut(&Client, &ProcessScope)->Control + Send>)
|
||||
//)?);
|
||||
//Ok(jack)
|
||||
//}
|
||||
//fn start (&self) {
|
||||
//}
|
||||
//fn process (&self, _: &Client, ps: &ProcessScope) -> Control {
|
||||
//Control::Continue
|
||||
//}
|
||||
//fn add_audio_in (&mut self, name: &str) -> Result<&mut Self, Box<dyn Error>> {
|
||||
//let port = self.client
|
||||
//.as_ref()
|
||||
//.expect("Not initialized.")
|
||||
//.as_client()
|
||||
//.register_port(name, AudioIn::default())?;
|
||||
//self.audio_ins.insert(name.into(), port);
|
||||
//Ok(self)
|
||||
//}
|
||||
//fn add_audio_out (&mut self, name: &str) -> Result<&mut Self, Box<dyn Error>> {
|
||||
//let port = self.client
|
||||
//.as_ref()
|
||||
//.expect("Not initialized.")
|
||||
//.as_client()
|
||||
//.register_port(name, AudioOut::default())?;
|
||||
//self.audio_outs.insert(name.into(), port);
|
||||
//Ok(self)
|
||||
//}
|
||||
//fn add_midi_in (&mut self, name: &str) -> Result<&mut Self, Box<dyn Error>> {
|
||||
//let port = self.client
|
||||
//.as_ref()
|
||||
//.expect("Not initialized.")
|
||||
//.as_client()
|
||||
//.register_port(name, MidiIn::default())?;
|
||||
//self.midi_ins.insert(name.into(), port);
|
||||
//Ok(self)
|
||||
//}
|
||||
//fn add_midi_out (&mut self, name: &str) -> Result<&mut Self, Box<dyn Error>> {
|
||||
//let port = self.client
|
||||
//.as_ref()
|
||||
//.expect("Not initialized.")
|
||||
//.as_client()
|
||||
//.register_port(name, MidiOut::default())?;
|
||||
//self.midi_outs.insert(name.into(), port);
|
||||
//Ok(self)
|
||||
//}
|
||||
//fn read_inputs (&self) {
|
||||
//// read input buffers
|
||||
////println!("read");
|
||||
//}
|
||||
//fn write_outputs (&self) {
|
||||
//// clear output buffers
|
||||
//// write output buffers
|
||||
////println!("write");
|
||||
//}
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -3,109 +3,98 @@ use ratatui::style::Stylize;
|
|||
|
||||
pub struct Sequencer {
|
||||
name: String,
|
||||
mode: SequencerMode,
|
||||
cursor: (u16, u16, u16),
|
||||
mode: SequencerView,
|
||||
note_axis: (u16, u16),
|
||||
note_cursor: u16,
|
||||
time_axis: (u16, u16),
|
||||
time_cursor: u16,
|
||||
rate: Hz,
|
||||
tempo: Tempo,
|
||||
sequences: Vec<Sequence>,
|
||||
input_port: ::jack::Port<::jack::MidiIn>,
|
||||
transport: ::jack::Transport,
|
||||
sequence: std::collections::BTreeMap<u32, Vec<Vec<u8>>>,
|
||||
ppq: u32,
|
||||
input_port: Port<MidiIn>,
|
||||
input_connect: Vec<String>,
|
||||
output_port: ::jack::Port<::jack::MidiOut>,
|
||||
output_port: Port<MidiOut>,
|
||||
output_connect: Vec<String>,
|
||||
playing: bool,
|
||||
recording: bool,
|
||||
overdub: bool,
|
||||
}
|
||||
|
||||
pub enum SequencerMode {
|
||||
enum SequencerView {
|
||||
Tiny,
|
||||
Compact,
|
||||
Vertical,
|
||||
Horizontal,
|
||||
Vertical,
|
||||
}
|
||||
|
||||
impl Sequencer {
|
||||
pub fn new (name: &str) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
|
||||
pub fn new (name: &str) -> Usually<DynamicDevice<Self>> {
|
||||
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())?;
|
||||
let device = DynamicDevice::new(render, handle, process, Self {
|
||||
DynamicDevice::new(
|
||||
render,
|
||||
handle,
|
||||
process,
|
||||
Self {
|
||||
name: name.into(),
|
||||
mode: SequencerMode::Vertical,
|
||||
cursor: (11, 0, 0),
|
||||
mode: SequencerView::Horizontal,
|
||||
note_axis: (36, 60),
|
||||
note_cursor: 0,
|
||||
time_axis: (0, 64),
|
||||
time_cursor: 0,
|
||||
rate: Hz(client.sample_rate() as u32),
|
||||
tempo: Tempo(120000),
|
||||
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())?,
|
||||
transport: client.transport(),
|
||||
input_port: client.register_port("in", MidiIn::default())?,
|
||||
input_connect: vec!["nanoKEY Studio * (capture): *".into()],
|
||||
output_port: client.register_port("out", MidiOut::default())?,
|
||||
output_connect: vec![],
|
||||
});
|
||||
device.activate(client)?;
|
||||
Ok(device)
|
||||
ppq: 96,
|
||||
sequence: std::collections::BTreeMap::new(),
|
||||
playing: true,
|
||||
recording: true,
|
||||
overdub: true,
|
||||
}
|
||||
).activate(client)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process (state: &mut Sequencer, client: &Client, scope: &ProcessScope) -> Control {
|
||||
for seq in state.sequences.iter() {
|
||||
seq.chunk_out(scope, &mut state.output_port);
|
||||
seq.chunk_in(scope, &state.input_port);
|
||||
}
|
||||
process_out(state, scope);
|
||||
process_in(state, scope);
|
||||
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,
|
||||
fn process_in (state: &Sequencer, scope: &ProcessScope) {
|
||||
for event in state.input_port.iter(scope) {
|
||||
match midly::live::LiveEvent::parse(event.bytes).unwrap() {
|
||||
midly::live::LiveEvent::Midi { channel, message } => match message {
|
||||
midly::MidiMessage::NoteOn { key, vel } => {
|
||||
panic!("on")
|
||||
},
|
||||
midly::MidiMessage::NoteOff { key, vel } => {
|
||||
panic!("off")
|
||||
},
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
fn process_out (state: &mut Sequencer, scope: &ProcessScope) {
|
||||
if state.playing {
|
||||
let size = scope.n_frames();
|
||||
let start = scope.last_frame_time();
|
||||
let end = start + size;
|
||||
let mut writer = port.writer(scope);
|
||||
let mut writer = state.output_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) {}
|
||||
|
|
@ -127,112 +116,152 @@ impl NotificationHandler for Sequencer {
|
|||
}
|
||||
}
|
||||
|
||||
pub const ACTIONS: [(&'static str, &'static str);4] = [
|
||||
("+/-", "Zoom"),
|
||||
("A/D", "Add/delete note"),
|
||||
("]/[", "Duration"),
|
||||
("CapsLock", "Auto advance"),
|
||||
pub fn handle (state: &mut Sequencer, event: &AppEvent) -> Result<(), Box<dyn Error>> {
|
||||
match event {
|
||||
AppEvent::Input(Event::Key(event)) => {
|
||||
for (code, _, _, command) in COMMANDS.iter() {
|
||||
if *code == event.code {
|
||||
command(state);
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const COMMANDS: [(KeyCode, &'static str, &'static str, &'static dyn Fn(&mut Sequencer));18] = [
|
||||
(KeyCode::Up, "cursor_up", "move cursor up", &cursor_up),
|
||||
(KeyCode::Down, "cursor_down", "move cursor down", &cursor_down),
|
||||
(KeyCode::Left, "cursor_left", "move cursor left", &cursor_left),
|
||||
(KeyCode::Right, "cursor_right", "move cursor right", &cursor_right),
|
||||
(KeyCode::Char(']'), "cursor_inc", "increase note duration", &cursor_inc),
|
||||
(KeyCode::Char('['), "cursor_dec", "decrease note duration", &cursor_dec),
|
||||
(KeyCode::Char('`'), "mode_next", "next view mode", &mode_next),
|
||||
(KeyCode::Tab, "mode_prev", "previous view mode", &mode_prev),
|
||||
(KeyCode::Char('+'), "zoom_in", "Zoom in", &nop),
|
||||
(KeyCode::Char('-'), "zoom_out", "Zoom out", &nop),
|
||||
(KeyCode::Char('a'), "note_add", "Add note", ¬e_add),
|
||||
(KeyCode::Char('d'), "note_del", "Delete note", ¬e_del),
|
||||
(KeyCode::CapsLock, "advance", "Toggle auto advance", &nop),
|
||||
(KeyCode::Char('w'), "rest", "Advance by note duration", &nop),
|
||||
(KeyCode::Char('r'), "record", "Toggle recodring", &toggle_record),
|
||||
(KeyCode::Char('o'), "overdub", "Toggle overdub", &toggle_overdub),
|
||||
(KeyCode::Char('p'), "play", "Toggle play/pause", &toggle_play),
|
||||
(KeyCode::Char('s'), "stop", "Stop and rewind", &nop),
|
||||
];
|
||||
|
||||
pub fn handle (state: &mut Sequencer, event: &AppEvent) -> Result<(), Box<dyn Error>> {
|
||||
|
||||
if let AppEvent::Focus = event {
|
||||
}
|
||||
|
||||
if let AppEvent::Input(Event::Key(event)) = event {
|
||||
match event.code {
|
||||
KeyCode::Down => {
|
||||
state.cursor.0 = if state.cursor.0 >= 23 {
|
||||
0
|
||||
fn nop (_: &mut Sequencer) {
|
||||
}
|
||||
fn note_add (s: &mut Sequencer) {
|
||||
let time = (s.time_axis.0 + s.time_cursor) as u32;
|
||||
let time_start = time * s.ppq;
|
||||
let time_end = (time + 1) * s.ppq;
|
||||
let note_on = vec![0, 0, 0];
|
||||
let note_off = vec![0, 0, 0];
|
||||
if s.sequence.contains_key(&time_start) {
|
||||
s.sequence.get_mut(&time_start).unwrap().push(note_off.clone());
|
||||
} else {
|
||||
state.cursor.0 + 1
|
||||
s.sequence.insert(time_start, vec![note_on]);
|
||||
}
|
||||
},
|
||||
KeyCode::Up => {
|
||||
state.cursor.0 = if state.cursor.0 == 0 {
|
||||
23
|
||||
if s.sequence.contains_key(&time_end) {
|
||||
s.sequence.get_mut(&time_end).unwrap().push(note_off.clone());
|
||||
} else {
|
||||
state.cursor.0 - 1
|
||||
s.sequence.insert(time_end, vec![note_off]);
|
||||
}
|
||||
},
|
||||
KeyCode::Left => {
|
||||
state.cursor.1 = if state.cursor.1 == 0 {
|
||||
63
|
||||
} else {
|
||||
state.cursor.1 - 1
|
||||
}
|
||||
fn note_del (_: &mut Sequencer) {
|
||||
}
|
||||
fn cursor_up (s: &mut Sequencer) {
|
||||
let time = s.time_axis.1 - s.time_axis.0;
|
||||
let note = s.note_axis.1 - s.note_axis.0;
|
||||
match s.mode {
|
||||
SequencerView::Vertical => { s.time_cursor = ((time + s.time_cursor) - 1) % time },
|
||||
SequencerView::Horizontal => { s.note_cursor = ((note + s.note_cursor) - 1) % note },
|
||||
_ => unimplemented!()
|
||||
}
|
||||
},
|
||||
KeyCode::Right => {
|
||||
state.cursor.1 = if state.cursor.1 == 63 {
|
||||
0
|
||||
} else {
|
||||
state.cursor.1 + 1
|
||||
}
|
||||
fn cursor_down (s: &mut Sequencer) {
|
||||
let time = s.time_axis.1 - s.time_axis.0;
|
||||
let note = s.note_axis.1 - s.note_axis.0;
|
||||
match s.mode {
|
||||
SequencerView::Vertical => { s.time_cursor = ((time + s.time_cursor) + 1) % time },
|
||||
SequencerView::Horizontal => { s.note_cursor = ((note + s.note_cursor) + 1) % note },
|
||||
_ => unimplemented!()
|
||||
}
|
||||
},
|
||||
KeyCode::Char('[') => {
|
||||
if state.cursor.2 > 0 {
|
||||
state.cursor.2 = state.cursor.2 - 1
|
||||
}
|
||||
fn cursor_left (s: &mut Sequencer) {
|
||||
let time = s.time_axis.1 - s.time_axis.0;
|
||||
let note = s.note_axis.1 - s.note_axis.0;
|
||||
match s.mode {
|
||||
SequencerView::Vertical => { s.note_cursor = ((note + s.note_cursor) - 1) % note },
|
||||
SequencerView::Horizontal => { s.time_cursor = ((time + s.time_cursor) - 1) % time },
|
||||
_ => unimplemented!()
|
||||
}
|
||||
},
|
||||
KeyCode::Char(']') => {
|
||||
state.cursor.2 = state.cursor.2 + 1
|
||||
},
|
||||
KeyCode::Char('i') => {
|
||||
//state.inputs_open.fetch_xor(true, Ordering::Relaxed);
|
||||
},
|
||||
KeyCode::Char('o') => {
|
||||
//state.outputs_open.fetch_xor(true, Ordering::Relaxed);
|
||||
},
|
||||
KeyCode::Char('a') => {
|
||||
let row = state.cursor.0 as usize;
|
||||
let step = state.cursor.1 as usize;
|
||||
let duration = state.cursor.2 as usize;
|
||||
//let mut sequence = state.sequence.lock().unwrap();
|
||||
//sequence[row][step] = Some(SequencerEvent::NoteOn(48 - row as u8, 128));
|
||||
//if state.cursor.2 > 0 {
|
||||
//sequence[row][step + duration] = Some(SequencerEvent::NoteOff(35));
|
||||
//}
|
||||
},
|
||||
_ => {
|
||||
println!("{event:?}");
|
||||
}
|
||||
fn cursor_right (s: &mut Sequencer) {
|
||||
let time = s.time_axis.1 - s.time_axis.0;
|
||||
let note = s.note_axis.1 - s.note_axis.0;
|
||||
match s.mode {
|
||||
SequencerView::Vertical => { s.note_cursor = ((note + s.note_cursor) + 1) % note },
|
||||
SequencerView::Horizontal => { s.time_cursor = ((time + s.time_cursor) + 1) % time },
|
||||
_ => unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn cursor_inc (s: &mut Sequencer) {
|
||||
//s.cursor.2 = s.cursor.2 + 1
|
||||
}
|
||||
fn cursor_dec (s: &mut Sequencer) {
|
||||
//if s.cursor.2 > 0 { s.cursor.2 = s.cursor.2 - 1 }
|
||||
}
|
||||
fn mode_next (s: &mut Sequencer) {
|
||||
s.mode = SequencerView::Horizontal
|
||||
}
|
||||
fn mode_prev (s: &mut Sequencer) {
|
||||
s.mode = SequencerView::Vertical
|
||||
}
|
||||
fn toggle_play (s: &mut Sequencer) {
|
||||
s.playing = !s.playing
|
||||
}
|
||||
fn toggle_record (s: &mut Sequencer) {
|
||||
s.recording = !s.recording
|
||||
}
|
||||
fn toggle_overdub (s: &mut Sequencer) {
|
||||
s.overdub = !s.overdub
|
||||
}
|
||||
|
||||
fn render (sequencer: &Sequencer, buf: &mut Buffer, mut area: Rect)
|
||||
-> Usually<Rect>
|
||||
{
|
||||
let Rect { x, y, width, height } = area;
|
||||
let (time0, time1) = sequencer.time_axis;
|
||||
let (note0, note1) = sequencer.note_axis;
|
||||
let header = draw_sequencer_header(sequencer, buf, area)?;
|
||||
let piano = match sequencer.mode {
|
||||
SequencerMode::Tiny => Rect {
|
||||
x: area.x,
|
||||
y: area.y,
|
||||
width: area.width,
|
||||
height: 0
|
||||
},
|
||||
SequencerMode::Compact => Rect {
|
||||
x: area.x,
|
||||
y: area.y,
|
||||
width: area.width,
|
||||
height: 0
|
||||
},
|
||||
SequencerMode::Vertical => draw_sequencer_vertical(sequencer, buf, Rect {
|
||||
x: area.x,
|
||||
y: area.y + header.height,
|
||||
width: area.width,
|
||||
height: area.height - header.height
|
||||
SequencerView::Tiny =>
|
||||
Rect { x, y, width, height: 0 },
|
||||
SequencerView::Compact =>
|
||||
Rect { x, y, width, height: 0 },
|
||||
SequencerView::Vertical => draw_sequencer_vertical(sequencer, buf, Rect {
|
||||
x,
|
||||
y: y + header.height,
|
||||
width: 3 + note1 - note0,
|
||||
height: 3 + time1 - time0,
|
||||
})?,
|
||||
SequencerView::Horizontal => draw_sequencer_horizontal(sequencer, buf, Rect {
|
||||
x,
|
||||
y: y + header.height,
|
||||
width: 3 + time1 - time0,
|
||||
height: 3 + note1 - note0,
|
||||
})?,
|
||||
_ => unimplemented!()
|
||||
};
|
||||
//draw_box_styled(buf, area, Some(Style::default().red()));
|
||||
Ok(Rect {
|
||||
x: area.x,
|
||||
y: area.y,
|
||||
Ok(draw_box(buf, Rect {
|
||||
x,
|
||||
y,
|
||||
width: header.width.max(piano.width),
|
||||
height: header.height + piano.height
|
||||
})
|
||||
height: header.height + piano.height + 1
|
||||
}))
|
||||
}
|
||||
|
||||
fn draw_sequencer_header (sequencer: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||
|
|
@ -240,8 +269,23 @@ fn draw_sequencer_header (sequencer: &Sequencer, buf: &mut Buffer, area: Rect) -
|
|||
let style = Style::default().gray();
|
||||
buf.set_string(x + 1, y + 1, &format!(" │ 00:00.00 / 00:00.00"), style);
|
||||
buf.set_string(x + 2, y + 1, &format!("{}", &sequencer.name), style.white().bold());
|
||||
buf.set_string(x + 1, y + 2, &format!(" ▶ PLAY │ ⏹ STOP │ ⏺ REC │ ⏺ DUB "), style);
|
||||
Ok(Rect { x, y, width, height: 4 })
|
||||
buf.set_string(x + 1, y + 2, &format!(" ▶ PLAY │ ⏹ STOP │ │"), style);
|
||||
buf.set_string(x + 2, y + 2, &format!("▶ PLAY"), if sequencer.playing {
|
||||
Style::default().green()
|
||||
} else {
|
||||
Style::default().dim()
|
||||
});
|
||||
buf.set_string(x + 24, y + 2, &format!("⏺ REC"), if sequencer.recording {
|
||||
Style::default().red()
|
||||
} else {
|
||||
Style::default().dim()
|
||||
});
|
||||
buf.set_string(x + 32, y + 2, &format!("⏺ DUB"), if sequencer.overdub {
|
||||
Style::default().yellow()
|
||||
} else {
|
||||
Style::default().dim()
|
||||
});
|
||||
Ok(Rect { x, y, width: 39, height: 4 })
|
||||
}
|
||||
|
||||
const KEY_WHITE: Style = Style {
|
||||
|
|
@ -267,33 +311,134 @@ const KEY_HORIZONTAL_STYLE: [Style;12] = [
|
|||
|
||||
fn draw_sequencer_vertical (sequencer: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||
let Rect { x, y, .. } = area;
|
||||
for i in 0..area.width-7 {
|
||||
let color = KEY_HORIZONTAL_STYLE[i as usize % 12];
|
||||
buf.set_string(x + 5 + i, y - 1, &format!("▄"), color);
|
||||
buf.set_string(x + 5 + i, y, &format!("▀"), color);
|
||||
}
|
||||
let (time0, time1) = sequencer.time_axis;
|
||||
let (note0, note1) = sequencer.note_axis;
|
||||
let bg = Style::default().on_black();
|
||||
for i in 0..area.height - 8 {
|
||||
buf.set_string(x + 5, y + 1 + i, &" ".repeat((area.width-7)as usize), bg);
|
||||
if i % 4 == 0 {
|
||||
buf.set_string(x + 2, y + 1 + i, &format!("{:2}", i / 4 + 1), Style::default());
|
||||
for step in 0..(time1-time0)/2 {
|
||||
let y = y + step - time0;
|
||||
buf.set_string(x + 5, y, &" ".repeat(32.max(note1-note0)as usize), bg);
|
||||
if step % 2 == 0 {
|
||||
buf.set_string(x + 2, y, &format!("{:2} ", step / 2 + 1), Style::default());
|
||||
}
|
||||
}
|
||||
for octave in -2i16..=8i16 {
|
||||
let x = x + 5 + (24i16 + octave * 12) as u16;
|
||||
if x >= area.x + area.width - 6 {
|
||||
break
|
||||
for key in note0..note1.max(note0+32) {
|
||||
let x = x + 5 + key - note0;
|
||||
let color = KEY_HORIZONTAL_STYLE[key as usize % 12];
|
||||
buf.set_string(x, y - 1, &format!("▄"), color);
|
||||
if key % 12 == 0 {
|
||||
let octave = format!("C{}", (key / 12) as i8 - 4);
|
||||
buf.set_string(x, y + 1, &octave, Style::default());
|
||||
}
|
||||
}
|
||||
buf.set_string(
|
||||
x,
|
||||
y + 1,
|
||||
&format!("C{octave}"),
|
||||
x + 5 + sequencer.note_cursor,
|
||||
y + sequencer.time_cursor / 2,
|
||||
if sequencer.time_cursor % 2 == 0 { "▀" } else { "▄" },
|
||||
Style::default()
|
||||
);
|
||||
}
|
||||
Ok(Rect { x, y, width: area.width, height: area.height - 6 })
|
||||
Ok(Rect { x, y, width: area.width, height: (time1-time0)/2 })
|
||||
}
|
||||
|
||||
const KEYS_VERTICAL: [&'static str; 6] = [
|
||||
"▀", "▀", "▀", "█", "▄", "▄",
|
||||
];
|
||||
|
||||
fn draw_sequencer_horizontal (sequencer: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||
let Rect { x, y, .. } = area;
|
||||
let (time0, time1) = sequencer.time_axis;
|
||||
let (note0, note1) = sequencer.note_axis;
|
||||
let bw = Style::default().dim();
|
||||
let bg = Style::default().on_black();
|
||||
for i in 0..32.max(note1-note0)/2 {
|
||||
let y = y + i;
|
||||
buf.set_string(x + 1, y, KEYS_VERTICAL[(i % 6) as usize], bw);
|
||||
buf.set_string(x + 2, y, "█", bw);
|
||||
buf.set_string(x + 5, y, &" ".repeat((time1 - time0) as usize), bg);
|
||||
if i % 6 == 0 {
|
||||
let octave = format!("C{}", ((note1 - i) / 6) as i8 - 4);
|
||||
buf.set_string(x+5, y, &octave, Style::default());
|
||||
}
|
||||
}
|
||||
for step in time0..time1 {
|
||||
let time_start = step as u32 * sequencer.ppq;
|
||||
let time_end = (step + 1) as u32 * sequencer.ppq;
|
||||
for (i, (t, events)) in sequencer.sequence.range(time_start..time_end).enumerate() {
|
||||
if events.len() > 0 {
|
||||
buf.set_string(x + 5 + step as u16, y, "█", bw);
|
||||
}
|
||||
}
|
||||
}
|
||||
buf.set_string(
|
||||
x + 5 + sequencer.time_cursor,
|
||||
y + sequencer.note_cursor / 2,
|
||||
if sequencer.note_cursor % 2 == 0 { "▀" } else { "▄" },
|
||||
Style::default()
|
||||
);
|
||||
Ok(Rect { x, y, width: time1 - time0 + 6, height: 32.max(note1 - note0) / 2 })
|
||||
}
|
||||
|
||||
//buf.set_string(x + 3, y, "╭1.1.", Style::default().dim());
|
||||
//buf.set_string(x + 3 + 16, y, "╭1.2.", Style::default().dim());
|
||||
//buf.set_string(x + 3 + 32, y, "╭1.3.", Style::default().dim());
|
||||
//buf.set_string(x + 3 + 48, y, "╭1.4.", Style::default().dim());
|
||||
////buf.set_string(x + 2, y, "╭", Style::default().dim());
|
||||
////buf.set_string(x + 2, y + 13, "╰", Style::default().dim());
|
||||
//buf.set_string(x + 2 + 65, y, "╮", Style::default().dim());
|
||||
//buf.set_string(x + 2 + 65, y + 13, "╯", Style::default().dim());
|
||||
//let transport = sequencer.transport.query().unwrap();
|
||||
//let frame = transport.pos.frame();
|
||||
//let rate = transport.pos.frame_rate().unwrap();
|
||||
//let second = (frame as f64) / (rate as f64);
|
||||
//let minute = second / 60f64;
|
||||
//let bpm = 120f64;
|
||||
//let div = 4;
|
||||
//let beats = minute * bpm;
|
||||
//let bars = beats as u32 / div as u32;
|
||||
//let beat = beats as u32 % div as u32 + 1;
|
||||
//let beat_sub = beats % 1.0;
|
||||
//buf.set_string(
|
||||
//x - 18, y + height,
|
||||
//format!("BBT {bars:04}:{beat:02}.{:02}", (beat_sub * 16.0) as u32),
|
||||
//Style::default()
|
||||
//);
|
||||
//let bg = if step as u32 == (beat - 1) * 16 + (beat_sub * 16.0) as u32 {
|
||||
//ratatui::style::Color::Gray
|
||||
//} else if step == sequencer.cursor.1 as usize || key == sequencer.cursor.0/2 {
|
||||
//ratatui::style::Color::Black
|
||||
//} else {
|
||||
//ratatui::style::Color::Reset
|
||||
//};
|
||||
//let top = sequence[(key * 2) as usize][step].is_some();
|
||||
//let bottom = sequence[(key * 2 + 1) as usize][step].is_some();
|
||||
//match (top, bottom) {
|
||||
//(true, true) => {
|
||||
//buf.set_string(x + 3 + step as u16, y + 1 + key, "█",
|
||||
//Style::default().yellow().not_dim().bold().bg(bg));
|
||||
//},
|
||||
//(true, false) => {
|
||||
//buf.set_string(x + 3 + step as u16, y + 1 + key, "▀",
|
||||
//Style::default().yellow().not_dim().bold().bg(bg));
|
||||
//},
|
||||
//(false, true) => {
|
||||
//buf.set_string(x + 3 + step as u16, y + 1 + key, "▄",
|
||||
//Style::default().yellow().not_dim().bold().bg(bg));
|
||||
//},
|
||||
//(false, false) => if step % 16 == 0 {
|
||||
//buf.set_string(x + 3 + step as u16, y + 1 + key, "┊",
|
||||
//Style::default().dim().bg(bg))
|
||||
//} else {
|
||||
//buf.set_string(x + 3 + step as u16, y + 1 + key, "·",
|
||||
//Style::default().dim().bg(bg))
|
||||
//},
|
||||
//}
|
||||
//for step in 0..64 {
|
||||
//if step % 8 == 0 {
|
||||
//buf.set_string(x + 3 + step as u16, y + 1 + 12, [
|
||||
//"|a", "|b", "|c", "|d", "|e", "|f", "|g", "|h"
|
||||
//][step / 8 as usize], Style::default().dim())
|
||||
//}
|
||||
//}
|
||||
|
||||
//{
|
||||
//let mut area = area.clone();
|
||||
//area.y = area.y + 3;
|
||||
|
|
@ -371,219 +516,32 @@ fn draw_sequencer_vertical (sequencer: &Sequencer, buf: &mut Buffer, area: Rect)
|
|||
//}
|
||||
//}
|
||||
//
|
||||
|
||||
fn draw_sequence_button (
|
||||
buf: &mut Buffer,
|
||||
x: u16,
|
||||
y: u16,
|
||||
name: &str
|
||||
) {
|
||||
draw_box(buf, Rect { x, y, width: 18, height: 3 });
|
||||
buf.set_string(x + 1, y + 1, &format!(" ▶ {} ", name),
|
||||
Style::default().white().bold().not_dim());
|
||||
buf.set_string(x + 4, y + 0, "┬", Style::default().gray());
|
||||
buf.set_string(x + 4, y + 1, "│", Style::default().gray().dim());
|
||||
buf.set_string(x + 4, y + 2, "┴", Style::default().gray());
|
||||
}
|
||||
|
||||
fn draw_rec_dub_button (
|
||||
buf: &mut Buffer,
|
||||
x: u16,
|
||||
y: u16,
|
||||
) {
|
||||
draw_box(buf, Rect { x, y, width: 18, height: 3 });
|
||||
buf.set_string(x + 1, y + 1, " ⏺ REC DUB ",
|
||||
Style::default().white().bold().not_dim());
|
||||
buf.set_string(x + 5, y + 0, "┬", Style::default().gray());
|
||||
buf.set_string(x + 5, y + 1, "│", Style::default().gray().dim());
|
||||
buf.set_string(x + 5, y + 2, "┴", Style::default().gray());
|
||||
buf.set_string(x + 11, y + 0, "┬", Style::default().gray());
|
||||
buf.set_string(x + 11, y + 1, "│", Style::default().gray().dim());
|
||||
buf.set_string(x + 11, y + 2, "┴", Style::default().gray());
|
||||
}
|
||||
|
||||
const NOTE_NAMES: [&'static str;12] = [
|
||||
"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B",
|
||||
];
|
||||
|
||||
const KEYS_VERTICAL: [&'static str; 6] = [
|
||||
"▀", "▀", "▀", "█", "▄", "▄",
|
||||
];
|
||||
|
||||
fn draw_sequence_keys (
|
||||
area: Rect,
|
||||
buf: &mut Buffer,
|
||||
transport: &::jack::TransportStatePosition,
|
||||
sequence: &Arc<Mutex<Vec<Vec<Option<self::Event>>>>>,
|
||||
cursor: &(u16, u16, u16)
|
||||
) {
|
||||
buf.set_string(area.x + 3, area.y, "╭1.1.", Style::default().dim());
|
||||
buf.set_string(area.x + 3 + 16, area.y, "╭1.2.", Style::default().dim());
|
||||
buf.set_string(area.x + 3 + 32, area.y, "╭1.3.", Style::default().dim());
|
||||
buf.set_string(area.x + 3 + 48, area.y, "╭1.4.", Style::default().dim());
|
||||
//buf.set_string(area.x + 2, area.y, "╭", Style::default().dim());
|
||||
//buf.set_string(area.x + 2, area.y + 13, "╰", Style::default().dim());
|
||||
buf.set_string(area.x + 2 + 65, area.y, "╮", Style::default().dim());
|
||||
buf.set_string(area.x + 2 + 65, area.y + 13, "╯", Style::default().dim());
|
||||
let frame = transport.pos.frame();
|
||||
let rate = transport.pos.frame_rate().unwrap();
|
||||
let second = (frame as f64) / (rate as f64);
|
||||
let minute = second / 60f64;
|
||||
let bpm = 120f64;
|
||||
let div = 4;
|
||||
let beats = minute * bpm;
|
||||
let bars = beats as u32 / div as u32;
|
||||
let beat = beats as u32 % div as u32 + 1;
|
||||
let beat_sub = beats % 1.0;
|
||||
//buf.set_string(
|
||||
//area.x - 18, area.y + area.height,
|
||||
//format!("BBT {bars:04}:{beat:02}.{:02}", (beat_sub * 16.0) as u32),
|
||||
//Style::default()
|
||||
//);
|
||||
let sequence = sequence.lock().unwrap();
|
||||
for key in 0..12 {
|
||||
buf.set_string(area.x, area.y + 1 + key, KEYS_VERTICAL[(key % 6) as usize],
|
||||
Style::default().dim());
|
||||
buf.set_string(area.x + 1, area.y + 1 + key, "█",
|
||||
Style::default().dim());
|
||||
for step in 0..64 {
|
||||
let bg = if step as u32 == (beat - 1) * 16 + (beat_sub * 16.0) as u32 {
|
||||
ratatui::style::Color::Gray
|
||||
} else if step == cursor.1 as usize || key == cursor.0/2 {
|
||||
ratatui::style::Color::Black
|
||||
} else {
|
||||
ratatui::style::Color::Reset
|
||||
};
|
||||
let top = sequence[(key * 2) as usize][step].is_some();
|
||||
let bottom = sequence[(key * 2 + 1) as usize][step].is_some();
|
||||
match (top, bottom) {
|
||||
(true, true) => {
|
||||
buf.set_string(area.x + 3 + step as u16, area.y + 1 + key, "█",
|
||||
Style::default().yellow().not_dim().bold().bg(bg));
|
||||
},
|
||||
(true, false) => {
|
||||
buf.set_string(area.x + 3 + step as u16, area.y + 1 + key, "▀",
|
||||
Style::default().yellow().not_dim().bold().bg(bg));
|
||||
},
|
||||
(false, true) => {
|
||||
buf.set_string(area.x + 3 + step as u16, area.y + 1 + key, "▄",
|
||||
Style::default().yellow().not_dim().bold().bg(bg));
|
||||
},
|
||||
(false, false) => if step % 16 == 0 {
|
||||
buf.set_string(area.x + 3 + step as u16, area.y + 1 + key, "┊",
|
||||
Style::default().dim().bg(bg))
|
||||
} else {
|
||||
buf.set_string(area.x + 3 + step as u16, area.y + 1 + key, "·",
|
||||
Style::default().dim().bg(bg))
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for step in 0..64 {
|
||||
if step % 8 == 0 {
|
||||
buf.set_string(area.x + 3 + step as u16, area.y + 1 + 12, [
|
||||
"|A", "|B", "|C", "|D", "|E", "|F", "|G", "|H"
|
||||
][step / 8 as usize], Style::default().dim())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_sequence_cursor (
|
||||
area: Rect, buf: &mut Buffer, cursor: &(u16, u16, u16)
|
||||
) {
|
||||
let cursor_character = match cursor.0 % 2 {
|
||||
0 => "▀",
|
||||
1 => "▄",
|
||||
_ => unreachable!()
|
||||
};
|
||||
let cursor_y = cursor.0 / 2;
|
||||
let cursor_text = if cursor.2 == 0 {
|
||||
cursor_character.into()
|
||||
} else {
|
||||
cursor_character.repeat(cursor.2 as usize)
|
||||
};
|
||||
let style = Style::default().yellow().dim();
|
||||
buf.set_string(area.x + cursor.1 + 3, area.y + 1 + cursor_y, cursor_text, style);
|
||||
}
|
||||
|
||||
//pub type MIDISequenceVoice = std::collections::BTreeMap<u32, NoteEvent>;
|
||||
|
||||
//#[derive(Clone)]
|
||||
//pub enum NoteEvent {
|
||||
//On(u8),
|
||||
//Off(u8),
|
||||
//fn draw_sequence_button (
|
||||
//buf: &mut Buffer,
|
||||
//x: u16,
|
||||
//y: u16,
|
||||
//name: &str
|
||||
//) {
|
||||
//draw_box(buf, Rect { x, y, width: 18, height: 3 });
|
||||
//buf.set_string(x + 1, y + 1, &format!(" ▶ {} ", name),
|
||||
//Style::default().white().bold().not_dim());
|
||||
//buf.set_string(x + 4, y + 0, "┬", Style::default().gray());
|
||||
//buf.set_string(x + 4, y + 1, "│", Style::default().gray().dim());
|
||||
//buf.set_string(x + 4, y + 2, "┴", Style::default().gray());
|
||||
//}
|
||||
|
||||
//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),
|
||||
//]
|
||||
//}
|
||||
//}
|
||||
//fn draw_rec_dub_button (
|
||||
//buf: &mut Buffer,
|
||||
//x: u16,
|
||||
//y: u16,
|
||||
//) {
|
||||
//draw_box(buf, Rect { x, y, width: 18, height: 3 });
|
||||
//buf.set_string(x + 1, y + 1, " ⏺ REC DUB ",
|
||||
//Style::default().white().bold().not_dim());
|
||||
//buf.set_string(x + 5, y + 0, "┬", Style::default().gray());
|
||||
//buf.set_string(x + 5, y + 1, "│", Style::default().gray().dim());
|
||||
//buf.set_string(x + 5, y + 2, "┴", Style::default().gray());
|
||||
//buf.set_string(x + 11, y + 0, "┬", Style::default().gray());
|
||||
//buf.set_string(x + 11, y + 1, "│", Style::default().gray().dim());
|
||||
//buf.set_string(x + 11, y + 2, "┴", Style::default().gray());
|
||||
//}
|
||||
|
||||
//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]
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
|
||||
//#[derive(Clone)]
|
||||
//pub enum SequencerEvent {
|
||||
//NoteOn(u8, u8),
|
||||
//NoteOff(u8)
|
||||
//}
|
||||
|
||||
//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 {
|
||||
//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()
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@ pub struct Transport {
|
|||
impl Transport {
|
||||
pub fn new (name: &str) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
|
||||
let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?;
|
||||
Ok(DynamicDevice::new(render, handle, process, Self {
|
||||
DynamicDevice::new(render, handle, process, Self {
|
||||
name: name.into(),
|
||||
transport: client.transport(),
|
||||
timesig: (4.0, 4.0),
|
||||
bpm: 113.0,
|
||||
}))
|
||||
}).activate(client)
|
||||
}
|
||||
|
||||
pub fn play_from_start_or_stop_and_rewind (&mut self) {
|
||||
|
|
@ -233,39 +233,22 @@ pub const ACTIONS: [(&'static str, &'static str);4] = [
|
|||
("(Shift-)Space", "⯈ Play/pause"),
|
||||
];
|
||||
|
||||
struct TransportJack;
|
||||
|
||||
impl NotificationHandler for TransportJack {
|
||||
fn thread_init (&self, _: &Client) {
|
||||
}
|
||||
|
||||
fn shutdown (&mut self, status: ClientStatus, reason: &str) {
|
||||
}
|
||||
|
||||
fn freewheel (&mut self, _: &Client, is_enabled: bool) {
|
||||
}
|
||||
|
||||
impl NotificationHandler for Transport {
|
||||
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 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 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
|
||||
}
|
||||
|
|
|
|||
27
src/draw.rs
27
src/draw.rs
|
|
@ -38,9 +38,9 @@ pub fn draw_leaf (buffer: &mut Buffer, area: Rect, y: u16, x: u16, text: &str) {
|
|||
buffer.set_string(area.x + x, area.y + 1 + y, bottom, border);
|
||||
}
|
||||
|
||||
pub fn draw_box (buffer: &mut Buffer, area: Rect) {
|
||||
pub fn draw_box (buffer: &mut Buffer, area: Rect) -> Rect {
|
||||
if area.width < 1 || area.height < 1 {
|
||||
return
|
||||
return area
|
||||
}
|
||||
let border = Style::default().gray().dim();
|
||||
let top = format!("╭{}╮", "─".repeat((area.width - 2).into()));
|
||||
|
|
@ -51,11 +51,12 @@ pub fn draw_box (buffer: &mut Buffer, area: Rect) {
|
|||
buffer.set_string(area.x + area.width - 1, y, format!("│"), border);
|
||||
}
|
||||
buffer.set_string(area.x, area.y + area.height - 1, bottom, border);
|
||||
area
|
||||
}
|
||||
|
||||
pub fn draw_box_styled (buffer: &mut Buffer, area: Rect, style: Option<Style>) {
|
||||
pub fn draw_box_styled (buffer: &mut Buffer, area: Rect, style: Option<Style>) -> Rect {
|
||||
if area.width < 1 || area.height < 1 {
|
||||
return
|
||||
return area
|
||||
}
|
||||
let style = style.unwrap_or(Style::default());
|
||||
let top = format!("╭{}╮", "┅".repeat((area.width - 2).into()));
|
||||
|
|
@ -66,14 +67,20 @@ pub fn draw_box_styled (buffer: &mut Buffer, area: Rect, style: Option<Style>) {
|
|||
buffer.set_string(area.x + area.width - 1, y, format!("┇"), style);
|
||||
}
|
||||
buffer.set_string(area.x, area.y + area.height - 1, bottom, style);
|
||||
area
|
||||
}
|
||||
|
||||
pub fn draw_focus_corners (buffer: &mut Buffer, area: Rect) {
|
||||
let focus = Style::default().yellow().bold().not_dim();
|
||||
buffer.set_string(area.x, area.y, "╭", focus);
|
||||
buffer.set_string(area.x + area.width - 1, area.y, "╮", focus);
|
||||
buffer.set_string(area.x, area.y + area.height - 1, "╰", focus);
|
||||
buffer.set_string(area.x + area.width - 1, area.y + area.height - 1, "╯", focus);
|
||||
pub fn draw_corners (buffer: &mut Buffer, area: Rect, style: Option<Style>) -> Rect {
|
||||
let style = style.unwrap_or(Style::default());
|
||||
buffer.set_string(area.x, area.y, "╭", style);
|
||||
buffer.set_string(area.x + area.width - 1, area.y, "╮", style);
|
||||
buffer.set_string(area.x, area.y + area.height - 1, "╰", style);
|
||||
buffer.set_string(area.x + area.width - 1, area.y + area.height - 1, "╯", style);
|
||||
area
|
||||
}
|
||||
|
||||
pub fn draw_focus_corners (buffer: &mut Buffer, area: Rect) -> Rect {
|
||||
draw_corners(buffer, area, Some(Style::default().yellow().bold().not_dim()))
|
||||
}
|
||||
|
||||
//pub fn render_toolbar_vertical (
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ impl Device for Rows {
|
|||
height: area.height - h
|
||||
})?;
|
||||
if self.focused && i == self.focus {
|
||||
draw_box_styled(buf, result, Some(Style::default().green().dim().bold()))
|
||||
draw_box_styled(buf, result, Some(Style::default().green().dim().bold()));
|
||||
}
|
||||
w = w.max(result.width);
|
||||
h = h + result.height;
|
||||
|
|
@ -111,7 +111,7 @@ impl Device for Columns {
|
|||
height: area.height
|
||||
})?;
|
||||
if self.focused && i == self.focus {
|
||||
draw_box_styled(buf, result, Some(Style::default().yellow()))
|
||||
draw_box_styled(buf, result, Some(Style::default().yellow()));
|
||||
}
|
||||
w = w + result.width;
|
||||
h = h.max(result.height);
|
||||
|
|
|
|||
11
src/main.rs
11
src/main.rs
|
|
@ -20,11 +20,8 @@ fn main () -> Result<(), Box<dyn Error>> {
|
|||
let cli = cli::Cli::parse();
|
||||
let xdg = microxdg::XdgApp::new("dawdle")?;
|
||||
crate::config::create_dirs(&xdg)?;
|
||||
//crate::device::run(Sequencer::new("Rhythm#000")?)
|
||||
crate::device::run(Rows::new(true, vec![
|
||||
Box::new(Sequencer::new("Rhythm#000")?),
|
||||
Box::new(Transport::new("Transport0")?),
|
||||
]))
|
||||
//crate::device::run(Rows::new(true, vec![
|
||||
//Box::new(Columns::new(false, vec![
|
||||
//Box::new(Chain::new("Chain#00", vec![
|
||||
//Box::new(Sequencer::new("Rhythm#000")?),
|
||||
|
|
@ -36,6 +33,8 @@ fn main () -> Result<(), Box<dyn Error>> {
|
|||
//])?),
|
||||
//])),
|
||||
//Box::new(Mixer::new("Mixer#000")?),
|
||||
//Box::new(Transport::new()?),
|
||||
//]))
|
||||
Box::new(Sequencer::new("Melody#000")?),
|
||||
Box::new(Transport::new("Transport")?),
|
||||
Box::new(Sequencer::new("Rhythm#000")?),
|
||||
]))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,11 +74,14 @@ pub use jack::{
|
|||
PortId,
|
||||
ProcessHandler,
|
||||
ProcessScope,
|
||||
RawMidi,
|
||||
Transport,
|
||||
TransportState,
|
||||
TransportStatePosition
|
||||
};
|
||||
|
||||
pub type BoxedNotificationHandler = Box<dyn Fn(AppEvent) + Send>;
|
||||
|
||||
pub type BoxedProcessHandler = Box<dyn FnMut(&Client, &ProcessScope)-> Control + Send>;
|
||||
|
||||
pub type Jack<N> = AsyncClient<N, ClosureProcessHandler<BoxedProcessHandler>>;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue