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)]
63pub struct DrumCell {
64 pub onset: Time,
66 pub duration: Time,
68 pub key: U7,
70 pub velocity: U7,
72 pub channel: Channel,
74}
75
76#[derive(Clone, Debug, PartialEq, Eq)]
78pub struct ScaleDegreeCell {
79 pub onset: Time,
81 pub duration: Time,
83 pub degree: i16,
85 pub octave: i8,
87 pub velocity: U7,
89 pub channel: Channel,
91}
92
93#[derive(Clone, Debug, PartialEq, Eq)]
95pub struct ObjectCell {
96 pub onset: Time,
98 pub duration: Time,
100 pub object: Symbol,
102}
103
104#[derive(Clone, Debug, PartialEq, Eq)]
106pub struct AutomationCell {
107 pub time: Time,
109 pub target: Symbol,
111 pub value: i64,
113}
114
115#[derive(Clone, Debug, PartialEq, Eq)]
117pub struct ControlChangeCell {
118 pub time: Time,
120 pub channel: Channel,
122 pub controller: U7,
124 pub value: U7,
126}
127
128#[derive(Clone, Debug, PartialEq, Eq)]
130pub struct PitchBendCell {
131 pub time: Time,
133 pub channel: Channel,
135 pub value: U14,
137}
138
139#[derive(Clone, Debug, PartialEq, Eq)]
141pub struct PolyPressureCell {
142 pub time: Time,
144 pub channel: Channel,
146 pub key: U7,
148 pub pressure: U7,
150}
151
152#[derive(Clone, Debug, PartialEq, Eq)]
154pub struct ChannelPressureCell {
155 pub time: Time,
157 pub channel: Channel,
159 pub pressure: U7,
161}
162
163#[derive(Clone, Debug, PartialEq, Eq)]
167pub enum PianoRollCell {
168 Note(TimedNote),
170 Drum(DrumCell),
172 ScaleDegree(ScaleDegreeCell),
174 Object(ObjectCell),
176 Automation(AutomationCell),
178 ControlChange(ControlChangeCell),
180 PitchBend(PitchBendCell),
182 PolyPressure(PolyPressureCell),
184 ChannelPressure(ChannelPressureCell),
186 Midi(MidiEvent),
188}
189
190impl PianoRollCell {
191 pub fn time(&self) -> Time {
193 match self {
194 Self::Note(cell) => cell.onset,
195 Self::Drum(cell) => cell.onset,
196 Self::ScaleDegree(cell) => cell.onset,
197 Self::Object(cell) => cell.onset,
198 Self::Automation(cell) => cell.time,
199 Self::ControlChange(cell) => cell.time,
200 Self::PitchBend(cell) => cell.time,
201 Self::PolyPressure(cell) => cell.time,
202 Self::ChannelPressure(cell) => cell.time,
203 Self::Midi(event) => tick_time_to_time(event.time),
204 }
205 }
206
207 pub fn lane_kind(&self) -> LaneKind {
209 match self {
210 Self::Note(_) => LaneKind::Note,
211 Self::Drum(_) => LaneKind::Drum,
212 Self::ScaleDegree(_) => LaneKind::ScaleDegree,
213 Self::Object(_) => LaneKind::Object,
214 Self::Automation(_) => LaneKind::Automation,
215 Self::ControlChange(_)
216 | Self::PitchBend(_)
217 | Self::PolyPressure(_)
218 | Self::ChannelPressure(_) => LaneKind::Control,
219 Self::Midi(_) => LaneKind::Midi,
220 }
221 }
222
223 pub fn kind_label(&self) -> &'static str {
225 match self {
226 Self::Note(_) => "note",
227 Self::Drum(_) => "drum",
228 Self::ScaleDegree(_) => "scale-degree",
229 Self::Object(_) => "object",
230 Self::Automation(_) => "automation",
231 Self::ControlChange(_) => "control-change",
232 Self::PitchBend(_) => "pitch-bend",
233 Self::PolyPressure(_) => "poly-pressure",
234 Self::ChannelPressure(_) => "channel-pressure",
235 Self::Midi(_) => "midi",
236 }
237 }
238
239 pub fn to_expr(&self) -> Expr {
241 match self {
242 Self::Note(cell) => map(vec![
243 ("kind", Expr::String("note".to_owned())),
244 ("onset", time_expr(cell.onset)),
245 ("duration", time_expr(cell.note.duration)),
246 ("pitch", Expr::String(pitch_label(cell.note.pitch))),
247 ("velocity", Expr::String(cell.note.velocity.to_string())),
248 ("channel", Expr::String(cell.note.channel.0.to_string())),
249 ]),
250 Self::Drum(cell) => map(vec![
251 ("kind", Expr::String("drum".to_owned())),
252 ("onset", time_expr(cell.onset)),
253 ("duration", time_expr(cell.duration)),
254 ("key", Expr::String(cell.key.0.to_string())),
255 ("velocity", Expr::String(cell.velocity.0.to_string())),
256 ("channel", Expr::String(cell.channel.0.to_string())),
257 ]),
258 Self::ScaleDegree(cell) => map(vec![
259 ("kind", Expr::String("scale-degree".to_owned())),
260 ("onset", time_expr(cell.onset)),
261 ("duration", time_expr(cell.duration)),
262 ("degree", Expr::String(cell.degree.to_string())),
263 ("octave", Expr::String(cell.octave.to_string())),
264 ("velocity", Expr::String(cell.velocity.0.to_string())),
265 ("channel", Expr::String(cell.channel.0.to_string())),
266 ]),
267 Self::Object(cell) => map(vec![
268 ("kind", Expr::String("object".to_owned())),
269 ("onset", time_expr(cell.onset)),
270 ("duration", time_expr(cell.duration)),
271 ("object", Expr::Symbol(cell.object.clone())),
272 ]),
273 Self::Automation(cell) => map(vec![
274 ("kind", Expr::String("automation".to_owned())),
275 ("time", time_expr(cell.time)),
276 ("target", Expr::Symbol(cell.target.clone())),
277 ("value", Expr::String(cell.value.to_string())),
278 ]),
279 Self::ControlChange(cell) => map(vec![
280 ("kind", Expr::String("control-change".to_owned())),
281 ("time", time_expr(cell.time)),
282 ("channel", Expr::String(cell.channel.0.to_string())),
283 ("controller", Expr::String(cell.controller.0.to_string())),
284 ("value", Expr::String(cell.value.0.to_string())),
285 ]),
286 Self::PitchBend(cell) => map(vec![
287 ("kind", Expr::String("pitch-bend".to_owned())),
288 ("time", time_expr(cell.time)),
289 ("channel", Expr::String(cell.channel.0.to_string())),
290 ("value", Expr::String(cell.value.0.to_string())),
291 ]),
292 Self::PolyPressure(cell) => map(vec![
293 ("kind", Expr::String("poly-pressure".to_owned())),
294 ("time", time_expr(cell.time)),
295 ("channel", Expr::String(cell.channel.0.to_string())),
296 ("key", Expr::String(cell.key.0.to_string())),
297 ("pressure", Expr::String(cell.pressure.0.to_string())),
298 ]),
299 Self::ChannelPressure(cell) => map(vec![
300 ("kind", Expr::String("channel-pressure".to_owned())),
301 ("time", time_expr(cell.time)),
302 ("channel", Expr::String(cell.channel.0.to_string())),
303 ("pressure", Expr::String(cell.pressure.0.to_string())),
304 ]),
305 Self::Midi(event) => map(vec![
306 ("kind", Expr::String("midi".to_owned())),
307 ("time", time_expr(tick_time_to_time(event.time))),
308 ("payload", Expr::String(format!("{:?}", event.payload))),
309 ]),
310 }
311 }
312}
313
314#[derive(Clone, Debug, PartialEq, Eq)]
316pub struct PianoRollLane {
317 pub id: LaneId,
319 pub kind: LaneKind,
321 pub cells: Vec<PianoRollCell>,
323}
324
325impl PianoRollLane {
326 pub fn new(
331 id: LaneId,
332 kind: LaneKind,
333 mut cells: Vec<PianoRollCell>,
334 ) -> Result<Self, MusicError> {
335 for cell in &cells {
336 if cell.lane_kind() != kind {
337 return Err(MusicError::PianoRollLaneCellMismatch {
338 lane: id.0.clone(),
339 lane_kind: kind.wire_label().to_owned(),
340 cell_kind: cell.kind_label().to_owned(),
341 });
342 }
343 validate_cell_time(cell)?;
344 }
345 stable_cell_order(&mut cells);
346 Ok(Self { id, kind, cells })
347 }
348
349 pub fn to_expr(&self) -> Expr {
351 map(vec![
352 ("id", Expr::String(self.id.0.clone())),
353 ("kind", Expr::Symbol(self.kind.symbol())),
354 (
355 "cells",
356 Expr::List(self.cells.iter().map(PianoRollCell::to_expr).collect()),
357 ),
358 ])
359 }
360}
361
362#[derive(Clone, Debug, PartialEq, Eq)]
367pub struct PianoRoll {
368 pub items: Vec<TimedNote>,
370 pub lanes: Vec<PianoRollLane>,
372 pub time: TimeGrid,
374}
375
376impl PianoRoll {
377 pub fn new(items: Vec<TimedNote>) -> Result<Self, MusicError> {
381 let cells = items
382 .into_iter()
383 .map(PianoRollCell::Note)
384 .collect::<Vec<_>>();
385 let lanes = if cells.is_empty() {
386 Vec::new()
387 } else {
388 vec![PianoRollLane::new(
389 LaneId::new("notes"),
390 LaneKind::Note,
391 cells,
392 )?]
393 };
394 Self::from_lanes_with_time(lanes, TimeGrid::default())
395 }
396
397 pub fn from_lanes(lanes: Vec<PianoRollLane>) -> Result<Self, MusicError> {
399 Self::from_lanes_with_time(lanes, TimeGrid::default())
400 }
401
402 pub fn from_lanes_with_time(
407 mut lanes: Vec<PianoRollLane>,
408 time: TimeGrid,
409 ) -> Result<Self, MusicError> {
410 TimeGrid::new(time.tpq, time.step)?;
411 lanes.sort_by(|left, right| {
412 left.id
413 .cmp(&right.id)
414 .then_with(|| left.kind.cmp(&right.kind))
415 });
416 let mut items = lanes
417 .iter()
418 .flat_map(|lane| lane.cells.iter())
419 .filter_map(cell_note)
420 .collect::<Vec<_>>();
421 stable_note_order(&mut items);
422 Ok(Self { items, lanes, time })
423 }
424
425 pub fn from_note_events(events: Vec<NoteEvent>) -> Result<Self, MusicError> {
429 let cells = events
430 .into_iter()
431 .map(|event| {
432 PianoRollCell::Note(TimedNote {
433 onset: tick_time_to_time(event.time),
434 note: Note {
435 duration: tick_time_to_time(event.duration),
436 pitch: event.pitch,
437 velocity: event.velocity,
438 channel: event.channel,
439 articulation: Articulation::Normal,
440 },
441 })
442 })
443 .collect::<Vec<_>>();
444 Self::from_lanes(vec![PianoRollLane::new(
445 LaneId::new("performance-notes"),
446 LaneKind::Note,
447 cells,
448 )?])
449 }
450
451 pub fn from_performance_take(take: &PerformanceTake) -> KernelResult<Self> {
455 let note_events = take.note_events()?;
456 Self::from_note_events(note_events)
457 .map_err(|err| sim_kernel::Error::Eval(format!("invalid piano-roll take: {err}")))
458 }
459
460 pub fn cells(&self) -> impl Iterator<Item = &PianoRollCell> {
462 self.lanes.iter().flat_map(|lane| lane.cells.iter())
463 }
464
465 pub fn to_expr(&self) -> Expr {
467 map(vec![
468 (
469 "object",
470 Expr::Symbol(Symbol::qualified("music", "PianoRoll")),
471 ),
472 ("tpq", Expr::String(self.time.tpq.to_string())),
473 ("step", time_expr(self.time.step)),
474 (
475 "lanes",
476 Expr::List(self.lanes.iter().map(PianoRollLane::to_expr).collect()),
477 ),
478 ])
479 }
480}
481
482fn stable_note_order(items: &mut [TimedNote]) {
483 items.sort_by(|left, right| {
484 left.onset
485 .cmp(&right.onset)
486 .then_with(|| left.note.pitch.semitone().cmp(&right.note.pitch.semitone()))
487 .then_with(|| left.note.channel.0.cmp(&right.note.channel.0))
488 });
489}
490
491fn stable_cell_order(cells: &mut [PianoRollCell]) {
492 cells.sort_by(|left, right| {
493 left.time()
494 .cmp(&right.time())
495 .then_with(|| left.kind_label().cmp(right.kind_label()))
496 });
497}
498
499fn validate_cell_time(cell: &PianoRollCell) -> Result<(), MusicError> {
500 ensure_non_negative(cell.time())?;
501 match cell {
502 PianoRollCell::Note(cell) => ensure_non_negative(cell.note.duration),
503 PianoRollCell::Drum(cell) => ensure_non_negative(cell.duration),
504 PianoRollCell::ScaleDegree(cell) => ensure_non_negative(cell.duration),
505 PianoRollCell::Object(cell) => ensure_non_negative(cell.duration),
506 PianoRollCell::Automation(_)
507 | PianoRollCell::ControlChange(_)
508 | PianoRollCell::PitchBend(_)
509 | PianoRollCell::PolyPressure(_)
510 | PianoRollCell::ChannelPressure(_)
511 | PianoRollCell::Midi(_) => Ok(()),
512 }
513}
514
515fn cell_note(cell: &PianoRollCell) -> Option<TimedNote> {
516 match cell {
517 PianoRollCell::Note(cell) => Some(cell.clone()),
518 PianoRollCell::Drum(cell) => Some(TimedNote {
519 onset: cell.onset,
520 note: Note {
521 duration: cell.duration,
522 pitch: Pitch::from_midi(cell.key.0),
523 velocity: cell.velocity.0.max(1),
524 channel: cell.channel,
525 articulation: Articulation::Normal,
526 },
527 }),
528 _ => None,
529 }
530}
531
532fn tick_time_to_time(time: sim_lib_midi_core::TickTime) -> Time {
533 Ratio::new(time.ticks, i64::from(time.tpq) * 4)
534}
535
536fn time_expr(time: Time) -> Expr {
537 map(vec![
538 ("numer", Expr::String(time.numer().to_string())),
539 ("denom", Expr::String(time.denom().to_string())),
540 ])
541}
542
543fn pitch_label(pitch: Pitch) -> String {
544 pitch
545 .to_midi()
546 .map(|midi| format!("midi:{midi}"))
547 .unwrap_or_else(|| format!("semitone:{}", pitch.semitone()))
548}
549
550fn map(entries: Vec<(&'static str, Expr)>) -> Expr {
551 Expr::Map(
552 entries
553 .into_iter()
554 .map(|(key, value)| (Expr::Symbol(Symbol::new(key)), value))
555 .collect(),
556 )
557}