mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +01:00
114 lines
3.7 KiB
Rust
114 lines
3.7 KiB
Rust
use crate::sequence::{Frame, Time};
|
|
|
|
/// Pulses per quarter note
|
|
pub const PPQ: usize = 96;
|
|
|
|
pub struct Message {
|
|
bytes: Vec<u8>
|
|
}
|
|
|
|
pub struct MIDISequence {
|
|
name: String,
|
|
length: usize,
|
|
events: Vec<(Frame, MIDIEvent)>,
|
|
notes: Vec<u8>,
|
|
}
|
|
|
|
impl MIDISequence {
|
|
pub fn new (name: &str, length: usize) -> Self {
|
|
Self {
|
|
name: name.to_string(),
|
|
length,
|
|
events: vec![],
|
|
notes: vec![],
|
|
}
|
|
}
|
|
pub fn add (&mut self, time: usize, event: MIDIEvent) -> &mut Self {
|
|
let mut sort = false;
|
|
match &event {
|
|
MIDIEvent::NoteOn(pitch, _velocity) => {
|
|
if !self.notes.contains(pitch) {
|
|
self.notes.push(*pitch);
|
|
sort = true;
|
|
}
|
|
}
|
|
}
|
|
if sort {
|
|
self.notes.sort();
|
|
}
|
|
self.events.push((time, event));
|
|
self
|
|
}
|
|
pub fn render (&self, stdout: &mut std::io::Stdout, resolution: usize)
|
|
-> Result<(), Box<dyn std::error::Error>>
|
|
{
|
|
use crossterm::{*, style::Stylize};
|
|
let (col, row) = cursor::position()?;
|
|
let unit = PPQ / resolution;
|
|
let length = self.length / unit;
|
|
let mut header = String::from(" ");
|
|
for i in 0..length {
|
|
if i % 8 == 0 {
|
|
header.push('┍');
|
|
} else {
|
|
header.push('━');
|
|
}
|
|
}
|
|
header.push('┑');
|
|
let mut tracks: Vec<(String, String)> = vec![];
|
|
for note in self.notes.iter().rev() {
|
|
let mut row_header = format!(" {note:3} ");
|
|
let mut row = String::new();
|
|
for beat in 0..length {
|
|
let mut active = false;
|
|
for (frame, event) in self.events.iter() {
|
|
match event {
|
|
MIDIEvent::NoteOn(pitch, _) => {
|
|
if pitch == note
|
|
&& *frame >= beat * unit
|
|
&& *frame < (beat + 1) * unit
|
|
{
|
|
active = true;
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if active {
|
|
row.push('⯀')
|
|
} else if beat == 0 {
|
|
row.push('│');
|
|
} else if beat % 8 == 0 {
|
|
row.push('┆');
|
|
} else {
|
|
row.push('·');
|
|
}
|
|
}
|
|
tracks.push((row_header, row));
|
|
}
|
|
stdout.queue(style::PrintStyledContent(header.grey()))?;
|
|
for (row_header, row) in tracks.into_iter() {
|
|
stdout
|
|
.queue(cursor::MoveDown(1))?
|
|
.queue(cursor::MoveToColumn(col))?
|
|
.queue(style::PrintStyledContent(row_header.grey()))?
|
|
.queue(style::PrintStyledContent(row.clone().white()))?
|
|
.queue(style::PrintStyledContent(row.clone().yellow()))?
|
|
.queue(style::PrintStyledContent(row.white()))?;
|
|
}
|
|
//stdout
|
|
//.queue(cursor::MoveDown(1))?
|
|
//.queue(cursor::MoveToColumn(col))?
|
|
//.queue(style::PrintStyledContent(footer.grey()))?;
|
|
//.queue(style::PrintStyledContent("················".grey()))?
|
|
//.queue(style::PrintStyledContent("│".yellow()))?
|
|
//.queue(cursor::MoveDown(1))?
|
|
//.queue(cursor::MoveLeft(18))?
|
|
//.queue(style::PrintStyledContent("└────────────────┘".yellow()))?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub enum MIDIEvent {
|
|
NoteOn(u8, u8)
|
|
}
|