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

OpenTTD Source 14.0-RC3

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