Update readme.md
[openttd-joker.git] / src / music / midifile.cpp
blobeb7e02303e43e3d56cf1987e1af0a46d702a9c3c
1 /* $Id$ */
3 /*
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/>.
8 */
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"
16 #include "midi.h"
17 #include <algorithm>
20 /* implementation based on description at: http://www.somascape.org/midi/tech/mfile.html */
23 /**
24 * Owning byte buffer readable as a stream.
25 * RAII-compliant to make teardown in error situations easier.
27 class ByteBuffer {
28 byte *buf;
29 size_t buflen;
30 size_t pos;
31 public:
32 /**
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) {
43 this->buflen = len;
44 this->pos = 0;
45 } else {
46 /* invalid state */
47 this->buflen = 0;
51 /**
52 * Destructor, frees the buffer.
54 ~ByteBuffer()
56 free(this->buf);
59 /**
60 * Return whether the buffer was constructed successfully.
61 * @return true is the buffer contains data
63 bool IsValid() const
65 return this->buflen > 0;
68 /**
69 * Return whether reading has reached the end of the buffer.
70 * @return true if there are no more bytes available to read
72 bool IsEnd() const
74 return this->pos >= this->buflen;
77 /**
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++];
86 return true;
89 /**
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)
98 res = 0;
99 byte b = 0;
100 do {
101 if (this->IsEnd()) return false;
102 b = this->buf[this->pos++];
103 res = (res << 7) | (b & 0x7F);
104 } while (b & 0x80);
105 return true;
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);
119 this->pos += length;
120 return true;
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;
132 this->pos += count;
133 return true;
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;
144 this->pos -= count;
145 return true;
149 static bool ReadTrackChunk(FILE *file, MidiFile &target)
151 byte buf[4];
153 const byte magic[] = { 'M', 'T', 'r', 'k' };
154 if (fread(buf, sizeof(magic), 1, file) != 1) {
155 return false;
157 if (memcmp(magic, buf, sizeof(magic)) != 0) {
158 return false;
161 /* read chunk length and then the whole chunk */
162 uint32 chunk_length;
163 if (fread(&chunk_length, 1, 4, file) != 4) {
164 return false;
166 chunk_length = FROM_BE32(chunk_length);
168 ByteBuffer chunk(file, chunk_length);
169 if (!chunk.IsValid()) {
170 return false;
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)) {
182 return false;
184 if (deltatime > 0) {
185 target.blocks.push_back(MidiFile::DataBlock(block->ticktime + deltatime));
186 block = &target.blocks.back();
189 /* read status byte */
190 byte status;
191 if (!chunk.ReadByte(status)) {
192 return false;
195 if ((status & 0x80) == 0) {
196 /* high bit not set means running status message, status is same as last
197 * convert to explicit status */
198 chunk.Rewind(1);
199 status = last_status;
200 goto running_status;
201 } else if ((status & 0xF0) != 0xF0) {
202 /* Regular channel message */
203 last_status = status;
204 running_status:
205 byte *data;
206 switch (status & 0xF0) {
207 case MIDIST_NOTEOFF:
208 case MIDIST_NOTEON:
209 case MIDIST_POLYPRESS:
210 case MIDIST_CONTROLLER:
211 case MIDIST_PITCHBEND:
212 /* 3 byte messages */
213 data = block->data.Append(3);
214 data[0] = status;
215 if (!chunk.ReadBuffer(&data[1], 2)) {
216 return false;
218 break;
219 case MIDIST_PROGCHG:
220 case MIDIST_CHANPRESS:
221 /* 2 byte messages */
222 data = block->data.Append(2);
223 data[0] = status;
224 if (!chunk.ReadByte(data[1])) {
225 return false;
227 break;
228 default:
229 NOT_REACHED();
231 } else if (status == MIDIST_SMF_META) {
232 /* Meta event, read event type byte and data length */
233 if (!chunk.ReadByte(buf[0])) {
234 return false;
236 uint32 length = 0;
237 if (!chunk.ReadVariableLength(length)) {
238 return false;
240 switch (buf[0]) {
241 case 0x2F:
242 /* end of track, no more data (length != 0 is illegal) */
243 return (length == 0);
244 case 0x51:
245 /* tempo change */
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]));
249 break;
250 default:
251 /* unimportant meta event, skip over it */
252 if (!chunk.Skip(length)) {
253 return false;
255 break;
257 } else if (status == MIDIST_SYSEX || (status == MIDIST_SMF_ESCAPE && running_sysex)) {
258 /* System exclusive message */
259 uint32 length = 0;
260 if (!chunk.ReadVariableLength(length)) {
261 return false;
263 byte *data = block->data.Append(length + 1);
264 data[0] = 0xF0;
265 if (!chunk.ReadBuffer(data + 1, length)) {
266 return false;
268 if (data[length] != 0xF7) {
269 /* engage Casio weirdo mode - convert to normal sysex */
270 running_sysex = true;
271 *block->data.Append() = 0xF7;
272 } else {
273 running_sysex = false;
275 } else if (status == MIDIST_SMF_ESCAPE) {
276 /* Escape sequence */
277 uint32 length = 0;
278 if (!chunk.ReadVariableLength(length)) {
279 return false;
281 byte *data = block->data.Append(length);
282 if (!chunk.ReadBuffer(data, length)) {
283 return false;
285 } else {
286 /* Messages undefined in standard midi files:
287 * 0xF1 - MIDI time code quarter frame
288 * 0xF2 - Song position pointer
289 * 0xF3 - Song select
290 * 0xF4 - undefined/reserved
291 * 0xF5 - undefined/reserved
292 * 0xF6 - Tune request for analog synths
293 * 0xF8..0xFE - System real-time messages
295 return false;
299 NOT_REACHED();
302 template<typename T>
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;
329 } else {
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 */
337 last_ticktime = 0;
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;
350 cur_block++;
351 } else {
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
356 cur_tempo++;
360 return true;
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);
374 FioFCloseFile(file);
375 return result;
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 */
388 byte buffer[14];
389 if (fread(buffer, sizeof(buffer), 1, file) != 1) {
390 return false;
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) {
396 return false;
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];
403 return true;
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();
415 this->tickdiv = 0;
417 bool success = false;
418 FILE *file = FioFOpenFile(filename, "rb", Subdirectory::BASESET_DIR);
420 SMFHeader header;
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)) {
432 goto cleanup;
436 success = FixupMidiData(*this);
438 cleanup:
439 FioFCloseFile(file);
440 return success;
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();
455 other.tickdiv = 0;