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