1use std::cmp::Reverse;
8use std::error::Error;
9use std::fmt;
10
11use midly::num::{u4, u7, u14, u15, u24, u28};
12use midly::{
13 Format, Header, MetaMessage, MidiMessage, PitchBend, Timing, TrackEvent, TrackEventKind,
14};
15
16use super::{NoteList, Velocity};
17use crate::cast;
18use crate::units::{Bpm, Seconds};
19
20pub const TICKS_PER_BEAT: u16 = 480;
22
23pub const DEFAULT_VELOCITY: Velocity = Velocity::from_const(100);
26
27pub const MIN_NOTE_LENGTH: Seconds = Seconds::from_const(0.010);
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33pub enum Velocities {
34 Constant(Velocity),
37 FromNotes(Velocity),
40}
41
42pub fn write_midi(
60 notes: &NoteList,
61 bpm: Bpm,
62 velocities: Velocities,
63) -> Result<Vec<u8>, MidiWriteError> {
64 encode(notes, bpm, velocities, None)
65}
66
67pub fn write_named_midi(
75 notes: &NoteList,
76 bpm: Bpm,
77 velocities: Velocities,
78 name: &str,
79) -> Result<Vec<u8>, MidiWriteError> {
80 encode(notes, bpm, velocities, Some(name))
81}
82
83#[must_use]
86pub fn midi_track_name(bytes: &[u8]) -> Option<String> {
87 let smf = midly::Smf::parse(bytes).ok()?;
88 smf.tracks
89 .iter()
90 .flatten()
91 .find_map(|event| match event.kind {
92 TrackEventKind::Meta(MetaMessage::TrackName(name)) => {
93 Some(String::from_utf8_lossy(name).into_owned())
94 }
95 _ => None,
96 })
97}
98
99fn encode(
100 notes: &NoteList,
101 bpm: Bpm,
102 velocities: Velocities,
103 name: Option<&str>,
104) -> Result<Vec<u8>, MidiWriteError> {
105 let ticks_per_second = ticks_per_second(bpm);
106 let tick = |seconds: Seconds| {
107 cast::round_to_usize(seconds.get() * ticks_per_second)
108 .and_then(|tick| u64::try_from(tick).ok())
109 .ok_or(MidiWriteError::TooLong(seconds))
110 };
111 let mut spans = Vec::with_capacity(notes.notes().len());
112 for note in notes.notes() {
113 let start = tick(note.onset())?;
114 let floor = note.onset().get() + MIN_NOTE_LENGTH.get();
115 let end = tick(Seconds::clamped(note.offset().get().max(floor)))?.max(start + 1);
116 let key = cast::round_to_usize(note.pitch().get())
117 .and_then(|key| u8::try_from(key).ok())
118 .and_then(u7::try_from)
119 .unwrap_or(u7::max_value());
121 let velocity = match velocities {
122 Velocities::Constant(velocity) => velocity,
123 Velocities::FromNotes(default) => note.velocity().unwrap_or(default),
124 };
125 spans.push((key, start, end, velocity));
126 }
127 encode_spans(spans, &[], bpm, name)
128}
129
130pub(crate) type Span = (u7, u64, u64, Velocity);
132
133pub(crate) fn encode_spans(
138 mut spans: Vec<Span>,
139 bends: &[(u64, u16)],
140 bpm: Bpm,
141 name: Option<&str>,
142) -> Result<Vec<u8>, MidiWriteError> {
143 if spans.is_empty() {
144 return Err(MidiWriteError::NoNotes);
145 }
146 let ticks_per_second = ticks_per_second(bpm);
147 spans.sort_by_key(|&(key, start, end, _)| (key, start, Reverse(end)));
150 spans.dedup_by_key(|&mut (key, start, ..)| (key, start));
151 let mut events = Vec::with_capacity(2 * spans.len() + bends.len());
152 for (i, &(key, start, end, velocity)) in spans.iter().enumerate() {
153 let end = match spans.get(i + 1) {
154 Some(&(next_key, next_start, ..)) if next_key == key => end.min(next_start),
155 _ => end,
156 };
157 events.push((start, Event::On, key, velocity.get()));
158 events.push((end, Event::Off, key, 0));
159 }
160 for &(at, value) in bends {
161 events.push((at, Event::Bend(value), u7::new(0), 0));
162 }
163 events.sort_by_key(|&(at, event, key, _)| (at, event, key));
164
165 let channel = u4::new(0);
166 let mut track: Vec<TrackEvent<'_>> = name
167 .map(|name| meta(MetaMessage::TrackName(name.as_bytes())))
168 .into_iter()
169 .collect();
170 track.extend([
171 meta(MetaMessage::Tempo(u24::new(tempo_us(bpm)))),
172 meta(MetaMessage::TimeSignature(4, 2, 24, 8)),
173 ]);
174 let mut now = 0_u64;
175 for (at, event, key, velocity) in events {
176 let message = match event {
177 Event::On => MidiMessage::NoteOn {
178 key,
179 vel: u7::new(velocity),
180 },
181 Event::Off => MidiMessage::NoteOff {
182 key,
183 vel: u7::new(0),
184 },
185 Event::Bend(value) => MidiMessage::PitchBend {
186 bend: PitchBend(u14::new(value)),
187 },
188 };
189 track.push(TrackEvent {
190 delta: u32::try_from(at - now).ok().and_then(u28::try_from).ok_or(
191 MidiWriteError::TooLong(Seconds::clamped(cast::u64_to_f64(at) / ticks_per_second)),
192 )?,
193 kind: TrackEventKind::Midi { channel, message },
194 });
195 now = at;
196 }
197 track.push(meta(MetaMessage::EndOfTrack));
198
199 let header = Header::new(
200 Format::SingleTrack,
201 Timing::Metrical(u15::new(TICKS_PER_BEAT)),
202 );
203 let mut bytes = Vec::new();
204 midly::write_std(&header, std::iter::once(&track), &mut bytes)
205 .map_err(|source| MidiWriteError::Encode(source.to_string()))?;
206 Ok(bytes)
207}
208
209#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
211enum Event {
212 Off,
213 Bend(u16),
214 On,
215}
216
217pub(crate) fn ticks_per_second(bpm: Bpm) -> f64 {
219 bpm.get() / 60.0 * f64::from(TICKS_PER_BEAT)
220}
221
222fn tempo_us(bpm: Bpm) -> u32 {
224 cast::round_to_usize(60e6 / bpm.get())
226 .and_then(|us| u32::try_from(us).ok())
227 .unwrap_or(u32::MAX)
228}
229
230fn meta(message: MetaMessage<'_>) -> TrackEvent<'_> {
231 TrackEvent {
232 delta: u28::new(0),
233 kind: TrackEventKind::Meta(message),
234 }
235}
236
237#[derive(Clone, Debug, PartialEq)]
239pub enum MidiWriteError {
240 NoNotes,
242 TooLong(Seconds),
244 Encode(String),
246}
247
248impl fmt::Display for MidiWriteError {
249 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
250 match self {
251 Self::NoNotes => write!(f, "cannot write a MIDI file without notes"),
252 Self::TooLong(at) => write!(
253 f,
254 "cannot write a note at {at}: it lies further from the previous note than a MIDI file can hold. Split the stem"
255 ),
256 Self::Encode(message) => write!(f, "cannot encode the MIDI file: {message}"),
257 }
258 }
259}
260
261impl Error for MidiWriteError {}