Feature: Console command to dump decoded music to .mid file

pull/59/head
Niels Martin Hansen 6 years ago committed by Michael Lutz
parent 921101ed06
commit e2fa4b71c6

@ -18,10 +18,15 @@
#include "midi.h"
#include <algorithm>
#include "../console_func.h"
#include "../console_internal.h"
/* SMF reader based on description at: http://www.somascape.org/midi/tech/mfile.html */
static MidiFile *_midifile_instance = NULL;
/**
* Owning byte buffer readable as a stream.
* RAII-compliant to make teardown in error situations easier.
@ -414,6 +419,8 @@ bool MidiFile::ReadSMFHeader(FILE *file, SMFHeader &header)
*/
bool MidiFile::LoadFile(const char *filename)
{
_midifile_instance = this;
this->blocks.clear();
this->tempos.clear();
this->tickdiv = 0;
@ -794,6 +801,8 @@ const byte MpsMachine::programvelocities[128] = {
*/
bool MidiFile::LoadMpsData(const byte *data, size_t length)
{
_midifile_instance = this;
MpsMachine machine(data, length, *this);
return machine.PlayInto() && FixupMidiData(*this);
}
@ -830,7 +839,218 @@ void MidiFile::MoveFrom(MidiFile &other)
std::swap(this->tempos, other.tempos);
this->tickdiv = other.tickdiv;
_midifile_instance = this;
other.blocks.clear();
other.tempos.clear();
other.tickdiv = 0;
}
static void WriteVariableLen(FILE *f, uint32 value)
{
if (value < 0x7F) {
byte tb = value;
fwrite(&tb, 1, 1, f);
} else if (value < 0x3FFF) {
byte tb[2];
tb[1] = value & 0x7F; value >>= 7;
tb[0] = (value & 0x7F) | 0x80; value >>= 7;
fwrite(tb, 1, sizeof(tb), f);
} else if (value < 0x1FFFFF) {
byte tb[3];
tb[2] = value & 0x7F; value >>= 7;
tb[1] = (value & 0x7F) | 0x80; value >>= 7;
tb[0] = (value & 0x7F) | 0x80; value >>= 7;
fwrite(tb, 1, sizeof(tb), f);
} else if (value < 0x0FFFFFFF) {
byte tb[4];
tb[3] = value & 0x7F; value >>= 7;
tb[2] = (value & 0x7F) | 0x80; value >>= 7;
tb[1] = (value & 0x7F) | 0x80; value >>= 7;
tb[0] = (value & 0x7F) | 0x80; value >>= 7;
fwrite(tb, 1, sizeof(tb), f);
}
}
/**
* Write a Standard MIDI File containing the decoded music.
* @param filename Name of file to write to
* @return True if the file was written to completion
*/
bool MidiFile::WriteSMF(const char *filename)
{
FILE *f = FioFOpenFile(filename, "wb", Subdirectory::NO_DIRECTORY);
if (!f) {
return false;
}
/* SMF header */
const byte fileheader[] = {
'M', 'T', 'h', 'd', // block name
0x00, 0x00, 0x00, 0x06, // BE32 block length, always 6 bytes
0x00, 0x00, // writing format 0 (all in one track)
0x00, 0x01, // containing 1 track (BE16)
(byte)(this->tickdiv >> 8), (byte)this->tickdiv, // tickdiv in BE16
};
fwrite(fileheader, sizeof(fileheader), 1, f);
/* Track header */
const byte trackheader[] = {
'M', 'T', 'r', 'k', // block name
0, 0, 0, 0, // BE32 block length, unknown at this time
};
fwrite(trackheader, sizeof(trackheader), 1, f);
/* Determine position to write the actual track block length at */
size_t tracksizepos = ftell(f) - 4;
/* Write blocks in sequence */
uint32 lasttime = 0;
size_t nexttempoindex = 0;
for (size_t bi = 0; bi < this->blocks.size(); bi++) {
DataBlock &block = this->blocks[bi];
TempoChange &nexttempo = this->tempos[nexttempoindex];
uint32 timediff = block.ticktime - lasttime;
/* Check if there is a tempo change before this block */
if (nexttempo.ticktime < block.ticktime) {
timediff = nexttempo.ticktime - lasttime;
}
/* Write delta time for block */
lasttime += timediff;
bool needtime = false;
WriteVariableLen(f, timediff);
/* Write tempo change if there is one */
if (nexttempo.ticktime <= block.ticktime) {
byte tempobuf[6] = { MIDIST_SMF_META, 0x51, 0x03, 0, 0, 0 };
tempobuf[3] = (nexttempo.tempo & 0x00FF0000) >> 16;
tempobuf[4] = (nexttempo.tempo & 0x0000FF00) >> 8;
tempobuf[5] = (nexttempo.tempo & 0x000000FF);
fwrite(tempobuf, sizeof(tempobuf), 1, f);
nexttempoindex++;
needtime = true;
}
/* If a tempo change occurred between two blocks, rather than
* at start of this one, start over with delta time for the block. */
if (nexttempo.ticktime < block.ticktime) {
/* Start loop over at same index */
bi--;
continue;
}
/* Write each block data command */
byte *dp = block.data.Begin();
while (dp < block.data.End()) {
/* Always zero delta time inside blocks */
if (needtime) {
fputc(0, f);
}
needtime = true;
/* Check message type and write appropriate number of bytes */
switch (*dp & 0xF0) {
case MIDIST_NOTEOFF:
case MIDIST_NOTEON:
case MIDIST_POLYPRESS:
case MIDIST_CONTROLLER:
case MIDIST_PITCHBEND:
fwrite(dp, 1, 3, f);
dp += 3;
continue;
case MIDIST_PROGCHG:
case MIDIST_CHANPRESS:
fwrite(dp, 1, 2, f);
dp += 2;
continue;
}
/* Sysex needs to measure length and write that as well */
if (*dp == MIDIST_SYSEX) {
fwrite(dp, 1, 1, f);
dp++;
byte *sysexend = dp;
while (*sysexend != MIDIST_ENDSYSEX) sysexend++;
ptrdiff_t sysexlen = sysexend - dp;
WriteVariableLen(f, sysexlen);
fwrite(dp, 1, sysexend - dp, f);
dp = sysexend;
continue;
}
/* Fail for any other commands */
fclose(f);
return false;
}
}
/* End of track marker */
static const byte track_end_marker[] = { 0x00, MIDIST_SMF_META, 0x2F, 0x00 };
fwrite(&track_end_marker, sizeof(track_end_marker), 1, f);
/* Fill out the RIFF block length */
size_t trackendpos = ftell(f);
fseek(f, tracksizepos, SEEK_SET);
uint32 tracksize = (uint32)(trackendpos - tracksizepos - 4); // blindly assume we never produce files larger than 2 GB
tracksize = TO_BE32(tracksize);
fwrite(&tracksize, 4, 1, f);
fclose(f);
return true;
}
static bool CmdDumpSMF(byte argc, char *argv[])
{
if (argc == 0) {
IConsolePrint(CC_WARNING, "Write the current song to a Standard MIDI File. Usage: 'dumpsmf <filename>'");
return true;
}
if (argc != 2) {
IConsolePrint(CC_WARNING, "You must specify a filename to write MIDI data to.");
return false;
}
if (_midifile_instance == NULL) {
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.");
return false;
}
char fnbuf[MAX_PATH] = { 0 };
if (seprintf(fnbuf, lastof(fnbuf), "%s%s", FiosGetScreenshotDir(), argv[1]) >= (int)lengthof(fnbuf)) {
IConsolePrint(CC_ERROR, "Filename too long.");
return false;
}
IConsolePrintF(CC_INFO, "Dumping MIDI to: %s", fnbuf);
if (_midifile_instance->WriteSMF(fnbuf)) {
IConsolePrint(CC_INFO, "File written successfully.");
return true;
} else {
IConsolePrint(CC_ERROR, "An error occurred writing MIDI file.");
return false;
}
}
static void RegisterConsoleMidiCommands()
{
static bool registered = false;
if (!registered) {
IConsoleCmdRegister("dumpsmf", CmdDumpSMF);
registered = true;
}
}
MidiFile::MidiFile()
{
RegisterConsoleMidiCommands();
}
MidiFile::~MidiFile()
{
if (_midifile_instance == this) {
_midifile_instance = NULL;
}
}

@ -36,11 +36,16 @@ struct MidiFile {
std::vector<TempoChange> tempos; ///< list of tempo changes in file
uint16 tickdiv; ///< ticks per quarter note
MidiFile();
~MidiFile();
bool LoadFile(const char *filename);
bool LoadMpsData(const byte *data, size_t length);
bool LoadSong(const MusicSongInfo &song);
void MoveFrom(MidiFile &other);
bool WriteSMF(const char *filename);
static bool ReadSMFHeader(const char *filename, SMFHeader &header);
static bool ReadSMFHeader(FILE *file, SMFHeader &header);
};

Loading…
Cancel
Save