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 "../core/mem_func.hpp"
16 #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 uint8_t *MidiGetStandardSysexMessage(MidiSysexMessage msg
, size_t &length
)
35 static uint8_t reset_gm_sysex
[] = { 0xF0, 0x7E, 0x7F, 0x09, 0x01, 0xF7 };
36 static uint8_t reset_gs_sysex
[] = { 0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7 };
37 static uint8_t reset_xg_sysex
[] = { 0xF0, 0x43, 0x10, 0x4C, 0x00, 0x00, 0x7E, 0x00, 0xF7 };
38 static uint8_t roland_reverb_sysex
[] = { 0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x01, 0x30, 0x02, 0x04, 0x00, 0x40, 0x40, 0x00, 0x00, 0x09, 0xF7 };
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.
63 std::vector
<uint8_t> buf
;
67 * Construct buffer from data in a file.
68 * If file does not have sufficient bytes available, the object is constructed
69 * in an error state, that causes all further function calls to fail.
70 * @param file file to read from at current position
71 * @param len number of bytes to read
73 ByteBuffer(FileHandle
&file
, size_t len
)
75 this->buf
.resize(len
);
76 if (fread(this->buf
.data(), 1, len
, file
) == len
) {
85 * Return whether the buffer was constructed successfully.
86 * @return true is the buffer contains data
90 return !this->buf
.empty();
94 * Return whether reading has reached the end of the buffer.
95 * @return true if there are no more bytes available to read
99 return this->pos
>= this->buf
.size();
103 * Read a single byte from the buffer.
104 * @param[out] b returns the read value
105 * @return true if a byte was available for reading
107 bool ReadByte(uint8_t &b
)
109 if (this->IsEnd()) return false;
110 b
= this->buf
[this->pos
++];
115 * Read a MIDI file variable length value.
116 * Each byte encodes 7 bits of the value, most-significant bits are encoded first.
117 * If the most significant bit in a byte is set, there are further bytes encoding the value.
118 * @param[out] res returns the read value
119 * @return true if there was data available
121 bool ReadVariableLength(uint32_t &res
)
126 if (this->IsEnd()) return false;
127 b
= this->buf
[this->pos
++];
128 res
= (res
<< 7) | (b
& 0x7F);
134 * Read bytes into a buffer.
135 * @param[out] dest buffer to copy into
136 * @param length number of bytes to read
137 * @return true if the requested number of bytes were available
139 bool ReadBuffer(uint8_t *dest
, size_t length
)
141 if (this->IsEnd()) return false;
142 if (this->buf
.size() - this->pos
< length
) return false;
143 std::copy(std::begin(this->buf
) + this->pos
, std::begin(this->buf
) + this->pos
+ length
, dest
);
149 * Read bytes into a MidiFile::DataBlock.
150 * @param[out] dest DataBlock to copy into
151 * @param length number of bytes to read
152 * @return true if the requested number of bytes were available
154 bool ReadDataBlock(MidiFile::DataBlock
*dest
, size_t length
)
156 if (this->IsEnd()) return false;
157 if (this->buf
.size() - this->pos
< length
) return false;
158 dest
->data
.insert(dest
->data
.end(), std::begin(this->buf
) + this->pos
, std::begin(this->buf
) + this->pos
+ length
);
164 * Skip over a number of bytes in the buffer.
165 * @param count number of bytes to skip over
166 * @return true if there were enough bytes available
168 bool Skip(size_t count
)
170 if (this->IsEnd()) return false;
171 if (this->buf
.size() - this->pos
< count
) return false;
177 * Go a number of bytes back to re-read.
178 * @param count number of bytes to go back
179 * @return true if at least count bytes had been read previously
181 bool Rewind(size_t count
)
183 if (count
> this->pos
) return false;
189 static bool ReadTrackChunk(FileHandle
&file
, MidiFile
&target
)
193 const uint8_t magic
[] = { 'M', 'T', 'r', 'k' };
194 if (fread(buf
, sizeof(magic
), 1, file
) != 1) {
197 if (memcmp(magic
, buf
, sizeof(magic
)) != 0) {
201 /* Read chunk length and then the whole chunk */
202 uint32_t chunk_length
;
203 if (fread(&chunk_length
, 1, 4, file
) != 4) {
206 chunk_length
= FROM_BE32(chunk_length
);
208 ByteBuffer
chunk(file
, chunk_length
);
209 if (!chunk
.IsValid()) {
213 target
.blocks
.push_back(MidiFile::DataBlock());
214 MidiFile::DataBlock
*block
= &target
.blocks
.back();
216 uint8_t last_status
= 0;
217 bool running_sysex
= false;
218 while (!chunk
.IsEnd()) {
219 /* Read deltatime for event, start new block */
220 uint32_t deltatime
= 0;
221 if (!chunk
.ReadVariableLength(deltatime
)) {
225 target
.blocks
.push_back(MidiFile::DataBlock(block
->ticktime
+ deltatime
));
226 block
= &target
.blocks
.back();
229 /* Read status byte */
231 if (!chunk
.ReadByte(status
)) {
235 if ((status
& 0x80) == 0) {
236 /* High bit not set means running status message, status is same as last
237 * convert to explicit status */
239 status
= last_status
;
241 } else if ((status
& 0xF0) != 0xF0) {
242 /* Regular channel message */
243 last_status
= status
;
245 switch (status
& 0xF0) {
248 case MIDIST_POLYPRESS
:
249 case MIDIST_CONTROLLER
:
250 case MIDIST_PITCHBEND
:
251 /* 3 byte messages */
252 block
->data
.push_back(status
);
253 if (!chunk
.ReadDataBlock(block
, 2)) {
258 case MIDIST_CHANPRESS
:
259 /* 2 byte messages */
260 block
->data
.push_back(status
);
261 if (!chunk
.ReadByte(buf
[0])) {
264 block
->data
.push_back(buf
[0]);
269 } else if (status
== MIDIST_SMF_META
) {
270 /* Meta event, read event type byte and data length */
271 if (!chunk
.ReadByte(buf
[0])) {
275 if (!chunk
.ReadVariableLength(length
)) {
280 /* end of track, no more data (length != 0 is illegal) */
281 return (length
== 0);
284 if (length
!= 3) return false;
285 if (!chunk
.ReadBuffer(buf
, 3)) return false;
286 target
.tempos
.push_back(MidiFile::TempoChange(block
->ticktime
, buf
[0] << 16 | buf
[1] << 8 | buf
[2]));
289 /* unimportant meta event, skip over it */
290 if (!chunk
.Skip(length
)) {
295 } else if (status
== MIDIST_SYSEX
|| (status
== MIDIST_SMF_ESCAPE
&& running_sysex
)) {
296 /* System exclusive message */
298 if (!chunk
.ReadVariableLength(length
)) {
301 block
->data
.push_back(0xF0);
302 if (!chunk
.ReadDataBlock(block
, length
)) {
305 if (block
->data
.back() != 0xF7) {
306 /* Engage Casio weirdo mode - convert to normal sysex */
307 running_sysex
= true;
308 block
->data
.push_back(0xF7);
310 running_sysex
= false;
312 } else if (status
== MIDIST_SMF_ESCAPE
) {
313 /* Escape sequence */
315 if (!chunk
.ReadVariableLength(length
)) {
318 if (!chunk
.ReadDataBlock(block
, length
)) {
322 /* Messages undefined in standard midi files:
323 * 0xF1 - MIDI time code quarter frame
324 * 0xF2 - Song position pointer
326 * 0xF4 - undefined/reserved
327 * 0xF5 - undefined/reserved
328 * 0xF6 - Tune request for analog synths
329 * 0xF8..0xFE - System real-time messages
339 bool TicktimeAscending(const T
&a
, const T
&b
)
341 return a
.ticktime
< b
.ticktime
;
344 static bool FixupMidiData(MidiFile
&target
)
346 /* Sort all tempo changes and events */
347 std::sort(target
.tempos
.begin(), target
.tempos
.end(), TicktimeAscending
<MidiFile::TempoChange
>);
348 std::sort(target
.blocks
.begin(), target
.blocks
.end(), TicktimeAscending
<MidiFile::DataBlock
>);
350 if (target
.tempos
.empty()) {
351 /* No tempo information, assume 120 bpm (500,000 microseconds per beat */
352 target
.tempos
.push_back(MidiFile::TempoChange(0, 500000));
354 /* Add sentinel tempo at end */
355 target
.tempos
.push_back(MidiFile::TempoChange(UINT32_MAX
, 0));
357 /* Merge blocks with identical tick times */
358 std::vector
<MidiFile::DataBlock
> merged_blocks
;
359 uint32_t last_ticktime
= 0;
360 for (size_t i
= 0; i
< target
.blocks
.size(); i
++) {
361 MidiFile::DataBlock
&block
= target
.blocks
[i
];
362 if (block
.data
.empty()) {
364 } else if (block
.ticktime
> last_ticktime
|| merged_blocks
.empty()) {
365 merged_blocks
.push_back(block
);
366 last_ticktime
= block
.ticktime
;
368 merged_blocks
.back().data
.insert(merged_blocks
.back().data
.end(), block
.data
.begin(), block
.data
.end());
371 std::swap(merged_blocks
, target
.blocks
);
373 /* Annotate blocks with real time */
375 uint32_t last_realtime
= 0;
376 size_t cur_tempo
= 0, cur_block
= 0;
377 while (cur_block
< target
.blocks
.size()) {
378 MidiFile::DataBlock
&block
= target
.blocks
[cur_block
];
379 MidiFile::TempoChange
&tempo
= target
.tempos
[cur_tempo
];
380 MidiFile::TempoChange
&next_tempo
= target
.tempos
[cur_tempo
+ 1];
381 if (block
.ticktime
<= next_tempo
.ticktime
) {
382 /* block is within the current tempo */
383 int64_t tickdiff
= block
.ticktime
- last_ticktime
;
384 last_ticktime
= block
.ticktime
;
385 last_realtime
+= uint32_t(tickdiff
* tempo
.tempo
/ target
.tickdiv
);
386 block
.realtime
= last_realtime
;
389 /* tempo change occurs before this block */
390 int64_t tickdiff
= next_tempo
.ticktime
- last_ticktime
;
391 last_ticktime
= next_tempo
.ticktime
;
392 last_realtime
+= uint32_t(tickdiff
* tempo
.tempo
/ target
.tickdiv
); // current tempo until the tempo change
401 * Read the header of a standard MIDI file.
402 * @param[in] filename name of file to read from
403 * @param[out] header filled with data read
404 * @return true if the file could be opened and contained a header with correct format
406 bool MidiFile::ReadSMFHeader(const std::string
&filename
, SMFHeader
&header
)
408 auto file
= FioFOpenFile(filename
, "rb", Subdirectory::BASESET_DIR
);
409 if (!file
.has_value()) return false;
410 bool result
= ReadSMFHeader(*file
, header
);
415 * Read the header of a standard MIDI file.
416 * The function will consume 14 bytes from the current file pointer position.
417 * @param[in] file open file to read from (should be in binary mode)
418 * @param[out] header filled with data read
419 * @return true if a header in correct format could be read from the file
421 bool MidiFile::ReadSMFHeader(FileHandle
&file
, SMFHeader
&header
)
423 /* Try to read header, fixed size */
425 if (fread(buffer
, sizeof(buffer
), 1, file
) != 1) {
429 /* Check magic, 'MThd' followed by 4 byte length indicator (always = 6 in SMF) */
430 const uint8_t magic
[] = { 'M', 'T', 'h', 'd', 0x00, 0x00, 0x00, 0x06 };
431 if (MemCmpT(buffer
, magic
, sizeof(magic
)) != 0) {
435 /* Read the parameters of the file */
436 header
.format
= (buffer
[8] << 8) | buffer
[9];
437 header
.tracks
= (buffer
[10] << 8) | buffer
[11];
438 header
.tickdiv
= (buffer
[12] << 8) | buffer
[13];
443 * Load a standard MIDI file.
444 * @param filename name of the file to load
445 * @returns true if loaded was successful
447 bool MidiFile::LoadFile(const std::string
&filename
)
449 _midifile_instance
= this;
451 this->blocks
.clear();
452 this->tempos
.clear();
455 auto file
= FioFOpenFile(filename
, "rb", Subdirectory::BASESET_DIR
);
456 if (!file
.has_value()) return false;
459 if (!ReadSMFHeader(*file
, header
)) return false;
461 /* Only format 0 (single-track) and format 1 (multi-track single-song) are accepted for now */
462 if (header
.format
!= 0 && header
.format
!= 1) return false;
463 /* Doesn't support SMPTE timecode files */
464 if ((header
.tickdiv
& 0x8000) != 0) return false;
466 this->tickdiv
= header
.tickdiv
;
468 for (; header
.tracks
> 0; header
.tracks
--) {
469 if (!ReadTrackChunk(*file
, *this)) {
474 return FixupMidiData(*this);
479 * Decoder for "MPS MIDI" format data.
480 * This format for MIDI music is also used in a few other Microprose games contemporary with Transport Tycoon.
482 * 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.
484 * 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,
485 * the first list contains a number of reusable "segments", and the second list contains the "master tracks". Each list is prefixed with a byte
486 * 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,
487 * there is no index as such, so the entire data must be seeked through to build an index.
489 * The actual MIDI data inside each track is almost standard MIDI, prefixing every event with a delay, encoded using the same variable-length format
490 * 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
491 * 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.)
493 * As implemented in the original decoder, there is no support for recursively calling segments from segments, i.e. code 0xFE must only occur in
494 * 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
495 * be the original game music, not music from other games, or new productions.
497 * Additionally, some program change and controller events are given special meaning, see comments in the code.
500 /** Starting parameter and playback status for one channel/track */
502 uint8_t cur_program
; ///< program selected, used for velocity scaling (lookup into programvelocities array)
503 uint8_t running_status
; ///< last midi status code seen
504 uint16_t delay
; ///< frames until next command
505 uint32_t playpos
; ///< next byte to play this channel from
506 uint32_t startpos
; ///< start position of master track
507 uint32_t returnpos
; ///< next return position after playing a segment
508 Channel() : cur_program(0xFF), running_status(0), delay(0), playpos(0), startpos(0), returnpos(0) { }
510 Channel channels
[16]; ///< playback status for each MIDI channel
511 std::vector
<uint32_t> segments
; ///< pointers into songdata to repeatable data segments
512 int16_t tempo_ticks
; ///< ticker that increments when playing a frame, decrements before playing a frame
513 int16_t current_tempo
; ///< threshold for actually playing a frame
514 int16_t initial_tempo
; ///< starting tempo of song
515 bool shouldplayflag
; ///< not-end-of-song flag
517 static const int TEMPO_RATE
;
518 static const uint8_t programvelocities
[128];
520 const uint8_t *songdata
; ///< raw data array
521 size_t songdatalen
; ///< length of song data
522 MidiFile
&target
; ///< recipient of data
524 /** Overridden MIDI status codes used in the data format */
526 MPSMIDIST_SEGMENT_RETURN
= 0xFD, ///< resume playing master track from stored position
527 MPSMIDIST_SEGMENT_CALL
= 0xFE, ///< store current position of master track playback, and begin playback of a segment
528 MPSMIDIST_ENDSONG
= 0xFF, ///< immediately end the song
531 static void AddMidiData(MidiFile::DataBlock
&block
, uint8_t b1
, uint8_t b2
)
533 block
.data
.push_back(b1
);
534 block
.data
.push_back(b2
);
536 static void AddMidiData(MidiFile::DataBlock
&block
, uint8_t b1
, uint8_t b2
, uint8_t b3
)
538 block
.data
.push_back(b1
);
539 block
.data
.push_back(b2
);
540 block
.data
.push_back(b3
);
544 * Construct a TTD DOS music format decoder.
545 * @param data Buffer of song data from CAT file, ownership remains with caller
546 * @param length Length of the data buffer in bytes
547 * @param target MidiFile object to add decoded data to
549 MpsMachine(const uint8_t *data
, size_t length
, MidiFile
&target
)
550 : songdata(data
), songdatalen(length
), target(target
)
556 /* First byte is the initial "tempo" */
557 this->initial_tempo
= this->songdata
[pos
++];
559 /* Next byte is a count of callable segments */
560 loopmax
= this->songdata
[pos
++];
561 for (loopidx
= 0; loopidx
< loopmax
; loopidx
++) {
562 /* Segments form a linked list in the stream,
563 * first two bytes in each is an offset to the next.
564 * Two bytes between offset to next and start of data
565 * are unaccounted for. */
566 this->segments
.push_back(pos
+ 4);
567 pos
+= FROM_LE16(*(const int16_t *)(this->songdata
+ pos
));
570 /* After segments follows list of master tracks for each channel,
571 * also prefixed with a byte counting actual tracks. */
572 loopmax
= this->songdata
[pos
++];
573 for (loopidx
= 0; loopidx
< loopmax
; loopidx
++) {
574 /* Similar structure to segments list, but also has
575 * the MIDI channel number as a byte before the offset
577 uint8_t ch
= this->songdata
[pos
++];
578 this->channels
[ch
].startpos
= pos
+ 4;
579 pos
+= FROM_LE16(*(const int16_t *)(this->songdata
+ pos
));
584 * Read an SMF-style variable length value (note duration) from songdata.
585 * @param pos Position to read from, updated to point to next byte after the value read
586 * @return Value read from data stream
588 uint16_t ReadVariableLength(uint32_t &pos
)
593 b
= this->songdata
[pos
++];
594 res
= (res
<< 7) + (b
& 0x7F);
600 * Prepare for playback from the beginning. Resets the song pointer for every track to the beginning.
604 for (int ch
= 0; ch
< 16; ch
++) {
605 Channel
&chandata
= this->channels
[ch
];
606 if (chandata
.startpos
!= 0) {
607 /* Active track, set position to beginning */
608 chandata
.playpos
= chandata
.startpos
;
609 chandata
.delay
= this->ReadVariableLength(chandata
.playpos
);
611 /* Inactive track, mark as such */
612 chandata
.playpos
= 0;
619 * Play one frame of data from one channel
621 uint16_t PlayChannelFrame(MidiFile::DataBlock
&outblock
, int channel
)
623 uint16_t newdelay
= 0;
625 Channel
&chandata
= this->channels
[channel
];
628 /* Read command/status byte */
629 b1
= this->songdata
[chandata
.playpos
++];
631 /* Command 0xFE, call segment from master track */
632 if (b1
== MPSMIDIST_SEGMENT_CALL
) {
633 b1
= this->songdata
[chandata
.playpos
++];
634 chandata
.returnpos
= chandata
.playpos
;
635 chandata
.playpos
= this->segments
[b1
];
636 newdelay
= this->ReadVariableLength(chandata
.playpos
);
643 /* Command 0xFD, return from segment to master track */
644 if (b1
== MPSMIDIST_SEGMENT_RETURN
) {
645 chandata
.playpos
= chandata
.returnpos
;
646 chandata
.returnpos
= 0;
647 newdelay
= this->ReadVariableLength(chandata
.playpos
);
654 /* Command 0xFF, end of song */
655 if (b1
== MPSMIDIST_ENDSONG
) {
656 this->shouldplayflag
= false;
660 /* Regular MIDI channel message status byte */
662 /* Save the status byte as running status for the channel
663 * and read another byte for first parameter to command */
664 chandata
.running_status
= b1
;
665 b1
= this->songdata
[chandata
.playpos
++];
668 switch (chandata
.running_status
& 0xF0) {
671 b2
= this->songdata
[chandata
.playpos
++];
673 /* Note on, read velocity and scale according to rules */
676 /* Percussion channel, fixed velocity scaling not in the table */
677 velocity
= (int16_t)b2
* 0x50;
679 /* Regular channel, use scaling from table */
680 velocity
= b2
* programvelocities
[chandata
.cur_program
];
682 b2
= (velocity
/ 128) & 0x00FF;
683 AddMidiData(outblock
, MIDIST_NOTEON
+ channel
, b1
, b2
);
686 AddMidiData(outblock
, MIDIST_NOTEON
+ channel
, b1
, 0);
689 case MIDIST_CONTROLLER
:
690 b2
= this->songdata
[chandata
.playpos
++];
691 if (b1
== MIDICT_MODE_MONO
) {
692 /* Unknown what the purpose of this is.
693 * Occurs in "Can't get There from Here" and in "Aliens Ate my Railway" a few times each.
694 * Possibly intended to give hints to other (non-GM) music drivers decoding the song.
697 } else if (b1
== 0) {
698 /* Standard MIDI controller 0 is "bank select", override meaning to change tempo.
699 * This is not actually used in any of the original songs. */
701 this->current_tempo
= ((int)b2
) * 48 / 60;
704 } else if (b1
== MIDICT_EFFECTS1
) {
705 /* Override value of this controller, default mapping is Reverb Send Level according to MMA RP-023.
706 * Unknown what the purpose of this particular value is. */
709 AddMidiData(outblock
, MIDIST_CONTROLLER
+ channel
, b1
, b2
);
713 /* Program change to "Applause" is originally used
714 * to cause the song to loop, but that gets handled
715 * separately in the output driver here.
716 * Just end the song. */
717 this->shouldplayflag
= false;
720 /* Used for note velocity scaling lookup */
721 chandata
.cur_program
= b1
;
722 /* Two programs translated to a third, this is likely to
723 * provide three different velocity scalings of "brass". */
724 if (b1
== 0x57 || b1
== 0x3F) {
727 AddMidiData(outblock
, MIDIST_PROGCHG
+ channel
, b1
);
729 case MIDIST_PITCHBEND
:
730 b2
= this->songdata
[chandata
.playpos
++];
731 AddMidiData(outblock
, MIDIST_PITCHBEND
+ channel
, b1
, b2
);
737 newdelay
= this->ReadVariableLength(chandata
.playpos
);
738 } while (newdelay
== 0);
744 * Play one frame of data into a block.
746 bool PlayFrame(MidiFile::DataBlock
&block
)
748 /* Update tempo/ticks counter */
749 this->tempo_ticks
-= this->current_tempo
;
750 if (this->tempo_ticks
> 0) {
753 this->tempo_ticks
+= TEMPO_RATE
;
755 /* Look over all channels, play those active */
756 for (int ch
= 0; ch
< 16; ch
++) {
757 Channel
&chandata
= this->channels
[ch
];
758 if (chandata
.playpos
!= 0) {
759 if (chandata
.delay
== 0) {
760 chandata
.delay
= this->PlayChannelFrame(block
, ch
);
766 return this->shouldplayflag
;
770 * Perform playback of whole song.
774 /* Tempo seems to be handled as TEMPO_RATE = 148 ticks per second.
775 * Use this as the tickdiv, and define the tempo to be somewhat less than one second (1M microseconds) per quarter note.
776 * This value was found experimentally to give a very close approximation of the correct playback speed.
777 * MIDI software loading exported files will show a bogus tempo, but playback will be correct. */
778 this->target
.tickdiv
= TEMPO_RATE
;
779 this->target
.tempos
.push_back(MidiFile::TempoChange(0, 980500));
781 /* Initialize playback simulation */
783 this->shouldplayflag
= true;
784 this->current_tempo
= (int32_t)this->initial_tempo
* 24 / 60;
785 this->tempo_ticks
= this->current_tempo
;
787 /* Always reset percussion channel to program 0 */
788 this->target
.blocks
.push_back(MidiFile::DataBlock());
789 AddMidiData(this->target
.blocks
.back(), MIDIST_PROGCHG
+ 9, 0x00);
791 /* Technically should be an endless loop, but having
792 * a maximum (about 10 minutes) avoids getting stuck,
793 * in case of corrupted data. */
794 for (uint32_t tick
= 0; tick
< 100000; tick
+= 1) {
795 this->target
.blocks
.push_back(MidiFile::DataBlock());
796 auto &block
= this->target
.blocks
.back();
797 block
.ticktime
= tick
;
798 if (!this->PlayFrame(block
)) {
805 /** Frames/ticks per second for music playback */
806 const int MpsMachine::TEMPO_RATE
= 148;
807 /** Base note velocities for various GM programs */
808 const uint8_t MpsMachine::programvelocities
[128] = {
809 100, 100, 100, 100, 100, 90, 100, 100, 100, 100, 100, 90, 100, 100, 100, 100,
810 100, 100, 85, 100, 100, 100, 100, 100, 100, 100, 100, 100, 90, 90, 110, 80,
811 100, 100, 100, 90, 70, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
812 100, 100, 90, 100, 100, 100, 100, 100, 100, 120, 100, 100, 100, 120, 100, 127,
813 100, 100, 90, 100, 100, 100, 100, 100, 100, 95, 100, 100, 100, 100, 100, 100,
814 100, 100, 100, 100, 100, 100, 100, 115, 100, 100, 100, 100, 100, 100, 100, 100,
815 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
816 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
820 * Create MIDI data from song data for the original Microprose music drivers.
821 * @param data pointer to block of data
822 * @param length size of data in bytes
823 * @return true if the data could be loaded
825 bool MidiFile::LoadMpsData(const uint8_t *data
, size_t length
)
827 _midifile_instance
= this;
829 MpsMachine
machine(data
, length
, *this);
830 return machine
.PlayInto() && FixupMidiData(*this);
833 bool MidiFile::LoadSong(const MusicSongInfo
&song
)
835 switch (song
.filetype
) {
836 case MTT_STANDARDMIDI
:
837 return this->LoadFile(song
.filename
);
840 auto songdata
= GetMusicCatEntryData(song
.filename
, song
.cat_index
);
841 if (songdata
.has_value()) {
842 bool result
= this->LoadMpsData(songdata
->data(), songdata
->size());
854 * Move data from other to this, and clears other.
855 * @param other object containing loaded data to take over
857 void MidiFile::MoveFrom(MidiFile
&other
)
859 std::swap(this->blocks
, other
.blocks
);
860 std::swap(this->tempos
, other
.tempos
);
861 this->tickdiv
= other
.tickdiv
;
863 _midifile_instance
= this;
865 other
.blocks
.clear();
866 other
.tempos
.clear();
870 static void WriteVariableLen(FileHandle
&f
, uint32_t value
)
874 fwrite(&tb
, 1, 1, f
);
875 } else if (value
<= 0x3FFF) {
877 tb
[1] = value
& 0x7F; value
>>= 7;
878 tb
[0] = (value
& 0x7F) | 0x80; value
>>= 7;
879 fwrite(tb
, 1, sizeof(tb
), f
);
880 } else if (value
<= 0x1FFFFF) {
882 tb
[2] = value
& 0x7F; value
>>= 7;
883 tb
[1] = (value
& 0x7F) | 0x80; value
>>= 7;
884 tb
[0] = (value
& 0x7F) | 0x80; value
>>= 7;
885 fwrite(tb
, 1, sizeof(tb
), f
);
886 } else if (value
<= 0x0FFFFFFF) {
888 tb
[3] = value
& 0x7F; value
>>= 7;
889 tb
[2] = (value
& 0x7F) | 0x80; value
>>= 7;
890 tb
[1] = (value
& 0x7F) | 0x80; value
>>= 7;
891 tb
[0] = (value
& 0x7F) | 0x80; value
>>= 7;
892 fwrite(tb
, 1, sizeof(tb
), f
);
897 * Write a Standard MIDI File containing the decoded music.
898 * @param filename Name of file to write to
899 * @return True if the file was written to completion
901 bool MidiFile::WriteSMF(const std::string
&filename
)
903 auto of
= FioFOpenFile(filename
, "wb", Subdirectory::NO_DIRECTORY
);
904 if (!of
.has_value()) return false;
908 const uint8_t fileheader
[] = {
909 'M', 'T', 'h', 'd', // block name
910 0x00, 0x00, 0x00, 0x06, // BE32 block length, always 6 bytes
911 0x00, 0x00, // writing format 0 (all in one track)
912 0x00, 0x01, // containing 1 track (BE16)
913 (uint8_t)(this->tickdiv
>> 8), (uint8_t)this->tickdiv
, // tickdiv in BE16
915 fwrite(fileheader
, sizeof(fileheader
), 1, f
);
918 const uint8_t trackheader
[] = {
919 'M', 'T', 'r', 'k', // block name
920 0, 0, 0, 0, // BE32 block length, unknown at this time
922 fwrite(trackheader
, sizeof(trackheader
), 1, f
);
923 /* Determine position to write the actual track block length at */
924 size_t tracksizepos
= ftell(f
) - 4;
926 /* Write blocks in sequence */
927 uint32_t lasttime
= 0;
928 size_t nexttempoindex
= 0;
929 for (size_t bi
= 0; bi
< this->blocks
.size(); bi
++) {
930 DataBlock
&block
= this->blocks
[bi
];
931 TempoChange
&nexttempo
= this->tempos
[nexttempoindex
];
933 uint32_t timediff
= block
.ticktime
- lasttime
;
935 /* Check if there is a tempo change before this block */
936 if (nexttempo
.ticktime
< block
.ticktime
) {
937 timediff
= nexttempo
.ticktime
- lasttime
;
940 /* Write delta time for block */
941 lasttime
+= timediff
;
942 bool needtime
= false;
943 WriteVariableLen(f
, timediff
);
945 /* Write tempo change if there is one */
946 if (nexttempo
.ticktime
<= block
.ticktime
) {
947 uint8_t tempobuf
[6] = { MIDIST_SMF_META
, 0x51, 0x03, 0, 0, 0 };
948 tempobuf
[3] = (nexttempo
.tempo
& 0x00FF0000) >> 16;
949 tempobuf
[4] = (nexttempo
.tempo
& 0x0000FF00) >> 8;
950 tempobuf
[5] = (nexttempo
.tempo
& 0x000000FF);
951 fwrite(tempobuf
, sizeof(tempobuf
), 1, f
);
955 /* If a tempo change occurred between two blocks, rather than
956 * at start of this one, start over with delta time for the block. */
957 if (nexttempo
.ticktime
< block
.ticktime
) {
958 /* Start loop over at same index */
963 /* Write each block data command */
964 uint8_t *dp
= block
.data
.data();
965 while (dp
< block
.data
.data() + block
.data
.size()) {
966 /* Always zero delta time inside blocks */
972 /* Check message type and write appropriate number of bytes */
973 switch (*dp
& 0xF0) {
976 case MIDIST_POLYPRESS
:
977 case MIDIST_CONTROLLER
:
978 case MIDIST_PITCHBEND
:
983 case MIDIST_CHANPRESS
:
989 /* Sysex needs to measure length and write that as well */
990 if (*dp
== MIDIST_SYSEX
) {
993 uint8_t *sysexend
= dp
;
994 while (*sysexend
!= MIDIST_ENDSYSEX
) sysexend
++;
995 ptrdiff_t sysexlen
= sysexend
- dp
;
996 WriteVariableLen(f
, sysexlen
);
997 fwrite(dp
, 1, sysexend
- dp
, f
);
1002 /* Fail for any other commands */
1007 /* End of track marker */
1008 static const uint8_t track_end_marker
[] = { 0x00, MIDIST_SMF_META
, 0x2F, 0x00 };
1009 fwrite(&track_end_marker
, sizeof(track_end_marker
), 1, f
);
1011 /* Fill out the RIFF block length */
1012 size_t trackendpos
= ftell(f
);
1013 fseek(f
, tracksizepos
, SEEK_SET
);
1014 uint32_t tracksize
= (uint32_t)(trackendpos
- tracksizepos
- 4); // blindly assume we never produce files larger than 2 GB
1015 tracksize
= TO_BE32(tracksize
);
1016 fwrite(&tracksize
, 4, 1, f
);
1022 * Get the name of a Standard MIDI File for a given song.
1023 * For songs already in SMF format, just returns the original.
1024 * Otherwise the song is converted, written to a temporary-ish file, and the written filename is returned.
1025 * @param song Song definition to query
1026 * @return Full filename string, empty string if failed
1028 std::string
MidiFile::GetSMFFile(const MusicSongInfo
&song
)
1030 if (song
.filetype
== MTT_STANDARDMIDI
) {
1031 std::string filename
= FioFindFullPath(Subdirectory::BASESET_DIR
, song
.filename
);
1032 if (!filename
.empty()) return filename
;
1033 filename
= FioFindFullPath(Subdirectory::OLD_GM_DIR
, song
.filename
);
1034 if (!filename
.empty()) return filename
;
1036 return std::string();
1039 if (song
.filetype
!= MTT_MPSMIDI
) return std::string();
1041 char basename
[MAX_PATH
];
1043 const char *fnstart
= strrchr(song
.filename
.c_str(), PATHSEPCHAR
);
1044 if (fnstart
== nullptr) {
1045 fnstart
= song
.filename
.c_str();
1050 /* Remove all '.' characters from filename */
1051 char *wp
= basename
;
1052 for (const char *rp
= fnstart
; *rp
!= '\0'; rp
++) {
1053 if (*rp
!= '.') *wp
++ = *rp
;
1058 std::string tempdirname
= FioGetDirectory(Searchpath::SP_AUTODOWNLOAD_DIR
, Subdirectory::BASESET_DIR
);
1059 tempdirname
+= basename
;
1060 AppendPathSeparator(tempdirname
);
1061 FioCreateDirectory(tempdirname
);
1063 std::string output_filename
= tempdirname
+ std::to_string(song
.cat_index
) + ".mid";
1065 if (FileExists(output_filename
)) {
1066 /* If the file already exists, assume it's the correct decoded data */
1067 return output_filename
;
1070 auto songdata
= GetMusicCatEntryData(song
.filename
, song
.cat_index
);
1071 if (!songdata
.has_value()) return std::string();
1074 if (!midifile
.LoadMpsData(songdata
->data(), songdata
->size())) {
1075 return std::string();
1078 if (midifile
.WriteSMF(output_filename
)) {
1079 return output_filename
;
1081 return std::string();
1086 static bool CmdDumpSMF(uint8_t argc
, char *argv
[])
1089 IConsolePrint(CC_HELP
, "Write the current song to a Standard MIDI File. Usage: 'dumpsmf <filename>'.");
1093 IConsolePrint(CC_WARNING
, "You must specify a filename to write MIDI data to.");
1097 if (_midifile_instance
== nullptr) {
1098 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.");
1102 std::string filename
= fmt::format("{}{}", FiosGetScreenshotDir(), argv
[1]);
1103 IConsolePrint(CC_INFO
, "Dumping MIDI to '{}'.", filename
);
1105 if (_midifile_instance
->WriteSMF(filename
)) {
1106 IConsolePrint(CC_INFO
, "File written successfully.");
1109 IConsolePrint(CC_ERROR
, "An error occurred writing MIDI file.");
1114 static void RegisterConsoleMidiCommands()
1116 static bool registered
= false;
1118 IConsole::CmdRegister("dumpsmf", CmdDumpSMF
);
1123 MidiFile::MidiFile()
1125 RegisterConsoleMidiCommands();
1128 MidiFile::~MidiFile()
1130 if (_midifile_instance
== this) {
1131 _midifile_instance
= nullptr;