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;
48 this->buf = MallocT<byte>(len);
49 if (fread(this->buf, 1, len, file) == len) {
72 return this->buflen > 0;
81 return this->pos >= this->buflen;
91 if (this->
IsEnd())
return false;
92 b = this->buf[this->pos++];
108 if (this->
IsEnd())
return false;
109 b = this->buf[this->pos++];
110 res = (res << 7) | (b & 0x7F);
123 if (this->
IsEnd())
return false;
124 if (this->buflen - this->pos < length)
return false;
125 memcpy(dest, this->buf + this->pos, length);
137 if (this->
IsEnd())
return false;
138 if (this->buflen - this->pos < count)
return false;
150 if (count > this->pos)
return false;
156 static bool ReadTrackChunk(FILE *file,
MidiFile &target)
160 const byte magic[] = {
'M',
'T',
'r',
'k' };
161 if (fread(buf,
sizeof(magic), 1, file) != 1) {
164 if (memcmp(magic, buf,
sizeof(magic)) != 0) {
170 if (fread(&chunk_length, 1, 4, file) != 4) {
173 chunk_length = FROM_BE32(chunk_length);
183 byte last_status = 0;
184 bool running_sysex =
false;
185 while (!chunk.
IsEnd()) {
187 uint32 deltatime = 0;
193 block = &target.
blocks.back();
202 if ((status & 0x80) == 0) {
206 status = last_status;
208 }
else if ((status & 0xF0) != 0xF0) {
210 last_status = status;
213 switch (status & 0xF0) {
216 case MIDIST_POLYPRESS:
217 case MIDIST_CONTROLLER:
218 case MIDIST_PITCHBEND:
227 case MIDIST_CHANPRESS:
238 }
else if (status == MIDIST_SMF_META) {
250 return (length == 0);
253 if (length != 3)
return false;
259 if (!chunk.
Skip(length)) {
264 }
else if (status == MIDIST_SYSEX || (status == MIDIST_SMF_ESCAPE && running_sysex)) {
275 if (data[length] != 0xF7) {
277 running_sysex =
true;
280 running_sysex =
false;
282 }
else if (status == MIDIST_SMF_ESCAPE) {
310 bool TicktimeAscending(
const T &a,
const T &b)
312 return a.ticktime < b.ticktime;
315 static bool FixupMidiData(
MidiFile &target)
318 std::sort(target.
tempos.begin(), target.
tempos.end(), TicktimeAscending<MidiFile::TempoChange>);
319 std::sort(target.
blocks.begin(), target.
blocks.end(), TicktimeAscending<MidiFile::DataBlock>);
321 if (target.
tempos.size() == 0) {
329 std::vector<MidiFile::DataBlock> merged_blocks;
330 uint32 last_ticktime = 0;
331 for (
size_t i = 0; i < target.
blocks.size(); i++) {
335 }
else if (block.
ticktime > last_ticktime || merged_blocks.size() == 0) {
336 merged_blocks.push_back(block);
339 byte *datadest = merged_blocks.back().data.Append(block.
data.
Length());
343 std::swap(merged_blocks, target.
blocks);
347 uint32 last_realtime = 0;
348 size_t cur_tempo = 0, cur_block = 0;
349 while (cur_block < target.
blocks.size()) {
355 int64 tickdiff = block.
ticktime - last_ticktime;
357 last_realtime += uint32(tickdiff * tempo.
tempo / target.
tickdiv);
362 int64 tickdiff = next_tempo.
ticktime - last_ticktime;
363 last_ticktime = next_tempo.
ticktime;
364 last_realtime += uint32(tickdiff * tempo.
tempo / target.
tickdiv);
381 if (!file)
return false;
382 bool result = ReadSMFHeader(file, header);
398 if (fread(buffer,
sizeof(buffer), 1, file) != 1) {
403 const byte magic[] = {
'M',
'T',
'h',
'd', 0x00, 0x00, 0x00, 0x06 };
404 if (
MemCmpT(buffer, magic,
sizeof(magic)) != 0) {
409 header.format = (buffer[8] << 8) | buffer[9];
410 header.tracks = (buffer[10] << 8) | buffer[11];
411 header.tickdiv = (buffer[12] << 8) | buffer[13];
422 _midifile_instance =
this;
424 this->blocks.clear();
425 this->tempos.clear();
428 bool success =
false;
430 if (file == NULL)
return false;
433 if (!ReadSMFHeader(file, header))
goto cleanup;
436 if (header.format != 0 && header.format != 1)
goto cleanup;
438 if ((header.tickdiv & 0x8000) != 0)
goto cleanup;
440 this->tickdiv = header.tickdiv;
442 for (; header.tracks > 0; header.tracks--) {
443 if (!ReadTrackChunk(file, *
this)) {
448 success = FixupMidiData(*
this);
486 Channel() : cur_program(0xFF), running_status(0), delay(0), playpos(0), startpos(0), returnpos(0) { }
496 static const byte programvelocities[128];
504 MPSMIDIST_SEGMENT_RETURN = 0xFD,
505 MPSMIDIST_SEGMENT_CALL = 0xFE,
506 MPSMIDIST_ENDSONG = 0xFF,
528 : songdata(data), songdatalen(length), target(target)
535 this->initial_tempo = this->songdata[pos++];
538 loopmax = this->songdata[pos++];
539 for (loopidx = 0; loopidx < loopmax; loopidx++) {
544 this->segments.push_back(pos + 4);
545 pos += FROM_LE16(*(
const int16 *)(this->songdata + pos));
550 loopmax = this->songdata[pos++];
551 for (loopidx = 0; loopidx < loopmax; loopidx++) {
555 byte ch = this->songdata[pos++];
556 this->channels[ch].
startpos = pos + 4;
557 pos += FROM_LE16(*(
const int16 *)(this->songdata + pos));
571 b = this->songdata[pos++];
572 res = (res << 7) + (b & 0x7F);
582 for (
int ch = 0; ch < 16; ch++) {
583 Channel &chandata = this->channels[ch];
603 Channel &chandata = this->channels[channel];
607 b1 = this->songdata[chandata.
playpos++];
610 if (b1 == MPSMIDIST_SEGMENT_CALL) {
611 b1 = this->songdata[chandata.
playpos++];
613 chandata.
playpos = this->segments[b1];
622 if (b1 == MPSMIDIST_SEGMENT_RETURN) {
633 if (b1 == MPSMIDIST_ENDSONG) {
634 this->shouldplayflag =
false;
643 b1 = this->songdata[chandata.
playpos++];
649 b2 = this->songdata[chandata.
playpos++];
655 velocity = (int16)b2 * 0x50;
658 velocity = b2 * programvelocities[chandata.
cur_program];
660 b2 = (velocity / 128) & 0x00FF;
661 AddMidiData(outblock, MIDIST_NOTEON + channel, b1, b2);
664 AddMidiData(outblock, MIDIST_NOTEON + channel, b1, 0);
667 case MIDIST_CONTROLLER:
668 b2 = this->songdata[chandata.
playpos++];
669 if (b1 == MIDICT_MODE_MONO) {
675 }
else if (b1 == 0) {
679 this->current_tempo = ((int)b2) * 48 / 60;
682 }
else if (b1 == MIDICT_EFFECTS1) {
687 AddMidiData(outblock, MIDIST_CONTROLLER + channel, b1, b2);
695 this->shouldplayflag =
false;
702 if (b1 == 0x57 || b1 == 0x3F) {
705 AddMidiData(outblock, MIDIST_PROGCHG + channel, b1);
707 case MIDIST_PITCHBEND:
708 b2 = this->songdata[chandata.
playpos++];
709 AddMidiData(outblock, MIDIST_PITCHBEND + channel, b1, b2);
716 }
while (newdelay == 0);
727 this->tempo_ticks -= this->current_tempo;
728 if (this->tempo_ticks > 0) {
731 this->tempo_ticks += TEMPO_RATE;
734 for (
int ch = 0; ch < 16; ch++) {
735 Channel &chandata = this->channels[ch];
737 if (chandata.
delay == 0) {
738 chandata.
delay = this->PlayChannelFrame(block, ch);
744 return this->shouldplayflag;
755 this->target.
tickdiv = TEMPO_RATE;
760 this->shouldplayflag =
true;
761 this->current_tempo = (int32)this->initial_tempo * 24 / 60;
762 this->tempo_ticks = this->current_tempo;
766 AddMidiData(this->target.
blocks.back(), MIDIST_PROGCHG+9, 0x00);
771 for (uint32 tick = 0; tick < 100000; tick+=1) {
773 auto &block = this->target.
blocks.back();
775 if (!this->PlayFrame(block)) {
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,
804 _midifile_instance =
this;
807 return machine.
PlayInto() && FixupMidiData(*
this);
814 return this->LoadFile(song.
filename);
817 size_t songdatalen = 0;
819 if (songdata != NULL) {
820 bool result = this->LoadMpsData(songdata, songdatalen);
838 std::swap(this->blocks, other.
blocks);
839 std::swap(this->tempos, other.
tempos);
842 _midifile_instance =
this;
849 static void WriteVariableLen(FILE *f, uint32 value)
853 fwrite(&tb, 1, 1, f);
854 }
else if (value < 0x3FFF) {
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) {
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) {
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);
888 const byte fileheader[] = {
890 0x00, 0x00, 0x00, 0x06,
893 (byte)(this->tickdiv >> 8), (byte)this->tickdiv,
895 fwrite(fileheader,
sizeof(fileheader), 1, f);
898 const byte trackheader[] = {
902 fwrite(trackheader,
sizeof(trackheader), 1, f);
904 size_t tracksizepos = ftell(f) - 4;
908 size_t nexttempoindex = 0;
909 for (
size_t bi = 0; bi < this->blocks.size(); bi++) {
911 TempoChange &nexttempo = this->tempos[nexttempoindex];
913 uint32 timediff = block.
ticktime - lasttime;
917 timediff = nexttempo.
ticktime - lasttime;
921 lasttime += timediff;
922 bool needtime =
false;
923 WriteVariableLen(f, timediff);
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);
945 while (dp < block.
data.
End()) {
953 switch (*dp & 0xF0) {
956 case MIDIST_POLYPRESS:
957 case MIDIST_CONTROLLER:
958 case MIDIST_PITCHBEND:
963 case MIDIST_CHANPRESS:
970 if (*dp == MIDIST_SYSEX) {
974 while (*sysexend != MIDIST_ENDSYSEX) sysexend++;
975 ptrdiff_t sysexlen = sysexend - dp;
976 WriteVariableLen(f, sysexlen);
977 fwrite(dp, 1, sysexend - dp, f);
989 static const byte track_end_marker[] = { 0x00, MIDIST_SMF_META, 0x2F, 0x00 };
990 fwrite(&track_end_marker,
sizeof(track_end_marker), 1, f);
993 size_t trackendpos = ftell(f);
994 fseek(f, tracksizepos, SEEK_SET);
995 uint32 tracksize = (uint32)(trackendpos - tracksizepos - 4);
996 tracksize = TO_BE32(tracksize);
997 fwrite(&tracksize, 4, 1, f);
1013 char filename[MAX_PATH];
1015 return std::string(filename);
1017 return std::string(filename);
1019 return std::string();
1025 char basename[MAX_PATH];
1027 const char *fnstart = strrchr(song.
filename, PATHSEPCHAR);
1028 if (fnstart == NULL) {
1035 char *wp = basename;
1036 for (
const char *rp = fnstart; *rp !=
'\0'; rp++) {
1037 if (*rp !=
'.') *wp++ = *rp;
1042 char tempdirname[MAX_PATH];
1047 char output_filename[MAX_PATH];
1052 return std::string(output_filename);
1058 if (data == NULL)
return std::string();
1063 return std::string();
1067 if (midifile.
WriteSMF(output_filename)) {
1068 return std::string(output_filename);
1070 return std::string();
1075 static bool CmdDumpSMF(byte argc,
char *argv[])
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.");
1091 char fnbuf[MAX_PATH] = { 0 };
1098 if (_midifile_instance->
WriteSMF(fnbuf)) {
1107 static void RegisterConsoleMidiCommands()
1109 static bool registered =
false;
1116 MidiFile::MidiFile()
1118 RegisterConsoleMidiCommands();
1121 MidiFile::~MidiFile()
1123 if (_midifile_instance ==
this) {
1124 _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