use num_rational::Ratio;
use sim_kernel::{Expr, Result as KernelResult, Symbol};
use sim_lib_midi_core::{Channel, MidiEvent, U7, U14};
use crate::model::ensure_non_negative;
use crate::{
Articulation, LaneId, LaneKind, MusicError, Note, NoteEvent, PerformanceTake, Pitch, Time,
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TimeGrid {
pub tpq: u32,
pub step: Time,
}
impl TimeGrid {
pub fn new(tpq: u32, step: Time) -> Result<Self, MusicError> {
if tpq == 0 || step <= Time::from_integer(0) {
return Err(MusicError::InvalidPianoRollGrid);
}
Ok(Self { tpq, step })
}
}
impl Default for TimeGrid {
fn default() -> Self {
Self {
tpq: 480,
step: Ratio::new(1, 16),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TimedNote {
pub onset: Time,
pub note: Note,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NoteOccurrence {
pub lane: LaneId,
pub cell_index: usize,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SlicedNote {
pub occurrence: NoteOccurrence,
pub timed: TimedNote,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NoteSlice {
pub at: Time,
pub until: Time,
pub notes: Vec<SlicedNote>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DrumCell {
pub onset: Time,
pub duration: Time,
pub key: U7,
pub velocity: U7,
pub channel: Channel,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ScaleDegreeCell {
pub onset: Time,
pub duration: Time,
pub degree: i16,
pub octave: i8,
pub velocity: U7,
pub channel: Channel,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ObjectCell {
pub onset: Time,
pub duration: Time,
pub object: Symbol,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AutomationCell {
pub time: Time,
pub target: Symbol,
pub value: i64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ControlChangeCell {
pub time: Time,
pub channel: Channel,
pub controller: U7,
pub value: U7,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PitchBendCell {
pub time: Time,
pub channel: Channel,
pub value: U14,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PolyPressureCell {
pub time: Time,
pub channel: Channel,
pub key: U7,
pub pressure: U7,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ChannelPressureCell {
pub time: Time,
pub channel: Channel,
pub pressure: U7,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PianoRollCell {
Note(TimedNote),
Drum(DrumCell),
ScaleDegree(ScaleDegreeCell),
Object(ObjectCell),
Automation(AutomationCell),
ControlChange(ControlChangeCell),
PitchBend(PitchBendCell),
PolyPressure(PolyPressureCell),
ChannelPressure(ChannelPressureCell),
Midi(MidiEvent),
}
impl PianoRollCell {
pub fn time(&self) -> Time {
match self {
Self::Note(cell) => cell.onset,
Self::Drum(cell) => cell.onset,
Self::ScaleDegree(cell) => cell.onset,
Self::Object(cell) => cell.onset,
Self::Automation(cell) => cell.time,
Self::ControlChange(cell) => cell.time,
Self::PitchBend(cell) => cell.time,
Self::PolyPressure(cell) => cell.time,
Self::ChannelPressure(cell) => cell.time,
Self::Midi(event) => tick_time_to_time(event.time),
}
}
pub fn lane_kind(&self) -> LaneKind {
match self {
Self::Note(_) => LaneKind::Note,
Self::Drum(_) => LaneKind::Drum,
Self::ScaleDegree(_) => LaneKind::ScaleDegree,
Self::Object(_) => LaneKind::Object,
Self::Automation(_) => LaneKind::Automation,
Self::ControlChange(_)
| Self::PitchBend(_)
| Self::PolyPressure(_)
| Self::ChannelPressure(_) => LaneKind::Control,
Self::Midi(_) => LaneKind::Midi,
}
}
pub fn kind_label(&self) -> &'static str {
match self {
Self::Note(_) => "note",
Self::Drum(_) => "drum",
Self::ScaleDegree(_) => "scale-degree",
Self::Object(_) => "object",
Self::Automation(_) => "automation",
Self::ControlChange(_) => "control-change",
Self::PitchBend(_) => "pitch-bend",
Self::PolyPressure(_) => "poly-pressure",
Self::ChannelPressure(_) => "channel-pressure",
Self::Midi(_) => "midi",
}
}
pub fn to_expr(&self) -> Expr {
match self {
Self::Note(cell) => map(vec![
("kind", Expr::String("note".to_owned())),
("onset", time_expr(cell.onset)),
("duration", time_expr(cell.note.duration)),
("pitch", Expr::String(pitch_label(cell.note.pitch))),
("velocity", Expr::String(cell.note.velocity.to_string())),
("channel", Expr::String(cell.note.channel.0.to_string())),
]),
Self::Drum(cell) => map(vec![
("kind", Expr::String("drum".to_owned())),
("onset", time_expr(cell.onset)),
("duration", time_expr(cell.duration)),
("key", Expr::String(cell.key.0.to_string())),
("velocity", Expr::String(cell.velocity.0.to_string())),
("channel", Expr::String(cell.channel.0.to_string())),
]),
Self::ScaleDegree(cell) => map(vec![
("kind", Expr::String("scale-degree".to_owned())),
("onset", time_expr(cell.onset)),
("duration", time_expr(cell.duration)),
("degree", Expr::String(cell.degree.to_string())),
("octave", Expr::String(cell.octave.to_string())),
("velocity", Expr::String(cell.velocity.0.to_string())),
("channel", Expr::String(cell.channel.0.to_string())),
]),
Self::Object(cell) => map(vec![
("kind", Expr::String("object".to_owned())),
("onset", time_expr(cell.onset)),
("duration", time_expr(cell.duration)),
("object", Expr::Symbol(cell.object.clone())),
]),
Self::Automation(cell) => map(vec![
("kind", Expr::String("automation".to_owned())),
("time", time_expr(cell.time)),
("target", Expr::Symbol(cell.target.clone())),
("value", Expr::String(cell.value.to_string())),
]),
Self::ControlChange(cell) => map(vec![
("kind", Expr::String("control-change".to_owned())),
("time", time_expr(cell.time)),
("channel", Expr::String(cell.channel.0.to_string())),
("controller", Expr::String(cell.controller.0.to_string())),
("value", Expr::String(cell.value.0.to_string())),
]),
Self::PitchBend(cell) => map(vec![
("kind", Expr::String("pitch-bend".to_owned())),
("time", time_expr(cell.time)),
("channel", Expr::String(cell.channel.0.to_string())),
("value", Expr::String(cell.value.0.to_string())),
]),
Self::PolyPressure(cell) => map(vec![
("kind", Expr::String("poly-pressure".to_owned())),
("time", time_expr(cell.time)),
("channel", Expr::String(cell.channel.0.to_string())),
("key", Expr::String(cell.key.0.to_string())),
("pressure", Expr::String(cell.pressure.0.to_string())),
]),
Self::ChannelPressure(cell) => map(vec![
("kind", Expr::String("channel-pressure".to_owned())),
("time", time_expr(cell.time)),
("channel", Expr::String(cell.channel.0.to_string())),
("pressure", Expr::String(cell.pressure.0.to_string())),
]),
Self::Midi(event) => map(vec![
("kind", Expr::String("midi".to_owned())),
("time", time_expr(tick_time_to_time(event.time))),
("payload", Expr::String(format!("{:?}", event.payload))),
]),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PianoRollLane {
pub id: LaneId,
pub kind: LaneKind,
pub cells: Vec<PianoRollCell>,
}
impl PianoRollLane {
pub fn new(
id: LaneId,
kind: LaneKind,
mut cells: Vec<PianoRollCell>,
) -> Result<Self, MusicError> {
for cell in &cells {
if cell.lane_kind() != kind {
return Err(MusicError::PianoRollLaneCellMismatch {
lane: id.0.clone(),
lane_kind: kind.wire_label().to_owned(),
cell_kind: cell.kind_label().to_owned(),
});
}
validate_cell_time(cell)?;
}
stable_cell_order(&mut cells);
Ok(Self { id, kind, cells })
}
pub fn to_expr(&self) -> Expr {
map(vec![
("id", Expr::String(self.id.0.clone())),
("kind", Expr::Symbol(self.kind.symbol())),
(
"cells",
Expr::List(self.cells.iter().map(PianoRollCell::to_expr).collect()),
),
])
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PianoRoll {
pub items: Vec<TimedNote>,
pub lanes: Vec<PianoRollLane>,
pub time: TimeGrid,
}
impl PianoRoll {
pub fn new(items: Vec<TimedNote>) -> Result<Self, MusicError> {
let cells = items
.into_iter()
.map(PianoRollCell::Note)
.collect::<Vec<_>>();
let lanes = if cells.is_empty() {
Vec::new()
} else {
vec![PianoRollLane::new(
LaneId::new("notes"),
LaneKind::Note,
cells,
)?]
};
Self::from_lanes_with_time(lanes, TimeGrid::default())
}
pub fn from_lanes(lanes: Vec<PianoRollLane>) -> Result<Self, MusicError> {
Self::from_lanes_with_time(lanes, TimeGrid::default())
}
pub fn from_lanes_with_time(
mut lanes: Vec<PianoRollLane>,
time: TimeGrid,
) -> Result<Self, MusicError> {
TimeGrid::new(time.tpq, time.step)?;
lanes.sort_by(|left, right| {
left.id
.cmp(&right.id)
.then_with(|| left.kind.cmp(&right.kind))
});
let mut items = lanes
.iter()
.flat_map(|lane| lane.cells.iter())
.filter_map(cell_note)
.collect::<Vec<_>>();
stable_note_order(&mut items);
Ok(Self { items, lanes, time })
}
pub fn from_note_events(events: Vec<NoteEvent>) -> Result<Self, MusicError> {
let cells = events
.into_iter()
.map(|event| {
PianoRollCell::Note(TimedNote {
onset: tick_time_to_time(event.time),
note: Note {
duration: tick_time_to_time(event.duration),
pitch: event.pitch,
velocity: event.velocity,
channel: event.channel,
articulation: Articulation::Normal,
},
})
})
.collect::<Vec<_>>();
Self::from_lanes(vec![PianoRollLane::new(
LaneId::new("performance-notes"),
LaneKind::Note,
cells,
)?])
}
pub fn from_performance_take(take: &PerformanceTake) -> KernelResult<Self> {
let note_events = take.note_events()?;
Self::from_note_events(note_events)
.map_err(|err| sim_kernel::Error::Eval(format!("invalid piano-roll take: {err}")))
}
pub fn cells(&self) -> impl Iterator<Item = &PianoRollCell> {
self.lanes.iter().flat_map(|lane| lane.cells.iter())
}
pub fn note_slices(&self) -> Vec<NoteSlice> {
let occurrences = self
.lanes
.iter()
.flat_map(|lane| {
lane.cells
.iter()
.enumerate()
.filter_map(|(cell_index, cell)| {
cell_note(cell).map(|timed| SlicedNote {
occurrence: NoteOccurrence {
lane: lane.id.clone(),
cell_index,
},
timed,
})
})
})
.collect::<Vec<_>>();
let mut boundaries = occurrences
.iter()
.flat_map(|note| {
[
note.timed.onset,
note.timed.onset + note.timed.note.duration,
]
})
.collect::<Vec<_>>();
boundaries.sort();
boundaries.dedup();
boundaries
.windows(2)
.filter_map(|pair| {
let at = pair[0];
let until = pair[1];
let notes = occurrences
.iter()
.filter(|note| {
note.timed.onset <= at && at < note.timed.onset + note.timed.note.duration
})
.cloned()
.collect::<Vec<_>>();
(!notes.is_empty() && at < until).then_some(NoteSlice { at, until, notes })
})
.collect()
}
pub fn to_expr(&self) -> Expr {
map(vec![
(
"object",
Expr::Symbol(Symbol::qualified("music", "PianoRoll")),
),
("tpq", Expr::String(self.time.tpq.to_string())),
("step", time_expr(self.time.step)),
(
"lanes",
Expr::List(self.lanes.iter().map(PianoRollLane::to_expr).collect()),
),
])
}
}
fn stable_note_order(items: &mut [TimedNote]) {
items.sort_by(|left, right| {
left.onset
.cmp(&right.onset)
.then_with(|| left.note.pitch.semitone().cmp(&right.note.pitch.semitone()))
.then_with(|| left.note.channel.0.cmp(&right.note.channel.0))
});
}
fn stable_cell_order(cells: &mut [PianoRollCell]) {
cells.sort_by(|left, right| {
left.time()
.cmp(&right.time())
.then_with(|| left.kind_label().cmp(right.kind_label()))
});
}
fn validate_cell_time(cell: &PianoRollCell) -> Result<(), MusicError> {
ensure_non_negative(cell.time())?;
match cell {
PianoRollCell::Note(cell) => ensure_non_negative(cell.note.duration),
PianoRollCell::Drum(cell) => ensure_non_negative(cell.duration),
PianoRollCell::ScaleDegree(cell) => ensure_non_negative(cell.duration),
PianoRollCell::Object(cell) => ensure_non_negative(cell.duration),
PianoRollCell::Automation(_)
| PianoRollCell::ControlChange(_)
| PianoRollCell::PitchBend(_)
| PianoRollCell::PolyPressure(_)
| PianoRollCell::ChannelPressure(_)
| PianoRollCell::Midi(_) => Ok(()),
}
}
fn cell_note(cell: &PianoRollCell) -> Option<TimedNote> {
match cell {
PianoRollCell::Note(cell) => Some(cell.clone()),
PianoRollCell::Drum(cell) => Some(TimedNote {
onset: cell.onset,
note: Note {
duration: cell.duration,
pitch: Pitch::from_midi(cell.key.0),
velocity: cell.velocity.0.max(1),
channel: cell.channel,
articulation: Articulation::Normal,
},
}),
_ => None,
}
}
fn tick_time_to_time(time: sim_lib_midi_core::TickTime) -> Time {
Ratio::new(time.ticks, i64::from(time.tpq) * 4)
}
fn time_expr(time: Time) -> Expr {
map(vec![
("numer", Expr::String(time.numer().to_string())),
("denom", Expr::String(time.denom().to_string())),
])
}
fn pitch_label(pitch: Pitch) -> String {
pitch
.to_midi()
.map(|midi| format!("midi:{midi}"))
.unwrap_or_else(|| format!("semitone:{}", pitch.semitone()))
}
fn map(entries: Vec<(&'static str, Expr)>) -> Expr {
Expr::Map(
entries
.into_iter()
.map(|(key, value)| (Expr::Symbol(Symbol::new(key)), value))
.collect(),
)
}