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