1use num_rational::Ratio;
2use sim_kernel::{Expr, Result as KernelResult, Symbol};
3use sim_lib_midi_core::{Channel, MidiEvent, U7, U14};
4
5use crate::model::ensure_non_negative;
6use crate::{
7 Articulation, LaneId, LaneKind, MusicError, Note, NoteEvent, PerformanceTake, Pitch, Time,
8};
9
10#[derive(Clone, Debug, PartialEq, Eq)]
24pub struct TimeGrid {
25 pub tpq: u32,
27 pub step: Time,
29}
30
31impl TimeGrid {
32 pub fn new(tpq: u32, step: Time) -> Result<Self, MusicError> {
36 if tpq == 0 || step <= Time::from_integer(0) {
37 return Err(MusicError::InvalidPianoRollGrid);
38 }
39 Ok(Self { tpq, step })
40 }
41}
42
43impl Default for TimeGrid {
44 fn default() -> Self {
45 Self {
46 tpq: 480,
47 step: Ratio::new(1, 16),
48 }
49 }
50}
51
52#[derive(Clone, Debug, PartialEq, Eq)]
54pub struct TimedNote {
55 pub onset: Time,
57 pub note: Note,
59}
60
61#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
66pub struct NoteOccurrence {
67 pub lane: LaneId,
69 pub cell_index: usize,
71}
72
73#[derive(Clone, Debug, PartialEq, Eq)]
75pub struct SlicedNote {
76 pub occurrence: NoteOccurrence,
78 pub timed: TimedNote,
80}
81
82#[derive(Clone, Debug, PartialEq, Eq)]
87pub struct NoteSlice {
88 pub at: Time,
90 pub until: Time,
92 pub notes: Vec<SlicedNote>,
94}
95
96#[derive(Clone, Debug, PartialEq, Eq)]
98pub struct DrumCell {
99 pub onset: Time,
101 pub duration: Time,
103 pub key: U7,
105 pub velocity: U7,
107 pub channel: Channel,
109}
110
111#[derive(Clone, Debug, PartialEq, Eq)]
113pub struct ScaleDegreeCell {
114 pub onset: Time,
116 pub duration: Time,
118 pub degree: i16,
120 pub octave: i8,
122 pub velocity: U7,
124 pub channel: Channel,
126}
127
128#[derive(Clone, Debug, PartialEq, Eq)]
130pub struct ObjectCell {
131 pub onset: Time,
133 pub duration: Time,
135 pub object: Symbol,
137}
138
139#[derive(Clone, Debug, PartialEq, Eq)]
141pub struct AutomationCell {
142 pub time: Time,
144 pub target: Symbol,
146 pub value: i64,
148}
149
150#[derive(Clone, Debug, PartialEq, Eq)]
152pub struct ControlChangeCell {
153 pub time: Time,
155 pub channel: Channel,
157 pub controller: U7,
159 pub value: U7,
161}
162
163#[derive(Clone, Debug, PartialEq, Eq)]
165pub struct PitchBendCell {
166 pub time: Time,
168 pub channel: Channel,
170 pub value: U14,
172}
173
174#[derive(Clone, Debug, PartialEq, Eq)]
176pub struct PolyPressureCell {
177 pub time: Time,
179 pub channel: Channel,
181 pub key: U7,
183 pub pressure: U7,
185}
186
187#[derive(Clone, Debug, PartialEq, Eq)]
189pub struct ChannelPressureCell {
190 pub time: Time,
192 pub channel: Channel,
194 pub pressure: U7,
196}
197
198#[derive(Clone, Debug, PartialEq, Eq)]
202pub enum PianoRollCell {
203 Note(TimedNote),
205 Drum(DrumCell),
207 ScaleDegree(ScaleDegreeCell),
209 Object(ObjectCell),
211 Automation(AutomationCell),
213 ControlChange(ControlChangeCell),
215 PitchBend(PitchBendCell),
217 PolyPressure(PolyPressureCell),
219 ChannelPressure(ChannelPressureCell),
221 Midi(MidiEvent),
223}
224
225impl PianoRollCell {
226 pub fn time(&self) -> Time {
228 match self {
229 Self::Note(cell) => cell.onset,
230 Self::Drum(cell) => cell.onset,
231 Self::ScaleDegree(cell) => cell.onset,
232 Self::Object(cell) => cell.onset,
233 Self::Automation(cell) => cell.time,
234 Self::ControlChange(cell) => cell.time,
235 Self::PitchBend(cell) => cell.time,
236 Self::PolyPressure(cell) => cell.time,
237 Self::ChannelPressure(cell) => cell.time,
238 Self::Midi(event) => tick_time_to_time(event.time),
239 }
240 }
241
242 pub fn lane_kind(&self) -> LaneKind {
244 match self {
245 Self::Note(_) => LaneKind::Note,
246 Self::Drum(_) => LaneKind::Drum,
247 Self::ScaleDegree(_) => LaneKind::ScaleDegree,
248 Self::Object(_) => LaneKind::Object,
249 Self::Automation(_) => LaneKind::Automation,
250 Self::ControlChange(_)
251 | Self::PitchBend(_)
252 | Self::PolyPressure(_)
253 | Self::ChannelPressure(_) => LaneKind::Control,
254 Self::Midi(_) => LaneKind::Midi,
255 }
256 }
257
258 pub fn kind_label(&self) -> &'static str {
260 match self {
261 Self::Note(_) => "note",
262 Self::Drum(_) => "drum",
263 Self::ScaleDegree(_) => "scale-degree",
264 Self::Object(_) => "object",
265 Self::Automation(_) => "automation",
266 Self::ControlChange(_) => "control-change",
267 Self::PitchBend(_) => "pitch-bend",
268 Self::PolyPressure(_) => "poly-pressure",
269 Self::ChannelPressure(_) => "channel-pressure",
270 Self::Midi(_) => "midi",
271 }
272 }
273
274 pub fn to_expr(&self) -> Expr {
276 match self {
277 Self::Note(cell) => map(vec![
278 ("kind", Expr::String("note".to_owned())),
279 ("onset", time_expr(cell.onset)),
280 ("duration", time_expr(cell.note.duration)),
281 ("pitch", Expr::String(pitch_label(cell.note.pitch))),
282 ("velocity", Expr::String(cell.note.velocity.to_string())),
283 ("channel", Expr::String(cell.note.channel.0.to_string())),
284 ]),
285 Self::Drum(cell) => map(vec![
286 ("kind", Expr::String("drum".to_owned())),
287 ("onset", time_expr(cell.onset)),
288 ("duration", time_expr(cell.duration)),
289 ("key", Expr::String(cell.key.0.to_string())),
290 ("velocity", Expr::String(cell.velocity.0.to_string())),
291 ("channel", Expr::String(cell.channel.0.to_string())),
292 ]),
293 Self::ScaleDegree(cell) => map(vec![
294 ("kind", Expr::String("scale-degree".to_owned())),
295 ("onset", time_expr(cell.onset)),
296 ("duration", time_expr(cell.duration)),
297 ("degree", Expr::String(cell.degree.to_string())),
298 ("octave", Expr::String(cell.octave.to_string())),
299 ("velocity", Expr::String(cell.velocity.0.to_string())),
300 ("channel", Expr::String(cell.channel.0.to_string())),
301 ]),
302 Self::Object(cell) => map(vec![
303 ("kind", Expr::String("object".to_owned())),
304 ("onset", time_expr(cell.onset)),
305 ("duration", time_expr(cell.duration)),
306 ("object", Expr::Symbol(cell.object.clone())),
307 ]),
308 Self::Automation(cell) => map(vec![
309 ("kind", Expr::String("automation".to_owned())),
310 ("time", time_expr(cell.time)),
311 ("target", Expr::Symbol(cell.target.clone())),
312 ("value", Expr::String(cell.value.to_string())),
313 ]),
314 Self::ControlChange(cell) => map(vec![
315 ("kind", Expr::String("control-change".to_owned())),
316 ("time", time_expr(cell.time)),
317 ("channel", Expr::String(cell.channel.0.to_string())),
318 ("controller", Expr::String(cell.controller.0.to_string())),
319 ("value", Expr::String(cell.value.0.to_string())),
320 ]),
321 Self::PitchBend(cell) => map(vec![
322 ("kind", Expr::String("pitch-bend".to_owned())),
323 ("time", time_expr(cell.time)),
324 ("channel", Expr::String(cell.channel.0.to_string())),
325 ("value", Expr::String(cell.value.0.to_string())),
326 ]),
327 Self::PolyPressure(cell) => map(vec![
328 ("kind", Expr::String("poly-pressure".to_owned())),
329 ("time", time_expr(cell.time)),
330 ("channel", Expr::String(cell.channel.0.to_string())),
331 ("key", Expr::String(cell.key.0.to_string())),
332 ("pressure", Expr::String(cell.pressure.0.to_string())),
333 ]),
334 Self::ChannelPressure(cell) => map(vec![
335 ("kind", Expr::String("channel-pressure".to_owned())),
336 ("time", time_expr(cell.time)),
337 ("channel", Expr::String(cell.channel.0.to_string())),
338 ("pressure", Expr::String(cell.pressure.0.to_string())),
339 ]),
340 Self::Midi(event) => map(vec![
341 ("kind", Expr::String("midi".to_owned())),
342 ("time", time_expr(tick_time_to_time(event.time))),
343 ("payload", Expr::String(format!("{:?}", event.payload))),
344 ]),
345 }
346 }
347}
348
349#[derive(Clone, Debug, PartialEq, Eq)]
351pub struct PianoRollLane {
352 pub id: LaneId,
354 pub kind: LaneKind,
356 pub cells: Vec<PianoRollCell>,
358}
359
360impl PianoRollLane {
361 pub fn new(
366 id: LaneId,
367 kind: LaneKind,
368 mut cells: Vec<PianoRollCell>,
369 ) -> Result<Self, MusicError> {
370 for cell in &cells {
371 if cell.lane_kind() != kind {
372 return Err(MusicError::PianoRollLaneCellMismatch {
373 lane: id.0.clone(),
374 lane_kind: kind.wire_label().to_owned(),
375 cell_kind: cell.kind_label().to_owned(),
376 });
377 }
378 validate_cell_time(cell)?;
379 }
380 stable_cell_order(&mut cells);
381 Ok(Self { id, kind, cells })
382 }
383
384 pub fn to_expr(&self) -> Expr {
386 map(vec![
387 ("id", Expr::String(self.id.0.clone())),
388 ("kind", Expr::Symbol(self.kind.symbol())),
389 (
390 "cells",
391 Expr::List(self.cells.iter().map(PianoRollCell::to_expr).collect()),
392 ),
393 ])
394 }
395}
396
397#[derive(Clone, Debug, PartialEq, Eq)]
402pub struct PianoRoll {
403 pub items: Vec<TimedNote>,
405 pub lanes: Vec<PianoRollLane>,
407 pub time: TimeGrid,
409}
410
411impl PianoRoll {
412 pub fn new(items: Vec<TimedNote>) -> Result<Self, MusicError> {
416 let cells = items
417 .into_iter()
418 .map(PianoRollCell::Note)
419 .collect::<Vec<_>>();
420 let lanes = if cells.is_empty() {
421 Vec::new()
422 } else {
423 vec![PianoRollLane::new(
424 LaneId::new("notes"),
425 LaneKind::Note,
426 cells,
427 )?]
428 };
429 Self::from_lanes_with_time(lanes, TimeGrid::default())
430 }
431
432 pub fn from_lanes(lanes: Vec<PianoRollLane>) -> Result<Self, MusicError> {
434 Self::from_lanes_with_time(lanes, TimeGrid::default())
435 }
436
437 pub fn from_lanes_with_time(
442 mut lanes: Vec<PianoRollLane>,
443 time: TimeGrid,
444 ) -> Result<Self, MusicError> {
445 TimeGrid::new(time.tpq, time.step)?;
446 lanes.sort_by(|left, right| {
447 left.id
448 .cmp(&right.id)
449 .then_with(|| left.kind.cmp(&right.kind))
450 });
451 let mut items = lanes
452 .iter()
453 .flat_map(|lane| lane.cells.iter())
454 .filter_map(cell_note)
455 .collect::<Vec<_>>();
456 stable_note_order(&mut items);
457 Ok(Self { items, lanes, time })
458 }
459
460 pub fn from_note_events(events: Vec<NoteEvent>) -> Result<Self, MusicError> {
464 let cells = events
465 .into_iter()
466 .map(|event| {
467 PianoRollCell::Note(TimedNote {
468 onset: tick_time_to_time(event.time),
469 note: Note {
470 duration: tick_time_to_time(event.duration),
471 pitch: event.pitch,
472 velocity: event.velocity,
473 channel: event.channel,
474 articulation: Articulation::Normal,
475 },
476 })
477 })
478 .collect::<Vec<_>>();
479 Self::from_lanes(vec![PianoRollLane::new(
480 LaneId::new("performance-notes"),
481 LaneKind::Note,
482 cells,
483 )?])
484 }
485
486 pub fn from_performance_take(take: &PerformanceTake) -> KernelResult<Self> {
490 let note_events = take.note_events()?;
491 Self::from_note_events(note_events)
492 .map_err(|err| sim_kernel::Error::Eval(format!("invalid piano-roll take: {err}")))
493 }
494
495 pub fn cells(&self) -> impl Iterator<Item = &PianoRollCell> {
497 self.lanes.iter().flat_map(|lane| lane.cells.iter())
498 }
499
500 pub fn note_slices(&self) -> Vec<NoteSlice> {
506 let occurrences = self
507 .lanes
508 .iter()
509 .flat_map(|lane| {
510 lane.cells
511 .iter()
512 .enumerate()
513 .filter_map(|(cell_index, cell)| {
514 cell_note(cell).map(|timed| SlicedNote {
515 occurrence: NoteOccurrence {
516 lane: lane.id.clone(),
517 cell_index,
518 },
519 timed,
520 })
521 })
522 })
523 .collect::<Vec<_>>();
524 let mut boundaries = occurrences
525 .iter()
526 .flat_map(|note| {
527 [
528 note.timed.onset,
529 note.timed.onset + note.timed.note.duration,
530 ]
531 })
532 .collect::<Vec<_>>();
533 boundaries.sort();
534 boundaries.dedup();
535 boundaries
536 .windows(2)
537 .filter_map(|pair| {
538 let at = pair[0];
539 let until = pair[1];
540 let notes = occurrences
541 .iter()
542 .filter(|note| {
543 note.timed.onset <= at && at < note.timed.onset + note.timed.note.duration
544 })
545 .cloned()
546 .collect::<Vec<_>>();
547 (!notes.is_empty() && at < until).then_some(NoteSlice { at, until, notes })
548 })
549 .collect()
550 }
551
552 pub fn to_expr(&self) -> Expr {
554 map(vec![
555 (
556 "object",
557 Expr::Symbol(Symbol::qualified("music", "PianoRoll")),
558 ),
559 ("tpq", Expr::String(self.time.tpq.to_string())),
560 ("step", time_expr(self.time.step)),
561 (
562 "lanes",
563 Expr::List(self.lanes.iter().map(PianoRollLane::to_expr).collect()),
564 ),
565 ])
566 }
567}
568
569fn stable_note_order(items: &mut [TimedNote]) {
570 items.sort_by(|left, right| {
571 left.onset
572 .cmp(&right.onset)
573 .then_with(|| left.note.pitch.semitone().cmp(&right.note.pitch.semitone()))
574 .then_with(|| left.note.channel.0.cmp(&right.note.channel.0))
575 });
576}
577
578fn stable_cell_order(cells: &mut [PianoRollCell]) {
579 cells.sort_by(|left, right| {
580 left.time()
581 .cmp(&right.time())
582 .then_with(|| left.kind_label().cmp(right.kind_label()))
583 });
584}
585
586fn validate_cell_time(cell: &PianoRollCell) -> Result<(), MusicError> {
587 ensure_non_negative(cell.time())?;
588 match cell {
589 PianoRollCell::Note(cell) => ensure_non_negative(cell.note.duration),
590 PianoRollCell::Drum(cell) => ensure_non_negative(cell.duration),
591 PianoRollCell::ScaleDegree(cell) => ensure_non_negative(cell.duration),
592 PianoRollCell::Object(cell) => ensure_non_negative(cell.duration),
593 PianoRollCell::Automation(_)
594 | PianoRollCell::ControlChange(_)
595 | PianoRollCell::PitchBend(_)
596 | PianoRollCell::PolyPressure(_)
597 | PianoRollCell::ChannelPressure(_)
598 | PianoRollCell::Midi(_) => Ok(()),
599 }
600}
601
602fn cell_note(cell: &PianoRollCell) -> Option<TimedNote> {
603 match cell {
604 PianoRollCell::Note(cell) => Some(cell.clone()),
605 PianoRollCell::Drum(cell) => Some(TimedNote {
606 onset: cell.onset,
607 note: Note {
608 duration: cell.duration,
609 pitch: Pitch::from_midi(cell.key.0),
610 velocity: cell.velocity.0.max(1),
611 channel: cell.channel,
612 articulation: Articulation::Normal,
613 },
614 }),
615 _ => None,
616 }
617}
618
619fn tick_time_to_time(time: sim_lib_midi_core::TickTime) -> Time {
620 Ratio::new(time.ticks, i64::from(time.tpq) * 4)
621}
622
623fn time_expr(time: Time) -> Expr {
624 map(vec![
625 ("numer", Expr::String(time.numer().to_string())),
626 ("denom", Expr::String(time.denom().to_string())),
627 ])
628}
629
630fn pitch_label(pitch: Pitch) -> String {
631 pitch
632 .to_midi()
633 .map(|midi| format!("midi:{midi}"))
634 .unwrap_or_else(|| format!("semitone:{}", pitch.semitone()))
635}
636
637fn map(entries: Vec<(&'static str, Expr)>) -> Expr {
638 Expr::Map(
639 entries
640 .into_iter()
641 .map(|(key, value)| (Expr::Symbol(Symbol::new(key)), value))
642 .collect(),
643 )
644}