mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 20:26:42 +01:00
systematizing jack handlers
This commit is contained in:
parent
d627d257ad
commit
4ae62c5bc2
10 changed files with 342 additions and 208 deletions
|
|
@ -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) {
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue