OpenTTD
midifile.cpp
1 /* $Id$ */
2 
3 /*
4 * This file is part of OpenTTD.
5 * 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.
6 * 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.
7 * 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/>.
8 */
9 
10 /* @file midifile.cpp Parser for standard MIDI files */
11 
12 #include "midifile.hpp"
13 #include "../fileio_func.h"
14 #include "../fileio_type.h"
15 #include "../string_func.h"
16 #include "../core/endian_func.hpp"
17 #include "../base_media_base.h"
18 #include "midi.h"
19 #include <algorithm>
20 
21 #include "../console_func.h"
22 #include "../console_internal.h"
23 
24 
25 /* SMF reader based on description at: http://www.somascape.org/midi/tech/mfile.html */
26 
27 
28 static MidiFile *_midifile_instance = NULL;
29 
34 class ByteBuffer {
35  byte *buf;
36  size_t buflen;
37  size_t pos;
38 public:
46  ByteBuffer(FILE *file, size_t len)
47  {
48  this->buf = MallocT<byte>(len);
49  if (fread(this->buf, 1, len, file) == len) {
50  this->buflen = len;
51  this->pos = 0;
52  } else {
53  /* invalid state */
54  this->buflen = 0;
55  }
56  }
57 
62  {
63  free(this->buf);
64  }
65 
70  bool IsValid() const
71  {
72  return this->buflen > 0;
73  }
74 
79  bool IsEnd() const
80  {
81  return this->pos >= this->buflen;
82  }
83 
89  bool ReadByte(byte &b)
90  {
91  if (this->IsEnd()) return false;
92  b = this->buf[this->pos++];
93  return true;
94  }
95 
103  bool ReadVariableLength(uint32 &res)
104  {
105  res = 0;
106  byte b = 0;
107  do {
108  if (this->IsEnd()) return false;
109  b = this->buf[this->pos++];
110  res = (res << 7) | (b & 0x7F);
111  } while (b & 0x80);
112  return true;
113  }
114 
121  bool ReadBuffer(byte *dest, size_t length)
122  {
123  if (this->IsEnd()) return false;
124  if (this->buflen - this->pos < length) return false;
125  memcpy(dest, this->buf + this->pos, length);
126  this->pos += length;
127  return true;
128  }
129 
135  bool Skip(size_t count)
136  {
137  if (this->IsEnd()) return false;
138  if (this->buflen - this->pos < count) return false;
139  this->pos += count;
140  return true;
141  }
142 
148  bool Rewind(size_t count)
149  {
150  if (count > this->pos) return false;
151  this->pos -= count;
152  return true;
153  }
154 };
155 
156 static bool ReadTrackChunk(FILE *file, MidiFile &target)
157 {
158  byte buf[4];
159 
160  const byte magic[] = { 'M', 'T', 'r', 'k' };
161  if (fread(buf, sizeof(magic), 1, file) != 1) {
162  return false;
163  }
164  if (memcmp(magic, buf, sizeof(magic)) != 0) {
165  return false;
166  }
167 
168  /* Read chunk length and then the whole chunk */
169  uint32 chunk_length;
170  if (fread(&chunk_length, 1, 4, file) != 4) {
171  return false;
172  }
173  chunk_length = FROM_BE32(chunk_length);
174 
175  ByteBuffer chunk(file, chunk_length);
176  if (!chunk.IsValid()) {
177  return false;
178  }
179 
180  target.blocks.push_back(MidiFile::DataBlock());
181  MidiFile::DataBlock *block = &target.blocks.back();
182 
183  byte last_status = 0;
184  bool running_sysex = false;
185  while (!chunk.IsEnd()) {
186  /* Read deltatime for event, start new block */
187  uint32 deltatime = 0;
188  if (!chunk.ReadVariableLength(deltatime)) {
189  return false;
190  }
191  if (deltatime > 0) {
192  target.blocks.push_back(MidiFile::DataBlock(block->ticktime + deltatime));
193  block = &target.blocks.back();
194  }
195 
196  /* Read status byte */
197  byte status;
198  if (!chunk.ReadByte(status)) {
199  return false;
200  }
201 
202  if ((status & 0x80) == 0) {
203  /* High bit not set means running status message, status is same as last
204  * convert to explicit status */
205  chunk.Rewind(1);
206  status = last_status;
207  goto running_status;
208  } else if ((status & 0xF0) != 0xF0) {
209  /* Regular channel message */
210  last_status = status;
211  running_status:
212  byte *data;
213  switch (status & 0xF0) {
214  case MIDIST_NOTEOFF:
215  case MIDIST_NOTEON:
216  case MIDIST_POLYPRESS:
217  case MIDIST_CONTROLLER:
218  case MIDIST_PITCHBEND:
219  /* 3 byte messages */
220  data = block->data.Append(3);
221  data[0] = status;
222  if (!chunk.ReadBuffer(&data[1], 2)) {
223  return false;
224  }
225  break;
226  case MIDIST_PROGCHG:
227  case MIDIST_CHANPRESS:
228  /* 2 byte messages */
229  data = block->data.Append(2);
230  data[0] = status;
231  if (!chunk.ReadByte(data[1])) {
232  return false;
233  }
234  break;
235  default:
236  NOT_REACHED();
237  }
238  } else if (status == MIDIST_SMF_META) {
239  /* Meta event, read event type byte and data length */
240  if (!chunk.ReadByte(buf[0])) {
241  return false;
242  }
243  uint32 length = 0;
244  if (!chunk.ReadVariableLength(length)) {
245  return false;
246  }
247  switch (buf[0]) {
248  case 0x2F:
249  /* end of track, no more data (length != 0 is illegal) */
250  return (length == 0);
251  case 0x51:
252  /* tempo change */
253  if (length != 3) return false;
254  if (!chunk.ReadBuffer(buf, 3)) return false;
255  target.tempos.push_back(MidiFile::TempoChange(block->ticktime, buf[0] << 16 | buf[1] << 8 | buf[2]));
256  break;
257  default:
258  /* unimportant meta event, skip over it */
259  if (!chunk.Skip(length)) {
260  return false;
261  }
262  break;
263  }
264  } else if (status == MIDIST_SYSEX || (status == MIDIST_SMF_ESCAPE && running_sysex)) {
265  /* System exclusive message */
266  uint32 length = 0;
267  if (!chunk.ReadVariableLength(length)) {
268  return false;
269  }
270  byte *data = block->data.Append(length + 1);
271  data[0] = 0xF0;
272  if (!chunk.ReadBuffer(data + 1, length)) {
273  return false;
274  }
275  if (data[length] != 0xF7) {
276  /* Engage Casio weirdo mode - convert to normal sysex */
277  running_sysex = true;
278  *block->data.Append() = 0xF7;
279  } else {
280  running_sysex = false;
281  }
282  } else if (status == MIDIST_SMF_ESCAPE) {
283  /* Escape sequence */
284  uint32 length = 0;
285  if (!chunk.ReadVariableLength(length)) {
286  return false;
287  }
288  byte *data = block->data.Append(length);
289  if (!chunk.ReadBuffer(data, length)) {
290  return false;
291  }
292  } else {
293  /* Messages undefined in standard midi files:
294  * 0xF1 - MIDI time code quarter frame
295  * 0xF2 - Song position pointer
296  * 0xF3 - Song select
297  * 0xF4 - undefined/reserved
298  * 0xF5 - undefined/reserved
299  * 0xF6 - Tune request for analog synths
300  * 0xF8..0xFE - System real-time messages
301  */
302  return false;
303  }
304  }
305 
306  NOT_REACHED();
307 }
308 
309 template<typename T>
310 bool TicktimeAscending(const T &a, const T &b)
311 {
312  return a.ticktime < b.ticktime;
313 }
314 
315 static bool FixupMidiData(MidiFile &target)
316 {
317  /* Sort all tempo changes and events */
318  std::sort(target.tempos.begin(), target.tempos.end(), TicktimeAscending<MidiFile::TempoChange>);
319  std::sort(target.blocks.begin(), target.blocks.end(), TicktimeAscending<MidiFile::DataBlock>);
320 
321  if (target.tempos.size() == 0) {
322  /* No tempo information, assume 120 bpm (500,000 microseconds per beat */
323  target.tempos.push_back(MidiFile::TempoChange(0, 500000));
324  }
325  /* Add sentinel tempo at end */
326  target.tempos.push_back(MidiFile::TempoChange(UINT32_MAX, 0));
327 
328  /* Merge blocks with identical tick times */
329  std::vector<MidiFile::DataBlock> merged_blocks;
330  uint32 last_ticktime = 0;
331  for (size_t i = 0; i < target.blocks.size(); i++) {
332  MidiFile::DataBlock &block = target.blocks[i];
333  if (block.data.Length() == 0) {
334  continue;
335  } else if (block.ticktime > last_ticktime || merged_blocks.size() == 0) {
336  merged_blocks.push_back(block);
337  last_ticktime = block.ticktime;
338  } else {
339  byte *datadest = merged_blocks.back().data.Append(block.data.Length());
340  memcpy(datadest, block.data.Begin(), block.data.Length());
341  }
342  }
343  std::swap(merged_blocks, target.blocks);
344 
345  /* Annotate blocks with real time */
346  last_ticktime = 0;
347  uint32 last_realtime = 0;
348  size_t cur_tempo = 0, cur_block = 0;
349  while (cur_block < target.blocks.size()) {
350  MidiFile::DataBlock &block = target.blocks[cur_block];
351  MidiFile::TempoChange &tempo = target.tempos[cur_tempo];
352  MidiFile::TempoChange &next_tempo = target.tempos[cur_tempo+1];
353  if (block.ticktime <= next_tempo.ticktime) {
354  /* block is within the current tempo */
355  int64 tickdiff = block.ticktime - last_ticktime;
356  last_ticktime = block.ticktime;
357  last_realtime += uint32(tickdiff * tempo.tempo / target.tickdiv);
358  block.realtime = last_realtime;
359  cur_block++;
360  } else {
361  /* tempo change occurs before this block */
362  int64 tickdiff = next_tempo.ticktime - last_ticktime;
363  last_ticktime = next_tempo.ticktime;
364  last_realtime += uint32(tickdiff * tempo.tempo / target.tickdiv); // current tempo until the tempo change
365  cur_tempo++;
366  }
367  }
368 
369  return true;
370 }
371 
378 bool MidiFile::ReadSMFHeader(const char *filename, SMFHeader &header)
379 {
380  FILE *file = FioFOpenFile(filename, "rb", Subdirectory::BASESET_DIR);
381  if (!file) return false;
382  bool result = ReadSMFHeader(file, header);
383  FioFCloseFile(file);
384  return result;
385 }
386 
394 bool MidiFile::ReadSMFHeader(FILE *file, SMFHeader &header)
395 {
396  /* Try to read header, fixed size */
397  byte buffer[14];
398  if (fread(buffer, sizeof(buffer), 1, file) != 1) {
399  return false;
400  }
401 
402  /* Check magic, 'MThd' followed by 4 byte length indicator (always = 6 in SMF) */
403  const byte magic[] = { 'M', 'T', 'h', 'd', 0x00, 0x00, 0x00, 0x06 };
404  if (MemCmpT(buffer, magic, sizeof(magic)) != 0) {
405  return false;
406  }
407 
408  /* Read the parameters of the file */
409  header.format = (buffer[8] << 8) | buffer[9];
410  header.tracks = (buffer[10] << 8) | buffer[11];
411  header.tickdiv = (buffer[12] << 8) | buffer[13];
412  return true;
413 }
414 
420 bool MidiFile::LoadFile(const char *filename)
421 {
422  _midifile_instance = this;
423 
424  this->blocks.clear();
425  this->tempos.clear();
426  this->tickdiv = 0;
427 
428  bool success = false;
429  FILE *file = FioFOpenFile(filename, "rb", Subdirectory::BASESET_DIR);
430  if (file == NULL) return false;
431 
432  SMFHeader header;
433  if (!ReadSMFHeader(file, header)) goto cleanup;
434 
435  /* Only format 0 (single-track) and format 1 (multi-track single-song) are accepted for now */
436  if (header.format != 0 && header.format != 1) goto cleanup;
437  /* Doesn't support SMPTE timecode files */
438  if ((header.tickdiv & 0x8000) != 0) goto cleanup;
439 
440  this->tickdiv = header.tickdiv;
441 
442  for (; header.tracks > 0; header.tracks--) {
443  if (!ReadTrackChunk(file, *this)) {
444  goto cleanup;
445  }
446  }
447 
448  success = FixupMidiData(*this);
449 
450 cleanup:
451  FioFCloseFile(file);
452  return success;
453 }
454 
455 
477 struct MpsMachine {
479  struct Channel {
480  byte cur_program;
482  uint16 delay;
483  uint32 playpos;
484  uint32 startpos;
485  uint32 returnpos;
486  Channel() : cur_program(0xFF), running_status(0), delay(0), playpos(0), startpos(0), returnpos(0) { }
487  };
488  Channel channels[16];
489  std::vector<uint32> segments;
490  int16 tempo_ticks;
494 
495  static const int TEMPO_RATE;
496  static const byte programvelocities[128];
497 
498  const byte *songdata;
499  size_t songdatalen;
501 
504  MPSMIDIST_SEGMENT_RETURN = 0xFD,
505  MPSMIDIST_SEGMENT_CALL = 0xFE,
506  MPSMIDIST_ENDSONG = 0xFF,
507  };
508 
509  static void AddMidiData(MidiFile::DataBlock &block, byte b1, byte b2)
510  {
511  *block.data.Append() = b1;
512  *block.data.Append() = b2;
513  }
514  static void AddMidiData(MidiFile::DataBlock &block, byte b1, byte b2, byte b3)
515  {
516  *block.data.Append() = b1;
517  *block.data.Append() = b2;
518  *block.data.Append() = b3;
519  }
520 
527  MpsMachine(const byte *data, size_t length, MidiFile &target)
528  : songdata(data), songdatalen(length), target(target)
529  {
530  uint32 pos = 0;
531  int loopmax;
532  int loopidx;
533 
534  /* First byte is the initial "tempo" */
535  this->initial_tempo = this->songdata[pos++];
536 
537  /* Next byte is a count of callable segments */
538  loopmax = this->songdata[pos++];
539  for (loopidx = 0; loopidx < loopmax; loopidx++) {
540  /* Segments form a linked list in the stream,
541  * first two bytes in each is an offset to the next.
542  * Two bytes between offset to next and start of data
543  * are unaccounted for. */
544  this->segments.push_back(pos + 4);
545  pos += FROM_LE16(*(const int16 *)(this->songdata + pos));
546  }
547 
548  /* After segments follows list of master tracks for each channel,
549  * also prefixed with a byte counting actual tracks. */
550  loopmax = this->songdata[pos++];
551  for (loopidx = 0; loopidx < loopmax; loopidx++) {
552  /* Similar structure to segments list, but also has
553  * the MIDI channel number as a byte before the offset
554  * to next track. */
555  byte ch = this->songdata[pos++];
556  this->channels[ch].startpos = pos + 4;
557  pos += FROM_LE16(*(const int16 *)(this->songdata + pos));
558  }
559  }
560 
566  uint16 ReadVariableLength(uint32 &pos)
567  {
568  byte b = 0;
569  uint16 res = 0;
570  do {
571  b = this->songdata[pos++];
572  res = (res << 7) + (b & 0x7F);
573  } while (b & 0x80);
574  return res;
575  }
576 
580  void RestartSong()
581  {
582  for (int ch = 0; ch < 16; ch++) {
583  Channel &chandata = this->channels[ch];
584  if (chandata.startpos != 0) {
585  /* Active track, set position to beginning */
586  chandata.playpos = chandata.startpos;
587  chandata.delay = this->ReadVariableLength(chandata.playpos);
588  } else {
589  /* Inactive track, mark as such */
590  chandata.playpos = 0;
591  chandata.delay = 0;
592  }
593  }
594  }
595 
599  uint16 PlayChannelFrame(MidiFile::DataBlock &outblock, int channel)
600  {
601  uint16 newdelay = 0;
602  byte b1, b2;
603  Channel &chandata = this->channels[channel];
604 
605  do {
606  /* Read command/status byte */
607  b1 = this->songdata[chandata.playpos++];
608 
609  /* Command 0xFE, call segment from master track */
610  if (b1 == MPSMIDIST_SEGMENT_CALL) {
611  b1 = this->songdata[chandata.playpos++];
612  chandata.returnpos = chandata.playpos;
613  chandata.playpos = this->segments[b1];
614  newdelay = this->ReadVariableLength(chandata.playpos);
615  if (newdelay == 0) {
616  continue;
617  }
618  return newdelay;
619  }
620 
621  /* Command 0xFD, return from segment to master track */
622  if (b1 == MPSMIDIST_SEGMENT_RETURN) {
623  chandata.playpos = chandata.returnpos;
624  chandata.returnpos = 0;
625  newdelay = this->ReadVariableLength(chandata.playpos);
626  if (newdelay == 0) {
627  continue;
628  }
629  return newdelay;
630  }
631 
632  /* Command 0xFF, end of song */
633  if (b1 == MPSMIDIST_ENDSONG) {
634  this->shouldplayflag = false;
635  return 0;
636  }
637 
638  /* Regular MIDI channel message status byte */
639  if (b1 >= 0x80) {
640  /* Save the status byte as running status for the channel
641  * and read another byte for first parameter to command */
642  chandata.running_status = b1;
643  b1 = this->songdata[chandata.playpos++];
644  }
645 
646  switch (chandata.running_status & 0xF0) {
647  case MIDIST_NOTEOFF:
648  case MIDIST_NOTEON:
649  b2 = this->songdata[chandata.playpos++];
650  if (b2 != 0) {
651  /* Note on, read velocity and scale according to rules */
652  int16 velocity;
653  if (channel == 9) {
654  /* Percussion channel, fixed velocity scaling not in the table */
655  velocity = (int16)b2 * 0x50;
656  } else {
657  /* Regular channel, use scaling from table */
658  velocity = b2 * programvelocities[chandata.cur_program];
659  }
660  b2 = (velocity / 128) & 0x00FF;
661  AddMidiData(outblock, MIDIST_NOTEON + channel, b1, b2);
662  } else {
663  /* Note off */
664  AddMidiData(outblock, MIDIST_NOTEON + channel, b1, 0);
665  }
666  break;
667  case MIDIST_CONTROLLER:
668  b2 = this->songdata[chandata.playpos++];
669  if (b1 == MIDICT_MODE_MONO) {
670  /* Unknown what the purpose of this is.
671  * Occurs in "Can't get There from Here" and in "Aliens Ate my Railway" a few times each.
672  * Possibly intended to give hints to other (non-GM) music drivers decoding the song.
673  */
674  break;
675  } else if (b1 == 0) {
676  /* Standard MIDI controller 0 is "bank select", override meaning to change tempo.
677  * This is not actually used in any of the original songs. */
678  if (b2 != 0) {
679  this->current_tempo = ((int)b2) * 48 / 60;
680  }
681  break;
682  } else if (b1 == MIDICT_EFFECTS1) {
683  /* Override value of this controller, default mapping is Reverb Send Level according to MMA RP-023.
684  * Unknown what the purpose of this particular value is. */
685  b2 = 30;
686  }
687  AddMidiData(outblock, MIDIST_CONTROLLER + channel, b1, b2);
688  break;
689  case MIDIST_PROGCHG:
690  if (b1 == 0x7E) {
691  /* Program change to "Applause" is originally used
692  * to cause the song to loop, but that gets handled
693  * separately in the output driver here.
694  * Just end the song. */
695  this->shouldplayflag = false;
696  break;
697  }
698  /* Used for note velocity scaling lookup */
699  chandata.cur_program = b1;
700  /* Two programs translated to a third, this is likely to
701  * provide three different velocity scalings of "brass". */
702  if (b1 == 0x57 || b1 == 0x3F) {
703  b1 = 0x3E;
704  }
705  AddMidiData(outblock, MIDIST_PROGCHG + channel, b1);
706  break;
707  case MIDIST_PITCHBEND:
708  b2 = this->songdata[chandata.playpos++];
709  AddMidiData(outblock, MIDIST_PITCHBEND + channel, b1, b2);
710  break;
711  default:
712  break;
713  }
714 
715  newdelay = this->ReadVariableLength(chandata.playpos);
716  } while (newdelay == 0);
717 
718  return newdelay;
719  }
720 
725  {
726  /* Update tempo/ticks counter */
727  this->tempo_ticks -= this->current_tempo;
728  if (this->tempo_ticks > 0) {
729  return true;
730  }
731  this->tempo_ticks += TEMPO_RATE;
732 
733  /* Look over all channels, play those active */
734  for (int ch = 0; ch < 16; ch++) {
735  Channel &chandata = this->channels[ch];
736  if (chandata.playpos != 0) {
737  if (chandata.delay == 0) {
738  chandata.delay = this->PlayChannelFrame(block, ch);
739  }
740  chandata.delay--;
741  }
742  }
743 
744  return this->shouldplayflag;
745  }
746 
750  bool PlayInto()
751  {
752  /* Tempo seems to be handled as TEMPO_RATE = 148 ticks per second.
753  * Use this as the tickdiv, and define the tempo to be one second (1M microseconds) per tickdiv.
754  * MIDI software loading exported files will show a bogus tempo, but playback will be correct. */
755  this->target.tickdiv = TEMPO_RATE;
756  this->target.tempos.push_back(MidiFile::TempoChange(0, 1000000));
757 
758  /* Initialize playback simulation */
759  this->RestartSong();
760  this->shouldplayflag = true;
761  this->current_tempo = (int32)this->initial_tempo * 24 / 60;
762  this->tempo_ticks = this->current_tempo;
763 
764  /* Always reset percussion channel to program 0 */
765  this->target.blocks.push_back(MidiFile::DataBlock());
766  AddMidiData(this->target.blocks.back(), MIDIST_PROGCHG+9, 0x00);
767 
768  /* Technically should be an endless loop, but having
769  * a maximum (about 10 minutes) avoids getting stuck,
770  * in case of corrupted data. */
771  for (uint32 tick = 0; tick < 100000; tick+=1) {
772  this->target.blocks.push_back(MidiFile::DataBlock());
773  auto &block = this->target.blocks.back();
774  block.ticktime = tick;
775  if (!this->PlayFrame(block)) {
776  break;
777  }
778  }
779  return true;
780  }
781 };
783 const int MpsMachine::TEMPO_RATE = 148;
785 const byte MpsMachine::programvelocities[128] = {
786  100, 100, 100, 100, 100, 90, 100, 100, 100, 100, 100, 90, 100, 100, 100, 100,
787  100, 100, 85, 100, 100, 100, 100, 100, 100, 100, 100, 100, 90, 90, 110, 80,
788  100, 100, 100, 90, 70, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
789  100, 100, 90, 100, 100, 100, 100, 100, 100, 120, 100, 100, 100, 120, 100, 127,
790  100, 100, 90, 100, 100, 100, 100, 100, 100, 95, 100, 100, 100, 100, 100, 100,
791  100, 100, 100, 100, 100, 100, 100, 115, 100, 100, 100, 100, 100, 100, 100, 100,
792  100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
793  100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
794 };
795 
802 bool MidiFile::LoadMpsData(const byte *data, size_t length)
803 {
804  _midifile_instance = this;
805 
806  MpsMachine machine(data, length, *this);
807  return machine.PlayInto() && FixupMidiData(*this);
808 }
809 
810 bool MidiFile::LoadSong(const MusicSongInfo &song)
811 {
812  switch (song.filetype) {
813  case MTT_STANDARDMIDI:
814  return this->LoadFile(song.filename);
815  case MTT_MPSMIDI:
816  {
817  size_t songdatalen = 0;
818  byte *songdata = GetMusicCatEntryData(song.filename, song.cat_index, songdatalen);
819  if (songdata != NULL) {
820  bool result = this->LoadMpsData(songdata, songdatalen);
821  free(songdata);
822  return result;
823  } else {
824  return false;
825  }
826  }
827  default:
828  NOT_REACHED();
829  }
830 }
831 
837 {
838  std::swap(this->blocks, other.blocks);
839  std::swap(this->tempos, other.tempos);
840  this->tickdiv = other.tickdiv;
841 
842  _midifile_instance = this;
843 
844  other.blocks.clear();
845  other.tempos.clear();
846  other.tickdiv = 0;
847 }
848 
849 static void WriteVariableLen(FILE *f, uint32 value)
850 {
851  if (value < 0x7F) {
852  byte tb = value;
853  fwrite(&tb, 1, 1, f);
854  } else if (value < 0x3FFF) {
855  byte tb[2];
856  tb[1] = value & 0x7F; value >>= 7;
857  tb[0] = (value & 0x7F) | 0x80; value >>= 7;
858  fwrite(tb, 1, sizeof(tb), f);
859  } else if (value < 0x1FFFFF) {
860  byte tb[3];
861  tb[2] = value & 0x7F; value >>= 7;
862  tb[1] = (value & 0x7F) | 0x80; value >>= 7;
863  tb[0] = (value & 0x7F) | 0x80; value >>= 7;
864  fwrite(tb, 1, sizeof(tb), f);
865  } else if (value < 0x0FFFFFFF) {
866  byte tb[4];
867  tb[3] = value & 0x7F; value >>= 7;
868  tb[2] = (value & 0x7F) | 0x80; value >>= 7;
869  tb[1] = (value & 0x7F) | 0x80; value >>= 7;
870  tb[0] = (value & 0x7F) | 0x80; value >>= 7;
871  fwrite(tb, 1, sizeof(tb), f);
872  }
873 }
874 
880 bool MidiFile::WriteSMF(const char *filename)
881 {
882  FILE *f = FioFOpenFile(filename, "wb", Subdirectory::NO_DIRECTORY);
883  if (!f) {
884  return false;
885  }
886 
887  /* SMF header */
888  const byte fileheader[] = {
889  'M', 'T', 'h', 'd', // block name
890  0x00, 0x00, 0x00, 0x06, // BE32 block length, always 6 bytes
891  0x00, 0x00, // writing format 0 (all in one track)
892  0x00, 0x01, // containing 1 track (BE16)
893  (byte)(this->tickdiv >> 8), (byte)this->tickdiv, // tickdiv in BE16
894  };
895  fwrite(fileheader, sizeof(fileheader), 1, f);
896 
897  /* Track header */
898  const byte trackheader[] = {
899  'M', 'T', 'r', 'k', // block name
900  0, 0, 0, 0, // BE32 block length, unknown at this time
901  };
902  fwrite(trackheader, sizeof(trackheader), 1, f);
903  /* Determine position to write the actual track block length at */
904  size_t tracksizepos = ftell(f) - 4;
905 
906  /* Write blocks in sequence */
907  uint32 lasttime = 0;
908  size_t nexttempoindex = 0;
909  for (size_t bi = 0; bi < this->blocks.size(); bi++) {
910  DataBlock &block = this->blocks[bi];
911  TempoChange &nexttempo = this->tempos[nexttempoindex];
912 
913  uint32 timediff = block.ticktime - lasttime;
914 
915  /* Check if there is a tempo change before this block */
916  if (nexttempo.ticktime < block.ticktime) {
917  timediff = nexttempo.ticktime - lasttime;
918  }
919 
920  /* Write delta time for block */
921  lasttime += timediff;
922  bool needtime = false;
923  WriteVariableLen(f, timediff);
924 
925  /* Write tempo change if there is one */
926  if (nexttempo.ticktime <= block.ticktime) {
927  byte tempobuf[6] = { MIDIST_SMF_META, 0x51, 0x03, 0, 0, 0 };
928  tempobuf[3] = (nexttempo.tempo & 0x00FF0000) >> 16;
929  tempobuf[4] = (nexttempo.tempo & 0x0000FF00) >> 8;
930  tempobuf[5] = (nexttempo.tempo & 0x000000FF);
931  fwrite(tempobuf, sizeof(tempobuf), 1, f);
932  nexttempoindex++;
933  needtime = true;
934  }
935  /* If a tempo change occurred between two blocks, rather than
936  * at start of this one, start over with delta time for the block. */
937  if (nexttempo.ticktime < block.ticktime) {
938  /* Start loop over at same index */
939  bi--;
940  continue;
941  }
942 
943  /* Write each block data command */
944  byte *dp = block.data.Begin();
945  while (dp < block.data.End()) {
946  /* Always zero delta time inside blocks */
947  if (needtime) {
948  fputc(0, f);
949  }
950  needtime = true;
951 
952  /* Check message type and write appropriate number of bytes */
953  switch (*dp & 0xF0) {
954  case MIDIST_NOTEOFF:
955  case MIDIST_NOTEON:
956  case MIDIST_POLYPRESS:
957  case MIDIST_CONTROLLER:
958  case MIDIST_PITCHBEND:
959  fwrite(dp, 1, 3, f);
960  dp += 3;
961  continue;
962  case MIDIST_PROGCHG:
963  case MIDIST_CHANPRESS:
964  fwrite(dp, 1, 2, f);
965  dp += 2;
966  continue;
967  }
968 
969  /* Sysex needs to measure length and write that as well */
970  if (*dp == MIDIST_SYSEX) {
971  fwrite(dp, 1, 1, f);
972  dp++;
973  byte *sysexend = dp;
974  while (*sysexend != MIDIST_ENDSYSEX) sysexend++;
975  ptrdiff_t sysexlen = sysexend - dp;
976  WriteVariableLen(f, sysexlen);
977  fwrite(dp, 1, sysexend - dp, f);
978  dp = sysexend;
979  continue;
980  }
981 
982  /* Fail for any other commands */
983  fclose(f);
984  return false;
985  }
986  }
987 
988  /* End of track marker */
989  static const byte track_end_marker[] = { 0x00, MIDIST_SMF_META, 0x2F, 0x00 };
990  fwrite(&track_end_marker, sizeof(track_end_marker), 1, f);
991 
992  /* Fill out the RIFF block length */
993  size_t trackendpos = ftell(f);
994  fseek(f, tracksizepos, SEEK_SET);
995  uint32 tracksize = (uint32)(trackendpos - tracksizepos - 4); // blindly assume we never produce files larger than 2 GB
996  tracksize = TO_BE32(tracksize);
997  fwrite(&tracksize, 4, 1, f);
998 
999  fclose(f);
1000  return true;
1001 }
1002 
1010 std::string MidiFile::GetSMFFile(const MusicSongInfo &song)
1011 {
1012  if (song.filetype == MTT_STANDARDMIDI) {
1013  char filename[MAX_PATH];
1014  if (FioFindFullPath(filename, lastof(filename), Subdirectory::BASESET_DIR, song.filename)) {
1015  return std::string(filename);
1016  } else if (FioFindFullPath(filename, lastof(filename), Subdirectory::OLD_GM_DIR, song.filename)) {
1017  return std::string(filename);
1018  } else {
1019  return std::string();
1020  }
1021  }
1022 
1023  if (song.filetype != MTT_MPSMIDI) return std::string();
1024 
1025  char basename[MAX_PATH];
1026  {
1027  const char *fnstart = strrchr(song.filename, PATHSEPCHAR);
1028  if (fnstart == NULL) {
1029  fnstart = song.filename;
1030  } else {
1031  fnstart++;
1032  }
1033 
1034  /* Remove all '.' characters from filename */
1035  char *wp = basename;
1036  for (const char *rp = fnstart; *rp != '\0'; rp++) {
1037  if (*rp != '.') *wp++ = *rp;
1038  }
1039  *wp++ = '\0';
1040  }
1041 
1042  char tempdirname[MAX_PATH];
1043  FioGetFullPath(tempdirname, lastof(tempdirname), Searchpath::SP_AUTODOWNLOAD_DIR, Subdirectory::BASESET_DIR, basename);
1044  if (!AppendPathSeparator(tempdirname, lastof(tempdirname))) return std::string();
1045  FioCreateDirectory(tempdirname);
1046 
1047  char output_filename[MAX_PATH];
1048  seprintf(output_filename, lastof(output_filename), "%s%d.mid", tempdirname, song.cat_index);
1049 
1050  if (FileExists(output_filename)) {
1051  /* If the file already exists, assume it's the correct decoded data */
1052  return std::string(output_filename);
1053  }
1054 
1055  byte *data;
1056  size_t datalen;
1057  data = GetMusicCatEntryData(song.filename, song.cat_index, datalen);
1058  if (data == NULL) return std::string();
1059 
1060  MidiFile midifile;
1061  if (!midifile.LoadMpsData(data, datalen)) {
1062  free(data);
1063  return std::string();
1064  }
1065  free(data);
1066 
1067  if (midifile.WriteSMF(output_filename)) {
1068  return std::string(output_filename);
1069  } else {
1070  return std::string();
1071  }
1072 }
1073 
1074 
1075 static bool CmdDumpSMF(byte argc, char *argv[])
1076 {
1077  if (argc == 0) {
1078  IConsolePrint(CC_WARNING, "Write the current song to a Standard MIDI File. Usage: 'dumpsmf <filename>'");
1079  return true;
1080  }
1081  if (argc != 2) {
1082  IConsolePrint(CC_WARNING, "You must specify a filename to write MIDI data to.");
1083  return false;
1084  }
1085 
1086  if (_midifile_instance == NULL) {
1087  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.");
1088  return false;
1089  }
1090 
1091  char fnbuf[MAX_PATH] = { 0 };
1092  if (seprintf(fnbuf, lastof(fnbuf), "%s%s", FiosGetScreenshotDir(), argv[1]) >= (int)lengthof(fnbuf)) {
1093  IConsolePrint(CC_ERROR, "Filename too long.");
1094  return false;
1095  }
1096  IConsolePrintF(CC_INFO, "Dumping MIDI to: %s", fnbuf);
1097 
1098  if (_midifile_instance->WriteSMF(fnbuf)) {
1099  IConsolePrint(CC_INFO, "File written successfully.");
1100  return true;
1101  } else {
1102  IConsolePrint(CC_ERROR, "An error occurred writing MIDI file.");
1103  return false;
1104  }
1105 }
1106 
1107 static void RegisterConsoleMidiCommands()
1108 {
1109  static bool registered = false;
1110  if (!registered) {
1111  IConsoleCmdRegister("dumpsmf", CmdDumpSMF);
1112  registered = true;
1113  }
1114 }
1115 
1116 MidiFile::MidiFile()
1117 {
1118  RegisterConsoleMidiCommands();
1119 }
1120 
1121 MidiFile::~MidiFile()
1122 {
1123  if (_midifile_instance == this) {
1124  _midifile_instance = NULL;
1125  }
1126 }
1127 
uint32 startpos
start position of master track
Definition: midifile.cpp:484
Metadata about a music track.
Standard MIDI file.
bool PlayInto()
Perform playback of whole song.
Definition: midifile.cpp:750
bool IsEnd() const
Return whether reading has reached the end of the buffer.
Definition: midifile.cpp:79
Old subdirectory for the music.
Definition: fileio_type.h:116
Header of a Stanard MIDI File.
Definition: midi.h:18
bool LoadMpsData(const byte *data, size_t length)
Create MIDI data from song data for the original Microprose music drivers.
Definition: midifile.cpp:802
static int MemCmpT(const T *ptr1, const T *ptr2, size_t num=1)
Type-safe version of memcmp().
Definition: mem_func.hpp:65
Decoder for "MPS MIDI" format data.
Definition: midifile.cpp:477
Owning byte buffer readable as a stream.
Definition: midifile.cpp:34
bool LoadFile(const char *filename)
Load a standard MIDI file.
Definition: midifile.cpp:420
void FioFCloseFile(FILE *f)
Close a file in a safe way.
Definition: fileio.cpp:334
uint16 ReadVariableLength(uint32 &pos)
Read an SMF-style variable length value (note duration) from songdata.
Definition: midifile.cpp:566
byte * GetMusicCatEntryData(const char *filename, size_t entrynum, size_t &entrylen)
Read the full data of a music CAT file entry.
Definition: music.cpp:57
int CDECL seprintf(char *str, const char *last, const char *format,...)
Safer implementation of snprintf; same as snprintf except:
Definition: string.cpp:409
bool shouldplayflag
not-end-of-song flag
Definition: midifile.cpp:493
SmallVector< byte, 8 > data
raw midi data contained in block
Definition: midifile.hpp:27
const T * Begin() const
Get the pointer to the first item (const)
uint16 tickdiv
ticks per quarter note
Definition: midifile.hpp:38
#define lastof(x)
Get the last element of an fixed size array.
Definition: depend.cpp:50
void RestartSong()
Prepare for playback from the beginning.
Definition: midifile.cpp:580
void MoveFrom(MidiFile &other)
Move data from other to this, and clears other.
Definition: midifile.cpp:836
Subdirectory for all base data (base sets, intro game)
Definition: fileio_type.h:118
std::vector< DataBlock > blocks
sequential time-annotated data of file, merged to a single track
Definition: midifile.hpp:36
bool Rewind(size_t count)
Go a number of bytes back to re-read.
Definition: midifile.cpp:148
bool PlayFrame(MidiFile::DataBlock &block)
Play one frame of data into a block.
Definition: midifile.cpp:724
const T * End() const
Get the pointer behind the last valid item (const)
T * Append(uint to_add=1)
Append an item and return it.
~ByteBuffer()
Destructor, frees the buffer.
Definition: midifile.cpp:61
byte running_status
last midi status code seen
Definition: midifile.cpp:481
bool AppendPathSeparator(char *buf, const char *last)
Appends, if necessary, the path separator character to the end of the string.
Definition: fileio.cpp:564
uint Length() const
Get the number of items in the list.
char * FioFindFullPath(char *buf, const char *last, Subdirectory subdir, const char *filename)
Find a path to the filename in one of the search directories.
Definition: fileio.cpp:356
void IConsolePrint(TextColour colour_code, const char *string)
Handle the printing of text entered into the console or redirected there by any other means...
Definition: console.cpp:88
Starting parameter and playback status for one channel/track.
Definition: midifile.cpp:479
FILE * FioFOpenFile(const char *filename, const char *mode, Subdirectory subdir, size_t *filesize)
Opens a OpenTTD file somewhere in a personal or global directory.
Definition: fileio.cpp:465
uint16 PlayChannelFrame(MidiFile::DataBlock &outblock, int channel)
Play one frame of data from one channel.
Definition: midifile.cpp:599
void CDECL IConsolePrintF(TextColour colour_code, const char *format,...)
Handle the printing of text entered into the console or redirected there by any other means...
Definition: console.cpp:132
A path without any base directory.
Definition: fileio_type.h:127
bool FileExists(const char *filename)
Test whether the given filename exists.
Definition: fileio.cpp:326
MPS GM driver MIDI format (contained in a CAT file)
bool ReadBuffer(byte *dest, size_t length)
Read bytes into a buffer.
Definition: midifile.cpp:121
static const byte programvelocities[128]
Base note velocities for various GM programs.
Definition: midifile.cpp:496
Search within the autodownload directory.
Definition: fileio_type.h:144
const char * filename
file on disk containing song (when used in MusicSet class, this pointer is owned by MD5File object fo...
#define lengthof(x)
Return the length of an fixed size array.
Definition: depend.cpp:42
uint32 tempo
new tempo in microseconds per tick
Definition: midifile.hpp:32
std::vector< TempoChange > tempos
list of tempo changes in file
Definition: midifile.hpp:37
uint32 realtime
real-time (microseconds) since start of file this block should be triggered at
Definition: midifile.hpp:26
uint32 returnpos
next return position after playing a segment
Definition: midifile.cpp:485
bool Skip(size_t count)
Skip over a number of bytes in the buffer.
Definition: midifile.cpp:135
bool ReadByte(byte &b)
Read a single byte from the buffer.
Definition: midifile.cpp:89
uint32 ticktime
tick number since start of file this tempo change occurs at
Definition: midifile.hpp:31
uint16 delay
frames until next command
Definition: midifile.cpp:482
static const int TEMPO_RATE
Frames/ticks per second for music playback.
Definition: midifile.cpp:495
int16 current_tempo
threshold for actually playing a frame
Definition: midifile.cpp:491
static std::string GetSMFFile(const MusicSongInfo &song)
Get the name of a Standard MIDI File for a given song.
Definition: midifile.cpp:1010
static bool ReadSMFHeader(const char *filename, SMFHeader &header)
Read the header of a standard MIDI file.
Definition: midifile.cpp:378
uint32 playpos
next byte to play this channel from
Definition: midifile.cpp:483
uint32 ticktime
tick number since start of file this block should be triggered at
Definition: midifile.hpp:25
MpsMachine(const byte *data, size_t length, MidiFile &target)
Construct a TTD DOS music format decoder.
Definition: midifile.cpp:527
int cat_index
entry index in CAT file, for filetype==MTT_MPSMIDI
static const TextColour CC_ERROR
Colour for error lines.
Definition: console_type.h:26
int16 tempo_ticks
ticker that increments when playing a frame, decrements before playing a frame
Definition: midifile.cpp:490
static void free(const void *ptr)
Version of the standard free that accepts const pointers.
Definition: depend.cpp:114
bool IsValid() const
Return whether the buffer was constructed successfully.
Definition: midifile.cpp:70
bool WriteSMF(const char *filename)
Write a Standard MIDI File containing the decoded music.
Definition: midifile.cpp:880
bool ReadVariableLength(uint32 &res)
Read a MIDI file variable length value.
Definition: midifile.cpp:103
const char * FiosGetScreenshotDir()
Get the directory for screenshots.
Definition: fios.cpp:642
byte cur_program
program selected, used for velocity scaling (lookup into programvelocities array) ...
Definition: midifile.cpp:480
MusicTrackType filetype
decoder required for song file
ByteBuffer(FILE *file, size_t len)
Construct buffer from data in a file.
Definition: midifile.cpp:46
void FioCreateDirectory(const char *name)
Create a directory with the given name.
Definition: fileio.cpp:534
MpsMidiStatus
Overridden MIDI status codes used in the data format.
Definition: midifile.cpp:503
const byte * songdata
raw data array
Definition: midifile.cpp:498
static const TextColour CC_WARNING
Colour for warning lines.
Definition: console_type.h:27
size_t songdatalen
length of song data
Definition: midifile.cpp:499
void IConsoleCmdRegister(const char *name, IConsoleCmdProc *proc, IConsoleHook *hook)
Register a new command to be used in the console.
Definition: console.cpp:256
MidiFile & target
recipient of data
Definition: midifile.cpp:500
int16 initial_tempo
starting tempo of song
Definition: midifile.cpp:492
static const TextColour CC_INFO
Colour for information lines.
Definition: console_type.h:28
std::vector< uint32 > segments
pointers into songdata to repeatable data segments
Definition: midifile.cpp:489