OpenTTD Source 20250312-master-gcdcc6b491d
midifile.cpp
1/*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
7
8/* @file midifile.cpp Parser for standard MIDI files */
9
10#include "midifile.hpp"
11#include "../fileio_func.h"
12#include "../fileio_type.h"
13#include "../string_func.h"
14#include "../core/endian_func.hpp"
15#include "../core/mem_func.hpp"
16#include "../base_media_base.h"
17#include "midi.h"
18
19#include "../console_func.h"
20#include "../console_internal.h"
21
22/* SMF reader based on description at: http://www.somascape.org/midi/tech/mfile.html */
23
24
25static MidiFile *_midifile_instance = nullptr;
26
33const uint8_t *MidiGetStandardSysexMessage(MidiSysexMessage msg, size_t &length)
34{
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 };
39
40 switch (msg) {
41 case MidiSysexMessage::ResetGM:
42 length = lengthof(reset_gm_sysex);
43 return reset_gm_sysex;
44 case MidiSysexMessage::ResetGS:
45 length = lengthof(reset_gs_sysex);
46 return reset_gs_sysex;
47 case MidiSysexMessage::ResetXG:
48 length = lengthof(reset_xg_sysex);
49 return reset_xg_sysex;
50 case MidiSysexMessage::RolandSetReverb:
51 length = lengthof(roland_reverb_sysex);
52 return roland_reverb_sysex;
53 default:
54 NOT_REACHED();
55 }
56}
57
63 std::vector<uint8_t> buf{};
64 size_t pos = 0;
65public:
73 ByteBuffer(FileHandle &file, size_t len)
74 {
75 this->buf.resize(len);
76 if (fread(this->buf.data(), 1, len, file) != len) {
77 /* invalid state */
78 this->buf.clear();
79 }
80 }
81
86 bool IsValid() const
87 {
88 return !this->buf.empty();
89 }
90
95 bool IsEnd() const
96 {
97 return this->pos >= this->buf.size();
98 }
99
105 bool ReadByte(uint8_t &b)
106 {
107 if (this->IsEnd()) return false;
108 b = this->buf[this->pos++];
109 return true;
110 }
111
119 bool ReadVariableLength(uint32_t &res)
120 {
121 res = 0;
122 uint8_t b = 0;
123 do {
124 if (this->IsEnd()) return false;
125 b = this->buf[this->pos++];
126 res = (res << 7) | (b & 0x7F);
127 } while (b & 0x80);
128 return true;
129 }
130
137 bool ReadBuffer(uint8_t *dest, size_t length)
138 {
139 if (this->IsEnd()) return false;
140 if (this->buf.size() - this->pos < length) return false;
141 std::copy(std::begin(this->buf) + this->pos, std::begin(this->buf) + this->pos + length, dest);
142 this->pos += length;
143 return true;
144 }
145
152 bool ReadDataBlock(MidiFile::DataBlock *dest, size_t length)
153 {
154 if (this->IsEnd()) return false;
155 if (this->buf.size() - this->pos < length) return false;
156 dest->data.insert(dest->data.end(), std::begin(this->buf) + this->pos, std::begin(this->buf) + this->pos + length);
157 this->pos += length;
158 return true;
159 }
160
166 bool Skip(size_t count)
167 {
168 if (this->IsEnd()) return false;
169 if (this->buf.size() - this->pos < count) return false;
170 this->pos += count;
171 return true;
172 }
173
179 bool Rewind(size_t count)
180 {
181 if (count > this->pos) return false;
182 this->pos -= count;
183 return true;
184 }
185};
186
187static bool ReadTrackChunk(FileHandle &file, MidiFile &target)
188{
189 uint8_t buf[4];
190
191 const uint8_t magic[] = { 'M', 'T', 'r', 'k' };
192 if (fread(buf, sizeof(magic), 1, file) != 1) {
193 return false;
194 }
195 if (memcmp(magic, buf, sizeof(magic)) != 0) {
196 return false;
197 }
198
199 /* Read chunk length and then the whole chunk */
200 uint32_t chunk_length;
201 if (fread(&chunk_length, 1, 4, file) != 4) {
202 return false;
203 }
204 chunk_length = FROM_BE32(chunk_length);
205
206 ByteBuffer chunk(file, chunk_length);
207 if (!chunk.IsValid()) {
208 return false;
209 }
210
211 MidiFile::DataBlock *block = &target.blocks.emplace_back();
212
213 uint8_t last_status = 0;
214 bool running_sysex = false;
215 while (!chunk.IsEnd()) {
216 /* Read deltatime for event, start new block */
217 uint32_t deltatime = 0;
218 if (!chunk.ReadVariableLength(deltatime)) {
219 return false;
220 }
221 if (deltatime > 0) {
222 block = &target.blocks.emplace_back(block->ticktime + deltatime);
223 }
224
225 /* Read status byte */
226 uint8_t status;
227 if (!chunk.ReadByte(status)) {
228 return false;
229 }
230
231 if ((status & 0x80) == 0) {
232 /* High bit not set means running status message, status is same as last
233 * convert to explicit status */
234 chunk.Rewind(1);
235 status = last_status;
236 goto running_status;
237 } else if ((status & 0xF0) != 0xF0) {
238 /* Regular channel message */
239 last_status = status;
240 running_status:
241 switch (status & 0xF0) {
242 case MIDIST_NOTEOFF:
243 case MIDIST_NOTEON:
244 case MIDIST_POLYPRESS:
245 case MIDIST_CONTROLLER:
246 case MIDIST_PITCHBEND:
247 /* 3 byte messages */
248 block->data.push_back(status);
249 if (!chunk.ReadDataBlock(block, 2)) {
250 return false;
251 }
252 break;
253 case MIDIST_PROGCHG:
254 case MIDIST_CHANPRESS:
255 /* 2 byte messages */
256 block->data.push_back(status);
257 if (!chunk.ReadByte(buf[0])) {
258 return false;
259 }
260 block->data.push_back(buf[0]);
261 break;
262 default:
263 NOT_REACHED();
264 }
265 } else if (status == MIDIST_SMF_META) {
266 /* Meta event, read event type byte and data length */
267 if (!chunk.ReadByte(buf[0])) {
268 return false;
269 }
270 uint32_t length = 0;
271 if (!chunk.ReadVariableLength(length)) {
272 return false;
273 }
274 switch (buf[0]) {
275 case 0x2F:
276 /* end of track, no more data (length != 0 is illegal) */
277 return (length == 0);
278 case 0x51:
279 /* tempo change */
280 if (length != 3) return false;
281 if (!chunk.ReadBuffer(buf, 3)) return false;
282 target.tempos.push_back(MidiFile::TempoChange(block->ticktime, buf[0] << 16 | buf[1] << 8 | buf[2]));
283 break;
284 default:
285 /* unimportant meta event, skip over it */
286 if (!chunk.Skip(length)) {
287 return false;
288 }
289 break;
290 }
291 } else if (status == MIDIST_SYSEX || (status == MIDIST_SMF_ESCAPE && running_sysex)) {
292 /* System exclusive message */
293 uint32_t length = 0;
294 if (!chunk.ReadVariableLength(length)) {
295 return false;
296 }
297 block->data.push_back(0xF0);
298 if (!chunk.ReadDataBlock(block, length)) {
299 return false;
300 }
301 if (block->data.back() != 0xF7) {
302 /* Engage Casio weirdo mode - convert to normal sysex */
303 running_sysex = true;
304 block->data.push_back(0xF7);
305 } else {
306 running_sysex = false;
307 }
308 } else if (status == MIDIST_SMF_ESCAPE) {
309 /* Escape sequence */
310 uint32_t length = 0;
311 if (!chunk.ReadVariableLength(length)) {
312 return false;
313 }
314 if (!chunk.ReadDataBlock(block, length)) {
315 return false;
316 }
317 } else {
318 /* Messages undefined in standard midi files:
319 * 0xF1 - MIDI time code quarter frame
320 * 0xF2 - Song position pointer
321 * 0xF3 - Song select
322 * 0xF4 - undefined/reserved
323 * 0xF5 - undefined/reserved
324 * 0xF6 - Tune request for analog synths
325 * 0xF8..0xFE - System real-time messages
326 */
327 return false;
328 }
329 }
330
331 NOT_REACHED();
332}
333
334template <typename T>
335bool TicktimeAscending(const T &a, const T &b)
336{
337 return a.ticktime < b.ticktime;
338}
339
340static bool FixupMidiData(MidiFile &target)
341{
342 /* Sort all tempo changes and events */
343 std::sort(target.tempos.begin(), target.tempos.end(), TicktimeAscending<MidiFile::TempoChange>);
344 std::sort(target.blocks.begin(), target.blocks.end(), TicktimeAscending<MidiFile::DataBlock>);
345
346 if (target.tempos.empty()) {
347 /* No tempo information, assume 120 bpm (500,000 microseconds per beat */
348 target.tempos.push_back(MidiFile::TempoChange(0, 500000));
349 }
350 /* Add sentinel tempo at end */
351 target.tempos.push_back(MidiFile::TempoChange(UINT32_MAX, 0));
352
353 /* Merge blocks with identical tick times */
354 std::vector<MidiFile::DataBlock> merged_blocks;
355 uint32_t last_ticktime = 0;
356 for (size_t i = 0; i < target.blocks.size(); i++) {
357 MidiFile::DataBlock &block = target.blocks[i];
358 if (block.data.empty()) {
359 continue;
360 } else if (block.ticktime > last_ticktime || merged_blocks.empty()) {
361 merged_blocks.push_back(block);
362 last_ticktime = block.ticktime;
363 } else {
364 merged_blocks.back().data.insert(merged_blocks.back().data.end(), block.data.begin(), block.data.end());
365 }
366 }
367 std::swap(merged_blocks, target.blocks);
368
369 /* Annotate blocks with real time */
370 last_ticktime = 0;
371 uint32_t last_realtime = 0;
372 size_t cur_tempo = 0, cur_block = 0;
373 while (cur_block < target.blocks.size()) {
374 MidiFile::DataBlock &block = target.blocks[cur_block];
375 MidiFile::TempoChange &tempo = target.tempos[cur_tempo];
376 MidiFile::TempoChange &next_tempo = target.tempos[cur_tempo + 1];
377 if (block.ticktime <= next_tempo.ticktime) {
378 /* block is within the current tempo */
379 int64_t tickdiff = block.ticktime - last_ticktime;
380 last_ticktime = block.ticktime;
381 last_realtime += uint32_t(tickdiff * tempo.tempo / target.tickdiv);
382 block.realtime = last_realtime;
383 cur_block++;
384 } else {
385 /* tempo change occurs before this block */
386 int64_t tickdiff = next_tempo.ticktime - last_ticktime;
387 last_ticktime = next_tempo.ticktime;
388 last_realtime += uint32_t(tickdiff * tempo.tempo / target.tickdiv); // current tempo until the tempo change
389 cur_tempo++;
390 }
391 }
392
393 return true;
394}
395
402bool MidiFile::ReadSMFHeader(const std::string &filename, SMFHeader &header)
403{
404 auto file = FioFOpenFile(filename, "rb", Subdirectory::BASESET_DIR);
405 if (!file.has_value()) return false;
406 bool result = ReadSMFHeader(*file, header);
407 return result;
408}
409
418{
419 /* Try to read header, fixed size */
420 uint8_t buffer[14];
421 if (fread(buffer, sizeof(buffer), 1, file) != 1) {
422 return false;
423 }
424
425 /* Check magic, 'MThd' followed by 4 byte length indicator (always = 6 in SMF) */
426 const uint8_t magic[] = { 'M', 'T', 'h', 'd', 0x00, 0x00, 0x00, 0x06 };
427 if (MemCmpT(buffer, magic, sizeof(magic)) != 0) {
428 return false;
429 }
430
431 /* Read the parameters of the file */
432 header.format = (buffer[8] << 8) | buffer[9];
433 header.tracks = (buffer[10] << 8) | buffer[11];
434 header.tickdiv = (buffer[12] << 8) | buffer[13];
435 return true;
436}
437
443bool MidiFile::LoadFile(const std::string &filename)
444{
445 _midifile_instance = this;
446
447 this->blocks.clear();
448 this->tempos.clear();
449 this->tickdiv = 0;
450
451 auto file = FioFOpenFile(filename, "rb", Subdirectory::BASESET_DIR);
452 if (!file.has_value()) return false;
453
454 SMFHeader header;
455 if (!ReadSMFHeader(*file, header)) return false;
456
457 /* Only format 0 (single-track) and format 1 (multi-track single-song) are accepted for now */
458 if (header.format != 0 && header.format != 1) return false;
459 /* Doesn't support SMPTE timecode files */
460 if ((header.tickdiv & 0x8000) != 0) return false;
461
462 this->tickdiv = header.tickdiv;
463
464 for (; header.tracks > 0; header.tracks--) {
465 if (!ReadTrackChunk(*file, *this)) {
466 return false;
467 }
468 }
469
470 return FixupMidiData(*this);
471}
472
473
497 struct Channel {
498 uint8_t cur_program = 0xFF;
499 uint8_t running_status = 0;
500 uint16_t delay = 0;
501 uint32_t playpos = 0;
502 uint32_t startpos = 0;
503 uint32_t returnpos = 0;
504 };
505 std::array<Channel, 16> channels{};
506 std::vector<uint32_t> segments{};
507 int16_t tempo_ticks = 0;
508 int16_t current_tempo = 0;
509 int16_t initial_tempo = 0;
510 bool shouldplayflag = false;
511
512 static const int TEMPO_RATE;
513 static const uint8_t programvelocities[128];
514
515 const uint8_t *songdata = nullptr;
516 size_t songdatalen = 0;
518
525
526 static void AddMidiData(MidiFile::DataBlock &block, uint8_t b1, uint8_t b2)
527 {
528 block.data.push_back(b1);
529 block.data.push_back(b2);
530 }
531 static void AddMidiData(MidiFile::DataBlock &block, uint8_t b1, uint8_t b2, uint8_t b3)
532 {
533 block.data.push_back(b1);
534 block.data.push_back(b2);
535 block.data.push_back(b3);
536 }
537
544 MpsMachine(const uint8_t *data, size_t length, MidiFile &target)
545 : songdata(data), songdatalen(length), target(target)
546 {
547 uint32_t pos = 0;
548 int loopmax;
549 int loopidx;
550
551 /* First byte is the initial "tempo" */
552 this->initial_tempo = this->songdata[pos++];
553
554 /* Next byte is a count of callable segments */
555 loopmax = this->songdata[pos++];
556 for (loopidx = 0; loopidx < loopmax; loopidx++) {
557 /* Segments form a linked list in the stream,
558 * first two bytes in each is an offset to the next.
559 * Two bytes between offset to next and start of data
560 * are unaccounted for. */
561 this->segments.push_back(pos + 4);
562 pos += FROM_LE16(*(const int16_t *)(this->songdata + pos));
563 }
564
565 /* After segments follows list of master tracks for each channel,
566 * also prefixed with a byte counting actual tracks. */
567 loopmax = this->songdata[pos++];
568 for (loopidx = 0; loopidx < loopmax; loopidx++) {
569 /* Similar structure to segments list, but also has
570 * the MIDI channel number as a byte before the offset
571 * to next track. */
572 uint8_t ch = this->songdata[pos++];
573 this->channels[ch].startpos = pos + 4;
574 pos += FROM_LE16(*(const int16_t *)(this->songdata + pos));
575 }
576 }
577
583 uint16_t ReadVariableLength(uint32_t &pos)
584 {
585 uint8_t b = 0;
586 uint16_t res = 0;
587 do {
588 b = this->songdata[pos++];
589 res = (res << 7) + (b & 0x7F);
590 } while (b & 0x80);
591 return res;
592 }
593
598 {
599 for (int ch = 0; ch < 16; ch++) {
600 Channel &chandata = this->channels[ch];
601 if (chandata.startpos != 0) {
602 /* Active track, set position to beginning */
603 chandata.playpos = chandata.startpos;
604 chandata.delay = this->ReadVariableLength(chandata.playpos);
605 } else {
606 /* Inactive track, mark as such */
607 chandata.playpos = 0;
608 chandata.delay = 0;
609 }
610 }
611 }
612
616 uint16_t PlayChannelFrame(MidiFile::DataBlock &outblock, int channel)
617 {
618 uint16_t newdelay = 0;
619 uint8_t b1, b2;
620 Channel &chandata = this->channels[channel];
621
622 do {
623 /* Read command/status byte */
624 b1 = this->songdata[chandata.playpos++];
625
626 /* Command 0xFE, call segment from master track */
627 if (b1 == MPSMIDIST_SEGMENT_CALL) {
628 b1 = this->songdata[chandata.playpos++];
629 chandata.returnpos = chandata.playpos;
630 chandata.playpos = this->segments[b1];
631 newdelay = this->ReadVariableLength(chandata.playpos);
632 if (newdelay == 0) {
633 continue;
634 }
635 return newdelay;
636 }
637
638 /* Command 0xFD, return from segment to master track */
639 if (b1 == MPSMIDIST_SEGMENT_RETURN) {
640 chandata.playpos = chandata.returnpos;
641 chandata.returnpos = 0;
642 newdelay = this->ReadVariableLength(chandata.playpos);
643 if (newdelay == 0) {
644 continue;
645 }
646 return newdelay;
647 }
648
649 /* Command 0xFF, end of song */
650 if (b1 == MPSMIDIST_ENDSONG) {
651 this->shouldplayflag = false;
652 return 0;
653 }
654
655 /* Regular MIDI channel message status byte */
656 if (b1 >= 0x80) {
657 /* Save the status byte as running status for the channel
658 * and read another byte for first parameter to command */
659 chandata.running_status = b1;
660 b1 = this->songdata[chandata.playpos++];
661 }
662
663 switch (chandata.running_status & 0xF0) {
664 case MIDIST_NOTEOFF:
665 case MIDIST_NOTEON:
666 b2 = this->songdata[chandata.playpos++];
667 if (b2 != 0) {
668 /* Note on, read velocity and scale according to rules */
669 int16_t velocity;
670 if (channel == 9) {
671 /* Percussion channel, fixed velocity scaling not in the table */
672 velocity = (int16_t)b2 * 0x50;
673 } else {
674 /* Regular channel, use scaling from table */
675 velocity = b2 * programvelocities[chandata.cur_program];
676 }
677 b2 = (velocity / 128) & 0x00FF;
678 AddMidiData(outblock, MIDIST_NOTEON + channel, b1, b2);
679 } else {
680 /* Note off */
681 AddMidiData(outblock, MIDIST_NOTEON + channel, b1, 0);
682 }
683 break;
684 case MIDIST_CONTROLLER:
685 b2 = this->songdata[chandata.playpos++];
686 if (b1 == MIDICT_MODE_MONO) {
687 /* Unknown what the purpose of this is.
688 * Occurs in "Can't get There from Here" and in "Aliens Ate my Railway" a few times each.
689 * Possibly intended to give hints to other (non-GM) music drivers decoding the song.
690 */
691 break;
692 } else if (b1 == 0) {
693 /* Standard MIDI controller 0 is "bank select", override meaning to change tempo.
694 * This is not actually used in any of the original songs. */
695 if (b2 != 0) {
696 this->current_tempo = ((int)b2) * 48 / 60;
697 }
698 break;
699 } else if (b1 == MIDICT_EFFECTS1) {
700 /* Override value of this controller, default mapping is Reverb Send Level according to MMA RP-023.
701 * Unknown what the purpose of this particular value is. */
702 b2 = 30;
703 }
704 AddMidiData(outblock, MIDIST_CONTROLLER + channel, b1, b2);
705 break;
706 case MIDIST_PROGCHG:
707 if (b1 == 0x7E) {
708 /* Program change to "Applause" is originally used
709 * to cause the song to loop, but that gets handled
710 * separately in the output driver here.
711 * Just end the song. */
712 this->shouldplayflag = false;
713 break;
714 }
715 /* Used for note velocity scaling lookup */
716 chandata.cur_program = b1;
717 /* Two programs translated to a third, this is likely to
718 * provide three different velocity scalings of "brass". */
719 if (b1 == 0x57 || b1 == 0x3F) {
720 b1 = 0x3E;
721 }
722 AddMidiData(outblock, MIDIST_PROGCHG + channel, b1);
723 break;
724 case MIDIST_PITCHBEND:
725 b2 = this->songdata[chandata.playpos++];
726 AddMidiData(outblock, MIDIST_PITCHBEND + channel, b1, b2);
727 break;
728 default:
729 break;
730 }
731
732 newdelay = this->ReadVariableLength(chandata.playpos);
733 } while (newdelay == 0);
734
735 return newdelay;
736 }
737
742 {
743 /* Update tempo/ticks counter */
744 this->tempo_ticks -= this->current_tempo;
745 if (this->tempo_ticks > 0) {
746 return true;
747 }
748 this->tempo_ticks += TEMPO_RATE;
749
750 /* Look over all channels, play those active */
751 for (int ch = 0; ch < 16; ch++) {
752 Channel &chandata = this->channels[ch];
753 if (chandata.playpos != 0) {
754 if (chandata.delay == 0) {
755 chandata.delay = this->PlayChannelFrame(block, ch);
756 }
757 chandata.delay--;
758 }
759 }
760
761 return this->shouldplayflag;
762 }
763
767 bool PlayInto()
768 {
769 /* Tempo seems to be handled as TEMPO_RATE = 148 ticks per second.
770 * Use this as the tickdiv, and define the tempo to be somewhat less than one second (1M microseconds) per quarter note.
771 * This value was found experimentally to give a very close approximation of the correct playback speed.
772 * MIDI software loading exported files will show a bogus tempo, but playback will be correct. */
773 this->target.tickdiv = TEMPO_RATE;
774 this->target.tempos.push_back(MidiFile::TempoChange(0, 980500));
775
776 /* Initialize playback simulation */
777 this->RestartSong();
778 this->shouldplayflag = true;
779 this->current_tempo = (int32_t)this->initial_tempo * 24 / 60;
780 this->tempo_ticks = this->current_tempo;
781
782 /* Always reset percussion channel to program 0 */
783 this->target.blocks.push_back(MidiFile::DataBlock());
784 AddMidiData(this->target.blocks.back(), MIDIST_PROGCHG + 9, 0x00);
785
786 /* Technically should be an endless loop, but having
787 * a maximum (about 10 minutes) avoids getting stuck,
788 * in case of corrupted data. */
789 for (uint32_t tick = 0; tick < 100000; tick += 1) {
790 auto &block = this->target.blocks.emplace_back();
791 block.ticktime = tick;
792 if (!this->PlayFrame(block)) {
793 break;
794 }
795 }
796 return true;
797 }
798};
800const int MpsMachine::TEMPO_RATE = 148;
802const uint8_t MpsMachine::programvelocities[128] = {
803 100, 100, 100, 100, 100, 90, 100, 100, 100, 100, 100, 90, 100, 100, 100, 100,
804 100, 100, 85, 100, 100, 100, 100, 100, 100, 100, 100, 100, 90, 90, 110, 80,
805 100, 100, 100, 90, 70, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
806 100, 100, 90, 100, 100, 100, 100, 100, 100, 120, 100, 100, 100, 120, 100, 127,
807 100, 100, 90, 100, 100, 100, 100, 100, 100, 95, 100, 100, 100, 100, 100, 100,
808 100, 100, 100, 100, 100, 100, 100, 115, 100, 100, 100, 100, 100, 100, 100, 100,
809 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
810 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
811};
812
819bool MidiFile::LoadMpsData(const uint8_t *data, size_t length)
820{
821 _midifile_instance = this;
822
823 MpsMachine machine(data, length, *this);
824 return machine.PlayInto() && FixupMidiData(*this);
825}
826
827bool MidiFile::LoadSong(const MusicSongInfo &song)
828{
829 switch (song.filetype) {
830 case MTT_STANDARDMIDI:
831 return this->LoadFile(song.filename);
832 case MTT_MPSMIDI:
833 {
834 auto songdata = GetMusicCatEntryData(song.filename, song.cat_index);
835 if (songdata.has_value()) {
836 bool result = this->LoadMpsData(songdata->data(), songdata->size());
837 return result;
838 } else {
839 return false;
840 }
841 }
842 default:
843 NOT_REACHED();
844 }
845}
846
852{
853 std::swap(this->blocks, other.blocks);
854 std::swap(this->tempos, other.tempos);
855 this->tickdiv = other.tickdiv;
856
857 _midifile_instance = this;
858
859 other.blocks.clear();
860 other.tempos.clear();
861 other.tickdiv = 0;
862}
863
864static void WriteVariableLen(FileHandle &f, uint32_t value)
865{
866 if (value <= 0x7F) {
867 uint8_t tb = value;
868 fwrite(&tb, 1, 1, f);
869 } else if (value <= 0x3FFF) {
870 uint8_t tb[2];
871 tb[1] = value & 0x7F; value >>= 7;
872 tb[0] = (value & 0x7F) | 0x80; value >>= 7;
873 fwrite(tb, 1, sizeof(tb), f);
874 } else if (value <= 0x1FFFFF) {
875 uint8_t tb[3];
876 tb[2] = value & 0x7F; value >>= 7;
877 tb[1] = (value & 0x7F) | 0x80; value >>= 7;
878 tb[0] = (value & 0x7F) | 0x80; value >>= 7;
879 fwrite(tb, 1, sizeof(tb), f);
880 } else if (value <= 0x0FFFFFFF) {
881 uint8_t tb[4];
882 tb[3] = value & 0x7F; value >>= 7;
883 tb[2] = (value & 0x7F) | 0x80; value >>= 7;
884 tb[1] = (value & 0x7F) | 0x80; value >>= 7;
885 tb[0] = (value & 0x7F) | 0x80; value >>= 7;
886 fwrite(tb, 1, sizeof(tb), f);
887 }
888}
889
895bool MidiFile::WriteSMF(const std::string &filename)
896{
897 auto of = FioFOpenFile(filename, "wb", Subdirectory::NO_DIRECTORY);
898 if (!of.has_value()) return false;
899 auto &f = *of;
900
901 /* SMF header */
902 const uint8_t fileheader[] = {
903 'M', 'T', 'h', 'd', // block name
904 0x00, 0x00, 0x00, 0x06, // BE32 block length, always 6 bytes
905 0x00, 0x00, // writing format 0 (all in one track)
906 0x00, 0x01, // containing 1 track (BE16)
907 (uint8_t)(this->tickdiv >> 8), (uint8_t)this->tickdiv, // tickdiv in BE16
908 };
909 fwrite(fileheader, sizeof(fileheader), 1, f);
910
911 /* Track header */
912 const uint8_t trackheader[] = {
913 'M', 'T', 'r', 'k', // block name
914 0, 0, 0, 0, // BE32 block length, unknown at this time
915 };
916 fwrite(trackheader, sizeof(trackheader), 1, f);
917 /* Determine position to write the actual track block length at */
918 size_t tracksizepos = ftell(f) - 4;
919
920 /* Write blocks in sequence */
921 uint32_t lasttime = 0;
922 size_t nexttempoindex = 0;
923 for (size_t bi = 0; bi < this->blocks.size(); bi++) {
924 DataBlock &block = this->blocks[bi];
925 TempoChange &nexttempo = this->tempos[nexttempoindex];
926
927 uint32_t timediff = block.ticktime - lasttime;
928
929 /* Check if there is a tempo change before this block */
930 if (nexttempo.ticktime < block.ticktime) {
931 timediff = nexttempo.ticktime - lasttime;
932 }
933
934 /* Write delta time for block */
935 lasttime += timediff;
936 bool needtime = false;
937 WriteVariableLen(f, timediff);
938
939 /* Write tempo change if there is one */
940 if (nexttempo.ticktime <= block.ticktime) {
941 uint8_t tempobuf[6] = { MIDIST_SMF_META, 0x51, 0x03, 0, 0, 0 };
942 tempobuf[3] = (nexttempo.tempo & 0x00FF0000) >> 16;
943 tempobuf[4] = (nexttempo.tempo & 0x0000FF00) >> 8;
944 tempobuf[5] = (nexttempo.tempo & 0x000000FF);
945 fwrite(tempobuf, sizeof(tempobuf), 1, f);
946 nexttempoindex++;
947 needtime = true;
948 }
949 /* If a tempo change occurred between two blocks, rather than
950 * at start of this one, start over with delta time for the block. */
951 if (nexttempo.ticktime < block.ticktime) {
952 /* Start loop over at same index */
953 bi--;
954 continue;
955 }
956
957 /* Write each block data command */
958 uint8_t *dp = block.data.data();
959 while (dp < block.data.data() + block.data.size()) {
960 /* Always zero delta time inside blocks */
961 if (needtime) {
962 fputc(0, f);
963 }
964 needtime = true;
965
966 /* Check message type and write appropriate number of bytes */
967 switch (*dp & 0xF0) {
968 case MIDIST_NOTEOFF:
969 case MIDIST_NOTEON:
970 case MIDIST_POLYPRESS:
971 case MIDIST_CONTROLLER:
972 case MIDIST_PITCHBEND:
973 fwrite(dp, 1, 3, f);
974 dp += 3;
975 continue;
976 case MIDIST_PROGCHG:
977 case MIDIST_CHANPRESS:
978 fwrite(dp, 1, 2, f);
979 dp += 2;
980 continue;
981 }
982
983 /* Sysex needs to measure length and write that as well */
984 if (*dp == MIDIST_SYSEX) {
985 fwrite(dp, 1, 1, f);
986 dp++;
987 uint8_t *sysexend = dp;
988 while (*sysexend != MIDIST_ENDSYSEX) sysexend++;
989 ptrdiff_t sysexlen = sysexend - dp;
990 WriteVariableLen(f, sysexlen);
991 fwrite(dp, 1, sysexend - dp, f);
992 dp = sysexend + 1;
993 continue;
994 }
995
996 /* Fail for any other commands */
997 return false;
998 }
999 }
1000
1001 /* End of track marker */
1002 static const uint8_t track_end_marker[] = { 0x00, MIDIST_SMF_META, 0x2F, 0x00 };
1003 fwrite(&track_end_marker, sizeof(track_end_marker), 1, f);
1004
1005 /* Fill out the RIFF block length */
1006 size_t trackendpos = ftell(f);
1007 fseek(f, tracksizepos, SEEK_SET);
1008 uint32_t tracksize = (uint32_t)(trackendpos - tracksizepos - 4); // blindly assume we never produce files larger than 2 GB
1009 tracksize = TO_BE32(tracksize);
1010 fwrite(&tracksize, 4, 1, f);
1011
1012 return true;
1013}
1014
1022std::string MidiFile::GetSMFFile(const MusicSongInfo &song)
1023{
1024 if (song.filetype == MTT_STANDARDMIDI) {
1025 std::string filename = FioFindFullPath(Subdirectory::BASESET_DIR, song.filename);
1026 if (!filename.empty()) return filename;
1028 if (!filename.empty()) return filename;
1029
1030 return std::string();
1031 }
1032
1033 if (song.filetype != MTT_MPSMIDI) return std::string();
1034
1035 char basename[MAX_PATH];
1036 {
1037 const char *fnstart = strrchr(song.filename.c_str(), PATHSEPCHAR);
1038 if (fnstart == nullptr) {
1039 fnstart = song.filename.c_str();
1040 } else {
1041 fnstart++;
1042 }
1043
1044 /* Remove all '.' characters from filename */
1045 char *wp = basename;
1046 for (const char *rp = fnstart; *rp != '\0'; rp++) {
1047 if (*rp != '.') *wp++ = *rp;
1048 }
1049 *wp++ = '\0';
1050 }
1051
1052 std::string tempdirname = FioGetDirectory(Searchpath::SP_AUTODOWNLOAD_DIR, Subdirectory::BASESET_DIR);
1053 tempdirname += basename;
1054 AppendPathSeparator(tempdirname);
1055 FioCreateDirectory(tempdirname);
1056
1057 std::string output_filename = tempdirname + std::to_string(song.cat_index) + ".mid";
1058
1059 if (FileExists(output_filename)) {
1060 /* If the file already exists, assume it's the correct decoded data */
1061 return output_filename;
1062 }
1063
1064 auto songdata = GetMusicCatEntryData(song.filename, song.cat_index);
1065 if (!songdata.has_value()) return std::string();
1066
1067 MidiFile midifile;
1068 if (!midifile.LoadMpsData(songdata->data(), songdata->size())) {
1069 return std::string();
1070 }
1071
1072 if (midifile.WriteSMF(output_filename)) {
1073 return output_filename;
1074 } else {
1075 return std::string();
1076 }
1077}
1078
1079
1080static bool CmdDumpSMF(uint8_t argc, char *argv[])
1081{
1082 if (argc == 0) {
1083 IConsolePrint(CC_HELP, "Write the current song to a Standard MIDI File. Usage: 'dumpsmf <filename>'.");
1084 return true;
1085 }
1086 if (argc != 2) {
1087 IConsolePrint(CC_WARNING, "You must specify a filename to write MIDI data to.");
1088 return false;
1089 }
1090
1091 if (_midifile_instance == nullptr) {
1092 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.");
1093 return false;
1094 }
1095
1096 std::string filename = fmt::format("{}{}", FiosGetScreenshotDir(), argv[1]);
1097 IConsolePrint(CC_INFO, "Dumping MIDI to '{}'.", filename);
1098
1099 if (_midifile_instance->WriteSMF(filename)) {
1100 IConsolePrint(CC_INFO, "File written successfully.");
1101 return true;
1102 } else {
1103 IConsolePrint(CC_ERROR, "An error occurred writing MIDI file.");
1104 return false;
1105 }
1106}
1107
1108static void RegisterConsoleMidiCommands()
1109{
1110 static bool registered = false;
1111 if (!registered) {
1112 IConsole::CmdRegister("dumpsmf", CmdDumpSMF);
1113 registered = true;
1114 }
1115}
1116
1117MidiFile::MidiFile()
1118{
1119 RegisterConsoleMidiCommands();
1120}
1121
1122MidiFile::~MidiFile()
1123{
1124 if (_midifile_instance == this) {
1125 _midifile_instance = nullptr;
1126 }
1127}
1128
std::optional< std::vector< uint8_t > > GetMusicCatEntryData(const std::string &filename, size_t entrynum)
Read the full data of a music CAT file entry.
Definition music.cpp:52
@ MTT_MPSMIDI
MPS GM driver MIDI format (contained in a CAT file)
@ MTT_STANDARDMIDI
Standard MIDI file.
Owning byte buffer readable as a stream.
Definition midifile.cpp:62
ByteBuffer(FileHandle &file, size_t len)
Construct buffer from data in a file.
Definition midifile.cpp:73
bool IsValid() const
Return whether the buffer was constructed successfully.
Definition midifile.cpp:86
bool ReadByte(uint8_t &b)
Read a single byte from the buffer.
Definition midifile.cpp:105
bool IsEnd() const
Return whether reading has reached the end of the buffer.
Definition midifile.cpp:95
bool ReadVariableLength(uint32_t &res)
Read a MIDI file variable length value.
Definition midifile.cpp:119
bool ReadBuffer(uint8_t *dest, size_t length)
Read bytes into a buffer.
Definition midifile.cpp:137
bool Skip(size_t count)
Skip over a number of bytes in the buffer.
Definition midifile.cpp:166
bool ReadDataBlock(MidiFile::DataBlock *dest, size_t length)
Read bytes into a MidiFile::DataBlock.
Definition midifile.cpp:152
bool Rewind(size_t count)
Go a number of bytes back to re-read.
Definition midifile.cpp:179
void IConsolePrint(TextColour colour_code, const std::string &string)
Handle the printing of text entered into the console or redirected there by any other means.
Definition console.cpp:89
static const TextColour CC_HELP
Colour for help lines.
static const TextColour CC_INFO
Colour for information lines.
static const TextColour CC_WARNING
Colour for warning lines.
static const TextColour CC_ERROR
Colour for error lines.
std::string FioFindFullPath(Subdirectory subdir, const std::string &filename)
Find a path to the filename in one of the search directories.
Definition fileio.cpp:145
void AppendPathSeparator(std::string &buf)
Appends, if necessary, the path separator character to the end of the string.
Definition fileio.cpp:346
void FioCreateDirectory(const std::string &name)
Create a directory with the given name If the parent directory does not exist, it will try to create ...
Definition fileio.cpp:316
bool FileExists(const std::string &filename)
Test whether the given filename exists.
Definition fileio.cpp:133
std::optional< FileHandle > FioFOpenFile(const std::string &filename, const char *mode, Subdirectory subdir, size_t *filesize)
Opens a OpenTTD file somewhere in a personal or global directory.
Definition fileio.cpp:243
const char * FiosGetScreenshotDir()
Get the directory for screenshots.
Definition fios.cpp:600
@ SP_AUTODOWNLOAD_DIR
Search within the autodownload directory.
@ NO_DIRECTORY
A path without any base directory.
@ OLD_GM_DIR
Old subdirectory for the music.
@ BASESET_DIR
Subdirectory for all base data (base sets, intro game)
int MemCmpT(const T *ptr1, const T *ptr2, size_t num=1)
Type-safe version of memcmp().
Definition mem_func.hpp:63
#define lengthof(array)
Return the length of an fixed size array.
Definition stdafx.h:277
static void CmdRegister(const std::string &name, IConsoleCmdProc *proc, IConsoleHook *hook=nullptr)
Register a new command to be used in the console.
Definition console.cpp:160
std::vector< uint8_t > data
raw midi data contained in block
Definition midifile.hpp:23
uint32_t realtime
real-time (microseconds) since start of file this block should be triggered at
Definition midifile.hpp:22
uint32_t ticktime
tick number since start of file this block should be triggered at
Definition midifile.hpp:21
uint32_t tempo
new tempo in microseconds per tick
Definition midifile.hpp:28
uint32_t ticktime
tick number since start of file this tempo change occurs at
Definition midifile.hpp:27
std::vector< TempoChange > tempos
list of tempo changes in file
Definition midifile.hpp:33
bool LoadMpsData(const uint8_t *data, size_t length)
Create MIDI data from song data for the original Microprose music drivers.
Definition midifile.cpp:819
void MoveFrom(MidiFile &other)
Move data from other to this, and clears other.
Definition midifile.cpp:851
bool LoadFile(const std::string &filename)
Load a standard MIDI file.
Definition midifile.cpp:443
static bool ReadSMFHeader(const std::string &filename, SMFHeader &header)
Read the header of a standard MIDI file.
Definition midifile.cpp:402
std::vector< DataBlock > blocks
sequential time-annotated data of file, merged to a single track
Definition midifile.hpp:32
static std::string GetSMFFile(const MusicSongInfo &song)
Get the name of a Standard MIDI File for a given song.
uint16_t tickdiv
ticks per quarter note
Definition midifile.hpp:34
bool WriteSMF(const std::string &filename)
Write a Standard MIDI File containing the decoded music.
Definition midifile.cpp:895
Starting parameter and playback status for one channel/track.
Definition midifile.cpp:497
uint8_t cur_program
program selected, used for velocity scaling (lookup into programvelocities array)
Definition midifile.cpp:498
uint16_t delay
frames until next command
Definition midifile.cpp:500
uint32_t playpos
next byte to play this channel from
Definition midifile.cpp:501
uint8_t running_status
last midi status code seen
Definition midifile.cpp:499
uint32_t startpos
start position of master track
Definition midifile.cpp:502
uint32_t returnpos
next return position after playing a segment
Definition midifile.cpp:503
Decoder for "MPS MIDI" format data.
Definition midifile.cpp:495
MpsMachine(const uint8_t *data, size_t length, MidiFile &target)
Construct a TTD DOS music format decoder.
Definition midifile.cpp:544
uint16_t PlayChannelFrame(MidiFile::DataBlock &outblock, int channel)
Play one frame of data from one channel.
Definition midifile.cpp:616
const uint8_t * songdata
raw data array
Definition midifile.cpp:515
int16_t initial_tempo
starting tempo of song
Definition midifile.cpp:509
uint16_t ReadVariableLength(uint32_t &pos)
Read an SMF-style variable length value (note duration) from songdata.
Definition midifile.cpp:583
static const uint8_t programvelocities[128]
Base note velocities for various GM programs.
Definition midifile.cpp:802
int16_t tempo_ticks
ticker that increments when playing a frame, decrements before playing a frame
Definition midifile.cpp:507
bool PlayFrame(MidiFile::DataBlock &block)
Play one frame of data into a block.
Definition midifile.cpp:741
MidiFile & target
recipient of data
Definition midifile.cpp:517
std::array< Channel, 16 > channels
playback status for each MIDI channel
Definition midifile.cpp:505
int16_t current_tempo
threshold for actually playing a frame
Definition midifile.cpp:508
std::vector< uint32_t > segments
pointers into songdata to repeatable data segments
Definition midifile.cpp:506
size_t songdatalen
length of song data
Definition midifile.cpp:516
static const int TEMPO_RATE
Frames/ticks per second for music playback.
Definition midifile.cpp:512
void RestartSong()
Prepare for playback from the beginning.
Definition midifile.cpp:597
MpsMidiStatus
Overridden MIDI status codes used in the data format.
Definition midifile.cpp:520
@ MPSMIDIST_SEGMENT_RETURN
resume playing master track from stored position
Definition midifile.cpp:521
@ MPSMIDIST_ENDSONG
immediately end the song
Definition midifile.cpp:523
@ MPSMIDIST_SEGMENT_CALL
store current position of master track playback, and begin playback of a segment
Definition midifile.cpp:522
bool shouldplayflag
not-end-of-song flag
Definition midifile.cpp:510
bool PlayInto()
Perform playback of whole song.
Definition midifile.cpp:767
Metadata about a music track.
MusicTrackType filetype
decoder required for song file
std::string filename
file on disk containing song (when used in MusicSet class)
int cat_index
entry index in CAT file, for filetype==MTT_MPSMIDI
Header of a Stanard MIDI File.
Definition midi.h:16