mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
systematizing jack handlers
This commit is contained in:
parent
d627d257ad
commit
4ae62c5bc2
10 changed files with 342 additions and 208 deletions
|
|
@ -165,6 +165,10 @@ pub enum EngineEvent {
|
|||
Update,
|
||||
/// Update the whole form.
|
||||
Redraw,
|
||||
/// Device gains focus
|
||||
Focus,
|
||||
/// Device loses focus
|
||||
Blur,
|
||||
}
|
||||
|
||||
pub fn activate_jack_client <N: NotificationHandler + Sync + 'static> (
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
-> Usually<Rect>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
Ok(Rect::default())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
-> Usually<Rect>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use crate::prelude::*;
|
|||
|
||||
pub struct Mixer {
|
||||
name: String,
|
||||
jack: Jack<Notifications>,
|
||||
jack: Jack<MixerJack>,
|
||||
tracks: Vec<Track>,
|
||||
selected_track: usize,
|
||||
selected_column: usize,
|
||||
|
|
@ -14,14 +14,9 @@ impl Mixer {
|
|||
name,
|
||||
ClientOptions::NO_START_SERVER
|
||||
)?;
|
||||
let jack = client.activate_async(
|
||||
Notifications,
|
||||
ClosureProcessHandler::new(Box::new(
|
||||
move |_: &Client, _: &ProcessScope| -> Control {
|
||||
Control::Continue
|
||||
}) as Box<dyn FnMut(&Client, &ProcessScope)->Control + Send>
|
||||
)
|
||||
)?;
|
||||
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(),
|
||||
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)
|
||||
-> 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) {
|
||||
}
|
||||
|
||||
|
|
@ -254,7 +264,7 @@ impl NotificationHandler for Notifications {
|
|||
//fn handle (&mut self, engine: &mut TUI<W>) -> Result<Option<bool>> {
|
||||
//Ok(None)
|
||||
//}
|
||||
//}
|
||||
//
|
||||
|
||||
//impl<W: Write> Output<TUI<W>, [u16;2]> for Mixer {
|
||||
//fn render (&self, envine: &mut TUI<W>) -> Result<Option<[u16;2]>> {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ pub const ACTIONS: [(&'static str, &'static str);2] = [
|
|||
|
||||
pub struct Sampler {
|
||||
name: String,
|
||||
jack: Jack<self::Notifications>,
|
||||
jack: Jack<SamplerJack>,
|
||||
samples: Arc<Mutex<Vec<Sample>>>,
|
||||
selected_sample: usize,
|
||||
selected_column: usize,
|
||||
|
|
@ -23,57 +23,65 @@ impl Sampler {
|
|||
];
|
||||
let samples = Arc::new(Mutex::new(samples));
|
||||
let input = client.register_port("trigger", ::jack::MidiIn::default())?;
|
||||
let handler: BoxedProcessHandler = Box::new({
|
||||
let exited = exited.clone();
|
||||
let jack = SamplerJack.activate(client, ClosureProcessHandler::new({
|
||||
let exited = exited.clone();
|
||||
let samples = samples.clone();
|
||||
Box::new(move |_: &Client, scope: &ProcessScope| -> 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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(),
|
||||
selected_sample: 0,
|
||||
selected_column: 0,
|
||||
samples,
|
||||
jack: client.activate_async(
|
||||
self::Notifications,
|
||||
ClosureProcessHandler::new(handler)
|
||||
)?,
|
||||
jack,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
port: Port<AudioOut>,
|
||||
name: String,
|
||||
|
|
@ -217,9 +225,17 @@ pub fn handle (state: &mut Sampler, event: &EngineEvent) -> Result<(), Box<dyn E
|
|||
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) {
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use ratatui::style::Stylize;
|
|||
|
||||
pub struct Sequencer {
|
||||
name: String,
|
||||
jack: Jack<SequencerJack>,
|
||||
playing: Arc<AtomicBool>,
|
||||
recording: Arc<AtomicBool>,
|
||||
overdub: Arc<AtomicBool>,
|
||||
|
|
@ -10,9 +11,16 @@ pub struct Sequencer {
|
|||
outputs_open: Arc<AtomicBool>,
|
||||
cursor: (u16, u16, u16),
|
||||
timesig: (f32, f32),
|
||||
pub jack_client: Jack<self::Notifications>,
|
||||
sequence: Arc<Mutex<Vec<Vec<Option<SequencerEvent>>>>>,
|
||||
sequences: Arc<Mutex<Vec<MIDISequence>>>,
|
||||
mode: SequencerMode,
|
||||
}
|
||||
|
||||
pub enum SequencerMode {
|
||||
Tiny,
|
||||
Compact,
|
||||
Vertical,
|
||||
Horizontal,
|
||||
}
|
||||
|
||||
impl Sequencer {
|
||||
|
|
@ -31,15 +39,6 @@ impl Sequencer {
|
|||
let playing = Arc::new(AtomicBool::new(true));
|
||||
let recording = Arc::new(AtomicBool::new(true));
|
||||
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(
|
||||
Mutex::new(vec![vec![None;64];128])
|
||||
);
|
||||
|
|
@ -53,7 +52,17 @@ impl Sequencer {
|
|||
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(),
|
||||
playing: playing.clone(),
|
||||
recording: recording.clone(),
|
||||
|
|
@ -65,67 +74,70 @@ impl Sequencer {
|
|||
])),
|
||||
cursor: (11, 0, 0),
|
||||
timesig: (4.0, 4.0),
|
||||
jack_client: crate::device::activate_jack_client(
|
||||
client,
|
||||
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
|
||||
})
|
||||
)?
|
||||
jack: SequencerJack.activate(client, ClosureProcessHandler::new(
|
||||
Box::new(move |client: &Client, scope: &ProcessScope| process(client, scope)) as BoxedProcessHandler
|
||||
))?
|
||||
}))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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] = [
|
||||
("+/-", "Zoom"),
|
||||
("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>> {
|
||||
|
||||
if let EngineEvent::Focus = event {
|
||||
}
|
||||
|
||||
if let EngineEvent::Input(Event::Key(event)) = event {
|
||||
match event.code {
|
||||
KeyCode::Down => {
|
||||
|
|
@ -197,33 +212,38 @@ pub fn handle (state: &mut Sequencer, event: &EngineEvent) -> Result<(), Box<dyn
|
|||
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)
|
||||
-> Usually<Rect>
|
||||
{
|
||||
let style = Style::default().gray();
|
||||
let header = draw_sequencer_header(sequencer, buf, area)?;
|
||||
let piano = draw_sequencer_vertical(sequencer, buf, Rect {
|
||||
x: area.x,
|
||||
y: area.y + header.height,
|
||||
width: area.width,
|
||||
height: area.height - header.height
|
||||
})?;
|
||||
let area = Rect {
|
||||
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
|
||||
})?,
|
||||
_ => unimplemented!()
|
||||
};
|
||||
//draw_box_styled(buf, area, Some(Style::default().red()));
|
||||
Ok(Rect {
|
||||
x: area.x,
|
||||
y: area.y,
|
||||
width: header.width.max(piano.width),
|
||||
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> {
|
||||
|
|
@ -235,32 +255,52 @@ fn draw_sequencer_header (sequencer: &Sequencer, buf: &mut Buffer, area: Rect) -
|
|||
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> {
|
||||
let Rect { x, y, .. } = area;
|
||||
let white = Style::default().gray();
|
||||
let black = Style::default().black();
|
||||
for i in 0..area.width-4 {
|
||||
let color = match i % 12 {
|
||||
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);
|
||||
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 bg = Style::default().on_black();
|
||||
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 })
|
||||
}
|
||||
|
|
@ -373,6 +413,14 @@ fn draw_rec_dub_button (
|
|||
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,
|
||||
|
|
@ -470,9 +518,17 @@ fn draw_sequence_cursor (
|
|||
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) {
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -47,50 +47,74 @@ pub fn render (state: &Transport, buf: &mut Buffer, mut area: Rect)
|
|||
area.width = area.width.min(80);
|
||||
area.height = 5;
|
||||
//draw_box(buf, area);
|
||||
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 label = Style::default().white().not_dim();
|
||||
let border = Style::default().gray().dim();
|
||||
let mut x = 2;
|
||||
for button in [
|
||||
"PLAY",
|
||||
"STOP",
|
||||
"REC",
|
||||
"DUB",
|
||||
"0.0.00",
|
||||
"0:00.000",
|
||||
&format!("BPM {:03}.{:03}",
|
||||
state.bpm as u64,
|
||||
((state.bpm % 1.0) * 1000.0) as u64
|
||||
)
|
||||
].iter() {
|
||||
buf.set_string(area.x + x, area.y + 2, button, label);
|
||||
x = x + button.len() as u16 + 1;
|
||||
buf.set_string(area.x + x, area.y + 1, "│", border);
|
||||
buf.set_string(area.x + x, area.y + 2, "│", border);
|
||||
buf.set_string(area.x + x, area.y + 3, "│", border);
|
||||
x = x + 2;
|
||||
}
|
||||
//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
|
||||
//.with_bpm(state.bpm)
|
||||
//.with_timesig(state.timesig.0, state.timesig.1));
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ impl Device for Rows {
|
|||
match event {
|
||||
EngineEvent::Input(Event::Key(KeyEvent { code: KeyCode::Esc, .. })) => {
|
||||
self.focused = true;
|
||||
self.items[self.focus].handle(&EngineEvent::Blur)?;
|
||||
Ok(())
|
||||
},
|
||||
_ => self.items[self.focus].handle(event)
|
||||
|
|
@ -39,9 +40,11 @@ impl Device for Rows {
|
|||
},
|
||||
Event::Key(KeyEvent { code: KeyCode::Enter, .. }) => {
|
||||
self.focused = false;
|
||||
self.items[self.focus].handle(&EngineEvent::Focus)?;
|
||||
},
|
||||
Event::Key(KeyEvent { code: KeyCode::Esc, .. }) => {
|
||||
self.focused = true;
|
||||
self.items[self.focus].handle(&EngineEvent::Blur)?;
|
||||
},
|
||||
_ => {
|
||||
println!("{event:?}");
|
||||
|
|
@ -63,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().gray().bold().not_dim()))
|
||||
draw_box_styled(buf, result, Some(Style::default().green().dim().bold()))
|
||||
}
|
||||
w = w.max(result.width);
|
||||
h = h + result.height;
|
||||
|
|
|
|||
|
|
@ -57,6 +57,6 @@ pub use crate::render::*;
|
|||
pub use crate::device::{
|
||||
Device,
|
||||
DynamicDevice,
|
||||
EngineEvent
|
||||
EngineEvent,
|
||||
};
|
||||
pub type Usually<T> = Result<T, Box<dyn Error>>;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue