mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +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,
|
||||||
/// 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> (
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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]>> {
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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>>;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue