systematizing jack handlers

This commit is contained in:
🪞👃🪞 2024-06-14 16:54:28 +03:00
parent d627d257ad
commit 4ae62c5bc2
10 changed files with 342 additions and 208 deletions

View file

@ -165,6 +165,10 @@ pub enum EngineEvent {
Update, Update,
/// Update the whole form. /// Update the whole form.
Redraw, Redraw,
/// Device gains focus
Focus,
/// Device loses focus
Blur,
} }
pub fn activate_jack_client <N: NotificationHandler + Sync + 'static> ( pub fn activate_jack_client <N: NotificationHandler + Sync + 'static> (

View file

@ -14,6 +14,13 @@ impl Chain {
} }
} }
pub fn process (
client: &Client,
scope: &ProcessScope
) -> Control {
Control::Continue
}
pub fn render (state: &Chain, buf: &mut Buffer, area: Rect) pub fn render (state: &Chain, buf: &mut Buffer, area: Rect)
-> Usually<Rect> -> Usually<Rect>
{ {

View file

@ -12,6 +12,13 @@ impl Launcher {
} }
} }
pub fn process (
client: &Client,
scope: &ProcessScope
) -> Control {
Control::Continue
}
pub fn render (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usually<Rect> { pub fn render (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
Ok(Rect::default()) Ok(Rect::default())
} }

View file

@ -12,6 +12,13 @@ impl Looper {
} }
} }
pub fn process (
client: &Client,
scope: &ProcessScope
) -> Control {
Control::Continue
}
pub fn render (state: &Looper, buf: &mut Buffer, area: Rect) pub fn render (state: &Looper, buf: &mut Buffer, area: Rect)
-> Usually<Rect> -> Usually<Rect>
{ {

View file

@ -2,7 +2,7 @@ use crate::prelude::*;
pub struct Mixer { pub struct Mixer {
name: String, name: String,
jack: Jack<Notifications>, jack: Jack<MixerJack>,
tracks: Vec<Track>, tracks: Vec<Track>,
selected_track: usize, selected_track: usize,
selected_column: usize, selected_column: usize,
@ -14,14 +14,9 @@ impl Mixer {
name, name,
ClientOptions::NO_START_SERVER ClientOptions::NO_START_SERVER
)?; )?;
let jack = client.activate_async( let jack = MixerJack.activate(client, ClosureProcessHandler::new(Box::new(
Notifications, move|client: &Client, scope: &ProcessScope|process(client, scope)
ClosureProcessHandler::new(Box::new( ) as BoxedProcessHandler))?;
move |_: &Client, _: &ProcessScope| -> Control {
Control::Continue
}) as Box<dyn FnMut(&Client, &ProcessScope)->Control + Send>
)
)?;
Ok(DynamicDevice::new(render, handle, |_|{}, Self { Ok(DynamicDevice::new(render, handle, |_|{}, Self {
name: name.into(), name: name.into(),
selected_column: 0, selected_column: 0,
@ -41,6 +36,13 @@ impl Mixer {
} }
} }
pub fn process (
client: &Client,
scope: &ProcessScope
) -> Control {
Control::Continue
}
pub fn render (state: &Mixer, buf: &mut Buffer, mut area: Rect) pub fn render (state: &Mixer, buf: &mut Buffer, mut area: Rect)
-> Usually<Rect> -> Usually<Rect>
{ {
@ -211,9 +213,17 @@ impl Track {
} }
} }
pub struct Notifications; pub struct MixerJack;
impl NotificationHandler for Notifications { impl MixerJack {
fn activate <P> (self, client: Client, handler: P) -> Usually<AsyncClient<Self, P>>
where P: 'static + Send + ::jack::ProcessHandler
{
Ok(client.activate_async(self, handler)?)
}
}
impl NotificationHandler for MixerJack {
fn thread_init (&self, _: &Client) { fn thread_init (&self, _: &Client) {
} }
@ -254,7 +264,7 @@ impl NotificationHandler for Notifications {
//fn handle (&mut self, engine: &mut TUI<W>) -> Result<Option<bool>> { //fn handle (&mut self, engine: &mut TUI<W>) -> Result<Option<bool>> {
//Ok(None) //Ok(None)
//} //}
//} //
//impl<W: Write> Output<TUI<W>, [u16;2]> for Mixer { //impl<W: Write> Output<TUI<W>, [u16;2]> for Mixer {
//fn render (&self, envine: &mut TUI<W>) -> Result<Option<[u16;2]>> { //fn render (&self, envine: &mut TUI<W>) -> Result<Option<[u16;2]>> {

View file

@ -7,7 +7,7 @@ pub const ACTIONS: [(&'static str, &'static str);2] = [
pub struct Sampler { pub struct Sampler {
name: String, name: String,
jack: Jack<self::Notifications>, jack: Jack<SamplerJack>,
samples: Arc<Mutex<Vec<Sample>>>, samples: Arc<Mutex<Vec<Sample>>>,
selected_sample: usize, selected_sample: usize,
selected_column: usize, selected_column: usize,
@ -23,57 +23,65 @@ 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 handler: BoxedProcessHandler = Box::new({ let jack = SamplerJack.activate(client, ClosureProcessHandler::new({
let exited = exited.clone(); let exited = exited.clone();
let samples = samples.clone(); let samples = samples.clone();
Box::new(move |_: &Client, scope: &ProcessScope| -> Control { Box::new(move |client: &Client, scope: &ProcessScope|{
if exited.fetch_and(true, Ordering::Relaxed) { process(client, scope, &exited, &samples, &input);
return Control::Quit
}
let mut samples = samples.lock().unwrap();
for event in input.iter(scope) {
let len = 3.min(event.bytes.len());
let mut data = [0; 3];
data[..len].copy_from_slice(&event.bytes[..len]);
if (data[0] >> 4) == 0b1001 { // note on
let channel = data[0] & 0b00001111;
let note = data[1];
let velocity = data[2];
for sample in samples.iter_mut() {
if /*sample.trigger.0 == channel &&*/ sample.trigger.1 == note {
sample.play(velocity);
}
}
}
for sample in samples.iter_mut() {
if let Some(playing) = sample.playing {
for (index, value) in sample.port.as_mut_slice(scope).iter_mut().enumerate() {
*value = *sample.data[0].get(playing + index).unwrap_or(&0f32);
}
if playing + scope.n_frames() as usize > sample.data[0].len() {
sample.playing = None
} else {
sample.playing = Some(playing + scope.n_frames() as usize)
}
}
}
}
Control::Continue Control::Continue
}) }) as BoxedProcessHandler
}); }))?;
Ok(DynamicDevice::new(render, handle, |_|{}, Self { Ok(DynamicDevice::new(render, handle, |_|{}, Self {
name: name.into(), name: name.into(),
selected_sample: 0, selected_sample: 0,
selected_column: 0, selected_column: 0,
samples, samples,
jack: client.activate_async( jack,
self::Notifications,
ClosureProcessHandler::new(handler)
)?,
})) }))
} }
} }
pub fn process (
client: &Client,
scope: &ProcessScope,
exited: &Arc<AtomicBool>,
samples: &Arc<Mutex<Vec<Sample>>>,
input: &Port<MidiIn>
) -> Control {
if exited.fetch_and(true, Ordering::Relaxed) {
return Control::Quit
}
let mut samples = samples.lock().unwrap();
for event in input.iter(scope) {
let len = 3.min(event.bytes.len());
let mut data = [0; 3];
data[..len].copy_from_slice(&event.bytes[..len]);
if (data[0] >> 4) == 0b1001 { // note on
let channel = data[0] & 0b00001111;
let note = data[1];
let velocity = data[2];
for sample in samples.iter_mut() {
if /*sample.trigger.0 == channel &&*/ sample.trigger.1 == note {
sample.play(velocity);
}
}
}
for sample in samples.iter_mut() {
if let Some(playing) = sample.playing {
for (index, value) in sample.port.as_mut_slice(scope).iter_mut().enumerate() {
*value = *sample.data[0].get(playing + index).unwrap_or(&0f32);
}
if playing + scope.n_frames() as usize > sample.data[0].len() {
sample.playing = None
} else {
sample.playing = Some(playing + scope.n_frames() as usize)
}
}
}
}
Control::Continue
}
pub struct Sample { pub struct Sample {
port: Port<AudioOut>, port: Port<AudioOut>,
name: String, name: String,
@ -217,9 +225,17 @@ pub fn handle (state: &mut Sampler, event: &EngineEvent) -> Result<(), Box<dyn E
Ok(()) Ok(())
} }
pub struct Notifications; pub struct SamplerJack;
impl NotificationHandler for Notifications { impl SamplerJack {
fn activate <P> (self, client: Client, handler: P) -> Usually<AsyncClient<Self, P>>
where P: 'static + Send + ::jack::ProcessHandler
{
Ok(client.activate_async(self, handler)?)
}
}
impl NotificationHandler for SamplerJack {
fn thread_init (&self, _: &Client) { fn thread_init (&self, _: &Client) {
} }

View file

@ -3,6 +3,7 @@ use ratatui::style::Stylize;
pub struct Sequencer { pub struct Sequencer {
name: String, name: String,
jack: Jack<SequencerJack>,
playing: Arc<AtomicBool>, playing: Arc<AtomicBool>,
recording: Arc<AtomicBool>, recording: Arc<AtomicBool>,
overdub: Arc<AtomicBool>, overdub: Arc<AtomicBool>,
@ -10,9 +11,16 @@ pub struct Sequencer {
outputs_open: Arc<AtomicBool>, outputs_open: Arc<AtomicBool>,
cursor: (u16, u16, u16), cursor: (u16, u16, u16),
timesig: (f32, f32), timesig: (f32, f32),
pub jack_client: Jack<self::Notifications>,
sequence: Arc<Mutex<Vec<Vec<Option<SequencerEvent>>>>>, sequence: Arc<Mutex<Vec<Vec<Option<SequencerEvent>>>>>,
sequences: Arc<Mutex<Vec<MIDISequence>>>, sequences: Arc<Mutex<Vec<MIDISequence>>>,
mode: SequencerMode,
}
pub enum SequencerMode {
Tiny,
Compact,
Vertical,
Horizontal,
} }
impl Sequencer { impl Sequencer {
@ -31,15 +39,6 @@ impl Sequencer {
let playing = Arc::new(AtomicBool::new(true)); let playing = Arc::new(AtomicBool::new(true));
let recording = Arc::new(AtomicBool::new(true)); let recording = Arc::new(AtomicBool::new(true));
let overdub = Arc::new(AtomicBool::new(false)); let overdub = Arc::new(AtomicBool::new(false));
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 sequence: Arc<Mutex<Vec<Vec<Option<SequencerEvent>>>>> = Arc::new( let sequence: Arc<Mutex<Vec<Vec<Option<SequencerEvent>>>>> = Arc::new(
Mutex::new(vec![vec![None;64];128]) Mutex::new(vec![vec![None;64];128])
); );
@ -53,7 +52,17 @@ impl Sequencer {
for (index, frame) in step_frames.iter().enumerate() { for (index, frame) in step_frames.iter().enumerate() {
frame_steps[*frame] = Some(index); 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 { Ok(DynamicDevice::new(render, handle, |_|{}, Self {
mode: SequencerMode::Vertical,
name: name.into(), name: name.into(),
playing: playing.clone(), playing: playing.clone(),
recording: recording.clone(), recording: recording.clone(),
@ -65,67 +74,70 @@ impl Sequencer {
])), ])),
cursor: (11, 0, 0), cursor: (11, 0, 0),
timesig: (4.0, 4.0), timesig: (4.0, 4.0),
jack_client: crate::device::activate_jack_client( jack: SequencerJack.activate(client, ClosureProcessHandler::new(
client, Box::new(move |client: &Client, scope: &ProcessScope| process(client, scope)) as BoxedProcessHandler
Notifications, ))?
Box::new(move |client: &Client, scope: &ProcessScope| -> Control {
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
})
)?
})) }))
} }
} }
pub fn process (
client: &Client,
scope: &ProcessScope
) -> Control {
//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
}
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"),
@ -135,6 +147,9 @@ pub const ACTIONS: [(&'static str, &'static str);4] = [
pub fn handle (state: &mut Sequencer, event: &EngineEvent) -> Result<(), Box<dyn Error>> { pub fn handle (state: &mut Sequencer, event: &EngineEvent) -> Result<(), Box<dyn Error>> {
if let EngineEvent::Focus = event {
}
if let EngineEvent::Input(Event::Key(event)) = event { if let EngineEvent::Input(Event::Key(event)) = event {
match event.code { match event.code {
KeyCode::Down => { KeyCode::Down => {
@ -197,33 +212,38 @@ pub fn handle (state: &mut Sequencer, event: &EngineEvent) -> Result<(), Box<dyn
Ok(()) Ok(())
} }
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 render (sequencer: &Sequencer, buf: &mut Buffer, mut area: Rect) fn render (sequencer: &Sequencer, buf: &mut Buffer, mut area: Rect)
-> Usually<Rect> -> Usually<Rect>
{ {
let style = Style::default().gray();
let header = draw_sequencer_header(sequencer, buf, area)?; let header = draw_sequencer_header(sequencer, buf, area)?;
let piano = draw_sequencer_vertical(sequencer, buf, Rect { let piano = match sequencer.mode {
x: area.x, SequencerMode::Tiny => Rect {
y: area.y + header.height, x: area.x,
width: area.width, y: area.y,
height: area.height - header.height width: area.width,
})?; height: 0
let area = Rect { },
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
})?,
_ => unimplemented!()
};
//draw_box_styled(buf, area, Some(Style::default().red()));
Ok(Rect {
x: area.x, x: area.x,
y: area.y, y: area.y,
width: header.width.max(piano.width), width: header.width.max(piano.width),
height: header.height + piano.height height: header.height + piano.height
}; })
//draw_box_styled(buf, area, Some(Style::default().red()));
Ok(area)
} }
fn draw_sequencer_header (sequencer: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> { fn draw_sequencer_header (sequencer: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
@ -235,32 +255,52 @@ fn draw_sequencer_header (sequencer: &Sequencer, buf: &mut Buffer, area: Rect) -
Ok(Rect { x, y, width, height: 4 }) Ok(Rect { x, y, width, height: 4 })
} }
const KEY_WHITE: Style = Style {
fg: Some(Color::Gray),
bg: None,
underline_color: None,
add_modifier: ::ratatui::style::Modifier::empty(),
sub_modifier: ::ratatui::style::Modifier::empty(),
};
const KEY_BLACK: Style = Style {
fg: Some(Color::Black),
bg: None,
underline_color: None,
add_modifier: ::ratatui::style::Modifier::empty(),
sub_modifier: ::ratatui::style::Modifier::empty(),
};
const KEY_HORIZONTAL_STYLE: [Style;12] = [
KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE,
KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE,
];
fn draw_sequencer_vertical (sequencer: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> { fn draw_sequencer_vertical (sequencer: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let Rect { x, y, .. } = area; let Rect { x, y, .. } = area;
let white = Style::default().gray(); for i in 0..area.width-7 {
let black = Style::default().black(); let color = KEY_HORIZONTAL_STYLE[i as usize % 12];
for i in 0..area.width-4 { buf.set_string(x + 5 + i, y - 1, &format!(""), color);
let color = match i % 12 { buf.set_string(x + 5 + i, y, &format!(""), color);
0 => white,
1 => black,
2 => white,
3 => black,
4 => white,
5 => white,
6 => black,
7 => white,
8 => black,
9 => white,
10 => black,
11 => white,
_ => unreachable!()
};
buf.set_string(x + 2 + i, y - 1, &format!(""), color);
buf.set_string(x + 2 + i, y, &format!(""), color);
} }
let bg = Style::default().on_black(); let bg = Style::default().on_black();
for i in 0..area.height - 8 { for i in 0..area.height - 8 {
buf.set_string(x + 2, y + 1 + i, &" ".repeat((area.width-4)as usize), bg); 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 octave in -2i16..=8i16 {
let x = x + 5 + (24i16 + octave * 12) as u16;
if x >= area.x + area.width - 6 {
break
}
buf.set_string(
x,
y + 1,
&format!("C{octave}"),
Style::default()
);
} }
Ok(Rect { x, y, width: area.width, height: area.height - 6 }) Ok(Rect { x, y, width: area.width, height: area.height - 6 })
} }
@ -373,6 +413,14 @@ fn draw_rec_dub_button (
buf.set_string(x + 11, y + 2, "", Style::default().gray()); 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 ( fn draw_sequence_keys (
area: Rect, area: Rect,
buf: &mut Buffer, buf: &mut Buffer,
@ -470,9 +518,17 @@ 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 Notifications; pub struct SequencerJack;
impl NotificationHandler for Notifications { impl SequencerJack {
fn activate (self, client: Client, handler: ClosureProcessHandler<BoxedProcessHandler>)
-> Usually<AsyncClient<Self, ClosureProcessHandler<BoxedProcessHandler>>>
{
Ok(client.activate_async(self, handler)?)
}
}
impl NotificationHandler for SequencerJack {
fn thread_init (&self, _: &Client) { fn thread_init (&self, _: &Client) {
} }

View file

@ -47,50 +47,74 @@ pub fn render (state: &Transport, buf: &mut Buffer, mut area: Rect)
area.width = area.width.min(80); area.width = area.width.min(80);
area.height = 5; area.height = 5;
//draw_box(buf, area); //draw_box(buf, area);
draw_leaf(buf, area, 1, 0, "REC"); let label = Style::default().white().not_dim();
draw_leaf(buf, area, 1, 5, "DUB"); let border = Style::default().gray().dim();
draw_leaf(buf, area, 1, 10, "STOP"); let mut x = 2;
draw_leaf(buf, area, 1, 16, "PLAY/PAUSE"); for button in [
draw_leaf(buf, area, 1, 28, "START"); "PLAY",
draw_leaf(buf, area, 1, 35, "Project: Witty Gerbil - Sha Na Na "); "STOP",
draw_leaf(buf, area, 3, 0, &format!("BPM {:03}.{:03}", "REC",
state.bpm as u64, "DUB",
((state.bpm % 1.0) * 1000.0) as u64 "0.0.00",
)); "0:00.000",
let position = state.transport.as_ref().map(|t|t.query()); &format!("BPM {:03}.{:03}",
if let Some(Ok(position)) = position { state.bpm as u64,
let rate = position.pos.frame_rate().unwrap(); ((state.bpm % 1.0) * 1000.0) as u64
let frame = position.pos.frame(); )
let second = (frame as f64) / (rate as f64); ].iter() {
let minute = second / 60f64; buf.set_string(area.x + x, area.y + 2, button, label);
let bpm = 120f64; x = x + button.len() as u16 + 1;
let div = 4; buf.set_string(area.x + x, area.y + 1, "", border);
let beats = minute * bpm; buf.set_string(area.x + x, area.y + 2, "", border);
let bars = beats as u32 / div as u32; buf.set_string(area.x + x, area.y + 3, "", border);
let beat = beats as u32 % div as u32 + 1; x = x + 2;
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()
//);
draw_leaf(buf, area, 3, 13, &format!("BBT {bars:04}:{beat:02}.{:02}",
(beat_sub * 16.0) as u32
));
let time = frame as f64 / rate as f64;
let seconds = time % 60.0;
let msec = seconds % 1.0;
let minutes = (time / 60.0) % 60.0;
let hours = time / 3600.0;
draw_leaf(buf, area, 3, 29, &format!("Time {:02}:{:02}:{:02}.{:03}",
hours as u64,
minutes as u64,
seconds as u64,
(msec * 1000.0) as u64
));
draw_leaf(buf, area, 3, 48, &format!("Rate {:>6}Hz", rate));
draw_leaf(buf, area, 3, 63, &format!("Frame {:>10}", frame));
} }
//buf.set_string(area.x, area.y + 5, "Witty Gerbil - Sha Na Na", label.bold());
//&format!(" │ 00:00.00 / 00:00.00"), style);
//draw_leaf(buf, area, 1, 0, "REC");
//draw_leaf(buf, area, 1, 5, "DUB");
//draw_leaf(buf, area, 1, 10, "STOP");
//draw_leaf(buf, area, 1, 16, "PLAY/PAUSE");
//draw_leaf(buf, area, 1, 28, "START");
//draw_leaf(buf, area, 1, 35, "Project: Witty Gerbil - Sha Na Na ");
//draw_leaf(buf, area, 3, 0, &format!("BPM {:03}.{:03}",
//state.bpm as u64,
//((state.bpm % 1.0) * 1000.0) as u64
//));
//let position = state.transport.as_ref().map(|t|t.query());
//if let Some(Ok(position)) = position {
//let rate = position.pos.frame_rate().unwrap();
//let frame = position.pos.frame();
//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()
////);
//draw_leaf(buf, area, 3, 13, &format!("BBT {bars:04}:{beat:02}.{:02}",
//(beat_sub * 16.0) as u32
//));
//let time = frame as f64 / rate as f64;
//let seconds = time % 60.0;
//let msec = seconds % 1.0;
//let minutes = (time / 60.0) % 60.0;
//let hours = time / 3600.0;
//draw_leaf(buf, area, 3, 29, &format!("Time {:02}:{:02}:{:02}.{:03}",
//hours as u64,
//minutes as u64,
//seconds as u64,
//(msec * 1000.0) as u64
//));
//draw_leaf(buf, area, 3, 48, &format!("Rate {:>6}Hz", rate));
//draw_leaf(buf, area, 3, 63, &format!("Frame {:>10}", frame));
//}
//let bbt = position.pos.bbt().map(|mut bbt|*bbt //let bbt = position.pos.bbt().map(|mut bbt|*bbt
//.with_bpm(state.bpm) //.with_bpm(state.bpm)
//.with_timesig(state.timesig.0, state.timesig.1)); //.with_timesig(state.timesig.0, state.timesig.1));

View file

@ -18,6 +18,7 @@ impl Device for Rows {
match event { match event {
EngineEvent::Input(Event::Key(KeyEvent { code: KeyCode::Esc, .. })) => { EngineEvent::Input(Event::Key(KeyEvent { code: KeyCode::Esc, .. })) => {
self.focused = true; self.focused = true;
self.items[self.focus].handle(&EngineEvent::Blur)?;
Ok(()) Ok(())
}, },
_ => self.items[self.focus].handle(event) _ => self.items[self.focus].handle(event)
@ -39,9 +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)?;
}, },
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)?;
}, },
_ => { _ => {
println!("{event:?}"); println!("{event:?}");
@ -63,7 +66,7 @@ impl Device for Rows {
height: area.height - h height: area.height - h
})?; })?;
if self.focused && i == self.focus { if self.focused && i == self.focus {
draw_box_styled(buf, result, Some(Style::default().gray().bold().not_dim())) draw_box_styled(buf, result, Some(Style::default().green().dim().bold()))
} }
w = w.max(result.width); w = w.max(result.width);
h = h + result.height; h = h + result.height;

View file

@ -57,6 +57,6 @@ pub use crate::render::*;
pub use crate::device::{ pub use crate::device::{
Device, Device,
DynamicDevice, DynamicDevice,
EngineEvent EngineEvent,
}; };
pub type Usually<T> = Result<T, Box<dyn Error>>; pub type Usually<T> = Result<T, Box<dyn Error>>;