OpenTTD Source  1.11.2
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 
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;
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  };
526  std::vector<uint32> segments;
527  int16 tempo_ticks;
531 
532  static const int TEMPO_RATE;
533  static const byte programvelocities[128];
534 
535  const byte *songdata;
536  size_t songdatalen;
538 
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 
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 
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;
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:493
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:523
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:460
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:369
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:456
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:385
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