10 #include "midifile.hpp" 11 #include "../fileio_func.h" 12 #include "../fileio_type.h" 13 #include "../string_func.h" 14 #include "../core/endian_func.hpp" 15 #include "../base_media_base.h" 19 #include "../console_func.h" 20 #include "../console_internal.h" 25 static MidiFile *_midifile_instance =
nullptr;
33 const byte *MidiGetStandardSysexMessage(MidiSysexMessage msg,
size_t &length)
35 static byte reset_gm_sysex[] = { 0xF0, 0x7E, 0x7F, 0x09, 0x01, 0xF7 };
36 static byte reset_gs_sysex[] = { 0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7 };
37 static byte reset_xg_sysex[] = { 0xF0, 0x43, 0x10, 0x4C, 0x00, 0x00, 0x7E, 0x00, 0xF7 };
38 static byte roland_reverb_sysex[] = { 0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x01, 0x30, 0x02, 0x04, 0x00, 0x40, 0x40, 0x00, 0x00, 0x09, 0xF7 };
41 case MidiSysexMessage::ResetGM:
43 return reset_gm_sysex;
44 case MidiSysexMessage::ResetGS:
46 return reset_gs_sysex;
47 case MidiSysexMessage::ResetXG:
49 return reset_xg_sysex;
50 case MidiSysexMessage::RolandSetReverb:
51 length =
lengthof(roland_reverb_sysex);
52 return roland_reverb_sysex;
76 this->buf = MallocT<byte>(len);
77 if (fread(this->buf, 1, len, file) == len) {
100 return this->buflen > 0;
109 return this->pos >= this->buflen;
119 if (this->
IsEnd())
return false;
120 b = this->buf[this->pos++];
136 if (this->
IsEnd())
return false;
137 b = this->buf[this->pos++];
138 res = (res << 7) | (b & 0x7F);
151 if (this->
IsEnd())
return false;
152 if (this->buflen - this->pos < length)
return false;
153 memcpy(dest, this->buf + this->pos, length);
166 if (this->
IsEnd())
return false;
167 if (this->buflen - this->pos < length)
return false;
168 dest->
data.insert(dest->
data.end(), this->buf + this->pos, this->buf + this->pos + length);
180 if (this->
IsEnd())
return false;
181 if (this->buflen - this->pos < count)
return false;
193 if (count > this->pos)
return false;
199 static bool ReadTrackChunk(FILE *file,
MidiFile &target)
203 const byte magic[] = {
'M',
'T',
'r',
'k' };
204 if (fread(buf,
sizeof(magic), 1, file) != 1) {
207 if (memcmp(magic, buf,
sizeof(magic)) != 0) {
213 if (fread(&chunk_length, 1, 4, file) != 4) {
216 chunk_length = FROM_BE32(chunk_length);
226 byte last_status = 0;
227 bool running_sysex =
false;
228 while (!chunk.
IsEnd()) {
230 uint32 deltatime = 0;
236 block = &target.
blocks.back();
245 if ((status & 0x80) == 0) {
249 status = last_status;
251 }
else if ((status & 0xF0) != 0xF0) {
253 last_status = status;
255 switch (status & 0xF0) {
258 case MIDIST_POLYPRESS:
259 case MIDIST_CONTROLLER:
260 case MIDIST_PITCHBEND:
262 block->
data.push_back(status);
268 case MIDIST_CHANPRESS:
270 block->
data.push_back(status);
274 block->
data.push_back(buf[0]);
279 }
else if (status == MIDIST_SMF_META) {
291 return (length == 0);
294 if (length != 3)
return false;
300 if (!chunk.
Skip(length)) {
305 }
else if (status == MIDIST_SYSEX || (status == MIDIST_SMF_ESCAPE && running_sysex)) {
311 block->
data.push_back(0xF0);
315 if (block->
data.back() != 0xF7) {
317 running_sysex =
true;
318 block->
data.push_back(0xF7);
320 running_sysex =
false;
322 }
else if (status == MIDIST_SMF_ESCAPE) {
349 bool TicktimeAscending(
const T &a,
const T &b)
351 return a.ticktime < b.ticktime;
354 static bool FixupMidiData(
MidiFile &target)
357 std::sort(target.
tempos.begin(), target.
tempos.end(), TicktimeAscending<MidiFile::TempoChange>);
358 std::sort(target.
blocks.begin(), target.
blocks.end(), TicktimeAscending<MidiFile::DataBlock>);
360 if (target.
tempos.size() == 0) {
368 std::vector<MidiFile::DataBlock> merged_blocks;
369 uint32 last_ticktime = 0;
370 for (
size_t i = 0; i < target.
blocks.size(); i++) {
372 if (block.
data.size() == 0) {
374 }
else if (block.
ticktime > last_ticktime || merged_blocks.size() == 0) {
375 merged_blocks.push_back(block);
378 merged_blocks.back().data.insert(merged_blocks.back().data.end(), block.
data.begin(), block.
data.end());
381 std::swap(merged_blocks, target.
blocks);
385 uint32 last_realtime = 0;
386 size_t cur_tempo = 0, cur_block = 0;
387 while (cur_block < target.
blocks.size()) {
393 int64 tickdiff = block.
ticktime - last_ticktime;
395 last_realtime += uint32(tickdiff * tempo.
tempo / target.
tickdiv);
400 int64 tickdiff = next_tempo.
ticktime - last_ticktime;
401 last_ticktime = next_tempo.
ticktime;
402 last_realtime += uint32(tickdiff * tempo.
tempo / target.
tickdiv);
419 if (!file)
return false;
420 bool result = ReadSMFHeader(file, header);
436 if (fread(buffer,
sizeof(buffer), 1, file) != 1) {
441 const byte magic[] = {
'M',
'T',
'h',
'd', 0x00, 0x00, 0x00, 0x06 };
442 if (
MemCmpT(buffer, magic,
sizeof(magic)) != 0) {
447 header.format = (buffer[8] << 8) | buffer[9];
448 header.tracks = (buffer[10] << 8) | buffer[11];
449 header.tickdiv = (buffer[12] << 8) | buffer[13];
460 _midifile_instance =
this;
462 this->blocks.clear();
463 this->tempos.clear();
466 bool success =
false;
468 if (file ==
nullptr)
return false;
471 if (!ReadSMFHeader(file, header))
goto cleanup;
474 if (header.format != 0 && header.format != 1)
goto cleanup;
476 if ((header.tickdiv & 0x8000) != 0)
goto cleanup;
478 this->tickdiv = header.tickdiv;
480 for (; header.tracks > 0; header.tracks--) {
481 if (!ReadTrackChunk(file, *
this)) {
486 success = FixupMidiData(*
this);
524 Channel() : cur_program(0xFF), running_status(0), delay(0), playpos(0), startpos(0), returnpos(0) { }
534 static const byte programvelocities[128];
542 MPSMIDIST_SEGMENT_RETURN = 0xFD,
543 MPSMIDIST_SEGMENT_CALL = 0xFE,
544 MPSMIDIST_ENDSONG = 0xFF,
549 block.
data.push_back(b1);
550 block.
data.push_back(b2);
554 block.
data.push_back(b1);
555 block.
data.push_back(b2);
556 block.
data.push_back(b3);
566 : songdata(data), songdatalen(length), target(target)
573 this->initial_tempo = this->songdata[pos++];
576 loopmax = this->songdata[pos++];
577 for (loopidx = 0; loopidx < loopmax; loopidx++) {
582 this->segments.push_back(pos + 4);
583 pos += FROM_LE16(*(
const int16 *)(this->songdata + pos));
588 loopmax = this->songdata[pos++];
589 for (loopidx = 0; loopidx < loopmax; loopidx++) {
593 byte ch = this->songdata[pos++];
594 this->channels[ch].
startpos = pos + 4;
595 pos += FROM_LE16(*(
const int16 *)(this->songdata + pos));
609 b = this->songdata[pos++];
610 res = (res << 7) + (b & 0x7F);
620 for (
int ch = 0; ch < 16; ch++) {
621 Channel &chandata = this->channels[ch];
641 Channel &chandata = this->channels[channel];
645 b1 = this->songdata[chandata.
playpos++];
648 if (b1 == MPSMIDIST_SEGMENT_CALL) {
649 b1 = this->songdata[chandata.
playpos++];
651 chandata.
playpos = this->segments[b1];
660 if (b1 == MPSMIDIST_SEGMENT_RETURN) {
671 if (b1 == MPSMIDIST_ENDSONG) {
672 this->shouldplayflag =
false;
681 b1 = this->songdata[chandata.
playpos++];
687 b2 = this->songdata[chandata.
playpos++];
693 velocity = (int16)b2 * 0x50;
696 velocity = b2 * programvelocities[chandata.
cur_program];
698 b2 = (velocity / 128) & 0x00FF;
699 AddMidiData(outblock, MIDIST_NOTEON + channel, b1, b2);
702 AddMidiData(outblock, MIDIST_NOTEON + channel, b1, 0);
705 case MIDIST_CONTROLLER:
706 b2 = this->songdata[chandata.
playpos++];
707 if (b1 == MIDICT_MODE_MONO) {
713 }
else if (b1 == 0) {
717 this->current_tempo = ((int)b2) * 48 / 60;
720 }
else if (b1 == MIDICT_EFFECTS1) {
725 AddMidiData(outblock, MIDIST_CONTROLLER + channel, b1, b2);
733 this->shouldplayflag =
false;
740 if (b1 == 0x57 || b1 == 0x3F) {
743 AddMidiData(outblock, MIDIST_PROGCHG + channel, b1);
745 case MIDIST_PITCHBEND:
746 b2 = this->songdata[chandata.
playpos++];
747 AddMidiData(outblock, MIDIST_PITCHBEND + channel, b1, b2);
754 }
while (newdelay == 0);
765 this->tempo_ticks -= this->current_tempo;
766 if (this->tempo_ticks > 0) {
769 this->tempo_ticks += TEMPO_RATE;
772 for (
int ch = 0; ch < 16; ch++) {
773 Channel &chandata = this->channels[ch];
775 if (chandata.
delay == 0) {
776 chandata.
delay = this->PlayChannelFrame(block, ch);
782 return this->shouldplayflag;
793 this->target.
tickdiv = TEMPO_RATE;
798 this->shouldplayflag =
true;
799 this->current_tempo = (int32)this->initial_tempo * 24 / 60;
800 this->tempo_ticks = this->current_tempo;
804 AddMidiData(this->target.
blocks.back(), MIDIST_PROGCHG+9, 0x00);
809 for (uint32 tick = 0; tick < 100000; tick+=1) {
811 auto &block = this->target.
blocks.back();
813 if (!this->PlayFrame(block)) {
824 100, 100, 100, 100, 100, 90, 100, 100, 100, 100, 100, 90, 100, 100, 100, 100,
825 100, 100, 85, 100, 100, 100, 100, 100, 100, 100, 100, 100, 90, 90, 110, 80,
826 100, 100, 100, 90, 70, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
827 100, 100, 90, 100, 100, 100, 100, 100, 100, 120, 100, 100, 100, 120, 100, 127,
828 100, 100, 90, 100, 100, 100, 100, 100, 100, 95, 100, 100, 100, 100, 100, 100,
829 100, 100, 100, 100, 100, 100, 100, 115, 100, 100, 100, 100, 100, 100, 100, 100,
830 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
831 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
842 _midifile_instance =
this;
845 return machine.
PlayInto() && FixupMidiData(*
this);
852 return this->LoadFile(song.
filename);
855 size_t songdatalen = 0;
857 if (songdata !=
nullptr) {
858 bool result = this->LoadMpsData(songdata, songdatalen);
876 std::swap(this->blocks, other.
blocks);
877 std::swap(this->tempos, other.
tempos);
880 _midifile_instance =
this;
887 static void WriteVariableLen(FILE *f, uint32 value)
891 fwrite(&tb, 1, 1, f);
892 }
else if (value < 0x3FFF) {
894 tb[1] = value & 0x7F; value >>= 7;
895 tb[0] = (value & 0x7F) | 0x80; value >>= 7;
896 fwrite(tb, 1,
sizeof(tb), f);
897 }
else if (value < 0x1FFFFF) {
899 tb[2] = value & 0x7F; 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);
903 }
else if (value < 0x0FFFFFFF) {
905 tb[3] = value & 0x7F; value >>= 7;
906 tb[2] = (value & 0x7F) | 0x80; value >>= 7;
907 tb[1] = (value & 0x7F) | 0x80; value >>= 7;
908 tb[0] = (value & 0x7F) | 0x80; value >>= 7;
909 fwrite(tb, 1,
sizeof(tb), f);
926 const byte fileheader[] = {
928 0x00, 0x00, 0x00, 0x06,
931 (byte)(this->tickdiv >> 8), (byte)this->tickdiv,
933 fwrite(fileheader,
sizeof(fileheader), 1, f);
936 const byte trackheader[] = {
940 fwrite(trackheader,
sizeof(trackheader), 1, f);
942 size_t tracksizepos = ftell(f) - 4;
946 size_t nexttempoindex = 0;
947 for (
size_t bi = 0; bi < this->blocks.size(); bi++) {
949 TempoChange &nexttempo = this->tempos[nexttempoindex];
951 uint32 timediff = block.
ticktime - lasttime;
955 timediff = nexttempo.
ticktime - lasttime;
959 lasttime += timediff;
960 bool needtime =
false;
961 WriteVariableLen(f, timediff);
965 byte tempobuf[6] = { MIDIST_SMF_META, 0x51, 0x03, 0, 0, 0 };
966 tempobuf[3] = (nexttempo.
tempo & 0x00FF0000) >> 16;
967 tempobuf[4] = (nexttempo.
tempo & 0x0000FF00) >> 8;
968 tempobuf[5] = (nexttempo.
tempo & 0x000000FF);
969 fwrite(tempobuf,
sizeof(tempobuf), 1, f);
982 byte *dp = block.
data.data();
983 while (dp < block.
data.data() + block.
data.size()) {
991 switch (*dp & 0xF0) {
994 case MIDIST_POLYPRESS:
995 case MIDIST_CONTROLLER:
996 case MIDIST_PITCHBEND:
1000 case MIDIST_PROGCHG:
1001 case MIDIST_CHANPRESS:
1002 fwrite(dp, 1, 2, f);
1008 if (*dp == MIDIST_SYSEX) {
1009 fwrite(dp, 1, 1, f);
1011 byte *sysexend = dp;
1012 while (*sysexend != MIDIST_ENDSYSEX) sysexend++;
1013 ptrdiff_t sysexlen = sysexend - dp;
1014 WriteVariableLen(f, sysexlen);
1015 fwrite(dp, 1, sysexend - dp, f);
1027 static const byte track_end_marker[] = { 0x00, MIDIST_SMF_META, 0x2F, 0x00 };
1028 fwrite(&track_end_marker,
sizeof(track_end_marker), 1, f);
1031 size_t trackendpos = ftell(f);
1032 fseek(f, tracksizepos, SEEK_SET);
1033 uint32 tracksize = (uint32)(trackendpos - tracksizepos - 4);
1034 tracksize = TO_BE32(tracksize);
1035 fwrite(&tracksize, 4, 1, f);
1051 char filename[MAX_PATH];
1053 return std::string(filename);
1055 return std::string(filename);
1057 return std::string();
1063 char basename[MAX_PATH];
1065 const char *fnstart = strrchr(song.
filename, PATHSEPCHAR);
1066 if (fnstart ==
nullptr) {
1073 char *wp = basename;
1074 for (
const char *rp = fnstart; *rp !=
'\0'; rp++) {
1075 if (*rp !=
'.') *wp++ = *rp;
1080 char tempdirname[MAX_PATH];
1085 char output_filename[MAX_PATH];
1090 return std::string(output_filename);
1096 if (data ==
nullptr)
return std::string();
1101 return std::string();
1105 if (midifile.
WriteSMF(output_filename)) {
1106 return std::string(output_filename);
1108 return std::string();
1113 static bool CmdDumpSMF(byte argc,
char *argv[])
1124 if (_midifile_instance ==
nullptr) {
1125 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.");
1129 char fnbuf[MAX_PATH] = { 0 };
1136 if (_midifile_instance->
WriteSMF(fnbuf)) {
1145 static void RegisterConsoleMidiCommands()
1147 static bool registered =
false;
1154 MidiFile::MidiFile()
1156 RegisterConsoleMidiCommands();
1159 MidiFile::~MidiFile()
1161 if (_midifile_instance ==
this) {
1162 _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