OpenTTD Source: src/music/midifile.cpp Source File

OpenTTD Source 1.11.0-RC1

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