AUDIO: Added dump-midi mechanism

This mechanism is enabled by '--dump-midi' command line parameter.
The midi events are printed to screen, and dumped to 'dump.mid' file.
This commit is contained in:
Zvika Haramaty 2020-02-19 19:51:53 +02:00 committed by Filippos Karapetis
parent 1fb3d61b56
commit 3b4810aab4
22 changed files with 243 additions and 23 deletions

View file

@ -1428,6 +1428,8 @@ arguments -- see the next section.
supported by some MIDI drivers)
--multi-midi Enable combination of AdLib and native MIDI
--native-mt32 True Roland MT-32 (disable GM emulation)
--dump-midi Dumps MIDI events to 'dump.mid', until quitting from game
(if file already exists, it will be overwritten)
--enable-gs Enable Roland GS mode for MIDI playback
--output-rate=RATE Select output sample rate in Hz (e.g. 22050)
--opl-driver=DRIVER Select AdLib (OPL) emulator (db, mame, nuked)

View file

@ -935,7 +935,7 @@ public:
int open();
void close();
void send(uint32 b);
void send(uint32 b) override;
void send(byte channel, uint32 b); // Supports higher than channel 15
uint32 property(int prop, uint32 param);
bool isOpen() const { return _isOpen; }

View file

@ -28,6 +28,7 @@
#include "common/textconsole.h"
#include "common/translation.h"
#include "common/util.h"
#include "common/file.h"
#include "gui/message.h"
#include "audio/mididrv.h"
#include "audio/musicplugin.h"
@ -441,3 +442,128 @@ void MidiDriver::sendGMReset() {
sysEx(resetSysEx, sizeof(resetSysEx));
g_system->delayMillis(100);
}
void MidiDriver_BASE::midiDumpInit() {
g_system->displayMessageOnOSD(_("Starting MIDI dump"));
_midiDumpCache.clear();
_prevMillis = g_system->getMillis(true);
}
int MidiDriver_BASE::midiDumpVarLength(const uint32 &delta) {
// MIDI file format has a very strange representation - "Variable Length Values"
// we're using only *7* bits of each byte for the data
// the MSB bit is 1 for all bytes, except the last one
if (delta <= 127) {
// "Variable Length Values" of 1 byte
debugN("0x%02x", delta);
_midiDumpCache.push_back(delta);
return 1;
} else {
// "Variable Length Values" of 2 bytes
// theoretically, "Variable Length Values" can have more than 2 bytes, but it won't happen in our use case
byte msb = delta / 128;
msb |= 0x80;
byte lsb = delta % 128;
debugN("0x%02x,0x%02x", msb, lsb);
_midiDumpCache.push_back(msb);
_midiDumpCache.push_back(lsb);
return 2;
}
}
void MidiDriver_BASE::midiDumpDelta() {
uint32 millis = g_system->getMillis(true);
uint32 delta = millis - _prevMillis;
_prevMillis = millis;
debugN("MIDI : delta(");
int varLength = midiDumpVarLength(delta);
if (varLength == 1)
debugN("),\t ");
else
debugN("), ");
}
void MidiDriver_BASE::midiDumpDo(uint32 b) {
const byte status = b & 0xff;
const byte firstOp = (b >> 8) & 0xff;
const byte secondOp = (b >> 16) & 0xff;
midiDumpDelta();
debugN("message(0x%02x 0x%02x", status, firstOp);
_midiDumpCache.push_back(status);
_midiDumpCache.push_back(firstOp);
if (status < 0xc0 || status > 0xdf) {
_midiDumpCache.push_back(secondOp);
debug(" 0x%02x)", secondOp);
} else
debug(")");
}
void MidiDriver_BASE::midiDumpSysEx(const byte *msg, uint16 length) {
midiDumpDelta();
_midiDumpCache.push_back(0xf0);
debugN("0xf0, length(");
midiDumpVarLength(length + 1); // +1 because of closing 0xf7
debugN("), sysex[");
for (int i = 0; i < length; i++) {
debugN("0x%x, ", msg[i]);
_midiDumpCache.push_back(msg[i]);
}
debug("0xf7]\t\t");
_midiDumpCache.push_back(0xf7);
}
void MidiDriver_BASE::midiDumpFinish() {
Common::DumpFile *midiDumpFile = new Common::DumpFile();
midiDumpFile->open("dump.mid");
midiDumpFile->write("MThd\0\0\0\x6\0\x1\0\x2", 12); // standard MIDI file header, with two tracks
midiDumpFile->write("\x1\xf4", 2); // division - 500 ticks per beat, i.e. a quarter note. Each tick is 1ms
midiDumpFile->write("MTrk", 4); // start of first track - doesn't contain real data, it's just common practice to use two tracks
midiDumpFile->writeUint32BE(4); // first track size
midiDumpFile->write("\0\xff\x2f\0", 4); // meta event - end of track
midiDumpFile->write("MTrk", 4); // start of second track
midiDumpFile->writeUint32BE(_midiDumpCache.size() + 4); // track size (+4 because of the 'end of track' event)
midiDumpFile->write(_midiDumpCache.data(), _midiDumpCache.size());
midiDumpFile->write("\0\xff\x2f\0", 4); // meta event - end of track
midiDumpFile->finalize();
midiDumpFile->close();
const char msg[] = "Ending MIDI dump, created 'dump.mid'";
g_system->displayMessageOnOSD(_(msg)); //TODO: why it doesn't appear?
debug(_(msg));
}
MidiDriver_BASE::MidiDriver_BASE() {
_midiDumpEnable = ConfMan.getBool("dump_midi");
if (_midiDumpEnable) {
midiDumpInit();
}
}
MidiDriver_BASE::~MidiDriver_BASE() {
if (_midiDumpEnable && !_midiDumpCache.empty()) {
midiDumpFinish();
}
}
void MidiDriver_BASE::send(byte status, byte firstOp, byte secondOp) {
send(status | ((uint32)firstOp << 8) | ((uint32)secondOp << 16));
}
void MidiDriver::midiDriverCommonSend(uint32 b) {
if (_midiDumpEnable) {
midiDumpDo(b);
}
}
void MidiDriver::midiDriverCommonSysEx(const byte *msg, uint16 length) {
if (_midiDumpEnable) {
midiDumpSysEx(msg, length);
}
}

