mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
record into sequencer from midi in!
This commit is contained in:
parent
2e7252f8b9
commit
2118615aea
2 changed files with 94 additions and 53 deletions
|
|
@ -1,6 +1,8 @@
|
|||
use crate::prelude::*;
|
||||
use ratatui::style::Stylize;
|
||||
|
||||
type Sequence = std::collections::BTreeMap<u32, Vec<::midly::MidiMessage>>;
|
||||
|
||||
pub struct Sequencer {
|
||||
name: String,
|
||||
mode: SequencerView,
|
||||
|
|
@ -11,7 +13,7 @@ pub struct Sequencer {
|
|||
rate: Hz,
|
||||
tempo: Tempo,
|
||||
transport: ::jack::Transport,
|
||||
sequence: std::collections::BTreeMap<u32, Vec<Vec<u8>>>,
|
||||
sequence: Sequence,
|
||||
ppq: u32,
|
||||
input_port: Port<MidiIn>,
|
||||
input_connect: Vec<String>,
|
||||
|
|
@ -64,14 +66,33 @@ pub fn process (state: &mut Sequencer, client: &Client, scope: &ProcessScope) ->
|
|||
}
|
||||
|
||||
fn process_in (state: &mut Sequencer, scope: &ProcessScope) {
|
||||
let pos = state.transport.query().unwrap().pos;
|
||||
let frame = pos.frame();
|
||||
let rate = pos.frame_rate().unwrap();
|
||||
let usecs = Frame(frame).to_usec(&Hz(rate)).0 as u64;
|
||||
let usec_per_beat = state.tempo.usec_per_beat().0 as u64;
|
||||
let usec_per_tick = state.tempo.usec_per_tick(state.ppq).0 as u64;
|
||||
let beats = usecs / usec_per_beat;
|
||||
let time = beats as u32 * state.ppq;
|
||||
|
||||
for event in state.input_port.iter(scope) {
|
||||
match midly::live::LiveEvent::parse(event.bytes).unwrap() {
|
||||
midly::live::LiveEvent::Midi { channel, message } => match message {
|
||||
midly::MidiMessage::NoteOn { key, vel } => {
|
||||
state.notes_on[key.as_int() as usize] = true
|
||||
state.notes_on[key.as_int() as usize] = true;
|
||||
if state.sequence.contains_key(&time) {
|
||||
state.sequence.get_mut(&time).unwrap().push(message.clone());
|
||||
} else {
|
||||
state.sequence.insert(time, vec![message.clone()]);
|
||||
}
|
||||
},
|
||||
midly::MidiMessage::NoteOff { key, vel } => {
|
||||
state.notes_on[key.as_int() as usize] = false
|
||||
state.notes_on[key.as_int() as usize] = false;
|
||||
if state.sequence.contains_key(&time) {
|
||||
state.sequence.get_mut(&time).unwrap().push(message.clone());
|
||||
} else {
|
||||
state.sequence.insert(time, vec![message.clone()]);
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
},
|
||||
|
|
@ -152,13 +173,14 @@ const COMMANDS: [(KeyCode, &'static str, &'static str, &'static dyn Fn(&mut Sequ
|
|||
fn nop (_: &mut Sequencer) {
|
||||
}
|
||||
fn note_add (s: &mut Sequencer) {
|
||||
let time = (s.time_axis.0 + s.time_cursor) as u32;
|
||||
let time = (s.time_axis.0 + s.time_cursor) as u32;
|
||||
let time_start = time * s.ppq;
|
||||
let time_end = (time + 1) * s.ppq;
|
||||
let note_on = vec![0, 0, 0];
|
||||
let note_off = vec![0, 0, 0];
|
||||
let key = ::midly::num::u7::from_int_lossy((s.note_cursor + s.note_axis.0) as u8);
|
||||
let note_on = ::midly::MidiMessage::NoteOn { key, vel: 100.into() };
|
||||
let note_off = ::midly::MidiMessage::NoteOff { key, vel: 100.into() };
|
||||
if s.sequence.contains_key(&time_start) {
|
||||
s.sequence.get_mut(&time_start).unwrap().push(note_off.clone());
|
||||
s.sequence.get_mut(&time_start).unwrap().push(note_on.clone());
|
||||
} else {
|
||||
s.sequence.insert(time_start, vec![note_on]);
|
||||
}
|
||||
|
|
@ -234,7 +256,17 @@ fn render (sequencer: &Sequencer, buf: &mut Buffer, mut area: Rect)
|
|||
let Rect { x, y, width, height } = area;
|
||||
let (time0, time1) = sequencer.time_axis;
|
||||
let (note0, note1) = sequencer.note_axis;
|
||||
let header = draw_sequencer_header(sequencer, buf, area)?;
|
||||
let pos = sequencer.transport.query().unwrap().pos;
|
||||
let frame = pos.frame();
|
||||
let rate = pos.frame_rate().unwrap();
|
||||
let usecs = Frame(frame).to_usec(&Hz(rate)).0 as u64;
|
||||
let usec_per_beat = sequencer.tempo.usec_per_beat().0 as u64;
|
||||
let usec_per_tick = sequencer.tempo.usec_per_tick(sequencer.ppq).0 as u64;
|
||||
let beats = usecs / usec_per_beat;
|
||||
let bar = beats / 4;
|
||||
let beat = beats % 4;
|
||||
let tick = (usecs % usec_per_beat) / usec_per_tick;
|
||||
let header = draw_sequencer_header(sequencer, buf, area, bar, beat, tick)?;
|
||||
let piano = match sequencer.mode {
|
||||
SequencerView::Tiny =>
|
||||
Rect { x, y, width, height: 0 },
|
||||
|
|
@ -245,7 +277,7 @@ fn render (sequencer: &Sequencer, buf: &mut Buffer, mut area: Rect)
|
|||
y: y + header.height,
|
||||
width: 3 + note1 - note0,
|
||||
height: 3 + time1 - time0,
|
||||
})?,
|
||||
}, beats)?,
|
||||
SequencerView::Horizontal => draw_sequencer_horizontal(sequencer, buf, Rect {
|
||||
x,
|
||||
y: y + header.height,
|
||||
|
|
@ -261,10 +293,10 @@ fn render (sequencer: &Sequencer, buf: &mut Buffer, mut area: Rect)
|
|||
}))
|
||||
}
|
||||
|
||||
fn draw_sequencer_header (sequencer: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
|
||||
fn draw_sequencer_header (sequencer: &Sequencer, buf: &mut Buffer, area: Rect, bar: u64, beat: u64, tick: u64) -> 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 + 1, y + 1, &format!(" │ {bar}.{beat}.{tick:03}"), style);
|
||||
buf.set_string(x + 2, y + 1, &format!("{}", &sequencer.name), style.white().bold());
|
||||
buf.set_string(x + 1, y + 2, &format!(" ▶ PLAY │ ⏹ STOP │ │"), style);
|
||||
buf.set_string(x + 2, y + 2, &format!("▶ PLAY"), if sequencer.playing {
|
||||
|
|
@ -306,8 +338,9 @@ const KEY_HORIZONTAL_STYLE: [Style;12] = [
|
|||
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, beat: u64) -> Usually<Rect> {
|
||||
let Rect { x, y, .. } = area;
|
||||
let transport = sequencer.transport.query().unwrap();
|
||||
let (time0, time1) = sequencer.time_axis;
|
||||
let (note0, note1) = sequencer.note_axis;
|
||||
let bw = Style::default().dim();
|
||||
|
|
@ -327,41 +360,33 @@ fn draw_sequencer_vertical (sequencer: &Sequencer, buf: &mut Buffer, area: Rect)
|
|||
}
|
||||
for step in time0..time1 {
|
||||
let y = y - time0 + step / 2;
|
||||
buf.set_string(x + 5, y, &" ".repeat(32.max(note1-note0)as usize), bg);
|
||||
//buf.set_string(x + 5, y, &" ".repeat(32.max(note1-note0)as usize), bg);
|
||||
if step % 8 == 0 {
|
||||
buf.set_string(x + 2, y, &format!("{:2} ", step + 1), Style::default());
|
||||
}
|
||||
if step % 2 == 0 {
|
||||
let mut has_1 = false;
|
||||
let (start, end) = (
|
||||
(step + 0) as u32 * sequencer.ppq,
|
||||
(step + 1) as u32 * sequencer.ppq);
|
||||
for (i, (t, events)) in sequencer.sequence.range(start..end).enumerate() {
|
||||
if events.len() > 0 {
|
||||
has_1 = true;
|
||||
break
|
||||
}
|
||||
}
|
||||
let mut has_2 = false;
|
||||
let (start, end) = (
|
||||
(step + 1) as u32 * sequencer.ppq,
|
||||
(step + 2) as u32 * sequencer.ppq
|
||||
);
|
||||
for (i, (t, events)) in sequencer.sequence.range(start..end).enumerate() {
|
||||
if events.len() > 0 {
|
||||
has_2 = true;
|
||||
break
|
||||
}
|
||||
}
|
||||
if has_1 || has_2 {
|
||||
buf.set_string(x + 4, y, &match (has_1, has_2) {
|
||||
(true, true) => "█",
|
||||
(true, false) => "▀",
|
||||
(false, true) => "▄",
|
||||
_ => " ",
|
||||
}, Style::default());
|
||||
for k in note0..note1.max(note0+32) {
|
||||
let key = ::midly::num::u7::from_int_lossy(k as u8);
|
||||
if step % 2 == 0 {
|
||||
let (a, b, c) = (
|
||||
(step + 0) as u32 * sequencer.ppq,
|
||||
(step + 1) as u32 * sequencer.ppq,
|
||||
(step + 2) as u32 * sequencer.ppq,
|
||||
);
|
||||
let (character, style) = match (
|
||||
contains_note_on(&sequencer.sequence, key, a, b),
|
||||
contains_note_on(&sequencer.sequence, key, b, c),
|
||||
) {
|
||||
(true, true) => ("█", bg),
|
||||
(true, false) => ("▀", bg),
|
||||
(false, true) => ("▄", bg),
|
||||
(false, false) => (" ", bg),
|
||||
};
|
||||
buf.set_string(x + 5 + k - note0, y, character, style);
|
||||
}
|
||||
}
|
||||
if beat == step as u64 {
|
||||
buf.set_string(x + 1, y, if beat % 2 == 0 { "▀" } else { "▄" }, Style::default().yellow());
|
||||
}
|
||||
}
|
||||
buf.set_string(
|
||||
x + 5 + sequencer.note_cursor,
|
||||
|
|
@ -372,6 +397,22 @@ fn draw_sequencer_vertical (sequencer: &Sequencer, buf: &mut Buffer, area: Rect)
|
|||
Ok(Rect { x, y, width: area.width, height: (time1-time0)/2 })
|
||||
}
|
||||
|
||||
fn contains_note_on (sequence: &Sequence, k: ::midly::num::u7, start: u32, end: u32) -> bool {
|
||||
for (i, (t, events)) in sequence.range(start..end).enumerate() {
|
||||
for event in events.iter() {
|
||||
match event {
|
||||
::midly::MidiMessage::NoteOn {key,..} => {
|
||||
if *key == k {
|
||||
return true
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const KEYS_VERTICAL: [&'static str; 6] = [
|
||||
"▀", "▀", "▀", "█", "▄", "▄",
|
||||
];
|
||||
|
|
|
|||
22
src/time.rs
22
src/time.rs
|
|
@ -5,7 +5,7 @@ pub struct Hz(pub u32);
|
|||
pub struct Frame(pub u32);
|
||||
|
||||
/// One microsecond.
|
||||
pub struct Usec(pub u32);
|
||||
pub struct Usec(pub u64);
|
||||
|
||||
/// Beats per minute as `120000` = 120.000BPM
|
||||
pub struct Tempo(pub u32);
|
||||
|
|
@ -24,29 +24,29 @@ pub enum NoteDuration {
|
|||
impl Frame {
|
||||
#[inline]
|
||||
pub fn to_usec (&self, rate: &Hz) -> Usec {
|
||||
Usec((self.0 * 1000) / rate.0)
|
||||
Usec((self.0 as u64 * 1000000) / rate.0 as u64)
|
||||
}
|
||||
}
|
||||
|
||||
impl Usec {
|
||||
#[inline]
|
||||
pub fn to_frame (&self, rate: &Hz) -> Frame {
|
||||
Frame((self.0 * rate.0) / 1000)
|
||||
Frame(((self.0 * rate.0 as u64) / 1000) as u32)
|
||||
}
|
||||
}
|
||||
|
||||
impl Tempo {
|
||||
#[inline]
|
||||
pub fn to_usec_per_beat (&self) -> Usec {
|
||||
Usec((60_000_000_000 / self.0 as usize) as u32)
|
||||
pub fn usec_per_beat (&self) -> Usec {
|
||||
Usec(60_000_000_000 / self.0 as u64)
|
||||
}
|
||||
#[inline]
|
||||
pub fn to_usec_per_quarter (&self) -> Usec {
|
||||
Usec(self.to_usec_per_quarter().0 / 4)
|
||||
pub fn usec_per_quarter (&self) -> Usec {
|
||||
Usec(self.usec_per_beat().0 / 4)
|
||||
}
|
||||
#[inline]
|
||||
pub fn to_usec_per_tick (&self, ppq: u32) -> Usec {
|
||||
Usec(self.to_usec_per_quarter().0 / ppq)
|
||||
pub fn usec_per_tick (&self, ppq: u32) -> Usec {
|
||||
Usec(self.usec_per_quarter().0 / ppq as u64)
|
||||
}
|
||||
#[inline]
|
||||
pub fn quantize (&self, step: &NoteDuration, time: Usec) -> Usec {
|
||||
|
|
@ -69,11 +69,11 @@ impl NoteDuration {
|
|||
fn to_usec (&self, bpm: &Tempo) -> Usec {
|
||||
Usec(match self {
|
||||
Self::Nth(time, flies) =>
|
||||
bpm.to_usec_per_beat().0 * *time as u32 / *flies as u32,
|
||||
bpm.usec_per_beat().0 * *time as u64 / *flies as u64,
|
||||
Self::Dotted(duration) =>
|
||||
duration.to_usec(bpm).0 * 3 / 2,
|
||||
Self::Tuplet(n, duration) =>
|
||||
duration.to_usec(bpm).0 * 2 / *n as u32,
|
||||
duration.to_usec(bpm).0 * 2 / *n as u64,
|
||||
})
|
||||
}
|
||||
fn to_frame (&self, bpm: &Tempo, rate: &Hz) -> Frame {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue