OpenTTD Source  14.0-beta3
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;
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  };
517  std::vector<uint32_t> segments;
518  int16_t tempo_ticks;
519  int16_t current_tempo;
520  int16_t initial_tempo;
522 
523  static const int TEMPO_RATE;
524  static const byte programvelocities[128];
525 
526  const byte *songdata;
527  size_t songdatalen;
529 
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 
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 
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;
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