Fix: Don't allow right-click to close world generation progress window. (#13084)
[openttd-github.git] / src / music / midifile.cpp
blobe3ebce3e74853f2fbcea53f7e2210023c27ec8c0
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 "../core/mem_func.hpp"
16 #include "../base_media_base.h"
17 #include "midi.h"
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 uint8_t *MidiGetStandardSysexMessage(MidiSysexMessage msg, size_t &length)
35 static uint8_t reset_gm_sysex[] = { 0xF0, 0x7E, 0x7F, 0x09, 0x01, 0xF7 };
36 static uint8_t reset_gs_sysex[] = { 0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7 };
37 static uint8_t reset_xg_sysex[] = { 0xF0, 0x43, 0x10, 0x4C, 0x00, 0x00, 0x7E, 0x00, 0xF7 };
38 static uint8_t 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 std::vector<uint8_t> buf;
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(FileHandle &file, size_t len)
75 this->buf.resize(len);
76 if (fread(this->buf.data(), 1, len, file) == len) {
77 this->pos = 0;
78 } else {
79 /* invalid state */
80 this->buf.clear();
84 /**
85 * Return whether the buffer was constructed successfully.
86 * @return true is the buffer contains data
88 bool IsValid() const
90 return !this->buf.empty();
93 /**
94 * Return whether reading has reached the end of the buffer.
95 * @return true if there are no more bytes available to read
97 bool IsEnd() const
99 return this->pos >= this->buf.size();
103 * Read a single byte from the buffer.
104 * @param[out] b returns the read value
105 * @return true if a byte was available for reading
107 bool ReadByte(uint8_t &b)
109 if (this->IsEnd()) return false;
110 b = this->buf[this->pos++];
111 return true;
115 * Read a MIDI file variable length value.
116 * Each byte encodes 7 bits of the value, most-significant bits are encoded first.
117 * If the most significant bit in a byte is set, there are further bytes encoding the value.
118 * @param[out] res returns the read value
119 * @return true if there was data available
121 bool ReadVariableLength(uint32_t &res)
123 res = 0;
124 uint8_t b = 0;
125 do {
126 if (this->IsEnd()) return false;
127 b = this->buf[this->pos++];
128 res = (res << 7) | (b & 0x7F);
129 } while (b & 0x80);
130 return true;
134 * Read bytes into a buffer.
135 * @param[out] dest buffer to copy into
136 * @param length number of bytes to read
137 * @return true if the requested number of bytes were available
139 bool ReadBuffer(uint8_t *dest, size_t length)
141 if (this->IsEnd()) return false;
142 if (this->buf.size() - this->pos < length) return false;
143 std::copy(std::begin(this->buf) + this->pos, std::begin(this->buf) + this->pos + length, dest);
144 this->pos += length;
145 return true;
149 * Read bytes into a MidiFile::DataBlock.
150 * @param[out] dest DataBlock to copy into
151 * @param length number of bytes to read
152 * @return true if the requested number of bytes were available
154 bool ReadDataBlock(MidiFile::DataBlock *dest, size_t length)
156 if (this->IsEnd()) return false;
157 if (this->buf.size() - this->pos < length) return false;
158 dest->data.insert(dest->data.end(), std::begin(this->buf) + this->pos, std::begin(this->buf) + this->pos + length);
159 this->pos += length;
160 return true;
164 * Skip over a number of bytes in the buffer.
165 * @param count number of bytes to skip over
166 * @return true if there were enough bytes available
168 bool Skip(size_t count)
170 if (this->IsEnd()) return false;
171 if (this->buf.size() - this->pos < count) return false;
172 this->pos += count;
173 return true;
177 * Go a number of bytes back to re-read.
178 * @param count number of bytes to go back
179 * @return true if at least count bytes had been read previously
181 bool Rewind(size_t count)
183 if (count > this->pos) return false;
184 this->pos -= count;
185 return true;
189 static bool ReadTrackChunk(FileHandle &file, MidiFile &target)
191 uint8_t buf[4];
193 const uint8_t magic[] = { 'M', 'T', 'r', 'k' };
194 if (fread(buf, sizeof(magic), 1, file) != 1) {
195 return false;
197 if (memcmp(magic, buf, sizeof(magic)) != 0) {
198 return false;
201 /* Read chunk length and then the whole chunk */
202 uint32_t chunk_length;
203 if (fread(&chunk_length, 1, 4, file) != 4) {
204 return false;
206 chunk_length = FROM_BE32(chunk_length);
208 ByteBuffer chunk(file, chunk_length);
209 if (!chunk.IsValid()) {
210 return false;
213 MidiFile::DataBlock *block = &target.blocks.emplace_back();
215 uint8_t last_status = 0;
216 bool running_sysex = false;
217 while (!chunk.IsEnd()) {
218 /* Read deltatime for event, start new block */
219 uint32_t deltatime = 0;
220 if (!chunk.ReadVariableLength(deltatime)) {
221 return false;
223 if (deltatime > 0) {
224 block = &target.blocks.emplace_back(block->ticktime + deltatime);
227 /* Read status byte */
228 uint8_t status;
229 if (!chunk.ReadByte(status)) {
230 return false;
233 if ((status & 0x80) == 0) {
234 /* High bit not set means running status message, status is same as last
235 * convert to explicit status */
236 chunk.Rewind(1);
237 status = last_status;
238 goto running_status;
239 } else if ((status & 0xF0) != 0xF0) {
240 /* Regular channel message */
241 last_status = status;
242 running_status:
243 switch (status & 0xF0) {
244 case MIDIST_NOTEOFF:
245 case MIDIST_NOTEON:
246 case MIDIST_POLYPRESS:
247 case MIDIST_CONTROLLER:
248 case MIDIST_PITCHBEND:
249 /* 3 byte messages */
250 block->data.push_back(status);
251 if (!chunk.ReadDataBlock(block, 2)) {
252 return false;
254 break;
255 case MIDIST_PROGCHG:
256 case MIDIST_CHANPRESS:
257 /* 2 byte messages */
258 block->data.push_back(status);
259 if (!chunk.ReadByte(buf[0])) {
260 return false;
262 block->data.push_back(buf[0]);
263 break;
264 default:
265 NOT_REACHED();
267 } else if (status == MIDIST_SMF_META) {
268 /* Meta event, read event type byte and data length */
269 if (!chunk.ReadByte(buf[0])) {
270 return false;
272 uint32_t length = 0;
273 if (!chunk.ReadVariableLength(length)) {
274 return false;
276 switch (buf[0]) {
277 case 0x2F:
278 /* end of track, no more data (length != 0 is illegal) */
279 return (length == 0);
280 case 0x51:
281 /* tempo change */
282 if (length != 3) return false;
283 if (!chunk.ReadBuffer(buf, 3)) return false;
284 target.tempos.push_back(MidiFile::TempoChange(block->ticktime, buf[0] << 16 | buf[1] << 8 | buf[2]));
285 break;
286 default:
287 /* unimportant meta event, skip over it */
288 if (!chunk.Skip(length)) {
289 return false;
291 break;
293 } else if (status == MIDIST_SYSEX || (status == MIDIST_SMF_ESCAPE && running_sysex)) {
294 /* System exclusive message */
295 uint32_t length = 0;
296 if (!chunk.ReadVariableLength(length)) {
297 return false;
299 block->data.push_back(0xF0);
300 if (!chunk.ReadDataBlock(block, length)) {
301 return false;
303 if (block->data.back() != 0xF7) {
304 /* Engage Casio weirdo mode - convert to normal sysex */
305 running_sysex = true;
306 block->data.push_back(0xF7);
307 } else {
308 running_sysex = false;
310 } else if (status == MIDIST_SMF_ESCAPE) {
311 /* Escape sequence */
312 uint32_t length = 0;
313 if (!chunk.ReadVariableLength(length)) {
314 return false;
316 if (!chunk.ReadDataBlock(block, length)) {
317 return false;
319 } else {
320 /* Messages undefined in standard midi files:
321 * 0xF1 - MIDI time code quarter frame
322 * 0xF2 - Song position pointer
323 * 0xF3 - Song select
324 * 0xF4 - undefined/reserved
325 * 0xF5 - undefined/reserved
326 * 0xF6 - Tune request for analog synths
327 * 0xF8..0xFE - System real-time messages
329 return false;
333 NOT_REACHED();
336 template<typename T>
337 bool TicktimeAscending(const T &a, const T &b)
339 return a.ticktime < b.ticktime;
342 static bool FixupMidiData(MidiFile &target)
344 /* Sort all tempo changes and events */
345 std::sort(target.tempos.begin(), target.tempos.end(), TicktimeAscending<MidiFile::TempoChange>);
346 std::sort(target.blocks.begin(), target.blocks.end(), TicktimeAscending<MidiFile::DataBlock>);
348 if (target.tempos.empty()) {
349 /* No tempo information, assume 120 bpm (500,000 microseconds per beat */
350 target.tempos.push_back(MidiFile::TempoChange(0, 500000));
352 /* Add sentinel tempo at end */
353 target.tempos.push_back(MidiFile::TempoChange(UINT32_MAX, 0));
355 /* Merge blocks with identical tick times */
356 std::vector<MidiFile::DataBlock> merged_blocks;
357 uint32_t last_ticktime = 0;
358 for (size_t i = 0; i < target.blocks.size(); i++) {
359 MidiFile::DataBlock &block = target.blocks[i];
360 if (block.data.empty()) {
361 continue;
362 } else if (block.ticktime > last_ticktime || merged_blocks.empty()) {
363 merged_blocks.push_back(block);
364 last_ticktime = block.ticktime;
365 } else {
366 merged_blocks.back().data.insert(merged_blocks.back().data.end(), block.data.begin(), block.data.end());
369 std::swap(merged_blocks, target.blocks);
371 /* Annotate blocks with real time */
372 last_ticktime = 0;
373 uint32_t last_realtime = 0;
374 size_t cur_tempo = 0, cur_block = 0;
375 while (cur_block < target.blocks.size()) {
376 MidiFile::DataBlock &block = target.blocks[cur_block];
377 MidiFile::TempoChange &tempo = target.tempos[cur_tempo];
378 MidiFile::TempoChange &next_tempo = target.tempos[cur_tempo + 1];
379 if (block.ticktime <= next_tempo.ticktime) {
380 /* block is within the current tempo */
381 int64_t tickdiff = block.ticktime - last_ticktime;
382 last_ticktime = block.ticktime;
383 last_realtime += uint32_t(tickdiff * tempo.tempo / target.tickdiv);
384 block.realtime = last_realtime;
385 cur_block++;
386 } else {
387 /* tempo change occurs before this block */
388 int64_t tickdiff = next_tempo.ticktime - last_ticktime;
389 last_ticktime = next_tempo.ticktime;
390 last_realtime += uint32_t(tickdiff * tempo.tempo / target.tickdiv); // current tempo until the tempo change
391 cur_tempo++;
395 return true;
399 * Read the header of a standard MIDI file.
400 * @param[in] filename name of file to read from
401 * @param[out] header filled with data read
402 * @return true if the file could be opened and contained a header with correct format
404 bool MidiFile::ReadSMFHeader(const std::string &filename, SMFHeader &header)
406 auto file = FioFOpenFile(filename, "rb", Subdirectory::BASESET_DIR);
407 if (!file.has_value()) return false;
408 bool result = ReadSMFHeader(*file, header);
409 return result;
413 * Read the header of a standard MIDI file.
414 * The function will consume 14 bytes from the current file pointer position.
415 * @param[in] file open file to read from (should be in binary mode)
416 * @param[out] header filled with data read
417 * @return true if a header in correct format could be read from the file
419 bool MidiFile::ReadSMFHeader(FileHandle &file, SMFHeader &header)
421 /* Try to read header, fixed size */
422 uint8_t buffer[14];
423 if (fread(buffer, sizeof(buffer), 1, file) != 1) {
424 return false;
427 /* Check magic, 'MThd' followed by 4 byte length indicator (always = 6 in SMF) */
428 const uint8_t magic[] = { 'M', 'T', 'h', 'd', 0x00, 0x00, 0x00, 0x06 };
429 if (MemCmpT(buffer, magic, sizeof(magic)) != 0) {
430 return false;
433 /* Read the parameters of the file */
434 header.format = (buffer[8] << 8) | buffer[9];
435 header.tracks = (buffer[10] << 8) | buffer[11];
436 header.tickdiv = (buffer[12] << 8) | buffer[13];
437 return true;
441 * Load a standard MIDI file.
442 * @param filename name of the file to load
443 * @returns true if loaded was successful
445 bool MidiFile::LoadFile(const std::string &filename)
447 _midifile_instance = this;
449 this->blocks.clear();
450 this->tempos.clear();
451 this->tickdiv = 0;
453 auto file = FioFOpenFile(filename, "rb", Subdirectory::BASESET_DIR);
454 if (!file.has_value()) return false;
456 SMFHeader header;
457 if (!ReadSMFHeader(*file, header)) return false;
459 /* Only format 0 (single-track) and format 1 (multi-track single-song) are accepted for now */
460 if (header.format != 0 && header.format != 1) return false;
461 /* Doesn't support SMPTE timecode files */
462 if ((header.tickdiv & 0x8000) != 0) return false;
464 this->tickdiv = header.tickdiv;
466 for (; header.tracks > 0; header.tracks--) {
467 if (!ReadTrackChunk(*file, *this)) {
468 return false;
472 return FixupMidiData(*this);
477 * Decoder for "MPS MIDI" format data.
478 * This format for MIDI music is also used in a few other Microprose games contemporary with Transport Tycoon.
480 * 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.
482 * 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,
483 * the first list contains a number of reusable "segments", and the second list contains the "master tracks". Each list is prefixed with a byte
484 * 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,
485 * there is no index as such, so the entire data must be seeked through to build an index.
487 * The actual MIDI data inside each track is almost standard MIDI, prefixing every event with a delay, encoded using the same variable-length format
488 * 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
489 * 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.)
491 * As implemented in the original decoder, there is no support for recursively calling segments from segments, i.e. code 0xFE must only occur in
492 * 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
493 * be the original game music, not music from other games, or new productions.
495 * Additionally, some program change and controller events are given special meaning, see comments in the code.
497 struct MpsMachine {
498 /** Starting parameter and playback status for one channel/track */
499 struct Channel {
500 uint8_t cur_program; ///< program selected, used for velocity scaling (lookup into programvelocities array)
501 uint8_t running_status; ///< last midi status code seen
502 uint16_t delay; ///< frames until next command
503 uint32_t playpos; ///< next byte to play this channel from
504 uint32_t startpos; ///< start position of master track
505 uint32_t returnpos; ///< next return position after playing a segment
506 Channel() : cur_program(0xFF), running_status(0), delay(0), playpos(0), startpos(0), returnpos(0) { }
508 Channel channels[16]; ///< playback status for each MIDI channel
509 std::vector<uint32_t> segments; ///< pointers into songdata to repeatable data segments
510 int16_t tempo_ticks; ///< ticker that increments when playing a frame, decrements before playing a frame
511 int16_t current_tempo; ///< threshold for actually playing a frame
512 int16_t initial_tempo; ///< starting tempo of song
513 bool shouldplayflag; ///< not-end-of-song flag
515 static const int TEMPO_RATE;
516 static const uint8_t programvelocities[128];
518 const uint8_t *songdata; ///< raw data array
519 size_t songdatalen; ///< length of song data
520 MidiFile &target; ///< recipient of data
522 /** Overridden MIDI status codes used in the data format */
523 enum MpsMidiStatus {
524 MPSMIDIST_SEGMENT_RETURN = 0xFD, ///< resume playing master track from stored position
525 MPSMIDIST_SEGMENT_CALL = 0xFE, ///< store current position of master track playback, and begin playback of a segment
526 MPSMIDIST_ENDSONG = 0xFF, ///< immediately end the song
529 static void AddMidiData(MidiFile::DataBlock &block, uint8_t b1, uint8_t b2)
531 block.data.push_back(b1);
532 block.data.push_back(b2);
534 static void AddMidiData(MidiFile::DataBlock &block, uint8_t b1, uint8_t b2, uint8_t b3)
536 block.data.push_back(b1);
537 block.data.push_back(b2);
538 block.data.push_back(b3);
542 * Construct a TTD DOS music format decoder.
543 * @param data Buffer of song data from CAT file, ownership remains with caller
544 * @param length Length of the data buffer in bytes
545 * @param target MidiFile object to add decoded data to
547 MpsMachine(const uint8_t *data, size_t length, MidiFile &target)
548 : songdata(data), songdatalen(length), target(target)
550 uint32_t pos = 0;
551 int loopmax;
552 int loopidx;
554 /* First byte is the initial "tempo" */
555 this->initial_tempo = this->songdata[pos++];
557 /* Next byte is a count of callable segments */
558 loopmax = this->songdata[pos++];
559 for (loopidx = 0; loopidx < loopmax; loopidx++) {
560 /* Segments form a linked list in the stream,
561 * first two bytes in each is an offset to the next.
562 * Two bytes between offset to next and start of data
563 * are unaccounted for. */
564 this->segments.push_back(pos + 4);
565 pos += FROM_LE16(*(const int16_t *)(this->songdata + pos));
568 /* After segments follows list of master tracks for each channel,
569 * also prefixed with a byte counting actual tracks. */
570 loopmax = this->songdata[pos++];
571 for (loopidx = 0; loopidx < loopmax; loopidx++) {
572 /* Similar structure to segments list, but also has
573 * the MIDI channel number as a byte before the offset
574 * to next track. */
575 uint8_t ch = this->songdata[pos++];
576 this->channels[ch].startpos = pos + 4;
577 pos += FROM_LE16(*(const int16_t *)(this->songdata + pos));
582 * Read an SMF-style variable length value (note duration) from songdata.
583 * @param pos Position to read from, updated to point to next byte after the value read
584 * @return Value read from data stream
586 uint16_t ReadVariableLength(uint32_t &pos)
588 uint8_t b = 0;
589 uint16_t res = 0;
590 do {
591 b = this->songdata[pos++];
592 res = (res << 7) + (b & 0x7F);
593 } while (b & 0x80);
594 return res;
598 * Prepare for playback from the beginning. Resets the song pointer for every track to the beginning.
600 void RestartSong()
602 for (int ch = 0; ch < 16; ch++) {
603 Channel &chandata = this->channels[ch];
604 if (chandata.startpos != 0) {
605 /* Active track, set position to beginning */
606 chandata.playpos = chandata.startpos;
607 chandata.delay = this->ReadVariableLength(chandata.playpos);
608 } else {
609 /* Inactive track, mark as such */
610 chandata.playpos = 0;
611 chandata.delay = 0;
617 * Play one frame of data from one channel
619 uint16_t PlayChannelFrame(MidiFile::DataBlock &outblock, int channel)
621 uint16_t newdelay = 0;
622 uint8_t b1, b2;
623 Channel &chandata = this->channels[channel];
625 do {
626 /* Read command/status byte */
627 b1 = this->songdata[chandata.playpos++];
629 /* Command 0xFE, call segment from master track */
630 if (b1 == MPSMIDIST_SEGMENT_CALL) {
631 b1 = this->songdata[chandata.playpos++];
632 chandata.returnpos = chandata.playpos;
633 chandata.playpos = this->segments[b1];
634 newdelay = this->ReadVariableLength(chandata.playpos);
635 if (newdelay == 0) {
636 continue;
638 return newdelay;
641 /* Command 0xFD, return from segment to master track */
642 if (b1 == MPSMIDIST_SEGMENT_RETURN) {
643 chandata.playpos = chandata.returnpos;
644 chandata.returnpos = 0;
645 newdelay = this->ReadVariableLength(chandata.playpos);
646 if (newdelay == 0) {
647 continue;
649 return newdelay;
652 /* Command 0xFF, end of song */
653 if (b1 == MPSMIDIST_ENDSONG) {
654 this->shouldplayflag = false;
655 return 0;
658 /* Regular MIDI channel message status byte */
659 if (b1 >= 0x80) {
660 /* Save the status byte as running status for the channel
661 * and read another byte for first parameter to command */
662 chandata.running_status = b1;
663 b1 = this->songdata[chandata.playpos++];
666 switch (chandata.running_status & 0xF0) {
667 case MIDIST_NOTEOFF:
668 case MIDIST_NOTEON:
669 b2 = this->songdata[chandata.playpos++];
670 if (b2 != 0) {
671 /* Note on, read velocity and scale according to rules */
672 int16_t velocity;
673 if (channel == 9) {
674 /* Percussion channel, fixed velocity scaling not in the table */
675 velocity = (int16_t)b2 * 0x50;
676 } else {
677 /* Regular channel, use scaling from table */
678 velocity = b2 * programvelocities[chandata.cur_program];
680 b2 = (velocity / 128) & 0x00FF;
681 AddMidiData(outblock, MIDIST_NOTEON + channel, b1, b2);
682 } else {
683 /* Note off */
684 AddMidiData(outblock, MIDIST_NOTEON + channel, b1, 0);
686 break;
687 case MIDIST_CONTROLLER:
688 b2 = this->songdata[chandata.playpos++];
689 if (b1 == MIDICT_MODE_MONO) {
690 /* Unknown what the purpose of this is.
691 * Occurs in "Can't get There from Here" and in "Aliens Ate my Railway" a few times each.
692 * Possibly intended to give hints to other (non-GM) music drivers decoding the song.
694 break;
695 } else if (b1 == 0) {
696 /* Standard MIDI controller 0 is "bank select", override meaning to change tempo.
697 * This is not actually used in any of the original songs. */
698 if (b2 != 0) {
699 this->current_tempo = ((int)b2) * 48 / 60;
701 break;
702 } else if (b1 == MIDICT_EFFECTS1) {
703 /* Override value of this controller, default mapping is Reverb Send Level according to MMA RP-023.
704 * Unknown what the purpose of this particular value is. */
705 b2 = 30;
707 AddMidiData(outblock, MIDIST_CONTROLLER + channel, b1, b2);
708 break;
709 case MIDIST_PROGCHG:
710 if (b1 == 0x7E) {
711 /* Program change to "Applause" is originally used
712 * to cause the song to loop, but that gets handled
713 * separately in the output driver here.
714 * Just end the song. */
715 this->shouldplayflag = false;
716 break;
718 /* Used for note velocity scaling lookup */
719 chandata.cur_program = b1;
720 /* Two programs translated to a third, this is likely to
721 * provide three different velocity scalings of "brass". */
722 if (b1 == 0x57 || b1 == 0x3F) {
723 b1 = 0x3E;
725 AddMidiData(outblock, MIDIST_PROGCHG + channel, b1);
726 break;
727 case MIDIST_PITCHBEND:
728 b2 = this->songdata[chandata.playpos++];
729 AddMidiData(outblock, MIDIST_PITCHBEND + channel, b1, b2);
730 break;
731 default:
732 break;
735 newdelay = this->ReadVariableLength(chandata.playpos);
736 } while (newdelay == 0);
738 return newdelay;
742 * Play one frame of data into a block.
744 bool PlayFrame(MidiFile::DataBlock &block)
746 /* Update tempo/ticks counter */
747 this->tempo_ticks -= this->current_tempo;
748 if (this->tempo_ticks > 0) {
749 return true;
751 this->tempo_ticks += TEMPO_RATE;
753 /* Look over all channels, play those active */
754 for (int ch = 0; ch < 16; ch++) {
755 Channel &chandata = this->channels[ch];
756 if (chandata.playpos != 0) {
757 if (chandata.delay == 0) {
758 chandata.delay = this->PlayChannelFrame(block, ch);
760 chandata.delay--;
764 return this->shouldplayflag;
768 * Perform playback of whole song.
770 bool PlayInto()
772 /* Tempo seems to be handled as TEMPO_RATE = 148 ticks per second.
773 * Use this as the tickdiv, and define the tempo to be somewhat less than one second (1M microseconds) per quarter note.
774 * This value was found experimentally to give a very close approximation of the correct playback speed.
775 * MIDI software loading exported files will show a bogus tempo, but playback will be correct. */
776 this->target.tickdiv = TEMPO_RATE;
777 this->target.tempos.push_back(MidiFile::TempoChange(0, 980500));
779 /* Initialize playback simulation */
780 this->RestartSong();
781 this->shouldplayflag = true;
782 this->current_tempo = (int32_t)this->initial_tempo * 24 / 60;
783 this->tempo_ticks = this->current_tempo;
785 /* Always reset percussion channel to program 0 */
786 this->target.blocks.push_back(MidiFile::DataBlock());
787 AddMidiData(this->target.blocks.back(), MIDIST_PROGCHG + 9, 0x00);
789 /* Technically should be an endless loop, but having
790 * a maximum (about 10 minutes) avoids getting stuck,
791 * in case of corrupted data. */
792 for (uint32_t tick = 0; tick < 100000; tick += 1) {
793 auto &block = this->target.blocks.emplace_back();
794 block.ticktime = tick;
795 if (!this->PlayFrame(block)) {
796 break;
799 return true;
802 /** Frames/ticks per second for music playback */
803 const int MpsMachine::TEMPO_RATE = 148;
804 /** Base note velocities for various GM programs */
805 const uint8_t MpsMachine::programvelocities[128] = {
806 100, 100, 100, 100, 100, 90, 100, 100, 100, 100, 100, 90, 100, 100, 100, 100,
807 100, 100, 85, 100, 100, 100, 100, 100, 100, 100, 100, 100, 90, 90, 110, 80,
808 100, 100, 100, 90, 70, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
809 100, 100, 90, 100, 100, 100, 100, 100, 100, 120, 100, 100, 100, 120, 100, 127,
810 100, 100, 90, 100, 100, 100, 100, 100, 100, 95, 100, 100, 100, 100, 100, 100,
811 100, 100, 100, 100, 100, 100, 100, 115, 100, 100, 100, 100, 100, 100, 100, 100,
812 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
813 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
817 * Create MIDI data from song data for the original Microprose music drivers.
818 * @param data pointer to block of data
819 * @param length size of data in bytes
820 * @return true if the data could be loaded
822 bool MidiFile::LoadMpsData(const uint8_t *data, size_t length)
824 _midifile_instance = this;
826 MpsMachine machine(data, length, *this);
827 return machine.PlayInto() && FixupMidiData(*this);
830 bool MidiFile::LoadSong(const MusicSongInfo &song)
832 switch (song.filetype) {
833 case MTT_STANDARDMIDI:
834 return this->LoadFile(song.filename);
835 case MTT_MPSMIDI:
837 auto songdata = GetMusicCatEntryData(song.filename, song.cat_index);
838 if (songdata.has_value()) {
839 bool result = this->LoadMpsData(songdata->data(), songdata->size());
840 return result;
841 } else {
842 return false;
845 default:
846 NOT_REACHED();
851 * Move data from other to this, and clears other.
852 * @param other object containing loaded data to take over
854 void MidiFile::MoveFrom(MidiFile &other)
856 std::swap(this->blocks, other.blocks);
857 std::swap(this->tempos, other.tempos);
858 this->tickdiv = other.tickdiv;
860 _midifile_instance = this;
862 other.blocks.clear();
863 other.tempos.clear();
864 other.tickdiv = 0;
867 static void WriteVariableLen(FileHandle &f, uint32_t value)
869 if (value <= 0x7F) {
870 uint8_t tb = value;
871 fwrite(&tb, 1, 1, f);
872 } else if (value <= 0x3FFF) {
873 uint8_t tb[2];
874 tb[1] = value & 0x7F; value >>= 7;
875 tb[0] = (value & 0x7F) | 0x80; value >>= 7;
876 fwrite(tb, 1, sizeof(tb), f);
877 } else if (value <= 0x1FFFFF) {
878 uint8_t tb[3];
879 tb[2] = value & 0x7F; value >>= 7;
880 tb[1] = (value & 0x7F) | 0x80; value >>= 7;
881 tb[0] = (value & 0x7F) | 0x80; value >>= 7;
882 fwrite(tb, 1, sizeof(tb), f);
883 } else if (value <= 0x0FFFFFFF) {
884 uint8_t tb[4];
885 tb[3] = value & 0x7F; value >>= 7;
886 tb[2] = (value & 0x7F) | 0x80; value >>= 7;
887 tb[1] = (value & 0x7F) | 0x80; value >>= 7;
888 tb[0] = (value & 0x7F) | 0x80; value >>= 7;
889 fwrite(tb, 1, sizeof(tb), f);
894 * Write a Standard MIDI File containing the decoded music.
895 * @param filename Name of file to write to
896 * @return True if the file was written to completion
898 bool MidiFile::WriteSMF(const std::string &filename)
900 auto of = FioFOpenFile(filename, "wb", Subdirectory::NO_DIRECTORY);
901 if (!of.has_value()) return false;
902 auto &f = *of;
904 /* SMF header */
905 const uint8_t fileheader[] = {
906 'M', 'T', 'h', 'd', // block name
907 0x00, 0x00, 0x00, 0x06, // BE32 block length, always 6 bytes
908 0x00, 0x00, // writing format 0 (all in one track)
909 0x00, 0x01, // containing 1 track (BE16)
910 (uint8_t)(this->tickdiv >> 8), (uint8_t)this->tickdiv, // tickdiv in BE16
912 fwrite(fileheader, sizeof(fileheader), 1, f);
914 /* Track header */
915 const uint8_t trackheader[] = {
916 'M', 'T', 'r', 'k', // block name
917 0, 0, 0, 0, // BE32 block length, unknown at this time
919 fwrite(trackheader, sizeof(trackheader), 1, f);
920 /* Determine position to write the actual track block length at */
921 size_t tracksizepos = ftell(f) - 4;
923 /* Write blocks in sequence */
924 uint32_t lasttime = 0;
925 size_t nexttempoindex = 0;
926 for (size_t bi = 0; bi < this->blocks.size(); bi++) {
927 DataBlock &block = this->blocks[bi];
928 TempoChange &nexttempo = this->tempos[nexttempoindex];
930 uint32_t timediff = block.ticktime - lasttime;
932 /* Check if there is a tempo change before this block */
933 if (nexttempo.ticktime < block.ticktime) {
934 timediff = nexttempo.ticktime - lasttime;
937 /* Write delta time for block */
938 lasttime += timediff;
939 bool needtime = false;
940 WriteVariableLen(f, timediff);
942 /* Write tempo change if there is one */
943 if (nexttempo.ticktime <= block.ticktime) {
944 uint8_t tempobuf[6] = { MIDIST_SMF_META, 0x51, 0x03, 0, 0, 0 };
945 tempobuf[3] = (nexttempo.tempo & 0x00FF0000) >> 16;
946 tempobuf[4] = (nexttempo.tempo & 0x0000FF00) >> 8;
947 tempobuf[5] = (nexttempo.tempo & 0x000000FF);
948 fwrite(tempobuf, sizeof(tempobuf), 1, f);
949 nexttempoindex++;
950 needtime = true;
952 /* If a tempo change occurred between two blocks, rather than
953 * at start of this one, start over with delta time for the block. */
954 if (nexttempo.ticktime < block.ticktime) {
955 /* Start loop over at same index */
956 bi--;
957 continue;
960 /* Write each block data command */
961 uint8_t *dp = block.data.data();
962 while (dp < block.data.data() + block.data.size()) {
963 /* Always zero delta time inside blocks */
964 if (needtime) {
965 fputc(0, f);
967 needtime = true;
969 /* Check message type and write appropriate number of bytes */
970 switch (*dp & 0xF0) {
971 case MIDIST_NOTEOFF:
972 case MIDIST_NOTEON:
973 case MIDIST_POLYPRESS:
974 case MIDIST_CONTROLLER:
975 case MIDIST_PITCHBEND:
976 fwrite(dp, 1, 3, f);
977 dp += 3;
978 continue;
979 case MIDIST_PROGCHG:
980 case MIDIST_CHANPRESS:
981 fwrite(dp, 1, 2, f);
982 dp += 2;
983 continue;
986 /* Sysex needs to measure length and write that as well */
987 if (*dp == MIDIST_SYSEX) {
988 fwrite(dp, 1, 1, f);
989 dp++;
990 uint8_t *sysexend = dp;
991 while (*sysexend != MIDIST_ENDSYSEX) sysexend++;
992 ptrdiff_t sysexlen = sysexend - dp;
993 WriteVariableLen(f, sysexlen);
994 fwrite(dp, 1, sysexend - dp, f);
995 dp = sysexend + 1;
996 continue;
999 /* Fail for any other commands */
1000 return false;
1004 /* End of track marker */
1005 static const uint8_t track_end_marker[] = { 0x00, MIDIST_SMF_META, 0x2F, 0x00 };
1006 fwrite(&track_end_marker, sizeof(track_end_marker), 1, f);
1008 /* Fill out the RIFF block length */
1009 size_t trackendpos = ftell(f);
1010 fseek(f, tracksizepos, SEEK_SET);
1011 uint32_t tracksize = (uint32_t)(trackendpos - tracksizepos - 4); // blindly assume we never produce files larger than 2 GB
1012 tracksize = TO_BE32(tracksize);
1013 fwrite(&tracksize, 4, 1, f);
1015 return true;
1019 * Get the name of a Standard MIDI File for a given song.
1020 * For songs already in SMF format, just returns the original.
1021 * Otherwise the song is converted, written to a temporary-ish file, and the written filename is returned.
1022 * @param song Song definition to query
1023 * @return Full filename string, empty string if failed
1025 std::string MidiFile::GetSMFFile(const MusicSongInfo &song)
1027 if (song.filetype == MTT_STANDARDMIDI) {
1028 std::string filename = FioFindFullPath(Subdirectory::BASESET_DIR, song.filename);
1029 if (!filename.empty()) return filename;
1030 filename = FioFindFullPath(Subdirectory::OLD_GM_DIR, song.filename);
1031 if (!filename.empty()) return filename;
1033 return std::string();
1036 if (song.filetype != MTT_MPSMIDI) return std::string();
1038 char basename[MAX_PATH];
1040 const char *fnstart = strrchr(song.filename.c_str(), PATHSEPCHAR);
1041 if (fnstart == nullptr) {
1042 fnstart = song.filename.c_str();
1043 } else {
1044 fnstart++;
1047 /* Remove all '.' characters from filename */
1048 char *wp = basename;
1049 for (const char *rp = fnstart; *rp != '\0'; rp++) {
1050 if (*rp != '.') *wp++ = *rp;
1052 *wp++ = '\0';
1055 std::string tempdirname = FioGetDirectory(Searchpath::SP_AUTODOWNLOAD_DIR, Subdirectory::BASESET_DIR);
1056 tempdirname += basename;
1057 AppendPathSeparator(tempdirname);
1058 FioCreateDirectory(tempdirname);
1060 std::string output_filename = tempdirname + std::to_string(song.cat_index) + ".mid";
1062 if (FileExists(output_filename)) {
1063 /* If the file already exists, assume it's the correct decoded data */
1064 return output_filename;
1067 auto songdata = GetMusicCatEntryData(song.filename, song.cat_index);
1068 if (!songdata.has_value()) return std::string();
1070 MidiFile midifile;
1071 if (!midifile.LoadMpsData(songdata->data(), songdata->size())) {
1072 return std::string();
1075 if (midifile.WriteSMF(output_filename)) {
1076 return output_filename;
1077 } else {
1078 return std::string();
1083 static bool CmdDumpSMF(uint8_t argc, char *argv[])
1085 if (argc == 0) {
1086 IConsolePrint(CC_HELP, "Write the current song to a Standard MIDI File. Usage: 'dumpsmf <filename>'.");
1087 return true;
1089 if (argc != 2) {
1090 IConsolePrint(CC_WARNING, "You must specify a filename to write MIDI data to.");
1091 return false;
1094 if (_midifile_instance == nullptr) {
1095 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.");
1096 return false;
1099 std::string filename = fmt::format("{}{}", FiosGetScreenshotDir(), argv[1]);
1100 IConsolePrint(CC_INFO, "Dumping MIDI to '{}'.", filename);
1102 if (_midifile_instance->WriteSMF(filename)) {
1103 IConsolePrint(CC_INFO, "File written successfully.");
1104 return true;
1105 } else {
1106 IConsolePrint(CC_ERROR, "An error occurred writing MIDI file.");
1107 return false;
1111 static void RegisterConsoleMidiCommands()
1113 static bool registered = false;
1114 if (!registered) {
1115 IConsole::CmdRegister("dumpsmf", CmdDumpSMF);
1116 registered = true;
1120 MidiFile::MidiFile()
1122 RegisterConsoleMidiCommands();
1125 MidiFile::~MidiFile()
1127 if (_midifile_instance == this) {
1128 _midifile_instance = nullptr;