1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
#![forbid(unsafe_code)]
use thiserror::Error;
/// Errors raised while reading or writing a Standard MIDI File.
#[derive(Debug, Error, Clone, PartialEq, Eq)]
pub enum SmfError {
/// The `MThd`/`MTrk` chunk header was malformed.
#[error("invalid header at byte {offset}")]
InvalidHeader {
/// Byte offset of the bad header.
offset: usize,
},
/// The byte stream ended before the structure was complete.
#[error("unexpected end of file at byte {offset}")]
UnexpectedEof {
/// Byte offset where more input was expected.
offset: usize,
},
/// A variable-length quantity was not terminated within four bytes.
#[error("invalid VLQ at byte {offset}")]
InvalidVlq {
/// Byte offset where the VLQ began.
offset: usize,
},
/// The header carried an invalid metrical or SMPTE division.
#[error("invalid SMF division 0x{raw:04x} at byte {offset}")]
InvalidDivision {
/// Byte offset of the division field.
offset: usize,
/// The raw division value.
raw: u16,
},
/// A configured defensive read limit was exceeded.
#[error("SMF {kind} limit exceeded at byte {offset}: requested {actual}, maximum {maximum}")]
LimitExceeded {
/// Byte offset of the value or event that crossed the limit.
offset: usize,
/// Resource being bounded.
kind: SmfLimitKind,
/// Requested or observed amount.
actual: usize,
/// Configured maximum.
maximum: usize,
},
/// A bounded allocation failed even though its requested size was within
/// the configured limits.
#[error("SMF allocation of {requested} items failed at byte {offset}")]
AllocationFailed {
/// Byte offset of the structure that required the allocation.
offset: usize,
/// Number of items requested.
requested: usize,
},
/// A data byte appeared with no running status in effect.
#[error("malformed running status at byte {offset}")]
MalformedRunningStatus {
/// Byte offset of the offending data byte.
offset: usize,
},
/// A status byte that the reader does not handle was encountered.
#[error("unsupported MIDI status 0x{status:02x} at byte {offset}")]
UnsupportedStatus {
/// Byte offset of the status byte.
offset: usize,
/// The unsupported status byte.
status: u8,
},
/// A channel message carried an out-of-range data byte.
#[error("invalid channel payload at byte {offset}")]
InvalidChannelData {
/// Byte offset of the bad data.
offset: usize,
},
/// A recognised meta event used a payload length forbidden by SMF.
#[error(
"invalid length {actual} for meta event 0x{type_byte:02x} at byte {offset}; expected {expected}"
)]
InvalidMetaLength {
/// Byte offset of the meta type byte.
offset: usize,
/// Meta event type.
type_byte: u8,
/// Required payload length.
expected: usize,
/// Encoded payload length.
actual: usize,
},
/// A track chunk ended without its required end-of-track meta event.
#[error("missing end-of-track event at byte {offset}")]
MissingEndOfTrack {
/// Byte offset immediately after the track chunk body.
offset: usize,
},
/// A system message used an invalid status, length, or data byte.
#[error("invalid system event 0x{status:02x} at byte {offset}")]
InvalidSystemEvent {
/// Byte offset of the status or first invalid data byte.
offset: usize,
/// System status byte.
status: u8,
},
/// The header format and the track count are inconsistent (for example,
/// format 0 with more than one track).
#[error("SMF format/track count mismatch")]
FormatTrackMismatch,
/// Format 2 contains independent patterns and cannot be flattened onto a
/// shared timeline without selecting a track.
#[error("SMF format 2 patterns require explicit track selection")]
IndependentPatternsCannotMerge,
/// The track count cannot be represented in the SMF header.
#[error("SMF track count {0} is outside 0..=65535")]
TrackCountOutOfRange(usize),
/// The ticks-per-quarter value cannot be represented as metrical SMF TPQ.
#[error("SMF ticks-per-quarter {0} cannot be written as metrical TPQ")]
TpqOutOfRange(u32),
/// An event time could not be represented exactly at the file resolution.
#[error("event time cannot be represented exactly at target TPQ")]
InexactEventTime,
/// Track events were not monotonic in absolute time, yielding a negative
/// delta.
#[error("track events are not monotonic in absolute time")]
NegativeDelta,
/// A track delta cannot be represented as an SMF four-byte VLQ.
#[error("SMF delta {0} exceeds the four-byte VLQ limit")]
DeltaOutOfRange(i64),
/// A track chunk body cannot be represented in the SMF chunk length field.
#[error("SMF chunk length {0} exceeds u32::MAX")]
ChunkTooLarge(usize),
/// A meta or SysEx payload length cannot be represented as an SMF four-byte
/// VLQ.
#[error("SMF payload length {0} exceeds the four-byte VLQ limit")]
PayloadTooLarge(usize),
/// Absolute tick accumulation overflowed the in-memory event time.
#[error("SMF absolute tick time overflow at byte {offset}")]
TimeOverflow {
/// Byte offset of the delta that overflowed.
offset: usize,
},
}
/// Defensive parser resources that can be bounded with [`SmfReadLimits`].
///
/// [`SmfReadLimits`]: crate::SmfReadLimits
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum SmfLimitKind {
/// Complete input bytes.
FileBytes,
/// Header chunk body bytes.
HeaderBytes,
/// Declared track count.
TrackCount,
/// One track chunk body.
TrackBytes,
/// Events across the complete file.
EventCount,
/// One meta or system-exclusive payload.
EventPayloadBytes,
/// Meta and system-exclusive payload bytes across the complete file.
TotalPayloadBytes,
}
impl std::fmt::Display for SmfLimitKind {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str(match self {
Self::FileBytes => "file-bytes",
Self::HeaderBytes => "header-bytes",
Self::TrackCount => "track-count",
Self::TrackBytes => "track-bytes",
Self::EventCount => "event-count",
Self::EventPayloadBytes => "event-payload-bytes",
Self::TotalPayloadBytes => "total-payload-bytes",
})
}
}