1 /**
2 `mididi.def` contains definition enums and types important to MIDI files.
3 
4 The implementation (and some of the documentation) is based on this specification:
5 https://www.cs.cmu.edu/~music/cmsip/readings/Standard-MIDI-file-format-updated.pdf
6 
7 Authors:
8     https://github.com/w2ptr
9 */
10 module mididi.def;
11 
12 // Copyright Wout Huynen 2021.
13 // Distributed under the Boost Software License, Version 1.0.
14 // (See accompanying file LICENSE.txt or copy at
15 // https://www.boost.org/LICENSE_1_0.txt)
16 
17 /**
18 `TrackFormat` is the MIDI data's format.
19 
20 This format is given in the header chunk of a MIDI file.
21 */
22 enum TrackFormat : ushort {
23     /**
24     If the format is `single`, that means the MIDI data consists of a header
25     chunk followed by a single track chunk.
26     */
27     single = 0,
28 
29     /**
30     If the format is `simultaneous`, that means the MIDI track data consists of
31     multiple tracks, running in parallel.
32     */
33     simultaneous = 1,
34 
35     /**
36     If the format is `sequential`, that means the MIDI track data consists of
37     multiple tracks, running sequentially.
38     */
39     sequential = 2,
40 }
41 
42 /**
43 A track event is one of the following:
44 - a MIDI event, meaning it carries around any message (channel or system),
45 except system exclusive messages;
46 - a system exclusive event, which offers an escape to transmit arbitrary bytes;
47 - a meta event, meaning it carries around other meta information.
48 
49 Use `isMIDIEvent()`, `isSysExEvent()` and `isMetaEvent()` to find which kind of
50 event it is from its status byte.
51 
52 See also:
53     `mididi.types.TrackEvent`; `mididi.types.MIDIEvent`,
54     `mididi.types.SysExEvent` and `mididi.types.MetaEvent`.
55 */
56 bool isMIDIEvent(ubyte statusByte) @nogc nothrow pure @safe {
57     // a status byte for an event always has to start with a 1
58     return statusByte >= 0x80 && statusByte != 0xF0 && statusByte != 0xF7 &&
59         statusByte != 0xFF;
60 }
61 /// ditto
62 bool isSysExEvent(ubyte statusByte) @nogc nothrow pure @safe {
63     return statusByte == 0xF0 || statusByte == 0xF7;
64 }
65 /// ditto
66 bool isMetaEvent(ubyte statusByte) @nogc nothrow pure @safe {
67     return statusByte == 0xFF;
68 }
69 
70 /**
71 `isChannelMessage()` and `isSystemMessage()` are used to find if a MIDI event
72 is a channel message or a system message.
73 
74 In turn, a channel message can be either a channel voice message or a channel
75 mode message. A system message can be either a system common message or a
76 system realtime message.
77 */
78 bool isChannelMessage(ubyte statusByte) @nogc nothrow pure @safe {
79     immutable b = cast(ubyte) (statusByte >> 4);
80     return b >= 0x8 && b <= 0xE;
81 }
82 /// ditto
83 bool isSystemMessage(ubyte statusByte) @nogc nothrow pure @safe {
84     return statusByte >= 0xF0 && statusByte <= 0xFE; // TODO: 0xFF? 0xFE? which one?
85 }
86 
87 /**
88 Returns:
89     how many data bytes should be read for the message with status byte
90     `statusByte`
91 Preconditions:
92     `statusByte` must be from a MIDI event
93 */
94 size_t getDataLength(ubyte statusByte) @nogc nothrow pure @safe
95 in (isMIDIEvent(statusByte)) {
96     import std.conv : text;
97 
98     if (isChannelMessage(statusByte)) {
99         immutable t = cast(ChannelMessageType) (statusByte >> 4);
100         with (ChannelMessageType) {
101             if (t == programChange || t == channelPressure) {
102                 return 1;
103             }
104 
105             if (
106                 t == noteOn || t == noteOff || t == polyphonicKeyPressure ||
107                 t == controlChangeOrMode || t == pitchWheelChange
108             ) {
109                 return 2;
110             }
111         }
112     } else if (isSystemMessage(statusByte)) {
113         immutable t = cast(SystemMessageType) statusByte;
114         with (SystemMessageType) {
115             if (
116                 t == tuneRequest || t == timingClock || t == start ||
117                 t == continue_ || t == stop || t == activeSensing
118             ) {
119                 return 0;
120             }
121 
122             if (t == songSelect) {
123                 return 1;
124             }
125 
126             if (t == songPositionPointer) {
127                 return 2;
128             }
129         }
130     }
131 
132     assert(false, "unreachable");
133 }
134 
135 /**
136 `ChannelMessageType` enumerates the possible types of channel messages.
137 
138 The underlying value of this enumeration is the four upper bits of the status
139 byte (and always starts with a 1 bit). After all, a channel message has the
140 message type in the four upper bits and a channel identifier in the four lower
141 bits of the status byte.
142 
143 The functions `isChannelVoiceMessage()` and `isChannelModeMessage()` need the
144 data bytes in addition to the status byte to identify if a channel message is a
145 voice message or a mode message, because some status byte values overlap (see
146 for example `ChannelMessageType.controlChangeOrMode`).
147 
148 See also:
149     `mididi.def.isChannelMessage`
150 */
151 enum ChannelMessageType : ubyte {
152     ///
153     noteOff = 0x8,
154 
155     ///
156     noteOn = 0x9,
157 
158     ///
159     polyphonicKeyPressure = 0xA,
160 
161     /**
162     NOTE: this kind of channel message can be either a Control Change message
163     or a Channel Mode message, depending on the data bytes. Use
164     `isChannelModeMessage()` to find out which it is.
165     */
166     controlChangeOrMode = 0xB,
167 
168     ///
169     programChange = 0xC,
170 
171     ///
172     channelPressure = 0xD,
173 
174     ///
175     pitchWheelChange = 0xE,
176 }
177 
178 /// ditto
179 bool isChannelVoiceMessage(ubyte statusByte, ubyte[2] dataBytes) @nogc nothrow pure @safe {
180     immutable b = cast(ubyte) (statusByte >> 4);
181     if (b == 0xB) {
182         return dataBytes[0] <= 0x77;
183     }
184     return b >= 0x8 && b <= 0xE;
185 }
186 /// ditto
187 bool isChannelModeMessage(ubyte statusByte, ubyte[2] dataBytes) @nogc nothrow pure @safe {
188     immutable b = cast(ubyte) (statusByte >> 4);
189     return b == 0xB && dataBytes[0] >= 0x78;
190 }
191 
192 /**
193 `SystemMessageType` enumerates the possible types of system message. Some of
194 these are system common messages, others are system realtime messages.
195 
196 If for a `ubyte x` we have that `isSystemMessage(x)` is true, then it can be
197 safely cast to `SystemMessageType` using `cast(SystemMessageType) x`.
198 
199 A system message is either a a "system common message" or a "system real-time
200 message". You can use `isSystemCommonMessage(x)` and
201 `isSystemRealTimeMessage(x)` to identify which is true, or compare to the enum
202 members to find out the exact message type.
203 
204 See also:
205     `mididi.def.isSystemMessage`
206 */
207 enum SystemMessageType : ubyte {
208     /**
209     This event can give any type of information specific to the manufacturer.
210 
211     A system exclusive message can consist of several packets, where each
212     packet is placed in one event. Each event then starts with the byte `0xF7`,
213     and the final event is also terminated by `0xF7`.
214     */
215     systemExclusive = 0xF0,
216 
217     ///
218     songPositionPointer = 0xF2,
219 
220     ///
221     songSelect = 0xF3,
222 
223     ///
224     tuneRequest = 0xF6,
225 
226     ///
227     endOfExclusive = 0xF7,
228 
229     ///
230     timingClock = 0xF8,
231 
232     ///
233     start = 0xFA,
234 
235     ///
236     continue_ = 0xFB,
237 
238     ///
239     stop = 0xFC,
240 
241     ///
242     activeSensing = 0xFE,
243 }
244 
245 /// ditto
246 bool isSystemCommonMessage(SystemMessageType type) @nogc nothrow pure @safe {
247     return isSystemCommonMessage(cast(ubyte) type);
248 }
249 /// ditto
250 bool isSystemCommonMessage(ubyte statusByte) @nogc nothrow pure @safe {
251     return statusByte >= 0xF0 && statusByte <= 0xF7;
252 }
253 /// ditto
254 bool isSystemRealTimeMessage(SystemMessageType type) @nogc nothrow pure @safe {
255      return isSystemRealTimeMessage(cast(ubyte) type);
256 }
257 /// ditto
258 bool isSystemRealTimeMessage(ubyte statusByte) @nogc nothrow pure @safe {
259     return statusByte >= 0xF8 && statusByte <= 0xFE; // TODO: 0xFF? 0xFE? which one?
260 }
261 
262 /**
263 `MetaEventType` enumerates the possible meta event types.
264 
265 Cast to `ubyte` to get the underlying value.
266 
267 Note:
268     do not use a value of this type in a `final switch`, because it might have
269     a value that is not identified in this enum, since values can always be
270     added later.
271 See also:
272     `mididi.def.isMetaEvent`
273 */
274 enum MetaEventType : ubyte {
275     /// 0x00
276     sequenceNumber = 0x00,
277 
278     /// 0x01
279     text = 0x01,
280 
281     /// 0x02
282     copyright = 0x02,
283     // TODO: this event should come in the first track, before any other event
284 
285     /// 0x03
286     sequenceName = 0x03,
287 
288     /// 0x04
289     instrumentName = 0x04,
290 
291     /// 0x05
292     lyric = 0x05,
293 
294     /// 0x06
295     marker = 0x06,
296 
297     /// 0x07
298     cuePoint = 0x07,
299 
300     /// 0x20
301     midiChannelPrefix = 0x20,
302 
303     /// 0x2F
304     endOfTrack = 0x2F,
305     // TODO: must come at end
306 
307     /// 0x51
308     setTempo = 0x51,
309 
310     /// 0x54
311     smpteOffset = 0x54,
312     // TODO: must come before any event with nonzero delta-time
313 
314     /// 0x58
315     timeSignature = 0x58,
316 
317     /// 0x59
318     keySignature = 0x59,
319 
320     /// 0x7F
321     sequencerSpecific = 0x7F,
322 }