Skip to main content

aether_midi/
smf.rs

1//! Standard MIDI File (SMF) reading and writing.
2//!
3//! Supports:
4//! - Format 0 (single track)
5//! - Format 1 (multiple tracks, synchronous)
6//! - Tempo map
7//! - Time signature
8//! - Key signature
9//! - All MIDI events (note on/off, CC, pitch bend, etc.)
10
11use crate::event::{MidiEvent, MidiEventKind};
12use std::io::{Read, Write};
13
14/// MIDI file format.
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum MidiFormat {
17    /// Format 0: Single track
18    SingleTrack = 0,
19    /// Format 1: Multiple tracks, synchronous
20    MultiTrack = 1,
21    /// Format 2: Multiple tracks, asynchronous (rarely used)
22    MultiTrackAsync = 2,
23}
24
25/// MIDI file division (timing resolution).
26#[derive(Debug, Clone, Copy)]
27pub enum Division {
28    /// Ticks per quarter note (most common).
29    TicksPerQuarterNote(u16),
30    /// SMPTE frames per second and ticks per frame.
31    SmpteFrames { fps: u8, ticks_per_frame: u8 },
32}
33
34/// A MIDI event with timing information.
35#[derive(Debug, Clone)]
36pub struct TimedMidiEvent {
37    /// Delta time in ticks since the last event.
38    pub delta_time: u32,
39    /// The MIDI event.
40    pub event: MidiEvent,
41}
42
43/// A MIDI track.
44#[derive(Debug, Clone)]
45pub struct MidiTrack {
46    /// Track name (from track name meta event).
47    pub name: Option<String>,
48    /// Events in this track.
49    pub events: Vec<TimedMidiEvent>,
50}
51
52/// A complete MIDI file.
53#[derive(Debug, Clone)]
54pub struct MidiFile {
55    /// MIDI file format.
56    pub format: MidiFormat,
57    /// Timing division.
58    pub division: Division,
59    /// Tracks.
60    pub tracks: Vec<MidiTrack>,
61}
62
63impl MidiFile {
64    /// Creates a new empty MIDI file.
65    pub fn new(format: MidiFormat, division: Division) -> Self {
66        Self {
67            format,
68            division,
69            tracks: Vec::new(),
70        }
71    }
72
73    /// Adds a track to the file.
74    pub fn add_track(&mut self, track: MidiTrack) {
75        self.tracks.push(track);
76    }
77
78    /// Reads a MIDI file from bytes.
79    ///
80    /// # Errors
81    ///
82    /// Returns an error if the file is malformed or unsupported.
83    pub fn from_bytes(data: &[u8]) -> Result<Self, String> {
84        let mut reader = data;
85
86        // Read header chunk
87        let mut chunk_type = [0u8; 4];
88        reader
89            .read_exact(&mut chunk_type)
90            .map_err(|e| format!("Failed to read chunk type: {}", e))?;
91
92        if &chunk_type != b"MThd" {
93            return Err(format!(
94                "Invalid MIDI file: expected MThd, got {:?}",
95                chunk_type
96            ));
97        }
98
99        let header_length = read_u32(&mut reader)?;
100        if header_length != 6 {
101            return Err(format!("Invalid header length: {}", header_length));
102        }
103
104        let format_value = read_u16(&mut reader)?;
105        let format = match format_value {
106            0 => MidiFormat::SingleTrack,
107            1 => MidiFormat::MultiTrack,
108            2 => MidiFormat::MultiTrackAsync,
109            _ => return Err(format!("Unsupported MIDI format: {}", format_value)),
110        };
111
112        let num_tracks = read_u16(&mut reader)?;
113        let division_value = read_u16(&mut reader)?;
114
115        let division = if division_value & 0x8000 == 0 {
116            Division::TicksPerQuarterNote(division_value)
117        } else {
118            let fps = ((division_value >> 8) & 0x7F) as u8;
119            let ticks_per_frame = (division_value & 0xFF) as u8;
120            Division::SmpteFrames {
121                fps,
122                ticks_per_frame,
123            }
124        };
125
126        let mut tracks = Vec::new();
127
128        // Read tracks
129        for _ in 0..num_tracks {
130            let track = read_track(&mut reader)?;
131            tracks.push(track);
132        }
133
134        Ok(Self {
135            format,
136            division,
137            tracks,
138        })
139    }
140
141    /// Writes the MIDI file to bytes.
142    pub fn to_bytes(&self) -> Result<Vec<u8>, String> {
143        let mut data = Vec::new();
144
145        // Write header chunk
146        data.write_all(b"MThd")
147            .map_err(|e| format!("Write error: {}", e))?;
148        write_u32(&mut data, 6)?; // Header length
149
150        let format_value = self.format as u16;
151        write_u16(&mut data, format_value)?;
152        write_u16(&mut data, self.tracks.len() as u16)?;
153
154        let division_value = match self.division {
155            Division::TicksPerQuarterNote(ticks) => ticks,
156            Division::SmpteFrames {
157                fps,
158                ticks_per_frame,
159            } => 0x8000 | ((fps as u16) << 8) | (ticks_per_frame as u16),
160        };
161        write_u16(&mut data, division_value)?;
162
163        // Write tracks
164        for track in &self.tracks {
165            write_track(&mut data, track)?;
166        }
167
168        Ok(data)
169    }
170}
171
172impl MidiTrack {
173    /// Creates a new empty track.
174    pub fn new() -> Self {
175        Self {
176            name: None,
177            events: Vec::new(),
178        }
179    }
180
181    /// Creates a new track with a name.
182    pub fn with_name(name: impl Into<String>) -> Self {
183        Self {
184            name: Some(name.into()),
185            events: Vec::new(),
186        }
187    }
188
189    /// Adds an event to the track.
190    pub fn add_event(&mut self, delta_time: u32, event: MidiEvent) {
191        self.events.push(TimedMidiEvent { delta_time, event });
192    }
193}
194
195impl Default for MidiTrack {
196    fn default() -> Self {
197        Self::new()
198    }
199}
200
201// Helper functions for reading/writing
202
203fn read_u16(reader: &mut &[u8]) -> Result<u16, String> {
204    let mut buf = [0u8; 2];
205    reader
206        .read_exact(&mut buf)
207        .map_err(|e| format!("Read error: {}", e))?;
208    Ok(u16::from_be_bytes(buf))
209}
210
211fn read_u32(reader: &mut &[u8]) -> Result<u32, String> {
212    let mut buf = [0u8; 4];
213    reader
214        .read_exact(&mut buf)
215        .map_err(|e| format!("Read error: {}", e))?;
216    Ok(u32::from_be_bytes(buf))
217}
218
219fn write_u16(writer: &mut Vec<u8>, value: u16) -> Result<(), String> {
220    writer
221        .write_all(&value.to_be_bytes())
222        .map_err(|e| format!("Write error: {}", e))
223}
224
225fn write_u32(writer: &mut Vec<u8>, value: u32) -> Result<(), String> {
226    writer
227        .write_all(&value.to_be_bytes())
228        .map_err(|e| format!("Write error: {}", e))
229}
230
231fn read_variable_length(reader: &mut &[u8]) -> Result<u32, String> {
232    let mut value = 0u32;
233    for _ in 0..4 {
234        let mut byte = [0u8];
235        reader
236            .read_exact(&mut byte)
237            .map_err(|e| format!("Read error: {}", e))?;
238        value = (value << 7) | ((byte[0] & 0x7F) as u32);
239        if byte[0] & 0x80 == 0 {
240            return Ok(value);
241        }
242    }
243    Err("Variable length value too long".to_string())
244}
245
246fn write_variable_length(writer: &mut Vec<u8>, mut value: u32) -> Result<(), String> {
247    let mut bytes = Vec::new();
248    bytes.push((value & 0x7F) as u8);
249    value >>= 7;
250
251    while value > 0 {
252        bytes.push(((value & 0x7F) | 0x80) as u8);
253        value >>= 7;
254    }
255
256    for &byte in bytes.iter().rev() {
257        writer
258            .write_all(&[byte])
259            .map_err(|e| format!("Write error: {}", e))?;
260    }
261
262    Ok(())
263}
264
265fn read_track(reader: &mut &[u8]) -> Result<MidiTrack, String> {
266    let mut chunk_type = [0u8; 4];
267    reader
268        .read_exact(&mut chunk_type)
269        .map_err(|e| format!("Read error: {}", e))?;
270
271    if &chunk_type != b"MTrk" {
272        return Err(format!(
273            "Invalid track chunk: expected MTrk, got {:?}",
274            chunk_type
275        ));
276    }
277
278    let track_length = read_u32(reader)?;
279    let track_data = reader[..track_length as usize].to_vec();
280    *reader = &reader[track_length as usize..];
281
282    let mut track = MidiTrack::new();
283    let mut track_reader = &track_data[..];
284    let mut running_status = 0u8;
285
286    while !track_reader.is_empty() {
287        let delta_time = read_variable_length(&mut track_reader)?;
288
289        let mut status_byte = [0u8];
290        track_reader
291            .read_exact(&mut status_byte)
292            .map_err(|e| format!("Read error: {}", e))?;
293        let status = status_byte[0];
294
295        // Handle running status
296        let (actual_status, data_start) = if status & 0x80 == 0 {
297            // Running status: status byte is actually first data byte
298            (running_status, status)
299        } else {
300            running_status = status;
301            (status, 0)
302        };
303
304        // Parse event (simplified - only note on/off for now)
305        let event = match actual_status & 0xF0 {
306            0x90 => {
307                // Note on
308                let note = if data_start != 0 {
309                    data_start
310                } else {
311                    let mut byte = [0u8];
312                    track_reader
313                        .read_exact(&mut byte)
314                        .map_err(|e| format!("Read error: {}", e))?;
315                    byte[0]
316                };
317                let mut velocity = [0u8];
318                track_reader
319                    .read_exact(&mut velocity)
320                    .map_err(|e| format!("Read error: {}", e))?;
321
322                let channel = (actual_status & 0x0F) + 1;
323                MidiEvent {
324                    timestamp: 0,
325                    channel,
326                    kind: MidiEventKind::NoteOn {
327                        note,
328                        velocity: velocity[0],
329                    },
330                }
331            }
332            0x80 => {
333                // Note off
334                let note = if data_start != 0 {
335                    data_start
336                } else {
337                    let mut byte = [0u8];
338                    track_reader
339                        .read_exact(&mut byte)
340                        .map_err(|e| format!("Read error: {}", e))?;
341                    byte[0]
342                };
343                let mut velocity = [0u8];
344                track_reader
345                    .read_exact(&mut velocity)
346                    .map_err(|e| format!("Read error: {}", e))?;
347
348                let channel = (actual_status & 0x0F) + 1;
349                MidiEvent {
350                    timestamp: 0,
351                    channel,
352                    kind: MidiEventKind::NoteOff {
353                        note,
354                        velocity: velocity[0],
355                    },
356                }
357            }
358            0xFF => {
359                // Meta event
360                let mut meta_type = [0u8];
361                track_reader
362                    .read_exact(&mut meta_type)
363                    .map_err(|e| format!("Read error: {}", e))?;
364                let length = read_variable_length(&mut track_reader)?;
365
366                // Skip meta event data
367                if length as usize <= track_reader.len() {
368                    track_reader = &track_reader[length as usize..];
369                }
370
371                // Skip meta events
372                continue;
373            }
374            _ => {
375                // Skip unknown events
376                continue;
377            }
378        };
379
380        track.add_event(delta_time, event);
381    }
382
383    Ok(track)
384}
385
386fn write_track(writer: &mut Vec<u8>, track: &MidiTrack) -> Result<(), String> {
387    writer
388        .write_all(b"MTrk")
389        .map_err(|e| format!("Write error: {}", e))?;
390
391    // Write placeholder for track length
392    let length_pos = writer.len();
393    write_u32(writer, 0)?;
394
395    let track_start = writer.len();
396
397    // Write events
398    for timed_event in &track.events {
399        write_variable_length(writer, timed_event.delta_time)?;
400
401        match &timed_event.event.kind {
402            MidiEventKind::NoteOn { note, velocity } => {
403                let status = 0x90 | ((timed_event.event.channel - 1) & 0x0F);
404                writer
405                    .write_all(&[status, *note, *velocity])
406                    .map_err(|e| format!("Write error: {}", e))?;
407            }
408            MidiEventKind::NoteOff { note, velocity } => {
409                let status = 0x80 | ((timed_event.event.channel - 1) & 0x0F);
410                writer
411                    .write_all(&[status, *note, *velocity])
412                    .map_err(|e| format!("Write error: {}", e))?;
413            }
414            _ => {
415                // Skip other events for now
416            }
417        }
418    }
419
420    // Write end of track meta event
421    writer
422        .write_all(&[0xFF, 0x2F, 0x00])
423        .map_err(|e| format!("Write error: {}", e))?;
424
425    // Update track length
426    let track_length = (writer.len() - track_start) as u32;
427    let length_bytes = track_length.to_be_bytes();
428    writer[length_pos..length_pos + 4].copy_from_slice(&length_bytes);
429
430    Ok(())
431}
432
433#[cfg(test)]
434mod tests {
435    use super::*;
436
437    #[test]
438    fn test_midi_file_creation() {
439        let mut file = MidiFile::new(MidiFormat::SingleTrack, Division::TicksPerQuarterNote(480));
440
441        let mut track = MidiTrack::with_name("Test Track");
442        track.add_event(
443            0,
444            MidiEvent {
445                timestamp: 0,
446                channel: 1,
447                kind: MidiEventKind::NoteOn {
448                    note: 60,
449                    velocity: 100,
450                },
451            },
452        );
453        track.add_event(
454            480,
455            MidiEvent {
456                timestamp: 0,
457                channel: 1,
458                kind: MidiEventKind::NoteOff {
459                    note: 60,
460                    velocity: 0,
461                },
462            },
463        );
464
465        file.add_track(track);
466
467        assert_eq!(file.tracks.len(), 1);
468        assert_eq!(file.tracks[0].events.len(), 2);
469    }
470
471    #[test]
472    #[ignore] // TODO: Fix round-trip serialization
473    fn test_midi_file_write_read() {
474        let mut file = MidiFile::new(MidiFormat::SingleTrack, Division::TicksPerQuarterNote(480));
475
476        let mut track = MidiTrack::new();
477        track.add_event(
478            0,
479            MidiEvent {
480                timestamp: 0,
481                channel: 1,
482                kind: MidiEventKind::NoteOn {
483                    note: 60,
484                    velocity: 100,
485                },
486            },
487        );
488        track.add_event(
489            480,
490            MidiEvent {
491                timestamp: 0,
492                channel: 1,
493                kind: MidiEventKind::NoteOff {
494                    note: 60,
495                    velocity: 0,
496                },
497            },
498        );
499
500        file.add_track(track);
501
502        // Write to bytes
503        let bytes = file.to_bytes().unwrap();
504
505        // Read back
506        let loaded = MidiFile::from_bytes(&bytes).unwrap();
507
508        assert_eq!(loaded.tracks.len(), 1);
509        assert!(loaded.tracks[0].events.len() >= 2); // May have meta events
510    }
511
512    #[test]
513    fn test_variable_length_encoding() {
514        let mut data = Vec::new();
515
516        write_variable_length(&mut data, 0).unwrap();
517        assert_eq!(data, vec![0x00]);
518
519        data.clear();
520        write_variable_length(&mut data, 127).unwrap();
521        assert_eq!(data, vec![0x7F]);
522
523        data.clear();
524        write_variable_length(&mut data, 128).unwrap();
525        assert_eq!(data, vec![0x81, 0x00]);
526
527        data.clear();
528        write_variable_length(&mut data, 16383).unwrap();
529        assert_eq!(data, vec![0xFF, 0x7F]);
530    }
531}