View file

@ -26,6 +26,7 @@
#include "common/scummsys.h"
#include "common/str.h"
#include "common/timer.h"
#include "common/array.h"
class MidiChannel;
@ -86,7 +87,9 @@ enum MidiDriverFlags {
*/
class MidiDriver_BASE {
public:
virtual ~MidiDriver_BASE() { }
MidiDriver_BASE();
virtual ~MidiDriver_BASE();
/**
* Output a packed midi command to the midi stream.
@ -96,6 +99,7 @@ public:
*/
virtual void send(uint32 b) = 0;
/**
* Output a midi command to the midi stream. Convenience wrapper
* around the usual 'packed' send method.
@ -103,10 +107,8 @@ public:
* Do NOT use this for sysEx transmission; instead, use the sysEx()
* method below.
*/
void send(byte status, byte firstOp, byte secondOp) {
send(status | ((uint32)firstOp << 8) | ((uint32)secondOp << 16));
}
void send(byte status, byte firstOp, byte secondOp);
/**
* Transmit a sysEx to the midi device.
*
@ -121,6 +123,39 @@ public:
// TODO: Document this.
virtual void metaEvent(byte type, byte *data, uint16 length) { }
protected:
/**
* Enables midi dumping to a 'dump.mid' file and to debug messages on screen
* It's set by '--dump-midi' command line parameter
*/
bool _midiDumpEnable;
/** Used for MIDI dumping delta calculation */
uint32 _prevMillis;
/** Stores all MIDI events, will be written to disk after an engine quits */
Common::Array<byte> _midiDumpCache;
/** Initialize midi dumping mechanism, called only if enabled */
void midiDumpInit();
/** Handles MIDI file variable length dumping */
int midiDumpVarLength(const uint32 &delta);
/** Handles MIDI file time delta dumping */
void midiDumpDelta();
/** Performs dumping of MIDI commands, called only if enabled */
void midiDumpDo(uint32 b);
/** Performs dumping of MIDI SysEx commands, called only if enabled */
void midiDumpSysEx(const byte *msg, uint16 length);
/** Writes the captured MIDI events to disk, called only if enabled */
void midiDumpFinish();
};
/**
@ -166,6 +201,13 @@ public:
/** Get the device description string matching the given device handle and the given type. */
static Common::String getDeviceString(DeviceHandle handle, DeviceStringType type);
/** Common operations to be done by all drivers on start of send */
void midiDriverCommonSend(uint32 b);
/** Common operations to be done by all drivers on start of sysEx */
void midiDriverCommonSysEx(const byte *msg, uint16 length);
private:
// If detectDevice() detects MT32 and we have a preferred MT32 device
// we use this to force getMusicType() to return MT_MT32 so that we don't

View file

@ -115,7 +115,7 @@ public:
bool hasNativeMT32() const { return _nativeMT32; }
// MidiDriver_BASE implementation
virtual void send(uint32 b);
virtual void send(uint32 b) override;
virtual void metaEvent(byte type, byte *data, uint16 length);
protected:

View file

@ -123,7 +123,7 @@ public:
// MidiDriver
int open();
void close();
void send(uint32 b);
void send(uint32 b) override;
MidiChannel *allocateChannel() { return NULL; }
MidiChannel *getPercussionChannel() { return NULL; }

View file

@ -79,7 +79,7 @@ public:
void close();
bool isOpen() const { return _isOpen; }
void send(uint32 b);
void send(uint32 b) override;
MidiChannel *allocateChannel() {
if (_driver)

View file

@ -70,7 +70,7 @@ public:
virtual int open();
virtual bool isOpen() const;
virtual void close();
virtual void send(uint32 b);
virtual void send(uint32 b) override;
virtual void sysEx(const byte *msg, uint16 length);
virtual void setTimerCallback(void *timerParam,
Common::TimerManager::TimerProc timerProc);

View file

@ -64,7 +64,7 @@ public:
int open();
void close();
void send(uint32 b);
void send(uint32 b) override;
MidiChannel *allocateChannel();
MidiChannel *getPercussionChannel();
@ -227,6 +227,8 @@ void MidiDriver_FluidSynth::close() {
}
void MidiDriver_FluidSynth::send(uint32 b) {
midiDriverCommonSend(b);
//byte param3 = (byte) ((b >> 24) & 0xFF);
uint param2 = (byte) ((b >> 16) & 0xFF);
uint param1 = (byte) ((b >> 8) & 0xFF);

View file

@ -122,7 +122,7 @@ public:
int open();
void close();
void send(uint32 b);
void send(uint32 b) override;
void setPitchBendRange(byte channel, uint range);
void sysEx(const byte *msg, uint16 length);
@ -227,6 +227,8 @@ int MidiDriver_MT32::open() {
}
void MidiDriver_MT32::send(uint32 b) {
midiDriverCommonSend(b);
Common::StackLock lock(_mutex);
_service.playMsg(b);
}
@ -243,6 +245,7 @@ void MidiDriver_MT32::setPitchBendRange(byte channel, uint range) {
}
void MidiDriver_MT32::sysEx(const byte *msg, uint16 length) {
midiDriverCommonSysEx(msg, length);
if (msg[0] == 0xf0) {
Common::StackLock lock(_mutex);
_service.playSysex(msg, length);

View file

@ -73,7 +73,7 @@ public:
int open();
bool isOpen() const { return _isOpen; }
void close();
void send(uint32 b);
void send(uint32 b) override;
void sysEx(const byte *msg, uint16 length);
private:
@ -186,6 +186,8 @@ void MidiDriver_ALSA::send(uint32 b) {
return;
}
midiDriverCommonSend(b);
unsigned int midiCmd[4];
ev.type = SND_SEQ_EVENT_OSS;
@ -268,6 +270,8 @@ void MidiDriver_ALSA::sysEx(const byte *msg, uint16 length) {
assert(length + 2 <= ARRAYSIZE(buf));
midiDriverCommonSysEx(msg, length);
// Add SysEx frame
buf[0] = 0xF0;
memcpy(buf + 1, msg, length);

View file

@ -50,7 +50,7 @@ public:
int open();
bool isOpen() const { return _isOpen; }
void close();
void send(uint32 b);
void send(uint32 b) override;
void sysEx(const byte *msg, uint16 length);
private:
@ -122,6 +122,8 @@ void MidiDriver_CAMD::send(uint32 b) {
return;
}
midiDriverCommonSend(b);
ULONG data = READ_LE_UINT32(&b);
_ICamd->PutMidi(_midi_link, data);
}

View file

@ -99,7 +99,7 @@ public:
int open();
bool isOpen() const { return _auGraph != 0; }
void close();
void send(uint32 b);
void send(uint32 b) override;
void sysEx(const byte *msg, uint16 length);
private:
@ -280,6 +280,8 @@ void MidiDriver_CORE::close() {
void MidiDriver_CORE::send(uint32 b) {
assert(isOpen());
midiDriverCommonSend(b);
byte status_byte = (b & 0x000000FF);
byte first_byte = (b & 0x0000FF00) >> 8;
byte second_byte = (b & 0x00FF0000) >> 16;

View file

@ -58,7 +58,7 @@ public:
int open();
bool isOpen() const { return mOutPort != 0 && mDest != 0; }
void close();
void send(uint32 b);
void send(uint32 b) override;
void sysEx(const byte *msg, uint16 length);
private:
@ -118,6 +118,8 @@ void MidiDriver_CoreMIDI::close() {
void MidiDriver_CoreMIDI::send(uint32 b) {
assert(isOpen());
midiDriverCommonSend(b);
// Extract the MIDI data
byte status_byte = (b & 0x000000FF);
byte first_byte = (b & 0x0000FF00) >> 8;

View file

@ -58,7 +58,7 @@ public:
int open();
bool isOpen() const { return _isOpen; }
void close();
void send(uint32 b);
void send(uint32 b) override;
void sysEx(const byte *msg, uint16 length);
private:
@ -135,6 +135,8 @@ void MidiDriver_DMEDIA::close() {
}
void MidiDriver_DMEDIA::send(uint32 b) {
midiDriverCommonSend(b);
MDevent event;
byte status_byte = (b & 0x000000FF);
byte first_byte = (b & 0x0000FF00) >> 8;
@ -177,6 +179,8 @@ void MidiDriver_DMEDIA::sysEx (const byte *msg, uint16 length) {
assert(length + 2 <= 256);
midiDriverCommonSysEx(msg, length);
memcpy(buf, msg, length);
buf[length] = MD_EOX;
event.sysexmsg = buf;

View file

@ -57,7 +57,7 @@ public:
int open();
bool isOpen() const { return _isOpen; }
void close();
void send(uint32 b);
void send(uint32 b) override;
void sysEx(const byte *msg, uint16 length);
private:
@ -109,6 +109,8 @@ void MidiDriver_SEQ::close() {
}
void MidiDriver_SEQ::send(uint32 b) {
midiDriverCommonSend(b);
unsigned char buf[256];
int position = 0;
@ -157,6 +159,8 @@ void MidiDriver_SEQ::sysEx(const byte *msg, uint16 length) {
assert(length + 2 <= 266);
midiDriverCommonSysEx(msg, length);
buf[position++] = SEQ_MIDIPUTC;
buf[position++] = 0xF0;
buf[position++] = _device_num;

View file

@ -47,7 +47,7 @@ public:
int open();
bool isOpen() const { return hdl != NULL; }
void close();
void send(uint32 b);
void send(uint32 b) override;
void sysEx(const byte *msg, uint16 length);
private:
@ -80,6 +80,8 @@ void MidiDriver_Sndio::send(uint32 b) {
unsigned char buf[4];
unsigned int len;
midiDriverCommonSend(b);
if (!hdl)
return;
buf[0] = b & 0xff;
@ -107,6 +109,8 @@ void MidiDriver_Sndio::sysEx(const byte *msg, uint16 length) {
assert(length + 2 <= ARRAYSIZE(buf));
midiDriverCommonSysEx(msg, length);
// Add SysEx frame
buf[0] = 0xF0;
memcpy(buf + 1, msg, length);

View file

@ -51,7 +51,7 @@ public:
int open();
bool isOpen() const { return _isOpen; }
void close();
void send(uint32 b);
void send(uint32 b) override;
void sysEx(const byte *msg, uint16 length);
private:
@ -72,6 +72,7 @@ void MidiDriver_STMIDI::close() {
}
void MidiDriver_STMIDI::send(uint32 b) {
midiDriverCommonSend(b);
byte status_byte = (b & 0x000000FF);
byte first_byte = (b & 0x0000FF00) >> 8;
@ -109,6 +110,8 @@ void MidiDriver_STMIDI::sysEx (const byte *msg, uint16 length) {
return;
}
midiDriverCommonSysEx(msg, length);
const byte *chr = msg;
warning("Sending SysEx Message");

View file

@ -87,7 +87,7 @@ public:
int open();
bool isOpen() const { return _isOpen; }
void close();
void send(uint32 b);
void send(uint32 b) override;
void sysEx(const byte *msg, uint16 length);
private:
@ -445,6 +445,8 @@ void MidiDriver_TIMIDITY::send(uint32 b) {
unsigned char buf[256];
int position = 0;
midiDriverCommonSend(b);
switch (b & 0xF0) {
case 0x80:
case 0x90:
@ -491,6 +493,8 @@ void MidiDriver_TIMIDITY::sysEx(const byte *msg, uint16 length) {
assert(length + 2 <= 266);
midiDriverCommonSysEx(msg, length);
buf[position++] = SEQ_MIDIPUTC;
buf[position++] = 0xF0;
buf[position++] = _device_num;

View file

@ -36,7 +36,6 @@
#include "common/translation.h"
#include "common/textconsole.h"
#include "common/error.h"
#include <mmsystem.h>
////////////////////////////////////////
@ -61,7 +60,7 @@ public:
int open();
bool isOpen() const { return _isOpen; }
void close();
void send(uint32 b);
void send(uint32 b) override;
void sysEx(const byte *msg, uint16 length);
};
@ -94,6 +93,8 @@ void MidiDriver_WIN::close() {
void MidiDriver_WIN::send(uint32 b) {
assert(_isOpen);
midiDriverCommonSend(b);
union {
DWORD dwData;
BYTE bData[4];
@ -107,6 +108,7 @@ void MidiDriver_WIN::send(uint32 b) {
check_error(midiOutShortMsg(_mo, u.dwData));
}
void MidiDriver_WIN::sysEx(const byte *msg, uint16 length) {
if (!_isOpen)
return;
@ -118,6 +120,8 @@ void MidiDriver_WIN::sysEx(const byte *msg, uint16 length) {
assert(length+2 <= 266);
midiDriverCommonSysEx(msg, length);
midiOutUnprepareHeader(_mo, &_streamHeader, sizeof(_streamHeader));
// Add SysEx frame

View file

@ -131,6 +131,8 @@ static const char HELP_STRING[] =
" supported by some MIDI drivers)\n"
" --multi-midi Enable combination AdLib and native MIDI\n"
" --native-mt32 True Roland MT-32 (disable GM emulation)\n"
" --dump-midi Dumps MIDI events to 'dump.mid', until quitting from game\n"
" (if file already exists, it will be overwritten)\n"
" --enable-gs Enable Roland GS mode for MIDI playback\n"
" --output-rate=RATE Select output sample rate in Hz (e.g. 22050)\n"
" --opl-driver=DRIVER Select AdLib (OPL) emulator (db, mame"
@ -233,6 +235,7 @@ void registerDefaults() {
ConfMan.registerDefault("multi_midi", false);
ConfMan.registerDefault("native_mt32", false);
ConfMan.registerDefault("dump_midi", false);
ConfMan.registerDefault("enable_gs", false);
ConfMan.registerDefault("midi_gain", 100);
@ -654,6 +657,9 @@ Common::String parseCommandLine(Common::StringMap &settings, int argc, const cha
DO_LONG_OPTION_BOOL("native-mt32")
END_OPTION
DO_LONG_OPTION_BOOL("dump-midi")
END_OPTION
DO_LONG_OPTION_BOOL("enable-gs")
END_OPTION

View file

@ -449,6 +449,12 @@ extern "C" int scummvm_main(int argc, const char * const argv[]) {
return res.getCode();
}
if (settings.contains("dump-midi")) {
// Store this command line setting in ConfMan, since all transient settings are destroyed
ConfMan.registerDefault("dump_midi", true);
}
// Init the backend. Must take place after all config data (including
// the command line params) was read.
system.initBackend();