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 }