4 * This file is part of OpenTTD.
5 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
6 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
10 /* @file midifile.cpp Parser for standard MIDI files */
12 #include "midifile.hpp"
13 #include "../fileio_func.h"
14 #include "../fileio_type.h"
15 #include "../core/endian_func.hpp"
20 /* implementation based on description at: http://www.somascape.org/midi/tech/mfile.html */
24 * Owning byte buffer readable as a stream.
25 * RAII-compliant to make teardown in error situations easier.
33 * Construct buffer from data in a file.
34 * If file does not have sufficient bytes available, the object is constructed
35 * in an error state, that causes all further function calls to fail.
36 * @param file file to read from at current position
37 * @param len number of bytes to read
39 ByteBuffer(FILE *file
, size_t len
)
41 this->buf
= MallocT
<byte
>(len
);
42 if (fread(this->buf
, 1, len
, file
) == len
) {
52 * Destructor, frees the buffer.
60 * Return whether the buffer was constructed successfully.
61 * @return true is the buffer contains data
65 return this->buflen
> 0;
69 * Return whether reading has reached the end of the buffer.
70 * @return true if there are no more bytes available to read
74 return this->pos
>= this->buflen
;
78 * Read a single byte from the buffer.
79 * @param[out] b returns the read value
80 * @return true if a byte was available for reading
82 bool ReadByte(byte
&b
)
84 if (this->IsEnd()) return false;
85 b
= this->buf
[this->pos
++];
90 * Read a MIDI file variable length value.
91 * Each byte encodes 7 bits of the value, most-significant bits are encoded first.
92 * If the most significant bit in a byte is set, there are further bytes encoding the value.
93 * @param[out] res returns the read value
94 * @return true if there was data available
96 bool ReadVariableLength(uint32
&res
)
101 if (this->IsEnd()) return false;
102 b
= this->buf
[this->pos
++];
103 res
= (res
<< 7) | (b
& 0x7F);
109 * Read bytes into a buffer.
110 * @param[out] dest buffer to copy info
111 * @param length number of bytes to read
112 * @return true if the requested number of bytes were available
114 bool ReadBuffer(byte
*dest
, size_t length
)
116 if (this->IsEnd()) return false;
117 if (this->buflen
- this->pos
< length
) return false;
118 memcpy(dest
, this->buf
+ this->pos
, length
);
124 * Skip over a number of bytes in the buffer.
125 * @param count number of bytes to skip over
126 * @return true if there were enough bytes available
128 bool Skip(size_t count
)
130 if (this->IsEnd()) return false;
131 if (this->buflen
- this->pos
< count
) return false;
137 * Go a number of bytes back to re-read.
138 * @param count number of bytes to go back
139 * @return true if at least count bytes had been read previously
141 bool Rewind(size_t count
)
143 if (count
> this->pos
) return false;
149 static bool ReadTrackChunk(FILE *file
, MidiFile
&target
)
153 const byte magic
[] = { 'M', 'T', 'r', 'k' };
154 if (fread(buf
, sizeof(magic
), 1, file
) != 1) {
157 if (memcmp(magic
, buf
, sizeof(magic
)) != 0) {
161 /* read chunk length and then the whole chunk */
163 if (fread(&chunk_length
, 1, 4, file
) != 4) {
166 chunk_length
= FROM_BE32(chunk_length
);
168 ByteBuffer
chunk(file
, chunk_length
);
169 if (!chunk
.IsValid()) {
173 target
.blocks
.push_back(MidiFile::DataBlock());
174 MidiFile::DataBlock
*block
= &target
.blocks
.back();
176 byte last_status
= 0;
177 bool running_sysex
= false;
178 while (!chunk
.IsEnd()) {
179 /* read deltatime for event, start new block */
180 uint32 deltatime
= 0;
181 if (!chunk
.ReadVariableLength(deltatime
)) {
185 target
.blocks
.push_back(MidiFile::DataBlock(block
->ticktime
+ deltatime
));
186 block
= &target
.blocks
.back();
189 /* read status byte */
191 if (!chunk
.ReadByte(status
)) {
195 if ((status
& 0x80) == 0) {
196 /* high bit not set means running status message, status is same as last
197 * convert to explicit status */
199 status
= last_status
;
201 } else if ((status
& 0xF0) != 0xF0) {
202 /* Regular channel message */
203 last_status
= status
;
206 switch (status
& 0xF0) {
209 case MIDIST_POLYPRESS
:
210 case MIDIST_CONTROLLER
:
211 case MIDIST_PITCHBEND
:
212 /* 3 byte messages */
213 data
= block
->data
.Append(3);
215 if (!chunk
.ReadBuffer(&data
[1], 2)) {
220 case MIDIST_CHANPRESS
:
221 /* 2 byte messages */
222 data
= block
->data
.Append(2);
224 if (!chunk
.ReadByte(data
[1])) {
231 } else if (status
== MIDIST_SMF_META
) {
232 /* Meta event, read event type byte and data length */
233 if (!chunk
.ReadByte(buf
[0])) {
237 if (!chunk
.ReadVariableLength(length
)) {
242 /* end of track, no more data (length != 0 is illegal) */
243 return (length
== 0);
246 if (length
!= 3) return false;
247 if (!chunk
.ReadBuffer(buf
, 3)) return false;
248 target
.tempos
.push_back(MidiFile::TempoChange(block
->ticktime
, buf
[0] << 16 | buf
[1] << 8 | buf
[2]));
251 /* unimportant meta event, skip over it */
252 if (!chunk
.Skip(length
)) {
257 } else if (status
== MIDIST_SYSEX
|| (status
== MIDIST_SMF_ESCAPE
&& running_sysex
)) {
258 /* System exclusive message */
260 if (!chunk
.ReadVariableLength(length
)) {
263 byte
*data
= block
->data
.Append(length
+ 1);
265 if (!chunk
.ReadBuffer(data
+ 1, length
)) {
268 if (data
[length
] != 0xF7) {
269 /* engage Casio weirdo mode - convert to normal sysex */
270 running_sysex
= true;
271 *block
->data
.Append() = 0xF7;
273 running_sysex
= false;
275 } else if (status
== MIDIST_SMF_ESCAPE
) {
276 /* Escape sequence */
278 if (!chunk
.ReadVariableLength(length
)) {
281 byte
*data
= block
->data
.Append(length
);
282 if (!chunk
.ReadBuffer(data
, length
)) {
286 /* Messages undefined in standard midi files:
287 * 0xF1 - MIDI time code quarter frame
288 * 0xF2 - Song position pointer
290 * 0xF4 - undefined/reserved
291 * 0xF5 - undefined/reserved
292 * 0xF6 - Tune request for analog synths
293 * 0xF8..0xFE - System real-time messages
303 bool TicktimeAscending(const T
&a
, const T
&b
)
305 return a
.ticktime
< b
.ticktime
;
308 static bool FixupMidiData(MidiFile
&target
)
310 /* Sort all tempo changes and events */
311 std::sort(target
.tempos
.begin(), target
.tempos
.end(), TicktimeAscending
<MidiFile::TempoChange
>);
312 std::sort(target
.blocks
.begin(), target
.blocks
.end(), TicktimeAscending
<MidiFile::DataBlock
>);
314 if (target
.tempos
.size() == 0) {
315 /* no tempo information, assume 120 bpm (500,000 microseconds per beat */
316 target
.tempos
.push_back(MidiFile::TempoChange(0, 500000));
318 /* add sentinel tempo at end */
319 target
.tempos
.push_back(MidiFile::TempoChange(UINT32_MAX
, 0));
321 /* merge blocks with identical tick times */
322 std::vector
<MidiFile::DataBlock
> merged_blocks
;
323 uint32 last_ticktime
= 0;
324 for (size_t i
= 0; i
< target
.blocks
.size(); i
++) {
325 MidiFile::DataBlock
&block
= target
.blocks
[i
];
326 if (block
.ticktime
> last_ticktime
|| merged_blocks
.size() == 0) {
327 merged_blocks
.push_back(block
);
328 last_ticktime
= block
.ticktime
;
330 byte
*datadest
= merged_blocks
.back().data
.Append(block
.data
.Length());
331 memcpy(datadest
, block
.data
.Begin(), block
.data
.Length());
334 std::swap(merged_blocks
, target
.blocks
);
336 /* annotate blocks with real time */
338 uint32 last_realtime
= 0;
339 size_t cur_tempo
= 0, cur_block
= 0;
340 while (cur_block
< target
.blocks
.size()) {
341 MidiFile::DataBlock
&block
= target
.blocks
[cur_block
];
342 MidiFile::TempoChange
&tempo
= target
.tempos
[cur_tempo
];
343 MidiFile::TempoChange
&next_tempo
= target
.tempos
[cur_tempo
+1];
344 if (block
.ticktime
<= next_tempo
.ticktime
) {
345 /* block is within the current tempo */
346 int64 tickdiff
= block
.ticktime
- last_ticktime
;
347 last_ticktime
= block
.ticktime
;
348 last_realtime
+= uint32(tickdiff
* tempo
.tempo
/ target
.tickdiv
);
349 block
.realtime
= last_realtime
;
352 /* tempo change occurs before this block */
353 int64 tickdiff
= next_tempo
.ticktime
- last_ticktime
;
354 last_ticktime
= next_tempo
.ticktime
;
355 last_realtime
+= uint32(tickdiff
* tempo
.tempo
/ target
.tickdiv
); // current tempo until the tempo change
364 * Read the header of a standard MIDI file.
365 * @param[in] filename name of file to read from
366 * @param[out] header filled with data read
367 * @return true if the file could be opened and contained a header with correct format
369 bool MidiFile::ReadSMFHeader(const char *filename
, SMFHeader
&header
)
371 FILE *file
= FioFOpenFile(filename
, "rb", Subdirectory::BASESET_DIR
);
372 if (!file
) return false;
373 bool result
= ReadSMFHeader(file
, header
);
379 * Read the header of a standard MIDI file.
380 * The function will consume 14 bytes from the current file pointer position.
381 * @param[in] file open file to read from (should be in binary mode)
382 * @param[out] header filled with data read
383 * @return true if a header in correct format could be read from the file
385 bool MidiFile::ReadSMFHeader(FILE *file
, SMFHeader
&header
)
387 /* Try to read header, fixed size */
389 if (fread(buffer
, sizeof(buffer
), 1, file
) != 1) {
393 /* check magic, 'MThd' followed by 4 byte length indicator (always = 6 in SMF) */
394 const byte magic
[] = { 'M', 'T', 'h', 'd', 0x00, 0x00, 0x00, 0x06 };
395 if (MemCmpT(buffer
, magic
, sizeof(magic
)) != 0) {
399 /* read the parameters of the file */
400 header
.format
= (buffer
[8] << 8) | buffer
[9];
401 header
.tracks
= (buffer
[10] << 8) | buffer
[11];
402 header
.tickdiv
= (buffer
[12] << 8) | buffer
[13];
407 * Load a standard MIDI file.
408 * @param filename name of the file to load
409 * @returns true if loaded was successful
411 bool MidiFile::LoadFile(const char *filename
)
413 this->blocks
.clear();
414 this->tempos
.clear();
417 bool success
= false;
418 FILE *file
= FioFOpenFile(filename
, "rb", Subdirectory::BASESET_DIR
);
421 if (!ReadSMFHeader(file
, header
)) goto cleanup
;
423 /* Only format 0 (single-track) and format 1 (multi-track single-song) are accepted for now */
424 if (header
.format
!= 0 && header
.format
!= 1) goto cleanup
;
425 /* Doesn't support SMPTE timecode files */
426 if ((header
.tickdiv
& 0x8000) != 0) goto cleanup
;
428 this->tickdiv
= header
.tickdiv
;
430 for (; header
.tracks
> 0; header
.tracks
--) {
431 if (!ReadTrackChunk(file
, *this)) {
436 success
= FixupMidiData(*this);
444 * Move data from other to this, and clears other.
445 * @param other object containing loaded data to take over
447 void MidiFile::MoveFrom(MidiFile
&other
)
449 std::swap(this->blocks
, other
.blocks
);
450 std::swap(this->tempos
, other
.tempos
);
451 this->tickdiv
= other
.tickdiv
;
453 other
.blocks
.clear();
454 other
.tempos
.clear();