mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
panic hook; two sequencers
This commit is contained in:
parent
0ee9e58dc7
commit
abee6cc2c8
16 changed files with 927 additions and 828 deletions
426
src/sequencer.rs
Normal file
426
src/sequencer.rs
Normal file
|
|
@ -0,0 +1,426 @@
|
|||
use crate::prelude::*;
|
||||
use ratatui::style::Stylize;
|
||||
|
||||
pub const ACTIONS: [(&'static str, &'static str);4] = [
|
||||
("+/-", "Zoom"),
|
||||
("A/D", "Add/delete note"),
|
||||
("]/[", "Duration"),
|
||||
("CapsLock", "Auto advance"),
|
||||
];
|
||||
|
||||
pub struct Sequencer {
|
||||
exited: Arc<AtomicBool>,
|
||||
sequence: Arc<Mutex<Vec<Vec<Option<Event>>>>>,
|
||||
cursor: (u16, u16, u16),
|
||||
timesig: (f32, f32),
|
||||
pub jack_client: Jack<Notifications>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Event {
|
||||
NoteOn(u8, u8),
|
||||
NoteOff(u8)
|
||||
}
|
||||
|
||||
impl Sequencer {
|
||||
|
||||
pub fn new (name: Option<&str>) -> Result<Self, Box<dyn Error>> {
|
||||
let exited = Arc::new(AtomicBool::new(false));
|
||||
let (client, _status) = Client::new(name.unwrap_or("sequencer"), ClientOptions::NO_START_SERVER)?;
|
||||
let mut port = client.register_port("sequence", ::jack::MidiOut::default())?;
|
||||
let sequence: Arc<Mutex<Vec<Vec<Option<Event>>>>> = Arc::new(Mutex::new(vec![vec![None;64];128]));
|
||||
let beats = 4;
|
||||
let steps = 16;
|
||||
let bpm = 120.0;
|
||||
let rate = 44100; // Hz
|
||||
let frame = 1f64 / rate as f64; // msec
|
||||
let buf = 512; // frames
|
||||
let t_beat = 60.0 / bpm; // msec
|
||||
let t_loop = t_beat * beats as f64; // msec
|
||||
let t_step = t_beat / steps as f64; // msec
|
||||
let mut step_frames = vec![];
|
||||
for step in 0..beats*steps {
|
||||
let step_index = (step as f64 * t_step / frame) as usize;
|
||||
step_frames.push(step_index);
|
||||
}
|
||||
let loop_frames = (t_loop*rate as f64) as usize;
|
||||
let mut frame_steps: Vec<Option<usize>> = vec![None;loop_frames];
|
||||
for (index, frame) in step_frames.iter().enumerate() {
|
||||
frame_steps[*frame] = Some(index);
|
||||
}
|
||||
Ok(Self {
|
||||
exited: exited.clone(),
|
||||
sequence: sequence.clone(),
|
||||
cursor: (11, 0, 0),
|
||||
timesig: (4.0, 4.0),
|
||||
jack_client: crate::engine::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;
|
||||
let mut writer = port.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 {
|
||||
Event::NoteOn(pitch, velocity) => [
|
||||
0b10010000,
|
||||
*pitch,
|
||||
*velocity
|
||||
],
|
||||
Event::NoteOff(pitch) => [
|
||||
0b10000000,
|
||||
*pitch,
|
||||
0b00000000
|
||||
],
|
||||
}
|
||||
}).unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Control::Continue
|
||||
})
|
||||
)?
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl Exitable for Sequencer {
|
||||
fn exit (&mut self) {
|
||||
self.exited.store(true, Ordering::Relaxed)
|
||||
}
|
||||
fn exited (&self) -> bool {
|
||||
self.exited.fetch_and(true, Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
#[test]
|
||||
fn test_midi_frames () {
|
||||
let beats = 4;
|
||||
let steps = 16;
|
||||
let bpm = 120;
|
||||
let rate = 44100; // Hz
|
||||
let frame = 1f64 / rate as f64; // msec
|
||||
let buf = 512; // frames
|
||||
let t_beat = 60.0 / bpm as f64; // msec
|
||||
let t_loop = t_beat * beats as f64; // msec
|
||||
let t_step = t_beat / steps as f64; // msec
|
||||
|
||||
let assign = |chunk: usize| {
|
||||
let start = chunk * buf; // frames
|
||||
let end = (chunk + 1) * buf; // frames
|
||||
println!("{chunk}: {start} .. {end}");
|
||||
let mut steps: Vec<(usize, usize, f64)> = vec![];
|
||||
for frame_index in start..end {
|
||||
let frame_msec = frame_index as f64 * frame;
|
||||
let offset = (frame_msec * 1000.0) % (t_step * 1000.0);
|
||||
if offset < 0.1 {
|
||||
let time = frame_index - start;
|
||||
let step_index = (frame_msec % t_loop / t_step) as usize;
|
||||
println!("{chunk}: {frame_index} ({time}) -> {step_index} ({frame_msec} % {t_step} = {offset})");
|
||||
steps.push((time, step_index, offset));
|
||||
}
|
||||
}
|
||||
steps
|
||||
};
|
||||
|
||||
for chunk in 0..10 {
|
||||
let chunk = assign(chunk);
|
||||
//println!("{chunk} {:#?}", assign(chunk));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_midi_frames_2 () {
|
||||
let beats = 4;
|
||||
let steps = 16;
|
||||
let bpm = 120;
|
||||
let rate = 44100; // Hz
|
||||
let frame = 1f64 / rate as f64; // msec
|
||||
let buf = 512; // frames
|
||||
let t_beat = 60.0 / bpm as f64; // msec
|
||||
let t_loop = t_beat * beats as f64; // msec
|
||||
let t_step = t_beat / steps as f64; // msec
|
||||
let mut step_frames = vec![];
|
||||
for step in 0..beats*steps {
|
||||
let step_index = (step as f64 * t_step / frame) as usize;
|
||||
step_frames.push(step_index);
|
||||
}
|
||||
let loop_frames = (t_loop*rate as f64) as usize;
|
||||
let mut frame_steps: Vec<Option<usize>> = vec![None;loop_frames];
|
||||
for (index, frame) in step_frames.iter().enumerate() {
|
||||
println!("{index} {frame}");
|
||||
frame_steps[*frame] = Some(index);
|
||||
}
|
||||
let assign = |chunk: usize| {
|
||||
let (start, end) = (chunk * buf, (chunk + 1) * buf); // frames
|
||||
let (start_looped, end_looped) = (start % loop_frames, end % loop_frames);
|
||||
println!("{chunk}: {start} .. {end} ({start_looped} .. {end_looped})");
|
||||
let mut steps: Vec<Option<usize>> = vec![None;buf];
|
||||
for frame in 0..buf {
|
||||
let value = frame_steps[(start_looped + frame) as usize % loop_frames];
|
||||
if value.is_some() { println!("{frame:03} = {value:?}, ") };
|
||||
steps[frame as usize] = value;
|
||||
}
|
||||
steps
|
||||
};
|
||||
for chunk in 0..1000 {
|
||||
let chunk = assign(chunk);
|
||||
//println!("{chunk} {:#?}", assign(chunk));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HandleInput for Sequencer {
|
||||
fn handle (&mut self, event: &crate::engine::Event) -> Result<(), Box<dyn Error>> {
|
||||
handle(self, event)
|
||||
}
|
||||
}
|
||||
|
||||
fn handle (state: &mut Sequencer, event: &crate::engine::Event) -> Result<(), Box<dyn Error>> {
|
||||
|
||||
if let crate::engine::Event::Input(crossterm::event::Event::Key(event)) = event {
|
||||
|
||||
match event.code {
|
||||
KeyCode::Char('c') => {
|
||||
if event.modifiers == KeyModifiers::CONTROL {
|
||||
state.exit();
|
||||
}
|
||||
},
|
||||
KeyCode::Down => {
|
||||
state.cursor.0 = if state.cursor.0 >= 23 {
|
||||
0
|
||||
} else {
|
||||
state.cursor.0 + 1
|
||||
}
|
||||
},
|
||||
KeyCode::Up => {
|
||||
state.cursor.0 = if state.cursor.0 == 0 {
|
||||
23
|
||||
} else {
|
||||
state.cursor.0 - 1
|
||||
}
|
||||
},
|
||||
KeyCode::Left => {
|
||||
state.cursor.1 = if state.cursor.1 == 0 {
|
||||
63
|
||||
} else {
|
||||
state.cursor.1 - 1
|
||||
}
|
||||
},
|
||||
KeyCode::Right => {
|
||||
state.cursor.1 = if state.cursor.1 == 63 {
|
||||
0
|
||||
} else {
|
||||
state.cursor.1 + 1
|
||||
}
|
||||
},
|
||||
KeyCode::Char('[') => {
|
||||
if state.cursor.2 > 0 {
|
||||
state.cursor.2 = state.cursor.2 - 1
|
||||
}
|
||||
},
|
||||
KeyCode::Char(']') => {
|
||||
state.cursor.2 = state.cursor.2 + 1
|
||||
},
|
||||
KeyCode::Char('a') => {
|
||||
let row = state.cursor.0 as usize;
|
||||
let step = state.cursor.1 as usize;
|
||||
let duration = state.cursor.2 as usize;
|
||||
let mut sequence = state.sequence.lock().unwrap();
|
||||
sequence[row][step] = Some(self::Event::NoteOn(48 - row as u8, 128));
|
||||
if state.cursor.2 > 0 {
|
||||
sequence[row][step + duration] = Some(self::Event::NoteOff(35));
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
println!("{event:?}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const NOTE_NAMES: [&'static str;12] = [
|
||||
"C ", "C#", "D ", "D#", "E ", "F ", "F#", "G ", "G#", "A ", "A#", "B ",
|
||||
];
|
||||
|
||||
const KEYS: [&'static str; 6] = [
|
||||
"▀", "▀", "▀", "█", "▄", "▄",
|
||||
];
|
||||
|
||||
impl WidgetRef for Sequencer {
|
||||
fn render_ref (&self, area: Rect, buf: &mut Buffer) {
|
||||
draw_box(buf, area);
|
||||
draw_leaf(buf, area, 1, 0, "Name: Melody#000");
|
||||
draw_leaf(buf, area, 3, 0, "Output: None");
|
||||
draw_leaf(buf, area, 5, 0, "Input: None");
|
||||
draw_leaf(buf, area, 7, 0, "Channel: 01");
|
||||
draw_leaf(buf, area, 9, 0, "Zoom: 1/64");
|
||||
draw_leaf(buf, area, 11, 0, "Rate: 1/1");
|
||||
let mut area = area.inner(&Margin { horizontal: 1, vertical: 3, });
|
||||
area.x = area.x + 14;
|
||||
draw_sequence_keys(area, buf, &self.jack_client.as_client().transport().query().unwrap(), &self.sequence);
|
||||
draw_sequence_header(area, buf);
|
||||
draw_sequence_cursor(area, buf, self.cursor);
|
||||
//╭{}╮
|
||||
//faul
|
||||
|
||||
//ring
|
||||
//rea.
|
||||
//╰{}╯
|
||||
let cursor = self.cursor;
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_sequence_header (
|
||||
area: Rect, buf: &mut Buffer
|
||||
) {
|
||||
buf.set_string(area.x + 3, area.y, "|1.1.", Style::default().dim());
|
||||
buf.set_string(area.x + 3 + 16, area.y, "|1.2.", Style::default().dim());
|
||||
buf.set_string(area.x + 3 + 32, area.y, "|1.3.", Style::default().dim());
|
||||
buf.set_string(area.x + 3 + 48, area.y, "|1.4.", Style::default().dim());
|
||||
}
|
||||
|
||||
fn draw_sequence_keys (
|
||||
area: Rect,
|
||||
buf: &mut Buffer,
|
||||
transport: &::jack::TransportStatePosition,
|
||||
sequence: &Arc<Mutex<Vec<Vec<Option<self::Event>>>>>
|
||||
) {
|
||||
buf.set_string(area.x + 2, area.y, "╭", Style::default().black());
|
||||
buf.set_string(area.x + 2, area.y + 13, "╰", Style::default().black());
|
||||
buf.set_string(area.x + 2 + 65, area.y, "╮", Style::default().black());
|
||||
buf.set_string(area.x + 2 + 65, area.y + 13, "╯", Style::default().black());
|
||||
//let transport = state.transport.query()?;
|
||||
let frame = transport.pos.frame();
|
||||
let rate = transport.pos.frame_rate().unwrap();
|
||||
let second = (frame as f64) / (rate as f64);
|
||||
let minute = second / 60f64;
|
||||
let bpm = 120f64;
|
||||
let div = 4;
|
||||
let beats = minute * bpm;
|
||||
let bars = beats as u32 / div as u32;
|
||||
let beat = beats as u32 % div as u32 + 1;
|
||||
let beat_sub = beats % 1.0;
|
||||
|
||||
let sequence = sequence.lock().unwrap();
|
||||
|
||||
for key in 0..12 {
|
||||
buf.set_string(area.x, area.y + 1 + key, KEYS[(key % 6) as usize],
|
||||
Style::default().black());
|
||||
buf.set_string(area.x + 1, area.y + 1 + key, "█",
|
||||
Style::default().black());
|
||||
for step in 0..64 {
|
||||
let bg = if step as u32 == (beat - 1) * 16 + (beat_sub * 16.0) as u32 {
|
||||
ratatui::style::Color::Black
|
||||
} else {
|
||||
ratatui::style::Color::Reset
|
||||
};
|
||||
let top = sequence[(key * 2) as usize][step].is_some();
|
||||
let bottom = sequence[(key * 2 + 1) as usize][step].is_some();
|
||||
match (top, bottom) {
|
||||
(true, true) => {
|
||||
buf.set_string(area.x + 3 + step as u16, area.y + 1 + key, "█",
|
||||
Style::default().yellow().bold().bg(bg));
|
||||
},
|
||||
(true, false) => {
|
||||
buf.set_string(area.x + 3 + step as u16, area.y + 1 + key, "▀",
|
||||
Style::default().yellow().bold().bg(bg));
|
||||
},
|
||||
(false, true) => {
|
||||
buf.set_string(area.x + 3 + step as u16, area.y + 1 + key, "▄",
|
||||
Style::default().yellow().bold().bg(bg));
|
||||
},
|
||||
(false, false) => if step % 16 == 0 {
|
||||
buf.set_string(area.x + 3 + step as u16, area.y + 1 + key, "┊",
|
||||
Style::default().black().dim().bg(bg))
|
||||
} else {
|
||||
buf.set_string(area.x + 3 + step as u16, area.y + 1 + key, "·",
|
||||
Style::default().black().dim().bg(bg))
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for step in 0..64 {
|
||||
if step % 8 == 0 {
|
||||
buf.set_string(area.x + 3 + step as u16, area.y + 1 + 12, [
|
||||
"|A", "|B", "|C", "|D", "|E", "|F", "|G", "|H"
|
||||
][step / 8 as usize], Style::default().dim())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_sequence_cursor (
|
||||
area: Rect, buf: &mut Buffer, cursor: (u16, u16, u16)
|
||||
) {
|
||||
let cursor_character = match cursor.0 % 2 {
|
||||
0 => "▀",
|
||||
1 => "▄",
|
||||
_ => unreachable!()
|
||||
};
|
||||
let cursor_y = cursor.0 / 2;
|
||||
buf.set_string(area.x + cursor.1 + 3, area.y + 1 + cursor_y, if cursor.2 == 0 {
|
||||
cursor_character.into()
|
||||
} else {
|
||||
cursor_character.repeat(cursor.2 as usize)
|
||||
}, Style::default().yellow());
|
||||
}
|
||||
|
||||
pub struct Notifications;
|
||||
|
||||
impl NotificationHandler for Notifications {
|
||||
fn thread_init (&self, _: &Client) {
|
||||
}
|
||||
|
||||
fn shutdown (&mut self, status: ClientStatus, reason: &str) {
|
||||
}
|
||||
|
||||
fn freewheel (&mut self, _: &Client, is_enabled: bool) {
|
||||
}
|
||||
|
||||
fn sample_rate (&mut self, _: &Client, _: Frames) -> Control {
|
||||
Control::Quit
|
||||
}
|
||||
|
||||
fn client_registration (&mut self, _: &Client, name: &str, is_reg: bool) {
|
||||
}
|
||||
|
||||
fn port_registration (&mut self, _: &Client, port_id: PortId, is_reg: bool) {
|
||||
}
|
||||
|
||||
fn port_rename (&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control {
|
||||
Control::Continue
|
||||
}
|
||||
|
||||
fn ports_connected (&mut self, _: &Client, id_a: PortId, id_b: PortId, are: bool) {
|
||||
}
|
||||
|
||||
fn graph_reorder (&mut self, _: &Client) -> Control {
|
||||
Control::Continue
|
||||
}
|
||||
|
||||
fn xrun (&mut self, _: &Client) -> Control {
|
||||
Control::Continue
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue