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 "../core/mem_func.hpp"
16 #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;
63 std::vector<byte> buf;
75 this->buf.resize(len);
76 if (fread(this->buf.data(), 1, len, file) == len) {
90 return !this->buf.empty();
99 return this->pos >= this->buf.size();
109 if (this->
IsEnd())
return false;
110 b = this->buf[this->pos++];
126 if (this->
IsEnd())
return false;
127 b = this->buf[this->pos++];
128 res = (res << 7) | (b & 0x7F);
141 if (this->
IsEnd())
return false;
142 if (this->buf.size() - this->pos < length)
return false;
143 std::copy(std::begin(this->buf) + this->pos, std::begin(this->buf) + this->pos + length, dest);
156 if (this->
IsEnd())
return false;
157 if (this->buf.size() - this->pos < length)
return false;
158 dest->
data.insert(dest->
data.end(), std::begin(this->buf) + this->pos, std::begin(this->buf) + this->pos + length);
170 if (this->
IsEnd())
return false;
171 if (this->buf.size() - this->pos < count)
return false;
183 if (count > this->pos)
return false;
189 static bool ReadTrackChunk(FILE *file,
MidiFile &target)
193 const byte magic[] = {
'M',
'T',
'r',
'k' };
194 if (fread(buf,
sizeof(magic), 1, file) != 1) {
197 if (memcmp(magic, buf,
sizeof(magic)) != 0) {
202 uint32_t chunk_length;
203 if (fread(&chunk_length, 1, 4, file) != 4) {
206 chunk_length = FROM_BE32(chunk_length);
209 if (!chunk.IsValid()) {
216 byte last_status = 0;
217 bool running_sysex =
false;
218 while (!chunk.IsEnd()) {
220 uint32_t deltatime = 0;
221 if (!chunk.ReadVariableLength(deltatime)) {
226 block = &target.
blocks.back();
231 if (!chunk.ReadByte(status)) {
235 if ((status & 0x80) == 0) {
239 status = last_status;
241 }
else if ((status & 0xF0) != 0xF0) {
243 last_status = status;
245 switch (status & 0xF0) {
248 case MIDIST_POLYPRESS:
249 case MIDIST_CONTROLLER:
250 case MIDIST_PITCHBEND:
252 block->
data.push_back(status);
253 if (!chunk.ReadDataBlock(block, 2)) {
258 case MIDIST_CHANPRESS:
260 block->
data.push_back(status);
261 if (!chunk.ReadByte(buf[0])) {
264 block->
data.push_back(buf[0]);
269 }
else if (status == MIDIST_SMF_META) {
271 if (!chunk.ReadByte(buf[0])) {
275 if (!chunk.ReadVariableLength(length)) {
281 return (length == 0);
284 if (length != 3)
return false;
285 if (!chunk.ReadBuffer(buf, 3))
return false;
290 if (!chunk.Skip(length)) {
295 }
else if (status == MIDIST_SYSEX || (status == MIDIST_SMF_ESCAPE && running_sysex)) {
298 if (!chunk.ReadVariableLength(length)) {
301 block->
data.push_back(0xF0);
302 if (!chunk.ReadDataBlock(block, length)) {
305 if (block->
data.back() != 0xF7) {
307 running_sysex =
true;
308 block->
data.push_back(0xF7);
310 running_sysex =
false;
312 }
else if (status == MIDIST_SMF_ESCAPE) {
315 if (!chunk.ReadVariableLength(length)) {
318 if (!chunk.ReadDataBlock(block, length)) {
339 bool TicktimeAscending(
const T &a,
const T &b)
341 return a.ticktime < b.ticktime;
344 static bool FixupMidiData(
MidiFile &target)
347 std::sort(target.
tempos.begin(), target.
tempos.end(), TicktimeAscending<MidiFile::TempoChange>);
348 std::sort(target.
blocks.begin(), target.
blocks.end(), TicktimeAscending<MidiFile::DataBlock>);
350 if (target.
tempos.empty()) {
358 std::vector<MidiFile::DataBlock> merged_blocks;
359 uint32_t last_ticktime = 0;
360 for (
size_t i = 0; i < target.
blocks.size(); i++) {
362 if (block.
data.empty()) {
364 }
else if (block.
ticktime > last_ticktime || merged_blocks.empty()) {
365 merged_blocks.push_back(block);
368 merged_blocks.back().data.insert(merged_blocks.back().data.end(), block.
data.begin(), block.
data.end());
371 std::swap(merged_blocks, target.
blocks);
375 uint32_t last_realtime = 0;
376 size_t cur_tempo = 0, cur_block = 0;
377 while (cur_block < target.
blocks.size()) {
383 int64_t tickdiff = block.
ticktime - last_ticktime;
385 last_realtime += uint32_t(tickdiff * tempo.
tempo / target.
tickdiv);
390 int64_t tickdiff = next_tempo.
ticktime - last_ticktime;
391 last_ticktime = next_tempo.
ticktime;
392 last_realtime += uint32_t(tickdiff * tempo.
tempo / target.
tickdiv);
409 if (!file)
return false;
426 if (fread(buffer,
sizeof(buffer), 1, file) != 1) {
431 const byte magic[] = {
'M',
'T',
'h',
'd', 0x00, 0x00, 0x00, 0x06 };
432 if (
MemCmpT(buffer, magic,
sizeof(magic)) != 0) {
437 header.format = (buffer[8] << 8) | buffer[9];
438 header.tracks = (buffer[10] << 8) | buffer[11];
439 header.tickdiv = (buffer[12] << 8) | buffer[13];
450 _midifile_instance =
this;
456 bool success =
false;
458 if (file ==
nullptr)
return false;
464 if (header.format != 0 && header.format != 1)
goto cleanup;
466 if ((header.tickdiv & 0x8000) != 0)
goto cleanup;
468 this->
tickdiv = header.tickdiv;
470 for (; header.tracks > 0; header.tracks--) {
471 if (!ReadTrackChunk(file, *
this)) {
476 success = FixupMidiData(*
this);
539 block.
data.push_back(b1);
540 block.
data.push_back(b2);
544 block.
data.push_back(b1);
545 block.
data.push_back(b2);
546 block.
data.push_back(b3);
563 this->initial_tempo = this->songdata[pos++];
566 loopmax = this->songdata[pos++];
567 for (loopidx = 0; loopidx < loopmax; loopidx++) {
572 this->segments.push_back(pos + 4);
573 pos += FROM_LE16(*(
const int16_t *)(this->songdata + pos));
578 loopmax = this->songdata[pos++];
579 for (loopidx = 0; loopidx < loopmax; loopidx++) {
583 byte ch = this->songdata[pos++];
584 this->channels[ch].
startpos = pos + 4;
585 pos += FROM_LE16(*(
const int16_t *)(this->songdata + pos));
599 b = this->songdata[pos++];
600 res = (res << 7) + (b & 0x7F);
610 for (
int ch = 0; ch < 16; ch++) {
611 Channel &chandata = this->channels[ch];
629 uint16_t newdelay = 0;
631 Channel &chandata = this->channels[channel];
635 b1 = this->songdata[chandata.
playpos++];
639 b1 = this->songdata[chandata.
playpos++];
641 chandata.
playpos = this->segments[b1];
662 this->shouldplayflag =
false;
671 b1 = this->songdata[chandata.
playpos++];
677 b2 = this->songdata[chandata.
playpos++];
683 velocity = (int16_t)b2 * 0x50;
688 b2 = (velocity / 128) & 0x00FF;
689 AddMidiData(outblock, MIDIST_NOTEON + channel, b1, b2);
692 AddMidiData(outblock, MIDIST_NOTEON + channel, b1, 0);
695 case MIDIST_CONTROLLER:
696 b2 = this->songdata[chandata.
playpos++];
697 if (b1 == MIDICT_MODE_MONO) {
703 }
else if (b1 == 0) {
707 this->current_tempo = ((int)b2) * 48 / 60;
710 }
else if (b1 == MIDICT_EFFECTS1) {
715 AddMidiData(outblock, MIDIST_CONTROLLER + channel, b1, b2);
723 this->shouldplayflag =
false;
730 if (b1 == 0x57 || b1 == 0x3F) {
733 AddMidiData(outblock, MIDIST_PROGCHG + channel, b1);
735 case MIDIST_PITCHBEND:
736 b2 = this->songdata[chandata.
playpos++];
737 AddMidiData(outblock, MIDIST_PITCHBEND + channel, b1, b2);
744 }
while (newdelay == 0);
756 if (this->tempo_ticks > 0) {
762 for (
int ch = 0; ch < 16; ch++) {
763 Channel &chandata = this->channels[ch];
765 if (chandata.
delay == 0) {
789 this->shouldplayflag =
true;
790 this->current_tempo = (int32_t)this->initial_tempo * 24 / 60;
795 AddMidiData(this->target.
blocks.back(), MIDIST_PROGCHG + 9, 0x00);
800 for (uint32_t tick = 0; tick < 100000; tick += 1) {
802 auto &block = this->target.
blocks.back();
815 100, 100, 100, 100, 100, 90, 100, 100, 100, 100, 100, 90, 100, 100, 100, 100,
816 100, 100, 85, 100, 100, 100, 100, 100, 100, 100, 100, 100, 90, 90, 110, 80,
817 100, 100, 100, 90, 70, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
818 100, 100, 90, 100, 100, 100, 100, 100, 100, 120, 100, 100, 100, 120, 100, 127,
819 100, 100, 90, 100, 100, 100, 100, 100, 100, 95, 100, 100, 100, 100, 100, 100,
820 100, 100, 100, 100, 100, 100, 100, 115, 100, 100, 100, 100, 100, 100, 100, 100,
821 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
822 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
833 _midifile_instance =
this;
836 return machine.
PlayInto() && FixupMidiData(*
this);
846 size_t songdatalen = 0;
848 if (songdata !=
nullptr) {
849 bool result = this->
LoadMpsData(songdata, songdatalen);
871 _midifile_instance =
this;
878 static void WriteVariableLen(FILE *f, uint32_t value)
882 fwrite(&tb, 1, 1, f);
883 }
else if (value <= 0x3FFF) {
885 tb[1] = value & 0x7F; value >>= 7;
886 tb[0] = (value & 0x7F) | 0x80; value >>= 7;
887 fwrite(tb, 1,
sizeof(tb), f);
888 }
else if (value <= 0x1FFFFF) {
890 tb[2] = value & 0x7F; value >>= 7;
891 tb[1] = (value & 0x7F) | 0x80; value >>= 7;
892 tb[0] = (value & 0x7F) | 0x80; value >>= 7;
893 fwrite(tb, 1,
sizeof(tb), f);
894 }
else if (value <= 0x0FFFFFFF) {
896 tb[3] = value & 0x7F; value >>= 7;
897 tb[2] = (value & 0x7F) | 0x80; value >>= 7;
898 tb[1] = (value & 0x7F) | 0x80; value >>= 7;
899 tb[0] = (value & 0x7F) | 0x80; value >>= 7;
900 fwrite(tb, 1,
sizeof(tb), f);
917 const byte fileheader[] = {
919 0x00, 0x00, 0x00, 0x06,
924 fwrite(fileheader,
sizeof(fileheader), 1, f);
927 const byte trackheader[] = {
931 fwrite(trackheader,
sizeof(trackheader), 1, f);
933 size_t tracksizepos = ftell(f) - 4;
936 uint32_t lasttime = 0;
937 size_t nexttempoindex = 0;
938 for (
size_t bi = 0; bi < this->
blocks.size(); bi++) {
942 uint32_t timediff = block.
ticktime - lasttime;
946 timediff = nexttempo.
ticktime - lasttime;
950 lasttime += timediff;
951 bool needtime =
false;
952 WriteVariableLen(f, timediff);
956 byte tempobuf[6] = { MIDIST_SMF_META, 0x51, 0x03, 0, 0, 0 };
957 tempobuf[3] = (nexttempo.
tempo & 0x00FF0000) >> 16;
958 tempobuf[4] = (nexttempo.
tempo & 0x0000FF00) >> 8;
959 tempobuf[5] = (nexttempo.
tempo & 0x000000FF);
960 fwrite(tempobuf,
sizeof(tempobuf), 1, f);
973 byte *dp = block.
data.data();
974 while (dp < block.
data.data() + block.
data.size()) {
982 switch (*dp & 0xF0) {
985 case MIDIST_POLYPRESS:
986 case MIDIST_CONTROLLER:
987 case MIDIST_PITCHBEND:
992 case MIDIST_CHANPRESS:
999 if (*dp == MIDIST_SYSEX) {
1000 fwrite(dp, 1, 1, f);
1002 byte *sysexend = dp;
1003 while (*sysexend != MIDIST_ENDSYSEX) sysexend++;
1004 ptrdiff_t sysexlen = sysexend - dp;
1005 WriteVariableLen(f, sysexlen);
1006 fwrite(dp, 1, sysexend - dp, f);
1018 static const byte track_end_marker[] = { 0x00, MIDIST_SMF_META, 0x2F, 0x00 };
1019 fwrite(&track_end_marker,
sizeof(track_end_marker), 1, f);
1022 size_t trackendpos = ftell(f);
1023 fseek(f, tracksizepos, SEEK_SET);
1024 uint32_t tracksize = (uint32_t)(trackendpos - tracksizepos - 4);
1025 tracksize = TO_BE32(tracksize);
1026 fwrite(&tracksize, 4, 1, f);
1043 if (!filename.empty())
return filename;
1045 if (!filename.empty())
return filename;
1047 return std::string();
1052 char basename[MAX_PATH];
1054 const char *fnstart = strrchr(song.
filename.c_str(), PATHSEPCHAR);
1055 if (fnstart ==
nullptr) {
1062 char *wp = basename;
1063 for (
const char *rp = fnstart; *rp !=
'\0'; rp++) {
1064 if (*rp !=
'.') *wp++ = *rp;
1070 tempdirname += basename;
1074 std::string output_filename = tempdirname + std::to_string(song.
cat_index) +
".mid";
1078 return output_filename;
1084 if (data ==
nullptr)
return std::string();
1089 return std::string();
1093 if (midifile.
WriteSMF(output_filename)) {
1094 return output_filename;
1096 return std::string();
1101 static bool CmdDumpSMF(
byte argc,
char *argv[])
1104 IConsolePrint(
CC_HELP,
"Write the current song to a Standard MIDI File. Usage: 'dumpsmf <filename>'.");
1112 if (_midifile_instance ==
nullptr) {
1113 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.");
1120 if (_midifile_instance->
WriteSMF(filename)) {
1129 static void RegisterConsoleMidiCommands()
1131 static bool registered =
false;
1138 MidiFile::MidiFile()
1140 RegisterConsoleMidiCommands();
1143 MidiFile::~MidiFile()
1145 if (_midifile_instance ==
this) {
1146 _midifile_instance =
nullptr;