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) {
797 this->shouldplayflag =
true;
798 this->current_tempo = (int32)this->initial_tempo * 24 / 60;
803 AddMidiData(this->target.
blocks.back(), MIDIST_PROGCHG+9, 0x00);
808 for (uint32 tick = 0; tick < 100000; tick+=1) {
810 auto &block = this->target.
blocks.back();
823 100, 100, 100, 100, 100, 90, 100, 100, 100, 100, 100, 90, 100, 100, 100, 100,
824 100, 100, 85, 100, 100, 100, 100, 100, 100, 100, 100, 100, 90, 90, 110, 80,
825 100, 100, 100, 90, 70, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
826 100, 100, 90, 100, 100, 100, 100, 100, 100, 120, 100, 100, 100, 120, 100, 127,
827 100, 100, 90, 100, 100, 100, 100, 100, 100, 95, 100, 100, 100, 100, 100, 100,
828 100, 100, 100, 100, 100, 100, 100, 115, 100, 100, 100, 100, 100, 100, 100, 100,
829 100, 100, 100, 100, 100, 100, 100, 100, 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,
841 _midifile_instance =
this;
844 return machine.
PlayInto() && FixupMidiData(*
this);
854 size_t songdatalen = 0;
856 if (songdata !=
nullptr) {
857 bool result = this->
LoadMpsData(songdata, songdatalen);
879 _midifile_instance =
this;
886 static void WriteVariableLen(FILE *f, uint32 value)
890 fwrite(&tb, 1, 1, f);
891 }
else if (value <= 0x3FFF) {
893 tb[1] = value & 0x7F; value >>= 7;
894 tb[0] = (value & 0x7F) | 0x80; value >>= 7;
895 fwrite(tb, 1,
sizeof(tb), f);
896 }
else if (value <= 0x1FFFFF) {
898 tb[2] = value & 0x7F; value >>= 7;
899 tb[1] = (value & 0x7F) | 0x80; value >>= 7;
900 tb[0] = (value & 0x7F) | 0x80; value >>= 7;
901 fwrite(tb, 1,
sizeof(tb), f);
902 }
else if (value <= 0x0FFFFFFF) {
904 tb[3] = value & 0x7F; value >>= 7;
905 tb[2] = (value & 0x7F) | 0x80; value >>= 7;
906 tb[1] = (value & 0x7F) | 0x80; value >>= 7;
907 tb[0] = (value & 0x7F) | 0x80; value >>= 7;
908 fwrite(tb, 1,
sizeof(tb), f);
925 const byte fileheader[] = {
927 0x00, 0x00, 0x00, 0x06,
932 fwrite(fileheader,
sizeof(fileheader), 1, f);
935 const byte trackheader[] = {
939 fwrite(trackheader,
sizeof(trackheader), 1, f);
941 size_t tracksizepos = ftell(f) - 4;
945 size_t nexttempoindex = 0;
946 for (
size_t bi = 0; bi < this->
blocks.size(); bi++) {
950 uint32 timediff = block.
ticktime - lasttime;
954 timediff = nexttempo.
ticktime - lasttime;
958 lasttime += timediff;
959 bool needtime =
false;
960 WriteVariableLen(f, timediff);
964 byte tempobuf[6] = { MIDIST_SMF_META, 0x51, 0x03, 0, 0, 0 };
965 tempobuf[3] = (nexttempo.
tempo & 0x00FF0000) >> 16;
966 tempobuf[4] = (nexttempo.
tempo & 0x0000FF00) >> 8;
967 tempobuf[5] = (nexttempo.
tempo & 0x000000FF);
968 fwrite(tempobuf,
sizeof(tempobuf), 1, f);
981 byte *dp = block.
data.data();
982 while (dp < block.
data.data() + block.
data.size()) {
990 switch (*dp & 0xF0) {
993 case MIDIST_POLYPRESS:
994 case MIDIST_CONTROLLER:
995 case MIDIST_PITCHBEND:
1000 case MIDIST_CHANPRESS:
1001 fwrite(dp, 1, 2, f);
1007 if (*dp == MIDIST_SYSEX) {
1008 fwrite(dp, 1, 1, f);
1010 byte *sysexend = dp;
1011 while (*sysexend != MIDIST_ENDSYSEX) sysexend++;
1012 ptrdiff_t sysexlen = sysexend - dp;
1013 WriteVariableLen(f, sysexlen);
1014 fwrite(dp, 1, sysexend - dp, f);
1026 static const byte track_end_marker[] = { 0x00, MIDIST_SMF_META, 0x2F, 0x00 };
1027 fwrite(&track_end_marker,
sizeof(track_end_marker), 1, f);
1030 size_t trackendpos = ftell(f);
1031 fseek(f, tracksizepos, SEEK_SET);
1032 uint32 tracksize = (uint32)(trackendpos - tracksizepos - 4);
1033 tracksize = TO_BE32(tracksize);
1034 fwrite(&tracksize, 4, 1, f);
1051 if (!filename.empty())
return filename;
1053 if (!filename.empty())
return filename;
1055 return std::string();
1060 char basename[MAX_PATH];
1062 const char *fnstart = strrchr(song.
filename, PATHSEPCHAR);
1063 if (fnstart ==
nullptr) {
1070 char *wp = basename;
1071 for (
const char *rp = fnstart; *rp !=
'\0'; rp++) {
1072 if (*rp !=
'.') *wp++ = *rp;
1078 tempdirname += basename;
1082 std::string output_filename = tempdirname + std::to_string(song.
cat_index) +
".mid";
1086 return output_filename;
1092 if (data ==
nullptr)
return std::string();
1097 return std::string();
1101 if (midifile.
WriteSMF(output_filename.c_str())) {
1102 return output_filename;
1104 return std::string();
1109 static bool CmdDumpSMF(
byte argc,
char *argv[])
1112 IConsolePrint(
CC_HELP,
"Write the current song to a Standard MIDI File. Usage: 'dumpsmf <filename>'.");
1120 if (_midifile_instance ==
nullptr) {
1121 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.");
1125 char fnbuf[MAX_PATH] = { 0 };
1132 if (_midifile_instance->
WriteSMF(fnbuf)) {
1141 static void RegisterConsoleMidiCommands()
1143 static bool registered =
false;
1150 MidiFile::MidiFile()
1152 RegisterConsoleMidiCommands();
1155 MidiFile::~MidiFile()
1157 if (_midifile_instance ==
this) {
1158 _midifile_instance =
nullptr;