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"
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;
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 };
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
;
58 * Owning byte buffer readable as a stream.
59 * RAII-compliant to make teardown in error situations easier.
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
) {
86 * Destructor, frees the buffer.
94 * Return whether the buffer was constructed successfully.
95 * @return true is the buffer contains data
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
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
++];
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
)
135 if (this->IsEnd()) return false;
136 b
= this->buf
[this->pos
++];
137 res
= (res
<< 7) | (b
& 0x7F);
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
);
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
);
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;
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;
198 static bool ReadTrackChunk(FILE *file
, MidiFile
&target
)
202 const byte magic
[] = { 'M', 'T', 'r', 'k' };
203 if (fread(buf
, sizeof(magic
), 1, file
) != 1) {
206 if (memcmp(magic
, buf
, sizeof(magic
)) != 0) {
210 /* Read chunk length and then the whole chunk */
212 if (fread(&chunk_length
, 1, 4, file
) != 4) {
215 chunk_length
= FROM_BE32(chunk_length
);
217 ByteBuffer
chunk(file
, chunk_length
);
218 if (!chunk
.IsValid()) {
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
)) {
234 target
.blocks
.push_back(MidiFile::DataBlock(block
->ticktime
+ deltatime
));
235 block
= &target
.blocks
.back();
238 /* Read status byte */
240 if (!chunk
.ReadByte(status
)) {
244 if ((status
& 0x80) == 0) {
245 /* High bit not set means running status message, status is same as last
246 * convert to explicit status */
248 status
= last_status
;
250 } else if ((status
& 0xF0) != 0xF0) {
251 /* Regular channel message */
252 last_status
= status
;
254 switch (status
& 0xF0) {
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)) {
267 case MIDIST_CHANPRESS
:
268 /* 2 byte messages */
269 block
->data
.push_back(status
);
270 if (!chunk
.ReadByte(buf
[0])) {
273 block
->data
.push_back(buf
[0]);
278 } else if (status
== MIDIST_SMF_META
) {
279 /* Meta event, read event type byte and data length */
280 if (!chunk
.ReadByte(buf
[0])) {
284 if (!chunk
.ReadVariableLength(length
)) {
289 /* end of track, no more data (length != 0 is illegal) */
290 return (length
== 0);
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]));
298 /* unimportant meta event, skip over it */
299 if (!chunk
.Skip(length
)) {
304 } else if (status
== MIDIST_SYSEX
|| (status
== MIDIST_SMF_ESCAPE
&& running_sysex
)) {
305 /* System exclusive message */
307 if (!chunk
.ReadVariableLength(length
)) {
310 block
->data
.push_back(0xF0);
311 if (!chunk
.ReadDataBlock(block
, length
)) {
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);
319 running_sysex
= false;
321 } else if (status
== MIDIST_SMF_ESCAPE
) {
322 /* Escape sequence */
324 if (!chunk
.ReadVariableLength(length
)) {
327 if (!chunk
.ReadDataBlock(block
, length
)) {
331 /* Messages undefined in standard midi files:
332 * 0xF1 - MIDI time code quarter frame
333 * 0xF2 - Song position pointer
335 * 0xF4 - undefined/reserved
336 * 0xF5 - undefined/reserved
337 * 0xF6 - Tune request for analog synths
338 * 0xF8..0xFE - System real-time messages
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) {
373 } else if (block
.ticktime
> last_ticktime
|| merged_blocks
.size() == 0) {
374 merged_blocks
.push_back(block
);
375 last_ticktime
= block
.ticktime
;
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 */
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
;
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
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
);
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 */
435 if (fread(buffer
, sizeof(buffer
), 1, file
) != 1) {
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) {
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];
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();
465 bool success
= false;
466 FILE *file
= FioFOpenFile(filename
, "rb", Subdirectory::BASESET_DIR
);
467 if (file
== nullptr) return false;
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)) {
485 success
= FixupMidiData(*this);
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.
515 /** Starting parameter and playback status for one channel/track */
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 */
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
)
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
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
)
608 b
= this->songdata
[pos
++];
609 res
= (res
<< 7) + (b
& 0x7F);
615 * Prepare for playback from the beginning. Resets the song pointer for every track to the beginning.
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
);
626 /* Inactive track, mark as such */
627 chandata
.playpos
= 0;
634 * Play one frame of data from one channel
636 uint16
PlayChannelFrame(MidiFile::DataBlock
&outblock
, int channel
)
640 Channel
&chandata
= this->channels
[channel
];
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
);
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
);
669 /* Command 0xFF, end of song */
670 if (b1
== MPSMIDIST_ENDSONG
) {
671 this->shouldplayflag
= false;
675 /* Regular MIDI channel message status byte */
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) {
686 b2
= this->songdata
[chandata
.playpos
++];
688 /* Note on, read velocity and scale according to rules */
691 /* Percussion channel, fixed velocity scaling not in the table */
692 velocity
= (int16
)b2
* 0x50;
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
);
701 AddMidiData(outblock
, MIDIST_NOTEON
+ channel
, b1
, 0);
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.
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. */
716 this->current_tempo
= ((int)b2
) * 48 / 60;
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. */
724 AddMidiData(outblock
, MIDIST_CONTROLLER
+ channel
, b1
, b2
);
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;
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) {
742 AddMidiData(outblock
, MIDIST_PROGCHG
+ channel
, b1
);
744 case MIDIST_PITCHBEND
:
745 b2
= this->songdata
[chandata
.playpos
++];
746 AddMidiData(outblock
, MIDIST_PITCHBEND
+ channel
, b1
, b2
);
752 newdelay
= this->ReadVariableLength(chandata
.playpos
);
753 } while (newdelay
== 0);
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) {
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
);
781 return this->shouldplayflag
;
785 * Perform playback of whole song.
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 */
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 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
;
1070 /* Remove all '.' characters from filename */
1071 char *wp
= basename
;
1072 for (const char *rp
= fnstart
; *rp
!= '\0'; rp
++) {
1073 if (*rp
!= '.') *wp
++ = *rp
;
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
;
1092 data
= GetMusicCatEntryData(song
.filename
, song
.cat_index
, datalen
);
1093 if (data
== nullptr) return std::string();
1096 if (!midifile
.LoadMpsData(data
, datalen
)) {
1098 return std::string();
1102 if (midifile
.WriteSMF(output_filename
.c_str())) {
1103 return output_filename
;
1105 return std::string();
1110 static bool CmdDumpSMF(byte argc
, char *argv
[])
1113 IConsolePrint(CC_HELP
, "Write the current song to a Standard MIDI File. Usage: 'dumpsmf <filename>'.");
1117 IConsolePrint(CC_WARNING
, "You must specify a filename to write MIDI data to.");
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.");
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.");
1131 IConsolePrint(CC_INFO
, "Dumping MIDI to '{}'.", fnbuf
);
1133 if (_midifile_instance
->WriteSMF(fnbuf
)) {
1134 IConsolePrint(CC_INFO
, "File written successfully.");
1137 IConsolePrint(CC_ERROR
, "An error occurred writing MIDI file.");
1142 static void RegisterConsoleMidiCommands()
1144 static bool registered
= false;
1146 IConsole::CmdRegister("dumpsmf", CmdDumpSMF
);
1151 MidiFile::MidiFile()
1153 RegisterConsoleMidiCommands();
1156 MidiFile::~MidiFile()
1158 if (_midifile_instance
== this) {
1159 _midifile_instance
= nullptr;