aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rw-r--r--include/adlmidi.h71
-rw-r--r--src/adlmidi.cpp57
-rw-r--r--src/adlmidi_midiplay.hpp11
-rw-r--r--src/adlmidi_sequencer.cpp5
-rw-r--r--src/cvt_xmi2mid.hpp100
-rw-r--r--src/midi_sequencer.hpp12
-rw-r--r--src/midi_sequencer_impl.hpp71
-rw-r--r--utils/midiplay/adlmidiplay.cpp64
-rw-r--r--utils/xmi2mid/xmi2mid.cpp14
10 files changed, 388 insertions, 19 deletions
diff --git a/README.md b/README.md
index 694457e..9271982 100644
--- a/README.md
+++ b/README.md
@@ -185,6 +185,8 @@ To build that example you will need to have installed SDL2 library.
* Fixed memory damages and crashes while playing XMI files
* Added bank-specific MT32 defaults (to don't confuse XMI playback between different games, works for AIL and IBK only, and for WOPL if set at the header)
* Added the chip channels allocation mode option
+ * Fixed the playback of multi-song XMI files
+ * Added an ability to switch the XMI song on the fly
## 1.5.0.1 2020-10-11
* Fixed an incorrect timer processing when using a real-time interface
diff --git a/include/adlmidi.h b/include/adlmidi.h
index 9cf53a8..0611da6 100644
--- a/include/adlmidi.h
+++ b/include/adlmidi.h
@@ -600,6 +600,13 @@ extern ADLMIDI_DECLSPEC void adl_setLoopEnabled(struct ADL_MIDIPlayer *device, i
extern ADLMIDI_DECLSPEC void adl_setLoopCount(struct ADL_MIDIPlayer *device, int loopCount);
/**
+ * @brief Make song immediately stop on reaching a loop end point
+ * @param device Instance of the library
+ * @param loopHooksOnly 0 - disabled, 1 - enabled
+ */
+extern ADLMIDI_DECLSPEC void adl_setLoopHooksOnly(struct ADL_MIDIPlayer *device, int loopHooksOnly);
+
+/**
* @brief Enable or disable soft panning with chip emulators
* @param device Instance of the library
* @param softPanEn 0 - disabled, 1 - enabled
@@ -825,6 +832,20 @@ extern ADLMIDI_DECLSPEC int adl_openFile(struct ADL_MIDIPlayer *device, const ch
extern ADLMIDI_DECLSPEC int adl_openData(struct ADL_MIDIPlayer *device, const void *mem, unsigned long size);
/**
+ * @brief Switch another song if multi-song file is playing (for example, XMI)
+ *
+ * Note: to set the initial song to load, you should call this function
+ * BBEFORE calling `adl_openFile` or `adl_openData`. When loaded file has more than
+ * one built-in songs (Usually XMIformat), it will be started from the selected number.
+ * You may call this function to switch another song.
+ *
+ * @param device Instance of the library
+ * @param songNumber Identifier of the track to load (or -1 to mix all tracks as one song)
+ * @return
+ */
+extern ADLMIDI_DECLSPEC void adl_selectSongNum(struct ADL_MIDIPlayer *device, int songNumber);
+
+/**
* @brief Resets MIDI player (per-channel setup) into initial state
* @param device Instance of the library
*/
@@ -1278,7 +1299,18 @@ typedef void (*ADL_NoteHook)(void *userdata, int adlchn, int note, int ins, int
typedef void (*ADL_DebugMessageHook)(void *userdata, const char *fmt, ...);
/**
+ * @brief Loop start/end point reach hook
+ * @param userdata Pointer to user data (usually, context of someting)
+ */
+typedef void (*ASL_LoopPointHook)(void *userdata);
+
+/**
* @brief Set raw MIDI event hook
+ *
+ * CAUTION: Don't call any libADLMIDI API functions from off this hook directly!
+ * Suggestion: Use boolean variables to mark the fact this hook got been called, and then,
+ * apply your action outside of this hook, for example, in the next after audio output call.
+ *
* @param device Instance of the library
* @param rawEventHook Pointer to the callback function which will be called on every MIDI event
* @param userData Pointer to user data which will be passed through the callback.
@@ -1287,6 +1319,11 @@ extern ADLMIDI_DECLSPEC void adl_setRawEventHook(struct ADL_MIDIPlayer *device,
/**
* @brief Set note hook
+ *
+ * CAUTION: Don't call any libADLMIDI API functions from off this hook directly!
+ * Suggestion: Use boolean variables to mark the fact this hook got been called, and then,
+ * apply your action outside of this hook, for example, in the next after audio output call.
+ *
* @param device Instance of the library
* @param noteHook Pointer to the callback function which will be called on every noteOn MIDI event
* @param userData Pointer to user data which will be passed through the callback.
@@ -1295,6 +1332,11 @@ extern ADLMIDI_DECLSPEC void adl_setNoteHook(struct ADL_MIDIPlayer *device, ADL_
/**
* @brief Set debug message hook
+ *
+ * CAUTION: Don't call any libADLMIDI API functions from off this hook directly!
+ * Suggestion: Use boolean variables to mark the fact this hook got been called, and then,
+ * apply your action outside of this hook, for example, in the next after audio output call.
+ *
* @param device Instance of the library
* @param debugMessageHook Pointer to the callback function which will be called on every debug message
* @param userData Pointer to user data which will be passed through the callback.
@@ -1302,6 +1344,35 @@ extern ADLMIDI_DECLSPEC void adl_setNoteHook(struct ADL_MIDIPlayer *device, ADL_
extern ADLMIDI_DECLSPEC void adl_setDebugMessageHook(struct ADL_MIDIPlayer *device, ADL_DebugMessageHook debugMessageHook, void *userData);
/**
+ * @brief Set the look start point hook
+ *
+ * CAUTION: Don't call any libADLMIDI API functions from off this hook directly!
+ * Suggestion: Use boolean variables to mark the fact this hook got been called, and then,
+ * apply your action outside of this hook, for example, in the next after audio output call.
+ *
+ * @param device Instance of the library
+ * @param loopStartHook Pointer to the callback function which will be called on every loop start point passing
+ * @param userData Pointer to user data which will be passed through the callback.
+ */
+extern ADLMIDI_DECLSPEC void adl_setLoopStartHook(struct ADL_MIDIPlayer *device, ASL_LoopPointHook loopStartHook, void *userData);
+
+/**
+ * @brief Set the look start point hook
+ *
+ * CAUTION: Don't call any libADLMIDI API functions from off this hook directly!
+ * Suggestion: Use boolean variables to mark the fact this hook got been called, and then,
+ * apply your action outside of this hook, for example, in the next after audio output call.
+ *
+ * If you want to switch the song after calling this hook, suggested to call the function
+ * adl_setLoopHooksOnly(device, 1) to immediately stop the song on reaching the loop point
+ *
+ * @param device Instance of the library
+ * @param loopStartHook Pointer to the callback function which will be called on every loop start point passing
+ * @param userData Pointer to user data which will be passed through the callback.
+ */
+extern ADLMIDI_DECLSPEC void adl_setLoopEndHook(struct ADL_MIDIPlayer *device, ASL_LoopPointHook loopEndHook, void *userData);
+
+/**
* @brief Get a textual description of the channel state. For display only.
* @param device Instance of the library
* @param text Destination char buffer for channel usage state. Every entry is assigned to the chip channel.
diff --git a/src/adlmidi.cpp b/src/adlmidi.cpp
index a9ec918..f613544 100644
--- a/src/adlmidi.cpp
+++ b/src/adlmidi.cpp
@@ -597,6 +597,19 @@ ADLMIDI_EXPORT void adl_setLoopCount(ADL_MIDIPlayer *device, int loopCount)
#endif
}
+ADLMIDI_EXPORT void adl_setLoopHooksOnly(ADL_MIDIPlayer *device, int loopHooksOnly)
+{
+#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER
+ if(!device)
+ return;
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ assert(play);
+ play->m_sequencer->setLoopHooksOnly(loopHooksOnly);
+#else
+ ADL_UNUSED(device);
+ ADL_UNUSED(loopHooksOnly);
+#endif
+}
ADLMIDI_EXPORT void adl_setSoftPanEnabled(ADL_MIDIPlayer *device, int softPanEn)
{
@@ -772,6 +785,20 @@ ADLMIDI_EXPORT int adl_openData(ADL_MIDIPlayer *device, const void *mem, unsigne
return -1;
}
+ADLMIDI_EXPORT void adl_selectSongNum(struct ADL_MIDIPlayer *device, int songNumber)
+{
+#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER
+ if(!device)
+ return;
+
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ assert(play);
+ play->m_sequencer->setLoadTrack(songNumber);
+#else
+ ADL_UNUSED(device);
+ ADL_UNUSED(songNumber);
+#endif
+}
ADLMIDI_EXPORT const char *adl_emulatorName()
{
@@ -1139,6 +1166,36 @@ ADLMIDI_EXPORT void adl_setDebugMessageHook(struct ADL_MIDIPlayer *device, ADL_D
#endif
}
+/* Set loop start hook */
+ADLMIDI_EXPORT void adl_setLoopStartHook(struct ADL_MIDIPlayer *device, ASL_LoopPointHook loopStartHook, void *userData)
+{
+ if(!device)
+ return;
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ assert(play);
+ play->hooks.onLoopStart = loopStartHook;
+ play->hooks.onLoopStart_userData = userData;
+#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER
+ play->m_sequencerInterface->onloopStart = loopStartHook;
+ play->m_sequencerInterface->onloopStart_userData = userData;
+#endif
+}
+
+/* Set loop end hook */
+ADLMIDI_EXPORT void adl_setLoopEndHook(struct ADL_MIDIPlayer *device, ASL_LoopPointHook loopEndHook, void *userData)
+{
+ if(!device)
+ return;
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ assert(play);
+ play->hooks.onLoopEnd = loopEndHook;
+ play->hooks.onLoopEnd_userData = userData;
+#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER
+ play->m_sequencerInterface->onloopEnd = loopEndHook;
+ play->m_sequencerInterface->onloopEnd_userData = userData;
+#endif
+}
+
#ifndef ADLMIDI_HW_OPL
# ifndef __WATCOMC__
diff --git a/src/adlmidi_midiplay.hpp b/src/adlmidi_midiplay.hpp
index 181350a..5208f7b 100644
--- a/src/adlmidi_midiplay.hpp
+++ b/src/adlmidi_midiplay.hpp
@@ -37,6 +37,10 @@ struct MIDIEventHooks
MIDIEventHooks() :
onNote(NULL),
onNote_userData(NULL),
+ onLoopStart(NULL),
+ onLoopStart_userData(NULL),
+ onLoopEnd(NULL),
+ onLoopEnd_userData(NULL),
onDebugMessage(NULL),
onDebugMessage_userData(NULL)
{}
@@ -46,6 +50,13 @@ struct MIDIEventHooks
NoteHook onNote;
void *onNote_userData;
+ // Loop start/end hooks
+ ASL_LoopPointHook onLoopStart;
+ void *onLoopStart_userData;
+ ASL_LoopPointHook onLoopEnd;
+ void *onLoopEnd_userData;
+
+
//! Library internal debug messages
typedef void (*DebugMessageHook)(void *userdata, const char *fmt, ...);
DebugMessageHook onDebugMessage;
diff --git a/src/adlmidi_sequencer.cpp b/src/adlmidi_sequencer.cpp
index ce68ca8..53338e0 100644
--- a/src/adlmidi_sequencer.cpp
+++ b/src/adlmidi_sequencer.cpp
@@ -141,6 +141,11 @@ void MIDIplay::initSequencerInterface()
seq->onSongStart = rtSongBegin;
seq->onSongStart_userData = this;
+
+ seq->onloopStart = hooks.onLoopStart;
+ seq->onloopStart_userData = hooks.onLoopStart_userData;
+ seq->onloopEnd = hooks.onLoopEnd;
+ seq->onloopEnd_userData = hooks.onLoopEnd_userData;
/* NonStandard calls End */
m_sequencer->setInterface(seq);
diff --git a/src/cvt_xmi2mid.hpp b/src/cvt_xmi2mid.hpp
index 84c2f83..181c7db 100644
--- a/src/cvt_xmi2mid.hpp
+++ b/src/cvt_xmi2mid.hpp
@@ -31,6 +31,8 @@
#include <stdint.h>
#include <assert.h>
+#include <vector>
+
#ifdef __DJGPP__
typedef signed char int8_t;
typedef unsigned char uint8_t;
@@ -124,7 +126,7 @@ static int32_t xmi2mid_ConvertSystemMessage(struct xmi2mid_xmi_ctx *ctx,
static int32_t xmi2mid_ConvertFiletoList(struct xmi2mid_xmi_ctx *ctx, const xmi2mid_rbrn *rbrn);
static uint32_t xmi2mid_ConvertListToMTrk(struct xmi2mid_xmi_ctx *ctx, midi_event *mlist);
static int xmi2mid_ParseXMI(struct xmi2mid_xmi_ctx *ctx);
-static int xmi2mid_ExtractTracks(struct xmi2mid_xmi_ctx *ctx);
+static int xmi2mid_ExtractTracks(struct xmi2mid_xmi_ctx *ctx, int32_t dstTrackNumber);
static uint32_t xmi2mid_ExtractTracksFromXmi(struct xmi2mid_xmi_ctx *ctx);
static uint32_t xmi2mid_read1(struct xmi2mid_xmi_ctx *ctx)
@@ -517,11 +519,12 @@ static const char xmi2mid_mt32asgs[256] = {
static int Convert_xmi2midi(uint8_t *in, uint32_t insize,
uint8_t **out, uint32_t *outsize,
- uint32_t convert_type)
+ uint32_t convert_type, int32_t trackNumber = -1)
{
struct xmi2mid_xmi_ctx ctx;
unsigned int i;
int ret = -1;
+ int hasSelectedTrack = 0;
if (convert_type > XMIDI_CONVERT_MT32_TO_GS) {
/*_WM_ERROR_NEW("%s:%i: %d is an invalid conversion type.", __FUNCTION__, __LINE__, convert_type);*/
@@ -539,11 +542,15 @@ static int Convert_xmi2midi(uint8_t *in, uint32_t insize,
goto _end;
}
- if (xmi2mid_ExtractTracks(&ctx) < 0) {
+ if (xmi2mid_ExtractTracks(&ctx, trackNumber) < 0) {
/*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_NOT_MIDI, NULL, 0);*/
goto _end;
}
+ hasSelectedTrack = trackNumber >= 0;
+ if (trackNumber >= ctx.info.tracks)
+ trackNumber = ctx.info.tracks - 1;
+
ctx.dst = (uint8_t*)malloc(DST_CHUNK);
ctx.dst_ptr = ctx.dst;
ctx.dstsize = DST_CHUNK;
@@ -558,10 +565,12 @@ static int Convert_xmi2midi(uint8_t *in, uint32_t insize,
xmi2mid_write4(&ctx, 6);
xmi2mid_write2(&ctx, ctx.info.type);
- xmi2mid_write2(&ctx, ctx.info.tracks);
- xmi2mid_write2(&ctx, ctx.timing[0]);/* write divisions from track0 */
+ xmi2mid_write2(&ctx, hasSelectedTrack ? 1 : ctx.info.tracks);
+ xmi2mid_write2(&ctx, ctx.timing[hasSelectedTrack ? trackNumber : 0]);/* write divisions from track0 */
- for (i = 0; i < ctx.info.tracks; i++)
+ if (hasSelectedTrack)
+ xmi2mid_ConvertListToMTrk(&ctx, ctx.events[trackNumber]);
+ else for (i = 0; i < ctx.info.tracks; i++)
xmi2mid_ConvertListToMTrk(&ctx, ctx.events[i]);
*out = ctx.dst;
*outsize = ctx.dstsize - ctx.dstrem;
@@ -583,6 +592,81 @@ _end: /* cleanup */
return (ret);
}
+static int Convert_xmi2midi_multi(uint8_t *in, uint32_t insize,
+ std::vector<uint8_t *> &out, std::vector<uint32_t> &outsize,
+ uint32_t convert_type)
+{
+ struct xmi2mid_xmi_ctx ctx;
+ unsigned int i;
+ int ret = -1;
+
+ if (convert_type > XMIDI_CONVERT_MT32_TO_GS) {
+ /*_WM_ERROR_NEW("%s:%i: %d is an invalid conversion type.", __FUNCTION__, __LINE__, convert_type);*/
+ return (ret);
+ }
+
+ memset(&ctx, 0, sizeof(struct xmi2mid_xmi_ctx));
+ ctx.src = ctx.src_ptr = in;
+ ctx.srcsize = insize;
+ ctx.src_end = ctx.src + insize;
+ ctx.convert_type = convert_type;
+
+ if (xmi2mid_ParseXMI(&ctx) < 0) {
+ /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_NOT_XMI, NULL, 0);*/
+ goto _end;
+ }
+
+ if (xmi2mid_ExtractTracks(&ctx, 0) < 0) {
+ /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_NOT_MIDI, NULL, 0);*/
+ goto _end;
+ }
+
+ for (i = 0; i < ctx.info.tracks; i++)
+ {
+ ctx.dst = (uint8_t*)malloc(DST_CHUNK);
+ ctx.dst_ptr = ctx.dst;
+ ctx.dstsize = DST_CHUNK;
+ ctx.dstrem = DST_CHUNK;
+
+ /* Header is 14 bytes long and add the rest as well */
+ xmi2mid_write1(&ctx, 'M');
+ xmi2mid_write1(&ctx, 'T');
+ xmi2mid_write1(&ctx, 'h');
+ xmi2mid_write1(&ctx, 'd');
+
+ xmi2mid_write4(&ctx, 6);
+
+ xmi2mid_write2(&ctx, ctx.info.type);
+ xmi2mid_write2(&ctx, 1);
+ xmi2mid_write2(&ctx, ctx.timing[i]);/* write divisions from track0 */
+
+ xmi2mid_ConvertListToMTrk(&ctx, ctx.events[i]);
+ out.push_back(ctx.dst);
+ outsize.push_back(ctx.dstsize - ctx.dstrem);
+ }
+
+ ret = 0;
+
+_end: /* cleanup */
+ if (ret < 0) {
+ for(size_t j = 0; j < out.size(); ++j)
+ {
+ free(out[j]);
+ out[j] = NULL;
+ }
+ out.clear();
+ outsize.clear();
+ }
+ if (ctx.events) {
+ for (i = 0; i < ctx.info.tracks; i++)
+ xmi2mid_DeleteEventList(ctx.events[i]);
+ free(ctx.events);
+ }
+ free(ctx.timing);
+
+ return (ret);
+}
+
static void xmi2mid_DeleteEventList(midi_event *mlist) {
midi_event *event;
midi_event *next;
@@ -1222,13 +1306,13 @@ badfile: /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too shor
return (-1);
}
-static int xmi2mid_ExtractTracks(struct xmi2mid_xmi_ctx *ctx) {
+static int xmi2mid_ExtractTracks(struct xmi2mid_xmi_ctx *ctx, int32_t dstTrackNumber) {
uint32_t i;
ctx->events = (midi_event **)calloc(ctx->info.tracks, sizeof(midi_event*));
ctx->timing = (int16_t *)calloc(ctx->info.tracks, sizeof(int16_t));
/* type-2 for multi-tracks, type-0 otherwise */
- ctx->info.type = (ctx->info.tracks > 1)? 2 : 0;
+ ctx->info.type = (ctx->info.tracks > 1 && (dstTrackNumber < 0 || ctx->info.tracks >= dstTrackNumber))? 2 : 0;
xmi2mid_seeksrc(ctx, ctx->datastart);
i = xmi2mid_ExtractTracksFromXmi(ctx);
diff --git a/src/midi_sequencer.hpp b/src/midi_sequencer.hpp
index 8872fde..2cba080 100644
--- a/src/midi_sequencer.hpp
+++ b/src/midi_sequencer.hpp
@@ -387,6 +387,12 @@ private:
//! Set the number of loops limit. Lesser than 0 - loop infinite
int m_loopCount;
+ //! The number of track of multi-track file (for exmaple, XMI) to load
+ int m_loadTrackNumber;
+
+ //! The XMI-specific list of raw songs, converted into SMF format
+ std::vector<std::vector<uint8_t > > m_rawSongsData;
+
/**
* @brief Loop stack entry
*/
@@ -612,6 +618,12 @@ public:
void setSoloTrack(size_t track);
/**
+ * @brief Set the track number of a multi-track file (such as XMI)
+ * @param trackNumber Identifier of the track to load (or -1 to mix all tracks as one song)
+ */
+ void setLoadTrack(int track);
+
+ /**
* @brief Defines a handler for callback trigger events
* @param handler Handler to invoke from the sequencer when triggered, or NULL.
* @param userData Instance of the library
diff --git a/src/midi_sequencer_impl.hpp b/src/midi_sequencer_impl.hpp
index bfd7c40..3c94968 100644
--- a/src/midi_sequencer_impl.hpp
+++ b/src/midi_sequencer_impl.hpp
@@ -315,6 +315,7 @@ BW_MidiSequencer::BW_MidiSequencer() :
m_tempoMultiplier(1.0),
m_atEnd(false),
m_loopCount(-1),
+ m_loadTrackNumber(0),
m_trackSolo(~static_cast<size_t>(0)),
m_triggerHandler(NULL),
m_triggerUserData(NULL)
@@ -450,6 +451,37 @@ void BW_MidiSequencer::setSoloTrack(size_t track)
m_trackSolo = track;
}
+void BW_MidiSequencer::setLoadTrack(int track)
+{
+ m_loadTrackNumber = track;
+
+ if(!m_rawSongsData.empty() && m_format == Format_XMIDI) // Reload the song
+ {
+ if(m_loadTrackNumber >= (int)m_rawSongsData.size())
+ m_loadTrackNumber = m_rawSongsData.size() - 1;
+
+ if(m_interface && m_interface->rt_controllerChange)
+ {
+ for(int i = 0; i < 15; i++)
+ m_interface->rt_controllerChange(m_interface->rtUserData, i, 123, 0);
+ }
+
+ m_atEnd = false;
+ m_loop.fullReset();
+ m_loop.caughtStart = true;
+
+ m_smfFormat = 0;
+
+ FileAndMemReader fr;
+ fr.openData(m_rawSongsData[m_loadTrackNumber].data(),
+ m_rawSongsData[m_loadTrackNumber].size());
+ parseSMF(fr);
+
+ m_format = Format_XMIDI;
+ }
+}
+
+
void BW_MidiSequencer::setTriggerHandler(TriggerHandler handler, void *userData)
{
m_triggerHandler = handler;
@@ -2285,6 +2317,7 @@ bool BW_MidiSequencer::loadMIDI(FileAndMemReader &fr)
m_smfFormat = 0;
m_cmfInstruments.clear();
+ m_rawSongsData.clear();
const size_t headerSize = 4 + 4 + 2 + 2 + 2; // 14
char headerBuf[headerSize] = "";
@@ -2888,7 +2921,12 @@ bool BW_MidiSequencer::parseXMI(FileAndMemReader &fr)
const size_t headerSize = 14;
char headerBuf[headerSize] = "";
size_t fsize = 0;
- BufferGuard<uint8_t> cvt_buf;
+// BufferGuard<uint8_t> cvt_buf;
+ std::vector<uint8_t *> song_buf;
+ std::vector<uint32_t> song_size;
+ bool ret;
+
+ (void)Convert_xmi2midi; /* Shut up the warning */
fsize = fr.read(headerBuf, 1, headerSize);
if(fsize < headerSize)
@@ -2931,24 +2969,41 @@ bool BW_MidiSequencer::parseXMI(FileAndMemReader &fr)
// Close source stream
fr.close();
- uint8_t *mid = NULL;
- uint32_t mid_len = 0;
- int m2mret = Convert_xmi2midi(mus, static_cast<uint32_t>(mus_len + 20),
- &mid, &mid_len, XMIDI_CONVERT_NOCONVERSION);
+// uint8_t *mid = NULL;
+// uint32_t mid_len = 0;
+ int m2mret = Convert_xmi2midi_multi(mus, static_cast<uint32_t>(mus_len + 20),
+ song_buf, song_size, XMIDI_CONVERT_NOCONVERSION);
if(mus)
free(mus);
if(m2mret < 0)
{
+ for(size_t i = 0; i < song_buf.size(); ++i)
+ std::free(song_buf[i]);
m_errorString = "Invalid XMI data format!";
return false;
}
- cvt_buf.set(mid);
+ if(m_loadTrackNumber >= (int)song_buf.size())
+ m_loadTrackNumber = song_buf.size() - 1;
+
+ for(size_t i = 0; i < song_buf.size(); ++i)
+ {
+ m_rawSongsData.push_back(std::vector<uint8_t >(song_buf[i], song_buf[i] + song_size[i]));
+ std::free(song_buf[i]);
+ }
+
+ song_buf.clear();
+ song_size.clear();
+
+ // cvt_buf.set(mid);
// Open converted MIDI file
- fr.openData(mid, static_cast<size_t>(mid_len));
+ fr.openData(m_rawSongsData[m_loadTrackNumber].data(),
+ m_rawSongsData[m_loadTrackNumber].size());
// Set format as XMIDI
m_format = Format_XMIDI;
- return parseSMF(fr);
+ ret = parseSMF(fr);
+
+ return ret;
}
#endif
diff --git a/utils/midiplay/adlmidiplay.cpp b/utils/midiplay/adlmidiplay.cpp
index dafad8a..8620446 100644
--- a/utils/midiplay/adlmidiplay.cpp
+++ b/utils/midiplay/adlmidiplay.cpp
@@ -283,6 +283,26 @@ static void sighandler(int dum)
}
#endif
+//#define DEBUG_SONG_CHANGE
+//#define DEBUG_SONG_CHANGE_BY_HOOK
+
+#ifdef DEBUG_SONG_CHANGE_BY_HOOK
+static bool gotXmiTrigger = false;
+
+static void xmiTriggerCallback(void *, unsigned trigger, size_t track)
+{
+ std::fprintf(stdout, " - Trigger hook: trigger %u, track: %u\n", trigger, (unsigned)track);
+ flushout(stdout);
+ gotXmiTrigger = true;
+}
+
+static void loopEndCallback(void *)
+{
+ std::fprintf(stdout, " - Loop End hook: trigger\n");
+ flushout(stdout);
+ gotXmiTrigger = true;
+}
+#endif
static void debugPrint(void * /*userdata*/, const char *fmt, ...)
{
@@ -422,6 +442,7 @@ int main(int argc, char **argv)
" will be combined into one\n"
" --solo <track> Selects a solo track to play\n"
" --only <track1,...,trackN> Selects a subset of tracks to play\n"
+ " --song <song ID 0...N-1> Selects a song to play (if XMI)\n"
" -ea Enable the auto-arpeggio\n"
#ifndef HARDWARE_OPL3
" -fp Enables full-panning stereo support\n"
@@ -512,6 +533,7 @@ int main(int argc, char **argv)
int volumeModel = ADLMIDI_VolumeModel_AUTO;
size_t soloTrack = ~(size_t)0;
+ int songNumLoad = -1;
std::vector<size_t> onlyTracks;
#if !defined(HARDWARE_OPL3) && !defined(OUTPUT_WAVE_ONLY)
@@ -636,6 +658,16 @@ int main(int argc, char **argv)
soloTrack = std::strtoul(argv[3], NULL, 10);
had_option = true;
}
+ else if(!std::strcmp("--song", argv[2]))
+ {
+ if(argc <= 3)
+ {
+ printError("The option --song requires an argument!\n");
+ return 1;
+ }
+ songNumLoad = std::strtol(argv[3], NULL, 10);
+ had_option = true;
+ }
else if(!std::strcmp("--only", argv[2]))
{
if(argc <= 3)
@@ -847,6 +879,14 @@ int main(int argc, char **argv)
}
}
+#if defined(DEBUG_SONG_CHANGE_BY_HOOK)
+ adl_setTriggerHandler(myDevice, xmiTriggerCallback, NULL);
+ adl_setLoopEndHook(myDevice, loopEndCallback, NULL);
+ adl_setLoopHooksOnly(myDevice, 1);
+#endif
+ if(songNumLoad >= 0)
+ adl_selectSongNum(myDevice, songNumLoad);
+
std::string musPath = argv[1];
//Open MIDI file to play
if(adl_openFile(myDevice, musPath.c_str()) != 0)
@@ -860,6 +900,8 @@ int main(int argc, char **argv)
std::fprintf(stdout, " - Track count: %lu\n", static_cast<unsigned long>(adl_trackCount(myDevice)));
std::fprintf(stdout, " - Volume model: %s\n", volume_model_to_str(adl_getVolumeRangeModel(myDevice)));
std::fprintf(stdout, " - Channel allocation mode: %s\n", chanalloc_to_str(adl_getChannelAllocMode(myDevice)));
+ if(songNumLoad >= 0)
+ std::fprintf(stdout, " - Attempt to load song number: %d\n", songNumLoad);
if(soloTrack != ~static_cast<size_t>(0))
{
@@ -949,6 +991,12 @@ int main(int argc, char **argv)
audio_start();
# endif
+# ifdef DEBUG_SONG_CHANGE
+ int delayBeforeSongChange = 50;
+ std::fprintf(stdout, "DEBUG: === Random song set test is active! ===\n");
+ flushout(stdout);
+# endif
+
# ifdef DEBUG_SEEKING_TEST
int delayBeforeSeek = 50;
std::fprintf(stdout, "DEBUG: === Random position set test is active! ===\n");
@@ -1030,6 +1078,22 @@ int main(int argc, char **argv)
}
# endif
+# ifdef DEBUG_SONG_CHANGE
+ if(delayBeforeSongChange-- <= 0)
+ {
+ delayBeforeSongChange = rand() % 100;
+ adl_selectSongNum(myDevice, rand() % 10);
+ }
+# endif
+
+# ifdef DEBUG_SONG_CHANGE_BY_HOOK
+ if(gotXmiTrigger)
+ {
+ gotXmiTrigger = false;
+ adl_selectSongNum(myDevice, (rand() % 10) + 1);
+ }
+# endif
+
# else//HARDWARE_OPL3
const double mindelay = 1.0 / newTimerFreq;
diff --git a/utils/xmi2mid/xmi2mid.cpp b/utils/xmi2mid/xmi2mid.cpp
index 65d5858..9442a5e 100644
--- a/utils/xmi2mid/xmi2mid.cpp
+++ b/utils/xmi2mid/xmi2mid.cpp
@@ -12,14 +12,21 @@
int main(int argc, char *argv[])
{
- if(argc != 2)
+ int songNumber = 0;
+
+ (void)Convert_xmi2midi_multi; /* Shut up the warning */
+
+ if(argc != 2 && argc != 3)
{
- fprintf(stderr, "Usage: xmi2mid <midi-file>\n");
+ fprintf(stderr, "Usage: xmi2mid <midi-file> [song-number 0...N-1]\n");
return 1;
}
const char *filename = argv[1];
+ if(argc > 2)
+ songNumber = atoi(argv[2]);
+
FILE *fh = fopen(filename, "rb");
if(!fh)
{
@@ -50,7 +57,8 @@ int main(int argc, char *argv[])
uint8_t *xmidata = NULL;
uint32_t xmisize = 0;
- if(Convert_xmi2midi(filedata, insize, &xmidata, &xmisize, XMIDI_CONVERT_NOCONVERSION) < 0)
+
+ if(Convert_xmi2midi(filedata, insize, &xmidata, &xmisize, XMIDI_CONVERT_NOCONVERSION, songNumber) < 0)
{
fprintf(stderr, "Error converting XMI to SMF.\n");
return 1;