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/>.
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"
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;
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 };
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
;
59 * Owning byte buffer readable as a stream.
60 * RAII-compliant to make teardown in error situations easier.
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
) {
87 * Destructor, frees the buffer.
95 * Return whether the buffer was constructed successfully.
96 * @return true is the buffer contains data
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
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
++];
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
)
136 if (this->IsEnd()) return false;
137 b
= this->buf
[this->pos
++];
138 res
= (res
<< 7) | (b
& 0x7F);
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
);
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
);
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;
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;
199 static bool ReadTrackChunk(FILE *file
, MidiFile
&target
)
203 const byte magic
[] = { 'M', 'T', 'r', 'k' };
204 if (fread(buf
, sizeof(magic
), 1, file
) != 1) {
207 if (memcmp(magic
, buf
, sizeof(magic
)) != 0) {
211 /* Read chunk length and then the whole chunk */
213 if (fread(&chunk_length
, 1, 4, file
) != 4) {
216 chunk_length
= FROM_BE32(chunk_length
);
218 ByteBuffer
chunk(file
, chunk_length
);
219 if (!chunk
.IsValid()) {
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
)) {
235 target
.blocks
.push_back(MidiFile::DataBlock(block
->ticktime
+ deltatime
));
236 block
= &target
.blocks
.back();
239 /* Read status byte */
241 if (!chunk
.ReadByte(status
)) {
245 if ((status
& 0x80) == 0) {
246 /* High bit not set means running status message, status is same as last
247 * convert to explicit status */
249 status
= last_status
;
251 } else if ((status
& 0xF0) != 0xF0) {
252 /* Regular channel message */
253 last_status
= status
;
255 switch (status
& 0xF0) {
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)) {
268 case MIDIST_CHANPRESS
:
269 /* 2 byte messages */
270 block
->data
.push_back(status
);
271 if (!chunk
.ReadByte(buf
[0])) {
274 block
->data
.push_back(buf
[0]);
279 } else if (status
== MIDIST_SMF_META
) {
280 /* Meta event, read event type byte and data length */
281 if (!chunk
.ReadByte(buf
[0])) {
285 if (!chunk
.ReadVariableLength(length
)) {
290 /* end of track, no more data (length != 0 is illegal) */
291 return (length
== 0);
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]));
299 /* unimportant meta event, skip over it */
300 if (!chunk
.Skip(length
)) {
305 } else if (status
== MIDIST_SYSEX
|| (status
== MIDIST_SMF_ESCAPE
&& running_sysex
)) {
306 /* System exclusive message */
308 if (!chunk
.ReadVariableLength(length
)) {
311 block
->data
.push_back(0xF0);
312 if (!chunk
.ReadDataBlock(block
, length
)) {
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);
320 running_sysex
= false;
322 } else if (status
== MIDIST_SMF_ESCAPE
) {
323 /* Escape sequence */
325 if (!chunk
.ReadVariableLength(length
)) {
328 if (!chunk
.ReadDataBlock(block
, length
)) {
332 /* Messages undefined in standard midi files:
333 * 0xF1 - MIDI time code quarter frame
334 * 0xF2 - Song position pointer
336 * 0xF4 - undefined/reserved
337 * 0xF5 - undefined/reserved
338 * 0xF6 - Tune request for analog synths
339 * 0xF8..0xFE - System real-time messages
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) {
374 } else if (block
.ticktime
> last_ticktime
|| merged_blocks
.size() == 0) {
375 merged_blocks
.push_back(block
);
376 last_ticktime
= block
.ticktime
;
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 */
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
;
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
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
);
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 */
436 if (fread(buffer
, sizeof(buffer
), 1, file
) != 1) {
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) {
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];
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();
466 bool success
= false;
467 FILE *file
= FioFOpenFile(filename
, "rb", Subdirectory::BASESET_DIR
);
468 if (file
== nullptr) return false;
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)) {
486 success
= FixupMidiData(*this);
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.
516 /** Starting parameter and playback status for one channel/track */
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 */
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
)
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
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
)
609 b
= this->songdata
[pos
++];
610 res
= (res
<< 7) + (b
& 0x7F);
616 * Prepare for playback from the beginning. Resets the song pointer for every track to the beginning.
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
);
627 /* Inactive track, mark as such */
628 chandata
.playpos
= 0;
635 * Play one frame of data from one channel
637 uint16
PlayChannelFrame(MidiFile::DataBlock
&outblock
, int channel
)
641 Channel
&chandata
= this->channels
[channel
];
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
);
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
);
670 /* Command 0xFF, end of song */
671 if (b1
== MPSMIDIST_ENDSONG
) {
672 this->shouldplayflag
= false;
676 /* Regular MIDI channel message status byte */
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) {
687 b2
= this->songdata
[chandata
.playpos
++];
689 /* Note on, read velocity and scale according to rules */
692 /* Percussion channel, fixed velocity scaling not in the table */
693 velocity
= (int16
)b2
* 0x50;
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
);
702 AddMidiData(outblock
, MIDIST_NOTEON
+ channel
, b1
, 0);
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.
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. */
717 this->current_tempo
= ((int)b2
) * 48 / 60;
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. */
725 AddMidiData(outblock
, MIDIST_CONTROLLER
+ channel
, b1
, b2
);
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;
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) {
743 AddMidiData(outblock
, MIDIST_PROGCHG
+ channel
, b1
);
745 case MIDIST_PITCHBEND
:
746 b2
= this->songdata
[chandata
.playpos
++];
747 AddMidiData(outblock
, MIDIST_PITCHBEND
+ channel
, b1
, b2
);
753 newdelay
= this->ReadVariableLength(chandata
.playpos
);
754 } while (newdelay
== 0);
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) {
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
);
782 return this->shouldplayflag
;
786 * Perform playback of whole song.
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 */
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
)) {
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
);
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
);
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();
887 static void WriteVariableLen(FILE *f
, uint32 value
)
891 fwrite(&tb
, 1, 1, f
);
892 } else if (value
<= 0x3FFF) {
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) {
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) {
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
);
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
);
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 */
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
);
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 */
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 */
990 /* Check message type and write appropriate number of bytes */
991 switch (*dp
& 0xF0) {
994 case MIDIST_POLYPRESS
:
995 case MIDIST_CONTROLLER
:
996 case MIDIST_PITCHBEND
:
1000 case MIDIST_PROGCHG
:
1001 case MIDIST_CHANPRESS
:
1002 fwrite(dp
, 1, 2, f
);
1007 /* Sysex needs to measure length and write that as well */
1008 if (*dp
== MIDIST_SYSEX
) {
1009 fwrite(dp
, 1, 1, f
);
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
);
1020 /* Fail for any other commands */
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
);
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
);
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
;
1072 /* Remove all '.' characters from filename */
1073 char *wp
= basename
;
1074 for (const char *rp
= fnstart
; *rp
!= '\0'; rp
++) {
1075 if (*rp
!= '.') *wp
++ = *rp
;
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
);
1095 data
= GetMusicCatEntryData(song
.filename
, song
.cat_index
, datalen
);
1096 if (data
== nullptr) return std::string();
1099 if (!midifile
.LoadMpsData(data
, datalen
)) {
1101 return std::string();
1105 if (midifile
.WriteSMF(output_filename
)) {
1106 return std::string(output_filename
);
1108 return std::string();
1113 static bool CmdDumpSMF(byte argc
, char *argv
[])
1116 IConsolePrint(CC_WARNING
, "Write the current song to a Standard MIDI File. Usage: 'dumpsmf <filename>'");
1120 IConsolePrint(CC_WARNING
, "You must specify a filename to write MIDI data to.");
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.");
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.");
1134 IConsolePrintF(CC_INFO
, "Dumping MIDI to: %s", fnbuf
);
1136 if (_midifile_instance
->WriteSMF(fnbuf
)) {
1137 IConsolePrint(CC_INFO
, "File written successfully.");
1140 IConsolePrint(CC_ERROR
, "An error occurred writing MIDI file.");
1145 static void RegisterConsoleMidiCommands()
1147 static bool registered
= false;
1149 IConsoleCmdRegister("dumpsmf", CmdDumpSMF
);
1154 MidiFile::MidiFile()
1156 RegisterConsoleMidiCommands();
1159 MidiFile::~MidiFile()
1161 if (_midifile_instance
== this) {
1162 _midifile_instance
= nullptr;