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" 28 static MidiFile *_midifile_instance = NULL;
36 const byte *MidiGetStandardSysexMessage(MidiSysexMessage msg,
size_t &length)
38 static byte reset_gm_sysex[] = { 0xF0, 0x7E, 0x7F, 0x09, 0x01, 0xF7 };
39 static byte reset_gs_sysex[] = { 0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7 };
40 static byte reset_xg_sysex[] = { 0xF0, 0x43, 0x10, 0x4C, 0x00, 0x00, 0x7E, 0x00, 0xF7 };
41 static byte roland_reverb_sysex[] = { 0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x01, 0x30, 0x02, 0x04, 0x00, 0x40, 0x40, 0x00, 0x00, 0x09, 0xF7 };
44 case MidiSysexMessage::ResetGM:
46 return reset_gm_sysex;
47 case MidiSysexMessage::ResetGS:
49 return reset_gs_sysex;
50 case MidiSysexMessage::ResetXG:
52 return reset_xg_sysex;
53 case MidiSysexMessage::RolandSetReverb:
54 length =
lengthof(roland_reverb_sysex);
55 return roland_reverb_sysex;
79 this->buf = MallocT<byte>(len);
80 if (fread(this->buf, 1, len, file) == len) {
103 return this->buflen > 0;
112 return this->pos >= this->buflen;
122 if (this->
IsEnd())
return false;
123 b = this->buf[this->pos++];
139 if (this->
IsEnd())
return false;
140 b = this->buf[this->pos++];
141 res = (res << 7) | (b & 0x7F);
154 if (this->
IsEnd())
return false;
155 if (this->buflen - this->pos < length)
return false;
156 memcpy(dest, this->buf + this->pos, length);
168 if (this->
IsEnd())
return false;
169 if (this->buflen - this->pos < count)
return false;
181 if (count > this->pos)
return false;
187 static bool ReadTrackChunk(FILE *file,
MidiFile &target)
191 const byte magic[] = {
'M',
'T',
'r',
'k' };
192 if (fread(buf,
sizeof(magic), 1, file) != 1) {
195 if (memcmp(magic, buf,
sizeof(magic)) != 0) {
201 if (fread(&chunk_length, 1, 4, file) != 4) {
204 chunk_length = FROM_BE32(chunk_length);
214 byte last_status = 0;
215 bool running_sysex =
false;
216 while (!chunk.
IsEnd()) {
218 uint32 deltatime = 0;
224 block = &target.
blocks.back();
233 if ((status & 0x80) == 0) {
237 status = last_status;
239 }
else if ((status & 0xF0) != 0xF0) {
241 last_status = status;
244 switch (status & 0xF0) {
247 case MIDIST_POLYPRESS:
248 case MIDIST_CONTROLLER:
249 case MIDIST_PITCHBEND:
258 case MIDIST_CHANPRESS:
269 }
else if (status == MIDIST_SMF_META) {
281 return (length == 0);
284 if (length != 3)
return false;
290 if (!chunk.
Skip(length)) {
295 }
else if (status == MIDIST_SYSEX || (status == MIDIST_SMF_ESCAPE && running_sysex)) {
306 if (data[length] != 0xF7) {
308 running_sysex =
true;
311 running_sysex =
false;
313 }
else if (status == MIDIST_SMF_ESCAPE) {
341 bool TicktimeAscending(
const T &a,
const T &b)
343 return a.ticktime < b.ticktime;
346 static bool FixupMidiData(
MidiFile &target)
349 std::sort(target.
tempos.begin(), target.
tempos.end(), TicktimeAscending<MidiFile::TempoChange>);
350 std::sort(target.
blocks.begin(), target.
blocks.end(), TicktimeAscending<MidiFile::DataBlock>);
352 if (target.
tempos.size() == 0) {
360 std::vector<MidiFile::DataBlock> merged_blocks;
361 uint32 last_ticktime = 0;
362 for (
size_t i = 0; i < target.
blocks.size(); i++) {
366 }
else if (block.
ticktime > last_ticktime || merged_blocks.size() == 0) {
367 merged_blocks.push_back(block);
370 byte *datadest = merged_blocks.back().data.Append(block.
data.
Length());
374 std::swap(merged_blocks, target.
blocks);
378 uint32 last_realtime = 0;
379 size_t cur_tempo = 0, cur_block = 0;
380 while (cur_block < target.
blocks.size()) {
386 int64 tickdiff = block.
ticktime - last_ticktime;
388 last_realtime += uint32(tickdiff * tempo.
tempo / target.
tickdiv);
393 int64 tickdiff = next_tempo.
ticktime - last_ticktime;
394 last_ticktime = next_tempo.
ticktime;
395 last_realtime += uint32(tickdiff * tempo.
tempo / target.
tickdiv);
412 if (!file)
return false;
413 bool result = ReadSMFHeader(file, header);
429 if (fread(buffer,
sizeof(buffer), 1, file) != 1) {
434 const byte magic[] = {
'M',
'T',
'h',
'd', 0x00, 0x00, 0x00, 0x06 };
435 if (
MemCmpT(buffer, magic,
sizeof(magic)) != 0) {
440 header.format = (buffer[8] << 8) | buffer[9];
441 header.tracks = (buffer[10] << 8) | buffer[11];
442 header.tickdiv = (buffer[12] << 8) | buffer[13];
453 _midifile_instance =
this;
455 this->blocks.clear();
456 this->tempos.clear();
459 bool success =
false;
461 if (file == NULL)
return false;
464 if (!ReadSMFHeader(file, header))
goto cleanup;
467 if (header.format != 0 && header.format != 1)
goto cleanup;
469 if ((header.tickdiv & 0x8000) != 0)
goto cleanup;
471 this->tickdiv = header.tickdiv;
473 for (; header.tracks > 0; header.tracks--) {
474 if (!ReadTrackChunk(file, *
this)) {
479 success = FixupMidiData(*
this);
517 Channel() : cur_program(0xFF), running_status(0), delay(0), playpos(0), startpos(0), returnpos(0) { }
527 static const byte programvelocities[128];
535 MPSMIDIST_SEGMENT_RETURN = 0xFD,
536 MPSMIDIST_SEGMENT_CALL = 0xFE,
537 MPSMIDIST_ENDSONG = 0xFF,
559 : songdata(data), songdatalen(length), target(target)
566 this->initial_tempo = this->songdata[pos++];
569 loopmax = this->songdata[pos++];
570 for (loopidx = 0; loopidx < loopmax; loopidx++) {
575 this->segments.push_back(pos + 4);
576 pos += FROM_LE16(*(
const int16 *)(this->songdata + pos));
581 loopmax = this->songdata[pos++];
582 for (loopidx = 0; loopidx < loopmax; loopidx++) {
586 byte ch = this->songdata[pos++];
587 this->channels[ch].
startpos = pos + 4;
588 pos += FROM_LE16(*(
const int16 *)(this->songdata + pos));
602 b = this->songdata[pos++];
603 res = (res << 7) + (b & 0x7F);
613 for (
int ch = 0; ch < 16; ch++) {
614 Channel &chandata = this->channels[ch];
634 Channel &chandata = this->channels[channel];
638 b1 = this->songdata[chandata.
playpos++];
641 if (b1 == MPSMIDIST_SEGMENT_CALL) {
642 b1 = this->songdata[chandata.
playpos++];
644 chandata.
playpos = this->segments[b1];
653 if (b1 == MPSMIDIST_SEGMENT_RETURN) {
664 if (b1 == MPSMIDIST_ENDSONG) {
665 this->shouldplayflag =
false;
674 b1 = this->songdata[chandata.
playpos++];
680 b2 = this->songdata[chandata.
playpos++];
686 velocity = (int16)b2 * 0x50;
689 velocity = b2 * programvelocities[chandata.
cur_program];
691 b2 = (velocity / 128) & 0x00FF;
692 AddMidiData(outblock, MIDIST_NOTEON + channel, b1, b2);
695 AddMidiData(outblock, MIDIST_NOTEON + channel, b1, 0);
698 case MIDIST_CONTROLLER:
699 b2 = this->songdata[chandata.
playpos++];
700 if (b1 == MIDICT_MODE_MONO) {
706 }
else if (b1 == 0) {
710 this->current_tempo = ((int)b2) * 48 / 60;
713 }
else if (b1 == MIDICT_EFFECTS1) {
718 AddMidiData(outblock, MIDIST_CONTROLLER + channel, b1, b2);
726 this->shouldplayflag =
false;
733 if (b1 == 0x57 || b1 == 0x3F) {
736 AddMidiData(outblock, MIDIST_PROGCHG + channel, b1);
738 case MIDIST_PITCHBEND:
739 b2 = this->songdata[chandata.
playpos++];
740 AddMidiData(outblock, MIDIST_PITCHBEND + channel, b1, b2);
747 }
while (newdelay == 0);
758 this->tempo_ticks -= this->current_tempo;
759 if (this->tempo_ticks > 0) {
762 this->tempo_ticks += TEMPO_RATE;
765 for (
int ch = 0; ch < 16; ch++) {
766 Channel &chandata = this->channels[ch];
768 if (chandata.
delay == 0) {
769 chandata.
delay = this->PlayChannelFrame(block, ch);
775 return this->shouldplayflag;
786 this->target.
tickdiv = TEMPO_RATE;
791 this->shouldplayflag =
true;
792 this->current_tempo = (int32)this->initial_tempo * 24 / 60;
793 this->tempo_ticks = this->current_tempo;
797 AddMidiData(this->target.
blocks.back(), MIDIST_PROGCHG+9, 0x00);
802 for (uint32 tick = 0; tick < 100000; tick+=1) {
804 auto &block = this->target.
blocks.back();
806 if (!this->PlayFrame(block)) {
817 100, 100, 100, 100, 100, 90, 100, 100, 100, 100, 100, 90, 100, 100, 100, 100,
818 100, 100, 85, 100, 100, 100, 100, 100, 100, 100, 100, 100, 90, 90, 110, 80,
819 100, 100, 100, 90, 70, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
820 100, 100, 90, 100, 100, 100, 100, 100, 100, 120, 100, 100, 100, 120, 100, 127,
821 100, 100, 90, 100, 100, 100, 100, 100, 100, 95, 100, 100, 100, 100, 100, 100,
822 100, 100, 100, 100, 100, 100, 100, 115, 100, 100, 100, 100, 100, 100, 100, 100,
823 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
824 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
835 _midifile_instance =
this;
838 return machine.
PlayInto() && FixupMidiData(*
this);
845 return this->LoadFile(song.
filename);
848 size_t songdatalen = 0;
850 if (songdata != NULL) {
851 bool result = this->LoadMpsData(songdata, songdatalen);
869 std::swap(this->blocks, other.
blocks);
870 std::swap(this->tempos, other.
tempos);
873 _midifile_instance =
this;
880 static void WriteVariableLen(FILE *f, uint32 value)
884 fwrite(&tb, 1, 1, f);
885 }
else if (value < 0x3FFF) {
887 tb[1] = value & 0x7F; value >>= 7;
888 tb[0] = (value & 0x7F) | 0x80; value >>= 7;
889 fwrite(tb, 1,
sizeof(tb), f);
890 }
else if (value < 0x1FFFFF) {
892 tb[2] = value & 0x7F; value >>= 7;
893 tb[1] = (value & 0x7F) | 0x80; value >>= 7;
894 tb[0] = (value & 0x7F) | 0x80; value >>= 7;
895 fwrite(tb, 1,
sizeof(tb), f);
896 }
else if (value < 0x0FFFFFFF) {
898 tb[3] = value & 0x7F; value >>= 7;
899 tb[2] = (value & 0x7F) | 0x80; value >>= 7;
900 tb[1] = (value & 0x7F) | 0x80; value >>= 7;
901 tb[0] = (value & 0x7F) | 0x80; value >>= 7;
902 fwrite(tb, 1,
sizeof(tb), f);
919 const byte fileheader[] = {
921 0x00, 0x00, 0x00, 0x06,
924 (byte)(this->tickdiv >> 8), (byte)this->tickdiv,
926 fwrite(fileheader,
sizeof(fileheader), 1, f);
929 const byte trackheader[] = {
933 fwrite(trackheader,
sizeof(trackheader), 1, f);
935 size_t tracksizepos = ftell(f) - 4;
939 size_t nexttempoindex = 0;
940 for (
size_t bi = 0; bi < this->blocks.size(); bi++) {
942 TempoChange &nexttempo = this->tempos[nexttempoindex];
944 uint32 timediff = block.
ticktime - lasttime;
948 timediff = nexttempo.
ticktime - lasttime;
952 lasttime += timediff;
953 bool needtime =
false;
954 WriteVariableLen(f, timediff);
958 byte tempobuf[6] = { MIDIST_SMF_META, 0x51, 0x03, 0, 0, 0 };
959 tempobuf[3] = (nexttempo.
tempo & 0x00FF0000) >> 16;
960 tempobuf[4] = (nexttempo.
tempo & 0x0000FF00) >> 8;
961 tempobuf[5] = (nexttempo.
tempo & 0x000000FF);
962 fwrite(tempobuf,
sizeof(tempobuf), 1, f);
976 while (dp < block.
data.
End()) {
984 switch (*dp & 0xF0) {
987 case MIDIST_POLYPRESS:
988 case MIDIST_CONTROLLER:
989 case MIDIST_PITCHBEND:
994 case MIDIST_CHANPRESS:
1001 if (*dp == MIDIST_SYSEX) {
1002 fwrite(dp, 1, 1, f);
1004 byte *sysexend = dp;
1005 while (*sysexend != MIDIST_ENDSYSEX) sysexend++;
1006 ptrdiff_t sysexlen = sysexend - dp;
1007 WriteVariableLen(f, sysexlen);
1008 fwrite(dp, 1, sysexend - dp, f);
1020 static const byte track_end_marker[] = { 0x00, MIDIST_SMF_META, 0x2F, 0x00 };
1021 fwrite(&track_end_marker,
sizeof(track_end_marker), 1, f);
1024 size_t trackendpos = ftell(f);
1025 fseek(f, tracksizepos, SEEK_SET);
1026 uint32 tracksize = (uint32)(trackendpos - tracksizepos - 4);
1027 tracksize = TO_BE32(tracksize);
1028 fwrite(&tracksize, 4, 1, f);
1044 char filename[MAX_PATH];
1046 return std::string(filename);
1048 return std::string(filename);
1050 return std::string();
1056 char basename[MAX_PATH];
1058 const char *fnstart = strrchr(song.
filename, PATHSEPCHAR);
1059 if (fnstart == NULL) {
1066 char *wp = basename;
1067 for (
const char *rp = fnstart; *rp !=
'\0'; rp++) {
1068 if (*rp !=
'.') *wp++ = *rp;
1073 char tempdirname[MAX_PATH];
1078 char output_filename[MAX_PATH];
1083 return std::string(output_filename);
1089 if (data == NULL)
return std::string();
1094 return std::string();
1098 if (midifile.
WriteSMF(output_filename)) {
1099 return std::string(output_filename);
1101 return std::string();
1106 static bool CmdDumpSMF(byte argc,
char *argv[])
1117 if (_midifile_instance == NULL) {
1118 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.");
1122 char fnbuf[MAX_PATH] = { 0 };
1129 if (_midifile_instance->
WriteSMF(fnbuf)) {
1138 static void RegisterConsoleMidiCommands()
1140 static bool registered =
false;
1147 MidiFile::MidiFile()
1149 RegisterConsoleMidiCommands();
1152 MidiFile::~MidiFile()
1154 if (_midifile_instance ==
this) {
1155 _midifile_instance = NULL;
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
SmallVector< byte, 8 > data
raw midi data contained in block
const T * Begin() const
Get the pointer to the first item (const)
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.
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.
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.
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.
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.
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.
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