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"
18 #include "../console_func.h"
19 #include "../console_internal.h"
24 static MidiFile *_midifile_instance =
nullptr;
32 const byte *MidiGetStandardSysexMessage(MidiSysexMessage msg,
size_t &length)
34 static byte reset_gm_sysex[] = { 0xF0, 0x7E, 0x7F, 0x09, 0x01, 0xF7 };
35 static byte reset_gs_sysex[] = { 0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7 };
36 static byte reset_xg_sysex[] = { 0xF0, 0x43, 0x10, 0x4C, 0x00, 0x00, 0x7E, 0x00, 0xF7 };
37 static byte roland_reverb_sysex[] = { 0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x01, 0x30, 0x02, 0x04, 0x00, 0x40, 0x40, 0x00, 0x00, 0x09, 0xF7 };
40 case MidiSysexMessage::ResetGM:
42 return reset_gm_sysex;
43 case MidiSysexMessage::ResetGS:
45 return reset_gs_sysex;
46 case MidiSysexMessage::ResetXG:
48 return reset_xg_sysex;
49 case MidiSysexMessage::RolandSetReverb:
50 length =
lengthof(roland_reverb_sysex);
51 return roland_reverb_sysex;
75 this->buf = MallocT<byte>(len);
76 if (fread(this->buf, 1, len, file) == len) {
99 return this->buflen > 0;
108 return this->pos >= this->buflen;
118 if (this->
IsEnd())
return false;
119 b = this->buf[this->pos++];
135 if (this->
IsEnd())
return false;
136 b = this->buf[this->pos++];
137 res = (res << 7) | (b & 0x7F);
150 if (this->
IsEnd())
return false;
151 if (this->buflen - this->pos < length)
return false;
152 memcpy(dest, this->buf + this->pos, length);
165 if (this->
IsEnd())
return false;
166 if (this->buflen - this->pos < length)
return false;
167 dest->
data.insert(dest->
data.end(), this->buf + this->pos, this->buf + this->pos + length);
179 if (this->
IsEnd())
return false;
180 if (this->buflen - this->pos < count)
return false;
192 if (count > this->pos)
return false;
198 static bool ReadTrackChunk(FILE *file,
MidiFile &target)
202 const byte magic[] = {
'M',
'T',
'r',
'k' };
203 if (fread(buf,
sizeof(magic), 1, file) != 1) {
206 if (memcmp(magic, buf,
sizeof(magic)) != 0) {
212 if (fread(&chunk_length, 1, 4, file) != 4) {
215 chunk_length = FROM_BE32(chunk_length);
218 if (!chunk.IsValid()) {
225 byte last_status = 0;
226 bool running_sysex =
false;
227 while (!chunk.IsEnd()) {
229 uint32 deltatime = 0;
230 if (!chunk.ReadVariableLength(deltatime)) {
235 block = &target.
blocks.back();
240 if (!chunk.ReadByte(status)) {
244 if ((status & 0x80) == 0) {
248 status = last_status;
250 }
else if ((status & 0xF0) != 0xF0) {
252 last_status = status;
254 switch (status & 0xF0) {
257 case MIDIST_POLYPRESS:
258 case MIDIST_CONTROLLER:
259 case MIDIST_PITCHBEND:
261 block->
data.push_back(status);
262 if (!chunk.ReadDataBlock(block, 2)) {
267 case MIDIST_CHANPRESS:
269 block->
data.push_back(status);
270 if (!chunk.ReadByte(buf[0])) {
273 block->
data.push_back(buf[0]);
278 }
else if (status == MIDIST_SMF_META) {
280 if (!chunk.ReadByte(buf[0])) {
284 if (!chunk.ReadVariableLength(length)) {
290 return (length == 0);
293 if (length != 3)
return false;
294 if (!chunk.ReadBuffer(buf, 3))
return false;
299 if (!chunk.Skip(length)) {
304 }
else if (status == MIDIST_SYSEX || (status == MIDIST_SMF_ESCAPE && running_sysex)) {
307 if (!chunk.ReadVariableLength(length)) {
310 block->
data.push_back(0xF0);
311 if (!chunk.ReadDataBlock(block, length)) {
314 if (block->
data.back() != 0xF7) {
316 running_sysex =
true;
317 block->
data.push_back(0xF7);
319 running_sysex =
false;
321 }
else if (status == MIDIST_SMF_ESCAPE) {
324 if (!chunk.ReadVariableLength(length)) {
327 if (!chunk.ReadDataBlock(block, length)) {
348 bool TicktimeAscending(
const T &a,
const T &b)
350 return a.ticktime < b.ticktime;
353 static bool FixupMidiData(
MidiFile &target)
356 std::sort(target.
tempos.begin(), target.
tempos.end(), TicktimeAscending<MidiFile::TempoChange>);
357 std::sort(target.
blocks.begin(), target.
blocks.end(), TicktimeAscending<MidiFile::DataBlock>);
359 if (target.
tempos.size() == 0) {
367 std::vector<MidiFile::DataBlock> merged_blocks;
368 uint32 last_ticktime = 0;
369 for (
size_t i = 0; i < target.
blocks.size(); i++) {
371 if (block.
data.size() == 0) {
373 }
else if (block.
ticktime > last_ticktime || merged_blocks.size() == 0) {
374 merged_blocks.push_back(block);
377 merged_blocks.back().data.insert(merged_blocks.back().data.end(), block.
data.begin(), block.
data.end());
380 std::swap(merged_blocks, target.
blocks);
384 uint32 last_realtime = 0;
385 size_t cur_tempo = 0, cur_block = 0;
386 while (cur_block < target.
blocks.size()) {
392 int64 tickdiff = block.
ticktime - last_ticktime;
394 last_realtime += uint32(tickdiff * tempo.
tempo / target.
tickdiv);
399 int64 tickdiff = next_tempo.
ticktime - last_ticktime;
400 last_ticktime = next_tempo.
ticktime;
401 last_realtime += uint32(tickdiff * tempo.
tempo / target.
tickdiv);
418 if (!file)
return false;
435 if (fread(buffer,
sizeof(buffer), 1, file) != 1) {
440 const byte magic[] = {
'M',
'T',
'h',
'd', 0x00, 0x00, 0x00, 0x06 };
441 if (
MemCmpT(buffer, magic,
sizeof(magic)) != 0) {
446 header.format = (buffer[8] << 8) | buffer[9];
447 header.tracks = (buffer[10] << 8) | buffer[11];
448 header.tickdiv = (buffer[12] << 8) | buffer[13];
459 _midifile_instance =
this;
465 bool success =
false;
467 if (file ==
nullptr)
return false;
473 if (header.format != 0 && header.format != 1)
goto cleanup;
475 if ((header.tickdiv & 0x8000) != 0)
goto cleanup;
477 this->
tickdiv = header.tickdiv;
479 for (; header.tracks > 0; header.tracks--) {
480 if (!ReadTrackChunk(file, *
this)) {
485 success = FixupMidiData(*
this);
548 block.
data.push_back(b1);
549 block.
data.push_back(b2);
553 block.
data.push_back(b1);
554 block.
data.push_back(b2);
555 block.
data.push_back(b3);
572 this->initial_tempo = this->songdata[pos++];
575 loopmax = this->songdata[pos++];
576 for (loopidx = 0; loopidx < loopmax; loopidx++) {
581 this->segments.push_back(pos + 4);
582 pos += FROM_LE16(*(
const int16 *)(this->songdata + pos));
587 loopmax = this->songdata[pos++];
588 for (loopidx = 0; loopidx < loopmax; loopidx++) {
592 byte ch = this->songdata[pos++];
593 this->channels[ch].
startpos = pos + 4;
594 pos += FROM_LE16(*(
const int16 *)(this->songdata + pos));
608 b = this->songdata[pos++];
609 res = (res << 7) + (b & 0x7F);
619 for (
int ch = 0; ch < 16; ch++) {
620 Channel &chandata = this->channels[ch];
640 Channel &chandata = this->channels[channel];
644 b1 = this->songdata[chandata.
playpos++];
648 b1 = this->songdata[chandata.
playpos++];
650 chandata.
playpos = this->segments[b1];
671 this->shouldplayflag =
false;
680 b1 = this->songdata[chandata.
playpos++];
686 b2 = this->songdata[chandata.
playpos++];
692 velocity = (int16)b2 * 0x50;
697 b2 = (velocity / 128) & 0x00FF;
698 AddMidiData(outblock, MIDIST_NOTEON + channel, b1, b2);
701 AddMidiData(outblock, MIDIST_NOTEON + channel, b1, 0);
704 case MIDIST_CONTROLLER:
705 b2 = this->songdata[chandata.
playpos++];
706 if (b1 == MIDICT_MODE_MONO) {
712 }
else if (b1 == 0) {
716 this->current_tempo = ((int)b2) * 48 / 60;
719 }
else if (b1 == MIDICT_EFFECTS1) {
724 AddMidiData(outblock, MIDIST_CONTROLLER + channel, b1, b2);
732 this->shouldplayflag =
false;
739 if (b1 == 0x57 || b1 == 0x3F) {
742 AddMidiData(outblock, MIDIST_PROGCHG + channel, b1);
744 case MIDIST_PITCHBEND:
745 b2 = this->songdata[chandata.
playpos++];
746 AddMidiData(outblock, MIDIST_PITCHBEND + channel, b1, b2);
753 }
while (newdelay == 0);
765 if (this->tempo_ticks > 0) {
771 for (
int ch = 0; ch < 16; ch++) {
772 Channel &chandata = this->channels[ch];
774 if (chandata.
delay == 0) {
798 this->shouldplayflag =
true;
799 this->current_tempo = (int32)this->initial_tempo * 24 / 60;
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();
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);
855 size_t songdatalen = 0;
857 if (songdata !=
nullptr) {
858 bool result = this->
LoadMpsData(songdata, songdatalen);
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,
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++) {
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);
1052 if (!filename.empty())
return filename;
1054 if (!filename.empty())
return filename;
1056 return std::string();
1061 char basename[MAX_PATH];
1063 const char *fnstart = strrchr(song.
filename, PATHSEPCHAR);
1064 if (fnstart ==
nullptr) {
1071 char *wp = basename;
1072 for (
const char *rp = fnstart; *rp !=
'\0'; rp++) {
1073 if (*rp !=
'.') *wp++ = *rp;
1079 tempdirname += basename;
1083 std::string output_filename = tempdirname + std::to_string(song.
cat_index) +
".mid";
1087 return output_filename;
1093 if (data ==
nullptr)
return std::string();
1098 return std::string();
1102 if (midifile.
WriteSMF(output_filename.c_str())) {
1103 return output_filename;
1105 return std::string();
1110 static bool CmdDumpSMF(
byte argc,
char *argv[])
1113 IConsolePrint(
CC_HELP,
"Write the current song to a Standard MIDI File. Usage: 'dumpsmf <filename>'.");
1121 if (_midifile_instance ==
nullptr) {
1122 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.");
1126 char fnbuf[MAX_PATH] = { 0 };
1133 if (_midifile_instance->
WriteSMF(fnbuf)) {
1142 static void RegisterConsoleMidiCommands()
1144 static bool registered =
false;
1151 MidiFile::MidiFile()
1153 RegisterConsoleMidiCommands();
1156 MidiFile::~MidiFile()
1158 if (_midifile_instance ==
this) {
1159 _midifile_instance =
nullptr;