midly/lib.rs
1//! # Overview
2//!
3//! `midly` is a full-featured MIDI parser and writer, focused on performance.
4//!
5//! Parsing a `.mid` file can be as simple as:
6//!
7//! ```rust
8//! # #[cfg(feature = "alloc")] {
9//! use midly::Smf;
10//!
11//! let smf = Smf::parse(include_bytes!("../test-asset/Clementi.mid")).unwrap();
12//!
13//! for (i, track) in smf.tracks.iter().enumerate() {
14//! println!("track {} has {} events", i, track.len());
15//! }
16//! # }
17//! ```
18//!
19//! # Parsing Standard Midi Files (`.mid` files)
20//!
21//! Parsing Standard Midi Files is usually done through the [`Smf`](struct.Smf.html) struct (or if
22//! working in a `no_std` environment without an allocator, through the [`parse`](fn.parse.html)
23//! function).
24//!
25//! Note that most types in this crate have a lifetime parameter, because they reference the bytes
26//! in the original file (in order to avoid allocations).
27//! For this reason, reading a file and parsing it must be done in two separate steps:
28//!
29//! ```rust
30//! # #[cfg(feature = "alloc")] {
31//! use std::fs;
32//! use midly::Smf;
33//!
34//! // Load bytes into a buffer
35//! let bytes = fs::read("test-asset/Clementi.mid").unwrap();
36//!
37//! // Parse bytes in a separate step
38//! let smf = Smf::parse(&bytes).unwrap();
39//! # }
40//! ```
41//!
42//! # Writing Standard Midi Files
43//!
44//! Saving `.mid` files is as simple as using the `Smf::save` method:
45//!
46//! ```rust
47//! # #[cfg(feature = "std")] {
48//! # use std::fs;
49//! # use midly::Smf;
50//! // Parse file
51//! let bytes = fs::read("test-asset/Clementi.mid").unwrap();
52//! let smf = Smf::parse(&bytes).unwrap();
53//!
54//! // Rewrite file
55//! smf.save("test-asset/ClementiRewritten.mid").unwrap();
56//! # }
57//! ```
58//!
59//! SMF files can also be written to an arbitrary writer:
60//!
61//! ```rust
62//! # #[cfg(feature = "std")] {
63//! # use std::fs;
64//! # use midly::Smf;
65//! # let bytes = fs::read("test-asset/Clementi.mid").unwrap();
66//! # let smf = Smf::parse(&bytes).unwrap();
67//! let mut in_memory = Vec::new();
68//! smf.write(&mut in_memory).unwrap();
69//!
70//! println!("midi file fits in {} bytes!", in_memory.len());
71//! # }
72//! ```
73//!
74//! # Parsing standalone MIDI messages
75//!
76//! When using an OS API such as [`midir`](https://docs.rs/midir),
77//! [`LiveEvent`](live/enum.LiveEvent.html) can be used to parse the raw MIDI bytes:
78//!
79//! ```rust
80//! use midly::{live::LiveEvent, MidiMessage};
81//!
82//! fn on_midi(event: &[u8]) {
83//! let event = LiveEvent::parse(event).unwrap();
84//! match event {
85//! LiveEvent::Midi { channel, message } => match message {
86//! MidiMessage::NoteOn { key, vel } => {
87//! println!("hit note {} on channel {}", key, channel);
88//! }
89//! _ => {}
90//! },
91//! _ => {}
92//! }
93//! }
94//! ```
95//!
96//! # Writing standalone MIDI messages
97//!
98//! Raw MIDI message bytes can be produced for consumption by OS APIs, such as
99//! [`midir`](https://docs.rs/midir), through the
100//! [`LiveEvent::write`](live/enum.LiveEvent.html#method.write) method:
101//!
102//! ```rust
103//! use midly::{live::LiveEvent, MidiMessage};
104//! # fn write_midi(bytes: &[u8]) {}
105//!
106//! fn note_on(channel: u8, key: u8) {
107//! let ev = LiveEvent::Midi {
108//! channel: channel.into(),
109//! message: MidiMessage::NoteOn {
110//! key: key.into(),
111//! vel: 127.into(),
112//! },
113//! };
114//! # let mut stack_buf = [0; 3];
115//! # let mut buf = {
116//! # #[cfg(feature = "alloc")] {
117//! let mut buf = Vec::new();
118//! # buf
119//! # }
120//! # #[cfg(not(feature = "alloc"))] {
121//! # &mut stack_buf[..]
122//! # }
123//! # };
124//! ev.write(&mut buf).unwrap();
125//! write_midi(&buf[..]);
126//! }
127//! # note_on(3, 61); note_on(2, 50); note_on(2,61);
128//! ```
129//!
130//! # About features
131//!
132//! The following cargo features are available to enable or disable parts of the crate:
133//!
134//! - `parallel` (enabled by default)
135//!
136//! This feature enables the use of multiple threads when parsing large midi files.
137//!
138//! Disabling this feature will remove the dependency on `rayon`.
139//!
140//! - `std` (enabled by default)
141//!
142//! This feature enables integration with `std`, for example implementing `std::error::Error` for
143//! [`midly::Error`](struct.Error.html), support for writing to `std::io::Write` streams, among
144//! others.
145//!
146//! Disabling this feature will make the crate `no_std`.
147//!
148//! - `alloc` (enabled by default)
149//!
150//! This feature enables allocations both for ergonomics and performance.
151//!
152//! Disabling both the `std` and the `alloc` feature will make the crate fully `no_std`, but will
153//! reduce functionality to a minimum.
154//! For example, the [`Smf`](struct.Smf.html) type is unavailable without the `alloc` feature.
155//! All types that are unavailable when a feature is disabled are marked as such in their
156//! documentation.
157//!
158//! - `strict`
159//!
160//! By default `midly` will attempt to plow through non-standard and even obviously corrupted
161//! files, throwing away any unreadable data, or even entire tracks in the worst scenario.
162//! By enabling the `strict` feature the parser will reject uncompliant data and do
163//! additional checking, throwing errors of the kind
164//! [`ErrorKind::Malformed`](enum.ErrorKind.html#variant.Malformed) when such a situation arises.
165
166#![cfg_attr(not(any(test, feature = "std")), no_std)]
167#![warn(missing_docs)]
168
169#[cfg(feature = "alloc")]
170extern crate alloc;
171
172macro_rules! bail {
173 ($err:expr) => {{
174 return Err($err.into());
175 }};
176}
177macro_rules! ensure {
178 ($cond:expr, $err:expr) => {{
179 if !$cond {
180 bail!($err)
181 }
182 }};
183}
184
185/// All of the errors this crate produces.
186#[macro_use]
187mod error;
188
189#[macro_use]
190mod prelude {
191 #[cfg(feature = "std")]
192 pub(crate) use crate::io::IoWrap;
193 pub(crate) use crate::{
194 error::{ErrorKind, Result, ResultExt, StdResult},
195 io::{Seek, Write, WriteCounter, WriteResult},
196 primitive::{u14, u24, u28, u4, u7, IntRead, IntReadBottom7, SplitChecked},
197 };
198 #[cfg(feature = "alloc")]
199 pub(crate) use alloc::{boxed::Box, vec, vec::Vec};
200 pub(crate) use core::{convert::TryFrom, fmt, marker::PhantomData, mem};
201 #[cfg(feature = "std")]
202 pub(crate) use std::{fs::File, io, path::Path};
203
204 macro_rules! bit_range {
205 ($val:expr, $range:expr) => {{
206 let mask = (1 << ($range.end - $range.start)) - 1;
207 ($val >> $range.start) & mask
208 }};
209 }
210}
211
212mod arena;
213mod event;
214pub mod io;
215pub mod live;
216mod primitive;
217mod riff;
218mod smf;
219pub mod stream;
220
221#[cfg(feature = "std")]
222pub use crate::smf::write_std;
223#[cfg(feature = "alloc")]
224pub use crate::{
225 arena::Arena,
226 smf::{BytemappedTrack, Smf, SmfBytemap, Track},
227};
228pub use crate::{
229 error::{Error, ErrorKind, Result},
230 event::{MetaMessage, MidiMessage, PitchBend, TrackEvent, TrackEventKind},
231 primitive::{Format, Fps, SmpteTime, Timing},
232 smf::{parse, write, EventBytemapIter, EventIter, Header, TrackIter},
233};
234
235/// Exotically-sized integers used by the MIDI standard.
236pub mod num {
237 pub use crate::primitive::{u14, u15, u24, u28, u4, u7};
238}
239
240#[cfg(test)]
241mod test;