Add: INR currency (#8136)
[openttd-github.git] / src / music / midifile.cpp
blob67cc7b192c4b3acd724d9f96614f0964eed8b30d
1 /*
2 * This file is part of OpenTTD.
3 * 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.
4 * 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.
5 * 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/>.
6 */
8 /* @file midifile.cpp Parser for standard MIDI files */
10 #include "midifile.hpp"
11 #include "../fileio_func.h"
12 #include "../fileio_type.h"
13 #include "../string_func.h"
14 #include "../core/endian_func.hpp"
15 #include "../base_media_base.h"
16 #include "midi.h"
17 #include <algorithm>
19 #include "../console_func.h"
20 #include "../console_internal.h"
22 /* SMF reader based on description at: http://www.somascape.org/midi/tech/mfile.html */
25 static MidiFile *_midifile_instance = nullptr;
27 /**
28 * Retrieve a well-known MIDI system exclusive message.
29 * @param msg Which sysex message to retrieve
30 * @param[out] length Receives the length of the returned buffer
31 * @return Pointer to byte buffer with sysex message
33 const byte *MidiGetStandardSysexMessage(MidiSysexMessage msg, size_t &length)
35 static byte reset_gm_sysex[] = { 0xF0, 0x7E, 0x7F, 0x09, 0x01, 0xF7 };
36 static byte reset_gs_sysex[] = { 0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7 };
37 static byte reset_xg_sysex[] = { 0xF0, 0x43, 0x10, 0x4C, 0x00, 0x00, 0x7E, 0x00, 0xF7 };
38 static byte roland_reverb_sysex[] = { 0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x01, 0x30, 0x02, 0x04, 0x00, 0x40, 0x40, 0x00, 0x00, 0x09, 0xF7 };
40 switch (msg) {
41 case MidiSysexMessage::ResetGM:
42 length = lengthof(reset_gm_sysex);
43 return reset_gm_sysex;
44 case MidiSysexMessage::ResetGS:
45 length = lengthof(reset_gs_sysex);
46 return reset_gs_sysex;
47 case MidiSysexMessage::ResetXG:
48 length = lengthof(reset_xg_sysex);
49 return reset_xg_sysex;
50 case MidiSysexMessage::RolandSetReverb:
51 length = lengthof(roland_reverb_sysex);
52 return roland_reverb_sysex;
53 default:
54 NOT_REACHED();
58 /**
59 * Owning byte buffer readable as a stream.
60 * RAII-compliant to make teardown in error situations easier.
62 class ByteBuffer {
63 byte *buf;
64 size_t buflen;
65 size_t pos;
66 public:
67 /**
68 * Construct buffer from data in a file.
69 * If file does not have sufficient bytes available, the object is constructed
70 * in an error state, that causes all further function calls to fail.
71 * @param file file to read from at current position
72 * @param len number of bytes to read
74 ByteBuffer(FILE *file, size_t len)
76 this->buf = MallocT<byte>(len);
77 if (fread(this->buf, 1, len, file) == len) {
78 this->buflen = len;
79 this->pos = 0;
80 } else {
81 /* invalid state */
82 this->buflen = 0;
86 /**
87 * Destructor, frees the buffer.
89 ~ByteBuffer()
91 free(this->buf);
94 /**
95 * Return whether the buffer was constructed successfully.
96 * @return true is the buffer contains data
98 bool IsValid() const
100 return this->buflen > 0;
104 * Return whether reading has reached the end of the buffer.
105 * @return true if there are no more bytes available to read
107 bool IsEnd() const
109 return this->pos >= this->buflen;
113 * Read a single byte from the buffer.
114 * @param[out] b returns the read value
115 * @return true if a byte was available for reading
117 bool ReadByte(byte &b)
119 if (this->IsEnd()) return false;
120 b = this->buf[this->pos++];
121 return true;
125 * Read a MIDI file variable length value.
126 * Each byte encodes 7 bits of the value, most-significant bits are encoded first.
127 * If the most significant bit in a byte is set, there are further bytes encoding the value.
128 * @param[out] res returns the read value
129 * @return true if there was data available
131 bool ReadVariableLength(uint32 &res)
133 res = 0;
134 byte b = 0;
135 do {
136 if (this->IsEnd()) return false;
137 b = this->buf[this->pos++];
138 res = (res << 7) | (b & 0x7F);
139 } while (b & 0x80);
140 return true;
144 * Read bytes into a buffer.
145 * @param[out] dest buffer to copy into
146 * @param length number of bytes to read
147 * @return true if the requested number of bytes were available
149 bool ReadBuffer(byte *dest, size_t length)
151 if (this->IsEnd()) return false;
152 if (this->buflen - this->pos < length) return false;
153 memcpy(dest, this->buf + this->pos, length);
154 this->pos += length;
155 return true;
159 * Read bytes into a MidiFile::DataBlock.
160 * @param[out] dest DataBlock to copy into
161 * @param length number of bytes to read
162 * @return true if the requested number of bytes were available
164 bool ReadDataBlock(MidiFile::DataBlock *dest, size_t length)
166 if (this->IsEnd()) return false;
167 if (this->buflen - this->pos < length) return false;
168 dest->data.insert(dest->data.end(), this->buf + this->pos, this->buf + this->pos + length);
169 this->pos += length;
170 return true;
174 * Skip over a number of bytes in the buffer.
175 * @param count number of bytes to skip over
176 * @return true if there were enough bytes available
178 bool Skip(size_t count)
180 if (this->IsEnd()) return false;
181 if (this->buflen - this->pos < count) return false;
182 this->pos += count;
183 return true;
187 * Go a number of bytes back to re-read.
188 * @param count number of bytes to go back
189 * @return true if at least count bytes had been read previously
191 bool Rewind(size_t count)
193 if (count > this->pos) return false;
194 this->pos -= count;
195 return true;
199 static bool ReadTrackChunk(FILE *file, MidiFile &target)
201 byte buf[4];
203 const byte magic[] = { 'M', 'T', 'r', 'k' };
204 if (fread(buf, sizeof(magic), 1, file) != 1) {
205 return false;
207 if (memcmp(magic, buf, sizeof(magic)) != 0) {
208 return false;
211 /* Read chunk length and then the whole chunk */
212 uint32 chunk_length;
213 if (fread(&chunk_length, 1, 4, file) != 4) {
214 return false;
216 chunk_length = FROM_BE32(chunk_length);
218 ByteBuffer chunk(file, chunk_length);
219 if (!chunk.IsValid()) {
220 return false;
223 target.blocks.push_back(MidiFile::DataBlock());
224 MidiFile::DataBlock *block = &target.blocks.back();
226 byte last_status = 0;
227 bool running_sysex = false;
228 while (!chunk.IsEnd()) {
229 /* Read deltatime for event, start new block */
230 uint32 deltatime = 0;
231 if (!chunk.ReadVariableLength(deltatime)) {
232 return false;
234 if (deltatime > 0) {
235 target.blocks.push_back(MidiFile::DataBlock(block->ticktime + deltatime));
236 block = &target.blocks.back();
239 /* Read status byte */
240 byte status;
241 if (!chunk.ReadByte(status)) {
242 return false;
245 if ((status & 0x80) == 0) {
246 /* High bit not set means running status message, status is same as last
247 * convert to explicit status */
248 chunk.Rewind(1);
249 status = last_status;
250 goto running_status;
251 } else if ((status & 0xF0) != 0xF0) {
252 /* Regular channel message */
253 last_status = status;
254 running_status:
255 switch (status & 0xF0) {
256 case MIDIST_NOTEOFF:
257 case MIDIST_NOTEON:
258 case MIDIST_POLYPRESS:
259 case MIDIST_CONTROLLER:
260 case MIDIST_PITCHBEND:
261 /* 3 byte messages */
262 block->data.push_back(status);
263 if (!chunk.ReadDataBlock(block, 2)) {
264 return false;
266 break;
267 case MIDIST_PROGCHG:
268 case MIDIST_CHANPRESS:
269 /* 2 byte messages */
270 block->data.push_back(status);
271 if (!chunk.ReadByte(buf[0])) {
272 return false;
274 block->data.push_back(buf[0]);
275 break;
276 default:
277 NOT_REACHED();
279 } else if (status == MIDIST_SMF_META) {
280 /* Meta event, read event type byte and data length */
281 if (!chunk.ReadByte(buf[0])) {
282 return false;
284 uint32 length = 0;
285 if (!chunk.ReadVariableLength(length)) {
286 return false;
288 switch (buf[0]) {
289 case 0x2F:
290 /* end of track, no more data (length != 0 is illegal) */
291 return (length == 0);
292 case 0x51:
293 /* tempo change */
294 if (length != 3) return false;
295 if (!chunk.ReadBuffer(buf, 3)) return false;
296 target.tempos.push_back(MidiFile::TempoChange(block->ticktime, buf[0] << 16 | buf[1] << 8 | buf[2]));
297 break;
298 default:
299 /* unimportant meta event, skip over it */
300 if (!chunk.Skip(length)) {
301 return false;
303 break;
305 } else if (status == MIDIST_SYSEX || (status == MIDIST_SMF_ESCAPE && running_sysex)) {
306 /* System exclusive message */
307 uint32 length = 0;
308 if (!chunk.ReadVariableLength(length)) {
309 return false;
311 block->data.push_back(0xF0);
312 if (!chunk.ReadDataBlock(block, length)) {
313 return false;
315 if (block->data.back() != 0xF7) {
316 /* Engage Casio weirdo mode - convert to normal sysex */
317 running_sysex = true;
318 block->data.push_back(0xF7);
319 } else {
320 running_sysex = false;
322 } else if (status == MIDIST_SMF_ESCAPE) {
323 /* Escape sequence */
324 uint32 length = 0;
325 if (!chunk.ReadVariableLength(length)) {
326 return false;
328 if (!chunk.ReadDataBlock(block, length)) {
329 return false;
331 } else {
332 /* Messages undefined in standard midi files:
333 * 0xF1 - MIDI time code quarter frame
334 * 0xF2 - Song position pointer
335 * 0xF3 - Song select
336 * 0xF4 - undefined/reserved
337 * 0xF5 - undefined/reserved
338 * 0xF6 - Tune request for analog synths
339 * 0xF8..0xFE - System real-time messages
341 return false;
345 NOT_REACHED();
348 template<typename T>
349 bool TicktimeAscending(const T &a, const T &b)
351 return a.ticktime < b.ticktime;
354 static bool FixupMidiData(MidiFile &target)
356 /* Sort all tempo changes and events */
357 std::sort(target.tempos.begin(), target.tempos.end(), TicktimeAscending<MidiFile::TempoChange>);
358 std::sort(target.blocks.begin(), target.blocks.end(), TicktimeAscending<MidiFile::DataBlock>);
360 if (target.tempos.size() == 0) {
361 /* No tempo information, assume 120 bpm (500,000 microseconds per beat */
362 target.tempos.push_back(MidiFile::TempoChange(0, 500000));
364 /* Add sentinel tempo at end */
365 target.tempos.push_back(MidiFile::TempoChange(UINT32_MAX, 0));
367 /* Merge blocks with identical tick times */
368 std::vector<MidiFile::DataBlock> merged_blocks;
369 uint32 last_ticktime = 0;
370 for (size_t i = 0; i < target.blocks.size(); i++) {
371 MidiFile::DataBlock &block = target.blocks[i];
372 if (block.data.size() == 0) {
373 continue;
374 } else if (block.ticktime > last_ticktime || merged_blocks.size() == 0) {
375 merged_blocks.push_back(block);
376 last_ticktime = block.ticktime;
377 } else {
378 merged_blocks.back().data.insert(merged_blocks.back().data.end(), block.data.begin(), block.data.end());
381 std::swap(merged_blocks, target.blocks);
383 /* Annotate blocks with real time */
384 last_ticktime = 0;
385 uint32 last_realtime = 0;
386 size_t cur_tempo = 0, cur_block = 0;
387 while (cur_block < target.blocks.size()) {
388 MidiFile::DataBlock &block = target.blocks[cur_block];
389 MidiFile::TempoChange &tempo = target.tempos[cur_tempo];
390 MidiFile::TempoChange &next_tempo = target.tempos[cur_tempo+1];
391 if (block.ticktime <= next_tempo.ticktime) {
392 /* block is within the current tempo */
393 int64 tickdiff = block.ticktime - last_ticktime;
394 last_ticktime = block.ticktime;
395 last_realtime += uint32(tickdiff * tempo.tempo / target.tickdiv);
396 block.realtime = last_realtime;
397 cur_block++;
398 } else {
399 /* tempo change occurs before this block */
400 int64 tickdiff = next_tempo.ticktime - last_ticktime;
401 last_ticktime = next_tempo.ticktime;
402 last_realtime += uint32(tickdiff * tempo.tempo / target.tickdiv); // current tempo until the tempo change
403 cur_tempo++;
407 return true;
411 * Read the header of a standard MIDI file.
412 * @param[in] filename name of file to read from
413 * @param[out] header filled with data read
414 * @return true if the file could be opened and contained a header with correct format
416 bool MidiFile::ReadSMFHeader(const char *filename, SMFHeader &header)
418 FILE *file = FioFOpenFile(filename, "rb", Subdirectory::BASESET_DIR);
419 if (!file) return false;
420 bool result = ReadSMFHeader(file, header);
421 FioFCloseFile(file);
422 return result;
426 * Read the header of a standard MIDI file.
427 * The function will consume 14 bytes from the current file pointer position.
428 * @param[in] file open file to read from (should be in binary mode)
429 * @param[out] header filled with data read
430 * @return true if a header in correct format could be read from the file
432 bool MidiFile::ReadSMFHeader(FILE *file, SMFHeader &header)
434 /* Try to read header, fixed size */
435 byte buffer[14];
436 if (fread(buffer, sizeof(buffer), 1, file) != 1) {
437 return false;
440 /* Check magic, 'MThd' followed by 4 byte length indicator (always = 6 in SMF) */
441 const byte magic[] = { 'M', 'T', 'h', 'd', 0x00, 0x00, 0x00, 0x06 };
442 if (MemCmpT(buffer, magic, sizeof(magic)) != 0) {
443 return false;
446 /* Read the parameters of the file */
447 header.format = (buffer[8] << 8) | buffer[9];
448 header.tracks = (buffer[10] << 8) | buffer[11];
449 header.tickdiv = (buffer[12] << 8) | buffer[13];
450 return true;
454 * Load a standard MIDI file.
455 * @param filename name of the file to load
456 * @returns true if loaded was successful
458 bool MidiFile::LoadFile(const char *filename)
460 _midifile_instance = this;
462 this->blocks.clear();
463 this->tempos.clear();
464 this->tickdiv = 0;
466 bool success = false;
467 FILE *file = FioFOpenFile(filename, "rb", Subdirectory::BASESET_DIR);
468 if (file == nullptr) return false;
470 SMFHeader header;
471 if (!ReadSMFHeader(file, header)) goto cleanup;
473 /* Only format 0 (single-track) and format 1 (multi-track single-song) are accepted for now */
474 if (header.format != 0 && header.format != 1) goto cleanup;
475 /* Doesn't support SMPTE timecode files */
476 if ((header.tickdiv & 0x8000) != 0) goto cleanup;
478 this->tickdiv = header.tickdiv;
480 for (; header.tracks > 0; header.tracks--) {
481 if (!ReadTrackChunk(file, *this)) {
482 goto cleanup;
486 success = FixupMidiData(*this);
488 cleanup:
489 FioFCloseFile(file);
490 return success;
495 * Decoder for "MPS MIDI" format data.
496 * This format for MIDI music is also used in a few other Microprose games contemporary with Transport Tycoon.
498 * 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.
500 * 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,
501 * the first list contains a number of reusable "segments", and the second list contains the "master tracks". Each list is prefixed with a byte
502 * 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,
503 * there is no index as such, so the entire data must be seeked through to build an index.
505 * The actual MIDI data inside each track is almost standard MIDI, prefixing every event with a delay, encoded using the same variable-length format
506 * 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
507 * 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.)
509 * As implemented in the original decoder, there is no support for recursively calling segments from segments, i.e. code 0xFE must only occur in
510 * 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
511 * be the original game music, not music from other games, or new productions.
513 * Additionally, some program change and controller events are given special meaning, see comments in the code.
515 struct MpsMachine {
516 /** Starting parameter and playback status for one channel/track */
517 struct Channel {
518 byte cur_program; ///< program selected, used for velocity scaling (lookup into programvelocities array)
519 byte running_status; ///< last midi status code seen
520 uint16 delay; ///< frames until next command
521 uint32 playpos; ///< next byte to play this channel from
522 uint32 startpos; ///< start position of master track
523 uint32 returnpos; ///< next return position after playing a segment
524 Channel() : cur_program(0xFF), running_status(0), delay(0), playpos(0), startpos(0), returnpos(0) { }
526 Channel channels[16]; ///< playback status for each MIDI channel
527 std::vector<uint32> segments; ///< pointers into songdata to repeatable data segments
528 int16 tempo_ticks; ///< ticker that increments when playing a frame, decrements before playing a frame
529 int16 current_tempo; ///< threshold for actually playing a frame
530 int16 initial_tempo; ///< starting tempo of song
531 bool shouldplayflag; ///< not-end-of-song flag
533 static const int TEMPO_RATE;
534 static const byte programvelocities[128];
536 const byte *songdata; ///< raw data array
537 size_t songdatalen; ///< length of song data
538 MidiFile &target; ///< recipient of data
540 /** Overridden MIDI status codes used in the data format */
541 enum MpsMidiStatus {
542 MPSMIDIST_SEGMENT_RETURN = 0xFD, ///< resume playing master track from stored position
543 MPSMIDIST_SEGMENT_CALL = 0xFE, ///< store current position of master track playback, and begin playback of a segment
544 MPSMIDIST_ENDSONG = 0xFF, ///< immediately end the song
547 static void AddMidiData(MidiFile::DataBlock &block, byte b1, byte b2)
549 block.data.push_back(b1);
550 block.data.push_back(b2);
552 static void AddMidiData(MidiFile::DataBlock &block, byte b1, byte b2, byte b3)
554 block.data.push_back(b1);
555 block.data.push_back(b2);
556 block.data.push_back(b3);
560 * Construct a TTD DOS music format decoder.
561 * @param data Buffer of song data from CAT file, ownership remains with caller
562 * @param length Length of the data buffer in bytes
563 * @param target MidiFile object to add decoded data to
565 MpsMachine(const byte *data, size_t length, MidiFile &target)
566 : songdata(data), songdatalen(length), target(target)
568 uint32 pos = 0;
569 int loopmax;
570 int loopidx;
572 /* First byte is the initial "tempo" */
573 this->initial_tempo = this->songdata[pos++];
575 /* Next byte is a count of callable segments */
576 loopmax = this->songdata[pos++];
577 for (loopidx = 0; loopidx < loopmax; loopidx++) {
578 /* Segments form a linked list in the stream,
579 * first two bytes in each is an offset to the next.
580 * Two bytes between offset to next and start of data
581 * are unaccounted for. */
582 this->segments.push_back(pos + 4);
583 pos += FROM_LE16(*(const int16 *)(this->songdata + pos));
586 /* After segments follows list of master tracks for each channel,
587 * also prefixed with a byte counting actual tracks. */
588 loopmax = this->songdata[pos++];
589 for (loopidx = 0; loopidx < loopmax; loopidx++) {
590 /* Similar structure to segments list, but also has
591 * the MIDI channel number as a byte before the offset
592 * to next track. */
593 byte ch = this->songdata[pos++];
594 this->channels[ch].startpos = pos + 4;
595 pos += FROM_LE16(*(const int16 *)(this->songdata + pos));
600 * Read an SMF-style variable length value (note duration) from songdata.
601 * @param pos Position to read from, updated to point to next byte after the value read
602 * @return Value read from data stream
604 uint16 ReadVariableLength(uint32 &pos)
606 byte b = 0;
607 uint16 res = 0;
608 do {
609 b = this->songdata[pos++];
610 res = (res << 7) + (b & 0x7F);
611 } while (b & 0x80);
612 return res;
616 * Prepare for playback from the beginning. Resets the song pointer for every track to the beginning.
618 void RestartSong()
620 for (int ch = 0; ch < 16; ch++) {
621 Channel &chandata = this->channels[ch];
622 if (chandata.startpos != 0) {
623 /* Active track, set position to beginning */
624 chandata.playpos = chandata.startpos;
625 chandata.delay = this->ReadVariableLength(chandata.playpos);
626 } else {
627 /* Inactive track, mark as such */
628 chandata.playpos = 0;
629 chandata.delay = 0;
635 * Play one frame of data from one channel
637 uint16 PlayChannelFrame(MidiFile::DataBlock &outblock, int channel)
639 uint16 newdelay = 0;
640 byte b1, b2;
641 Channel &chandata = this->channels[channel];
643 do {
644 /* Read command/status byte */
645 b1 = this->songdata[chandata.playpos++];
647 /* Command 0xFE, call segment from master track */
648 if (b1 == MPSMIDIST_SEGMENT_CALL) {
649 b1 = this->songdata[chandata.playpos++];
650 chandata.returnpos = chandata.playpos;
651 chandata.playpos = this->segments[b1];
652 newdelay = this->ReadVariableLength(chandata.playpos);
653 if (newdelay == 0) {
654 continue;
656 return newdelay;
659 /* Command 0xFD, return from segment to master track */
660 if (b1 == MPSMIDIST_SEGMENT_RETURN) {
661 chandata.playpos = chandata.returnpos;
662 chandata.returnpos = 0;
663 newdelay = this->ReadVariableLength(chandata.playpos);
664 if (newdelay == 0) {
665 continue;
667 return newdelay;
670 /* Command 0xFF, end of song */
671 if (b1 == MPSMIDIST_ENDSONG) {
672 this->shouldplayflag = false;
673 return 0;
676 /* Regular MIDI channel message status byte */
677 if (b1 >= 0x80) {
678 /* Save the status byte as running status for the channel
679 * and read another byte for first parameter to command */
680 chandata.running_status = b1;
681 b1 = this->songdata[chandata.playpos++];
684 switch (chandata.running_status & 0xF0) {
685 case MIDIST_NOTEOFF:
686 case MIDIST_NOTEON:
687 b2 = this->songdata[chandata.playpos++];
688 if (b2 != 0) {
689 /* Note on, read velocity and scale according to rules */
690 int16 velocity;
691 if (channel == 9) {
692 /* Percussion channel, fixed velocity scaling not in the table */
693 velocity = (int16)b2 * 0x50;
694 } else {
695 /* Regular channel, use scaling from table */
696 velocity = b2 * programvelocities[chandata.cur_program];
698 b2 = (velocity / 128) & 0x00FF;
699 AddMidiData(outblock, MIDIST_NOTEON + channel, b1, b2);
700 } else {
701 /* Note off */
702 AddMidiData(outblock, MIDIST_NOTEON + channel, b1, 0);
704 break;
705 case MIDIST_CONTROLLER:
706 b2 = this->songdata[chandata.playpos++];
707 if (b1 == MIDICT_MODE_MONO) {
708 /* Unknown what the purpose of this is.
709 * Occurs in "Can't get There from Here" and in "Aliens Ate my Railway" a few times each.
710 * Possibly intended to give hints to other (non-GM) music drivers decoding the song.
712 break;
713 } else if (b1 == 0) {
714 /* Standard MIDI controller 0 is "bank select", override meaning to change tempo.
715 * This is not actually used in any of the original songs. */
716 if (b2 != 0) {
717 this->current_tempo = ((int)b2) * 48 / 60;
719 break;
720 } else if (b1 == MIDICT_EFFECTS1) {
721 /* Override value of this controller, default mapping is Reverb Send Level according to MMA RP-023.
722 * Unknown what the purpose of this particular value is. */
723 b2 = 30;
725 AddMidiData(outblock, MIDIST_CONTROLLER + channel, b1, b2);
726 break;
727 case MIDIST_PROGCHG:
728 if (b1 == 0x7E) {
729 /* Program change to "Applause" is originally used
730 * to cause the song to loop, but that gets handled
731 * separately in the output driver here.
732 * Just end the song. */
733 this->shouldplayflag = false;
734 break;
736 /* Used for note velocity scaling lookup */
737 chandata.cur_program = b1;
738 /* Two programs translated to a third, this is likely to
739 * provide three different velocity scalings of "brass". */
740 if (b1 == 0x57 || b1 == 0x3F) {
741 b1 = 0x3E;
743 AddMidiData(outblock, MIDIST_PROGCHG + channel, b1);
744 break;
745 case MIDIST_PITCHBEND:
746 b2 = this->songdata[chandata.playpos++];
747 AddMidiData(outblock, MIDIST_PITCHBEND + channel, b1, b2);
748 break;
749 default:
750 break;
753 newdelay = this->ReadVariableLength(chandata.playpos);
754 } while (newdelay == 0);
756 return newdelay;
760 * Play one frame of data into a block.
762 bool PlayFrame(MidiFile::DataBlock &block)
764 /* Update tempo/ticks counter */
765 this->tempo_ticks -= this->current_tempo;
766 if (this->tempo_ticks > 0) {
767 return true;
769 this->tempo_ticks += TEMPO_RATE;
771 /* Look over all channels, play those active */
772 for (int ch = 0; ch < 16; ch++) {
773 Channel &chandata = this->channels[ch];
774 if (chandata.playpos != 0) {
775 if (chandata.delay == 0) {
776 chandata.delay = this->PlayChannelFrame(block, ch);
778 chandata.delay--;
782 return this->shouldplayflag;
786 * Perform playback of whole song.
788 bool PlayInto()
790 /* Tempo seems to be handled as TEMPO_RATE = 148 ticks per second.
791 * Use this as the tickdiv, and define the tempo to be one second (1M microseconds) per tickdiv.
792 * MIDI software loading exported files will show a bogus tempo, but playback will be correct. */
793 this->target.tickdiv = TEMPO_RATE;
794 this->target.tempos.push_back(MidiFile::TempoChange(0, 1000000));
796 /* Initialize playback simulation */
797 this->RestartSong();
798 this->shouldplayflag = true;
799 this->current_tempo = (int32)this->initial_tempo * 24 / 60;
800 this->tempo_ticks = this->current_tempo;
802 /* Always reset percussion channel to program 0 */
803 this->target.blocks.push_back(MidiFile::DataBlock());
804 AddMidiData(this->target.blocks.back(), MIDIST_PROGCHG+9, 0x00);
806 /* Technically should be an endless loop, but having
807 * a maximum (about 10 minutes) avoids getting stuck,
808 * in case of corrupted data. */
809 for (uint32 tick = 0; tick < 100000; tick+=1) {
810 this->target.blocks.push_back(MidiFile::DataBlock());
811 auto &block = this->target.blocks.back();
812 block.ticktime = tick;
813 if (!this->PlayFrame(block)) {
814 break;
817 return true;
820 /** Frames/ticks per second for music playback */
821 const int MpsMachine::TEMPO_RATE = 148;
822 /** Base note velocities for various GM programs */
823 const byte MpsMachine::programvelocities[128] = {
824 100, 100, 100, 100, 100, 90, 100, 100, 100, 100, 100, 90, 100, 100, 100, 100,
825 100, 100, 85, 100, 100, 100, 100, 100, 100, 100, 100, 100, 90, 90, 110, 80,
826 100, 100, 100, 90, 70, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
827 100, 100, 90, 100, 100, 100, 100, 100, 100, 120, 100, 100, 100, 120, 100, 127,
828 100, 100, 90, 100, 100, 100, 100, 100, 100, 95, 100, 100, 100, 100, 100, 100,
829 100, 100, 100, 100, 100, 100, 100, 115, 100, 100, 100, 100, 100, 100, 100, 100,
830 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
831 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
835 * Create MIDI data from song data for the original Microprose music drivers.
836 * @param data pointer to block of data
837 * @param length size of data in bytes
838 * @return true if the data could be loaded
840 bool MidiFile::LoadMpsData(const byte *data, size_t length)
842 _midifile_instance = this;
844 MpsMachine machine(data, length, *this);
845 return machine.PlayInto() && FixupMidiData(*this);
848 bool MidiFile::LoadSong(const MusicSongInfo &song)
850 switch (song.filetype) {
851 case MTT_STANDARDMIDI:
852 return this->LoadFile(song.filename);
853 case MTT_MPSMIDI:
855 size_t songdatalen = 0;
856 byte *songdata = GetMusicCatEntryData(song.filename, song.cat_index, songdatalen);
857 if (songdata != nullptr) {
858 bool result = this->LoadMpsData(songdata, songdatalen);
859 free(songdata);
860 return result;
861 } else {
862 return false;
865 default:
866 NOT_REACHED();
871 * Move data from other to this, and clears other.
872 * @param other object containing loaded data to take over
874 void MidiFile::MoveFrom(MidiFile &other)
876 std::swap(this->blocks, other.blocks);
877 std::swap(this->tempos, other.tempos);
878 this->tickdiv = other.tickdiv;
880 _midifile_instance = this;
882 other.blocks.clear();
883 other.tempos.clear();
884 other.tickdiv = 0;
887 static void WriteVariableLen(FILE *f, uint32 value)
889 if (value <= 0x7F) {
890 byte tb = value;
891 fwrite(&tb, 1, 1, f);
892 } else if (value <= 0x3FFF) {
893 byte tb[2];
894 tb[1] = value & 0x7F; value >>= 7;
895 tb[0] = (value & 0x7F) | 0x80; value >>= 7;
896 fwrite(tb, 1, sizeof(tb), f);
897 } else if (value <= 0x1FFFFF) {
898 byte tb[3];
899 tb[2] = value & 0x7F; value >>= 7;
900 tb[1] = (value & 0x7F) | 0x80; value >>= 7;
901 tb[0] = (value & 0x7F) | 0x80; value >>= 7;
902 fwrite(tb, 1, sizeof(tb), f);
903 } else if (value <= 0x0FFFFFFF) {
904 byte tb[4];
905 tb[3] = value & 0x7F; value >>= 7;
906 tb[2] = (value & 0x7F) | 0x80; value >>= 7;
907 tb[1] = (value & 0x7F) | 0x80; value >>= 7;
908 tb[0] = (value & 0x7F) | 0x80; value >>= 7;
909 fwrite(tb, 1, sizeof(tb), f);
914 * Write a Standard MIDI File containing the decoded music.
915 * @param filename Name of file to write to
916 * @return True if the file was written to completion
918 bool MidiFile::WriteSMF(const char *filename)
920 FILE *f = FioFOpenFile(filename, "wb", Subdirectory::NO_DIRECTORY);
921 if (!f) {
922 return false;
925 /* SMF header */
926 const byte fileheader[] = {
927 'M', 'T', 'h', 'd', // block name
928 0x00, 0x00, 0x00, 0x06, // BE32 block length, always 6 bytes
929 0x00, 0x00, // writing format 0 (all in one track)
930 0x00, 0x01, // containing 1 track (BE16)
931 (byte)(this->tickdiv >> 8), (byte)this->tickdiv, // tickdiv in BE16
933 fwrite(fileheader, sizeof(fileheader), 1, f);
935 /* Track header */
936 const byte trackheader[] = {
937 'M', 'T', 'r', 'k', // block name
938 0, 0, 0, 0, // BE32 block length, unknown at this time
940 fwrite(trackheader, sizeof(trackheader), 1, f);
941 /* Determine position to write the actual track block length at */
942 size_t tracksizepos = ftell(f) - 4;
944 /* Write blocks in sequence */
945 uint32 lasttime = 0;
946 size_t nexttempoindex = 0;
947 for (size_t bi = 0; bi < this->blocks.size(); bi++) {
948 DataBlock &block = this->blocks[bi];
949 TempoChange &nexttempo = this->tempos[nexttempoindex];
951 uint32 timediff = block.ticktime - lasttime;
953 /* Check if there is a tempo change before this block */
954 if (nexttempo.ticktime < block.ticktime) {
955 timediff = nexttempo.ticktime - lasttime;
958 /* Write delta time for block */
959 lasttime += timediff;
960 bool needtime = false;
961 WriteVariableLen(f, timediff);
963 /* Write tempo change if there is one */
964 if (nexttempo.ticktime <= block.ticktime) {
965 byte tempobuf[6] = { MIDIST_SMF_META, 0x51, 0x03, 0, 0, 0 };
966 tempobuf[3] = (nexttempo.tempo & 0x00FF0000) >> 16;
967 tempobuf[4] = (nexttempo.tempo & 0x0000FF00) >> 8;
968 tempobuf[5] = (nexttempo.tempo & 0x000000FF);
969 fwrite(tempobuf, sizeof(tempobuf), 1, f);
970 nexttempoindex++;
971 needtime = true;
973 /* If a tempo change occurred between two blocks, rather than
974 * at start of this one, start over with delta time for the block. */
975 if (nexttempo.ticktime < block.ticktime) {
976 /* Start loop over at same index */
977 bi--;
978 continue;
981 /* Write each block data command */
982 byte *dp = block.data.data();
983 while (dp < block.data.data() + block.data.size()) {
984 /* Always zero delta time inside blocks */
985 if (needtime) {
986 fputc(0, f);
988 needtime = true;
990 /* Check message type and write appropriate number of bytes */
991 switch (*dp & 0xF0) {
992 case MIDIST_NOTEOFF:
993 case MIDIST_NOTEON:
994 case MIDIST_POLYPRESS:
995 case MIDIST_CONTROLLER:
996 case MIDIST_PITCHBEND:
997 fwrite(dp, 1, 3, f);
998 dp += 3;
999 continue;
1000 case MIDIST_PROGCHG:
1001 case MIDIST_CHANPRESS:
1002 fwrite(dp, 1, 2, f);
1003 dp += 2;
1004 continue;
1007 /* Sysex needs to measure length and write that as well */
1008 if (*dp == MIDIST_SYSEX) {
1009 fwrite(dp, 1, 1, f);
1010 dp++;
1011 byte *sysexend = dp;
1012 while (*sysexend != MIDIST_ENDSYSEX) sysexend++;
1013 ptrdiff_t sysexlen = sysexend - dp;
1014 WriteVariableLen(f, sysexlen);
1015 fwrite(dp, 1, sysexend - dp, f);
1016 dp = sysexend + 1;
1017 continue;
1020 /* Fail for any other commands */
1021 fclose(f);
1022 return false;
1026 /* End of track marker */
1027 static const byte track_end_marker[] = { 0x00, MIDIST_SMF_META, 0x2F, 0x00 };
1028 fwrite(&track_end_marker, sizeof(track_end_marker), 1, f);
1030 /* Fill out the RIFF block length */
1031 size_t trackendpos = ftell(f);
1032 fseek(f, tracksizepos, SEEK_SET);
1033 uint32 tracksize = (uint32)(trackendpos - tracksizepos - 4); // blindly assume we never produce files larger than 2 GB
1034 tracksize = TO_BE32(tracksize);
1035 fwrite(&tracksize, 4, 1, f);
1037 fclose(f);
1038 return true;
1042 * Get the name of a Standard MIDI File for a given song.
1043 * For songs already in SMF format, just returns the original.
1044 * Otherwise the song is converted, written to a temporary-ish file, and the written filename is returned.
1045 * @param song Song definition to query
1046 * @return Full filename string, empty string if failed
1048 std::string MidiFile::GetSMFFile(const MusicSongInfo &song)
1050 if (song.filetype == MTT_STANDARDMIDI) {
1051 char filename[MAX_PATH];
1052 if (FioFindFullPath(filename, lastof(filename), Subdirectory::BASESET_DIR, song.filename)) {
1053 return std::string(filename);
1054 } else if (FioFindFullPath(filename, lastof(filename), Subdirectory::OLD_GM_DIR, song.filename)) {
1055 return std::string(filename);
1056 } else {
1057 return std::string();
1061 if (song.filetype != MTT_MPSMIDI) return std::string();
1063 char basename[MAX_PATH];
1065 const char *fnstart = strrchr(song.filename, PATHSEPCHAR);
1066 if (fnstart == nullptr) {
1067 fnstart = song.filename;
1068 } else {
1069 fnstart++;
1072 /* Remove all '.' characters from filename */
1073 char *wp = basename;
1074 for (const char *rp = fnstart; *rp != '\0'; rp++) {
1075 if (*rp != '.') *wp++ = *rp;
1077 *wp++ = '\0';
1080 char tempdirname[MAX_PATH];
1081 FioGetFullPath(tempdirname, lastof(tempdirname), Searchpath::SP_AUTODOWNLOAD_DIR, Subdirectory::BASESET_DIR, basename);
1082 if (!AppendPathSeparator(tempdirname, lastof(tempdirname))) return std::string();
1083 FioCreateDirectory(tempdirname);
1085 char output_filename[MAX_PATH];
1086 seprintf(output_filename, lastof(output_filename), "%s%d.mid", tempdirname, song.cat_index);
1088 if (FileExists(output_filename)) {
1089 /* If the file already exists, assume it's the correct decoded data */
1090 return std::string(output_filename);
1093 byte *data;
1094 size_t datalen;
1095 data = GetMusicCatEntryData(song.filename, song.cat_index, datalen);
1096 if (data == nullptr) return std::string();
1098 MidiFile midifile;
1099 if (!midifile.LoadMpsData(data, datalen)) {
1100 free(data);
1101 return std::string();
1103 free(data);
1105 if (midifile.WriteSMF(output_filename)) {
1106 return std::string(output_filename);
1107 } else {
1108 return std::string();
1113 static bool CmdDumpSMF(byte argc, char *argv[])
1115 if (argc == 0) {
1116 IConsolePrint(CC_WARNING, "Write the current song to a Standard MIDI File. Usage: 'dumpsmf <filename>'");
1117 return true;
1119 if (argc != 2) {
1120 IConsolePrint(CC_WARNING, "You must specify a filename to write MIDI data to.");
1121 return false;
1124 if (_midifile_instance == nullptr) {
1125 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.");
1126 return false;
1129 char fnbuf[MAX_PATH] = { 0 };
1130 if (seprintf(fnbuf, lastof(fnbuf), "%s%s", FiosGetScreenshotDir(), argv[1]) >= (int)lengthof(fnbuf)) {
1131 IConsolePrint(CC_ERROR, "Filename too long.");
1132 return false;
1134 IConsolePrintF(CC_INFO, "Dumping MIDI to: %s", fnbuf);
1136 if (_midifile_instance->WriteSMF(fnbuf)) {
1137 IConsolePrint(CC_INFO, "File written successfully.");
1138 return true;
1139 } else {
1140 IConsolePrint(CC_ERROR, "An error occurred writing MIDI file.");
1141 return false;
1145 static void RegisterConsoleMidiCommands()
1147 static bool registered = false;
1148 if (!registered) {
1149 IConsoleCmdRegister("dumpsmf", CmdDumpSMF);
1150 registered = true;
1154 MidiFile::MidiFile()
1156 RegisterConsoleMidiCommands();
1159 MidiFile::~MidiFile()
1161 if (_midifile_instance == this) {
1162 _midifile_instance = nullptr;