OpenTTD Source  20240919-master-gdf0233f4c2
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 uint8_t *MidiGetStandardSysexMessage(MidiSysexMessage msg, size_t &length)
34 {
35  static uint8_t reset_gm_sysex[] = { 0xF0, 0x7E, 0x7F, 0x09, 0x01, 0xF7 };
36  static uint8_t reset_gs_sysex[] = { 0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7 };
37  static uint8_t reset_xg_sysex[] = { 0xF0, 0x43, 0x10, 0x4C, 0x00, 0x00, 0x7E, 0x00, 0xF7 };
38  static uint8_t 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<uint8_t> buf;
64  size_t pos;
65 public:
73  ByteBuffer(FileHandle &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(uint8_t &b)
108  {
109  if (this->IsEnd()) return false;
110  b = this->buf[this->pos++];
111  return true;
112  }
113 
121  bool ReadVariableLength(uint32_t &res)
122  {
123  res = 0;
124  uint8_t b = 0;
125  do {
126  if (this->IsEnd()) return false;
127  b = this->buf[this->pos++];
128  res = (res << 7) | (b & 0x7F);
129  } while (b & 0x80);
130  return true;
131  }
132 
139  bool ReadBuffer(uint8_t *dest, size_t length)
140  {
141  if (this->IsEnd()) return false;
142  if (this->buf.size() - this->pos < length) return false;
143  std::copy(std::begin(this->buf) + this->pos, std::begin(this->buf) + this->pos + length, dest);
144  this->pos += length;
145  return true;
146  }
147 
154  bool ReadDataBlock(MidiFile::DataBlock *dest, size_t length)
155  {
156  if (this->IsEnd()) return false;
157  if (this->buf.size() - this->pos < length) return false;
158  dest->data.insert(dest->data.end(), std::begin(this->buf) + this->pos, std::begin(this->buf) + this->pos + length);
159  this->pos += length;
160  return true;
161  }
162 
168  bool Skip(size_t count)
169  {
170  if (this->IsEnd()) return false;
171  if (this->buf.size() - this->pos < count) return false;
172  this->pos += count;
173  return true;
174  }
175 
181  bool Rewind(size_t count)
182  {
183  if (count > this->pos) return false;
184  this->pos -= count;
185  return true;
186  }
187 };
188 
189 static bool ReadTrackChunk(FileHandle &file, MidiFile &target)
190 {
191  uint8_t buf[4];
192 
193  const uint8_t magic[] = { 'M', 'T', 'r', 'k' };
194  if (fread(buf, sizeof(magic), 1, file) != 1) {
195  return false;
196  }
197  if (memcmp(magic, buf, sizeof(magic)) != 0) {
198  return false;
199  }
200 
201  /* Read chunk length and then the whole chunk */
202  uint32_t chunk_length;
203  if (fread(&chunk_length, 1, 4, file) != 4) {
204  return false;
205  }
206  chunk_length = FROM_BE32(chunk_length);
207 
208  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  uint8_t 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  uint8_t 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  auto file = FioFOpenFile(filename, "rb", Subdirectory::BASESET_DIR);
409  if (!file.has_value()) return false;
410  bool result = ReadSMFHeader(*file, header);
411  return result;
412 }
413 
422 {
423  /* Try to read header, fixed size */
424  uint8_t buffer[14];
425  if (fread(buffer, sizeof(buffer), 1, file) != 1) {
426  return false;
427  }
428 
429  /* Check magic, 'MThd' followed by 4 byte length indicator (always = 6 in SMF) */
430  const uint8_t magic[] = { 'M', 'T', 'h', 'd', 0x00, 0x00, 0x00, 0x06 };
431  if (MemCmpT(buffer, magic, sizeof(magic)) != 0) {
432  return false;
433  }
434 
435  /* Read the parameters of the file */
436  header.format = (buffer[8] << 8) | buffer[9];
437  header.tracks = (buffer[10] << 8) | buffer[11];
438  header.tickdiv = (buffer[12] << 8) | buffer[13];
439  return true;
440 }
441 
447 bool MidiFile::LoadFile(const std::string &filename)
448 {
449  _midifile_instance = this;
450 
451  this->blocks.clear();
452  this->tempos.clear();
453  this->tickdiv = 0;
454 
455  auto file = FioFOpenFile(filename, "rb", Subdirectory::BASESET_DIR);
456  if (!file.has_value()) return false;
457 
458  SMFHeader header;
459  if (!ReadSMFHeader(*file, header)) return false;
460 
461  /* Only format 0 (single-track) and format 1 (multi-track single-song) are accepted for now */
462  if (header.format != 0 && header.format != 1) return false;
463  /* Doesn't support SMPTE timecode files */
464  if ((header.tickdiv & 0x8000) != 0) return false;
465 
466  this->tickdiv = header.tickdiv;
467 
468  for (; header.tracks > 0; header.tracks--) {
469  if (!ReadTrackChunk(*file, *this)) {
470  return false;
471  }
472  }
473 
474  return FixupMidiData(*this);
475 }
476 
477 
499 struct MpsMachine {
501  struct Channel {
502  uint8_t cur_program;
503  uint8_t running_status;
504  uint16_t delay;
505  uint32_t playpos;
506  uint32_t startpos;
507  uint32_t returnpos;
508  Channel() : cur_program(0xFF), running_status(0), delay(0), playpos(0), startpos(0), returnpos(0) { }
509  };
511  std::vector<uint32_t> segments;
512  int16_t tempo_ticks;
513  int16_t current_tempo;
514  int16_t initial_tempo;
516 
517  static const int TEMPO_RATE;
518  static const uint8_t programvelocities[128];
519 
520  const uint8_t *songdata;
521  size_t songdatalen;
523 
529  };
530 
531  static void AddMidiData(MidiFile::DataBlock &block, uint8_t b1, uint8_t b2)
532  {
533  block.data.push_back(b1);
534  block.data.push_back(b2);
535  }
536  static void AddMidiData(MidiFile::DataBlock &block, uint8_t b1, uint8_t b2, uint8_t b3)
537  {
538  block.data.push_back(b1);
539  block.data.push_back(b2);
540  block.data.push_back(b3);
541  }
542 
549  MpsMachine(const uint8_t *data, size_t length, MidiFile &target)
550  : songdata(data), songdatalen(length), target(target)
551  {
552  uint32_t pos = 0;
553  int loopmax;
554  int loopidx;
555 
556  /* First byte is the initial "tempo" */
557  this->initial_tempo = this->songdata[pos++];
558 
559  /* Next byte is a count of callable segments */
560  loopmax = this->songdata[pos++];
561  for (loopidx = 0; loopidx < loopmax; loopidx++) {
562  /* Segments form a linked list in the stream,
563  * first two bytes in each is an offset to the next.
564  * Two bytes between offset to next and start of data
565  * are unaccounted for. */
566  this->segments.push_back(pos + 4);
567  pos += FROM_LE16(*(const int16_t *)(this->songdata + pos));
568  }
569 
570  /* After segments follows list of master tracks for each channel,
571  * also prefixed with a byte counting actual tracks. */
572  loopmax = this->songdata[pos++];
573  for (loopidx = 0; loopidx < loopmax; loopidx++) {
574  /* Similar structure to segments list, but also has
575  * the MIDI channel number as a byte before the offset
576  * to next track. */
577  uint8_t ch = this->songdata[pos++];
578  this->channels[ch].startpos = pos + 4;
579  pos += FROM_LE16(*(const int16_t *)(this->songdata + pos));
580  }
581  }
582 
588  uint16_t ReadVariableLength(uint32_t &pos)
589  {
590  uint8_t b = 0;
591  uint16_t res = 0;
592  do {
593  b = this->songdata[pos++];
594  res = (res << 7) + (b & 0x7F);
595  } while (b & 0x80);
596  return res;
597  }
598 
602  void RestartSong()
603  {
604  for (int ch = 0; ch < 16; ch++) {
605  Channel &chandata = this->channels[ch];
606  if (chandata.startpos != 0) {
607  /* Active track, set position to beginning */
608  chandata.playpos = chandata.startpos;
609  chandata.delay = this->ReadVariableLength(chandata.playpos);
610  } else {
611  /* Inactive track, mark as such */
612  chandata.playpos = 0;
613  chandata.delay = 0;
614  }
615  }
616  }
617 
621  uint16_t PlayChannelFrame(MidiFile::DataBlock &outblock, int channel)
622  {
623  uint16_t newdelay = 0;
624  uint8_t b1, b2;
625  Channel &chandata = this->channels[channel];
626 
627  do {
628  /* Read command/status byte */
629  b1 = this->songdata[chandata.playpos++];
630 
631  /* Command 0xFE, call segment from master track */
632  if (b1 == MPSMIDIST_SEGMENT_CALL) {
633  b1 = this->songdata[chandata.playpos++];
634  chandata.returnpos = chandata.playpos;
635  chandata.playpos = this->segments[b1];
636  newdelay = this->ReadVariableLength(chandata.playpos);
637  if (newdelay == 0) {
638  continue;
639  }
640  return newdelay;
641  }
642 
643  /* Command 0xFD, return from segment to master track */
644  if (b1 == MPSMIDIST_SEGMENT_RETURN) {
645  chandata.playpos = chandata.returnpos;
646  chandata.returnpos = 0;
647  newdelay = this->ReadVariableLength(chandata.playpos);
648  if (newdelay == 0) {
649  continue;
650  }
651  return newdelay;
652  }
653 
654  /* Command 0xFF, end of song */
655  if (b1 == MPSMIDIST_ENDSONG) {
656  this->shouldplayflag = false;
657  return 0;
658  }
659 
660  /* Regular MIDI channel message status byte */
661  if (b1 >= 0x80) {
662  /* Save the status byte as running status for the channel
663  * and read another byte for first parameter to command */
664  chandata.running_status = b1;
665  b1 = this->songdata[chandata.playpos++];
666  }
667 
668  switch (chandata.running_status & 0xF0) {
669  case MIDIST_NOTEOFF:
670  case MIDIST_NOTEON:
671  b2 = this->songdata[chandata.playpos++];
672  if (b2 != 0) {
673  /* Note on, read velocity and scale according to rules */
674  int16_t velocity;
675  if (channel == 9) {
676  /* Percussion channel, fixed velocity scaling not in the table */
677  velocity = (int16_t)b2 * 0x50;
678  } else {
679  /* Regular channel, use scaling from table */
680  velocity = b2 * programvelocities[chandata.cur_program];
681  }
682  b2 = (velocity / 128) & 0x00FF;
683  AddMidiData(outblock, MIDIST_NOTEON + channel, b1, b2);
684  } else {
685  /* Note off */
686  AddMidiData(outblock, MIDIST_NOTEON + channel, b1, 0);
687  }
688  break;
689  case MIDIST_CONTROLLER:
690  b2 = this->songdata[chandata.playpos++];
691  if (b1 == MIDICT_MODE_MONO) {
692  /* Unknown what the purpose of this is.
693  * Occurs in "Can't get There from Here" and in "Aliens Ate my Railway" a few times each.
694  * Possibly intended to give hints to other (non-GM) music drivers decoding the song.
695  */
696  break;
697  } else if (b1 == 0) {
698  /* Standard MIDI controller 0 is "bank select", override meaning to change tempo.
699  * This is not actually used in any of the original songs. */
700  if (b2 != 0) {
701  this->current_tempo = ((int)b2) * 48 / 60;
702  }
703  break;
704  } else if (b1 == MIDICT_EFFECTS1) {
705  /* Override value of this controller, default mapping is Reverb Send Level according to MMA RP-023.
706  * Unknown what the purpose of this particular value is. */
707  b2 = 30;
708  }
709  AddMidiData(outblock, MIDIST_CONTROLLER + channel, b1, b2);
710  break;
711  case MIDIST_PROGCHG:
712  if (b1 == 0x7E) {
713  /* Program change to "Applause" is originally used
714  * to cause the song to loop, but that gets handled
715  * separately in the output driver here.
716  * Just end the song. */
717  this->shouldplayflag = false;
718  break;
719  }
720  /* Used for note velocity scaling lookup */
721  chandata.cur_program = b1;
722  /* Two programs translated to a third, this is likely to
723  * provide three different velocity scalings of "brass". */
724  if (b1 == 0x57 || b1 == 0x3F) {
725  b1 = 0x3E;
726  }
727  AddMidiData(outblock, MIDIST_PROGCHG + channel, b1);
728  break;
729  case MIDIST_PITCHBEND:
730  b2 = this->songdata[chandata.playpos++];
731  AddMidiData(outblock, MIDIST_PITCHBEND + channel, b1, b2);
732  break;
733  default:
734  break;
735  }
736 
737  newdelay = this->ReadVariableLength(chandata.playpos);
738  } while (newdelay == 0);
739 
740  return newdelay;
741  }
742 
747  {
748  /* Update tempo/ticks counter */
749  this->tempo_ticks -= this->current_tempo;
750  if (this->tempo_ticks > 0) {
751  return true;
752  }
753  this->tempo_ticks += TEMPO_RATE;
754 
755  /* Look over all channels, play those active */
756  for (int ch = 0; ch < 16; ch++) {
757  Channel &chandata = this->channels[ch];
758  if (chandata.playpos != 0) {
759  if (chandata.delay == 0) {
760  chandata.delay = this->PlayChannelFrame(block, ch);
761  }
762  chandata.delay--;
763  }
764  }
765 
766  return this->shouldplayflag;
767  }
768 
772  bool PlayInto()
773  {
774  /* Tempo seems to be handled as TEMPO_RATE = 148 ticks per second.
775  * Use this as the tickdiv, and define the tempo to be somewhat less than one second (1M microseconds) per quarter note.
776  * This value was found experimentally to give a very close approximation of the correct playback speed.
777  * MIDI software loading exported files will show a bogus tempo, but playback will be correct. */
778  this->target.tickdiv = TEMPO_RATE;
779  this->target.tempos.push_back(MidiFile::TempoChange(0, 980500));
780 
781  /* Initialize playback simulation */
782  this->RestartSong();
783  this->shouldplayflag = true;
784  this->current_tempo = (int32_t)this->initial_tempo * 24 / 60;
785  this->tempo_ticks = this->current_tempo;
786 
787  /* Always reset percussion channel to program 0 */
788  this->target.blocks.push_back(MidiFile::DataBlock());
789  AddMidiData(this->target.blocks.back(), MIDIST_PROGCHG + 9, 0x00);
790 
791  /* Technically should be an endless loop, but having
792  * a maximum (about 10 minutes) avoids getting stuck,
793  * in case of corrupted data. */
794  for (uint32_t tick = 0; tick < 100000; tick += 1) {
795  this->target.blocks.push_back(MidiFile::DataBlock());
796  auto &block = this->target.blocks.back();
797  block.ticktime = tick;
798  if (!this->PlayFrame(block)) {
799  break;
800  }
801  }
802  return true;
803  }
804 };
806 const int MpsMachine::TEMPO_RATE = 148;
808 const uint8_t MpsMachine::programvelocities[128] = {
809  100, 100, 100, 100, 100, 90, 100, 100, 100, 100, 100, 90, 100, 100, 100, 100,
810  100, 100, 85, 100, 100, 100, 100, 100, 100, 100, 100, 100, 90, 90, 110, 80,
811  100, 100, 100, 90, 70, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
812  100, 100, 90, 100, 100, 100, 100, 100, 100, 120, 100, 100, 100, 120, 100, 127,
813  100, 100, 90, 100, 100, 100, 100, 100, 100, 95, 100, 100, 100, 100, 100, 100,
814  100, 100, 100, 100, 100, 100, 100, 115, 100, 100, 100, 100, 100, 100, 100, 100,
815  100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
816  100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
817 };
818 
825 bool MidiFile::LoadMpsData(const uint8_t *data, size_t length)
826 {
827  _midifile_instance = this;
828 
829  MpsMachine machine(data, length, *this);
830  return machine.PlayInto() && FixupMidiData(*this);
831 }
832 
833 bool MidiFile::LoadSong(const MusicSongInfo &song)
834 {
835  switch (song.filetype) {
836  case MTT_STANDARDMIDI:
837  return this->LoadFile(song.filename);
838  case MTT_MPSMIDI:
839  {
840  auto songdata = GetMusicCatEntryData(song.filename, song.cat_index);
841  if (songdata.has_value()) {
842  bool result = this->LoadMpsData(songdata->data(), songdata->size());
843  return result;
844  } else {
845  return false;
846  }
847  }
848  default:
849  NOT_REACHED();
850  }
851 }
852 
858 {
859  std::swap(this->blocks, other.blocks);
860  std::swap(this->tempos, other.tempos);
861  this->tickdiv = other.tickdiv;
862 
863  _midifile_instance = this;
864 
865  other.blocks.clear();
866  other.tempos.clear();
867  other.tickdiv = 0;
868 }
869 
870 static void WriteVariableLen(FileHandle &f, uint32_t value)
871 {
872  if (value <= 0x7F) {
873  uint8_t tb = value;
874  fwrite(&tb, 1, 1, f);
875  } else if (value <= 0x3FFF) {
876  uint8_t tb[2];
877  tb[1] = value & 0x7F; value >>= 7;
878  tb[0] = (value & 0x7F) | 0x80; value >>= 7;
879  fwrite(tb, 1, sizeof(tb), f);
880  } else if (value <= 0x1FFFFF) {
881  uint8_t tb[3];
882  tb[2] = value & 0x7F; value >>= 7;
883  tb[1] = (value & 0x7F) | 0x80; value >>= 7;
884  tb[0] = (value & 0x7F) | 0x80; value >>= 7;
885  fwrite(tb, 1, sizeof(tb), f);
886  } else if (value <= 0x0FFFFFFF) {
887  uint8_t tb[4];
888  tb[3] = value & 0x7F; value >>= 7;
889  tb[2] = (value & 0x7F) | 0x80; value >>= 7;
890  tb[1] = (value & 0x7F) | 0x80; value >>= 7;
891  tb[0] = (value & 0x7F) | 0x80; value >>= 7;
892  fwrite(tb, 1, sizeof(tb), f);
893  }
894 }
895 
901 bool MidiFile::WriteSMF(const std::string &filename)
902 {
903  auto of = FioFOpenFile(filename, "wb", Subdirectory::NO_DIRECTORY);
904  if (!of.has_value()) return false;
905  auto &f = *of;
906 
907  /* SMF header */
908  const uint8_t fileheader[] = {
909  'M', 'T', 'h', 'd', // block name
910  0x00, 0x00, 0x00, 0x06, // BE32 block length, always 6 bytes
911  0x00, 0x00, // writing format 0 (all in one track)
912  0x00, 0x01, // containing 1 track (BE16)
913  (uint8_t)(this->tickdiv >> 8), (uint8_t)this->tickdiv, // tickdiv in BE16
914  };
915  fwrite(fileheader, sizeof(fileheader), 1, f);
916 
917  /* Track header */
918  const uint8_t trackheader[] = {
919  'M', 'T', 'r', 'k', // block name
920  0, 0, 0, 0, // BE32 block length, unknown at this time
921  };
922  fwrite(trackheader, sizeof(trackheader), 1, f);
923  /* Determine position to write the actual track block length at */
924  size_t tracksizepos = ftell(f) - 4;
925 
926  /* Write blocks in sequence */
927  uint32_t lasttime = 0;
928  size_t nexttempoindex = 0;
929  for (size_t bi = 0; bi < this->blocks.size(); bi++) {
930  DataBlock &block = this->blocks[bi];
931  TempoChange &nexttempo = this->tempos[nexttempoindex];
932 
933  uint32_t timediff = block.ticktime - lasttime;
934 
935  /* Check if there is a tempo change before this block */
936  if (nexttempo.ticktime < block.ticktime) {
937  timediff = nexttempo.ticktime - lasttime;
938  }
939 
940  /* Write delta time for block */
941  lasttime += timediff;
942  bool needtime = false;
943  WriteVariableLen(f, timediff);
944 
945  /* Write tempo change if there is one */
946  if (nexttempo.ticktime <= block.ticktime) {
947  uint8_t tempobuf[6] = { MIDIST_SMF_META, 0x51, 0x03, 0, 0, 0 };
948  tempobuf[3] = (nexttempo.tempo & 0x00FF0000) >> 16;
949  tempobuf[4] = (nexttempo.tempo & 0x0000FF00) >> 8;
950  tempobuf[5] = (nexttempo.tempo & 0x000000FF);
951  fwrite(tempobuf, sizeof(tempobuf), 1, f);
952  nexttempoindex++;
953  needtime = true;
954  }
955  /* If a tempo change occurred between two blocks, rather than
956  * at start of this one, start over with delta time for the block. */
957  if (nexttempo.ticktime < block.ticktime) {
958  /* Start loop over at same index */
959  bi--;
960  continue;
961  }
962 
963  /* Write each block data command */
964  uint8_t *dp = block.data.data();
965  while (dp < block.data.data() + block.data.size()) {
966  /* Always zero delta time inside blocks */
967  if (needtime) {
968  fputc(0, f);
969  }
970  needtime = true;
971 
972  /* Check message type and write appropriate number of bytes */
973  switch (*dp & 0xF0) {
974  case MIDIST_NOTEOFF:
975  case MIDIST_NOTEON:
976  case MIDIST_POLYPRESS:
977  case MIDIST_CONTROLLER:
978  case MIDIST_PITCHBEND:
979  fwrite(dp, 1, 3, f);
980  dp += 3;
981  continue;
982  case MIDIST_PROGCHG:
983  case MIDIST_CHANPRESS:
984  fwrite(dp, 1, 2, f);
985  dp += 2;
986  continue;
987  }
988 
989  /* Sysex needs to measure length and write that as well */
990  if (*dp == MIDIST_SYSEX) {
991  fwrite(dp, 1, 1, f);
992  dp++;
993  uint8_t *sysexend = dp;
994  while (*sysexend != MIDIST_ENDSYSEX) sysexend++;
995  ptrdiff_t sysexlen = sysexend - dp;
996  WriteVariableLen(f, sysexlen);
997  fwrite(dp, 1, sysexend - dp, f);
998  dp = sysexend + 1;
999  continue;
1000  }
1001 
1002  /* Fail for any other commands */
1003  return false;
1004  }
1005  }
1006 
1007  /* End of track marker */
1008  static const uint8_t track_end_marker[] = { 0x00, MIDIST_SMF_META, 0x2F, 0x00 };
1009  fwrite(&track_end_marker, sizeof(track_end_marker), 1, f);
1010 
1011  /* Fill out the RIFF block length */
1012  size_t trackendpos = ftell(f);
1013  fseek(f, tracksizepos, SEEK_SET);
1014  uint32_t tracksize = (uint32_t)(trackendpos - tracksizepos - 4); // blindly assume we never produce files larger than 2 GB
1015  tracksize = TO_BE32(tracksize);
1016  fwrite(&tracksize, 4, 1, f);
1017 
1018  return true;
1019 }
1020 
1028 std::string MidiFile::GetSMFFile(const MusicSongInfo &song)
1029 {
1030  if (song.filetype == MTT_STANDARDMIDI) {
1031  std::string filename = FioFindFullPath(Subdirectory::BASESET_DIR, song.filename);
1032  if (!filename.empty()) return filename;
1034  if (!filename.empty()) return filename;
1035 
1036  return std::string();
1037  }
1038 
1039  if (song.filetype != MTT_MPSMIDI) return std::string();
1040 
1041  char basename[MAX_PATH];
1042  {
1043  const char *fnstart = strrchr(song.filename.c_str(), PATHSEPCHAR);
1044  if (fnstart == nullptr) {
1045  fnstart = song.filename.c_str();
1046  } else {
1047  fnstart++;
1048  }
1049 
1050  /* Remove all '.' characters from filename */
1051  char *wp = basename;
1052  for (const char *rp = fnstart; *rp != '\0'; rp++) {
1053  if (*rp != '.') *wp++ = *rp;
1054  }
1055  *wp++ = '\0';
1056  }
1057 
1058  std::string tempdirname = FioGetDirectory(Searchpath::SP_AUTODOWNLOAD_DIR, Subdirectory::BASESET_DIR);
1059  tempdirname += basename;
1060  AppendPathSeparator(tempdirname);
1061  FioCreateDirectory(tempdirname);
1062 
1063  std::string output_filename = tempdirname + std::to_string(song.cat_index) + ".mid";
1064 
1065  if (FileExists(output_filename)) {
1066  /* If the file already exists, assume it's the correct decoded data */
1067  return output_filename;
1068  }
1069 
1070  auto songdata = GetMusicCatEntryData(song.filename, song.cat_index);
1071  if (!songdata.has_value()) return std::string();
1072 
1073  MidiFile midifile;
1074  if (!midifile.LoadMpsData(songdata->data(), songdata->size())) {
1075  return std::string();
1076  }
1077 
1078  if (midifile.WriteSMF(output_filename)) {
1079  return output_filename;
1080  } else {
1081  return std::string();
1082  }
1083 }
1084 
1085 
1086 static bool CmdDumpSMF(uint8_t argc, char *argv[])
1087 {
1088  if (argc == 0) {
1089  IConsolePrint(CC_HELP, "Write the current song to a Standard MIDI File. Usage: 'dumpsmf <filename>'.");
1090  return true;
1091  }
1092  if (argc != 2) {
1093  IConsolePrint(CC_WARNING, "You must specify a filename to write MIDI data to.");
1094  return false;
1095  }
1096 
1097  if (_midifile_instance == nullptr) {
1098  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.");
1099  return false;
1100  }
1101 
1102  std::string filename = fmt::format("{}{}", FiosGetScreenshotDir(), argv[1]);
1103  IConsolePrint(CC_INFO, "Dumping MIDI to '{}'.", filename);
1104 
1105  if (_midifile_instance->WriteSMF(filename)) {
1106  IConsolePrint(CC_INFO, "File written successfully.");
1107  return true;
1108  } else {
1109  IConsolePrint(CC_ERROR, "An error occurred writing MIDI file.");
1110  return false;
1111  }
1112 }
1113 
1114 static void RegisterConsoleMidiCommands()
1115 {
1116  static bool registered = false;
1117  if (!registered) {
1118  IConsole::CmdRegister("dumpsmf", CmdDumpSMF);
1119  registered = true;
1120  }
1121 }
1122 
1123 MidiFile::MidiFile()
1124 {
1125  RegisterConsoleMidiCommands();
1126 }
1127 
1128 MidiFile::~MidiFile()
1129 {
1130  if (_midifile_instance == this) {
1131  _midifile_instance = nullptr;
1132  }
1133 }
1134 
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:525
MpsMachine::MPSMIDIST_ENDSONG
@ MPSMIDIST_ENDSONG
immediately end the song
Definition: midifile.cpp:528
SP_AUTODOWNLOAD_DIR
@ SP_AUTODOWNLOAD_DIR
Search within the autodownload directory.
Definition: fileio_type.h:150
CC_INFO
static const TextColour CC_INFO
Colour for information lines.
Definition: console_type.h:27
MidiFile::tempos
std::vector< TempoChange > tempos
list of tempo changes in file
Definition: midifile.hpp:33
MpsMachine::channels
Channel channels[16]
playback status for each MIDI channel
Definition: midifile.cpp:510
MidiFile
Definition: midifile.hpp:19
MpsMachine::songdatalen
size_t songdatalen
length of song data
Definition: midifile.cpp:521
MpsMachine::Channel::returnpos
uint32_t returnpos
next return position after playing a segment
Definition: midifile.cpp:507
MidiFile::LoadFile
bool LoadFile(const std::string &filename)
Load a standard MIDI file.
Definition: midifile.cpp:447
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:144
BASESET_DIR
@ BASESET_DIR
Subdirectory for all base data (base sets, intro game)
Definition: fileio_type.h:123
MpsMachine::tempo_ticks
int16_t tempo_ticks
ticker that increments when playing a frame, decrements before playing a frame
Definition: midifile.cpp:512
MidiFile::TempoChange
Definition: midifile.hpp:26
MidiFile::LoadMpsData
bool LoadMpsData(const uint8_t *data, size_t length)
Create MIDI data from song data for the original Microprose music drivers.
Definition: midifile.cpp:825
MidiFile::TempoChange::ticktime
uint32_t ticktime
tick number since start of file this tempo change occurs at
Definition: midifile.hpp:27
MpsMachine::RestartSong
void RestartSong()
Prepare for playback from the beginning.
Definition: midifile.cpp:602
MpsMachine
Decoder for "MPS MIDI" format data.
Definition: midifile.cpp:499
MpsMachine::Channel::startpos
uint32_t startpos
start position of master track
Definition: midifile.cpp:506
MpsMachine::Channel::running_status
uint8_t running_status
last midi status code seen
Definition: midifile.cpp:503
MpsMachine::songdata
const uint8_t * songdata
raw data array
Definition: midifile.cpp:520
OLD_GM_DIR
@ OLD_GM_DIR
Old subdirectory for the music.
Definition: fileio_type.h:121
MpsMachine::shouldplayflag
bool shouldplayflag
not-end-of-song flag
Definition: midifile.cpp:515
ByteBuffer::ReadByte
bool ReadByte(uint8_t &b)
Read a single byte from the buffer.
Definition: midifile.cpp:107
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:28
MpsMachine::initial_tempo
int16_t initial_tempo
starting tempo of song
Definition: midifile.cpp:514
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:160
MpsMachine::PlayFrame
bool PlayFrame(MidiFile::DataBlock &block)
Play one frame of data into a block.
Definition: midifile.cpp:746
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
FileExists
bool FileExists(const std::string &filename)
Test whether the given filename exists.
Definition: fileio.cpp:132
MpsMachine::programvelocities
static const uint8_t programvelocities[128]
Base note velocities for various GM programs.
Definition: midifile.cpp:518
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
lengthof
#define lengthof(array)
Return the length of an fixed size array.
Definition: stdafx.h:280
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:316
FileHandle
Definition: fileio_type.h:158
ByteBuffer::ByteBuffer
ByteBuffer(FileHandle &file, size_t len)
Construct buffer from data in a file.
Definition: midifile.cpp:73
MpsMachine::MpsMachine
MpsMachine(const uint8_t *data, size_t length, MidiFile &target)
Construct a TTD DOS music format decoder.
Definition: midifile.cpp:549
MpsMachine::segments
std::vector< uint32_t > segments
pointers into songdata to repeatable data segments
Definition: midifile.cpp:511
MpsMachine::TEMPO_RATE
static const int TEMPO_RATE
Frames/ticks per second for music playback.
Definition: midifile.cpp:517
MidiFile::tickdiv
uint16_t tickdiv
ticks per quarter note
Definition: midifile.hpp:34
AppendPathSeparator
void AppendPathSeparator(std::string &buf)
Appends, if necessary, the path separator character to the end of the string.
Definition: fileio.cpp:346
MidiFile::GetSMFFile
static std::string GetSMFFile(const MusicSongInfo &song)
Get the name of a Standard MIDI File for a given song.
Definition: midifile.cpp:1028
MidiFile::DataBlock::data
std::vector< uint8_t > data
raw midi data contained in block
Definition: midifile.hpp:23
MidiFile::WriteSMF
bool WriteSMF(const std::string &filename)
Write a Standard MIDI File containing the decoded music.
Definition: midifile.cpp:901
MpsMachine::Channel::cur_program
uint8_t cur_program
program selected, used for velocity scaling (lookup into programvelocities array)
Definition: midifile.cpp:502
MpsMachine::Channel::playpos
uint32_t playpos
next byte to play this channel from
Definition: midifile.cpp:505
NO_DIRECTORY
@ NO_DIRECTORY
A path without any base directory.
Definition: fileio_type.h:133
MidiFile::blocks
std::vector< DataBlock > blocks
sequential time-annotated data of file, merged to a single track
Definition: midifile.hpp:32
MpsMachine::Channel
Starting parameter and playback status for one channel/track.
Definition: midifile.cpp:501
FioFOpenFile
std::optional< FileHandle > FioFOpenFile(const std::string &filename, const char *mode, Subdirectory subdir, size_t *filesize)
Opens a OpenTTD file somewhere in a personal or global directory.
Definition: fileio.cpp:242
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:526
ByteBuffer::ReadBuffer
bool ReadBuffer(uint8_t *dest, size_t length)
Read bytes into a buffer.
Definition: midifile.cpp:139
MidiFile::MoveFrom
void MoveFrom(MidiFile &other)
Move data from other to this, and clears other.
Definition: midifile.cpp:857
ByteBuffer::IsValid
bool IsValid() const
Return whether the buffer was constructed successfully.
Definition: midifile.cpp:88
GetMusicCatEntryData
std::optional< std::vector< uint8_t > > GetMusicCatEntryData(const std::string &filename, size_t entrynum)
Read the full data of a music CAT file entry.
Definition: music.cpp:52
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:772
MpsMachine::PlayChannelFrame
uint16_t PlayChannelFrame(MidiFile::DataBlock &outblock, int channel)
Play one frame of data from one channel.
Definition: midifile.cpp:621
MpsMachine::MPSMIDIST_SEGMENT_CALL
@ MPSMIDIST_SEGMENT_CALL
store current position of master track playback, and begin playback of a segment
Definition: midifile.cpp:527
MpsMachine::current_tempo
int16_t current_tempo
threshold for actually playing a frame
Definition: midifile.cpp:513
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:22
ByteBuffer::IsEnd
bool IsEnd() const
Return whether reading has reached the end of the buffer.
Definition: midifile.cpp:97
MidiFile::DataBlock
Definition: midifile.hpp:20
MidiFile::DataBlock::ticktime
uint32_t ticktime
tick number since start of file this block should be triggered at
Definition: midifile.hpp:21
MusicSongInfo::cat_index
int cat_index
entry index in CAT file, for filetype==MTT_MPSMIDI
Definition: base_media_base.h:330
MpsMachine::Channel::delay
uint16_t delay
frames until next command
Definition: midifile.cpp:504
MpsMachine::ReadVariableLength
uint16_t ReadVariableLength(uint32_t &pos)
Read an SMF-style variable length value (note duration) from songdata.
Definition: midifile.cpp:588
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:602
MpsMachine::target
MidiFile & target
recipient of data
Definition: midifile.cpp:522
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:89