Add: Draw network password indicator lock in company drop down list. (#7079)
[openttd-github.git] / src / music / midifile.cpp
blob91f83c529d976879a5e941588a2cc8559a5f5347
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 "../string_func.h"
16 #include "../core/endian_func.hpp"
17 #include "../base_media_base.h"
18 #include "midi.h"
19 #include <algorithm>
21 #include "../console_func.h"
22 #include "../console_internal.h"
25 /* SMF reader based on description at: http://www.somascape.org/midi/tech/mfile.html */
28 static MidiFile *_midifile_instance = NULL;
30 /**
31 * Owning byte buffer readable as a stream.
32 * RAII-compliant to make teardown in error situations easier.
34 class ByteBuffer {
35 byte *buf;
36 size_t buflen;
37 size_t pos;
38 public:
39 /**
40 * Construct buffer from data in a file.
41 * If file does not have sufficient bytes available, the object is constructed
42 * in an error state, that causes all further function calls to fail.
43 * @param file file to read from at current position
44 * @param len number of bytes to read
46 ByteBuffer(FILE *file, size_t len)
48 this->buf = MallocT<byte>(len);
49 if (fread(this->buf, 1, len, file) == len) {
50 this->buflen = len;
51 this->pos = 0;
52 } else {
53 /* invalid state */
54 this->buflen = 0;
58 /**
59 * Destructor, frees the buffer.
61 ~ByteBuffer()
63 free(this->buf);
66 /**
67 * Return whether the buffer was constructed successfully.
68 * @return true is the buffer contains data
70 bool IsValid() const
72 return this->buflen > 0;
75 /**
76 * Return whether reading has reached the end of the buffer.
77 * @return true if there are no more bytes available to read
79 bool IsEnd() const
81 return this->pos >= this->buflen;
84 /**
85 * Read a single byte from the buffer.
86 * @param[out] b returns the read value
87 * @return true if a byte was available for reading
89 bool ReadByte(byte &b)
91 if (this->IsEnd()) return false;
92 b = this->buf[this->pos++];
93 return true;
96 /**
97 * Read a MIDI file variable length value.
98 * Each byte encodes 7 bits of the value, most-significant bits are encoded first.
99 * If the most significant bit in a byte is set, there are further bytes encoding the value.
100 * @param[out] res returns the read value
101 * @return true if there was data available
103 bool ReadVariableLength(uint32 &res)
105 res = 0;
106 byte b = 0;
107 do {
108 if (this->IsEnd()) return false;
109 b = this->buf[this->pos++];
110 res = (res << 7) | (b & 0x7F);
111 } while (b & 0x80);
112 return true;
116 * Read bytes into a buffer.
117 * @param[out] dest buffer to copy info
118 * @param length number of bytes to read
119 * @return true if the requested number of bytes were available
121 bool ReadBuffer(byte *dest, size_t length)
123 if (this->IsEnd()) return false;
124 if (this->buflen - this->pos < length) return false;
125 memcpy(dest, this->buf + this->pos, length);
126 this->pos += length;
127 return true;
131 * Skip over a number of bytes in the buffer.
132 * @param count number of bytes to skip over
133 * @return true if there were enough bytes available
135 bool Skip(size_t count)
137 if (this->IsEnd()) return false;
138 if (this->buflen - this->pos < count) return false;
139 this->pos += count;
140 return true;
144 * Go a number of bytes back to re-read.
145 * @param count number of bytes to go back
146 * @return true if at least count bytes had been read previously
148 bool Rewind(size_t count)
150 if (count > this->pos) return false;
151 this->pos -= count;
152 return true;
156 static bool ReadTrackChunk(FILE *file, MidiFile &target)
158 byte buf[4];
160 const byte magic[] = { 'M', 'T', 'r', 'k' };
161 if (fread(buf, sizeof(magic), 1, file) != 1) {
162 return false;
164 if (memcmp(magic, buf, sizeof(magic)) != 0) {
165 return false;
168 /* Read chunk length and then the whole chunk */
169 uint32 chunk_length;
170 if (fread(&chunk_length, 1, 4, file) != 4) {
171 return false;
173 chunk_length = FROM_BE32(chunk_length);
175 ByteBuffer chunk(file, chunk_length);
176 if (!chunk.IsValid()) {
177 return false;
180 target.blocks.push_back(MidiFile::DataBlock());
181 MidiFile::DataBlock *block = &target.blocks.back();
183 byte last_status = 0;
184 bool running_sysex = false;
185 while (!chunk.IsEnd()) {
186 /* Read deltatime for event, start new block */
187 uint32 deltatime = 0;
188 if (!chunk.ReadVariableLength(deltatime)) {
189 return false;
191 if (deltatime > 0) {
192 target.blocks.push_back(MidiFile::DataBlock(block->ticktime + deltatime));
193 block = &target.blocks.back();
196 /* Read status byte */
197 byte status;
198 if (!chunk.ReadByte(status)) {
199 return false;
202 if ((status & 0x80) == 0) {
203 /* High bit not set means running status message, status is same as last
204 * convert to explicit status */
205 chunk.Rewind(1);
206 status = last_status;
207 goto running_status;
208 } else if ((status & 0xF0) != 0xF0) {
209 /* Regular channel message */
210 last_status = status;
211 running_status:
212 byte *data;
213 switch (status & 0xF0) {
214 case MIDIST_NOTEOFF:
215 case MIDIST_NOTEON:
216 case MIDIST_POLYPRESS:
217 case MIDIST_CONTROLLER:
218 case MIDIST_PITCHBEND:
219 /* 3 byte messages */
220 data = block->data.Append(3);
221 data[0] = status;
222 if (!chunk.ReadBuffer(&data[1], 2)) {
223 return false;
225 break;
226 case MIDIST_PROGCHG:
227 case MIDIST_CHANPRESS:
228 /* 2 byte messages */
229 data = block->data.Append(2);
230 data[0] = status;
231 if (!chunk.ReadByte(data[1])) {
232 return false;
234 break;
235 default:
236 NOT_REACHED();
238 } else if (status == MIDIST_SMF_META) {
239 /* Meta event, read event type byte and data length */
240 if (!chunk.ReadByte(buf[0])) {
241 return false;
243 uint32 length = 0;
244 if (!chunk.ReadVariableLength(length)) {
245 return false;
247 switch (buf[0]) {
248 case 0x2F:
249 /* end of track, no more data (length != 0 is illegal) */
250 return (length == 0);
251 case 0x51:
252 /* tempo change */
253 if (length != 3) return false;
254 if (!chunk.ReadBuffer(buf, 3)) return false;
255 target.tempos.push_back(MidiFile::TempoChange(block->ticktime, buf[0] << 16 | buf[1] << 8 | buf[2]));
256 break;
257 default:
258 /* unimportant meta event, skip over it */
259 if (!chunk.Skip(length)) {
260 return false;
262 break;
264 } else if (status == MIDIST_SYSEX || (status == MIDIST_SMF_ESCAPE && running_sysex)) {
265 /* System exclusive message */
266 uint32 length = 0;
267 if (!chunk.ReadVariableLength(length)) {
268 return false;
270 byte *data = block->data.Append(length + 1);
271 data[0] = 0xF0;
272 if (!chunk.ReadBuffer(data + 1, length)) {
273 return false;
275 if (data[length] != 0xF7) {
276 /* Engage Casio weirdo mode - convert to normal sysex */
277 running_sysex = true;
278 *block->data.Append() = 0xF7;
279 } else {
280 running_sysex = false;
282 } else if (status == MIDIST_SMF_ESCAPE) {
283 /* Escape sequence */
284 uint32 length = 0;
285 if (!chunk.ReadVariableLength(length)) {
286 return false;
288 byte *data = block->data.Append(length);
289 if (!chunk.ReadBuffer(data, length)) {
290 return false;
292 } else {
293 /* Messages undefined in standard midi files:
294 * 0xF1 - MIDI time code quarter frame
295 * 0xF2 - Song position pointer
296 * 0xF3 - Song select
297 * 0xF4 - undefined/reserved
298 * 0xF5 - undefined/reserved
299 * 0xF6 - Tune request for analog synths
300 * 0xF8..0xFE - System real-time messages
302 return false;
306 NOT_REACHED();
309 template<typename T>
310 bool TicktimeAscending(const T &a, const T &b)
312 return a.ticktime < b.ticktime;
315 static bool FixupMidiData(MidiFile &target)
317 /* Sort all tempo changes and events */
318 std::sort(target.tempos.begin(), target.tempos.end(), TicktimeAscending<MidiFile::TempoChange>);
319 std::sort(target.blocks.begin(), target.blocks.end(), TicktimeAscending<MidiFile::DataBlock>);
321 if (target.tempos.size() == 0) {
322 /* No tempo information, assume 120 bpm (500,000 microseconds per beat */
323 target.tempos.push_back(MidiFile::TempoChange(0, 500000));
325 /* Add sentinel tempo at end */
326 target.tempos.push_back(MidiFile::TempoChange(UINT32_MAX, 0));
328 /* Merge blocks with identical tick times */
329 std::vector<MidiFile::DataBlock> merged_blocks;
330 uint32 last_ticktime = 0;
331 for (size_t i = 0; i < target.blocks.size(); i++) {
332 MidiFile::DataBlock &block = target.blocks[i];
333 if (block.data.Length() == 0) {
334 continue;
335 } else if (block.ticktime > last_ticktime || merged_blocks.size() == 0) {
336 merged_blocks.push_back(block);
337 last_ticktime = block.ticktime;
338 } else {
339 byte *datadest = merged_blocks.back().data.Append(block.data.Length());
340 memcpy(datadest, block.data.Begin(), block.data.Length());
343 std::swap(merged_blocks, target.blocks);
345 /* Annotate blocks with real time */
346 last_ticktime = 0;
347 uint32 last_realtime = 0;
348 size_t cur_tempo = 0, cur_block = 0;
349 while (cur_block < target.blocks.size()) {
350 MidiFile::DataBlock &block = target.blocks[cur_block];
351 MidiFile::TempoChange &tempo = target.tempos[cur_tempo];
352 MidiFile::TempoChange &next_tempo = target.tempos[cur_tempo+1];
353 if (block.ticktime <= next_tempo.ticktime) {
354 /* block is within the current tempo */
355 int64 tickdiff = block.ticktime - last_ticktime;
356 last_ticktime = block.ticktime;
357 last_realtime += uint32(tickdiff * tempo.tempo / target.tickdiv);
358 block.realtime = last_realtime;
359 cur_block++;
360 } else {
361 /* tempo change occurs before this block */
362 int64 tickdiff = next_tempo.ticktime - last_ticktime;
363 last_ticktime = next_tempo.ticktime;
364 last_realtime += uint32(tickdiff * tempo.tempo / target.tickdiv); // current tempo until the tempo change
365 cur_tempo++;
369 return true;
373 * Read the header of a standard MIDI file.
374 * @param[in] filename name of file to read from
375 * @param[out] header filled with data read
376 * @return true if the file could be opened and contained a header with correct format
378 bool MidiFile::ReadSMFHeader(const char *filename, SMFHeader &header)
380 FILE *file = FioFOpenFile(filename, "rb", Subdirectory::BASESET_DIR);
381 if (!file) return false;
382 bool result = ReadSMFHeader(file, header);
383 FioFCloseFile(file);
384 return result;
388 * Read the header of a standard MIDI file.
389 * The function will consume 14 bytes from the current file pointer position.
390 * @param[in] file open file to read from (should be in binary mode)
391 * @param[out] header filled with data read
392 * @return true if a header in correct format could be read from the file
394 bool MidiFile::ReadSMFHeader(FILE *file, SMFHeader &header)
396 /* Try to read header, fixed size */
397 byte buffer[14];
398 if (fread(buffer, sizeof(buffer), 1, file) != 1) {
399 return false;
402 /* Check magic, 'MThd' followed by 4 byte length indicator (always = 6 in SMF) */
403 const byte magic[] = { 'M', 'T', 'h', 'd', 0x00, 0x00, 0x00, 0x06 };
404 if (MemCmpT(buffer, magic, sizeof(magic)) != 0) {
405 return false;
408 /* Read the parameters of the file */
409 header.format = (buffer[8] << 8) | buffer[9];
410 header.tracks = (buffer[10] << 8) | buffer[11];
411 header.tickdiv = (buffer[12] << 8) | buffer[13];
412 return true;
416 * Load a standard MIDI file.
417 * @param filename name of the file to load
418 * @returns true if loaded was successful
420 bool MidiFile::LoadFile(const char *filename)
422 _midifile_instance = this;
424 this->blocks.clear();
425 this->tempos.clear();
426 this->tickdiv = 0;
428 bool success = false;
429 FILE *file = FioFOpenFile(filename, "rb", Subdirectory::BASESET_DIR);
430 if (file == NULL) return false;
432 SMFHeader header;
433 if (!ReadSMFHeader(file, header)) goto cleanup;
435 /* Only format 0 (single-track) and format 1 (multi-track single-song) are accepted for now */
436 if (header.format != 0 && header.format != 1) goto cleanup;
437 /* Doesn't support SMPTE timecode files */
438 if ((header.tickdiv & 0x8000) != 0) goto cleanup;
440 this->tickdiv = header.tickdiv;
442 for (; header.tracks > 0; header.tracks--) {
443 if (!ReadTrackChunk(file, *this)) {
444 goto cleanup;
448 success = FixupMidiData(*this);
450 cleanup:
451 FioFCloseFile(file);
452 return success;
457 * Decoder for "MPS MIDI" format data.
458 * This format for MIDI music is also used in a few other Microprose games contemporary with Transport Tycoon.
460 * The song data are usually packed inside a CAT file, with one CAT chunk per song. The song titles are used as names for the CAT chunks.
462 * Unlike the Standard MIDI File format, which is based on the IFF structure, the MPS MIDI format is best described as two linked lists of sub-tracks,
463 * the first list contains a number of reusable "segments", and the second list contains the "master tracks". Each list is prefixed with a byte
464 * giving the number of elements in the list, and the actual list is just a byte count (BE16 format) for the segment/track followed by the actual data,
465 * there is no index as such, so the entire data must be seeked through to build an index.
467 * The actual MIDI data inside each track is almost standard MIDI, prefixing every event with a delay, encoded using the same variable-length format
468 * used in SMF. A few status codes have changed meaning in MPS MIDI: 0xFE changes control from master track to a segment, 0xFD returns from a segment
469 * to the master track, and 0xFF is used to end the song. (In Standard MIDI all those values must only occur in real-time data.)
471 * As implemented in the original decoder, there is no support for recursively calling segments from segments, i.e. code 0xFE must only occur in
472 * a master track, and code 0xFD must only occur in a segment. There are no checks made for this, it's assumed that the only input data will ever
473 * be the original game music, not music from other games, or new productions.
475 * Additionally, some program change and controller events are given special meaning, see comments in the code.
477 struct MpsMachine {
478 /** Starting parameter and playback status for one channel/track */
479 struct Channel {
480 byte cur_program; ///< program selected, used for velocity scaling (lookup into programvelocities array)
481 byte running_status; ///< last midi status code seen
482 uint16 delay; ///< frames until next command
483 uint32 playpos; ///< next byte to play this channel from
484 uint32 startpos; ///< start position of master track
485 uint32 returnpos; ///< next return position after playing a segment
486 Channel() : cur_program(0xFF), running_status(0), delay(0), playpos(0), startpos(0), returnpos(0) { }
488 Channel channels[16]; ///< playback status for each MIDI channel
489 std::vector<uint32> segments; ///< pointers into songdata to repeatable data segments
490 int16 tempo_ticks; ///< ticker that increments when playing a frame, decrements before playing a frame
491 int16 current_tempo; ///< threshold for actually playing a frame
492 int16 initial_tempo; ///< starting tempo of song
493 bool shouldplayflag; ///< not-end-of-song flag
495 static const int TEMPO_RATE;
496 static const byte programvelocities[128];
498 const byte *songdata; ///< raw data array
499 size_t songdatalen; ///< length of song data
500 MidiFile &target; ///< recipient of data
502 /** Overridden MIDI status codes used in the data format */
503 enum MpsMidiStatus {
504 MPSMIDIST_SEGMENT_RETURN = 0xFD, ///< resume playing master track from stored position
505 MPSMIDIST_SEGMENT_CALL = 0xFE, ///< store current position of master track playback, and begin playback of a segment
506 MPSMIDIST_ENDSONG = 0xFF, ///< immediately end the song
509 static void AddMidiData(MidiFile::DataBlock &block, byte b1, byte b2)
511 *block.data.Append() = b1;
512 *block.data.Append() = b2;
514 static void AddMidiData(MidiFile::DataBlock &block, byte b1, byte b2, byte b3)
516 *block.data.Append() = b1;
517 *block.data.Append() = b2;
518 *block.data.Append() = b3;
522 * Construct a TTD DOS music format decoder.
523 * @param data Buffer of song data from CAT file, ownership remains with caller
524 * @param length Length of the data buffer in bytes
525 * @param target MidiFile object to add decoded data to
527 MpsMachine(const byte *data, size_t length, MidiFile &target)
528 : songdata(data), songdatalen(length), target(target)
530 uint32 pos = 0;
531 int loopmax;
532 int loopidx;
534 /* First byte is the initial "tempo" */
535 this->initial_tempo = this->songdata[pos++];
537 /* Next byte is a count of callable segments */
538 loopmax = this->songdata[pos++];
539 for (loopidx = 0; loopidx < loopmax; loopidx++) {
540 /* Segments form a linked list in the stream,
541 * first two bytes in each is an offset to the next.
542 * Two bytes between offset to next and start of data
543 * are unaccounted for. */
544 this->segments.push_back(pos + 4);
545 pos += FROM_LE16(*(const int16 *)(this->songdata + pos));
548 /* After segments follows list of master tracks for each channel,
549 * also prefixed with a byte counting actual tracks. */
550 loopmax = this->songdata[pos++];
551 for (loopidx = 0; loopidx < loopmax; loopidx++) {
552 /* Similar structure to segments list, but also has
553 * the MIDI channel number as a byte before the offset
554 * to next track. */
555 byte ch = this->songdata[pos++];
556 this->channels[ch].startpos = pos + 4;
557 pos += FROM_LE16(*(const int16 *)(this->songdata + pos));
562 * Read an SMF-style variable length value (note duration) from songdata.
563 * @param pos Position to read from, updated to point to next byte after the value read
564 * @return Value read from data stream
566 uint16 ReadVariableLength(uint32 &pos)
568 byte b = 0;
569 uint16 res = 0;
570 do {
571 b = this->songdata[pos++];
572 res = (res << 7) + (b & 0x7F);
573 } while (b & 0x80);
574 return res;
578 * Prepare for playback from the beginning. Resets the song pointer for every track to the beginning.
580 void RestartSong()
582 for (int ch = 0; ch < 16; ch++) {
583 Channel &chandata = this->channels[ch];
584 if (chandata.startpos != 0) {
585 /* Active track, set position to beginning */
586 chandata.playpos = chandata.startpos;
587 chandata.delay = this->ReadVariableLength(chandata.playpos);
588 } else {
589 /* Inactive track, mark as such */
590 chandata.playpos = 0;
591 chandata.delay = 0;
597 * Play one frame of data from one channel
599 uint16 PlayChannelFrame(MidiFile::DataBlock &outblock, int channel)
601 uint16 newdelay = 0;
602 byte b1, b2;
603 Channel &chandata = this->channels[channel];
605 do {
606 /* Read command/status byte */
607 b1 = this->songdata[chandata.playpos++];
609 /* Command 0xFE, call segment from master track */
610 if (b1 == MPSMIDIST_SEGMENT_CALL) {
611 b1 = this->songdata[chandata.playpos++];
612 chandata.returnpos = chandata.playpos;
613 chandata.playpos = this->segments[b1];
614 newdelay = this->ReadVariableLength(chandata.playpos);
615 if (newdelay == 0) {
616 continue;
618 return newdelay;
621 /* Command 0xFD, return from segment to master track */
622 if (b1 == MPSMIDIST_SEGMENT_RETURN) {
623 chandata.playpos = chandata.returnpos;
624 chandata.returnpos = 0;
625 newdelay = this->ReadVariableLength(chandata.playpos);
626 if (newdelay == 0) {
627 continue;
629 return newdelay;
632 /* Command 0xFF, end of song */
633 if (b1 == MPSMIDIST_ENDSONG) {
634 this->shouldplayflag = false;
635 return 0;
638 /* Regular MIDI channel message status byte */
639 if (b1 >= 0x80) {
640 /* Save the status byte as running status for the channel
641 * and read another byte for first parameter to command */
642 chandata.running_status = b1;
643 b1 = this->songdata[chandata.playpos++];
646 switch (chandata.running_status & 0xF0) {
647 case MIDIST_NOTEOFF:
648 case MIDIST_NOTEON:
649 b2 = this->songdata[chandata.playpos++];
650 if (b2 != 0) {
651 /* Note on, read velocity and scale according to rules */
652 int16 velocity;
653 if (channel == 9) {
654 /* Percussion channel, fixed velocity scaling not in the table */
655 velocity = (int16)b2 * 0x50;
656 } else {
657 /* Regular channel, use scaling from table */
658 velocity = b2 * programvelocities[chandata.cur_program];
660 b2 = (velocity / 128) & 0x00FF;
661 AddMidiData(outblock, MIDIST_NOTEON + channel, b1, b2);
662 } else {
663 /* Note off */
664 AddMidiData(outblock, MIDIST_NOTEON + channel, b1, 0);
666 break;
667 case MIDIST_CONTROLLER:
668 b2 = this->songdata[chandata.playpos++];
669 if (b1 == MIDICT_MODE_MONO) {
670 /* Unknown what the purpose of this is.
671 * Occurs in "Can't get There from Here" and in "Aliens Ate my Railway" a few times each.
672 * Possibly intended to give hints to other (non-GM) music drivers decoding the song.
674 break;
675 } else if (b1 == 0) {
676 /* Standard MIDI controller 0 is "bank select", override meaning to change tempo.
677 * This is not actually used in any of the original songs. */
678 if (b2 != 0) {
679 this->current_tempo = ((int)b2) * 48 / 60;
681 break;
682 } else if (b1 == MIDICT_EFFECTS1) {
683 /* Override value of this controller, default mapping is Reverb Send Level according to MMA RP-023.
684 * Unknown what the purpose of this particular value is. */
685 b2 = 30;
687 AddMidiData(outblock, MIDIST_CONTROLLER + channel, b1, b2);
688 break;
689 case MIDIST_PROGCHG:
690 if (b1 == 0x7E) {
691 /* Program change to "Applause" is originally used
692 * to cause the song to loop, but that gets handled
693 * separately in the output driver here.
694 * Just end the song. */
695 this->shouldplayflag = false;
696 break;
698 /* Used for note velocity scaling lookup */
699 chandata.cur_program = b1;
700 /* Two programs translated to a third, this is likely to
701 * provide three different velocity scalings of "brass". */
702 if (b1 == 0x57 || b1 == 0x3F) {
703 b1 = 0x3E;
705 AddMidiData(outblock, MIDIST_PROGCHG + channel, b1);
706 break;
707 case MIDIST_PITCHBEND:
708 b2 = this->songdata[chandata.playpos++];
709 AddMidiData(outblock, MIDIST_PITCHBEND + channel, b1, b2);
710 break;
711 default:
712 break;
715 newdelay = this->ReadVariableLength(chandata.playpos);
716 } while (newdelay == 0);
718 return newdelay;
722 * Play one frame of data into a block.
724 bool PlayFrame(MidiFile::DataBlock &block)
726 /* Update tempo/ticks counter */
727 this->tempo_ticks -= this->current_tempo;
728 if (this->tempo_ticks > 0) {
729 return true;
731 this->tempo_ticks += TEMPO_RATE;
733 /* Look over all channels, play those active */
734 for (int ch = 0; ch < 16; ch++) {
735 Channel &chandata = this->channels[ch];
736 if (chandata.playpos != 0) {
737 if (chandata.delay == 0) {
738 chandata.delay = this->PlayChannelFrame(block, ch);
740 chandata.delay--;
744 return this->shouldplayflag;
748 * Perform playback of whole song.
750 bool PlayInto()
752 /* Tempo seems to be handled as TEMPO_RATE = 148 ticks per second.
753 * Use this as the tickdiv, and define the tempo to be one second (1M microseconds) per tickdiv.
754 * MIDI software loading exported files will show a bogus tempo, but playback will be correct. */
755 this->target.tickdiv = TEMPO_RATE;
756 this->target.tempos.push_back(MidiFile::TempoChange(0, 1000000));
758 /* Initialize playback simulation */
759 this->RestartSong();
760 this->shouldplayflag = true;
761 this->current_tempo = (int32)this->initial_tempo * 24 / 60;
762 this->tempo_ticks = this->current_tempo;
764 /* Always reset percussion channel to program 0 */
765 this->target.blocks.push_back(MidiFile::DataBlock());
766 AddMidiData(this->target.blocks.back(), MIDIST_PROGCHG+9, 0x00);
768 /* Technically should be an endless loop, but having
769 * a maximum (about 10 minutes) avoids getting stuck,
770 * in case of corrupted data. */
771 for (uint32 tick = 0; tick < 100000; tick+=1) {
772 this->target.blocks.push_back(MidiFile::DataBlock());
773 auto &block = this->target.blocks.back();
774 block.ticktime = tick;
775 if (!this->PlayFrame(block)) {
776 break;
779 return true;
782 /** Frames/ticks per second for music playback */
783 const int MpsMachine::TEMPO_RATE = 148;
784 /** Base note velocities for various GM programs */
785 const byte MpsMachine::programvelocities[128] = {
786 100, 100, 100, 100, 100, 90, 100, 100, 100, 100, 100, 90, 100, 100, 100, 100,
787 100, 100, 85, 100, 100, 100, 100, 100, 100, 100, 100, 100, 90, 90, 110, 80,
788 100, 100, 100, 90, 70, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
789 100, 100, 90, 100, 100, 100, 100, 100, 100, 120, 100, 100, 100, 120, 100, 127,
790 100, 100, 90, 100, 100, 100, 100, 100, 100, 95, 100, 100, 100, 100, 100, 100,
791 100, 100, 100, 100, 100, 100, 100, 115, 100, 100, 100, 100, 100, 100, 100, 100,
792 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
793 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
797 * Create MIDI data from song data for the original Microprose music drivers.
798 * @param data pointer to block of data
799 * @param length size of data in bytes
800 * @return true if the data could be loaded
802 bool MidiFile::LoadMpsData(const byte *data, size_t length)
804 _midifile_instance = this;
806 MpsMachine machine(data, length, *this);
807 return machine.PlayInto() && FixupMidiData(*this);
810 bool MidiFile::LoadSong(const MusicSongInfo &song)
812 switch (song.filetype) {
813 case MTT_STANDARDMIDI:
814 return this->LoadFile(song.filename);
815 case MTT_MPSMIDI:
817 size_t songdatalen = 0;
818 byte *songdata = GetMusicCatEntryData(song.filename, song.cat_index, songdatalen);
819 if (songdata != NULL) {
820 bool result = this->LoadMpsData(songdata, songdatalen);
821 free(songdata);
822 return result;
823 } else {
824 return false;
827 default:
828 NOT_REACHED();
833 * Move data from other to this, and clears other.
834 * @param other object containing loaded data to take over
836 void MidiFile::MoveFrom(MidiFile &other)
838 std::swap(this->blocks, other.blocks);
839 std::swap(this->tempos, other.tempos);
840 this->tickdiv = other.tickdiv;
842 _midifile_instance = this;
844 other.blocks.clear();
845 other.tempos.clear();
846 other.tickdiv = 0;
849 static void WriteVariableLen(FILE *f, uint32 value)
851 if (value < 0x7F) {
852 byte tb = value;
853 fwrite(&tb, 1, 1, f);
854 } else if (value < 0x3FFF) {
855 byte tb[2];
856 tb[1] = value & 0x7F; value >>= 7;
857 tb[0] = (value & 0x7F) | 0x80; value >>= 7;
858 fwrite(tb, 1, sizeof(tb), f);
859 } else if (value < 0x1FFFFF) {
860 byte tb[3];
861 tb[2] = value & 0x7F; value >>= 7;
862 tb[1] = (value & 0x7F) | 0x80; value >>= 7;
863 tb[0] = (value & 0x7F) | 0x80; value >>= 7;
864 fwrite(tb, 1, sizeof(tb), f);
865 } else if (value < 0x0FFFFFFF) {
866 byte tb[4];
867 tb[3] = value & 0x7F; value >>= 7;
868 tb[2] = (value & 0x7F) | 0x80; value >>= 7;
869 tb[1] = (value & 0x7F) | 0x80; value >>= 7;
870 tb[0] = (value & 0x7F) | 0x80; value >>= 7;
871 fwrite(tb, 1, sizeof(tb), f);
876 * Write a Standard MIDI File containing the decoded music.
877 * @param filename Name of file to write to
878 * @return True if the file was written to completion
880 bool MidiFile::WriteSMF(const char *filename)
882 FILE *f = FioFOpenFile(filename, "wb", Subdirectory::NO_DIRECTORY);
883 if (!f) {
884 return false;
887 /* SMF header */
888 const byte fileheader[] = {
889 'M', 'T', 'h', 'd', // block name
890 0x00, 0x00, 0x00, 0x06, // BE32 block length, always 6 bytes
891 0x00, 0x00, // writing format 0 (all in one track)
892 0x00, 0x01, // containing 1 track (BE16)
893 (byte)(this->tickdiv >> 8), (byte)this->tickdiv, // tickdiv in BE16
895 fwrite(fileheader, sizeof(fileheader), 1, f);
897 /* Track header */
898 const byte trackheader[] = {
899 'M', 'T', 'r', 'k', // block name
900 0, 0, 0, 0, // BE32 block length, unknown at this time
902 fwrite(trackheader, sizeof(trackheader), 1, f);
903 /* Determine position to write the actual track block length at */
904 size_t tracksizepos = ftell(f) - 4;
906 /* Write blocks in sequence */
907 uint32 lasttime = 0;
908 size_t nexttempoindex = 0;
909 for (size_t bi = 0; bi < this->blocks.size(); bi++) {
910 DataBlock &block = this->blocks[bi];
911 TempoChange &nexttempo = this->tempos[nexttempoindex];
913 uint32 timediff = block.ticktime - lasttime;
915 /* Check if there is a tempo change before this block */
916 if (nexttempo.ticktime < block.ticktime) {
917 timediff = nexttempo.ticktime - lasttime;
920 /* Write delta time for block */
921 lasttime += timediff;
922 bool needtime = false;
923 WriteVariableLen(f, timediff);
925 /* Write tempo change if there is one */
926 if (nexttempo.ticktime <= block.ticktime) {
927 byte tempobuf[6] = { MIDIST_SMF_META, 0x51, 0x03, 0, 0, 0 };
928 tempobuf[3] = (nexttempo.tempo & 0x00FF0000) >> 16;
929 tempobuf[4] = (nexttempo.tempo & 0x0000FF00) >> 8;
930 tempobuf[5] = (nexttempo.tempo & 0x000000FF);
931 fwrite(tempobuf, sizeof(tempobuf), 1, f);
932 nexttempoindex++;
933 needtime = true;
935 /* If a tempo change occurred between two blocks, rather than
936 * at start of this one, start over with delta time for the block. */
937 if (nexttempo.ticktime < block.ticktime) {
938 /* Start loop over at same index */
939 bi--;
940 continue;
943 /* Write each block data command */
944 byte *dp = block.data.Begin();
945 while (dp < block.data.End()) {
946 /* Always zero delta time inside blocks */
947 if (needtime) {
948 fputc(0, f);
950 needtime = true;
952 /* Check message type and write appropriate number of bytes */
953 switch (*dp & 0xF0) {
954 case MIDIST_NOTEOFF:
955 case MIDIST_NOTEON:
956 case MIDIST_POLYPRESS:
957 case MIDIST_CONTROLLER:
958 case MIDIST_PITCHBEND:
959 fwrite(dp, 1, 3, f);
960 dp += 3;
961 continue;
962 case MIDIST_PROGCHG:
963 case MIDIST_CHANPRESS:
964 fwrite(dp, 1, 2, f);
965 dp += 2;
966 continue;
969 /* Sysex needs to measure length and write that as well */
970 if (*dp == MIDIST_SYSEX) {
971 fwrite(dp, 1, 1, f);
972 dp++;
973 byte *sysexend = dp;
974 while (*sysexend != MIDIST_ENDSYSEX) sysexend++;
975 ptrdiff_t sysexlen = sysexend - dp;
976 WriteVariableLen(f, sysexlen);
977 fwrite(dp, 1, sysexend - dp, f);
978 dp = sysexend;
979 continue;
982 /* Fail for any other commands */
983 fclose(f);
984 return false;
988 /* End of track marker */
989 static const byte track_end_marker[] = { 0x00, MIDIST_SMF_META, 0x2F, 0x00 };
990 fwrite(&track_end_marker, sizeof(track_end_marker), 1, f);
992 /* Fill out the RIFF block length */
993 size_t trackendpos = ftell(f);
994 fseek(f, tracksizepos, SEEK_SET);
995 uint32 tracksize = (uint32)(trackendpos - tracksizepos - 4); // blindly assume we never produce files larger than 2 GB
996 tracksize = TO_BE32(tracksize);
997 fwrite(&tracksize, 4, 1, f);
999 fclose(f);
1000 return true;
1004 * Get the name of a Standard MIDI File for a given song.
1005 * For songs already in SMF format, just returns the original.
1006 * Otherwise the song is converted, written to a temporary-ish file, and the written filename is returned.
1007 * @param song Song definition to query
1008 * @return Full filename string, empty string if failed
1010 std::string MidiFile::GetSMFFile(const MusicSongInfo &song)
1012 if (song.filetype == MTT_STANDARDMIDI) {
1013 char filename[MAX_PATH];
1014 if (FioFindFullPath(filename, lastof(filename), Subdirectory::BASESET_DIR, song.filename)) {
1015 return std::string(filename);
1016 } else if (FioFindFullPath(filename, lastof(filename), Subdirectory::OLD_GM_DIR, song.filename)) {
1017 return std::string(filename);
1018 } else {
1019 return std::string();
1023 if (song.filetype != MTT_MPSMIDI) return std::string();
1025 char basename[MAX_PATH];
1027 const char *fnstart = strrchr(song.filename, PATHSEPCHAR);
1028 if (fnstart == NULL) {
1029 fnstart = song.filename;
1030 } else {
1031 fnstart++;
1034 /* Remove all '.' characters from filename */
1035 char *wp = basename;
1036 for (const char *rp = fnstart; *rp != '\0'; rp++) {
1037 if (*rp != '.') *wp++ = *rp;
1039 *wp++ = '\0';
1042 char tempdirname[MAX_PATH];
1043 FioGetFullPath(tempdirname, lastof(tempdirname), Searchpath::SP_AUTODOWNLOAD_DIR, Subdirectory::BASESET_DIR, basename);
1044 if (!AppendPathSeparator(tempdirname, lastof(tempdirname))) return std::string();
1045 FioCreateDirectory(tempdirname);
1047 char output_filename[MAX_PATH];
1048 seprintf(output_filename, lastof(output_filename), "%s%d.mid", tempdirname, song.cat_index);
1050 if (FileExists(output_filename)) {
1051 /* If the file already exists, assume it's the correct decoded data */
1052 return std::string(output_filename);
1055 byte *data;
1056 size_t datalen;
1057 data = GetMusicCatEntryData(song.filename, song.cat_index, datalen);
1058 if (data == NULL) return std::string();
1060 MidiFile midifile;
1061 if (!midifile.LoadMpsData(data, datalen)) {
1062 free(data);
1063 return std::string();
1065 free(data);
1067 if (midifile.WriteSMF(output_filename)) {
1068 return std::string(output_filename);
1069 } else {
1070 return std::string();
1075 static bool CmdDumpSMF(byte argc, char *argv[])
1077 if (argc == 0) {
1078 IConsolePrint(CC_WARNING, "Write the current song to a Standard MIDI File. Usage: 'dumpsmf <filename>'");
1079 return true;
1081 if (argc != 2) {
1082 IConsolePrint(CC_WARNING, "You must specify a filename to write MIDI data to.");
1083 return false;
1086 if (_midifile_instance == NULL) {
1087 IConsolePrint(CC_ERROR, "There is no MIDI file loaded currently, make sure music is playing, and you're using a driver that works with raw MIDI.");
1088 return false;
1091 char fnbuf[MAX_PATH] = { 0 };
1092 if (seprintf(fnbuf, lastof(fnbuf), "%s%s", FiosGetScreenshotDir(), argv[1]) >= (int)lengthof(fnbuf)) {
1093 IConsolePrint(CC_ERROR, "Filename too long.");
1094 return false;
1096 IConsolePrintF(CC_INFO, "Dumping MIDI to: %s", fnbuf);
1098 if (_midifile_instance->WriteSMF(fnbuf)) {
1099 IConsolePrint(CC_INFO, "File written successfully.");
1100 return true;
1101 } else {
1102 IConsolePrint(CC_ERROR, "An error occurred writing MIDI file.");
1103 return false;
1107 static void RegisterConsoleMidiCommands()
1109 static bool registered = false;
1110 if (!registered) {
1111 IConsoleCmdRegister("dumpsmf", CmdDumpSMF);
1112 registered = true;
1116 MidiFile::MidiFile()
1118 RegisterConsoleMidiCommands();
1121 MidiFile::~MidiFile()
1123 if (_midifile_instance == this) {
1124 _midifile_instance = NULL;