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 uint8_t *MidiGetStandardSysexMessage(MidiSysexMessage msg,
size_t &length)
35 static uint8_t reset_gm_sysex[] = { 0xF0, 0x7E, 0x7F, 0x09, 0x01, 0xF7 };
36 static uint8_t reset_gs_sysex[] = { 0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7 };
37 static uint8_t reset_xg_sysex[] = { 0xF0, 0x43, 0x10, 0x4C, 0x00, 0x00, 0x7E, 0x00, 0xF7 };
38 static uint8_t 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<uint8_t> 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;
193 const uint8_t 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 uint8_t 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.has_value())
return false;
425 if (fread(buffer,
sizeof(buffer), 1, file) != 1) {
430 const uint8_t magic[] = {
'M',
'T',
'h',
'd', 0x00, 0x00, 0x00, 0x06 };
431 if (
MemCmpT(buffer, magic,
sizeof(magic)) != 0) {
436 header.format = (buffer[8] << 8) | buffer[9];
437 header.tracks = (buffer[10] << 8) | buffer[11];
438 header.tickdiv = (buffer[12] << 8) | buffer[13];
449 _midifile_instance =
this;
456 if (!file.has_value())
return false;
462 if (header.format != 0 && header.format != 1)
return false;
464 if ((header.tickdiv & 0x8000) != 0)
return false;
466 this->
tickdiv = header.tickdiv;
468 for (; header.tracks > 0; header.tracks--) {
469 if (!ReadTrackChunk(*file, *
this)) {
474 return FixupMidiData(*
this);
533 block.
data.push_back(b1);
534 block.
data.push_back(b2);
538 block.
data.push_back(b1);
539 block.
data.push_back(b2);
540 block.
data.push_back(b3);
557 this->initial_tempo = this->songdata[pos++];
560 loopmax = this->songdata[pos++];
561 for (loopidx = 0; loopidx < loopmax; loopidx++) {
566 this->segments.push_back(pos + 4);
567 pos += FROM_LE16(*(
const int16_t *)(this->songdata + pos));
572 loopmax = this->songdata[pos++];
573 for (loopidx = 0; loopidx < loopmax; loopidx++) {
577 uint8_t ch = this->songdata[pos++];
578 this->channels[ch].
startpos = pos + 4;
579 pos += FROM_LE16(*(
const int16_t *)(this->songdata + pos));
593 b = this->songdata[pos++];
594 res = (res << 7) + (b & 0x7F);
604 for (
int ch = 0; ch < 16; ch++) {
605 Channel &chandata = this->channels[ch];
623 uint16_t newdelay = 0;
625 Channel &chandata = this->channels[channel];
629 b1 = this->songdata[chandata.
playpos++];
633 b1 = this->songdata[chandata.
playpos++];
635 chandata.
playpos = this->segments[b1];
656 this->shouldplayflag =
false;
665 b1 = this->songdata[chandata.
playpos++];
671 b2 = this->songdata[chandata.
playpos++];
677 velocity = (int16_t)b2 * 0x50;
682 b2 = (velocity / 128) & 0x00FF;
683 AddMidiData(outblock, MIDIST_NOTEON + channel, b1, b2);
686 AddMidiData(outblock, MIDIST_NOTEON + channel, b1, 0);
689 case MIDIST_CONTROLLER:
690 b2 = this->songdata[chandata.
playpos++];
691 if (b1 == MIDICT_MODE_MONO) {
697 }
else if (b1 == 0) {
701 this->current_tempo = ((int)b2) * 48 / 60;
704 }
else if (b1 == MIDICT_EFFECTS1) {
709 AddMidiData(outblock, MIDIST_CONTROLLER + channel, b1, b2);
717 this->shouldplayflag =
false;
724 if (b1 == 0x57 || b1 == 0x3F) {
727 AddMidiData(outblock, MIDIST_PROGCHG + channel, b1);
729 case MIDIST_PITCHBEND:
730 b2 = this->songdata[chandata.
playpos++];
731 AddMidiData(outblock, MIDIST_PITCHBEND + channel, b1, b2);
738 }
while (newdelay == 0);
750 if (this->tempo_ticks > 0) {
756 for (
int ch = 0; ch < 16; ch++) {
757 Channel &chandata = this->channels[ch];
759 if (chandata.
delay == 0) {
783 this->shouldplayflag =
true;
784 this->current_tempo = (int32_t)this->initial_tempo * 24 / 60;
789 AddMidiData(this->target.
blocks.back(), MIDIST_PROGCHG + 9, 0x00);
794 for (uint32_t tick = 0; tick < 100000; tick += 1) {
796 auto &block = this->target.
blocks.back();
809 100, 100, 100, 100, 100, 90, 100, 100, 100, 100, 100, 90, 100, 100, 100, 100,
810 100, 100, 85, 100, 100, 100, 100, 100, 100, 100, 100, 100, 90, 90, 110, 80,
811 100, 100, 100, 90, 70, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
812 100, 100, 90, 100, 100, 100, 100, 100, 100, 120, 100, 100, 100, 120, 100, 127,
813 100, 100, 90, 100, 100, 100, 100, 100, 100, 95, 100, 100, 100, 100, 100, 100,
814 100, 100, 100, 100, 100, 100, 100, 115, 100, 100, 100, 100, 100, 100, 100, 100,
815 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
816 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,
827 _midifile_instance =
this;
830 return machine.
PlayInto() && FixupMidiData(*
this);
841 if (songdata.has_value()) {
842 bool result = this->
LoadMpsData(songdata->data(), songdata->size());
863 _midifile_instance =
this;
870 static void WriteVariableLen(
FileHandle &f, uint32_t value)
874 fwrite(&tb, 1, 1, f);
875 }
else if (value <= 0x3FFF) {
877 tb[1] = value & 0x7F; value >>= 7;
878 tb[0] = (value & 0x7F) | 0x80; value >>= 7;
879 fwrite(tb, 1,
sizeof(tb), f);
880 }
else if (value <= 0x1FFFFF) {
882 tb[2] = value & 0x7F; value >>= 7;
883 tb[1] = (value & 0x7F) | 0x80; value >>= 7;
884 tb[0] = (value & 0x7F) | 0x80; value >>= 7;
885 fwrite(tb, 1,
sizeof(tb), f);
886 }
else if (value <= 0x0FFFFFFF) {
888 tb[3] = value & 0x7F; value >>= 7;
889 tb[2] = (value & 0x7F) | 0x80; value >>= 7;
890 tb[1] = (value & 0x7F) | 0x80; value >>= 7;
891 tb[0] = (value & 0x7F) | 0x80; value >>= 7;
892 fwrite(tb, 1,
sizeof(tb), f);
904 if (!of.has_value())
return false;
908 const uint8_t fileheader[] = {
910 0x00, 0x00, 0x00, 0x06,
915 fwrite(fileheader,
sizeof(fileheader), 1, f);
918 const uint8_t trackheader[] = {
922 fwrite(trackheader,
sizeof(trackheader), 1, f);
924 size_t tracksizepos = ftell(f) - 4;
927 uint32_t lasttime = 0;
928 size_t nexttempoindex = 0;
929 for (
size_t bi = 0; bi < this->
blocks.size(); bi++) {
933 uint32_t timediff = block.
ticktime - lasttime;
937 timediff = nexttempo.
ticktime - lasttime;
941 lasttime += timediff;
942 bool needtime =
false;
943 WriteVariableLen(f, timediff);
947 uint8_t tempobuf[6] = { MIDIST_SMF_META, 0x51, 0x03, 0, 0, 0 };
948 tempobuf[3] = (nexttempo.
tempo & 0x00FF0000) >> 16;
949 tempobuf[4] = (nexttempo.
tempo & 0x0000FF00) >> 8;
950 tempobuf[5] = (nexttempo.
tempo & 0x000000FF);
951 fwrite(tempobuf,
sizeof(tempobuf), 1, f);
964 uint8_t *dp = block.
data.data();
965 while (dp < block.
data.data() + block.
data.size()) {
973 switch (*dp & 0xF0) {
976 case MIDIST_POLYPRESS:
977 case MIDIST_CONTROLLER:
978 case MIDIST_PITCHBEND:
983 case MIDIST_CHANPRESS:
990 if (*dp == MIDIST_SYSEX) {
993 uint8_t *sysexend = dp;
994 while (*sysexend != MIDIST_ENDSYSEX) sysexend++;
995 ptrdiff_t sysexlen = sysexend - dp;
996 WriteVariableLen(f, sysexlen);
997 fwrite(dp, 1, sysexend - dp, f);
1008 static const uint8_t track_end_marker[] = { 0x00, MIDIST_SMF_META, 0x2F, 0x00 };
1009 fwrite(&track_end_marker,
sizeof(track_end_marker), 1, f);
1012 size_t trackendpos = ftell(f);
1013 fseek(f, tracksizepos, SEEK_SET);
1014 uint32_t tracksize = (uint32_t)(trackendpos - tracksizepos - 4);
1015 tracksize = TO_BE32(tracksize);
1016 fwrite(&tracksize, 4, 1, f);
1032 if (!filename.empty())
return filename;
1034 if (!filename.empty())
return filename;
1036 return std::string();
1041 char basename[MAX_PATH];
1043 const char *fnstart = strrchr(song.
filename.c_str(), PATHSEPCHAR);
1044 if (fnstart ==
nullptr) {
1051 char *wp = basename;
1052 for (
const char *rp = fnstart; *rp !=
'\0'; rp++) {
1053 if (*rp !=
'.') *wp++ = *rp;
1059 tempdirname += basename;
1063 std::string output_filename = tempdirname + std::to_string(song.
cat_index) +
".mid";
1067 return output_filename;
1071 if (!songdata.has_value())
return std::string();
1074 if (!midifile.
LoadMpsData(songdata->data(), songdata->size())) {
1075 return std::string();
1078 if (midifile.
WriteSMF(output_filename)) {
1079 return output_filename;
1081 return std::string();
1086 static bool CmdDumpSMF(uint8_t argc,
char *argv[])
1089 IConsolePrint(
CC_HELP,
"Write the current song to a Standard MIDI File. Usage: 'dumpsmf <filename>'.");
1097 if (_midifile_instance ==
nullptr) {
1098 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.");
1105 if (_midifile_instance->
WriteSMF(filename)) {
1114 static void RegisterConsoleMidiCommands()
1116 static bool registered =
false;
1123 MidiFile::MidiFile()
1125 RegisterConsoleMidiCommands();
1128 MidiFile::~MidiFile()
1130 if (_midifile_instance ==
this) {
1131 _midifile_instance =
nullptr;