mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
703 lines
25 KiB
Rust
703 lines
25 KiB
Rust
use crate::prelude::*;
|
|
use ratatui::style::Stylize;
|
|
|
|
pub struct Sequencer {
|
|
name: String,
|
|
jack: Jack<SequencerJack>,
|
|
playing: Arc<AtomicBool>,
|
|
recording: Arc<AtomicBool>,
|
|
overdub: Arc<AtomicBool>,
|
|
inputs_open: Arc<AtomicBool>,
|
|
outputs_open: Arc<AtomicBool>,
|
|
cursor: (u16, u16, u16),
|
|
timesig: (f32, f32),
|
|
sequence: Arc<Mutex<Vec<Vec<Option<SequencerEvent>>>>>,
|
|
sequences: Arc<Mutex<Vec<MIDISequence>>>,
|
|
mode: SequencerMode,
|
|
}
|
|
|
|
pub enum SequencerMode {
|
|
Tiny,
|
|
Compact,
|
|
Vertical,
|
|
Horizontal,
|
|
}
|
|
|
|
impl Sequencer {
|
|
|
|
pub fn new (name: &str) -> Result<DynamicDevice<Self>, Box<dyn Error>> {
|
|
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 exited = Arc::new(AtomicBool::new(false));
|
|
let playing = Arc::new(AtomicBool::new(true));
|
|
let recording = Arc::new(AtomicBool::new(true));
|
|
let overdub = Arc::new(AtomicBool::new(false));
|
|
let sequence: Arc<Mutex<Vec<Vec<Option<SequencerEvent>>>>> = Arc::new(
|
|
Mutex::new(vec![vec![None;64];128])
|
|
);
|
|
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);
|
|
}
|
|
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(),
|
|
overdub: overdub.clone(),
|
|
sequence: sequence.clone(),
|
|
inputs_open: Arc::new(AtomicBool::new(false)),
|
|
outputs_open: Arc::new(AtomicBool::new(false)),
|
|
sequences: Arc::new(Mutex::new(vec![
|
|
])),
|
|
cursor: (11, 0, 0),
|
|
timesig: (4.0, 4.0),
|
|
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"),
|
|
("]/[", "Duration"),
|
|
("CapsLock", "Auto advance"),
|
|
];
|
|
|
|
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 => {
|
|
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('i') => {
|
|
state.inputs_open.fetch_xor(true, Ordering::Relaxed);
|
|
},
|
|
KeyCode::Char('o') => {
|
|
state.outputs_open.fetch_xor(true, Ordering::Relaxed);
|
|
},
|
|
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(SequencerEvent::NoteOn(48 - row as u8, 128));
|
|
if state.cursor.2 > 0 {
|
|
sequence[row][step + duration] = Some(SequencerEvent::NoteOff(35));
|
|
}
|
|
},
|
|
_ => {
|
|
println!("{event:?}");
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn render (sequencer: &Sequencer, buf: &mut Buffer, mut area: Rect)
|
|
-> Usually<Rect>
|
|
{
|
|
let header = draw_sequencer_header(sequencer, buf, area)?;
|
|
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
|
|
})
|
|
}
|
|
|
|
fn draw_sequencer_header (sequencer: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
|
let Rect { x, y, width, height } = area;
|
|
let style = Style::default().gray();
|
|
buf.set_string(x + 1, y + 1, &format!(" │ 00:00.00 / 00:00.00"), style);
|
|
buf.set_string(x + 2, y + 1, &format!("{}", &sequencer.name), style.white().bold());
|
|
buf.set_string(x + 1, y + 2, &format!(" ▶ PLAY │ ⏹ STOP │ ⏺ REC │ ⏺ DUB "), style);
|
|
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;
|
|
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 + 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 })
|
|
}
|
|
|
|
//{
|
|
//let mut area = area.clone();
|
|
//area.y = area.y + 3;
|
|
//area.x = area.x + 1;
|
|
//area.height = area.height - 2;
|
|
//draw_sequence_keys(area, buf,
|
|
//&sequencer.jack_client.as_client().transport().query().unwrap(),
|
|
//&sequencer.sequence,
|
|
//&sequencer.cursor);
|
|
//draw_sequence_cursor(area, buf,
|
|
//&sequencer.cursor);
|
|
//}
|
|
//draw_box(buf, Rect { x, y, width: 18, height });
|
|
|
|
//draw_box(buf, Rect { x, y, width: 40, height: 8 });
|
|
//buf.set_string(x + 1, y + 3,
|
|
//&format!(" │ INPUT │ OUTPUT │"),
|
|
//Style::default().gray());
|
|
//buf.set_string(x + 1, y + 5,
|
|
//&format!(" {} │ IN │ ⏺ REC │ ⏺ DUB │ ▶ PLAY │ ⏹ STOP │ OUT │ 00:00.00 / 00:00.00", &sequencer.name),
|
|
//Style::default().gray());
|
|
//if sequencer.inputs_open.fetch_and(true, Ordering::Relaxed) {
|
|
//buf.set_string(x + 15, y + 1, "IN", Style::default().green().bold());
|
|
//}
|
|
//if sequencer.outputs_open.fetch_and(true, Ordering::Relaxed) {
|
|
//buf.set_string(x + 54, y + 1, "OUT", Style::default().green().bold());
|
|
//}
|
|
|
|
//let mut command = |y2: u16, c: &str, ommand: &str, value: &str| {
|
|
//buf.set_string(x + 1, y + y2, c, Style::default().bold());
|
|
//buf.set_string(x + 2, y + y2, ommand, Style::default().dim());
|
|
//buf.set_string(x + 4 + ommand.len() as u16, y + y2, value, Style::default().bold());
|
|
//};
|
|
//for (y, c, ommand, value) in [
|
|
////(5, "I", "nputs", "[+]"),
|
|
////(6, "O", "utputs", "[+]"),
|
|
////(7, "C", "hannel", "01"),
|
|
//(8, "G", "rid", "1/16"),
|
|
//(9, "Z", "oom", "1/64"),
|
|
//(10, "R", "ate", "1/1"),
|
|
//(11, "S", "ync", "1 bar"),
|
|
//(12, "A", "dd note", ""),
|
|
//(13, "D", "elete note", ""),
|
|
//] {
|
|
//command(y, c, ommand, value)
|
|
//}
|
|
//draw_box(buf, Rect { x, y: y + height - 1, width, height: 3 });
|
|
//buf.set_string(x + 1, y + height,
|
|
//&format!(" Grid 1/16 │ Zoom 1/64 │ Rate 1/1 │ Sync 1 bar │ Add note │ Delete note "),
|
|
//Style::default().gray());
|
|
//buf.set_string(x + 1, y + 1, &format!(" ▶ {} ", &sequencer.name),
|
|
//Style::default().white().bold().not_dim());
|
|
//buf.set_string(x + 4, y + 0, "┬", Style::default().gray());
|
|
//buf.set_string(x + 4, y + 1, "│", Style::default().gray().dim());
|
|
//buf.set_string(x + 4, y + 2, "┴", Style::default().gray());
|
|
//buf.set_string(x + 21, y + 1, " ⏺ REC DUB ",
|
|
//Style::default().white().bold().not_dim());
|
|
//buf.set_string(x + 18, y + 0, "┬", Style::default().gray());
|
|
//buf.set_string(x + 18, y + 1, "│", Style::default().gray().dim());
|
|
//buf.set_string(x + 18, y + 2, "┴", Style::default().gray());
|
|
|
|
//draw_rec_dub_button(buf, x + 17, y);
|
|
//draw_sequence_button(buf, x, y, &sequencer.name);
|
|
|
|
//draw_leaf(buf, Rect { x, y: y + height, width, height: 2 }, 0, 0, "Inputs ");
|
|
//draw_leaf(buf, Rect { x, y: y + height, width, height: 2 }, 0, 8, "Outputs");
|
|
//for x2 in 0..4 {
|
|
//for y2 in 0..4 {
|
|
//buf.set_string(
|
|
//x + 2 + x2 * 3,
|
|
//y + 5 + y2,
|
|
//format!("{:>02} ", 1 + x2 + y2 * 4),
|
|
//Style::default().green().bold().not_dim()
|
|
//)
|
|
//}
|
|
//}
|
|
//
|
|
|
|
fn draw_sequence_button (
|
|
buf: &mut Buffer,
|
|
x: u16,
|
|
y: u16,
|
|
name: &str
|
|
) {
|
|
draw_box(buf, Rect { x, y, width: 18, height: 3 });
|
|
buf.set_string(x + 1, y + 1, &format!(" ▶ {} ", name),
|
|
Style::default().white().bold().not_dim());
|
|
buf.set_string(x + 4, y + 0, "┬", Style::default().gray());
|
|
buf.set_string(x + 4, y + 1, "│", Style::default().gray().dim());
|
|
buf.set_string(x + 4, y + 2, "┴", Style::default().gray());
|
|
}
|
|
|
|
fn draw_rec_dub_button (
|
|
buf: &mut Buffer,
|
|
x: u16,
|
|
y: u16,
|
|
) {
|
|
draw_box(buf, Rect { x, y, width: 18, height: 3 });
|
|
buf.set_string(x + 1, y + 1, " ⏺ REC DUB ",
|
|
Style::default().white().bold().not_dim());
|
|
buf.set_string(x + 5, y + 0, "┬", Style::default().gray());
|
|
buf.set_string(x + 5, y + 1, "│", Style::default().gray().dim());
|
|
buf.set_string(x + 5, y + 2, "┴", Style::default().gray());
|
|
buf.set_string(x + 11, y + 0, "┬", Style::default().gray());
|
|
buf.set_string(x + 11, y + 1, "│", Style::default().gray().dim());
|
|
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,
|
|
transport: &::jack::TransportStatePosition,
|
|
sequence: &Arc<Mutex<Vec<Vec<Option<self::Event>>>>>,
|
|
cursor: &(u16, u16, u16)
|
|
) {
|
|
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());
|
|
//buf.set_string(area.x + 2, area.y, "╭", Style::default().dim());
|
|
//buf.set_string(area.x + 2, area.y + 13, "╰", Style::default().dim());
|
|
buf.set_string(area.x + 2 + 65, area.y, "╮", Style::default().dim());
|
|
buf.set_string(area.x + 2 + 65, area.y + 13, "╯", Style::default().dim());
|
|
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;
|
|
//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()
|
|
//);
|
|
let sequence = sequence.lock().unwrap();
|
|
for key in 0..12 {
|
|
buf.set_string(area.x, area.y + 1 + key, KEYS_VERTICAL[(key % 6) as usize],
|
|
Style::default().dim());
|
|
buf.set_string(area.x + 1, area.y + 1 + key, "█",
|
|
Style::default().dim());
|
|
for step in 0..64 {
|
|
let bg = if step as u32 == (beat - 1) * 16 + (beat_sub * 16.0) as u32 {
|
|
ratatui::style::Color::Gray
|
|
} else if step == cursor.1 as usize || key == cursor.0/2 {
|
|
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().not_dim().bold().bg(bg));
|
|
},
|
|
(true, false) => {
|
|
buf.set_string(area.x + 3 + step as u16, area.y + 1 + key, "▀",
|
|
Style::default().yellow().not_dim().bold().bg(bg));
|
|
},
|
|
(false, true) => {
|
|
buf.set_string(area.x + 3 + step as u16, area.y + 1 + key, "▄",
|
|
Style::default().yellow().not_dim().bold().bg(bg));
|
|
},
|
|
(false, false) => if step % 16 == 0 {
|
|
buf.set_string(area.x + 3 + step as u16, area.y + 1 + key, "┊",
|
|
Style::default().dim().bg(bg))
|
|
} else {
|
|
buf.set_string(area.x + 3 + step as u16, area.y + 1 + key, "·",
|
|
Style::default().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;
|
|
let cursor_text = if cursor.2 == 0 {
|
|
cursor_character.into()
|
|
} else {
|
|
cursor_character.repeat(cursor.2 as usize)
|
|
};
|
|
let style = Style::default().yellow().dim();
|
|
buf.set_string(area.x + cursor.1 + 3, area.y + 1 + cursor_y, cursor_text, style);
|
|
}
|
|
|
|
pub struct SequencerJack;
|
|
|
|
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 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
|
|
}
|
|
}
|
|
|
|
pub struct MIDISequence {
|
|
channels: [MIDISequenceChannel;16],
|
|
}
|
|
|
|
pub struct MIDISequenceChannel {
|
|
number: u8,
|
|
notes: [MIDISequenceVoice;128],
|
|
}
|
|
|
|
pub type MIDISequenceVoice = std::collections::BTreeMap<u32, NoteEvent>;
|
|
|
|
#[derive(Clone)]
|
|
pub enum NoteEvent {
|
|
On(u8),
|
|
Off(u8),
|
|
}
|
|
|
|
const VOICE_EMPTY: MIDISequenceVoice = MIDISequenceVoice::new();
|
|
|
|
impl MIDISequence {
|
|
fn new () -> Self {
|
|
Self {
|
|
channels: [
|
|
MIDISequenceChannel::new(1),
|
|
MIDISequenceChannel::new(2),
|
|
MIDISequenceChannel::new(3),
|
|
MIDISequenceChannel::new(4),
|
|
MIDISequenceChannel::new(5),
|
|
MIDISequenceChannel::new(6),
|
|
MIDISequenceChannel::new(7),
|
|
MIDISequenceChannel::new(8),
|
|
MIDISequenceChannel::new(9),
|
|
MIDISequenceChannel::new(10),
|
|
MIDISequenceChannel::new(11),
|
|
MIDISequenceChannel::new(12),
|
|
MIDISequenceChannel::new(13),
|
|
MIDISequenceChannel::new(14),
|
|
MIDISequenceChannel::new(15),
|
|
MIDISequenceChannel::new(16),
|
|
]
|
|
}
|
|
}
|
|
}
|
|
|
|
impl MIDISequenceChannel {
|
|
fn new (number: u8) -> Self {
|
|
Self {
|
|
number,
|
|
notes: [VOICE_EMPTY;128]
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub enum SequencerEvent {
|
|
NoteOn(u8, u8),
|
|
NoteOff(u8)
|
|
}
|
|
|
|
#[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));
|
|
}
|
|
}
|
|
}
|