Fix 03cc0d6: Mark level crossings dirty when removing road from them, not from bridge...
[openttd-github.git] / src / music / midifile.cpp
blob8f12aae1e3a00d636413fbbf377a242369ed1213
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"
18 #include "../console_func.h"
19 #include "../console_internal.h"
21 /* SMF reader based on description at: http://www.somascape.org/midi/tech/mfile.html */
24 static MidiFile *_midifile_instance = nullptr;
26 /**
27 * Retrieve a well-known MIDI system exclusive message.
28 * @param msg Which sysex message to retrieve
29 * @param[out] length Receives the length of the returned buffer
30 * @return Pointer to byte buffer with sysex message
32 const byte *MidiGetStandardSysexMessage(MidiSysexMessage msg, size_t &length)
34 static byte reset_gm_sysex[] = { 0xF0, 0x7E, 0x7F, 0x09, 0x01, 0xF7 };
35 static byte reset_gs_sysex[] = { 0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7 };
36 static byte reset_xg_sysex[] = { 0xF0, 0x43, 0x10, 0x4C, 0x00, 0x00, 0x7E, 0x00, 0xF7 };
37 static byte roland_reverb_sysex[] = { 0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x01, 0x30, 0x02, 0x04, 0x00, 0x40, 0x40, 0x00, 0x00, 0x09, 0xF7 };
39 switch (msg) {
40 case MidiSysexMessage::ResetGM:
41 length = lengthof(reset_gm_sysex);
42 return reset_gm_sysex;
43 case MidiSysexMessage::ResetGS:
44 length = lengthof(reset_gs_sysex);
45 return reset_gs_sysex;
46 case MidiSysexMessage::ResetXG:
47 length = lengthof(reset_xg_sysex);
48 return reset_xg_sysex;
49 case MidiSysexMessage::RolandSetReverb:
50 length = lengthof(roland_reverb_sysex);
51 return roland_reverb_sysex;
52 default:
53 NOT_REACHED();
57 /**
58 * Owning byte buffer readable as a stream.
59 * RAII-compliant to make teardown in error situations easier.
61 class ByteBuffer {
62 byte *buf;
63 size_t buflen;
64 size_t pos;
65 public:
66 /**
67 * Construct buffer from data in a file.
68 * If file does not have sufficient bytes available, the object is constructed
69 * in an error state, that causes all further function calls to fail.
70 * @param file file to read from at current position
71 * @param len number of bytes to read
73 ByteBuffer(FILE *file, size_t len)
75 this->buf = MallocT<byte>(len);
76 if (fread(this->buf, 1, len, file) == len) {
77 this->buflen = len;
78 this->pos = 0;
79 } else {
80 /* invalid state */
81 this->buflen = 0;
85 /**
86 * Destructor, frees the buffer.
88 ~ByteBuffer()
90 free(this->buf);
93 /**
94 * Return whether the buffer was constructed successfully.
95 * @return true is the buffer contains data
97 bool IsValid() const
99 return this->buflen > 0;
103 * Return whether reading has reached the end of the buffer.
104 * @return true if there are no more bytes available to read
106 bool IsEnd() const
108 return this->pos >= this->buflen;
112 * Read a single byte from the buffer.
113 * @param[out] b returns the read value
114 * @return true if a byte was available for reading
116 bool ReadByte(byte &b)
118 if (this->IsEnd()) return false;
119 b = this->buf[this->pos++];
120 return true;
124 * Read a MIDI file variable length value.
125 * Each byte encodes 7 bits of the value, most-significant bits are encoded first.
126 * If the most significant bit in a byte is set, there are further bytes encoding the value.
127 * @param[out] res returns the read value
128 * @return true if there was data available
130 bool ReadVariableLength(uint32 &res)
132 res = 0;
133 byte b = 0;
134 do {
135 if (this->IsEnd()) return false;
136 b = this->buf[this->pos++];
137 res = (res << 7) | (b & 0x7F);
138 } while (b & 0x80);
139 return true;
143 * Read bytes into a buffer.
144 * @param[out] dest buffer to copy into
145 * @param length number of bytes to read
146 * @return true if the requested number of bytes were available
148 bool ReadBuffer(byte *dest, size_t length)
150 if (this->IsEnd()) return false;
151 if (this->buflen - this->pos < length) return false;
152 memcpy(dest, this->buf + this->pos, length);
153 this->pos += length;
154 return true;
158 * Read bytes into a MidiFile::DataBlock.
159 * @param[out] dest DataBlock to copy into
160 * @param length number of bytes to read
161 * @return true if the requested number of bytes were available
163 bool ReadDataBlock(MidiFile::DataBlock *dest, size_t length)
165 if (this->IsEnd()) return false;
166 if (this->buflen - this->pos < length) return false;
167 dest->data.insert(dest->data.end(), this->buf + this->pos, this->buf + this->pos + length);
168 this->pos += length;
169 return true;
173 * Skip over a number of bytes in the buffer.
174 * @param count number of bytes to skip over
175 * @return true if there were enough bytes available
177 bool Skip(size_t count)
179 if (this->IsEnd()) return false;
180 if (this->buflen - this->pos < count) return false;
181 this->pos += count;
182 return true;
186 * Go a number of bytes back to re-read.
187 * @param count number of bytes to go back
188 * @return true if at least count bytes had been read previously
190 bool Rewind(size_t count)
192 if (count > this->pos) return false;
193 this->pos -= count;
194 return true;
198 static bool ReadTrackChunk(FILE *file, MidiFile &target)
200 byte buf[4];
202 const byte magic[] = { 'M', 'T', 'r', 'k' };
203 if (fread(buf, sizeof(magic), 1, file) != 1) {
204 return false;
206 if (memcmp(magic, buf, sizeof(magic)) != 0) {
207 return false;
210 /* Read chunk length and then the whole chunk */
211 uint32 chunk_length;
212 if (fread(&chunk_length, 1, 4, file) != 4) {
213 return false;
215 chunk_length = FROM_BE32(chunk_length);
217 ByteBuffer chunk(file, chunk_length);
218 if (!chunk.IsValid()) {
219 return false;
222 target.blocks.push_back(MidiFile::DataBlock());
223 MidiFile::DataBlock *block = &target.blocks.back();
225 byte last_status = 0;
226 bool running_sysex = false;
227 while (!chunk.IsEnd()) {
228 /* Read deltatime for event, start new block */
229 uint32 deltatime = 0;
230 if (!chunk.ReadVariableLength(deltatime)) {
231 return false;
233 if (deltatime > 0) {
234 target.blocks.push_back(MidiFile::DataBlock(block->ticktime + deltatime));
235 block = &target.blocks.back();
238 /* Read status byte */
239 byte status;
240 if (!chunk.ReadByte(status)) {
241 return false;
244 if ((status & 0x80) == 0) {
245 /* High bit not set means running status message, status is same as last
246 * convert to explicit status */
247 chunk.Rewind(1);
248 status = last_status;
249 goto running_status;
250 } else if ((status & 0xF0) != 0xF0) {
251 /* Regular channel message */
252 last_status = status;
253 running_status:
254 switch (status & 0xF0) {
255 case MIDIST_NOTEOFF:
256 case MIDIST_NOTEON:
257 case MIDIST_POLYPRESS:
258 case MIDIST_CONTROLLER:
259 case MIDIST_PITCHBEND:
260 /* 3 byte messages */
261 block->data.push_back(status);
262 if (!chunk.ReadDataBlock(block, 2)) {
263 return false;
265 break;
266 case MIDIST_PROGCHG:
267 case MIDIST_CHANPRESS:
268 /* 2 byte messages */
269 block->data.push_back(status);
270 if (!chunk.ReadByte(buf[0])) {
271 return false;
273 block->data.push_back(buf[0]);
274 break;
275 default:
276 NOT_REACHED();
278 } else if (status == MIDIST_SMF_META) {
279 /* Meta event, read event type byte and data length */
280 if (!chunk.ReadByte(buf[0])) {
281 return false;
283 uint32 length = 0;
284 if (!chunk.ReadVariableLength(length)) {
285 return false;
287 switch (buf[0]) {
288 case 0x2F:
289 /* end of track, no more data (length != 0 is illegal) */
290 return (length == 0);
291 case 0x51:
292 /* tempo change */
293 if (length != 3) return false;
294 if (!chunk.ReadBuffer(buf, 3)) return false;
295 target.tempos.push_back(MidiFile::TempoChange(block->ticktime, buf[0] << 16 | buf[1] << 8 | buf[2]));
296 break;
297 default:
298 /* unimportant meta event, skip over it */
299 if (!chunk.Skip(length)) {
300 return false;
302 break;
304 } else if (status == MIDIST_SYSEX || (status == MIDIST_SMF_ESCAPE && running_sysex)) {
305 /* System exclusive message */
306 uint32 length = 0;
307 if (!chunk.ReadVariableLength(length)) {
308 return false;
310 block->data.push_back(0xF0);
311 if (!chunk.ReadDataBlock(block, length)) {
312 return false;
314 if (block->data.back() != 0xF7) {
315 /* Engage Casio weirdo mode - convert to normal sysex */
316 running_sysex = true;
317 block->data.push_back(0xF7);
318 } else {
319 running_sysex = false;
321 } else if (status == MIDIST_SMF_ESCAPE) {
322 /* Escape sequence */
323 uint32 length = 0;
324 if (!chunk.ReadVariableLength(length)) {
325 return false;
327 if (!chunk.ReadDataBlock(block, length)) {
328 return false;
330 } else {
331 /* Messages undefined in standard midi files:
332 * 0xF1 - MIDI time code quarter frame
333 * 0xF2 - Song position pointer
334 * 0xF3 - Song select
335 * 0xF4 - undefined/reserved
336 * 0xF5 - undefined/reserved
337 * 0xF6 - Tune request for analog synths
338 * 0xF8..0xFE - System real-time messages
340 return false;
344 NOT_REACHED();
347 template<typename T>
348 bool TicktimeAscending(const T &a, const T &b)
350 return a.ticktime < b.ticktime;
353 static bool FixupMidiData(MidiFile &target)
355 /* Sort all tempo changes and events */
356 std::sort(target.tempos.begin(), target.tempos.end(), TicktimeAscending<MidiFile::TempoChange>);
357 std::sort(target.blocks.begin(), target.blocks.end(), TicktimeAscending<MidiFile::DataBlock>);
359 if (target.tempos.size() == 0) {
360 /* No tempo information, assume 120 bpm (500,000 microseconds per beat */
361 target.tempos.push_back(MidiFile::TempoChange(0, 500000));
363 /* Add sentinel tempo at end */
364 target.tempos.push_back(MidiFile::TempoChange(UINT32_MAX, 0));
366 /* Merge blocks with identical tick times */
367 std::vector<MidiFile::DataBlock> merged_blocks;
368 uint32 last_ticktime = 0;
369 for (size_t i = 0; i < target.blocks.size(); i++) {
370 MidiFile::DataBlock &block = target.blocks[i];
371 if (block.data.size() == 0) {
372 continue;
373 } else if (block.ticktime > last_ticktime || merged_blocks.size() == 0) {
374 merged_blocks.push_back(block);
375 last_ticktime = block.ticktime;
376 } else {
377 merged_blocks.back().data.insert(merged_blocks.back().data.end(), block.data.begin(), block.data.end());
380 std::swap(merged_blocks, target.blocks);
382 /* Annotate blocks with real time */
383 last_ticktime = 0;
384 uint32 last_realtime = 0;
385 size_t cur_tempo = 0, cur_block = 0;
386 while (cur_block < target.blocks.size()) {
387 MidiFile::DataBlock &block = target.blocks[cur_block];
388 MidiFile::TempoChange &tempo = target.tempos[cur_tempo];
389 MidiFile::TempoChange &next_tempo = target.tempos[cur_tempo+1];
390 if (block.ticktime <= next_tempo.ticktime) {
391 /* block is within the current tempo */
392 int64 tickdiff = block.ticktime - last_ticktime;
393 last_ticktime = block.ticktime;
394 last_realtime += uint32(tickdiff * tempo.tempo / target.tickdiv);
395 block.realtime = last_realtime;
396 cur_block++;
397 } else {
398 /* tempo change occurs before this block */
399 int64 tickdiff = next_tempo.ticktime - last_ticktime;
400 last_ticktime = next_tempo.ticktime;
401 last_realtime += uint32(tickdiff * tempo.tempo / target.tickdiv); // current tempo until the tempo change
402 cur_tempo++;
406 return true;
410 * Read the header of a standard MIDI file.
411 * @param[in] filename name of file to read from
412 * @param[out] header filled with data read
413 * @return true if the file could be opened and contained a header with correct format
415 bool MidiFile::ReadSMFHeader(const char *filename, SMFHeader &header)
417 FILE *file = FioFOpenFile(filename, "rb", Subdirectory::BASESET_DIR);
418 if (!file) return false;
419 bool result = ReadSMFHeader(file, header);
420 FioFCloseFile(file);
421 return result;
425 * Read the header of a standard MIDI file.
426 * The function will consume 14 bytes from the current file pointer position.
427 * @param[in] file open file to read from (should be in binary mode)
428 * @param[out] header filled with data read
429 * @return true if a header in correct format could be read from the file
431 bool MidiFile::ReadSMFHeader(FILE *file, SMFHeader &header)
433 /* Try to read header, fixed size */
434 byte buffer[14];
435 if (fread(buffer, sizeof(buffer), 1, file) != 1) {
436 return false;
439 /* Check magic, 'MThd' followed by 4 byte length indicator (always = 6 in SMF) */
440 const byte magic[] = { 'M', 'T', 'h', 'd', 0x00, 0x00, 0x00, 0x06 };
441 if (MemCmpT(buffer, magic, sizeof(magic)) != 0) {
442 return false;
445 /* Read the parameters of the file */
446 header.format = (buffer[8] << 8) | buffer[9];
447 header.tracks = (buffer[10] << 8) | buffer[11];
448 header.tickdiv = (buffer[12] << 8) | buffer[13];
449 return true;
453 * Load a standard MIDI file.
454 * @param filename name of the file to load
455 * @returns true if loaded was successful
457 bool MidiFile::LoadFile(const char *filename)
459 _midifile_instance = this;
461 this->blocks.clear();
462 this->tempos.clear();
463 this->tickdiv = 0;
465 bool success = false;
466 FILE *file = FioFOpenFile(filename, "rb", Subdirectory::BASESET_DIR);
467 if (file == nullptr) return false;
469 SMFHeader header;
470 if (!ReadSMFHeader(file, header)) goto cleanup;
472 /* Only format 0 (single-track) and format 1 (multi-track single-song) are accepted for now */
473 if (header.format != 0 && header.format != 1) goto cleanup;
474 /* Doesn't support SMPTE timecode files */
475 if ((header.tickdiv & 0x8000) != 0) goto cleanup;
477 this->tickdiv = header.tickdiv;
479 for (; header.tracks > 0; header.tracks--) {
480 if (!ReadTrackChunk(file, *this)) {
481 goto cleanup;
485 success = FixupMidiData(*this);
487 cleanup:
488 FioFCloseFile(file);
489 return success;
494 * Decoder for "MPS MIDI" format data.
495 * This format for MIDI music is also used in a few other Microprose games contemporary with Transport Tycoon.
497 * 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.
499 * 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,
500 * the first list contains a number of reusable "segments", and the second list contains the "master tracks". Each list is prefixed with a byte
501 * 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,
502 * there is no index as such, so the entire data must be seeked through to build an index.
504 * The actual MIDI data inside each track is almost standard MIDI, prefixing every event with a delay, encoded using the same variable-length format
505 * 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
506 * 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.)
508 * As implemented in the original decoder, there is no support for recursively calling segments from segments, i.e. code 0xFE must only occur in
509 * 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
510 * be the original game music, not music from other games, or new productions.
512 * Additionally, some program change and controller events are given special meaning, see comments in the code.
514 struct MpsMachine {
515 /** Starting parameter and playback status for one channel/track */
516 struct Channel {
517 byte cur_program; ///< program selected, used for velocity scaling (lookup into programvelocities array)
518 byte running_status; ///< last midi status code seen
519 uint16 delay; ///< frames until next command
520 uint32 playpos; ///< next byte to play this channel from
521 uint32 startpos; ///< start position of master track
522 uint32 returnpos; ///< next return position after playing a segment
523 Channel() : cur_program(0xFF), running_status(0), delay(0), playpos(0), startpos(0), returnpos(0) { }
525 Channel channels[16]; ///< playback status for each MIDI channel
526 std::vector<uint32> segments; ///< pointers into songdata to repeatable data segments
527 int16 tempo_ticks; ///< ticker that increments when playing a frame, decrements before playing a frame
528 int16 current_tempo; ///< threshold for actually playing a frame
529 int16 initial_tempo; ///< starting tempo of song
530 bool shouldplayflag; ///< not-end-of-song flag
532 static const int TEMPO_RATE;
533 static const byte programvelocities[128];
535 const byte *songdata; ///< raw data array
536 size_t songdatalen; ///< length of song data
537 MidiFile &target; ///< recipient of data
539 /** Overridden MIDI status codes used in the data format */
540 enum MpsMidiStatus {
541 MPSMIDIST_SEGMENT_RETURN = 0xFD, ///< resume playing master track from stored position
542 MPSMIDIST_SEGMENT_CALL = 0xFE, ///< store current position of master track playback, and begin playback of a segment
543 MPSMIDIST_ENDSONG = 0xFF, ///< immediately end the song
546 static void AddMidiData(MidiFile::DataBlock &block, byte b1, byte b2)
548 block.data.push_back(b1);
549 block.data.push_back(b2);
551 static void AddMidiData(MidiFile::DataBlock &block, byte b1, byte b2, byte b3)
553 block.data.push_back(b1);
554 block.data.push_back(b2);
555 block.data.push_back(b3);
559 * Construct a TTD DOS music format decoder.
560 * @param data Buffer of song data from CAT file, ownership remains with caller
561 * @param length Length of the data buffer in bytes
562 * @param target MidiFile object to add decoded data to
564 MpsMachine(const byte *data, size_t length, MidiFile &target)
565 : songdata(data), songdatalen(length), target(target)
567 uint32 pos = 0;
568 int loopmax;
569 int loopidx;
571 /* First byte is the initial "tempo" */
572 this->initial_tempo = this->songdata[pos++];
574 /* Next byte is a count of callable segments */
575 loopmax = this->songdata[pos++];
576 for (loopidx = 0; loopidx < loopmax; loopidx++) {
577 /* Segments form a linked list in the stream,
578 * first two bytes in each is an offset to the next.
579 * Two bytes between offset to next and start of data
580 * are unaccounted for. */
581 this->segments.push_back(pos + 4);
582 pos += FROM_LE16(*(const int16 *)(this->songdata + pos));
585 /* After segments follows list of master tracks for each channel,
586 * also prefixed with a byte counting actual tracks. */
587 loopmax = this->songdata[pos++];
588 for (loopidx = 0; loopidx < loopmax; loopidx++) {
589 /* Similar structure to segments list, but also has
590 * the MIDI channel number as a byte before the offset
591 * to next track. */
592 byte ch = this->songdata[pos++];
593 this->channels[ch].startpos = pos + 4;
594 pos += FROM_LE16(*(const int16 *)(this->songdata + pos));
599 * Read an SMF-style variable length value (note duration) from songdata.
600 * @param pos Position to read from, updated to point to next byte after the value read
601 * @return Value read from data stream
603 uint16 ReadVariableLength(uint32 &pos)
605 byte b = 0;
606 uint16 res = 0;
607 do {
608 b = this->songdata[pos++];
609 res = (res << 7) + (b & 0x7F);
610 } while (b & 0x80);
611 return res;
615 * Prepare for playback from the beginning. Resets the song pointer for every track to the beginning.
617 void RestartSong()
619 for (int ch = 0; ch < 16; ch++) {
620 Channel &chandata = this->channels[ch];
621 if (chandata.startpos != 0) {
622 /* Active track, set position to beginning */
623 chandata.playpos = chandata.startpos;
624 chandata.delay = this->ReadVariableLength(chandata.playpos);
625 } else {
626 /* Inactive track, mark as such */
627 chandata.playpos = 0;
628 chandata.delay = 0;
634 * Play one frame of data from one channel
636 uint16 PlayChannelFrame(MidiFile::DataBlock &outblock, int channel)
638 uint16 newdelay = 0;
639 byte b1, b2;
640 Channel &chandata = this->channels[channel];
642 do {
643 /* Read command/status byte */
644 b1 = this->songdata[chandata.playpos++];
646 /* Command 0xFE, call segment from master track */
647 if (b1 == MPSMIDIST_SEGMENT_CALL) {
648 b1 = this->songdata[chandata.playpos++];
649 chandata.returnpos = chandata.playpos;
650 chandata.playpos = this->segments[b1];
651 newdelay = this->ReadVariableLength(chandata.playpos);
652 if (newdelay == 0) {
653 continue;
655 return newdelay;
658 /* Command 0xFD, return from segment to master track */
659 if (b1 == MPSMIDIST_SEGMENT_RETURN) {
660 chandata.playpos = chandata.returnpos;
661 chandata.returnpos = 0;
662 newdelay = this->ReadVariableLength(chandata.playpos);
663 if (newdelay == 0) {
664 continue;
666 return newdelay;
669 /* Command 0xFF, end of song */
670 if (b1 == MPSMIDIST_ENDSONG) {
671 this->shouldplayflag = false;
672 return 0;
675 /* Regular MIDI channel message status byte */
676 if (b1 >= 0x80) {
677 /* Save the status byte as running status for the channel
678 * and read another byte for first parameter to command */
679 chandata.running_status = b1;
680 b1 = this->songdata[chandata.playpos++];
683 switch (chandata.running_status & 0xF0) {
684 case MIDIST_NOTEOFF:
685 case MIDIST_NOTEON:
686 b2 = this->songdata[chandata.playpos++];
687 if (b2 != 0) {
688 /* Note on, read velocity and scale according to rules */
689 int16 velocity;
690 if (channel == 9) {
691 /* Percussion channel, fixed velocity scaling not in the table */
692 velocity = (int16)b2 * 0x50;
693 } else {
694 /* Regular channel, use scaling from table */
695 velocity = b2 * programvelocities[chandata.cur_program];
697 b2 = (velocity / 128) & 0x00FF;
698 AddMidiData(outblock, MIDIST_NOTEON + channel, b1, b2);
699 } else {
700 /* Note off */
701 AddMidiData(outblock, MIDIST_NOTEON + channel, b1, 0);
703 break;
704 case MIDIST_CONTROLLER:
705 b2 = this->songdata[chandata.playpos++];
706 if (b1 == MIDICT_MODE_MONO) {
707 /* Unknown what the purpose of this is.
708 * Occurs in "Can't get There from Here" and in "Aliens Ate my Railway" a few times each.
709 * Possibly intended to give hints to other (non-GM) music drivers decoding the song.
711 break;
712 } else if (b1 == 0) {
713 /* Standard MIDI controller 0 is "bank select", override meaning to change tempo.
714 * This is not actually used in any of the original songs. */
715 if (b2 != 0) {
716 this->current_tempo = ((int)b2) * 48 / 60;
718 break;
719 } else if (b1 == MIDICT_EFFECTS1) {
720 /* Override value of this controller, default mapping is Reverb Send Level according to MMA RP-023.
721 * Unknown what the purpose of this particular value is. */
722 b2 = 30;
724 AddMidiData(outblock, MIDIST_CONTROLLER + channel, b1, b2);
725 break;
726 case MIDIST_PROGCHG:
727 if (b1 == 0x7E) {
728 /* Program change to "Applause" is originally used
729 * to cause the song to loop, but that gets handled
730 * separately in the output driver here.
731 * Just end the song. */
732 this->shouldplayflag = false;
733 break;
735 /* Used for note velocity scaling lookup */
736 chandata.cur_program = b1;
737 /* Two programs translated to a third, this is likely to
738 * provide three different velocity scalings of "brass". */
739 if (b1 == 0x57 || b1 == 0x3F) {
740 b1 = 0x3E;
742 AddMidiData(outblock, MIDIST_PROGCHG + channel, b1);
743 break;
744 case MIDIST_PITCHBEND:
745 b2 = this->songdata[chandata.playpos++];
746 AddMidiData(outblock, MIDIST_PITCHBEND + channel, b1, b2);
747 break;
748 default:
749 break;
752 newdelay = this->ReadVariableLength(chandata.playpos);
753 } while (newdelay == 0);
755 return newdelay;
759 * Play one frame of data into a block.
761 bool PlayFrame(MidiFile::DataBlock &block)
763 /* Update tempo/ticks counter */
764 this->tempo_ticks -= this->current_tempo;
765 if (this->tempo_ticks > 0) {
766 return true;
768 this->tempo_ticks += TEMPO_RATE;
770 /* Look over all channels, play those active */
771 for (int ch = 0; ch < 16; ch++) {
772 Channel &chandata = this->channels[ch];
773 if (chandata.playpos != 0) {
774 if (chandata.delay == 0) {
775 chandata.delay = this->PlayChannelFrame(block, ch);
777 chandata.delay--;
781 return this->shouldplayflag;
785 * Perform playback of whole song.
787 bool PlayInto()
789 /* Tempo seems to be handled as TEMPO_RATE = 148 ticks per second.
790 * Use this as the tickdiv, and define the tempo to be somewhat less than one second (1M microseconds) per quarter note.
791 * This value was found experimentally to give a very close approximation of the correct playback speed.
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, 980500));
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 std::string filename = FioFindFullPath(Subdirectory::BASESET_DIR, song.filename);
1052 if (!filename.empty()) return filename;
1053 filename = FioFindFullPath(Subdirectory::OLD_GM_DIR, song.filename);
1054 if (!filename.empty()) return filename;
1056 return std::string();
1059 if (song.filetype != MTT_MPSMIDI) return std::string();
1061 char basename[MAX_PATH];
1063 const char *fnstart = strrchr(song.filename, PATHSEPCHAR);
1064 if (fnstart == nullptr) {
1065 fnstart = song.filename;
1066 } else {
1067 fnstart++;
1070 /* Remove all '.' characters from filename */
1071 char *wp = basename;
1072 for (const char *rp = fnstart; *rp != '\0'; rp++) {
1073 if (*rp != '.') *wp++ = *rp;
1075 *wp++ = '\0';
1078 std::string tempdirname = FioGetDirectory(Searchpath::SP_AUTODOWNLOAD_DIR, Subdirectory::BASESET_DIR);
1079 tempdirname += basename;
1080 AppendPathSeparator(tempdirname);
1081 FioCreateDirectory(tempdirname);
1083 std::string output_filename = tempdirname + std::to_string(song.cat_index) + ".mid";
1085 if (FileExists(output_filename)) {
1086 /* If the file already exists, assume it's the correct decoded data */
1087 return output_filename;
1090 byte *data;
1091 size_t datalen;
1092 data = GetMusicCatEntryData(song.filename, song.cat_index, datalen);
1093 if (data == nullptr) return std::string();
1095 MidiFile midifile;
1096 if (!midifile.LoadMpsData(data, datalen)) {
1097 free(data);
1098 return std::string();
1100 free(data);
1102 if (midifile.WriteSMF(output_filename.c_str())) {
1103 return output_filename;
1104 } else {
1105 return std::string();
1110 static bool CmdDumpSMF(byte argc, char *argv[])
1112 if (argc == 0) {
1113 IConsolePrint(CC_HELP, "Write the current song to a Standard MIDI File. Usage: 'dumpsmf <filename>'.");
1114 return true;
1116 if (argc != 2) {
1117 IConsolePrint(CC_WARNING, "You must specify a filename to write MIDI data to.");
1118 return false;
1121 if (_midifile_instance == nullptr) {
1122 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.");
1123 return false;
1126 char fnbuf[MAX_PATH] = { 0 };
1127 if (seprintf(fnbuf, lastof(fnbuf), "%s%s", FiosGetScreenshotDir(), argv[1]) >= (int)lengthof(fnbuf)) {
1128 IConsolePrint(CC_ERROR, "Filename too long.");
1129 return false;
1131 IConsolePrint(CC_INFO, "Dumping MIDI to '{}'.", fnbuf);
1133 if (_midifile_instance->WriteSMF(fnbuf)) {
1134 IConsolePrint(CC_INFO, "File written successfully.");
1135 return true;
1136 } else {
1137 IConsolePrint(CC_ERROR, "An error occurred writing MIDI file.");
1138 return false;
1142 static void RegisterConsoleMidiCommands()
1144 static bool registered = false;
1145 if (!registered) {
1146 IConsole::CmdRegister("dumpsmf", CmdDumpSMF);
1147 registered = true;
1151 MidiFile::MidiFile()
1153 RegisterConsoleMidiCommands();
1156 MidiFile::~MidiFile()
1158 if (_midifile_instance == this) {
1159 _midifile_instance = nullptr;