4 * This file is part of OpenTTD.
5 * 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.
6 * 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.
7 * 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/>.
10 /* @file midifile.cpp Parser for standard MIDI files */
12 #include "midifile.hpp"
13 #include "../fileio_func.h"
14 #include "../fileio_type.h"
15 #include "../string_func.h"
16 #include "../core/endian_func.hpp"
17 #include "../base_media_base.h"
21 #include "../console_func.h"
22 #include "../console_internal.h"
25 /* SMF reader based on description at: http://www.somascape.org/midi/tech/mfile.html */
28 static MidiFile
*_midifile_instance
= NULL
;
31 * Owning byte buffer readable as a stream.
32 * RAII-compliant to make teardown in error situations easier.
40 * Construct buffer from data in a file.
41 * If file does not have sufficient bytes available, the object is constructed
42 * in an error state, that causes all further function calls to fail.
43 * @param file file to read from at current position
44 * @param len number of bytes to read
46 ByteBuffer(FILE *file
, size_t len
)
48 this->buf
= MallocT
<byte
>(len
);
49 if (fread(this->buf
, 1, len
, file
) == len
) {
59 * Destructor, frees the buffer.
67 * Return whether the buffer was constructed successfully.
68 * @return true is the buffer contains data
72 return this->buflen
> 0;
76 * Return whether reading has reached the end of the buffer.
77 * @return true if there are no more bytes available to read
81 return this->pos
>= this->buflen
;
85 * Read a single byte from the buffer.
86 * @param[out] b returns the read value
87 * @return true if a byte was available for reading
89 bool ReadByte(byte
&b
)
91 if (this->IsEnd()) return false;
92 b
= this->buf
[this->pos
++];
97 * Read a MIDI file variable length value.
98 * Each byte encodes 7 bits of the value, most-significant bits are encoded first.
99 * If the most significant bit in a byte is set, there are further bytes encoding the value.
100 * @param[out] res returns the read value
101 * @return true if there was data available
103 bool ReadVariableLength(uint32
&res
)
108 if (this->IsEnd()) return false;
109 b
= this->buf
[this->pos
++];
110 res
= (res
<< 7) | (b
& 0x7F);
116 * Read bytes into a buffer.
117 * @param[out] dest buffer to copy info
118 * @param length number of bytes to read
119 * @return true if the requested number of bytes were available
121 bool ReadBuffer(byte
*dest
, size_t length
)
123 if (this->IsEnd()) return false;
124 if (this->buflen
- this->pos
< length
) return false;
125 memcpy(dest
, this->buf
+ this->pos
, length
);
131 * Skip over a number of bytes in the buffer.
132 * @param count number of bytes to skip over
133 * @return true if there were enough bytes available
135 bool Skip(size_t count
)
137 if (this->IsEnd()) return false;
138 if (this->buflen
- this->pos
< count
) return false;
144 * Go a number of bytes back to re-read.
145 * @param count number of bytes to go back
146 * @return true if at least count bytes had been read previously
148 bool Rewind(size_t count
)
150 if (count
> this->pos
) return false;
156 static bool ReadTrackChunk(FILE *file
, MidiFile
&target
)
160 const byte magic
[] = { 'M', 'T', 'r', 'k' };
161 if (fread(buf
, sizeof(magic
), 1, file
) != 1) {
164 if (memcmp(magic
, buf
, sizeof(magic
)) != 0) {
168 /* Read chunk length and then the whole chunk */
170 if (fread(&chunk_length
, 1, 4, file
) != 4) {
173 chunk_length
= FROM_BE32(chunk_length
);
175 ByteBuffer
chunk(file
, chunk_length
);
176 if (!chunk
.IsValid()) {
180 target
.blocks
.push_back(MidiFile::DataBlock());
181 MidiFile::DataBlock
*block
= &target
.blocks
.back();
183 byte last_status
= 0;
184 bool running_sysex
= false;
185 while (!chunk
.IsEnd()) {
186 /* Read deltatime for event, start new block */
187 uint32 deltatime
= 0;
188 if (!chunk
.ReadVariableLength(deltatime
)) {
192 target
.blocks
.push_back(MidiFile::DataBlock(block
->ticktime
+ deltatime
));
193 block
= &target
.blocks
.back();
196 /* Read status byte */
198 if (!chunk
.ReadByte(status
)) {
202 if ((status
& 0x80) == 0) {
203 /* High bit not set means running status message, status is same as last
204 * convert to explicit status */
206 status
= last_status
;
208 } else if ((status
& 0xF0) != 0xF0) {
209 /* Regular channel message */
210 last_status
= status
;
213 switch (status
& 0xF0) {
216 case MIDIST_POLYPRESS
:
217 case MIDIST_CONTROLLER
:
218 case MIDIST_PITCHBEND
:
219 /* 3 byte messages */
220 data
= block
->data
.Append(3);
222 if (!chunk
.ReadBuffer(&data
[1], 2)) {
227 case MIDIST_CHANPRESS
:
228 /* 2 byte messages */
229 data
= block
->data
.Append(2);
231 if (!chunk
.ReadByte(data
[1])) {
238 } else if (status
== MIDIST_SMF_META
) {
239 /* Meta event, read event type byte and data length */
240 if (!chunk
.ReadByte(buf
[0])) {
244 if (!chunk
.ReadVariableLength(length
)) {
249 /* end of track, no more data (length != 0 is illegal) */
250 return (length
== 0);
253 if (length
!= 3) return false;
254 if (!chunk
.ReadBuffer(buf
, 3)) return false;
255 target
.tempos
.push_back(MidiFile::TempoChange(block
->ticktime
, buf
[0] << 16 | buf
[1] << 8 | buf
[2]));
258 /* unimportant meta event, skip over it */
259 if (!chunk
.Skip(length
)) {
264 } else if (status
== MIDIST_SYSEX
|| (status
== MIDIST_SMF_ESCAPE
&& running_sysex
)) {
265 /* System exclusive message */
267 if (!chunk
.ReadVariableLength(length
)) {
270 byte
*data
= block
->data
.Append(length
+ 1);
272 if (!chunk
.ReadBuffer(data
+ 1, length
)) {
275 if (data
[length
] != 0xF7) {
276 /* Engage Casio weirdo mode - convert to normal sysex */
277 running_sysex
= true;
278 *block
->data
.Append() = 0xF7;
280 running_sysex
= false;
282 } else if (status
== MIDIST_SMF_ESCAPE
) {
283 /* Escape sequence */
285 if (!chunk
.ReadVariableLength(length
)) {
288 byte
*data
= block
->data
.Append(length
);
289 if (!chunk
.ReadBuffer(data
, length
)) {
293 /* Messages undefined in standard midi files:
294 * 0xF1 - MIDI time code quarter frame
295 * 0xF2 - Song position pointer
297 * 0xF4 - undefined/reserved
298 * 0xF5 - undefined/reserved
299 * 0xF6 - Tune request for analog synths
300 * 0xF8..0xFE - System real-time messages
310 bool TicktimeAscending(const T
&a
, const T
&b
)
312 return a
.ticktime
< b
.ticktime
;
315 static bool FixupMidiData(MidiFile
&target
)
317 /* Sort all tempo changes and events */
318 std::sort(target
.tempos
.begin(), target
.tempos
.end(), TicktimeAscending
<MidiFile::TempoChange
>);
319 std::sort(target
.blocks
.begin(), target
.blocks
.end(), TicktimeAscending
<MidiFile::DataBlock
>);
321 if (target
.tempos
.size() == 0) {
322 /* No tempo information, assume 120 bpm (500,000 microseconds per beat */
323 target
.tempos
.push_back(MidiFile::TempoChange(0, 500000));
325 /* Add sentinel tempo at end */
326 target
.tempos
.push_back(MidiFile::TempoChange(UINT32_MAX
, 0));
328 /* Merge blocks with identical tick times */
329 std::vector
<MidiFile::DataBlock
> merged_blocks
;
330 uint32 last_ticktime
= 0;
331 for (size_t i
= 0; i
< target
.blocks
.size(); i
++) {
332 MidiFile::DataBlock
&block
= target
.blocks
[i
];
333 if (block
.data
.Length() == 0) {
335 } else if (block
.ticktime
> last_ticktime
|| merged_blocks
.size() == 0) {
336 merged_blocks
.push_back(block
);
337 last_ticktime
= block
.ticktime
;
339 byte
*datadest
= merged_blocks
.back().data
.Append(block
.data
.Length());
340 memcpy(datadest
, block
.data
.Begin(), block
.data
.Length());
343 std::swap(merged_blocks
, target
.blocks
);
345 /* Annotate blocks with real time */
347 uint32 last_realtime
= 0;
348 size_t cur_tempo
= 0, cur_block
= 0;
349 while (cur_block
< target
.blocks
.size()) {
350 MidiFile::DataBlock
&block
= target
.blocks
[cur_block
];
351 MidiFile::TempoChange
&tempo
= target
.tempos
[cur_tempo
];
352 MidiFile::TempoChange
&next_tempo
= target
.tempos
[cur_tempo
+1];
353 if (block
.ticktime
<= next_tempo
.ticktime
) {
354 /* block is within the current tempo */
355 int64 tickdiff
= block
.ticktime
- last_ticktime
;
356 last_ticktime
= block
.ticktime
;
357 last_realtime
+= uint32(tickdiff
* tempo
.tempo
/ target
.tickdiv
);
358 block
.realtime
= last_realtime
;
361 /* tempo change occurs before this block */
362 int64 tickdiff
= next_tempo
.ticktime
- last_ticktime
;
363 last_ticktime
= next_tempo
.ticktime
;
364 last_realtime
+= uint32(tickdiff
* tempo
.tempo
/ target
.tickdiv
); // current tempo until the tempo change
373 * Read the header of a standard MIDI file.
374 * @param[in] filename name of file to read from
375 * @param[out] header filled with data read
376 * @return true if the file could be opened and contained a header with correct format
378 bool MidiFile::ReadSMFHeader(const char *filename
, SMFHeader
&header
)
380 FILE *file
= FioFOpenFile(filename
, "rb", Subdirectory::BASESET_DIR
);
381 if (!file
) return false;
382 bool result
= ReadSMFHeader(file
, header
);
388 * Read the header of a standard MIDI file.
389 * The function will consume 14 bytes from the current file pointer position.
390 * @param[in] file open file to read from (should be in binary mode)
391 * @param[out] header filled with data read
392 * @return true if a header in correct format could be read from the file
394 bool MidiFile::ReadSMFHeader(FILE *file
, SMFHeader
&header
)
396 /* Try to read header, fixed size */
398 if (fread(buffer
, sizeof(buffer
), 1, file
) != 1) {
402 /* Check magic, 'MThd' followed by 4 byte length indicator (always = 6 in SMF) */
403 const byte magic
[] = { 'M', 'T', 'h', 'd', 0x00, 0x00, 0x00, 0x06 };
404 if (MemCmpT(buffer
, magic
, sizeof(magic
)) != 0) {
408 /* Read the parameters of the file */
409 header
.format
= (buffer
[8] << 8) | buffer
[9];
410 header
.tracks
= (buffer
[10] << 8) | buffer
[11];
411 header
.tickdiv
= (buffer
[12] << 8) | buffer
[13];
416 * Load a standard MIDI file.
417 * @param filename name of the file to load
418 * @returns true if loaded was successful
420 bool MidiFile::LoadFile(const char *filename
)
422 _midifile_instance
= this;
424 this->blocks
.clear();
425 this->tempos
.clear();
428 bool success
= false;
429 FILE *file
= FioFOpenFile(filename
, "rb", Subdirectory::BASESET_DIR
);
430 if (file
== NULL
) return false;
433 if (!ReadSMFHeader(file
, header
)) goto cleanup
;
435 /* Only format 0 (single-track) and format 1 (multi-track single-song) are accepted for now */
436 if (header
.format
!= 0 && header
.format
!= 1) goto cleanup
;
437 /* Doesn't support SMPTE timecode files */
438 if ((header
.tickdiv
& 0x8000) != 0) goto cleanup
;
440 this->tickdiv
= header
.tickdiv
;
442 for (; header
.tracks
> 0; header
.tracks
--) {
443 if (!ReadTrackChunk(file
, *this)) {
448 success
= FixupMidiData(*this);
457 * Decoder for "MPS MIDI" format data.
458 * This format for MIDI music is also used in a few other Microprose games contemporary with Transport Tycoon.
460 * 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.
462 * 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,
463 * the first list contains a number of reusable "segments", and the second list contains the "master tracks". Each list is prefixed with a byte
464 * 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,
465 * there is no index as such, so the entire data must be seeked through to build an index.
467 * The actual MIDI data inside each track is almost standard MIDI, prefixing every event with a delay, encoded using the same variable-length format
468 * 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
469 * 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.)
471 * As implemented in the original decoder, there is no support for recursively calling segments from segments, i.e. code 0xFE must only occur in
472 * 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
473 * be the original game music, not music from other games, or new productions.
475 * Additionally, some program change and controller events are given special meaning, see comments in the code.
478 /** Starting parameter and playback status for one channel/track */
480 byte cur_program
; ///< program selected, used for velocity scaling (lookup into programvelocities array)
481 byte running_status
; ///< last midi status code seen
482 uint16 delay
; ///< frames until next command
483 uint32 playpos
; ///< next byte to play this channel from
484 uint32 startpos
; ///< start position of master track
485 uint32 returnpos
; ///< next return position after playing a segment
486 Channel() : cur_program(0xFF), running_status(0), delay(0), playpos(0), startpos(0), returnpos(0) { }
488 Channel channels
[16]; ///< playback status for each MIDI channel
489 std::vector
<uint32
> segments
; ///< pointers into songdata to repeatable data segments
490 int16 tempo_ticks
; ///< ticker that increments when playing a frame, decrements before playing a frame
491 int16 current_tempo
; ///< threshold for actually playing a frame
492 int16 initial_tempo
; ///< starting tempo of song
493 bool shouldplayflag
; ///< not-end-of-song flag
495 static const int TEMPO_RATE
;
496 static const byte programvelocities
[128];
498 const byte
*songdata
; ///< raw data array
499 size_t songdatalen
; ///< length of song data
500 MidiFile
&target
; ///< recipient of data
502 /** Overridden MIDI status codes used in the data format */
504 MPSMIDIST_SEGMENT_RETURN
= 0xFD, ///< resume playing master track from stored position
505 MPSMIDIST_SEGMENT_CALL
= 0xFE, ///< store current position of master track playback, and begin playback of a segment
506 MPSMIDIST_ENDSONG
= 0xFF, ///< immediately end the song
509 static void AddMidiData(MidiFile::DataBlock
&block
, byte b1
, byte b2
)
511 *block
.data
.Append() = b1
;
512 *block
.data
.Append() = b2
;
514 static void AddMidiData(MidiFile::DataBlock
&block
, byte b1
, byte b2
, byte b3
)
516 *block
.data
.Append() = b1
;
517 *block
.data
.Append() = b2
;
518 *block
.data
.Append() = b3
;
522 * Construct a TTD DOS music format decoder.
523 * @param data Buffer of song data from CAT file, ownership remains with caller
524 * @param length Length of the data buffer in bytes
525 * @param target MidiFile object to add decoded data to
527 MpsMachine(const byte
*data
, size_t length
, MidiFile
&target
)
528 : songdata(data
), songdatalen(length
), target(target
)
534 /* First byte is the initial "tempo" */
535 this->initial_tempo
= this->songdata
[pos
++];
537 /* Next byte is a count of callable segments */
538 loopmax
= this->songdata
[pos
++];
539 for (loopidx
= 0; loopidx
< loopmax
; loopidx
++) {
540 /* Segments form a linked list in the stream,
541 * first two bytes in each is an offset to the next.
542 * Two bytes between offset to next and start of data
543 * are unaccounted for. */
544 this->segments
.push_back(pos
+ 4);
545 pos
+= FROM_LE16(*(const int16
*)(this->songdata
+ pos
));
548 /* After segments follows list of master tracks for each channel,
549 * also prefixed with a byte counting actual tracks. */
550 loopmax
= this->songdata
[pos
++];
551 for (loopidx
= 0; loopidx
< loopmax
; loopidx
++) {
552 /* Similar structure to segments list, but also has
553 * the MIDI channel number as a byte before the offset
555 byte ch
= this->songdata
[pos
++];
556 this->channels
[ch
].startpos
= pos
+ 4;
557 pos
+= FROM_LE16(*(const int16
*)(this->songdata
+ pos
));
562 * Read an SMF-style variable length value (note duration) from songdata.
563 * @param pos Position to read from, updated to point to next byte after the value read
564 * @return Value read from data stream
566 uint16
ReadVariableLength(uint32
&pos
)
571 b
= this->songdata
[pos
++];
572 res
= (res
<< 7) + (b
& 0x7F);
578 * Prepare for playback from the beginning. Resets the song pointer for every track to the beginning.
582 for (int ch
= 0; ch
< 16; ch
++) {
583 Channel
&chandata
= this->channels
[ch
];
584 if (chandata
.startpos
!= 0) {
585 /* Active track, set position to beginning */
586 chandata
.playpos
= chandata
.startpos
;
587 chandata
.delay
= this->ReadVariableLength(chandata
.playpos
);
589 /* Inactive track, mark as such */
590 chandata
.playpos
= 0;
597 * Play one frame of data from one channel
599 uint16
PlayChannelFrame(MidiFile::DataBlock
&outblock
, int channel
)
603 Channel
&chandata
= this->channels
[channel
];
606 /* Read command/status byte */
607 b1
= this->songdata
[chandata
.playpos
++];
609 /* Command 0xFE, call segment from master track */
610 if (b1
== MPSMIDIST_SEGMENT_CALL
) {
611 b1
= this->songdata
[chandata
.playpos
++];
612 chandata
.returnpos
= chandata
.playpos
;
613 chandata
.playpos
= this->segments
[b1
];
614 newdelay
= this->ReadVariableLength(chandata
.playpos
);
621 /* Command 0xFD, return from segment to master track */
622 if (b1
== MPSMIDIST_SEGMENT_RETURN
) {
623 chandata
.playpos
= chandata
.returnpos
;
624 chandata
.returnpos
= 0;
625 newdelay
= this->ReadVariableLength(chandata
.playpos
);
632 /* Command 0xFF, end of song */
633 if (b1
== MPSMIDIST_ENDSONG
) {
634 this->shouldplayflag
= false;
638 /* Regular MIDI channel message status byte */
640 /* Save the status byte as running status for the channel
641 * and read another byte for first parameter to command */
642 chandata
.running_status
= b1
;
643 b1
= this->songdata
[chandata
.playpos
++];
646 switch (chandata
.running_status
& 0xF0) {
649 b2
= this->songdata
[chandata
.playpos
++];
651 /* Note on, read velocity and scale according to rules */
654 /* Percussion channel, fixed velocity scaling not in the table */
655 velocity
= (int16
)b2
* 0x50;
657 /* Regular channel, use scaling from table */
658 velocity
= b2
* programvelocities
[chandata
.cur_program
];
660 b2
= (velocity
/ 128) & 0x00FF;
661 AddMidiData(outblock
, MIDIST_NOTEON
+ channel
, b1
, b2
);
664 AddMidiData(outblock
, MIDIST_NOTEON
+ channel
, b1
, 0);
667 case MIDIST_CONTROLLER
:
668 b2
= this->songdata
[chandata
.playpos
++];
669 if (b1
== MIDICT_MODE_MONO
) {
670 /* Unknown what the purpose of this is.
671 * Occurs in "Can't get There from Here" and in "Aliens Ate my Railway" a few times each.
672 * Possibly intended to give hints to other (non-GM) music drivers decoding the song.
675 } else if (b1
== 0) {
676 /* Standard MIDI controller 0 is "bank select", override meaning to change tempo.
677 * This is not actually used in any of the original songs. */
679 this->current_tempo
= ((int)b2
) * 48 / 60;
682 } else if (b1
== MIDICT_EFFECTS1
) {
683 /* Override value of this controller, default mapping is Reverb Send Level according to MMA RP-023.
684 * Unknown what the purpose of this particular value is. */
687 AddMidiData(outblock
, MIDIST_CONTROLLER
+ channel
, b1
, b2
);
691 /* Program change to "Applause" is originally used
692 * to cause the song to loop, but that gets handled
693 * separately in the output driver here.
694 * Just end the song. */
695 this->shouldplayflag
= false;
698 /* Used for note velocity scaling lookup */
699 chandata
.cur_program
= b1
;
700 /* Two programs translated to a third, this is likely to
701 * provide three different velocity scalings of "brass". */
702 if (b1
== 0x57 || b1
== 0x3F) {
705 AddMidiData(outblock
, MIDIST_PROGCHG
+ channel
, b1
);
707 case MIDIST_PITCHBEND
:
708 b2
= this->songdata
[chandata
.playpos
++];
709 AddMidiData(outblock
, MIDIST_PITCHBEND
+ channel
, b1
, b2
);
715 newdelay
= this->ReadVariableLength(chandata
.playpos
);
716 } while (newdelay
== 0);
722 * Play one frame of data into a block.
724 bool PlayFrame(MidiFile::DataBlock
&block
)
726 /* Update tempo/ticks counter */
727 this->tempo_ticks
-= this->current_tempo
;
728 if (this->tempo_ticks
> 0) {
731 this->tempo_ticks
+= TEMPO_RATE
;
733 /* Look over all channels, play those active */
734 for (int ch
= 0; ch
< 16; ch
++) {
735 Channel
&chandata
= this->channels
[ch
];
736 if (chandata
.playpos
!= 0) {
737 if (chandata
.delay
== 0) {
738 chandata
.delay
= this->PlayChannelFrame(block
, ch
);
744 return this->shouldplayflag
;
748 * Perform playback of whole song.
752 /* Tempo seems to be handled as TEMPO_RATE = 148 ticks per second.
753 * Use this as the tickdiv, and define the tempo to be one second (1M microseconds) per tickdiv.
754 * MIDI software loading exported files will show a bogus tempo, but playback will be correct. */
755 this->target
.tickdiv
= TEMPO_RATE
;
756 this->target
.tempos
.push_back(MidiFile::TempoChange(0, 1000000));
758 /* Initialize playback simulation */
760 this->shouldplayflag
= true;
761 this->current_tempo
= (int32
)this->initial_tempo
* 24 / 60;
762 this->tempo_ticks
= this->current_tempo
;
764 /* Always reset percussion channel to program 0 */
765 this->target
.blocks
.push_back(MidiFile::DataBlock());
766 AddMidiData(this->target
.blocks
.back(), MIDIST_PROGCHG
+9, 0x00);
768 /* Technically should be an endless loop, but having
769 * a maximum (about 10 minutes) avoids getting stuck,
770 * in case of corrupted data. */
771 for (uint32 tick
= 0; tick
< 100000; tick
+=1) {
772 this->target
.blocks
.push_back(MidiFile::DataBlock());
773 auto &block
= this->target
.blocks
.back();
774 block
.ticktime
= tick
;
775 if (!this->PlayFrame(block
)) {
782 /** Frames/ticks per second for music playback */
783 const int MpsMachine::TEMPO_RATE
= 148;
784 /** Base note velocities for various GM programs */
785 const byte
MpsMachine::programvelocities
[128] = {
786 100, 100, 100, 100, 100, 90, 100, 100, 100, 100, 100, 90, 100, 100, 100, 100,
787 100, 100, 85, 100, 100, 100, 100, 100, 100, 100, 100, 100, 90, 90, 110, 80,
788 100, 100, 100, 90, 70, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
789 100, 100, 90, 100, 100, 100, 100, 100, 100, 120, 100, 100, 100, 120, 100, 127,
790 100, 100, 90, 100, 100, 100, 100, 100, 100, 95, 100, 100, 100, 100, 100, 100,
791 100, 100, 100, 100, 100, 100, 100, 115, 100, 100, 100, 100, 100, 100, 100, 100,
792 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
793 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
797 * Create MIDI data from song data for the original Microprose music drivers.
798 * @param data pointer to block of data
799 * @param length size of data in bytes
800 * @return true if the data could be loaded
802 bool MidiFile::LoadMpsData(const byte
*data
, size_t length
)
804 _midifile_instance
= this;
806 MpsMachine
machine(data
, length
, *this);
807 return machine
.PlayInto() && FixupMidiData(*this);
810 bool MidiFile::LoadSong(const MusicSongInfo
&song
)
812 switch (song
.filetype
) {
813 case MTT_STANDARDMIDI
:
814 return this->LoadFile(song
.filename
);
817 size_t songdatalen
= 0;
818 byte
*songdata
= GetMusicCatEntryData(song
.filename
, song
.cat_index
, songdatalen
);
819 if (songdata
!= NULL
) {
820 bool result
= this->LoadMpsData(songdata
, songdatalen
);
833 * Move data from other to this, and clears other.
834 * @param other object containing loaded data to take over
836 void MidiFile::MoveFrom(MidiFile
&other
)
838 std::swap(this->blocks
, other
.blocks
);
839 std::swap(this->tempos
, other
.tempos
);
840 this->tickdiv
= other
.tickdiv
;
842 _midifile_instance
= this;
844 other
.blocks
.clear();
845 other
.tempos
.clear();
849 static void WriteVariableLen(FILE *f
, uint32 value
)
853 fwrite(&tb
, 1, 1, f
);
854 } else if (value
< 0x3FFF) {
856 tb
[1] = value
& 0x7F; value
>>= 7;
857 tb
[0] = (value
& 0x7F) | 0x80; value
>>= 7;
858 fwrite(tb
, 1, sizeof(tb
), f
);
859 } else if (value
< 0x1FFFFF) {
861 tb
[2] = value
& 0x7F; value
>>= 7;
862 tb
[1] = (value
& 0x7F) | 0x80; value
>>= 7;
863 tb
[0] = (value
& 0x7F) | 0x80; value
>>= 7;
864 fwrite(tb
, 1, sizeof(tb
), f
);
865 } else if (value
< 0x0FFFFFFF) {
867 tb
[3] = value
& 0x7F; value
>>= 7;
868 tb
[2] = (value
& 0x7F) | 0x80; value
>>= 7;
869 tb
[1] = (value
& 0x7F) | 0x80; value
>>= 7;
870 tb
[0] = (value
& 0x7F) | 0x80; value
>>= 7;
871 fwrite(tb
, 1, sizeof(tb
), f
);
876 * Write a Standard MIDI File containing the decoded music.
877 * @param filename Name of file to write to
878 * @return True if the file was written to completion
880 bool MidiFile::WriteSMF(const char *filename
)
882 FILE *f
= FioFOpenFile(filename
, "wb", Subdirectory::NO_DIRECTORY
);
888 const byte fileheader
[] = {
889 'M', 'T', 'h', 'd', // block name
890 0x00, 0x00, 0x00, 0x06, // BE32 block length, always 6 bytes
891 0x00, 0x00, // writing format 0 (all in one track)
892 0x00, 0x01, // containing 1 track (BE16)
893 (byte
)(this->tickdiv
>> 8), (byte
)this->tickdiv
, // tickdiv in BE16
895 fwrite(fileheader
, sizeof(fileheader
), 1, f
);
898 const byte trackheader
[] = {
899 'M', 'T', 'r', 'k', // block name
900 0, 0, 0, 0, // BE32 block length, unknown at this time
902 fwrite(trackheader
, sizeof(trackheader
), 1, f
);
903 /* Determine position to write the actual track block length at */
904 size_t tracksizepos
= ftell(f
) - 4;
906 /* Write blocks in sequence */
908 size_t nexttempoindex
= 0;
909 for (size_t bi
= 0; bi
< this->blocks
.size(); bi
++) {
910 DataBlock
&block
= this->blocks
[bi
];
911 TempoChange
&nexttempo
= this->tempos
[nexttempoindex
];
913 uint32 timediff
= block
.ticktime
- lasttime
;
915 /* Check if there is a tempo change before this block */
916 if (nexttempo
.ticktime
< block
.ticktime
) {
917 timediff
= nexttempo
.ticktime
- lasttime
;
920 /* Write delta time for block */
921 lasttime
+= timediff
;
922 bool needtime
= false;
923 WriteVariableLen(f
, timediff
);
925 /* Write tempo change if there is one */
926 if (nexttempo
.ticktime
<= block
.ticktime
) {
927 byte tempobuf
[6] = { MIDIST_SMF_META
, 0x51, 0x03, 0, 0, 0 };
928 tempobuf
[3] = (nexttempo
.tempo
& 0x00FF0000) >> 16;
929 tempobuf
[4] = (nexttempo
.tempo
& 0x0000FF00) >> 8;
930 tempobuf
[5] = (nexttempo
.tempo
& 0x000000FF);
931 fwrite(tempobuf
, sizeof(tempobuf
), 1, f
);
935 /* If a tempo change occurred between two blocks, rather than
936 * at start of this one, start over with delta time for the block. */
937 if (nexttempo
.ticktime
< block
.ticktime
) {
938 /* Start loop over at same index */
943 /* Write each block data command */
944 byte
*dp
= block
.data
.Begin();
945 while (dp
< block
.data
.End()) {
946 /* Always zero delta time inside blocks */
952 /* Check message type and write appropriate number of bytes */
953 switch (*dp
& 0xF0) {
956 case MIDIST_POLYPRESS
:
957 case MIDIST_CONTROLLER
:
958 case MIDIST_PITCHBEND
:
963 case MIDIST_CHANPRESS
:
969 /* Sysex needs to measure length and write that as well */
970 if (*dp
== MIDIST_SYSEX
) {
974 while (*sysexend
!= MIDIST_ENDSYSEX
) sysexend
++;
975 ptrdiff_t sysexlen
= sysexend
- dp
;
976 WriteVariableLen(f
, sysexlen
);
977 fwrite(dp
, 1, sysexend
- dp
, f
);
982 /* Fail for any other commands */
988 /* End of track marker */
989 static const byte track_end_marker
[] = { 0x00, MIDIST_SMF_META
, 0x2F, 0x00 };
990 fwrite(&track_end_marker
, sizeof(track_end_marker
), 1, f
);
992 /* Fill out the RIFF block length */
993 size_t trackendpos
= ftell(f
);
994 fseek(f
, tracksizepos
, SEEK_SET
);
995 uint32 tracksize
= (uint32
)(trackendpos
- tracksizepos
- 4); // blindly assume we never produce files larger than 2 GB
996 tracksize
= TO_BE32(tracksize
);
997 fwrite(&tracksize
, 4, 1, f
);
1004 * Get the name of a Standard MIDI File for a given song.
1005 * For songs already in SMF format, just returns the original.
1006 * Otherwise the song is converted, written to a temporary-ish file, and the written filename is returned.
1007 * @param song Song definition to query
1008 * @return Full filename string, empty string if failed
1010 std::string
MidiFile::GetSMFFile(const MusicSongInfo
&song
)
1012 if (song
.filetype
== MTT_STANDARDMIDI
) {
1013 char filename
[MAX_PATH
];
1014 if (FioFindFullPath(filename
, lastof(filename
), Subdirectory::BASESET_DIR
, song
.filename
)) {
1015 return std::string(filename
);
1016 } else if (FioFindFullPath(filename
, lastof(filename
), Subdirectory::OLD_GM_DIR
, song
.filename
)) {
1017 return std::string(filename
);
1019 return std::string();
1023 if (song
.filetype
!= MTT_MPSMIDI
) return std::string();
1025 char basename
[MAX_PATH
];
1027 const char *fnstart
= strrchr(song
.filename
, PATHSEPCHAR
);
1028 if (fnstart
== NULL
) {
1029 fnstart
= song
.filename
;
1034 /* Remove all '.' characters from filename */
1035 char *wp
= basename
;
1036 for (const char *rp
= fnstart
; *rp
!= '\0'; rp
++) {
1037 if (*rp
!= '.') *wp
++ = *rp
;
1042 char tempdirname
[MAX_PATH
];
1043 FioGetFullPath(tempdirname
, lastof(tempdirname
), Searchpath::SP_AUTODOWNLOAD_DIR
, Subdirectory::BASESET_DIR
, basename
);
1044 if (!AppendPathSeparator(tempdirname
, lastof(tempdirname
))) return std::string();
1045 FioCreateDirectory(tempdirname
);
1047 char output_filename
[MAX_PATH
];
1048 seprintf(output_filename
, lastof(output_filename
), "%s%d.mid", tempdirname
, song
.cat_index
);
1050 if (FileExists(output_filename
)) {
1051 /* If the file already exists, assume it's the correct decoded data */
1052 return std::string(output_filename
);
1057 data
= GetMusicCatEntryData(song
.filename
, song
.cat_index
, datalen
);
1058 if (data
== NULL
) return std::string();
1061 if (!midifile
.LoadMpsData(data
, datalen
)) {
1063 return std::string();
1067 if (midifile
.WriteSMF(output_filename
)) {
1068 return std::string(output_filename
);
1070 return std::string();
1075 static bool CmdDumpSMF(byte argc
, char *argv
[])
1078 IConsolePrint(CC_WARNING
, "Write the current song to a Standard MIDI File. Usage: 'dumpsmf <filename>'");
1082 IConsolePrint(CC_WARNING
, "You must specify a filename to write MIDI data to.");
1086 if (_midifile_instance
== NULL
) {
1087 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.");
1091 char fnbuf
[MAX_PATH
] = { 0 };
1092 if (seprintf(fnbuf
, lastof(fnbuf
), "%s%s", FiosGetScreenshotDir(), argv
[1]) >= (int)lengthof(fnbuf
)) {
1093 IConsolePrint(CC_ERROR
, "Filename too long.");
1096 IConsolePrintF(CC_INFO
, "Dumping MIDI to: %s", fnbuf
);
1098 if (_midifile_instance
->WriteSMF(fnbuf
)) {
1099 IConsolePrint(CC_INFO
, "File written successfully.");
1102 IConsolePrint(CC_ERROR
, "An error occurred writing MIDI file.");
1107 static void RegisterConsoleMidiCommands()
1109 static bool registered
= false;
1111 IConsoleCmdRegister("dumpsmf", CmdDumpSMF
);
1116 MidiFile::MidiFile()
1118 RegisterConsoleMidiCommands();
1121 MidiFile::~MidiFile()
1123 if (_midifile_instance
== this) {
1124 _midifile_instance
= NULL
;