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" 21 #include "../console_func.h" 22 #include "../console_internal.h" 27 static MidiFile *_midifile_instance =
nullptr;
35 const byte *MidiGetStandardSysexMessage(MidiSysexMessage msg,
size_t &length)
37 static byte reset_gm_sysex[] = { 0xF0, 0x7E, 0x7F, 0x09, 0x01, 0xF7 };
38 static byte reset_gs_sysex[] = { 0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7 };
39 static byte reset_xg_sysex[] = { 0xF0, 0x43, 0x10, 0x4C, 0x00, 0x00, 0x7E, 0x00, 0xF7 };
40 static byte roland_reverb_sysex[] = { 0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x01, 0x30, 0x02, 0x04, 0x00, 0x40, 0x40, 0x00, 0x00, 0x09, 0xF7 };
43 case MidiSysexMessage::ResetGM:
45 return reset_gm_sysex;
46 case MidiSysexMessage::ResetGS:
48 return reset_gs_sysex;
49 case MidiSysexMessage::ResetXG:
51 return reset_xg_sysex;
52 case MidiSysexMessage::RolandSetReverb:
53 length =
lengthof(roland_reverb_sysex);
54 return roland_reverb_sysex;
78 this->buf = MallocT<byte>(len);
79 if (fread(this->buf, 1, len, file) == len) {
102 return this->buflen > 0;
111 return this->pos >= this->buflen;
121 if (this->
IsEnd())
return false;
122 b = this->buf[this->pos++];
138 if (this->
IsEnd())
return false;
139 b = this->buf[this->pos++];
140 res = (res << 7) | (b & 0x7F);
153 if (this->
IsEnd())
return false;
154 if (this->buflen - this->pos < length)
return false;
155 memcpy(dest, this->buf + this->pos, length);
168 if (this->
IsEnd())
return false;
169 if (this->buflen - this->pos < length)
return false;
170 dest->
data.insert(dest->
data.end(), this->buf + this->pos, this->buf + this->pos + length);
182 if (this->
IsEnd())
return false;
183 if (this->buflen - this->pos < count)
return false;
195 if (count > this->pos)
return false;
201 static bool ReadTrackChunk(FILE *file,
MidiFile &target)
205 const byte magic[] = {
'M',
'T',
'r',
'k' };
206 if (fread(buf,
sizeof(magic), 1, file) != 1) {
209 if (memcmp(magic, buf,
sizeof(magic)) != 0) {
215 if (fread(&chunk_length, 1, 4, file) != 4) {
218 chunk_length = FROM_BE32(chunk_length);
228 byte last_status = 0;
229 bool running_sysex =
false;
230 while (!chunk.
IsEnd()) {
232 uint32 deltatime = 0;
238 block = &target.
blocks.back();
247 if ((status & 0x80) == 0) {
251 status = last_status;
253 }
else if ((status & 0xF0) != 0xF0) {
255 last_status = status;
257 switch (status & 0xF0) {
260 case MIDIST_POLYPRESS:
261 case MIDIST_CONTROLLER:
262 case MIDIST_PITCHBEND:
264 block->
data.push_back(status);
270 case MIDIST_CHANPRESS:
272 block->
data.push_back(status);
276 block->
data.push_back(buf[0]);
281 }
else if (status == MIDIST_SMF_META) {
293 return (length == 0);
296 if (length != 3)
return false;
302 if (!chunk.
Skip(length)) {
307 }
else if (status == MIDIST_SYSEX || (status == MIDIST_SMF_ESCAPE && running_sysex)) {
313 block->
data.push_back(0xF0);
317 if (block->
data.back() != 0xF7) {
319 running_sysex =
true;
320 block->
data.push_back(0xF7);
322 running_sysex =
false;
324 }
else if (status == MIDIST_SMF_ESCAPE) {
351 bool TicktimeAscending(
const T &a,
const T &b)
353 return a.ticktime < b.ticktime;
356 static bool FixupMidiData(
MidiFile &target)
359 std::sort(target.
tempos.begin(), target.
tempos.end(), TicktimeAscending<MidiFile::TempoChange>);
360 std::sort(target.
blocks.begin(), target.
blocks.end(), TicktimeAscending<MidiFile::DataBlock>);
362 if (target.
tempos.size() == 0) {
370 std::vector<MidiFile::DataBlock> merged_blocks;
371 uint32 last_ticktime = 0;
372 for (
size_t i = 0; i < target.
blocks.size(); i++) {
374 if (block.
data.size() == 0) {
376 }
else if (block.
ticktime > last_ticktime || merged_blocks.size() == 0) {
377 merged_blocks.push_back(block);
380 merged_blocks.back().data.insert(merged_blocks.back().data.end(), block.
data.begin(), block.
data.end());
383 std::swap(merged_blocks, target.
blocks);
387 uint32 last_realtime = 0;
388 size_t cur_tempo = 0, cur_block = 0;
389 while (cur_block < target.
blocks.size()) {
395 int64 tickdiff = block.
ticktime - last_ticktime;
397 last_realtime += uint32(tickdiff * tempo.
tempo / target.
tickdiv);
402 int64 tickdiff = next_tempo.
ticktime - last_ticktime;
403 last_ticktime = next_tempo.
ticktime;
404 last_realtime += uint32(tickdiff * tempo.
tempo / target.
tickdiv);
421 if (!file)
return false;
422 bool result = ReadSMFHeader(file, header);
438 if (fread(buffer,
sizeof(buffer), 1, file) != 1) {
443 const byte magic[] = {
'M',
'T',
'h',
'd', 0x00, 0x00, 0x00, 0x06 };
444 if (
MemCmpT(buffer, magic,
sizeof(magic)) != 0) {
449 header.format = (buffer[8] << 8) | buffer[9];
450 header.tracks = (buffer[10] << 8) | buffer[11];
451 header.tickdiv = (buffer[12] << 8) | buffer[13];
462 _midifile_instance =
this;
464 this->blocks.clear();
465 this->tempos.clear();
468 bool success =
false;
470 if (file ==
nullptr)
return false;
473 if (!ReadSMFHeader(file, header))
goto cleanup;
476 if (header.format != 0 && header.format != 1)
goto cleanup;
478 if ((header.tickdiv & 0x8000) != 0)
goto cleanup;
480 this->tickdiv = header.tickdiv;
482 for (; header.tracks > 0; header.tracks--) {
483 if (!ReadTrackChunk(file, *
this)) {
488 success = FixupMidiData(*
this);
526 Channel() : cur_program(0xFF), running_status(0), delay(0), playpos(0), startpos(0), returnpos(0) { }
536 static const byte programvelocities[128];
544 MPSMIDIST_SEGMENT_RETURN = 0xFD,
545 MPSMIDIST_SEGMENT_CALL = 0xFE,
546 MPSMIDIST_ENDSONG = 0xFF,
551 block.
data.push_back(b1);
552 block.
data.push_back(b2);
556 block.
data.push_back(b1);
557 block.
data.push_back(b2);
558 block.
data.push_back(b3);
568 : songdata(data), songdatalen(length), target(target)
575 this->initial_tempo = this->songdata[pos++];
578 loopmax = this->songdata[pos++];
579 for (loopidx = 0; loopidx < loopmax; loopidx++) {
584 this->segments.push_back(pos + 4);
585 pos += FROM_LE16(*(
const int16 *)(this->songdata + pos));
590 loopmax = this->songdata[pos++];
591 for (loopidx = 0; loopidx < loopmax; loopidx++) {
595 byte ch = this->songdata[pos++];
596 this->channels[ch].
startpos = pos + 4;
597 pos += FROM_LE16(*(
const int16 *)(this->songdata + pos));
611 b = this->songdata[pos++];
612 res = (res << 7) + (b & 0x7F);
622 for (
int ch = 0; ch < 16; ch++) {
623 Channel &chandata = this->channels[ch];
643 Channel &chandata = this->channels[channel];
647 b1 = this->songdata[chandata.
playpos++];
650 if (b1 == MPSMIDIST_SEGMENT_CALL) {
651 b1 = this->songdata[chandata.
playpos++];
653 chandata.
playpos = this->segments[b1];
662 if (b1 == MPSMIDIST_SEGMENT_RETURN) {
673 if (b1 == MPSMIDIST_ENDSONG) {
674 this->shouldplayflag =
false;
683 b1 = this->songdata[chandata.
playpos++];
689 b2 = this->songdata[chandata.
playpos++];
695 velocity = (int16)b2 * 0x50;
698 velocity = b2 * programvelocities[chandata.
cur_program];
700 b2 = (velocity / 128) & 0x00FF;
701 AddMidiData(outblock, MIDIST_NOTEON + channel, b1, b2);
704 AddMidiData(outblock, MIDIST_NOTEON + channel, b1, 0);
707 case MIDIST_CONTROLLER:
708 b2 = this->songdata[chandata.
playpos++];
709 if (b1 == MIDICT_MODE_MONO) {
715 }
else if (b1 == 0) {
719 this->current_tempo = ((int)b2) * 48 / 60;
722 }
else if (b1 == MIDICT_EFFECTS1) {
727 AddMidiData(outblock, MIDIST_CONTROLLER + channel, b1, b2);
735 this->shouldplayflag =
false;
742 if (b1 == 0x57 || b1 == 0x3F) {
745 AddMidiData(outblock, MIDIST_PROGCHG + channel, b1);
747 case MIDIST_PITCHBEND:
748 b2 = this->songdata[chandata.
playpos++];
749 AddMidiData(outblock, MIDIST_PITCHBEND + channel, b1, b2);
756 }
while (newdelay == 0);
767 this->tempo_ticks -= this->current_tempo;
768 if (this->tempo_ticks > 0) {
771 this->tempo_ticks += TEMPO_RATE;
774 for (
int ch = 0; ch < 16; ch++) {
775 Channel &chandata = this->channels[ch];
777 if (chandata.
delay == 0) {
778 chandata.
delay = this->PlayChannelFrame(block, ch);
784 return this->shouldplayflag;
795 this->target.
tickdiv = TEMPO_RATE;
800 this->shouldplayflag =
true;
801 this->current_tempo = (int32)this->initial_tempo * 24 / 60;
802 this->tempo_ticks = this->current_tempo;
806 AddMidiData(this->target.
blocks.back(), MIDIST_PROGCHG+9, 0x00);
811 for (uint32 tick = 0; tick < 100000; tick+=1) {
813 auto &block = this->target.
blocks.back();
815 if (!this->PlayFrame(block)) {
826 100, 100, 100, 100, 100, 90, 100, 100, 100, 100, 100, 90, 100, 100, 100, 100,
827 100, 100, 85, 100, 100, 100, 100, 100, 100, 100, 100, 100, 90, 90, 110, 80,
828 100, 100, 100, 90, 70, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
829 100, 100, 90, 100, 100, 100, 100, 100, 100, 120, 100, 100, 100, 120, 100, 127,
830 100, 100, 90, 100, 100, 100, 100, 100, 100, 95, 100, 100, 100, 100, 100, 100,
831 100, 100, 100, 100, 100, 100, 100, 115, 100, 100, 100, 100, 100, 100, 100, 100,
832 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
833 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
844 _midifile_instance =
this;
847 return machine.
PlayInto() && FixupMidiData(*
this);
854 return this->LoadFile(song.
filename);
857 size_t songdatalen = 0;
859 if (songdata !=
nullptr) {
860 bool result = this->LoadMpsData(songdata, songdatalen);
878 std::swap(this->blocks, other.
blocks);
879 std::swap(this->tempos, other.
tempos);
882 _midifile_instance =
this;
889 static void WriteVariableLen(FILE *f, uint32 value)
893 fwrite(&tb, 1, 1, f);
894 }
else if (value < 0x3FFF) {
896 tb[1] = value & 0x7F; value >>= 7;
897 tb[0] = (value & 0x7F) | 0x80; value >>= 7;
898 fwrite(tb, 1,
sizeof(tb), f);
899 }
else if (value < 0x1FFFFF) {
901 tb[2] = value & 0x7F; value >>= 7;
902 tb[1] = (value & 0x7F) | 0x80; value >>= 7;
903 tb[0] = (value & 0x7F) | 0x80; value >>= 7;
904 fwrite(tb, 1,
sizeof(tb), f);
905 }
else if (value < 0x0FFFFFFF) {
907 tb[3] = value & 0x7F; value >>= 7;
908 tb[2] = (value & 0x7F) | 0x80; value >>= 7;
909 tb[1] = (value & 0x7F) | 0x80; value >>= 7;
910 tb[0] = (value & 0x7F) | 0x80; value >>= 7;
911 fwrite(tb, 1,
sizeof(tb), f);
928 const byte fileheader[] = {
930 0x00, 0x00, 0x00, 0x06,
933 (byte)(this->tickdiv >> 8), (byte)this->tickdiv,
935 fwrite(fileheader,
sizeof(fileheader), 1, f);
938 const byte trackheader[] = {
942 fwrite(trackheader,
sizeof(trackheader), 1, f);
944 size_t tracksizepos = ftell(f) - 4;
948 size_t nexttempoindex = 0;
949 for (
size_t bi = 0; bi < this->blocks.size(); bi++) {
951 TempoChange &nexttempo = this->tempos[nexttempoindex];
953 uint32 timediff = block.
ticktime - lasttime;
957 timediff = nexttempo.
ticktime - lasttime;
961 lasttime += timediff;
962 bool needtime =
false;
963 WriteVariableLen(f, timediff);
967 byte tempobuf[6] = { MIDIST_SMF_META, 0x51, 0x03, 0, 0, 0 };
968 tempobuf[3] = (nexttempo.
tempo & 0x00FF0000) >> 16;
969 tempobuf[4] = (nexttempo.
tempo & 0x0000FF00) >> 8;
970 tempobuf[5] = (nexttempo.
tempo & 0x000000FF);
971 fwrite(tempobuf,
sizeof(tempobuf), 1, f);
984 byte *dp = block.
data.data();
985 while (dp < block.
data.data() + block.
data.size()) {
993 switch (*dp & 0xF0) {
996 case MIDIST_POLYPRESS:
997 case MIDIST_CONTROLLER:
998 case MIDIST_PITCHBEND:
1002 case MIDIST_PROGCHG:
1003 case MIDIST_CHANPRESS:
1004 fwrite(dp, 1, 2, f);
1010 if (*dp == MIDIST_SYSEX) {
1011 fwrite(dp, 1, 1, f);
1013 byte *sysexend = dp;
1014 while (*sysexend != MIDIST_ENDSYSEX) sysexend++;
1015 ptrdiff_t sysexlen = sysexend - dp;
1016 WriteVariableLen(f, sysexlen);
1017 fwrite(dp, 1, sysexend - dp, f);
1029 static const byte track_end_marker[] = { 0x00, MIDIST_SMF_META, 0x2F, 0x00 };
1030 fwrite(&track_end_marker,
sizeof(track_end_marker), 1, f);
1033 size_t trackendpos = ftell(f);
1034 fseek(f, tracksizepos, SEEK_SET);
1035 uint32 tracksize = (uint32)(trackendpos - tracksizepos - 4);
1036 tracksize = TO_BE32(tracksize);
1037 fwrite(&tracksize, 4, 1, f);
1053 char filename[MAX_PATH];
1055 return std::string(filename);
1057 return std::string(filename);
1059 return std::string();
1065 char basename[MAX_PATH];
1067 const char *fnstart = strrchr(song.
filename, PATHSEPCHAR);
1068 if (fnstart ==
nullptr) {
1075 char *wp = basename;
1076 for (
const char *rp = fnstart; *rp !=
'\0'; rp++) {
1077 if (*rp !=
'.') *wp++ = *rp;
1082 char tempdirname[MAX_PATH];
1087 char output_filename[MAX_PATH];
1092 return std::string(output_filename);
1098 if (data ==
nullptr)
return std::string();
1103 return std::string();
1107 if (midifile.
WriteSMF(output_filename)) {
1108 return std::string(output_filename);
1110 return std::string();
1115 static bool CmdDumpSMF(byte argc,
char *argv[])
1126 if (_midifile_instance ==
nullptr) {
1127 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.");
1131 char fnbuf[MAX_PATH] = { 0 };
1138 if (_midifile_instance->
WriteSMF(fnbuf)) {
1147 static void RegisterConsoleMidiCommands()
1149 static bool registered =
false;
1156 MidiFile::MidiFile()
1158 RegisterConsoleMidiCommands();
1161 MidiFile::~MidiFile()
1163 if (_midifile_instance ==
this) {
1164 _midifile_instance =
nullptr;
uint32 startpos
start position of master track
Metadata about a music track.
bool PlayInto()
Perform playback of whole song.
bool IsEnd() const
Return whether reading has reached the end of the buffer.
Old subdirectory for the music.
bool LoadMpsData(const byte *data, size_t length)
Create MIDI data from song data for the original Microprose music drivers.
static int MemCmpT(const T *ptr1, const T *ptr2, size_t num=1)
Type-safe version of memcmp().
Decoder for "MPS MIDI" format data.
Owning byte buffer readable as a stream.
bool LoadFile(const char *filename)
Load a standard MIDI file.
void FioFCloseFile(FILE *f)
Close a file in a safe way.
uint16 ReadVariableLength(uint32 &pos)
Read an SMF-style variable length value (note duration) from songdata.
int CDECL seprintf(char *str, const char *last, const char *format,...)
Safer implementation of snprintf; same as snprintf except:
bool shouldplayflag
not-end-of-song flag
uint16 tickdiv
ticks per quarter note
#define lastof(x)
Get the last element of an fixed size array.
void RestartSong()
Prepare for playback from the beginning.
void MoveFrom(MidiFile &other)
Move data from other to this, and clears other.
Subdirectory for all base data (base sets, intro game)
std::vector< DataBlock > blocks
sequential time-annotated data of file, merged to a single track
bool Rewind(size_t count)
Go a number of bytes back to re-read.
bool PlayFrame(MidiFile::DataBlock &block)
Play one frame of data into a block.
~ByteBuffer()
Destructor, frees the buffer.
byte running_status
last midi status code seen
bool AppendPathSeparator(char *buf, const char *last)
Appends, if necessary, the path separator character to the end of the string.
char * FioFindFullPath(char *buf, const char *last, Subdirectory subdir, const char *filename)
Find a path to the filename in one of the search directories.
void IConsolePrint(TextColour colour_code, const char *string)
Handle the printing of text entered into the console or redirected there by any other means...
Starting parameter and playback status for one channel/track.
FILE * FioFOpenFile(const char *filename, const char *mode, Subdirectory subdir, size_t *filesize)
Opens a OpenTTD file somewhere in a personal or global directory.
bool ReadDataBlock(MidiFile::DataBlock *dest, size_t length)
Read bytes into a MidiFile::DataBlock.
uint16 PlayChannelFrame(MidiFile::DataBlock &outblock, int channel)
Play one frame of data from one channel.
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...
A path without any base directory.
bool FileExists(const char *filename)
Test whether the given filename exists.
std::vector< byte > data
raw midi data contained in block
bool ReadBuffer(byte *dest, size_t length)
Read bytes into a buffer.
static const byte programvelocities[128]
Base note velocities for various GM programs.
Search within the autodownload directory.
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.
uint32 tempo
new tempo in microseconds per tick
std::vector< TempoChange > tempos
list of tempo changes in file
uint32 realtime
real-time (microseconds) since start of file this block should be triggered at
uint32 returnpos
next return position after playing a segment
bool Skip(size_t count)
Skip over a number of bytes in the buffer.
bool ReadByte(byte &b)
Read a single byte from the buffer.
uint32 ticktime
tick number since start of file this tempo change occurs at
uint16 delay
frames until next command
static const int TEMPO_RATE
Frames/ticks per second for music playback.
int16 current_tempo
threshold for actually playing a frame
static std::string GetSMFFile(const MusicSongInfo &song)
Get the name of a Standard MIDI File for a given song.
static bool ReadSMFHeader(const char *filename, SMFHeader &header)
Read the header of a standard MIDI file.
uint32 playpos
next byte to play this channel from
uint32 ticktime
tick number since start of file this block should be triggered at
MpsMachine(const byte *data, size_t length, MidiFile &target)
Construct a TTD DOS music format decoder.
int cat_index
entry index in CAT file, for filetype==MTT_MPSMIDI
static const TextColour CC_ERROR
Colour for error lines.
int16 tempo_ticks
ticker that increments when playing a frame, decrements before playing a frame
static void free(const void *ptr)
Version of the standard free that accepts const pointers.
bool IsValid() const
Return whether the buffer was constructed successfully.
bool WriteSMF(const char *filename)
Write a Standard MIDI File containing the decoded music.
bool ReadVariableLength(uint32 &res)
Read a MIDI file variable length value.
const char * FiosGetScreenshotDir()
Get the directory for screenshots.
byte cur_program
program selected, used for velocity scaling (lookup into programvelocities array) ...
MusicTrackType filetype
decoder required for song file
ByteBuffer(FILE *file, size_t len)
Construct buffer from data in a file.
void FioCreateDirectory(const char *name)
Create a directory with the given name.
MpsMidiStatus
Overridden MIDI status codes used in the data format.
const byte * songdata
raw data array
static const TextColour CC_WARNING
Colour for warning lines.
size_t songdatalen
length of song data
void IConsoleCmdRegister(const char *name, IConsoleCmdProc *proc, IConsoleHook *hook)
Register a new command to be used in the console.
MidiFile & target
recipient of data
int16 initial_tempo
starting tempo of song
static const TextColour CC_INFO
Colour for information lines.
std::vector< uint32 > segments
pointers into songdata to repeatable data segments