diff options
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | include/adlmidi.h | 71 | ||||
-rw-r--r-- | src/adlmidi.cpp | 57 | ||||
-rw-r--r-- | src/adlmidi_midiplay.hpp | 11 | ||||
-rw-r--r-- | src/adlmidi_sequencer.cpp | 5 | ||||
-rw-r--r-- | src/cvt_xmi2mid.hpp | 100 | ||||
-rw-r--r-- | src/midi_sequencer.hpp | 12 | ||||
-rw-r--r-- | src/midi_sequencer_impl.hpp | 71 | ||||
-rw-r--r-- | utils/midiplay/adlmidiplay.cpp | 64 | ||||
-rw-r--r-- | utils/xmi2mid/xmi2mid.cpp | 14 |
10 files changed, 388 insertions, 19 deletions
@@ -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; |