diff options
-rw-r--r-- | CMakeLists.txt | 18 | ||||
-rw-r--r-- | README.md | 23 | ||||
-rw-r--r-- | libADLMIDI-test.pro | 10 | ||||
-rw-r--r-- | libADLMIDI.pro | 10 | ||||
-rw-r--r-- | src/adlmidi.cpp | 246 | ||||
-rw-r--r-- | src/adlmidi_load.cpp | 450 | ||||
-rw-r--r-- | src/adlmidi_midiplay.cpp | 1292 | ||||
-rw-r--r-- | src/adlmidi_mus2mid.h | 49 | ||||
-rw-r--r-- | src/adlmidi_private.hpp | 370 | ||||
-rw-r--r-- | src/adlmidi_sequencer.cpp | 153 | ||||
-rw-r--r-- | src/adlmidi_xmi2mid.h | 60 | ||||
-rw-r--r-- | src/cvt_mus2mid.hpp (renamed from src/adlmidi_mus2mid.c) | 156 | ||||
-rw-r--r-- | src/cvt_xmi2mid.hpp (renamed from src/adlmidi_xmi2mid.c) | 377 | ||||
-rw-r--r-- | src/file_reader.hpp | 37 | ||||
-rw-r--r-- | src/midi_sequencer.h | 91 | ||||
-rw-r--r-- | src/midi_sequencer.hpp | 496 | ||||
-rw-r--r-- | src/midi_sequencer_impl.hpp | 1780 |
17 files changed, 3231 insertions, 2387 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index d75342f..e51b86d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -203,20 +203,18 @@ list(APPEND libADLMIDI_SOURCES ${libADLMIDI_SOURCE_DIR}/src/wopl/wopl_file.c ) -if(WITH_MUS_SUPPORT AND WITH_MIDI_SEQUENCER) +if(WITH_MIDI_SEQUENCER) list(APPEND libADLMIDI_SOURCES - ${libADLMIDI_SOURCE_DIR}/src/adlmidi_mus2mid.c + ${libADLMIDI_SOURCE_DIR}/src/adlmidi_sequencer.cpp ) -else() - add_definitions(-DADLMIDI_DISABLE_MUS_SUPPORT) endif() -if(WITH_XMI_SUPPORT AND WITH_MIDI_SEQUENCER) - list(APPEND libADLMIDI_SOURCES - ${libADLMIDI_SOURCE_DIR}/src/adlmidi_xmi2mid.c - ) -else() - add_definitions(-DADLMIDI_DISABLE_XMI_SUPPORT) +if(NOT WITH_MUS_SUPPORT OR NOT WITH_MIDI_SEQUENCER) + add_definitions(-DBWMIDI_DISABLE_MUS_SUPPORT) +endif() + +if(NOT WITH_XMI_SUPPORT OR NOT WITH_MIDI_SEQUENCER) + add_definitions(-DBWMIDI_DISABLE_XMI_SUPPORT) endif() if(NOT DJGPP AND NOT MSDOS) @@ -93,8 +93,8 @@ You need to make in the any IDE a library project and put into it next files (or include those files into subfolder of your exist project instead if you want to use it statically): ### Useful macros -* `ADLMIDI_DISABLE_XMI_SUPPORT` - Disables XMI to MIDI converter -* `ADLMIDI_DISABLE_MUS_SUPPORT` - Disables MUS to MIDI converter +* `BWMIDI_DISABLE_XMI_SUPPORT` - Disables XMI to MIDI converter +* `BWMIDI_DISABLE_MUS_SUPPORT` - Disables MUS to MIDI converter * `ADLMIDI_DISABLE_MIDI_SEQUENCER` - Completely disables built-in MIDI sequencer. * `ADLMIDI_DISABLE_DOSBOX_EMULATOR` - Disables DosBox 0.74 OPL3 emulator. * `ADLMIDI_DISABLE_NUKED_EMULATOR` - Disables Nuked OPL3 emulator. @@ -108,7 +108,6 @@ You need to make in the any IDE a library project and put into it next files * chips/* - Various OPL3 chip emulators and commonized interface over them * adldata.hh - bank structures definition * adlmidi_private.hpp - header of internal private APIs -* fraction.hpp - Fraction number handling * adldata.cpp - Automatically generated database of FM banks from "fm_banks" directory via "gen_adldata" tool. **Don't build it if you have defined `DISABLE_EMBEDDED_BANKS` macro!** * adlmidi.cpp - code of library @@ -117,16 +116,16 @@ You need to make in the any IDE a library project and put into it next files * adlmidi_opl3.cpp - OPL3 chips manager * adlmidi_private.cpp - some internal functions sources +#### MIDI Sequencer +To remove MIDI Sequecer, define `ADLMIDI_DISABLE_MIDI_SEQUENCER` macro and remove all those files +* adlmidi_sequencer.cpp - MIDI Sequencer related source +* cvt_mus2mid.hpp - MUS2MID converter source (define `BWMIDI_DISABLE_MUS_SUPPORT` macro to remove MUS support) +* cvt_xmi2mid.hpp - XMI2MID converter source (define `BWMIDI_DISABLE_XMI_SUPPORT` macro to remove XMI support) +* fraction.hpp - Fraction number handling (Used by Sequencer only) +* midi_sequencer.h - MIDI Sequencer C bindings +* midi_sequencer.hpp - MIDI Sequencer C++ declaration +* midi_sequencer_impl.hpp - MIDI Sequencer C++ implementation (must be once included into one of CPP together with interfaces initializations) -#### MUS2MIDI converter -To remove MUS support, define `ADLMIDI_DISABLE_MUS_SUPPORT` macro and remove those files: -* adlmidi_mus2mid.h - MUS2MID converter header -* adlmidi_mus2mid.c - MUS2MID converter source - -#### XMI2MIDI converter -To remove XMI support, define `ADLMIDI_DISABLE_XMI_SUPPORT` macro and remove those files: -* adlmidi_xmi2mid.h - XMI2MID converter header -* adlmidi_xmi2mid.c - XMI2MID converter source **Important**: Please use DosBox emulator on mobile devices because it requires small CPU power. Nuked OPL synthesizer is very accurate (compared to real OPL3 chip), but it requires much more power device and is high probability your device will lag and playback will be choppy. diff --git a/libADLMIDI-test.pro b/libADLMIDI-test.pro index 982a9a2..d26a5f5 100644 --- a/libADLMIDI-test.pro +++ b/libADLMIDI-test.pro @@ -27,9 +27,7 @@ HEADERS += \ include/adlmidi.h \ src/adlbank.h \ src/adldata.hh \ - src/adlmidi_mus2mid.h \ src/adlmidi_private.hpp \ - src/adlmidi_xmi2mid.h \ src/chips/dosbox/dbopl.h \ src/chips/dosbox_opl3.h \ src/chips/nuked/nukedopl3_174.h \ @@ -38,6 +36,11 @@ HEADERS += \ src/chips/nuked_opl3_v174.h \ src/chips/opl_chip_base.h \ src/chips/opl_chip_base.tcc \ + src/cvt_mus2mid.hpp \ + src/cvt_xmi2mid.hpp \ + src/midi_sequencer.h \ + src/midi_sequencer.hpp \ + src/midi_sequencer_impl.hpp \ src/fraction.hpp \ src/midiplay/wave_writer.h @@ -47,10 +50,9 @@ SOURCES += \ src/adlmidi.cpp \ src/adlmidi_load.cpp \ src/adlmidi_midiplay.cpp \ - src/adlmidi_mus2mid.c \ src/adlmidi_opl3.cpp \ src/adlmidi_private.cpp \ - src/adlmidi_xmi2mid.c \ + src/adlmidi_sequencer.cpp \ src/wopl/wopl_file.c \ src/chips/dosbox/dbopl.cpp \ src/chips/dosbox_opl3.cpp \ diff --git a/libADLMIDI.pro b/libADLMIDI.pro index 64c9aa1..92c6009 100644 --- a/libADLMIDI.pro +++ b/libADLMIDI.pro @@ -21,9 +21,7 @@ HEADERS += \ include/adlmidi.h \ src/adlbank.h \ src/adldata.hh \ - src/adlmidi_mus2mid.h \ src/adlmidi_private.hpp \ - src/adlmidi_xmi2mid.h \ src/wopl/wopl_file.h \ src/chips/dosbox/dbopl.h \ src/chips/dosbox_opl3.h \ @@ -33,6 +31,11 @@ HEADERS += \ src/chips/nuked_opl3_v174.h \ src/chips/opl_chip_base.h \ src/chips/opl_chip_base.tcc \ + src/cvt_mus2mid.hpp \ + src/cvt_xmi2mid.hpp \ + src/midi_sequencer.h \ + src/midi_sequencer.hpp \ + src/midi_sequencer_impl.hpp \ src/fraction.hpp SOURCES += \ @@ -41,10 +44,9 @@ SOURCES += \ src/adlmidi.cpp \ src/adlmidi_load.cpp \ src/adlmidi_midiplay.cpp \ - src/adlmidi_mus2mid.c \ src/adlmidi_opl3.cpp \ src/adlmidi_private.cpp \ - src/adlmidi_xmi2mid.c \ + src/adlmidi_sequencer.cpp \ src/wopl/wopl_file.c \ src/chips/dosbox/dbopl.cpp \ src/chips/dosbox_opl3.cpp \ diff --git a/src/adlmidi.cpp b/src/adlmidi.cpp index 9210b5b..69755d3 100644 --- a/src/adlmidi.cpp +++ b/src/adlmidi.cpp @@ -104,12 +104,12 @@ ADLMIDI_EXPORT int adl_getNumChips(struct ADL_MIDIPlayer *device) ADLMIDI_EXPORT int adl_setBank(ADL_MIDIPlayer *device, int bank) { - #ifdef DISABLE_EMBEDDED_BANKS +#ifdef DISABLE_EMBEDDED_BANKS ADL_UNUSED(device); ADL_UNUSED(bank); ADLMIDI_ErrorString = "This build of libADLMIDI has no embedded banks. Please load bank by using of adl_openBankFile() or adl_openBankData() functions instead of adl_setBank()"; return -1; - #else +#else const uint32_t NumBanks = static_cast<uint32_t>(maxAdlBanks()); int32_t bankno = bank; @@ -130,7 +130,7 @@ ADLMIDI_EXPORT int adl_setBank(ADL_MIDIPlayer *device, int bank) play->applySetup(); return adlRefreshNumCards(device); - #endif +#endif } ADLMIDI_EXPORT int adl_getBanksCount() @@ -355,20 +355,30 @@ ADLMIDI_EXPORT void adl_setFullRangeBrightness(struct ADL_MIDIPlayer *device, in { if(!device) return; MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer); + if(!play) + return; play->m_setup.fullRangeBrightnessCC74 = (fr_brightness != 0); } ADLMIDI_EXPORT void adl_setLoopEnabled(ADL_MIDIPlayer *device, int loopEn) { - if(!device) return; + if(!device) + return; MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer); - play->m_setup.loopingIsEnabled = (loopEn != 0); + if(!play) + return; +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER + play->m_sequencer.setLoopEnabled(loopEn != 0); +#else + ADL_UNUSED(loopEn); +#endif } /* !!!DEPRECATED!!! */ ADLMIDI_EXPORT void adl_setLogarithmicVolumes(struct ADL_MIDIPlayer *device, int logvol) { - if(!device) return; + if(!device) + return; MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer); play->m_setup.LogarithmicVolumes = (logvol != 0); if(play->m_setup.LogarithmicVolumes) @@ -379,7 +389,8 @@ ADLMIDI_EXPORT void adl_setLogarithmicVolumes(struct ADL_MIDIPlayer *device, int ADLMIDI_EXPORT void adl_setVolumeRangeModel(struct ADL_MIDIPlayer *device, int volumeModel) { - if(!device) return; + if(!device) + return; MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer); play->m_setup.VolumeModel = volumeModel; if(play->m_setup.VolumeModel == ADLMIDI_VolumeModel_AUTO)//Use bank default volume model @@ -444,7 +455,7 @@ ADLMIDI_EXPORT int adl_openFile(ADL_MIDIPlayer *device, const char *filePath) } else return 0; #else - (void)filePath; + ADL_UNUSED(filePath); play->setErrorString("ADLMIDI: MIDI Sequencer is not supported in this build of library!"); return -1; #endif //ADLMIDI_DISABLE_MIDI_SEQUENCER @@ -470,7 +481,8 @@ ADLMIDI_EXPORT int adl_openData(ADL_MIDIPlayer *device, const void *mem, unsigne } else return 0; #else - (void)mem;(void)size; + ADL_UNUSED(mem); + ADL_UNUSED(size); play->setErrorString("ADLMIDI: MIDI Sequencer is not supported in this build of library!"); return -1; #endif //ADLMIDI_DISABLE_MIDI_SEQUENCER @@ -564,16 +576,7 @@ ADLMIDI_EXPORT const char *adl_errorInfo(struct ADL_MIDIPlayer *device) ADLMIDI_EXPORT const char *adl_getMusicTitle(struct ADL_MIDIPlayer *device) { - if(!device) - return ""; - MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer); - if(!play) - return ""; - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - return play->musTitle.c_str(); - #else - return ""; - #endif + return adl_metaMusicTitle(device); } ADLMIDI_EXPORT void adl_close(struct ADL_MIDIPlayer *device) @@ -601,119 +604,159 @@ ADLMIDI_EXPORT double adl_totalTimeLength(struct ADL_MIDIPlayer *device) { if(!device) return -1.0; - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - return reinterpret_cast<MIDIplay *>(device->adl_midiPlayer)->timeLength(); - #else +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER + return reinterpret_cast<MIDIplay *>(device->adl_midiPlayer)->m_sequencer.timeLength(); +#else return -1.0; - #endif +#endif } ADLMIDI_EXPORT double adl_loopStartTime(struct ADL_MIDIPlayer *device) { if(!device) return -1.0; - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - return reinterpret_cast<MIDIplay *>(device->adl_midiPlayer)->getLoopStart(); - #else +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER + return reinterpret_cast<MIDIplay *>(device->adl_midiPlayer)->m_sequencer.getLoopStart(); +#else return -1.0; - #endif +#endif } ADLMIDI_EXPORT double adl_loopEndTime(struct ADL_MIDIPlayer *device) { if(!device) return -1.0; - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - return reinterpret_cast<MIDIplay *>(device->adl_midiPlayer)->getLoopEnd(); - #else +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER + return reinterpret_cast<MIDIplay *>(device->adl_midiPlayer)->m_sequencer.getLoopEnd(); +#else return -1.0; - #endif +#endif } ADLMIDI_EXPORT double adl_positionTell(struct ADL_MIDIPlayer *device) { +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER if(!device) return -1.0; - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - return reinterpret_cast<MIDIplay *>(device->adl_midiPlayer)->tell(); - #else + MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer); + if(!play) + return -1.0; + return play->m_sequencer.tell(); +#else + ADL_UNUSED(device); return -1.0; - #endif +#endif } ADLMIDI_EXPORT void adl_positionSeek(struct ADL_MIDIPlayer *device, double seconds) { +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER + if(seconds < 0.0) + return;//Seeking negative position is forbidden! :-P if(!device) return; - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - reinterpret_cast<MIDIplay *>(device->adl_midiPlayer)->seek(seconds); - #endif + MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer); + if(!play) + return; + play->realTime_panic(); + play->m_setup.delay = play->m_sequencer.seek(seconds, play->m_setup.mindelay); + play->m_setup.carry = 0.0; +#else + ADL_UNUSED(device); + ADL_UNUSED(seconds); +#endif } ADLMIDI_EXPORT void adl_positionRewind(struct ADL_MIDIPlayer *device) { +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER if(!device) return; - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - reinterpret_cast<MIDIplay *>(device->adl_midiPlayer)->rewind(); - #endif + MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer); + if(!play) + return; + play->realTime_panic(); + play->m_sequencer.rewind(); +#else + ADL_UNUSED(device); +#endif } ADLMIDI_EXPORT void adl_setTempo(struct ADL_MIDIPlayer *device, double tempo) { +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER if(!device || (tempo <= 0.0)) return; - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - reinterpret_cast<MIDIplay *>(device->adl_midiPlayer)->setTempo(tempo); - #endif + MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer); + if(!play) + return; + play->m_sequencer.setTempo(tempo); +#else + ADL_UNUSED(device); + ADL_UNUSED(tempo); +#endif } ADLMIDI_EXPORT const char *adl_metaMusicTitle(struct ADL_MIDIPlayer *device) { +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER if(!device) return ""; - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - return reinterpret_cast<MIDIplay *>(device->adl_midiPlayer)->musTitle.c_str(); - #else + MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer); + if(!play) + return ""; + return play->m_sequencer.getMusicTitle().c_str(); +#else + ADL_UNUSED(device); return ""; - #endif +#endif } ADLMIDI_EXPORT const char *adl_metaMusicCopyright(struct ADL_MIDIPlayer *device) { +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER if(!device) return ""; - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - return reinterpret_cast<MIDIplay *>(device->adl_midiPlayer)->musCopyright.c_str(); - #else + MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer); + if(!play) + return ""; + return play->m_sequencer.getMusicCopyright().c_str(); +#else + ADL_UNUSED(device); return ""; - #endif +#endif } ADLMIDI_EXPORT size_t adl_metaTrackTitleCount(struct ADL_MIDIPlayer *device) { +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER if(!device) return 0; -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - return reinterpret_cast<MIDIplay *>(device->adl_midiPlayer)->musTrackTitles.size(); + MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer); + if(!play) + return 0; + return play->m_sequencer.getTrackTitles().size(); #else + ADL_UNUSED(device); return 0; #endif } ADLMIDI_EXPORT const char *adl_metaTrackTitle(struct ADL_MIDIPlayer *device, size_t index) { - if(!device) - return 0; #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER + if(!device) + return ""; MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer); - if(index >= play->musTrackTitles.size()) + const std::vector<std::string> &titles = play->m_sequencer.getTrackTitles(); + if(index >= titles.size()) return "INVALID"; - return play->musTrackTitles[index].c_str(); + return titles[index].c_str(); #else - (void)device; (void)index; + ADL_UNUSED(device); + ADL_UNUSED(index); return "NOT SUPPORTED"; #endif } @@ -721,11 +764,15 @@ ADLMIDI_EXPORT const char *adl_metaTrackTitle(struct ADL_MIDIPlayer *device, siz ADLMIDI_EXPORT size_t adl_metaMarkerCount(struct ADL_MIDIPlayer *device) { +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER if(!device) return 0; -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - return reinterpret_cast<MIDIplay *>(device->adl_midiPlayer)->musMarkers.size(); + MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer); + if(!play) + return 0; + return play->m_sequencer.getMarkers().size(); #else + ADL_UNUSED(device); return 0; #endif } @@ -733,9 +780,10 @@ ADLMIDI_EXPORT size_t adl_metaMarkerCount(struct ADL_MIDIPlayer *device) ADLMIDI_EXPORT Adl_MarkerEntry adl_metaMarker(struct ADL_MIDIPlayer *device, size_t index) { struct Adl_MarkerEntry marker; - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer); - if(!device || !play || (index >= play->musMarkers.size())) + const std::vector<MidiSequencer::MIDI_MarkerEntry> &markers = play->m_sequencer.getMarkers(); + if(!device || !play || (index >= markers.size())) { marker.label = "INVALID"; marker.pos_time = 0.0; @@ -744,27 +792,34 @@ ADLMIDI_EXPORT Adl_MarkerEntry adl_metaMarker(struct ADL_MIDIPlayer *device, siz } else { - MIDIplay::MIDI_MarkerEntry &mk = play->musMarkers[index]; + const MidiSequencer::MIDI_MarkerEntry &mk = markers[index]; marker.label = mk.label.c_str(); marker.pos_time = mk.pos_time; marker.pos_ticks = (unsigned long)mk.pos_ticks; } - #else - (void)device; (void)index; +#else + ADL_UNUSED(device); + ADL_UNUSED(index); marker.label = "NOT SUPPORTED"; marker.pos_time = 0.0; marker.pos_ticks = 0; - #endif +#endif return marker; } ADLMIDI_EXPORT void adl_setRawEventHook(struct ADL_MIDIPlayer *device, ADL_RawEventHook rawEventHook, void *userData) { +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER if(!device) return; MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer); - play->hooks.onEvent = rawEventHook; - play->hooks.onEvent_userData = userData; + play->m_sequencerInterface.onEvent = rawEventHook; + play->m_sequencerInterface.onEvent_userData = userData; +#else + ADL_UNUSED(device); + ADL_UNUSED(rawEventHook); + ADL_UNUSED(userData); +#endif } /* Set note hook */ @@ -785,6 +840,10 @@ ADLMIDI_EXPORT void adl_setDebugMessageHook(struct ADL_MIDIPlayer *device, ADL_D MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer); play->hooks.onDebugMessage = debugMessageHook; play->hooks.onDebugMessage_userData = userData; +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER + play->m_sequencerInterface.onDebugMessage = debugMessageHook; + play->m_sequencerInterface.onDebugMessage_userData = userData; +#endif } #ifndef ADLMIDI_HW_OPL @@ -930,15 +989,16 @@ ADLMIDI_EXPORT int adl_playFormat(ADL_MIDIPlayer *device, int sampleCount, ADL_UInt8 *out_left, ADL_UInt8 *out_right, const ADLMIDI_AudioFormat *format) { - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - #ifdef ADLMIDI_HW_OPL - (void)device; - (void)sampleCount; - (void)out_left; - (void)out_right; - (void)format; +#if defined(ADLMIDI_DISABLE_MIDI_SEQUENCER) || defined(ADLMIDI_HW_OPL) + ADL_UNUSED(device); + ADL_UNUSED(sampleCount); + ADL_UNUSED(out_left); + ADL_UNUSED(out_right); + ADL_UNUSED(format); return 0; - #else +#endif + +#if !defined(ADLMIDI_DISABLE_MIDI_SEQUENCER) && !defined(ADLMIDI_HW_OPL) sampleCount -= sampleCount % 2; //Avoid even sample requests if(sampleCount < 0) return 0; @@ -975,7 +1035,7 @@ ADLMIDI_EXPORT int adl_playFormat(ADL_MIDIPlayer *device, int sampleCount, // setup.SkipForward -= 1; //else { - if((player->atEnd) && (setup.delay <= 0.0)) + if((player->m_sequencer.positionAtEnd()) && (setup.delay <= 0.0)) break;//Stop to fetch samples at reaching the song end with disabled loop ssize_t leftSamples = left / 2; @@ -1024,10 +1084,7 @@ ADLMIDI_EXPORT int adl_playFormat(ADL_MIDIPlayer *device, int sampleCount, } return static_cast<int>(gotten_len); - #endif - #else - return 0; - #endif //ADLMIDI_DISABLE_MIDI_SEQUENCER +#endif //ADLMIDI_DISABLE_MIDI_SEQUENCER } @@ -1040,14 +1097,14 @@ ADLMIDI_EXPORT int adl_generateFormat(struct ADL_MIDIPlayer *device, int sampleC ADL_UInt8 *out_left, ADL_UInt8 *out_right, const ADLMIDI_AudioFormat *format) { - #ifdef ADLMIDI_HW_OPL - (void)device; - (void)sampleCount; - (void)out_left; - (void)out_right; - (void)format; +#ifdef ADLMIDI_HW_OPL + ADL_UNUSED(device); + ADL_UNUSED(sampleCount); + ADL_UNUSED(out_left); + ADL_UNUSED(out_right); + ADL_UNUSED(format); return 0; - #else +#else sampleCount -= sampleCount % 2; //Avoid even sample requests if(sampleCount < 0) return 0; @@ -1101,12 +1158,12 @@ ADLMIDI_EXPORT int adl_generateFormat(struct ADL_MIDIPlayer *device, int sampleC gotten_len += (in_generatedPhys) /* - setup.stored_samples*/; } - player->TickIteratos(eat_delay); + player->TickIterators(eat_delay); }//... } return static_cast<int>(gotten_len); - #endif +#endif } ADLMIDI_EXPORT double adl_tickEvents(struct ADL_MIDIPlayer *device, double seconds, double granuality) @@ -1119,7 +1176,9 @@ ADLMIDI_EXPORT double adl_tickEvents(struct ADL_MIDIPlayer *device, double secon return -1.0; return player->Tick(seconds, granuality); #else - (void)seconds; (void)granuality; + ADL_UNUSED(device); + ADL_UNUSED(seconds); + ADL_UNUSED(granuality); return -1.0; #endif } @@ -1132,8 +1191,9 @@ ADLMIDI_EXPORT int adl_atEnd(struct ADL_MIDIPlayer *device) MIDIplay *player = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer); if(!player) return 1; - return (int)player->atEnd; + return (int)player->m_sequencer.positionAtEnd(); #else + ADL_UNUSED(device); return 1; #endif } diff --git a/src/adlmidi_load.cpp b/src/adlmidi_load.cpp index 10bea21..3e51b77 100644 --- a/src/adlmidi_load.cpp +++ b/src/adlmidi_load.cpp @@ -24,40 +24,6 @@ #include "adlmidi_private.hpp" #include "wopl/wopl_file.h" -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER -# ifndef ADLMIDI_DISABLE_MUS_SUPPORT -# include "adlmidi_mus2mid.h" -# endif//MUS -# ifndef ADLMIDI_DISABLE_XMI_SUPPORT -# include "adlmidi_xmi2mid.h" -# endif//XMI -#endif //ADLMIDI_DISABLE_MIDI_SEQUENCER - -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER -uint64_t MIDIplay::ReadBEint(const void *buffer, size_t nbytes) -{ - uint64_t result = 0; - const unsigned char *data = reinterpret_cast<const unsigned char *>(buffer); - - for(unsigned n = 0; n < nbytes; ++n) - result = (result << 8) + data[n]; - - return result; -} - -uint64_t MIDIplay::ReadLEint(const void *buffer, size_t nbytes) -{ - uint64_t result = 0; - const unsigned char *data = reinterpret_cast<const unsigned char *>(buffer); - - for(unsigned n = 0; n < nbytes; ++n) - result = result + static_cast<uint64_t>(data[n] << (n * 8)); - - return result; -} - -#endif - bool MIDIplay::LoadBank(const std::string &filename) { FileAndMemReader file; @@ -293,185 +259,44 @@ bool MIDIplay::LoadBank(FileAndMemReader &fr) } #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER -bool MIDIplay::LoadMIDI(const std::string &filename) -{ - FileAndMemReader file; - file.openFile(filename.c_str()); - if(!LoadMIDI(file)) - return false; - return true; -} - -bool MIDIplay::LoadMIDI(const void *data, size_t size) -{ - FileAndMemReader file; - file.openData(data, size); - return LoadMIDI(file); -} -bool MIDIplay::LoadMIDI(FileAndMemReader &fr) +bool MIDIplay::LoadMIDI_pre() { - size_t fsize; - ADL_UNUSED(fsize); - //! Temp buffer for conversion - AdlMIDI_CPtr<uint8_t> cvt_buf; - errorString.clear(); - - #ifdef DISABLE_EMBEDDED_BANKS +#ifdef DISABLE_EMBEDDED_BANKS if((opl.AdlBank != ~0u) || opl.dynamic_banks.empty()) { errorStringOut = "Bank is not set! Please load any instruments bank by using of adl_openBankFile() or adl_openBankData() functions!"; return false; } - #endif - - if(!fr.isValid()) - { - errorStringOut = "Invalid data stream!\n"; - #ifndef _WIN32 - errorStringOut += std::strerror(errno); - #endif - return false; - } - +#endif /**** Set all properties BEFORE starting of actial file reading! ****/ applySetup(); - atEnd = false; - loopStart = true; - invalidLoop = false; - - bool is_GMF = false; // GMD/MUS files (ScummVM) - //bool is_MUS = false; // MUS/DMX files (Doom) - bool is_IMF = false; // IMF - bool is_CMF = false; // Creative Music format (CMF/CTMF) - bool is_RSXX = false; // RSXX, such as Cartooners - - const size_t HeaderSize = 4 + 4 + 2 + 2 + 2; // 14 - char HeaderBuf[HeaderSize] = ""; - size_t DeltaTicks = 192, TrackCount = 1; - -riffskip: - fsize = fr.read(HeaderBuf, 1, HeaderSize); + caugh_missing_instruments.clear(); + caugh_missing_banks_melodic.clear(); + caugh_missing_banks_percussion.clear(); - if(std::memcmp(HeaderBuf, "RIFF", 4) == 0) - { - fr.seek(6l, SEEK_CUR); - goto riffskip; - } + return true; +} - if(std::memcmp(HeaderBuf, "GMF\x1", 4) == 0) - { - // GMD/MUS files (ScummVM) - fr.seek(7 - static_cast<long>(HeaderSize), SEEK_CUR); - is_GMF = true; - } - #ifndef ADLMIDI_DISABLE_MUS_SUPPORT - else if(std::memcmp(HeaderBuf, "MUS\x1A", 4) == 0) - { - // MUS/DMX files (Doom) - fr.seek(0, SEEK_END); - size_t mus_len = fr.tell(); - fr.seek(0, SEEK_SET); - uint8_t *mus = (uint8_t *)malloc(mus_len); - if(!mus) - { - errorStringOut = "Out of memory!"; - return false; - } - fr.read(mus, 1, mus_len); - //Close source stream - fr.close(); - - uint8_t *mid = NULL; - uint32_t mid_len = 0; - int m2mret = AdlMidi_mus2midi(mus, static_cast<uint32_t>(mus_len), - &mid, &mid_len, 0); - if(mus) free(mus); - if(m2mret < 0) - { - errorStringOut = "Invalid MUS/DMX data format!"; - return false; - } - cvt_buf.reset(mid); - //Open converted MIDI file - fr.openData(mid, static_cast<size_t>(mid_len)); - //Re-Read header again! - goto riffskip; - } - #endif //ADLMIDI_DISABLE_MUS_SUPPORT - #ifndef ADLMIDI_DISABLE_XMI_SUPPORT - else if(std::memcmp(HeaderBuf, "FORM", 4) == 0) +bool MIDIplay::LoadMIDI_post() +{ + MidiSequencer::FileFormat format = m_sequencer.getFormat(); + if(format == MidiSequencer::Format_CMF) { - if(std::memcmp(HeaderBuf + 8, "XDIR", 4) != 0) - { - fr.close(); - errorStringOut = fr.fileName() + ": Invalid format\n"; - return false; - } + const std::vector<MidiSequencer::CmfInstrument> &instruments = m_sequencer.getRawCmfInstruments(); + opl.dynamic_banks.clear();//Clean up old banks - fr.seek(0, SEEK_END); - size_t mus_len = fr.tell(); - fr.seek(0, SEEK_SET); - uint8_t *mus = (uint8_t*)malloc(mus_len); - if(!mus) + uint16_t ins_count = static_cast<uint16_t>(instruments.size()); + for(uint16_t i = 0; i < ins_count; ++i) { - errorStringOut = "Out of memory!"; - return false; - } - fr.read(mus, 1, mus_len); - //Close source stream - fr.close(); - - uint8_t *mid = NULL; - uint32_t mid_len = 0; - int m2mret = AdlMidi_xmi2midi(mus, static_cast<uint32_t>(mus_len), - &mid, &mid_len, XMIDI_CONVERT_NOCONVERSION); - if(mus) free(mus); - if(m2mret < 0) - { - errorStringOut = "Invalid XMI data format!"; - return false; - } - cvt_buf.reset(mid); - //Open converted MIDI file - fr.openData(mid, static_cast<size_t>(mid_len)); - //Re-Read header again! - goto riffskip; - } - #endif //ADLMIDI_DISABLE_XMI_SUPPORT - else if(std::memcmp(HeaderBuf, "CTMF", 4) == 0) - { - opl.dynamic_banks.clear(); - // Creative Music Format (CMF). - // When playing CTMF files, use the following commandline: - // adlmidi song8.ctmf -p -v 1 1 0 - // i.e. enable percussion mode, deeper vibrato, and use only 1 card. - is_CMF = true; - //unsigned version = ReadLEint(HeaderBuf+4, 2); - uint64_t ins_start = ReadLEint(HeaderBuf + 6, 2); - uint64_t mus_start = ReadLEint(HeaderBuf + 8, 2); - //unsigned deltas = ReadLEint(HeaderBuf+10, 2); - uint64_t ticks = ReadLEint(HeaderBuf + 12, 2); - // Read title, author, remarks start offsets in file - fr.read(HeaderBuf, 1, 6); - //unsigned long notes_starts[3] = {ReadLEint(HeaderBuf+0,2),ReadLEint(HeaderBuf+0,4),ReadLEint(HeaderBuf+0,6)}; - fr.seek(16, SEEK_CUR); // Skip the channels-in-use table - fr.read(HeaderBuf, 1, 4); - uint64_t ins_count = ReadLEint(HeaderBuf + 0, 2); //, basictempo = ReadLEint(HeaderBuf+2, 2); - fr.seek(static_cast<long>(ins_start), SEEK_SET); - - //std::printf("%u instruments\n", ins_count); - for(unsigned i = 0; i < ins_count; ++i) - { - unsigned bank = i / 256; + const uint8_t *InsData = instruments[i].data; + uint16_t bank = i / 256; bank = (bank & 127) + ((bank >> 7) << 8); if(bank > 127 + (127 << 8)) break; bank += (i % 256 < 128) ? 0 : OPL3::PercussionTag; - unsigned char InsData[16]; - fr.read(InsData, 1, 16); /*std::printf("Ins %3u: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n", i, InsData[0],InsData[1],InsData[2],InsData[3], InsData[4],InsData[5],InsData[6],InsData[7], InsData[8],InsData[9],InsData[10],InsData[11], InsData[12],InsData[13],InsData[14],InsData[15]);*/ @@ -500,224 +325,63 @@ riffskip: adlins.voice2_fine_tune = 0.0; } - fr.seeku(mus_start, SEEK_SET); - TrackCount = 1; - DeltaTicks = (size_t)ticks; opl.AdlBank = ~0u; // Ignore AdlBank number, use dynamic banks instead //std::printf("CMF deltas %u ticks %u, basictempo = %u\n", deltas, ticks, basictempo); opl.AdlPercussionMode = true; opl.m_musicMode = OPL3::MODE_CMF; opl.m_volumeScale = OPL3::VOLUME_NATIVE; } - else + else if(format == MidiSequencer::Format_RSXX) { - // Try to identify RSXX format - if(HeaderBuf[0] == 0x7D) - { - fr.seek(0x6D, SEEK_SET); - fr.read(HeaderBuf, 6, 1); - if(std::memcmp(HeaderBuf, "rsxx}u", 6) == 0) - { - is_RSXX = true; - fr.seek(0x7D, SEEK_SET); - TrackCount = 1; - DeltaTicks = 60; - //opl.CartoonersVolumes = true; - opl.m_musicMode = OPL3::MODE_RSXX; - opl.m_volumeScale = OPL3::VOLUME_NATIVE; - } - } - - // Try parsing as an IMF file - if(!is_RSXX) - { - do - { - uint8_t raw[4]; - size_t end = static_cast<size_t>(HeaderBuf[0]) + 256 * static_cast<size_t>(HeaderBuf[1]); - - if(!end || (end & 3)) - break; - - size_t backup_pos = fr.tell(); - int64_t sum1 = 0, sum2 = 0; - fr.seek(2, SEEK_SET); - - for(unsigned n = 0; n < 42; ++n) - { - if(fr.read(raw, 1, 4) != 4) - break; - int64_t value1 = raw[0]; - value1 += raw[1] << 8; - sum1 += value1; - int64_t value2 = raw[2]; - value2 += raw[3] << 8; - sum2 += value2; - } - - fr.seek(static_cast<long>(backup_pos), SEEK_SET); - - if(sum1 > sum2) - { - is_IMF = true; - DeltaTicks = 1; - } - } while(false); - } - - if(!is_IMF && !is_RSXX) - { - if(std::memcmp(HeaderBuf, "MThd\0\0\0\6", 8) != 0) - { - fr.close(); - errorStringOut = fr.fileName() + ": Invalid format, Header signature is unknown!\n"; - return false; - } - - /*size_t Fmt = ReadBEint(HeaderBuf + 8, 2);*/ - TrackCount = (size_t)ReadBEint(HeaderBuf + 10, 2); - DeltaTicks = (size_t)ReadBEint(HeaderBuf + 12, 2); - } + //opl.CartoonersVolumes = true; + opl.m_musicMode = OPL3::MODE_RSXX; + opl.m_volumeScale = OPL3::VOLUME_NATIVE; } - - TrackData.clear(); - TrackData.resize(TrackCount, std::vector<uint8_t>()); - InvDeltaTicks = fraction<uint64_t>(1, 1000000l * static_cast<uint64_t>(DeltaTicks)); - if(is_CMF || is_RSXX) - Tempo = fraction<uint64_t>(1, static_cast<uint64_t>(DeltaTicks)); - else - Tempo = fraction<uint64_t>(1, static_cast<uint64_t>(DeltaTicks) * 2); - static const unsigned char EndTag[4] = {0xFF, 0x2F, 0x00, 0x00}; - size_t totalGotten = 0; - - for(size_t tk = 0; tk < TrackCount; ++tk) + else if(format == MidiSequencer::Format_IMF) { - // Read track header - size_t TrackLength; - - if(is_IMF) - { - //std::fprintf(stderr, "Reading IMF file...\n"); - size_t end = static_cast<size_t>(HeaderBuf[0]) + 256 * static_cast<size_t>(HeaderBuf[1]); - unsigned IMF_tempo = 1428; - static const unsigned char imf_tempo[] = {0x0,//Zero delay! - MidiEvent::T_SPECIAL, MidiEvent::ST_TEMPOCHANGE, 0x4, - static_cast<uint8_t>(IMF_tempo >> 24), - static_cast<uint8_t>(IMF_tempo >> 16), - static_cast<uint8_t>(IMF_tempo >> 8), - static_cast<uint8_t>(IMF_tempo) - }; - TrackData[tk].insert(TrackData[tk].end(), imf_tempo, imf_tempo + sizeof(imf_tempo)); - TrackData[tk].push_back(0x00); - fr.seek(2, SEEK_SET); - - while(fr.tell() < end && !fr.eof()) - { - uint8_t special_event_buf[5]; - uint8_t raw[4]; - special_event_buf[0] = MidiEvent::T_SPECIAL; - special_event_buf[1] = MidiEvent::ST_RAWOPL; - special_event_buf[2] = 0x02; - if(fr.read(raw, 1, 4) != 4) - break; - special_event_buf[3] = raw[0]; // port index - special_event_buf[4] = raw[1]; // port value - uint32_t delay = static_cast<uint32_t>(raw[2]); - delay += 256 * static_cast<uint32_t>(raw[3]); - totalGotten += 4; - //if(special_event_buf[3] <= 8) continue; - //fprintf(stderr, "Put %02X <- %02X, plus %04X delay\n", special_event_buf[3],special_event_buf[4], delay); - TrackData[tk].insert(TrackData[tk].end(), special_event_buf, special_event_buf + 5); - //if(delay>>21) TrackData[tk].push_back( 0x80 | ((delay>>21) & 0x7F ) ); - if(delay >> 14) - TrackData[tk].push_back(0x80 | ((delay >> 14) & 0x7F)); - if(delay >> 7) - TrackData[tk].push_back(0x80 | ((delay >> 7) & 0x7F)); - TrackData[tk].push_back(((delay >> 0) & 0x7F)); - } - - TrackData[tk].insert(TrackData[tk].end(), EndTag + 0, EndTag + 4); - //CurrentPosition.track[tk].delay = 0; - //CurrentPosition.began = true; - //std::fprintf(stderr, "Done reading IMF file\n"); - opl.NumFourOps = 0; //Don't use 4-operator channels for IMF playing! - opl.m_musicMode = OPL3::MODE_IMF; - } - else - { - // Take the rest of the file - if(is_GMF || is_CMF || is_RSXX) - { - size_t pos = fr.tell(); - fr.seek(0, SEEK_END); - TrackLength = fr.tell() - pos; - fr.seek(static_cast<long>(pos), SEEK_SET); - } - //else if(is_MUS) // Read TrackLength from file position 4 - //{ - // size_t pos = fr.tell(); - // fr.seek(4, SEEK_SET); - // TrackLength = static_cast<size_t>(fr.getc()); - // TrackLength += static_cast<size_t>(fr.getc() << 8); - // fr.seek(static_cast<long>(pos), SEEK_SET); - //} - else - { - fsize = fr.read(HeaderBuf, 1, 8); - if(std::memcmp(HeaderBuf, "MTrk", 4) != 0) - { - fr.close(); - errorStringOut = fr.fileName() + ": Invalid format, MTrk signature is not found!\n"; - return false; - } - TrackLength = (size_t)ReadBEint(HeaderBuf + 4, 4); - } - - // Read track data - TrackData[tk].resize(TrackLength); - fsize = fr.read(&TrackData[tk][0], 1, TrackLength); - totalGotten += fsize; - - if(is_GMF/*|| is_MUS*/) // Note: CMF does include the track end tag. - TrackData[tk].insert(TrackData[tk].end(), EndTag + 0, EndTag + 4); - if(is_RSXX)//Finalize raw track data with a zero - TrackData[tk].push_back(0); - - //bool ok = false; - //// Read next event time - //uint64_t tkDelay = ReadVarLenEx(tk, ok); - //if(ok) - // CurrentPosition.track[tk].delay = tkDelay; - //else - //{ - // std::stringstream msg; - // msg << fr._fileName << ": invalid variable length in the track " << tk << "! (error code " << tkDelay << ")"; - // ADLMIDI_ErrorString = msg.str(); - // return false; - //} - } + //std::fprintf(stderr, "Done reading IMF file\n"); + opl.NumFourOps = 0; //Don't use 4-operator channels for IMF playing! + opl.m_musicMode = OPL3::MODE_IMF; } - for(size_t tk = 0; tk < TrackCount; ++tk) - totalGotten += TrackData[tk].size(); + opl.Reset(m_setup.emulator, m_setup.PCM_RATE, this); // Reset OPL3 chip + //opl.Reset(); // ...twice (just in case someone misprogrammed OPL3 previously) + ch.clear(); + ch.resize(opl.NumChannels); + + return true; +} - if(totalGotten == 0) +bool MIDIplay::LoadMIDI(const std::string &filename) +{ + FileAndMemReader file; + file.openFile(filename.c_str()); + if(!LoadMIDI_pre()) + return false; + if(!m_sequencer.loadMIDI(file)) { - errorStringOut = fr.fileName() + ": Empty track data"; + errorStringOut = m_sequencer.getErrorString(); return false; } + if(!LoadMIDI_post()) + return false; + return true; +} - //Build new MIDI events table - if(!buildTrackData()) +bool MIDIplay::LoadMIDI(const void *data, size_t size) +{ + FileAndMemReader file; + file.openData(data, size); + if(!LoadMIDI_pre()) + return false; + if(!m_sequencer.loadMIDI(file)) { - errorStringOut = fr.fileName() + ": MIDI data parsing error has occouped!\n" + errorString; + errorStringOut = m_sequencer.getErrorString(); return false; } - - opl.Reset(m_setup.emulator, m_setup.PCM_RATE, this); // Reset OPL3 chip - //opl.Reset(); // ...twice (just in case someone misprogrammed OPL3 previously) - ch.clear(); - ch.resize(opl.NumChannels); + if(!LoadMIDI_post()) + return false; return true; } -#endif //ADLMIDI_DISABLE_MIDI_SEQUENCER + +#endif /* ADLMIDI_DISABLE_MIDI_SEQUENCER */ diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index 2f186a6..5b7f0d9 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -115,586 +115,12 @@ void MIDIplay::AdlChannel::AddAge(int64_t ms) } } -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - -MIDIplay::MidiEvent::MidiEvent() : - type(T_UNKNOWN), - subtype(T_UNKNOWN), - channel(0), - isValid(1), - absPosition(0) -{} - - -MIDIplay::MidiTrackRow::MidiTrackRow() : - time(0.0), - delay(0), - absPos(0), - timeDelay(0.0) -{} - -void MIDIplay::MidiTrackRow::reset() -{ - time = 0.0; - delay = 0; - absPos = 0; - timeDelay = 0.0; - events.clear(); -} - -void MIDIplay::MidiTrackRow::sortEvents(bool *noteStates) -{ - typedef std::vector<MidiEvent> EvtArr; - EvtArr metas; - EvtArr noteOffs; - EvtArr controllers; - EvtArr anyOther; - - metas.reserve(events.size()); - noteOffs.reserve(events.size()); - controllers.reserve(events.size()); - anyOther.reserve(events.size()); - - for(size_t i = 0; i < events.size(); i++) - { - if(events[i].type == MidiEvent::T_NOTEOFF) - noteOffs.push_back(events[i]); - else if((events[i].type == MidiEvent::T_CTRLCHANGE) - || (events[i].type == MidiEvent::T_PATCHCHANGE) - || (events[i].type == MidiEvent::T_WHEEL) - || (events[i].type == MidiEvent::T_CHANAFTTOUCH)) - { - controllers.push_back(events[i]); - } - else if((events[i].type == MidiEvent::T_SPECIAL) && (events[i].subtype == MidiEvent::ST_MARKER)) - metas.push_back(events[i]); - else - anyOther.push_back(events[i]); - } - - /* - * If Note-Off and it's Note-On is on the same row - move this damned note off down! - */ - if(noteStates) - { - std::set<size_t> markAsOn; - for(size_t i = 0; i < anyOther.size(); i++) - { - const MidiEvent e = anyOther[i]; - if(e.type == MidiEvent::T_NOTEON) - { - const size_t note_i = (e.channel * 255) + (e.data[0] & 0x7F); - //Check, was previously note is on or off - bool wasOn = noteStates[note_i]; - markAsOn.insert(note_i); - // Detect zero-length notes are following previously pressed note - int noteOffsOnSameNote = 0; - for(EvtArr::iterator j = noteOffs.begin(); j != noteOffs.end();) - { - //If note was off, and note-off on same row with note-on - move it down! - if( - ((*j).channel == e.channel) && - ((*j).data[0] == e.data[0]) - ) - { - //If note is already off OR more than one note-off on same row and same note - if(!wasOn || (noteOffsOnSameNote != 0)) - { - anyOther.push_back(*j); - j = noteOffs.erase(j); - markAsOn.erase(note_i); - continue; - } - else - { - //When same row has many note-offs on same row - //that means a zero-length note follows previous note - //it must be shuted down - noteOffsOnSameNote++; - } - } - j++; - } - } - } - - //Mark other notes as released - for(EvtArr::iterator j = noteOffs.begin(); j != noteOffs.end(); j++) - { - size_t note_i = (j->channel * 255) + (j->data[0] & 0x7F); - noteStates[note_i] = false; - } - - for(std::set<size_t>::iterator j = markAsOn.begin(); j != markAsOn.end(); j++) - noteStates[*j] = true; - } - /***********************************************************************************/ - - events.clear(); - events.insert(events.end(), noteOffs.begin(), noteOffs.end()); - events.insert(events.end(), metas.begin(), metas.end()); - events.insert(events.end(), controllers.begin(), controllers.end()); - events.insert(events.end(), anyOther.begin(), anyOther.end()); -} -#endif //ADLMIDI_DISABLE_MIDI_SEQUENCER - -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER -bool MIDIplay::buildTrackData() -{ - fullSongTimeLength = 0.0; - loopStartTime = -1.0; - loopEndTime = -1.0; - musTitle.clear(); - musCopyright.clear(); - musTrackTitles.clear(); - musMarkers.clear(); - caugh_missing_instruments.clear(); - caugh_missing_banks_melodic.clear(); - caugh_missing_banks_percussion.clear(); - trackDataNew.clear(); - const size_t trackCount = TrackData.size(); - trackDataNew.resize(trackCount, MidiTrackQueue()); - - invalidLoop = false; - bool gotLoopStart = false, gotLoopEnd = false, gotLoopEventInThisRow = false; - //! Tick position of loop start tag - uint64_t loopStartTicks = 0; - //! Tick position of loop end tag - uint64_t loopEndTicks = 0; - //! Full length of song in ticks - uint64_t ticksSongLength = 0; - //! Cache for error message strign - char error[150]; - - CurrentPositionNew.track.clear(); - CurrentPositionNew.track.resize(trackCount); - - //! Caches note on/off states. - bool noteStates[16 * 255]; - /* This is required to carefully detect zero-length notes * - * and avoid a move of "note-off" event over "note-on" while sort. * - * Otherwise, after sort those notes will play infinite sound */ - - //Tempo change events - std::vector<MidiEvent> tempos; - - /* - * TODO: Make this be safer for memory in case of broken input data - * which may cause going away of available track data (and then give a crash!) - * - * POST: Check this more carefully for possible vulnuabilities are can crash this - */ - for(size_t tk = 0; tk < trackCount; ++tk) - { - uint64_t abs_position = 0; - int status = 0; - MidiEvent event; - bool ok = false; - uint8_t *end = TrackData[tk].data() + TrackData[tk].size(); - uint8_t *trackPtr = TrackData[tk].data(); - std::memset(noteStates, 0, sizeof(noteStates)); - - //Time delay that follows the first event in the track - { - MidiTrackRow evtPos; - if(opl.m_musicMode == OPL3::MODE_RSXX) - ok = true; - else - evtPos.delay = ReadVarLenEx(&trackPtr, end, ok); - if(!ok) - { - int len = snprintf(error, 150, "buildTrackData: Can't read variable-length value at begin of track %d.\n", (int)tk); - if((len > 0) && (len < 150)) - errorString += std::string(error, (size_t)len); - return false; - } - - //HACK: Begin every track with "Reset all controllers" event to avoid controllers state break came from end of song - for(uint8_t chan = 0; chan < 16; chan++) - { - MidiEvent event; - event.type = MidiEvent::T_CTRLCHANGE; - event.channel = chan; - event.data.push_back(121); - event.data.push_back(0); - evtPos.events.push_back(event); - } - - evtPos.absPos = abs_position; - abs_position += evtPos.delay; - trackDataNew[tk].push_back(evtPos); - } - - MidiTrackRow evtPos; - do - { - event = parseEvent(&trackPtr, end, status); - if(!event.isValid) - { - int len = snprintf(error, 150, "buildTrackData: Fail to parse event in the track %d.\n", (int)tk); - if((len > 0) && (len < 150)) - errorString += std::string(error, (size_t)len); - return false; - } - - evtPos.events.push_back(event); - if(event.type == MidiEvent::T_SPECIAL) - { - if(event.subtype == MidiEvent::ST_TEMPOCHANGE) - { - event.absPosition = abs_position; - tempos.push_back(event); - } - else if(!invalidLoop && (event.subtype == MidiEvent::ST_LOOPSTART)) - { - /* - * loopStart is invalid when: - * - starts together with loopEnd - * - appears more than one time in same MIDI file - */ - if(gotLoopStart || gotLoopEventInThisRow) - invalidLoop = true; - else - { - gotLoopStart = true; - loopStartTicks = abs_position; - } - //In this row we got loop event, register this! - gotLoopEventInThisRow = true; - } - else if(!invalidLoop && (event.subtype == MidiEvent::ST_LOOPEND)) - { - /* - * loopEnd is invalid when: - * - starts before loopStart - * - starts together with loopStart - * - appars more than one time in same MIDI file - */ - if(gotLoopEnd || gotLoopEventInThisRow) - invalidLoop = true; - else - { - gotLoopEnd = true; - loopEndTicks = abs_position; - } - //In this row we got loop event, register this! - gotLoopEventInThisRow = true; - } - } - - if(event.subtype != MidiEvent::ST_ENDTRACK)//Don't try to read delta after EndOfTrack event! - { - evtPos.delay = ReadVarLenEx(&trackPtr, end, ok); - if(!ok) - { - /* End of track has been reached! However, there is no EOT event presented */ - event.type = MidiEvent::T_SPECIAL; - event.subtype = MidiEvent::ST_ENDTRACK; - } - } - - if((evtPos.delay > 0) || (event.subtype == MidiEvent::ST_ENDTRACK)) - { - evtPos.absPos = abs_position; - abs_position += evtPos.delay; - evtPos.sortEvents(noteStates); - trackDataNew[tk].push_back(evtPos); - evtPos.reset(); - gotLoopEventInThisRow = false; - } - } - while((trackPtr <= end) && (event.subtype != MidiEvent::ST_ENDTRACK)); - - if(ticksSongLength < abs_position) - ticksSongLength = abs_position; - //Set the chain of events begin - if(trackDataNew[tk].size() > 0) - CurrentPositionNew.track[tk].pos = trackDataNew[tk].begin(); - } - - if(gotLoopStart && !gotLoopEnd) - { - gotLoopEnd = true; - loopEndTicks = ticksSongLength; - } - - //loopStart must be located before loopEnd! - if(loopStartTicks >= loopEndTicks) - invalidLoop = true; - - /********************************************************************************/ - //Calculate time basing on collected tempo events - /********************************************************************************/ - for(size_t tk = 0; tk < trackCount; ++tk) - { - fraction<uint64_t> currentTempo = Tempo; - double time = 0.0; - uint64_t abs_position = 0; - size_t tempo_change_index = 0; - MidiTrackQueue &track = trackDataNew[tk]; - if(track.empty()) - continue;//Empty track is useless! - -#ifdef DEBUG_TIME_CALCULATION - std::fprintf(stdout, "\n============Track %" PRIuPTR "=============\n", tk); - std::fflush(stdout); -#endif - - MidiTrackRow *posPrev = &(*(track.begin()));//First element - for(MidiTrackQueue::iterator it = track.begin(); it != track.end(); it++) - { -#ifdef DEBUG_TIME_CALCULATION - bool tempoChanged = false; -#endif - MidiTrackRow &pos = *it; - if((posPrev != &pos) && //Skip first event - (!tempos.empty()) && //Only when in-track tempo events are available - (tempo_change_index < tempos.size()) - ) - { - // If tempo event is going between of current and previous event - if(tempos[tempo_change_index].absPosition <= pos.absPos) - { - //Stop points: begin point and tempo change points are before end point - std::vector<TempoChangePoint> points; - fraction<uint64_t> t; - TempoChangePoint firstPoint = {posPrev->absPos, currentTempo}; - points.push_back(firstPoint); - - //Collect tempo change points between previous and current events - do - { - TempoChangePoint tempoMarker; - MidiEvent &tempoPoint = tempos[tempo_change_index]; - tempoMarker.absPos = tempoPoint.absPosition; - tempoMarker.tempo = InvDeltaTicks * fraction<uint64_t>(ReadBEint(tempoPoint.data.data(), tempoPoint.data.size())); - points.push_back(tempoMarker); - tempo_change_index++; - } - while((tempo_change_index < tempos.size()) && - (tempos[tempo_change_index].absPosition <= pos.absPos)); - - // Re-calculate time delay of previous event - time -= posPrev->timeDelay; - posPrev->timeDelay = 0.0; - - for(size_t i = 0, j = 1; j < points.size(); i++, j++) - { - /* If one or more tempo events are appears between of two events, - * calculate delays between each tempo point, begin and end */ - uint64_t midDelay = 0; - //Delay between points - midDelay = points[j].absPos - points[i].absPos; - //Time delay between points - t = midDelay * currentTempo; - posPrev->timeDelay += t.value(); - - //Apply next tempo - currentTempo = points[j].tempo; -#ifdef DEBUG_TIME_CALCULATION - tempoChanged = true; -#endif - } - //Then calculate time between last tempo change point and end point - TempoChangePoint tailTempo = points.back(); - uint64_t postDelay = pos.absPos - tailTempo.absPos; - t = postDelay * currentTempo; - posPrev->timeDelay += t.value(); - - //Store Common time delay - posPrev->time = time; - time += posPrev->timeDelay; - } - } - - fraction<uint64_t> t = pos.delay * currentTempo; - pos.timeDelay = t.value(); - pos.time = time; - time += pos.timeDelay; - - //Capture markers after time value calculation - for(size_t i = 0; i < pos.events.size(); i++) - { - MidiEvent &e = pos.events[i]; - if((e.type == MidiEvent::T_SPECIAL) && (e.subtype == MidiEvent::ST_MARKER)) - { - MIDI_MarkerEntry marker; - marker.label = std::string((char *)e.data.data(), e.data.size()); - marker.pos_ticks = pos.absPos; - marker.pos_time = pos.time; - musMarkers.push_back(marker); - } - } - - //Capture loop points time positions - if(!invalidLoop) - { - // Set loop points times - if(loopStartTicks == pos.absPos) - loopStartTime = pos.time; - else if(loopEndTicks == pos.absPos) - loopEndTime = pos.time; - } - -#ifdef DEBUG_TIME_CALCULATION - std::fprintf(stdout, "= %10" PRId64 " = %10f%s\n", pos.absPos, pos.time, tempoChanged ? " <----TEMPO CHANGED" : ""); - std::fflush(stdout); -#endif - - abs_position += pos.delay; - posPrev = &pos; - } - - if(time > fullSongTimeLength) - fullSongTimeLength = time; - } - - fullSongTimeLength += postSongWaitDelay; - //Set begin of the music - trackBeginPositionNew = CurrentPositionNew; - //Initial loop position will begin at begin of track until passing of the loop point - LoopBeginPositionNew = CurrentPositionNew; - - /********************************************************************************/ - //Resolve "hell of all times" of too short drum notes: - //move too short percussion note-offs far far away as possible - /********************************************************************************/ -#if 1 //Use this to record WAVEs for comparison before/after implementing of this - if(opl.m_musicMode == OPL3::MODE_MIDI)//Percussion fix is needed for MIDI only, not for IMF/RSXX or CMF - { - //! Minimal real time in seconds -#define DRUM_NOTE_MIN_TIME 0.03 - //! Minimal ticks count -#define DRUM_NOTE_MIN_TICKS 15 - struct NoteState - { - double delay; - uint64_t delayTicks; - bool isOn; - char ___pad[7]; - } drNotes[255]; - uint16_t banks[16]; - - for(size_t tk = 0; tk < trackCount; ++tk) - { - std::memset(drNotes, 0, sizeof(drNotes)); - std::memset(banks, 0, sizeof(banks)); - MidiTrackQueue &track = trackDataNew[tk]; - if(track.empty()) - continue;//Empty track is useless! - - for(MidiTrackQueue::iterator it = track.begin(); it != track.end(); it++) - { - MidiTrackRow &pos = *it; - - for(ssize_t e = 0; e < (ssize_t)pos.events.size(); e++) - { - MidiEvent *et = &pos.events[(size_t)e]; - - /* Set MSB/LSB bank */ - if(et->type == MidiEvent::T_CTRLCHANGE) - { - uint8_t ctrlno = et->data[0]; - uint8_t value = et->data[1]; - switch(ctrlno) - { - case 0: // Set bank msb (GM bank) - banks[et->channel] = uint16_t(uint16_t(value) << 8) | uint16_t(banks[et->channel] & 0x00FF); - break; - case 32: // Set bank lsb (XG bank) - banks[et->channel] = (banks[et->channel] & 0xFF00) | (uint16_t(value) & 0x00FF); - break; - } - continue; - } - - bool percussion = (et->channel == 9) || - banks[et->channel] == 0x7E00 || //XG SFX1/SFX2 channel (16128 signed decimal) - banks[et->channel] == 0x7F00; //XG Percussion channel (16256 signed decimal) - if(!percussion) - continue; - - if(et->type == MidiEvent::T_NOTEON) - { - uint8_t note = et->data[0] & 0x7F; - NoteState &ns = drNotes[note]; - ns.isOn = true; - ns.delay = 0.0; - ns.delayTicks = 0; - } - else if(et->type == MidiEvent::T_NOTEOFF) - { - uint8_t note = et->data[0] & 0x7F; - NoteState &ns = drNotes[note]; - if(ns.isOn) - { - ns.isOn = false; - if(ns.delayTicks < DRUM_NOTE_MIN_TICKS || ns.delay < DRUM_NOTE_MIN_TIME)//If note is too short - { - //Move it into next event position if that possible - for(MidiTrackQueue::iterator itNext = it; - itNext != track.end(); - itNext++) - { - MidiTrackRow &posN = *itNext; - if(ns.delayTicks > DRUM_NOTE_MIN_TICKS && ns.delay > DRUM_NOTE_MIN_TIME) - { - //Put note-off into begin of next event list - posN.events.insert(posN.events.begin(), pos.events[(size_t)e]); - //Renive this event from a current row - pos.events.erase(pos.events.begin() + (int)e); - e--; - break; - } - ns.delay += posN.timeDelay; - ns.delayTicks += posN.delay; - } - } - ns.delay = 0.0; - ns.delayTicks = 0; - } - } - } - - //Append time delays to sustaining notes - for(size_t no = 0; no < 128; no++) - { - NoteState &ns = drNotes[no]; - if(ns.isOn) - { - ns.delay += pos.timeDelay; - ns.delayTicks += pos.delay; - } - } - } - } -#undef DRUM_NOTE_MIN_TIME -#undef DRUM_NOTE_MIN_TICKS - } -#endif - - return true; -} -#endif - - MIDIplay::MIDIplay(unsigned long sampleRate): cmf_percussion_mode(false), m_arpeggioCounter(0) #if defined(ADLMIDI_AUDIO_TICK_HANDLER) , m_audioTickCounter(0) #endif -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - , fullSongTimeLength(0.0), - postSongWaitDelay(1.0), - loopStartTime(-1.0), - loopEndTime(-1.0), - tempoMultiplier(1.0), - atEnd(false), - loopStart(false), - loopEnd(false), - invalidLoop(false) -#endif { devices.clear(); @@ -714,13 +140,15 @@ MIDIplay::MIDIplay(unsigned long sampleRate): m_setup.LogarithmicVolumes = false; m_setup.VolumeModel = ADLMIDI_VolumeModel_AUTO; //m_setup.SkipForward = 0; - m_setup.loopingIsEnabled = false; m_setup.ScaleModulators = -1; m_setup.fullRangeBrightnessCC74 = false; m_setup.delay = 0.0; m_setup.carry = 0.0; m_setup.tick_skip_samples_delay = 0; +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER + initSequencerInterface(); +#endif applySetup(); ChooseDevice("none"); realTime_ResetState(); @@ -766,79 +194,7 @@ void MIDIplay::applySetup() m_arpeggioCounter = 0; } -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER -uint64_t MIDIplay::ReadVarLen(uint8_t **ptr) -{ - uint64_t result = 0; - for(;;) - { - uint8_t byte = *((*ptr)++); - result = (result << 7) + (byte & 0x7F); - if(!(byte & 0x80)) - break; - } - return result; -} - -uint64_t MIDIplay::ReadVarLenEx(uint8_t **ptr, uint8_t *end, bool &ok) -{ - uint64_t result = 0; - ok = false; - - for(;;) - { - if(*ptr >= end) - return 2; - unsigned char byte = *((*ptr)++); - result = (result << 7) + (byte & 0x7F); - if(!(byte & 0x80)) - break; - } - - ok = true; - return result; -} - -double MIDIplay::Tick(double s, double granularity) -{ - s *= tempoMultiplier; -#ifdef ENABLE_BEGIN_SILENCE_SKIPPING - if(CurrentPositionNew.began) -#endif - CurrentPositionNew.wait -= s; - CurrentPositionNew.absTimePosition += s; - - int antiFreezeCounter = 10000;//Limit 10000 loops to avoid freezing - while((CurrentPositionNew.wait <= granularity * 0.5) && (antiFreezeCounter > 0)) - { - //std::fprintf(stderr, "wait = %g...\n", CurrentPosition.wait); - if(!ProcessEventsNew()) - break; - if(CurrentPositionNew.wait <= 0.0) - antiFreezeCounter--; - } - - if(antiFreezeCounter <= 0) - CurrentPositionNew.wait += 1.0;/* Add extra 1 second when over 10000 events - with zero delay are been detected */ - - for(uint16_t c = 0; c < opl.NumChannels; ++c) - ch[c].AddAge(static_cast<int64_t>(s * 1000.0)); - - UpdateVibrato(s); - UpdateArpeggio(s); -#if !defined(ADLMIDI_AUDIO_TICK_HANDLER) - UpdateGlide(s); -#endif - - if(CurrentPositionNew.wait < 0.0)//Avoid negative delay value! - return 0.0; - - return CurrentPositionNew.wait; -} -#endif /* ADLMIDI_DISABLE_MIDI_SEQUENCER */ - -void MIDIplay::TickIteratos(double s) +void MIDIplay::TickIterators(double s) { for(uint16_t c = 0; c < opl.NumChannels; ++c) ch[c].AddAge(static_cast<int64_t>(s * 1000.0)); @@ -849,115 +205,6 @@ void MIDIplay::TickIteratos(double s) #endif } -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - -void MIDIplay::seek(double seconds) -{ - if(seconds < 0.0) - return;//Seeking negative position is forbidden! :-P - const double granularity = m_setup.mindelay, - granualityHalf = granularity * 0.5, - s = seconds;//m_setup.delay < m_setup.maxdelay ? m_setup.delay : m_setup.maxdelay; - - /* Attempt to go away out of song end must rewind position to begin */ - if(seconds > fullSongTimeLength) - { - rewind(); - return; - } - - bool loopFlagState = m_setup.loopingIsEnabled; - // Turn loop pooints off because it causes wrong position rememberin on a quick seek - m_setup.loopingIsEnabled = false; - - /* - * Seeking search is similar to regular ticking, except of next things: - * - We don't processsing arpeggio and vibrato - * - To keep correctness of the state after seek, begin every search from begin - * - All sustaining notes must be killed - * - Ignore Note-On events - */ - rewind(); - - /* - * Set "loop Start" to false to prevent overwrite of loopStart position with - * seek destinition position - * - * TODO: Detect & set loopStart position on load time to don't break loop while seeking - */ - loopStart = false; - - while((CurrentPositionNew.absTimePosition < seconds) && - (CurrentPositionNew.absTimePosition < fullSongTimeLength)) - { - CurrentPositionNew.wait -= s; - CurrentPositionNew.absTimePosition += s; - int antiFreezeCounter = 10000;//Limit 10000 loops to avoid freezing - double dstWait = CurrentPositionNew.wait + granualityHalf; - while((CurrentPositionNew.wait <= granualityHalf)/*&& (antiFreezeCounter > 0)*/) - { - //std::fprintf(stderr, "wait = %g...\n", CurrentPosition.wait); - if(!ProcessEventsNew(true)) - break; - //Avoid freeze because of no waiting increasing in more than 10000 cycles - if(CurrentPositionNew.wait <= dstWait) - antiFreezeCounter--; - else - { - dstWait = CurrentPositionNew.wait + granualityHalf; - antiFreezeCounter = 10000; - } - } - if(antiFreezeCounter <= 0) - CurrentPositionNew.wait += 1.0;/* Add extra 1 second when over 10000 events - with zero delay are been detected */ - } - - if(CurrentPositionNew.wait < 0.0) - CurrentPositionNew.wait = 0.0; - - m_setup.loopingIsEnabled = loopFlagState; - m_setup.delay = CurrentPositionNew.wait; - m_setup.carry = 0.0; -} - -double MIDIplay::tell() -{ - return CurrentPositionNew.absTimePosition; -} - -double MIDIplay::timeLength() -{ - return fullSongTimeLength; -} - -double MIDIplay::getLoopStart() -{ - return loopStartTime; -} - -double MIDIplay::getLoopEnd() -{ - return loopEndTime; -} - -void MIDIplay::rewind() -{ - Panic(); - KillSustainingNotes(-1, -1); - CurrentPositionNew = trackBeginPositionNew; - atEnd = false; - loopStart = true; - loopEnd = false; - //invalidLoop = false;//No more needed here as this flag is set on load time -} - -void MIDIplay::setTempo(double tempo) -{ - tempoMultiplier = tempo; -} -#endif /* ADLMIDI_DISABLE_MIDI_SEQUENCER */ - void MIDIplay::realTime_ResetState() { for(size_t ch = 0; ch < Ch.size(); ch++) @@ -1465,6 +712,26 @@ void MIDIplay::realTime_panic() KillSustainingNotes(-1, -1); } +void MIDIplay::realTime_deviceSwitch(size_t track, const char *data, size_t length) +{ + const std::string indata(data, length); + current_device[track] = ChooseDevice(indata); +} + +uint64_t MIDIplay::realTime_currentDevice(size_t track) +{ + return current_device[track]; +} + +void MIDIplay::realTime_rawOPL(uint8_t reg, uint8_t value) +{ + if((reg & 0xF0) == 0xC0) + value |= 0x30; + //std::printf("OPL poke %02X, %02X\n", reg, value); + //std::fflush(stdout); + opl.Poke(0, reg, value); +} + #if defined(ADLMIDI_AUDIO_TICK_HANDLER) void MIDIplay::AudioTick(uint32_t chipId, uint32_t rate) { @@ -1652,17 +919,17 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, } /* DEBUG ONLY!!! - static uint32_t max = 0; + static uint32_t max = 0; - if(volume == 0) - max = 0; + if(volume == 0) + max = 0; - if(volume > max) - max = volume; + if(volume > max) + max = volume; - printf("%d\n", max); - fflush(stdout); - */ + printf("%d\n", max); + fflush(stdout); + */ } if(props_mask & Upd_Pitch) @@ -1707,339 +974,6 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, } } -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER -bool MIDIplay::ProcessEventsNew(bool isSeek) -{ - if(CurrentPositionNew.track.size() == 0) - atEnd = true;//No MIDI track data to play - if(atEnd) - return false;//No more events in the queue - - loopEnd = false; - const size_t TrackCount = CurrentPositionNew.track.size(); - const PositionNew RowBeginPosition(CurrentPositionNew); - -#ifdef DEBUG_TIME_CALCULATION - double maxTime = 0.0; -#endif - - for(size_t tk = 0; tk < TrackCount; ++tk) - { - PositionNew::TrackInfo &track = CurrentPositionNew.track[tk]; - if((track.status >= 0) && (track.delay <= 0)) - { - //Check is an end of track has been reached - if(track.pos == trackDataNew[tk].end()) - { - track.status = -1; - break; - } - - // Handle event - for(size_t i = 0; i < track.pos->events.size(); i++) - { - const MidiEvent &evt = track.pos->events[i]; -#ifdef ENABLE_BEGIN_SILENCE_SKIPPING - if(!CurrentPositionNew.began && (evt.type == MidiEvent::T_NOTEON)) - CurrentPositionNew.began = true; -#endif - if(isSeek && (evt.type == MidiEvent::T_NOTEON)) - continue; - HandleEvent(tk, evt, track.status); - if(loopEnd) - break;//Stop event handling on catching loopEnd event! - } - -#ifdef DEBUG_TIME_CALCULATION - if(maxTime < track.pos->time) - maxTime = track.pos->time; -#endif - // Read next event time (unless the track just ended) - if(track.status >= 0) - { - track.delay += track.pos->delay; - track.pos++; - } - } - } - -#ifdef DEBUG_TIME_CALCULATION - std::fprintf(stdout, " \r"); - std::fprintf(stdout, "Time: %10f; Audio: %10f\r", maxTime, CurrentPositionNew.absTimePosition); - std::fflush(stdout); -#endif - - // Find shortest delay from all track - uint64_t shortest = 0; - bool shortest_no = true; - - for(size_t tk = 0; tk < TrackCount; ++tk) - { - PositionNew::TrackInfo &track = CurrentPositionNew.track[tk]; - if((track.status >= 0) && (shortest_no || track.delay < shortest)) - { - shortest = track.delay; - shortest_no = false; - } - } - - //if(shortest > 0) UI.PrintLn("shortest: %ld", shortest); - - // Schedule the next playevent to be processed after that delay - for(size_t tk = 0; tk < TrackCount; ++tk) - CurrentPositionNew.track[tk].delay -= shortest; - - fraction<uint64_t> t = shortest * Tempo; - -#ifdef ENABLE_BEGIN_SILENCE_SKIPPING - if(CurrentPositionNew.began) -#endif - CurrentPositionNew.wait += t.value(); - - //if(shortest > 0) UI.PrintLn("Delay %ld (%g)", shortest, (double)t.valuel()); - if(loopStart) - { - LoopBeginPositionNew = RowBeginPosition; - loopStart = false; - } - - if(shortest_no || loopEnd) - { - //Loop if song end or loop end point has reached - loopEnd = false; - shortest = 0; - if(!m_setup.loopingIsEnabled) - { - atEnd = true; //Don't handle events anymore - CurrentPositionNew.wait += postSongWaitDelay;//One second delay until stop playing - return true;//We have caugh end here! - } - CurrentPositionNew = LoopBeginPositionNew; - } - - return true;//Has events in queue -} - -MIDIplay::MidiEvent MIDIplay::parseEvent(uint8_t **pptr, uint8_t *end, int &status) -{ - uint8_t *&ptr = *pptr; - MIDIplay::MidiEvent evt; - - if(ptr + 1 > end) - { - //When track doesn't ends on the middle of event data, it's must be fine - evt.type = MidiEvent::T_SPECIAL; - evt.subtype = MidiEvent::ST_ENDTRACK; - return evt; - } - - unsigned char byte = *(ptr++); - bool ok = false; - - if(byte == MidiEvent::T_SYSEX || byte == MidiEvent::T_SYSEX2)// Ignore SysEx - { - uint64_t length = ReadVarLenEx(pptr, end, ok); - if(!ok || (ptr + length > end)) - { - errorString += "parseEvent: Can't read SysEx event - Unexpected end of track data.\n"; - evt.isValid = 0; - return evt; - } - ptr += (size_t)length; - return evt; - } - - if(byte == MidiEvent::T_SPECIAL) - { - // Special event FF - uint8_t evtype = *(ptr++); - uint64_t length = ReadVarLenEx(pptr, end, ok); - if(!ok || (ptr + length > end)) - { - errorString += "parseEvent: Can't read Special event - Unexpected end of track data.\n"; - evt.isValid = 0; - return evt; - } - std::string data(length ? (const char *)ptr : 0, (size_t)length); - ptr += (size_t)length; - - evt.type = byte; - evt.subtype = evtype; - evt.data.insert(evt.data.begin(), data.begin(), data.end()); - -#if 0 /* Print all tempo events */ - if(evt.subtype == MidiEvent::ST_TEMPOCHANGE) - { - if(hooks.onDebugMessage) - hooks.onDebugMessage(hooks.onDebugMessage_userData, "Temp Change: %02X%02X%02X", evt.data[0], evt.data[1], evt.data[2]); - } -#endif - - /* TODO: Store those meta-strings separately and give ability to read them - * by external functions (to display song title and copyright in the player) */ - if(evt.subtype == MidiEvent::ST_COPYRIGHT) - { - if(musCopyright.empty()) - { - musCopyright = std::string((const char *)evt.data.data(), evt.data.size()); - if(hooks.onDebugMessage) - hooks.onDebugMessage(hooks.onDebugMessage_userData, "Music copyright: %s", musCopyright.c_str()); - } - else if(hooks.onDebugMessage) - { - std::string str((const char *)evt.data.data(), evt.data.size()); - hooks.onDebugMessage(hooks.onDebugMessage_userData, "Extra copyright event: %s", str.c_str()); - } - } - else if(evt.subtype == MidiEvent::ST_SQTRKTITLE) - { - if(musTitle.empty()) - { - musTitle = std::string((const char *)evt.data.data(), evt.data.size()); - if(hooks.onDebugMessage) - hooks.onDebugMessage(hooks.onDebugMessage_userData, "Music title: %s", musTitle.c_str()); - } - else if(hooks.onDebugMessage) - { - //TODO: Store track titles and associate them with each track and make API to retreive them - std::string str((const char *)evt.data.data(), evt.data.size()); - musTrackTitles.push_back(str); - hooks.onDebugMessage(hooks.onDebugMessage_userData, "Track title: %s", str.c_str()); - } - } - else if(evt.subtype == MidiEvent::ST_INSTRTITLE) - { - if(hooks.onDebugMessage) - { - std::string str((const char *)evt.data.data(), evt.data.size()); - hooks.onDebugMessage(hooks.onDebugMessage_userData, "Instrument: %s", str.c_str()); - } - } - else if(evt.subtype == MidiEvent::ST_MARKER) - { - //To lower - for(size_t i = 0; i < data.size(); i++) - { - if(data[i] <= 'Z' && data[i] >= 'A') - data[i] = data[i] - ('Z' - 'z'); - } - - if(data == "loopstart") - { - //Return a custom Loop Start event instead of Marker - evt.subtype = MidiEvent::ST_LOOPSTART; - evt.data.clear();//Data is not needed - return evt; - } - - if(data == "loopend") - { - //Return a custom Loop End event instead of Marker - evt.subtype = MidiEvent::ST_LOOPEND; - evt.data.clear();//Data is not needed - return evt; - } - } - - if(evtype == MidiEvent::ST_ENDTRACK) - status = -1;//Finalize track - - return evt; - } - - // Any normal event (80..EF) - if(byte < 0x80) - { - byte = static_cast<uint8_t>(status | 0x80); - ptr--; - } - - //Sys Com Song Select(Song #) [0-127] - if(byte == MidiEvent::T_SYSCOMSNGSEL) - { - if(ptr + 1 > end) - { - errorString += "parseEvent: Can't read System Command Song Select event - Unexpected end of track data.\n"; - evt.isValid = 0; - return evt; - } - evt.type = byte; - evt.data.push_back(*(ptr++)); - return evt; - } - - //Sys Com Song Position Pntr [LSB, MSB] - if(byte == MidiEvent::T_SYSCOMSPOSPTR) - { - if(ptr + 2 > end) - { - errorString += "parseEvent: Can't read System Command Position Pointer event - Unexpected end of track data.\n"; - evt.isValid = 0; - return evt; - } - evt.type = byte; - evt.data.push_back(*(ptr++)); - evt.data.push_back(*(ptr++)); - return evt; - } - - uint8_t midCh = byte & 0x0F, evType = (byte >> 4) & 0x0F; - status = byte; - evt.channel = midCh; - evt.type = evType; - - switch(evType) - { - case MidiEvent::T_NOTEOFF://2 byte length - case MidiEvent::T_NOTEON: - case MidiEvent::T_NOTETOUCH: - case MidiEvent::T_CTRLCHANGE: - case MidiEvent::T_WHEEL: - if(ptr + 2 > end) - { - errorString += "parseEvent: Can't read regular 2-byte event - Unexpected end of track data.\n"; - evt.isValid = 0; - return evt; - } - - evt.data.push_back(*(ptr++)); - evt.data.push_back(*(ptr++)); - - /* TODO: Implement conversion of RSXX's note volumes out of synthesizer */ - /*if((opl.m_musicMode == OPL3::MODE_RSXX) && (evType == MidiEvent::T_NOTEON) && (evt.data[1] != 0)) - { - //NOT WORKING YET - evt.type = MidiEvent::T_NOTETOUCH; - } - else */if((evType == MidiEvent::T_NOTEON) && (evt.data[1] == 0)) - { - evt.type = MidiEvent::T_NOTEOFF; // Note ON with zero velocity is Note OFF! - } //111'th loopStart controller (RPG Maker and others) - else if((evType == MidiEvent::T_CTRLCHANGE) && (evt.data[0] == 111)) - { - //Change event type to custom Loop Start event and clear data - evt.type = MidiEvent::T_SPECIAL; - evt.subtype = MidiEvent::ST_LOOPSTART; - evt.data.clear(); - } - - return evt; - case MidiEvent::T_PATCHCHANGE://1 byte length - case MidiEvent::T_CHANAFTTOUCH: - if(ptr + 1 > end) - { - errorString += "parseEvent: Can't read regular 1-byte event - Unexpected end of track data.\n"; - evt.isValid = 0; - return evt; - } - evt.data.push_back(*(ptr++)); - return evt; - } - - return evt; -} -#endif /* ADLMIDI_DISABLE_MIDI_SEQUENCER */ - const std::string &MIDIplay::getErrorString() { return errorStringOut; @@ -2050,165 +984,6 @@ void MIDIplay::setErrorString(const std::string &err) errorStringOut = err; } -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER -void MIDIplay::HandleEvent(size_t tk, const MIDIplay::MidiEvent &evt, int &status) -{ - if(hooks.onEvent) - { - hooks.onEvent(hooks.onEvent_userData, - evt.type, - evt.subtype, - evt.channel, - evt.data.data(), - evt.data.size()); - } - - if(evt.type == MidiEvent::T_SYSEX || evt.type == MidiEvent::T_SYSEX2) // Ignore SysEx - { - //std::string data( length?(const char*) &TrackData[tk][CurrentPosition.track[tk].ptr]:0, length ); - //UI.PrintLn("SysEx %02X: %u bytes", byte, length/*, data.c_str()*/); - return; - } - - if(evt.type == MidiEvent::T_SPECIAL) - { - // Special event FF - uint8_t evtype = evt.subtype; - uint64_t length = (uint64_t)evt.data.size(); - std::string data(length ? (const char *)evt.data.data() : 0, (size_t)length); - - if(evtype == MidiEvent::ST_ENDTRACK)//End Of Track - { - status = -1; - return; - } - - if(evtype == MidiEvent::ST_TEMPOCHANGE)//Tempo change - { - Tempo = InvDeltaTicks * fraction<uint64_t>(ReadBEint(evt.data.data(), evt.data.size())); - return; - } - - if(evtype == MidiEvent::ST_MARKER)//Meta event - { - //Do nothing! :-P - return; - } - - if(evtype == MidiEvent::ST_DEVICESWITCH) - { - current_device[tk] = ChooseDevice(data); - return; - } - - //if(evtype >= 1 && evtype <= 6) - // UI.PrintLn("Meta %d: %s", evtype, data.c_str()); - - //Turn on Loop handling when loop is enabled - if(m_setup.loopingIsEnabled && !invalidLoop) - { - if(evtype == MidiEvent::ST_LOOPSTART) // Special non-spec ADLMIDI special for IMF playback: Direct poke to AdLib - { - loopStart = true; - return; - } - - if(evtype == MidiEvent::ST_LOOPEND) // Special non-spec ADLMIDI special for IMF playback: Direct poke to AdLib - { - loopEnd = true; - return; - } - } - - if(evtype == MidiEvent::ST_RAWOPL) // Special non-spec ADLMIDI special for IMF playback: Direct poke to AdLib - { - uint8_t i = static_cast<uint8_t>(data[0]), v = static_cast<uint8_t>(data[1]); - if((i & 0xF0) == 0xC0) - v |= 0x30; - //std::printf("OPL poke %02X, %02X\n", i, v); - //std::fflush(stdout); - opl.Poke(0, i, v); - return; - } - - return; - } - - // Any normal event (80..EF) - // if(evt.type < 0x80) - // { - // byte = static_cast<uint8_t>(CurrentPosition.track[tk].status | 0x80); - // CurrentPosition.track[tk].ptr--; - // } - - if(evt.type == MidiEvent::T_SYSCOMSNGSEL || - evt.type == MidiEvent::T_SYSCOMSPOSPTR) - return; - - /*UI.PrintLn("@%X Track %u: %02X %02X", - CurrentPosition.track[tk].ptr-1, (unsigned)tk, byte, - TrackData[tk][CurrentPosition.track[tk].ptr]);*/ - uint8_t midCh = evt.channel;//byte & 0x0F, EvType = byte >> 4; - midCh += (uint8_t)current_device[tk]; - status = evt.type; - - switch(evt.type) - { - case MidiEvent::T_NOTEOFF: // Note off - { - uint8_t note = evt.data[0]; - realTime_NoteOff(midCh, note); - break; - } - - case MidiEvent::T_NOTEON: // Note on - { - uint8_t note = evt.data[0]; - uint8_t vol = evt.data[1]; - /*if(*/ realTime_NoteOn(midCh, note, vol); /*)*/ - //CurrentPosition.began = true; - break; - } - - case MidiEvent::T_NOTETOUCH: // Note touch - { - uint8_t note = evt.data[0]; - uint8_t vol = evt.data[1]; - realTime_NoteAfterTouch(midCh, note, vol); - break; - } - - case MidiEvent::T_CTRLCHANGE: // Controller change - { - uint8_t ctrlno = evt.data[0]; - uint8_t value = evt.data[1]; - realTime_Controller(midCh, ctrlno, value); - break; - } - - case MidiEvent::T_PATCHCHANGE: // Patch change - realTime_PatchChange(midCh, evt.data[0]); - break; - - case MidiEvent::T_CHANAFTTOUCH: // Channel after-touch - { - // TODO: Verify, is this correct action? - uint8_t vol = evt.data[0]; - realTime_ChannelAfterTouch(midCh, vol); - break; - } - - case MidiEvent::T_WHEEL: // Wheel/pitch bend - { - uint8_t a = evt.data[0]; - uint8_t b = evt.data[1]; - realTime_PitchBend(midCh, b, a); - break; - } - } -} -#endif /* ADLMIDI_DISABLE_MIDI_SEQUENCER */ - int64_t MIDIplay::CalculateAdlChannelGoodness(size_t c, const MIDIchannel::NoteInfo::Phys &ins, uint16_t) const { int64_t s = -ch[c].koff_time_until_neglible; @@ -2508,9 +1283,6 @@ void MIDIplay::UpdateVibrato(double amount) } } - - - uint64_t MIDIplay::ChooseDevice(const std::string &name) { std::map<std::string, uint64_t>::iterator i = devices.find(name); diff --git a/src/adlmidi_mus2mid.h b/src/adlmidi_mus2mid.h deleted file mode 100644 index cc41b87..0000000 --- a/src/adlmidi_mus2mid.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * MUS2MIDI: DMX (DOOM) MUS to MIDI Library Header - * - * Copyright (C) 2014-2016 Bret Curtis - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#ifndef MUSLIB_H -#define MUSLIB_H - -#include <stdint.h> - -#ifdef __DJGPP__ -typedef signed char int8_t; -typedef unsigned char uint8_t; -typedef signed short int16_t; -typedef unsigned short uint16_t; -typedef signed long int32_t; -typedef unsigned long uint32_t; -#endif - -#ifdef __cplusplus -extern "C" -{ -#endif - -int AdlMidi_mus2midi(uint8_t *in, uint32_t insize, - uint8_t **out, uint32_t *outsize, - uint16_t frequency); - -#ifdef __cplusplus -} -#endif - -#endif /* MUSLIB_H */ diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp index 13160b2..446bae0 100644 --- a/src/adlmidi_private.hpp +++ b/src/adlmidi_private.hpp @@ -132,7 +132,14 @@ typedef int32_t ssize_t; #endif #include "file_reader.hpp" -#include "fraction.hpp" + +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER +// Rename class to avoid ABI conflicts +#define BW_MidiSequencer AdlMidiSequencer +#include "midi_sequencer.hpp" +typedef BW_MidiSequencer MidiSequencer; +#endif//ADLMIDI_DISABLE_MIDI_SEQUENCER + #ifndef ADLMIDI_HW_OPL #include "chips/opl_chip_base.h" #endif @@ -307,17 +314,11 @@ public: struct MIDIEventHooks { MIDIEventHooks() : - onEvent(NULL), - onEvent_userData(NULL), onNote(NULL), onNote_userData(NULL), onDebugMessage(NULL), onDebugMessage_userData(NULL) {} - //! Raw MIDI event hook - typedef void (*RawEventHook)(void *userdata, uint8_t type, uint8_t subtype, uint8_t channel, const uint8_t *data, size_t len); - RawEventHook onEvent; - void *onEvent_userData; //! Note on/off hooks typedef void (*NoteHook)(void *userdata, int adlchn, int note, int ins, int pressure, double bend); @@ -669,130 +670,20 @@ public: #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER /** - * @brief MIDI Event utility container + * @brief MIDI files player sequencer */ - class MidiEvent - { - public: - MidiEvent(); - - enum Types - { - T_UNKNOWN = 0x00, - T_NOTEOFF = 0x08,//size == 2 - T_NOTEON = 0x09,//size == 2 - T_NOTETOUCH = 0x0A,//size == 2 - T_CTRLCHANGE = 0x0B,//size == 2 - T_PATCHCHANGE = 0x0C,//size == 1 - T_CHANAFTTOUCH = 0x0D,//size == 1 - T_WHEEL = 0x0E,//size == 2 - - T_SYSEX = 0xF0,//size == len - T_SYSCOMSPOSPTR = 0xF2,//size == 2 - T_SYSCOMSNGSEL = 0xF3,//size == 1 - T_SYSEX2 = 0xF7,//size == len - T_SPECIAL = 0xFF - }; - enum SubTypes - { - ST_SEQNUMBER = 0x00,//size == 2 - ST_TEXT = 0x01,//size == len - ST_COPYRIGHT = 0x02,//size == len - ST_SQTRKTITLE = 0x03,//size == len - ST_INSTRTITLE = 0x04,//size == len - ST_LYRICS = 0x05,//size == len - ST_MARKER = 0x06,//size == len - ST_CUEPOINT = 0x07,//size == len - ST_DEVICESWITCH = 0x09,//size == len <CUSTOM> - ST_MIDICHPREFIX = 0x20,//size == 1 - - ST_ENDTRACK = 0x2F,//size == 0 - ST_TEMPOCHANGE = 0x51,//size == 3 - ST_SMPTEOFFSET = 0x54,//size == 5 - ST_TIMESIGNATURE = 0x55, //size == 4 - ST_KEYSIGNATURE = 0x59,//size == 2 - ST_SEQUENCERSPEC = 0x7F, //size == len - - /* Non-standard, internal ADLMIDI usage only */ - ST_LOOPSTART = 0xE1,//size == 0 <CUSTOM> - ST_LOOPEND = 0xE2,//size == 0 <CUSTOM> - ST_RAWOPL = 0xE3//size == 0 <CUSTOM> - }; - //! Main type of event - uint8_t type; - //! Sub-type of the event - uint8_t subtype; - //! Targeted MIDI channel - uint8_t channel; - //! Is valid event - uint8_t isValid; - //! Reserved 5 bytes padding - uint8_t __padding[4]; - //! Absolute tick position (Used for the tempo calculation only) - uint64_t absPosition; - //! Raw data of this event - std::vector<uint8_t> data; - }; + MidiSequencer m_sequencer; /** - * @brief A track position event contains a chain of MIDI events until next delay value - * - * Created with purpose to sort events by type in the same position - * (for example, to keep controllers always first than note on events or lower than note-off events) + * @brief Interface between MIDI sequencer and this library */ - class MidiTrackRow - { - public: - MidiTrackRow(); - void reset(); - //! Absolute time position in seconds - double time; - //! Delay to next event in ticks - uint64_t delay; - //! Absolute position in ticks - uint64_t absPos; - //! Delay to next event in seconds - double timeDelay; - std::vector<MidiEvent> events; - /** - * @brief Sort events in this position - */ - void sortEvents(bool *noteStates = NULL); - }; + BW_MidiRtInterface m_sequencerInterface; /** - * @brief Tempo change point entry. Used in the MIDI data building function only. + * @brief Initialize MIDI sequencer interface */ - struct TempoChangePoint - { - uint64_t absPos; - fraction<uint64_t> tempo; - }; - //P.S. I declared it here instead of local in-function because C++99 can't process templates with locally-declared structures - - typedef std::list<MidiTrackRow> MidiTrackQueue; - - // Information about each track - struct PositionNew - { - bool began; - char padding[7]; - double wait; - double absTimePosition; - struct TrackInfo - { - size_t ptr; - uint64_t delay; - int status; - char padding2[4]; - MidiTrackQueue::iterator pos; - TrackInfo(): ptr(0), delay(0), status(0) {} - }; - std::vector<TrackInfo> track; - PositionNew(): began(false), wait(0.0), absTimePosition(0.0), track() - {} - }; -#endif//ADLMIDI_DISABLE_MIDI_SEQUENCER + void initSequencerInterface(); +#endif //ADLMIDI_DISABLE_MIDI_SEQUENCER struct Setup { @@ -807,7 +698,6 @@ public: bool LogarithmicVolumes; int VolumeModel; //unsigned int SkipForward; - bool loopingIsEnabled; int ScaleModulators; bool fullRangeBrightnessCC74; @@ -854,31 +744,9 @@ private: uint32_t m_audioTickCounter; #endif -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - std::vector<std::vector<uint8_t> > TrackData; - - PositionNew CurrentPositionNew, LoopBeginPositionNew, trackBeginPositionNew; - - //! Full song length in seconds - double fullSongTimeLength; - //! Delay after song playd before rejecting the output stream requests - double postSongWaitDelay; - - //! Loop start time - double loopStartTime; - //! Loop end time - double loopEndTime; -#endif - //! Local error string - std::string errorString; //! Local error string std::string errorStringOut; -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - //! Pre-processed track data storage - std::vector<MidiTrackQueue > trackDataNew; -#endif - //! Missing instruments catches std::set<uint8_t> caugh_missing_instruments; //! Missing melodic banks catches @@ -886,90 +754,49 @@ private: //! Missing percussion banks catches std::set<uint16_t> caugh_missing_banks_percussion; -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - /** - * @brief Build MIDI track data from the raw track data storage - * @return true if everything successfully processed, or false on any error - */ - bool buildTrackData(); - - /** - * @brief Parse one event from raw MIDI track stream - * @param [_inout] ptr pointer to pointer to current position on the raw data track - * @param [_in] end address to end of raw track data, needed to validate position and size - * @param [_inout] status status of the track processing - * @return Parsed MIDI event entry - */ - MidiEvent parseEvent(uint8_t **ptr, uint8_t *end, int &status); -#endif//ADLMIDI_DISABLE_MIDI_SEQUENCER - public: const std::string &getErrorString(); void setErrorString(const std::string &err); -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - std::string musTitle; - std::string musCopyright; - std::vector<std::string> musTrackTitles; - std::vector<MIDI_MarkerEntry> musMarkers; - - fraction<uint64_t> InvDeltaTicks, Tempo; - //! Tempo multiplier - double tempoMultiplier; - bool atEnd, - loopStart, - loopEnd, - invalidLoop; /*Loop points are invalid (loopStart after loopEnd or loopStart and loopEnd are on same place)*/ - char ____padding2[2]; -#endif OPL3 opl; int32_t outBuf[1024]; Setup m_setup; + bool LoadBank(const std::string &filename); + bool LoadBank(const void *data, size_t size); + bool LoadBank(FileAndMemReader &fr); + #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER /** - * @brief Utility function to read Big-Endian integer from raw binary data - * @param buffer Pointer to raw binary buffer - * @param nbytes Count of bytes to parse integer - * @return Extracted unsigned integer + * @brief MIDI file loading pre-process + * @return true on success, false on failure */ - static uint64_t ReadBEint(const void *buffer, size_t nbytes); + bool LoadMIDI_pre(); /** - * @brief Utility function to read Little-Endian integer from raw binary data - * @param buffer Pointer to raw binary buffer - * @param nbytes Count of bytes to parse integer - * @return Extracted unsigned integer + * @brief MIDI file loading post-process + * @return true on success, false on failure */ - static uint64_t ReadLEint(const void *buffer, size_t nbytes); + bool LoadMIDI_post(); /** - * @brief Standard MIDI Variable-Length numeric value parser without of validation - * @param [_inout] ptr Pointer to memory block that contains begin of variable-length value - * @return Unsigned integer that conains parsed variable-length value - */ - uint64_t ReadVarLen(uint8_t **ptr); - /** - * @brief Secure Standard MIDI Variable-Length numeric value parser with anti-out-of-range protection - * @param [_inout] ptr Pointer to memory block that contains begin of variable-length value, will be iterated forward - * @param [_in end Pointer to end of memory block where variable-length value is stored (after end of track) - * @param [_out] ok Reference to boolean which takes result of variable-length value parsing - * @return Unsigned integer that conains parsed variable-length value + * @brief Load music file from a file + * @param filename Path to music file + * @return true on success, false on failure */ - uint64_t ReadVarLenEx(uint8_t **ptr, uint8_t *end, bool &ok); -#endif - - bool LoadBank(const std::string &filename); - bool LoadBank(const void *data, size_t size); - bool LoadBank(FileAndMemReader &fr); -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER bool LoadMIDI(const std::string &filename); + + /** + * @brief Load music file from the memory block + * @param data pointer to the memory block + * @param size size of memory block + * @return true on success, false on failure + */ bool LoadMIDI(const void *data, size_t size); - bool LoadMIDI(FileAndMemReader &fr); /** * @brief Periodic tick handler. @@ -978,79 +805,129 @@ public: * @return desired number of seconds until next call */ double Tick(double s, double granularity); -#endif +#endif //ADLMIDI_DISABLE_MIDI_SEQUENCER /** * @brief Process extra iterators like vibrato or arpeggio * @param s seconds since last call */ - void TickIteratos(double s); + void TickIterators(double s); -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - /** - * @brief Change current position to specified time position in seconds - * @param seconds Absolute time position in seconds - */ - void seek(double seconds); + /* RealTime event triggers */ /** - * @brief Gives current time position in seconds - * @return Current time position in seconds + * @brief Reset state of all channels */ - double tell(); + void realTime_ResetState(); /** - * @brief Gives time length of current song in seconds - * @return Time length of current song in seconds + * @brief Note On event + * @param channel MIDI channel + * @param note Note key (from 0 to 127) + * @param velocity Velocity level (from 0 to 127) + * @return true if Note On event was accepted */ - double timeLength(); + bool realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity); /** - * @brief Gives loop start time position in seconds - * @return Loop start time position in seconds or -1 if song has no loop points + * @brief Note Off event + * @param channel MIDI channel + * @param note Note key (from 0 to 127) */ - double getLoopStart(); + void realTime_NoteOff(uint8_t channel, uint8_t note); /** - * @brief Gives loop end time position in seconds - * @return Loop end time position in seconds or -1 if song has no loop points + * @brief Note aftertouch event + * @param channel MIDI channel + * @param note Note key (from 0 to 127) + * @param atVal After-Touch level (from 0 to 127) */ - double getLoopEnd(); + void realTime_NoteAfterTouch(uint8_t channel, uint8_t note, uint8_t atVal); /** - * @brief Return to begin of current song + * @brief Channel aftertouch event + * @param channel MIDI channel + * @param atVal After-Touch level (from 0 to 127) */ - void rewind(); + void realTime_ChannelAfterTouch(uint8_t channel, uint8_t atVal); /** - * @brief Set tempo multiplier - * @param tempo Tempo multiplier: 1.0 - original tempo. >1 - faster, <1 - slower + * @brief Controller Change event + * @param channel MIDI channel + * @param type Type of controller + * @param value Value of the controller (from 0 to 127) */ - void setTempo(double tempo); -#endif//ADLMIDI_DISABLE_MIDI_SEQUENCER - - /* RealTime event triggers */ - void realTime_ResetState(); - - bool realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity); - void realTime_NoteOff(uint8_t channel, uint8_t note); - - void realTime_NoteAfterTouch(uint8_t channel, uint8_t note, uint8_t atVal); - void realTime_ChannelAfterTouch(uint8_t channel, uint8_t atVal); - void realTime_Controller(uint8_t channel, uint8_t type, uint8_t value); + /** + * @brief Patch change + * @param channel MIDI channel + * @param patch Patch Number (from 0 to 127) + */ void realTime_PatchChange(uint8_t channel, uint8_t patch); + /** + * @brief Pitch bend change + * @param channel MIDI channel + * @param pitch Concoctated raw pitch value + */ void realTime_PitchBend(uint8_t channel, uint16_t pitch); + + /** + * @brief Pitch bend change + * @param channel MIDI channel + * @param msb MSB of pitch value + * @param lsb LSB of pitch value + */ void realTime_PitchBend(uint8_t channel, uint8_t msb, uint8_t lsb); + /** + * @brief LSB Bank Change CC + * @param channel MIDI channel + * @param lsb LSB value of bank number + */ void realTime_BankChangeLSB(uint8_t channel, uint8_t lsb); + + /** + * @brief MSB Bank Change CC + * @param channel MIDI channel + * @param lsb MSB value of bank number + */ void realTime_BankChangeMSB(uint8_t channel, uint8_t msb); + + /** + * @brief Bank Change (united value) + * @param channel MIDI channel + * @param bank Bank number value + */ void realTime_BankChange(uint8_t channel, uint16_t bank); + /** + * @brief Turn off all notes and mute the sound of releasing notes + */ void realTime_panic(); + /** + * @brief Device switch (to extend 16-channels limit of MIDI standard) + * @param track MIDI track index + * @param data Device name + * @param length Length of device name string + */ + void realTime_deviceSwitch(size_t track, const char *data, size_t length); + /** + * @brief Currently selected device index + * @param track MIDI track index + * @return Multiple 16 value + */ + uint64_t realTime_currentDevice(size_t track); + + /** + * @brief Send raw OPL chip command + * @param reg OPL Register + * @param value Value to write + */ + void realTime_rawOPL(uint8_t reg, uint8_t value); + #if defined(ADLMIDI_AUDIO_TICK_HANDLER) // Audio rate tick handler void AudioTick(uint32_t chipId, uint32_t rate); @@ -1074,11 +951,6 @@ private: unsigned props_mask, int32_t select_adlchn = -1); -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - bool ProcessEventsNew(bool isSeek = false); - void HandleEvent(size_t tk, const MidiEvent &evt, int &status); -#endif - // Determine how good a candidate this adlchannel // would be for playing a note from this instrument. int64_t CalculateAdlChannelGoodness(size_t c, const MIDIchannel::NoteInfo::Phys &ins, uint16_t /*MidCh*/) const; diff --git a/src/adlmidi_sequencer.cpp b/src/adlmidi_sequencer.cpp new file mode 100644 index 0000000..8bc3569 --- /dev/null +++ b/src/adlmidi_sequencer.cpp @@ -0,0 +1,153 @@ +/* + * libADLMIDI is a free MIDI to WAV conversion library with OPL3 emulation + * + * Original ADLMIDI code: Copyright (c) 2010-2014 Joel Yliluoma <bisqwit@iki.fi> + * ADLMIDI Library API: Copyright (c) 2015-2018 Vitaly Novichkov <admin@wohlnet.ru> + * + * Library is based on the ADLMIDI, a MIDI player for Linux and Windows with OPL3 emulation: + * http://iki.fi/bisqwit/source/adlmidi.html + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER + +#include "adlmidi_private.hpp" + +// Inlucde MIDI sequencer class implementation +#include "midi_sequencer_impl.hpp" + + +/**************************************************** + * Real-Time MIDI calls proxies * + ****************************************************/ + +static void rtNoteOn(void *userdata, uint8_t channel, uint8_t note, uint8_t velocity) +{ + MIDIplay *context = reinterpret_cast<MIDIplay *>(userdata); + context->realTime_NoteOn(channel, note, velocity); +} + +static void rtNoteOff(void *userdata, uint8_t channel, uint8_t note) +{ + MIDIplay *context = reinterpret_cast<MIDIplay *>(userdata); + context->realTime_NoteOff(channel, note); +} + +static void rtNoteAfterTouch(void *userdata, uint8_t channel, uint8_t note, uint8_t atVal) +{ + MIDIplay *context = reinterpret_cast<MIDIplay *>(userdata); + context->realTime_NoteAfterTouch(channel, note, atVal); +} + +static void rtChannelAfterTouch(void *userdata, uint8_t channel, uint8_t atVal) +{ + MIDIplay *context = reinterpret_cast<MIDIplay *>(userdata); + context->realTime_ChannelAfterTouch(channel, atVal); +} + +static void rtControllerChange(void *userdata, uint8_t channel, uint8_t type, uint8_t value) +{ + MIDIplay *context = reinterpret_cast<MIDIplay *>(userdata); + context->realTime_Controller(channel, type, value); +} + +static void rtPatchChange(void *userdata, uint8_t channel, uint8_t patch) +{ + MIDIplay *context = reinterpret_cast<MIDIplay *>(userdata); + context->realTime_PatchChange(channel, patch); +} + +static void rtPitchBend(void *userdata, uint8_t channel, uint8_t msb, uint8_t lsb) +{ + MIDIplay *context = reinterpret_cast<MIDIplay *>(userdata); + context->realTime_PitchBend(channel, msb, lsb); +} + +static void rtSysEx(void *userdata, const uint8_t *msg, size_t size) +{ + MIDIplay *context = reinterpret_cast<MIDIplay *>(userdata); + ADL_UNUSED(context); + ADL_UNUSED(msg); + ADL_UNUSED(size); + /* TODO: pass SysEx HERE! */ +} + + +/* NonStandard calls */ +static void rtRawOPL(void *userdata, uint8_t reg, uint8_t value) +{ + MIDIplay *context = reinterpret_cast<MIDIplay *>(userdata); + return context->realTime_rawOPL(reg, value); +} + +static void rtDeviceSwitch(void *userdata, size_t track, const char *data, size_t length) +{ + MIDIplay *context = reinterpret_cast<MIDIplay *>(userdata); + context->realTime_deviceSwitch(track, data, length); +} + +static uint64_t rtCurrentDevice(void *userdata, size_t track) +{ + MIDIplay *context = reinterpret_cast<MIDIplay *>(userdata); + return context->realTime_currentDevice(track); +} +/* NonStandard calls End */ + + +void MIDIplay::initSequencerInterface() +{ + std::memset(&m_sequencerInterface, 0, sizeof(BW_MidiRtInterface)); + + m_sequencerInterface.onDebugMessage = hooks.onDebugMessage; + m_sequencerInterface.onDebugMessage_userData = hooks.onDebugMessage_userData; + + /* MIDI Real-Time calls */ + m_sequencerInterface.rtUserData = this; + m_sequencerInterface.rt_noteOn = rtNoteOn; + m_sequencerInterface.rt_noteOff = rtNoteOff; + m_sequencerInterface.rt_noteAfterTouch = rtNoteAfterTouch; + m_sequencerInterface.rt_channelAfterTouch = rtChannelAfterTouch; + m_sequencerInterface.rt_controllerChange = rtControllerChange; + m_sequencerInterface.rt_patchChange = rtPatchChange; + m_sequencerInterface.rt_pitchBend = rtPitchBend; + m_sequencerInterface.rt_systemExclusive = rtSysEx; + + /* NonStandard calls */ + m_sequencerInterface.rt_rawOPL = rtRawOPL; + m_sequencerInterface.rt_deviceSwitch = rtDeviceSwitch; + m_sequencerInterface.rt_currentDevice = rtCurrentDevice; + /* NonStandard calls End */ + + m_sequencer.setInterface(&m_sequencerInterface); +} + +double MIDIplay::Tick(double s, double granularity) +{ + double ret = m_sequencer.Tick(s, granularity); + + s *= m_sequencer.getTempoMultiplier(); + for(uint16_t c = 0; c < opl.NumChannels; ++c) + ch[c].AddAge(static_cast<int64_t>(s * 1000.0)); + + UpdateVibrato(s); + UpdateArpeggio(s); +#if !defined(ADLMIDI_AUDIO_TICK_HANDLER) + UpdateGlide(s); +#endif + + return ret; +} + +#endif /* ADLMIDI_DISABLE_MIDI_SEQUENCER */ diff --git a/src/adlmidi_xmi2mid.h b/src/adlmidi_xmi2mid.h deleted file mode 100644 index 950d58c..0000000 --- a/src/adlmidi_xmi2mid.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - * XMIDI: Miles XMIDI to MID Library Header - * - * Copyright (C) 2001 Ryan Nunn - * Copyright (C) 2014-2016 Bret Curtis - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -/* XMIDI Converter */ - -#ifndef XMIDILIB_H -#define XMIDILIB_H - -#include <stdint.h> - -#ifdef __DJGPP__ -typedef signed char int8_t; -typedef unsigned char uint8_t; -typedef signed short int16_t; -typedef unsigned short uint16_t; -typedef signed long int32_t; -typedef unsigned long uint32_t; -#endif - -#ifdef __cplusplus -extern "C" -{ -#endif - -/* Conversion types for Midi files */ -#define XMIDI_CONVERT_NOCONVERSION 0x00 -#define XMIDI_CONVERT_MT32_TO_GM 0x01 -#define XMIDI_CONVERT_MT32_TO_GS 0x02 -#define XMIDI_CONVERT_MT32_TO_GS127 0x03 /* This one is broken, don't use */ -#define XMIDI_CONVERT_MT32_TO_GS127DRUM 0x04 /* This one is broken, don't use */ -#define XMIDI_CONVERT_GS127_TO_GS 0x05 - -int AdlMidi_xmi2midi(uint8_t *in, uint32_t insize, - uint8_t **out, uint32_t *outsize, - uint32_t convert_type); - -#ifdef __cplusplus -} -#endif - -#endif /* XMIDILIB_H */ diff --git a/src/adlmidi_mus2mid.c b/src/cvt_mus2mid.hpp index 3f3e1b8..5a465c2 100644 --- a/src/adlmidi_mus2mid.c +++ b/src/cvt_mus2mid.hpp @@ -24,19 +24,28 @@ #include <stddef.h> #include <stdlib.h> #include <string.h> -#include "adlmidi_mus2mid.h" +#include <stdint.h> + +#ifdef __DJGPP__ +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef signed short int16_t; +typedef unsigned short uint16_t; +typedef signed long int32_t; +typedef unsigned long uint32_t; +#endif -#define FREQUENCY 140 /* default Hz or BPM */ +#define MUS_FREQUENCY 140 /* default Hz or BPM */ #if 0 /* older units: */ -#define TEMPO 0x001aa309 /* MPQN: 60000000 / 34.37Hz = 1745673 */ -#define DIVISION 0x0059 /* 89 -- used by many mus2midi converters */ +#define MUS_TEMPO 0x001aa309 /* MPQN: 60000000 / 34.37Hz = 1745673 */ +#define MUS_DIVISION 0x0059 /* 89 -- used by many mus2midi converters */ #endif -#define TEMPO 0x00068A1B /* MPQN: 60000000 / 140BPM (140Hz) = 428571 */ +#define MUS_TEMPO 0x00068A1B /* MPQN: 60000000 / 140BPM (140Hz) = 428571 */ /* 0x000D1436 -> MPQN: 60000000 / 70BPM (70Hz) = 857142 */ -#define DIVISION 0x0101 /* 257 for 140Hz files with a 140MPQN */ +#define MUS_DIVISION 0x0101 /* 257 for 140Hz files with a 140MPQN */ /* 0x0088 -> 136 for 70Hz files with a 140MPQN */ /* 0x010B -> 267 for 70hz files with a 70MPQN */ /* 0x01F9 -> 505 for 140hz files with a 70MPQN */ @@ -72,11 +81,11 @@ #define MUSEVENT_CONTROLLERCHANGE 4 #define MUSEVENT_END 6 -#define MIDI_MAXCHANNELS 16 +#define MUS_MIDI_MAXCHANNELS 16 static char MUS_ID[] = { 'M', 'U', 'S', 0x1A }; -static uint8_t midimap[] = +static uint8_t mus_midimap[] = {/* MIDI Number Description */ 0, /* 0 program change */ 0, /* 1 bank selection */ @@ -129,35 +138,35 @@ struct mus_ctx { }; #define DST_CHUNK 8192 -static void resize_dst(struct mus_ctx *ctx) { +static void mus2mid_resize_dst(struct mus_ctx *ctx) { uint32_t pos = (uint32_t)(ctx->dst_ptr - ctx->dst); - ctx->dst = realloc(ctx->dst, ctx->dstsize + DST_CHUNK); + ctx->dst = (uint8_t *)realloc(ctx->dst, ctx->dstsize + DST_CHUNK); ctx->dstsize += DST_CHUNK; ctx->dstrem += DST_CHUNK; ctx->dst_ptr = ctx->dst + pos; } -static void write1(struct mus_ctx *ctx, uint32_t val) +static void mus2mid_write1(struct mus_ctx *ctx, uint32_t val) { if (ctx->dstrem < 1) - resize_dst(ctx); + mus2mid_resize_dst(ctx); *ctx->dst_ptr++ = val & 0xff; ctx->dstrem--; } -static void write2(struct mus_ctx *ctx, uint32_t val) +static void mus2mid_write2(struct mus_ctx *ctx, uint32_t val) { if (ctx->dstrem < 2) - resize_dst(ctx); + mus2mid_resize_dst(ctx); *ctx->dst_ptr++ = (val>>8) & 0xff; *ctx->dst_ptr++ = val & 0xff; ctx->dstrem -= 2; } -static void write4(struct mus_ctx *ctx, uint32_t val) +static void mus2mid_write4(struct mus_ctx *ctx, uint32_t val) { if (ctx->dstrem < 4) - resize_dst(ctx); + mus2mid_resize_dst(ctx); *ctx->dst_ptr++ = (val>>24)&0xff; *ctx->dst_ptr++ = (val>>16)&0xff; *ctx->dst_ptr++ = (val>>8) & 0xff; @@ -165,28 +174,28 @@ static void write4(struct mus_ctx *ctx, uint32_t val) ctx->dstrem -= 4; } -static void seekdst(struct mus_ctx *ctx, uint32_t pos) { +static void mus2mid_seekdst(struct mus_ctx *ctx, uint32_t pos) { ctx->dst_ptr = ctx->dst + pos; while (ctx->dstsize < pos) - resize_dst(ctx); + mus2mid_resize_dst(ctx); ctx->dstrem = ctx->dstsize - pos; } -static void skipdst(struct mus_ctx *ctx, int32_t pos) { +static void mus2mid_skipdst(struct mus_ctx *ctx, int32_t pos) { size_t newpos; ctx->dst_ptr += pos; newpos = ctx->dst_ptr - ctx->dst; while (ctx->dstsize < newpos) - resize_dst(ctx); + mus2mid_resize_dst(ctx); ctx->dstrem = (uint32_t)(ctx->dstsize - newpos); } -static uint32_t getdstpos(struct mus_ctx *ctx) { +static uint32_t mus2mid_getdstpos(struct mus_ctx *ctx) { return (uint32_t)(ctx->dst_ptr - ctx->dst); } /* writes a variable length integer to a buffer, and returns bytes written */ -static int32_t writevarlen(int32_t value, uint8_t *out) +static int32_t mus2mid_writevarlen(int32_t value, uint8_t *out) { int32_t buffer, count = 0; @@ -209,20 +218,21 @@ static int32_t writevarlen(int32_t value, uint8_t *out) return (count); } -#define READ_INT16(b) ((b)[0] | ((b)[1] << 8)) -#define READ_INT32(b) ((b)[0] | ((b)[1] << 8) | ((b)[2] << 16) | ((b)[3] << 24)) +#define MUS_READ_INT16(b) ((b)[0] | ((b)[1] << 8)) +#define MUS_READ_INT32(b) ((b)[0] | ((b)[1] << 8) | ((b)[2] << 16) | ((b)[3] << 24)) -int AdlMidi_mus2midi(uint8_t *in, uint32_t insize, - uint8_t **out, uint32_t *outsize, - uint16_t frequency) { +static int Convert_mus2midi(uint8_t *in, uint32_t insize, + uint8_t **out, uint32_t *outsize, + uint16_t frequency) +{ struct mus_ctx ctx; MUSHeader header; uint8_t *cur, *end; uint32_t track_size_pos, begin_track_pos, current_pos; int32_t delta_time;/* Delta time for midi event */ int temp, ret = -1; - int channel_volume[MIDI_MAXCHANNELS]; - int channelMap[MIDI_MAXCHANNELS], currentChannel; + int channel_volume[MUS_MIDI_MAXCHANNELS]; + int channelMap[MUS_MIDI_MAXCHANNELS], currentChannel; if (insize < MUS_HEADERSIZE) { /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too short)", 0);*/ @@ -230,15 +240,15 @@ int AdlMidi_mus2midi(uint8_t *in, uint32_t insize, } if (!frequency) - frequency = FREQUENCY; + frequency = MUS_FREQUENCY; /* read the MUS header and set our location */ memcpy(header.ID, in, 4); - header.scoreLen = READ_INT16(&in[4]); - header.scoreStart = READ_INT16(&in[6]); - header.channels = READ_INT16(&in[8]); - header.sec_channels = READ_INT16(&in[10]); - header.instrCnt = READ_INT16(&in[12]); + header.scoreLen = MUS_READ_INT16(&in[4]); + header.scoreStart = MUS_READ_INT16(&in[6]); + header.channels = MUS_READ_INT16(&in[8]); + header.sec_channels = MUS_READ_INT16(&in[10]); + header.instrCnt = MUS_READ_INT16(&in[12]); if (memcmp(header.ID, MUS_ID, 4)) { /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_NOT_MUS, NULL, 0);*/ @@ -249,7 +259,7 @@ int AdlMidi_mus2midi(uint8_t *in, uint32_t insize, return (-1); } /* channel #15 should be excluded in the numchannels field: */ - if (header.channels > MIDI_MAXCHANNELS - 1) { + if (header.channels > MUS_MIDI_MAXCHANNELS - 1) { /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_INVALID, NULL, 0);*/ return (-1); } @@ -258,50 +268,50 @@ int AdlMidi_mus2midi(uint8_t *in, uint32_t insize, ctx.src = ctx.src_ptr = in; ctx.srcsize = insize; - ctx.dst = calloc(DST_CHUNK, sizeof(uint8_t)); + ctx.dst = (uint8_t*)calloc(DST_CHUNK, sizeof(uint8_t)); ctx.dst_ptr = ctx.dst; ctx.dstsize = DST_CHUNK; ctx.dstrem = DST_CHUNK; /* Map channel 15 to 9 (percussions) */ - for (temp = 0; temp < MIDI_MAXCHANNELS; ++temp) { + for (temp = 0; temp < MUS_MIDI_MAXCHANNELS; ++temp) { channelMap[temp] = -1; channel_volume[temp] = 0x40; } channelMap[15] = 9; /* Header is 14 bytes long and add the rest as well */ - write1(&ctx, 'M'); - write1(&ctx, 'T'); - write1(&ctx, 'h'); - write1(&ctx, 'd'); - write4(&ctx, 6); /* length of header */ - write2(&ctx, 0); /* MIDI type (always 0) */ - write2(&ctx, 1); /* MUS files only have 1 track */ - write2(&ctx, DIVISION); /* division */ + mus2mid_write1(&ctx, 'M'); + mus2mid_write1(&ctx, 'T'); + mus2mid_write1(&ctx, 'h'); + mus2mid_write1(&ctx, 'd'); + mus2mid_write4(&ctx, 6); /* length of header */ + mus2mid_write2(&ctx, 0); /* MIDI type (always 0) */ + mus2mid_write2(&ctx, 1); /* MUS files only have 1 track */ + mus2mid_write2(&ctx, MUS_DIVISION); /* division */ /* Write out track header and track length position for later */ - begin_track_pos = getdstpos(&ctx); - write1(&ctx, 'M'); - write1(&ctx, 'T'); - write1(&ctx, 'r'); - write1(&ctx, 'k'); - track_size_pos = getdstpos(&ctx); - skipdst(&ctx, 4); + begin_track_pos = mus2mid_getdstpos(&ctx); + mus2mid_write1(&ctx, 'M'); + mus2mid_write1(&ctx, 'T'); + mus2mid_write1(&ctx, 'r'); + mus2mid_write1(&ctx, 'k'); + track_size_pos = mus2mid_getdstpos(&ctx); + mus2mid_skipdst(&ctx, 4); /* write tempo: microseconds per quarter note */ - write1(&ctx, 0x00); /* delta time */ - write1(&ctx, 0xff); /* sys command */ - write2(&ctx, 0x5103); /* command - set tempo */ - write1(&ctx, TEMPO & 0x000000ff); - write1(&ctx, (TEMPO & 0x0000ff00) >> 8); - write1(&ctx, (TEMPO & 0x00ff0000) >> 16); + mus2mid_write1(&ctx, 0x00); /* delta time */ + mus2mid_write1(&ctx, 0xff); /* sys command */ + mus2mid_write2(&ctx, 0x5103); /* command - set tempo */ + mus2mid_write1(&ctx, MUS_TEMPO & 0x000000ff); + mus2mid_write1(&ctx, (MUS_TEMPO & 0x0000ff00) >> 8); + mus2mid_write1(&ctx, (MUS_TEMPO & 0x00ff0000) >> 16); /* Percussions channel starts out at full volume */ - write1(&ctx, 0x00); - write1(&ctx, 0xB9); - write1(&ctx, 0x07); - write1(&ctx, 127); + mus2mid_write1(&ctx, 0x00); + mus2mid_write1(&ctx, 0xB9); + mus2mid_write1(&ctx, 0x07); + mus2mid_write1(&ctx, 127); /* get current position in source, and end of position */ cur = in + header.scoreStart; @@ -324,7 +334,7 @@ int AdlMidi_mus2midi(uint8_t *in, uint32_t insize, channel = (event & 15); /* current channel */ /* write variable length delta time */ - out_local += writevarlen(delta_time, out_local); + out_local += mus2mid_writevarlen(delta_time, out_local); /* set all channels to 127 (max) volume */ if (channelMap[channel] < 0) { @@ -359,12 +369,12 @@ int AdlMidi_mus2midi(uint8_t *in, uint32_t insize, break; case MUSEVENT_CHANNELMODE: status |= 0xB0; - if (*cur >= sizeof(midimap) / sizeof(midimap[0])) { + if (*cur >= sizeof(mus_midimap) / sizeof(mus_midimap[0])) { /*_WM_ERROR_NEW("%s:%i: can't map %u to midi", __FUNCTION__, __LINE__, *cur);*/ goto _end; } - bit1 = midimap[*cur++]; + bit1 = mus_midimap[*cur++]; bit2 = (*cur++ == 12) ? header.channels + 1 : 0x00; break; case MUSEVENT_CONTROLLERCHANGE: @@ -376,12 +386,12 @@ int AdlMidi_mus2midi(uint8_t *in, uint32_t insize, bitc = 1; } else { status |= 0xB0; - if (*cur >= sizeof(midimap) / sizeof(midimap[0])) { + if (*cur >= sizeof(mus_midimap) / sizeof(mus_midimap[0])) { /*_WM_ERROR_NEW("%s:%i: can't map %u to midi", __FUNCTION__, __LINE__, *cur);*/ goto _end; } - bit1 = midimap[*cur++]; + bit1 = mus_midimap[*cur++]; bit2 = *cur++; } break; @@ -412,7 +422,7 @@ int AdlMidi_mus2midi(uint8_t *in, uint32_t insize, if (out_local != temp_buffer) { if (ctx.dstrem < sizeof(temp_buffer)) - resize_dst(&ctx); + mus2mid_resize_dst(&ctx); memcpy(ctx.dst_ptr, temp_buffer, out_local - temp_buffer); ctx.dst_ptr += out_local - temp_buffer; @@ -430,10 +440,10 @@ int AdlMidi_mus2midi(uint8_t *in, uint32_t insize, } /* write out track length */ - current_pos = getdstpos(&ctx); - seekdst(&ctx, track_size_pos); - write4(&ctx, current_pos - begin_track_pos - TRK_CHUNKSIZE); - seekdst(&ctx, current_pos); /* reseek to end position */ + current_pos = mus2mid_getdstpos(&ctx); + mus2mid_seekdst(&ctx, track_size_pos); + mus2mid_write4(&ctx, current_pos - begin_track_pos - TRK_CHUNKSIZE); + mus2mid_seekdst(&ctx, current_pos); /* reseek to end position */ *out = ctx.dst; *outsize = ctx.dstsize - ctx.dstrem; diff --git a/src/adlmidi_xmi2mid.c b/src/cvt_xmi2mid.hpp index 3389709..c164e3d 100644 --- a/src/adlmidi_xmi2mid.c +++ b/src/cvt_xmi2mid.hpp @@ -28,26 +28,42 @@ #include <stdint.h> #include <string.h> #include <stdlib.h> +#include <stdint.h> -#include "adlmidi_xmi2mid.h" +#ifdef __DJGPP__ +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef signed short int16_t; +typedef unsigned short uint16_t; +typedef signed long int32_t; +typedef unsigned long uint32_t; +#endif + +/* Conversion types for Midi files */ +#define XMIDI_CONVERT_NOCONVERSION 0x00 +#define XMIDI_CONVERT_MT32_TO_GM 0x01 +#define XMIDI_CONVERT_MT32_TO_GS 0x02 +#define XMIDI_CONVERT_MT32_TO_GS127 0x03 /* This one is broken, don't use */ +#define XMIDI_CONVERT_MT32_TO_GS127DRUM 0x04 /* This one is broken, don't use */ +#define XMIDI_CONVERT_GS127_TO_GS 0x05 /* Midi Status Bytes */ -#define MIDI_STATUS_NOTE_OFF 0x8 -#define MIDI_STATUS_NOTE_ON 0x9 -#define MIDI_STATUS_AFTERTOUCH 0xA -#define MIDI_STATUS_CONTROLLER 0xB -#define MIDI_STATUS_PROG_CHANGE 0xC -#define MIDI_STATUS_PRESSURE 0xD -#define MIDI_STATUS_PITCH_WHEEL 0xE -#define MIDI_STATUS_SYSEX 0xF - -typedef struct _midi_event { +#define XMI2MID_MIDI_STATUS_NOTE_OFF 0x8 +#define XMI2MID_MIDI_STATUS_NOTE_ON 0x9 +#define XMI2MID_MIDI_STATUS_AFTERTOUCH 0xA +#define XMI2MID_MIDI_STATUS_CONTROLLER 0xB +#define XMI2MID_MIDI_STATUS_PROG_CHANGE 0xC +#define XMI2MID_MIDI_STATUS_PRESSURE 0xD +#define XMI2MID_MIDI_STATUS_PITCH_WHEEL 0xE +#define XMI2MID_MIDI_STATUS_SYSEX 0xF + +typedef struct _xmi2mid_midi_event { int32_t time; uint8_t status; uint8_t data[2]; uint32_t len; uint8_t *buffer; - struct _midi_event *next; + struct _xmi2mid_midi_event *next; } midi_event; typedef struct { @@ -55,7 +71,7 @@ typedef struct { uint16_t tracks; } midi_descriptor; -struct xmi_ctx { +struct xmi2mid_xmi_ctx { uint8_t *src, *src_ptr; uint32_t srcsize; uint32_t datastart; @@ -65,35 +81,35 @@ struct xmi_ctx { midi_descriptor info; int bank127[16]; midi_event **events; - signed short *timing; + int16_t *timing; midi_event *list; midi_event *current; }; /* forward declarations of private functions */ -static void DeleteEventList(midi_event *mlist); -static void CreateNewEvent(struct xmi_ctx *ctx, int32_t time); /* List manipulation */ -static int GetVLQ(struct xmi_ctx *ctx, uint32_t *quant); /* Variable length quantity */ -static int GetVLQ2(struct xmi_ctx *ctx, uint32_t *quant);/* Variable length quantity */ -static int PutVLQ(struct xmi_ctx *ctx, uint32_t value); /* Variable length quantity */ -static int ConvertEvent(struct xmi_ctx *ctx, +static void xmi2mid_DeleteEventList(midi_event *mlist); +static void xmi2mid_CreateNewEvent(struct xmi2mid_xmi_ctx *ctx, int32_t time); /* List manipulation */ +static int xmi2mid_GetVLQ(struct xmi2mid_xmi_ctx *ctx, uint32_t *quant); /* Variable length quantity */ +static int xmi2mid_GetVLQ2(struct xmi2mid_xmi_ctx *ctx, uint32_t *quant);/* Variable length quantity */ +static int xmi2mid_PutVLQ(struct xmi2mid_xmi_ctx *ctx, uint32_t value); /* Variable length quantity */ +static int xmi2mid_ConvertEvent(struct xmi2mid_xmi_ctx *ctx, const int32_t time, const uint8_t status, const int size); -static int32_t ConvertSystemMessage(struct xmi_ctx *ctx, +static int32_t xmi2mid_ConvertSystemMessage(struct xmi2mid_xmi_ctx *ctx, const int32_t time, const uint8_t status); -static int32_t ConvertFiletoList(struct xmi_ctx *ctx); -static uint32_t ConvertListToMTrk(struct xmi_ctx *ctx, midi_event *mlist); -static int ParseXMI(struct xmi_ctx *ctx); -static int ExtractTracks(struct xmi_ctx *ctx); -static uint32_t ExtractTracksFromXmi(struct xmi_ctx *ctx); +static int32_t xmi2mid_ConvertFiletoList(struct xmi2mid_xmi_ctx *ctx); +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 uint32_t xmi2mid_ExtractTracksFromXmi(struct xmi2mid_xmi_ctx *ctx); -static uint32_t read1(struct xmi_ctx *ctx) +static uint32_t xmi2mid_read1(struct xmi2mid_xmi_ctx *ctx) { uint8_t b0; b0 = *ctx->src_ptr++; return (b0); } -static uint32_t read2(struct xmi_ctx *ctx) +static uint32_t xmi2mid_read2(struct xmi2mid_xmi_ctx *ctx) { uint8_t b0, b1; b0 = *ctx->src_ptr++; @@ -101,7 +117,7 @@ static uint32_t read2(struct xmi_ctx *ctx) return (b0 + ((uint32_t)b1 << 8)); } -static uint32_t read4(struct xmi_ctx *ctx) +static uint32_t xmi2mid_read4(struct xmi2mid_xmi_ctx *ctx) { uint8_t b0, b1, b2, b3; b3 = *ctx->src_ptr++; @@ -111,42 +127,42 @@ static uint32_t read4(struct xmi_ctx *ctx) return (b0 + ((uint32_t)b1<<8) + ((uint32_t)b2<<16) + ((uint32_t)b3<<24)); } -static void copy(struct xmi_ctx *ctx, char *b, uint32_t len) +static void xmi2mid_copy(struct xmi2mid_xmi_ctx *ctx, char *b, uint32_t len) { memcpy(b, ctx->src_ptr, len); ctx->src_ptr += len; } #define DST_CHUNK 8192 -static void resize_dst(struct xmi_ctx *ctx) { +static void xmi2mid_resize_dst(struct xmi2mid_xmi_ctx *ctx) { uint32_t pos = (uint32_t)(ctx->dst_ptr - ctx->dst); - ctx->dst = realloc(ctx->dst, ctx->dstsize + DST_CHUNK); + ctx->dst = (uint8_t *)realloc(ctx->dst, ctx->dstsize + DST_CHUNK); ctx->dstsize += DST_CHUNK; ctx->dstrem += DST_CHUNK; ctx->dst_ptr = ctx->dst + pos; } -static void write1(struct xmi_ctx *ctx, uint32_t val) +static void xmi2mid_write1(struct xmi2mid_xmi_ctx *ctx, uint32_t val) { if (ctx->dstrem < 1) - resize_dst(ctx); + xmi2mid_resize_dst(ctx); *ctx->dst_ptr++ = val & 0xff; ctx->dstrem--; } -static void write2(struct xmi_ctx *ctx, uint32_t val) +static void xmi2mid_write2(struct xmi2mid_xmi_ctx *ctx, uint32_t val) { if (ctx->dstrem < 2) - resize_dst(ctx); + xmi2mid_resize_dst(ctx); *ctx->dst_ptr++ = (val>>8) & 0xff; *ctx->dst_ptr++ = val & 0xff; ctx->dstrem -= 2; } -static void write4(struct xmi_ctx *ctx, uint32_t val) +static void xmi2mid_write4(struct xmi2mid_xmi_ctx *ctx, uint32_t val) { if (ctx->dstrem < 4) - resize_dst(ctx); + xmi2mid_resize_dst(ctx); *ctx->dst_ptr++ = (val>>24)&0xff; *ctx->dst_ptr++ = (val>>16)&0xff; *ctx->dst_ptr++ = (val>>8) & 0xff; @@ -154,39 +170,39 @@ static void write4(struct xmi_ctx *ctx, uint32_t val) ctx->dstrem -= 4; } -static void seeksrc(struct xmi_ctx *ctx, uint32_t pos) { +static void xmi2mid_seeksrc(struct xmi2mid_xmi_ctx *ctx, uint32_t pos) { ctx->src_ptr = ctx->src + pos; } -static void seekdst(struct xmi_ctx *ctx, uint32_t pos) { +static void xmi2mid_seekdst(struct xmi2mid_xmi_ctx *ctx, uint32_t pos) { ctx->dst_ptr = ctx->dst + pos; while (ctx->dstsize < pos) - resize_dst(ctx); + xmi2mid_resize_dst(ctx); ctx->dstrem = ctx->dstsize - pos; } -static void skipsrc(struct xmi_ctx *ctx, int32_t pos) { +static void xmi2mid_skipsrc(struct xmi2mid_xmi_ctx *ctx, int32_t pos) { ctx->src_ptr += pos; } -static void skipdst(struct xmi_ctx *ctx, int32_t pos) { +static void xmi2mid_skipdst(struct xmi2mid_xmi_ctx *ctx, int32_t pos) { size_t newpos; ctx->dst_ptr += pos; newpos = ctx->dst_ptr - ctx->dst; while (ctx->dstsize < newpos) - resize_dst(ctx); + xmi2mid_resize_dst(ctx); ctx->dstrem = (uint32_t)(ctx->dstsize - newpos); } -static uint32_t getsrcsize(struct xmi_ctx *ctx) { +static uint32_t xmi2mid_getsrcsize(struct xmi2mid_xmi_ctx *ctx) { return (ctx->srcsize); } -static uint32_t getsrcpos(struct xmi_ctx *ctx) { +static uint32_t xmi2mid_getsrcpos(struct xmi2mid_xmi_ctx *ctx) { return (uint32_t)(ctx->src_ptr - ctx->src); } -static uint32_t getdstpos(struct xmi_ctx *ctx) { +static uint32_t xmi2mid_getdstpos(struct xmi2mid_xmi_ctx *ctx) { return (uint32_t)(ctx->dst_ptr - ctx->dst); } @@ -195,7 +211,7 @@ static uint32_t getdstpos(struct xmi_ctx *ctx) { * This is only suitable for music that doesn't do timbre changes * XMIDIs that contain Timbre changes will not convert properly. */ -static const char mt32asgm[128] = { +static const char xmi2mid_mt32asgm[128] = { 0, /* 0 Piano 1 */ 1, /* 1 Piano 2 */ 2, /* 2 Piano 3 (synth) */ @@ -328,7 +344,7 @@ static const char mt32asgm[128] = { /* Same as above, except include patch changes * so GS instruments can be used */ -static const char mt32asgs[256] = { +static const char xmi2mid_mt32asgs[256] = { 0, 0, /* 0 Piano 1 */ 1, 0, /* 1 Piano 2 */ 2, 0, /* 2 Piano 3 (synth) */ @@ -459,10 +475,11 @@ static const char mt32asgs[256] = { 121, 0 /* 127 Jungle Tune set to Breath Noise */ }; -int AdlMidi_xmi2midi(uint8_t *in, uint32_t insize, - uint8_t **out, uint32_t *outsize, - uint32_t convert_type) { - struct xmi_ctx ctx; +static int Convert_xmi2midi(uint8_t *in, uint32_t insize, + uint8_t **out, uint32_t *outsize, + uint32_t convert_type) +{ + struct xmi2mid_xmi_ctx ctx; unsigned int i; int ret = -1; @@ -471,40 +488,40 @@ int AdlMidi_xmi2midi(uint8_t *in, uint32_t insize, return (ret); } - memset(&ctx, 0, sizeof(struct xmi_ctx)); + memset(&ctx, 0, sizeof(struct xmi2mid_xmi_ctx)); ctx.src = ctx.src_ptr = in; ctx.srcsize = insize; ctx.convert_type = convert_type; - if (ParseXMI(&ctx) < 0) { + if (xmi2mid_ParseXMI(&ctx) < 0) { /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_NOT_XMI, NULL, 0);*/ goto _end; } - if (ExtractTracks(&ctx) < 0) { + if (xmi2mid_ExtractTracks(&ctx) < 0) { /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_NOT_MIDI, NULL, 0);*/ goto _end; } - ctx.dst = malloc(DST_CHUNK); + 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 */ - write1(&ctx, 'M'); - write1(&ctx, 'T'); - write1(&ctx, 'h'); - write1(&ctx, 'd'); + xmi2mid_write1(&ctx, 'M'); + xmi2mid_write1(&ctx, 'T'); + xmi2mid_write1(&ctx, 'h'); + xmi2mid_write1(&ctx, 'd'); - write4(&ctx, 6); + xmi2mid_write4(&ctx, 6); - write2(&ctx, ctx.info.type); - write2(&ctx, ctx.info.tracks); - write2(&ctx, ctx.timing[0]);/* write divisions from track0 */ + xmi2mid_write2(&ctx, ctx.info.type); + xmi2mid_write2(&ctx, ctx.info.tracks); + xmi2mid_write2(&ctx, ctx.timing[0]);/* write divisions from track0 */ for (i = 0; i < ctx.info.tracks; i++) - ConvertListToMTrk(&ctx, ctx.events[i]); + xmi2mid_ConvertListToMTrk(&ctx, ctx.events[i]); *out = ctx.dst; *outsize = ctx.dstsize - ctx.dstrem; ret = 0; @@ -517,7 +534,7 @@ _end: /* cleanup */ } if (ctx.events) { for (i = 0; i < ctx.info.tracks; i++) - DeleteEventList(ctx.events[i]); + xmi2mid_DeleteEventList(ctx.events[i]); free(ctx.events); } free(ctx.timing); @@ -525,7 +542,7 @@ _end: /* cleanup */ return (ret); } -static void DeleteEventList(midi_event *mlist) { +static void xmi2mid_DeleteEventList(midi_event *mlist) { midi_event *event; midi_event *next; @@ -539,15 +556,15 @@ static void DeleteEventList(midi_event *mlist) { } /* Sets current to the new event and updates list */ -static void CreateNewEvent(struct xmi_ctx *ctx, int32_t time) { +static void xmi2mid_CreateNewEvent(struct xmi2mid_xmi_ctx *ctx, int32_t time) { if (!ctx->list) { - ctx->list = ctx->current = calloc(1, sizeof(midi_event)); + ctx->list = ctx->current = (struct _xmi2mid_midi_event *)calloc(1, sizeof(midi_event)); ctx->current->time = (time < 0)? 0 : time; return; } if (time < 0) { - midi_event *event = calloc(1, sizeof(midi_event)); + midi_event *event = (midi_event *)calloc(1, sizeof(midi_event)); event->next = ctx->list; ctx->list = ctx->current = event; return; @@ -558,7 +575,7 @@ static void CreateNewEvent(struct xmi_ctx *ctx, int32_t time) { while (ctx->current->next) { if (ctx->current->next->time > time) { - midi_event *event = calloc(1, sizeof(midi_event)); + midi_event *event = (midi_event *)calloc(1, sizeof(midi_event)); event->next = ctx->current->next; ctx->current->next = event; ctx->current = event; @@ -569,19 +586,19 @@ static void CreateNewEvent(struct xmi_ctx *ctx, int32_t time) { ctx->current = ctx->current->next; } - ctx->current->next = calloc(1, sizeof(midi_event)); + ctx->current->next = (struct _xmi2mid_midi_event *)calloc(1, sizeof(midi_event)); ctx->current = ctx->current->next; ctx->current->time = time; } /* Conventional Variable Length Quantity */ -static int GetVLQ(struct xmi_ctx *ctx, uint32_t *quant) { +static int xmi2mid_GetVLQ(struct xmi2mid_xmi_ctx *ctx, uint32_t *quant) { int i; uint32_t data; *quant = 0; for (i = 0; i < 4; i++) { - data = read1(ctx); + data = xmi2mid_read1(ctx); *quant <<= 7; *quant |= data & 0x7F; @@ -594,15 +611,15 @@ static int GetVLQ(struct xmi_ctx *ctx, uint32_t *quant) { } /* XMIDI Delta Variable Length Quantity */ -static int GetVLQ2(struct xmi_ctx *ctx, uint32_t *quant) { +static int xmi2mid_GetVLQ2(struct xmi2mid_xmi_ctx *ctx, uint32_t *quant) { int i; int32_t data; *quant = 0; for (i = 0; i < 4; i++) { - data = read1(ctx); + data = xmi2mid_read1(ctx); if (data & 0x80) { - skipsrc(ctx, -1); + xmi2mid_skipsrc(ctx, -1); break; } *quant += data; @@ -610,7 +627,7 @@ static int GetVLQ2(struct xmi_ctx *ctx, uint32_t *quant) { return (i); } -static int PutVLQ(struct xmi_ctx *ctx, uint32_t value) { +static int xmi2mid_PutVLQ(struct xmi2mid_xmi_ctx *ctx, uint32_t value) { int32_t buffer; int i = 1, j; buffer = value & 0x7F; @@ -620,7 +637,7 @@ static int PutVLQ(struct xmi_ctx *ctx, uint32_t value) { i++; } for (j = 0; j < i; j++) { - write1(ctx, buffer & 0xFF); + xmi2mid_write1(ctx, buffer & 0xFF); buffer >>= 8; } @@ -634,14 +651,14 @@ static int PutVLQ(struct xmi_ctx *ctx, uint32_t value) { * size 2 is dual data byte * size 3 is XMI Note on * Returns bytes converted */ -static int ConvertEvent(struct xmi_ctx *ctx, const int32_t time, +static int xmi2mid_ConvertEvent(struct xmi2mid_xmi_ctx *ctx, const int32_t time, const uint8_t status, const int size) { uint32_t delta = 0; int32_t data; midi_event *prev; int i; - data = read1(ctx); + data = xmi2mid_read1(ctx); /*HACK!*/ if (((status >> 4) == 0xB) && (status & 0xF) != 9 && (data == 114)) { @@ -650,7 +667,7 @@ static int ConvertEvent(struct xmi_ctx *ctx, const int32_t time, /* Bank changes are handled here */ if ((status >> 4) == 0xB && data == 0) { - data = read1(ctx); + data = xmi2mid_read1(ctx); ctx->bank127[status & 0xF] = 0; @@ -661,7 +678,7 @@ static int ConvertEvent(struct xmi_ctx *ctx, const int32_t time, && (status & 0xF) == 9) ) return (2); - CreateNewEvent(ctx, time); + xmi2mid_CreateNewEvent(ctx, time); ctx->current->status = status; ctx->current->data[0] = 0; ctx->current->data[1] = data == 127 ? 0 : data;/*HACK:*/ @@ -678,22 +695,22 @@ static int ConvertEvent(struct xmi_ctx *ctx, const int32_t time, { if (ctx->convert_type == XMIDI_CONVERT_MT32_TO_GM) { - data = mt32asgm[data]; + data = xmi2mid_mt32asgm[data]; } else if ((ctx->convert_type == XMIDI_CONVERT_GS127_TO_GS && ctx->bank127[status&0xF]) || ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS || ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS127DRUM) { - CreateNewEvent (ctx, time); + xmi2mid_CreateNewEvent (ctx, time); ctx->current->status = 0xB0 | (status&0xF); ctx->current->data[0] = 0; - ctx->current->data[1] = mt32asgs[data*2+1]; + ctx->current->data[1] = xmi2mid_mt32asgs[data*2+1]; - data = mt32asgs[data*2]; + data = xmi2mid_mt32asgs[data*2]; } else if (ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS127) { - CreateNewEvent (ctx, time); + xmi2mid_CreateNewEvent (ctx, time); ctx->current->status = 0xB0 | (status&0xF); ctx->current->data[0] = 0; ctx->current->data[1] = 127; @@ -703,13 +720,13 @@ static int ConvertEvent(struct xmi_ctx *ctx, const int32_t time, else if ((status >> 4) == 0xC && (status&0xF) == 9 && (ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS127DRUM || ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS127)) { - CreateNewEvent (ctx, time); + xmi2mid_CreateNewEvent (ctx, time); ctx->current->status = 0xB9; ctx->current->data[0] = 0; ctx->current->data[1] = 127; } - CreateNewEvent(ctx, time); + xmi2mid_CreateNewEvent(ctx, time); ctx->current->status = status; ctx->current->data[0] = data; @@ -717,15 +734,15 @@ static int ConvertEvent(struct xmi_ctx *ctx, const int32_t time, if (size == 1) return (1); - ctx->current->data[1] = read1(ctx); + ctx->current->data[1] = xmi2mid_read1(ctx); if (size == 2) return (2); /* XMI Note On handling */ prev = ctx->current; - i = GetVLQ(ctx, &delta); - CreateNewEvent(ctx, time + delta * 3); + i = xmi2mid_GetVLQ(ctx, &delta); + xmi2mid_CreateNewEvent(ctx, time + delta * 3); ctx->current->status = status; ctx->current->data[0] = data; @@ -736,99 +753,99 @@ static int ConvertEvent(struct xmi_ctx *ctx, const int32_t time, } /* Simple routine to convert system messages */ -static int32_t ConvertSystemMessage(struct xmi_ctx *ctx, const int32_t time, +static int32_t xmi2mid_ConvertSystemMessage(struct xmi2mid_xmi_ctx *ctx, const int32_t time, const uint8_t status) { int32_t i = 0; - CreateNewEvent(ctx, time); + xmi2mid_CreateNewEvent(ctx, time); ctx->current->status = status; /* Handling of Meta events */ if (status == 0xFF) { - ctx->current->data[0] = read1(ctx); + ctx->current->data[0] = xmi2mid_read1(ctx); i++; } - i += GetVLQ(ctx, &ctx->current->len); + i += xmi2mid_GetVLQ(ctx, &ctx->current->len); if (!ctx->current->len) return (i); - ctx->current->buffer = malloc(sizeof(uint8_t)*ctx->current->len); - copy(ctx, (char *) ctx->current->buffer, ctx->current->len); + ctx->current->buffer = (uint8_t *)malloc(sizeof(uint8_t)*ctx->current->len); + xmi2mid_copy(ctx, (char *) ctx->current->buffer, ctx->current->len); return (i + ctx->current->len); } /* XMIDI and Midi to List * Returns XMIDI PPQN */ -static int32_t ConvertFiletoList(struct xmi_ctx *ctx) { +static int32_t xmi2mid_ConvertFiletoList(struct xmi2mid_xmi_ctx *ctx) { int32_t time = 0; uint32_t data; int32_t end = 0; int32_t tempo = 500000; int32_t tempo_set = 0; uint32_t status = 0; - uint32_t file_size = getsrcsize(ctx); + uint32_t file_size = xmi2mid_getsrcsize(ctx); /* Set Drum track to correct setting if required */ if (ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS127) { - CreateNewEvent(ctx, 0); + xmi2mid_CreateNewEvent(ctx, 0); ctx->current->status = 0xB9; ctx->current->data[0] = 0; ctx->current->data[1] = 127; } - while (!end && getsrcpos(ctx) < file_size) { - GetVLQ2(ctx, &data); + while (!end && xmi2mid_getsrcpos(ctx) < file_size) { + xmi2mid_GetVLQ2(ctx, &data); time += data * 3; - status = read1(ctx); + status = xmi2mid_read1(ctx); switch (status >> 4) { - case MIDI_STATUS_NOTE_ON: - ConvertEvent(ctx, time, status, 3); + case XMI2MID_MIDI_STATUS_NOTE_ON: + xmi2mid_ConvertEvent(ctx, time, status, 3); break; /* 2 byte data */ - case MIDI_STATUS_NOTE_OFF: - case MIDI_STATUS_AFTERTOUCH: - case MIDI_STATUS_CONTROLLER: - case MIDI_STATUS_PITCH_WHEEL: - ConvertEvent(ctx, time, status, 2); + case XMI2MID_MIDI_STATUS_NOTE_OFF: + case XMI2MID_MIDI_STATUS_AFTERTOUCH: + case XMI2MID_MIDI_STATUS_CONTROLLER: + case XMI2MID_MIDI_STATUS_PITCH_WHEEL: + xmi2mid_ConvertEvent(ctx, time, status, 2); break; /* 1 byte data */ - case MIDI_STATUS_PROG_CHANGE: - case MIDI_STATUS_PRESSURE: - ConvertEvent(ctx, time, status, 1); + case XMI2MID_MIDI_STATUS_PROG_CHANGE: + case XMI2MID_MIDI_STATUS_PRESSURE: + xmi2mid_ConvertEvent(ctx, time, status, 1); break; - case MIDI_STATUS_SYSEX: + case XMI2MID_MIDI_STATUS_SYSEX: if (status == 0xFF) { - int32_t pos = getsrcpos(ctx); - uint32_t dat = read1(ctx); + int32_t pos = xmi2mid_getsrcpos(ctx); + uint32_t dat = xmi2mid_read1(ctx); if (dat == 0x2F) /* End */ end = 1; else if (dat == 0x51 && !tempo_set) /* Tempo. Need it for PPQN */ { - skipsrc(ctx, 1); - tempo = read1(ctx) << 16; - tempo += read1(ctx) << 8; - tempo += read1(ctx); + xmi2mid_skipsrc(ctx, 1); + tempo = xmi2mid_read1(ctx) << 16; + tempo += xmi2mid_read1(ctx) << 8; + tempo += xmi2mid_read1(ctx); tempo *= 3; tempo_set = 1; } else if (dat == 0x51 && tempo_set) /* Skip any other tempo changes */ { - GetVLQ(ctx, &dat); - skipsrc(ctx, dat); + xmi2mid_GetVLQ(ctx, &dat); + xmi2mid_skipsrc(ctx, dat); break; } - seeksrc(ctx, pos); + xmi2mid_seeksrc(ctx, pos); } - ConvertSystemMessage(ctx, time, status); + xmi2mid_ConvertSystemMessage(ctx, time, status); break; default: @@ -841,7 +858,7 @@ static int32_t ConvertFiletoList(struct xmi_ctx *ctx) { /* Converts and event list to a MTrk * Returns bytes of the array * buf can be NULL */ -static uint32_t ConvertListToMTrk(struct xmi_ctx *ctx, midi_event *mlist) { +static uint32_t xmi2mid_ConvertListToMTrk(struct xmi2mid_xmi_ctx *ctx, midi_event *mlist) { int32_t time = 0; midi_event *event; uint32_t delta; @@ -851,22 +868,22 @@ static uint32_t ConvertListToMTrk(struct xmi_ctx *ctx, midi_event *mlist) { uint32_t size_pos, cur_pos; int end = 0; - write1(ctx, 'M'); - write1(ctx, 'T'); - write1(ctx, 'r'); - write1(ctx, 'k'); + xmi2mid_write1(ctx, 'M'); + xmi2mid_write1(ctx, 'T'); + xmi2mid_write1(ctx, 'r'); + xmi2mid_write1(ctx, 'k'); - size_pos = getdstpos(ctx); - skipdst(ctx, 4); + size_pos = xmi2mid_getdstpos(ctx); + xmi2mid_skipdst(ctx, 4); for (event = mlist; event && !end; event = event->next) { delta = (event->time - time); time = event->time; - i += PutVLQ(ctx, delta); + i += xmi2mid_PutVLQ(ctx, delta); if ((event->status != last_status) || (event->status >= 0xF0)) { - write1(ctx, event->status); + xmi2mid_write1(ctx, event->status); i++; } @@ -880,8 +897,8 @@ static uint32_t ConvertListToMTrk(struct xmi_ctx *ctx, midi_event *mlist) { case 0xA: case 0xB: case 0xE: - write1(ctx, event->data[0]); - write1(ctx, event->data[1]); + xmi2mid_write1(ctx, event->data[0]); + xmi2mid_write1(ctx, event->data[1]); i += 2; break; @@ -889,7 +906,7 @@ static uint32_t ConvertListToMTrk(struct xmi_ctx *ctx, midi_event *mlist) { * Program Change and Channel Pressure */ case 0xC: case 0xD: - write1(ctx, event->data[0]); + xmi2mid_write1(ctx, event->data[0]); i++; break; @@ -899,13 +916,13 @@ static uint32_t ConvertListToMTrk(struct xmi_ctx *ctx, midi_event *mlist) { if (event->status == 0xFF) { if (event->data[0] == 0x2f) end = 1; - write1(ctx, event->data[0]); + xmi2mid_write1(ctx, event->data[0]); i++; } - i += PutVLQ(ctx, event->len); + i += xmi2mid_PutVLQ(ctx, event->len); if (event->len) { for (j = 0; j < event->len; j++) { - write1(ctx, event->buffer[j]); + xmi2mid_write1(ctx, event->buffer[j]); i++; } } @@ -918,44 +935,44 @@ static uint32_t ConvertListToMTrk(struct xmi_ctx *ctx, midi_event *mlist) { } } - cur_pos = getdstpos(ctx); - seekdst(ctx, size_pos); - write4(ctx, i - 8); - seekdst(ctx, cur_pos); + cur_pos = xmi2mid_getdstpos(ctx); + xmi2mid_seekdst(ctx, size_pos); + xmi2mid_write4(ctx, i - 8); + xmi2mid_seekdst(ctx, cur_pos); return (i); } /* Assumes correct xmidi */ -static uint32_t ExtractTracksFromXmi(struct xmi_ctx *ctx) { +static uint32_t xmi2mid_ExtractTracksFromXmi(struct xmi2mid_xmi_ctx *ctx) { uint32_t num = 0; signed short ppqn; uint32_t len = 0; int32_t begin; char buf[32]; - while (getsrcpos(ctx) < getsrcsize(ctx) && num != ctx->info.tracks) { + while (xmi2mid_getsrcpos(ctx) < xmi2mid_getsrcsize(ctx) && num != ctx->info.tracks) { /* Read first 4 bytes of name */ - copy(ctx, buf, 4); - len = read4(ctx); + xmi2mid_copy(ctx, buf, 4); + len = xmi2mid_read4(ctx); /* Skip the FORM entries */ if (!memcmp(buf, "FORM", 4)) { - skipsrc(ctx, 4); - copy(ctx, buf, 4); - len = read4(ctx); + xmi2mid_skipsrc(ctx, 4); + xmi2mid_copy(ctx, buf, 4); + len = xmi2mid_read4(ctx); } if (memcmp(buf, "EVNT", 4)) { - skipsrc(ctx, (len + 1) & ~1); + xmi2mid_skipsrc(ctx, (len + 1) & ~1); continue; } ctx->list = NULL; - begin = getsrcpos(ctx); + begin = xmi2mid_getsrcpos(ctx); /* Convert it */ - if (!(ppqn = ConvertFiletoList(ctx))) { + if (!(ppqn = xmi2mid_ConvertFiletoList(ctx))) { /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, NULL, 0);*/ break; } @@ -966,14 +983,14 @@ static uint32_t ExtractTracksFromXmi(struct xmi_ctx *ctx) { num++; /* go to start of next track */ - seeksrc(ctx, begin + ((len + 1) & ~1)); + xmi2mid_seeksrc(ctx, begin + ((len + 1) & ~1)); } /* Return how many were converted */ return (num); } -static int ParseXMI(struct xmi_ctx *ctx) { +static int xmi2mid_ParseXMI(struct xmi2mid_xmi_ctx *ctx) { uint32_t i; uint32_t start; uint32_t len; @@ -981,26 +998,26 @@ static int ParseXMI(struct xmi_ctx *ctx) { uint32_t file_size; char buf[32]; - file_size = getsrcsize(ctx); - if (getsrcpos(ctx) + 8 > file_size) { + file_size = xmi2mid_getsrcsize(ctx); + if (xmi2mid_getsrcpos(ctx) + 8 > file_size) { badfile: /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too short)", 0);*/ return (-1); } /* Read first 4 bytes of header */ - copy(ctx, buf, 4); + xmi2mid_copy(ctx, buf, 4); /* Could be XMIDI */ if (!memcmp(buf, "FORM", 4)) { /* Read length of */ - len = read4(ctx); + len = xmi2mid_read4(ctx); - start = getsrcpos(ctx); + start = xmi2mid_getsrcpos(ctx); if (start + 4 > file_size) goto badfile; /* Read 4 bytes of type */ - copy(ctx, buf, 4); + xmi2mid_copy(ctx, buf, 4); /* XDIRless XMIDI, we can handle them here. */ if (!memcmp(buf, "XMID", 4)) { @@ -1016,21 +1033,21 @@ badfile: /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too shor for (i = 4; i < len; i++) { /* check too short files */ - if (getsrcpos(ctx) + 10 > file_size) + if (xmi2mid_getsrcpos(ctx) + 10 > file_size) break; /* Read 4 bytes of type */ - copy(ctx, buf, 4); + xmi2mid_copy(ctx, buf, 4); /* Read length of chunk */ - chunk_len = read4(ctx); + chunk_len = xmi2mid_read4(ctx); /* Add eight bytes */ i += 8; if (memcmp(buf, "INFO", 4)) { /* Must align */ - skipsrc(ctx, (chunk_len + 1) & ~1); + xmi2mid_skipsrc(ctx, (chunk_len + 1) & ~1); i += (chunk_len + 1) & ~1; continue; } @@ -1039,7 +1056,7 @@ badfile: /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too shor if (chunk_len < 2) break; - ctx->info.tracks = read2(ctx); + ctx->info.tracks = xmi2mid_read2(ctx); break; } @@ -1050,12 +1067,12 @@ badfile: /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too shor /* Ok now to start part 2 * Goto the right place */ - seeksrc(ctx, start + ((len + 1) & ~1)); - if (getsrcpos(ctx) + 12 > file_size) + xmi2mid_seeksrc(ctx, start + ((len + 1) & ~1)); + if (xmi2mid_getsrcpos(ctx) + 12 > file_size) goto badfile; /* Read 4 bytes of type */ - copy(ctx, buf, 4); + xmi2mid_copy(ctx, buf, 4); if (memcmp(buf, "CAT ", 4)) { /*_WM_ERROR_NEW("XMI error: expected \"CAT \", found \"%c%c%c%c\".", @@ -1064,10 +1081,10 @@ badfile: /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too shor } /* Now read length of this track */ - read4(ctx); + xmi2mid_read4(ctx); /* Read 4 bytes of type */ - copy(ctx, buf, 4); + xmi2mid_copy(ctx, buf, 4); if (memcmp(buf, "XMID", 4)) { /*_WM_ERROR_NEW("XMI error: expected \"XMID\", found \"%c%c%c%c\".", @@ -1076,7 +1093,7 @@ badfile: /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too shor } /* Valid XMID */ - ctx->datastart = getsrcpos(ctx); + ctx->datastart = xmi2mid_getsrcpos(ctx); return (0); } } @@ -1084,16 +1101,16 @@ badfile: /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too shor return (-1); } -static int ExtractTracks(struct xmi_ctx *ctx) { +static int xmi2mid_ExtractTracks(struct xmi2mid_xmi_ctx *ctx) { uint32_t i; - ctx->events = calloc(ctx->info.tracks, sizeof(midi_event*)); - ctx->timing = calloc(ctx->info.tracks, sizeof(int16_t)); + 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; - seeksrc(ctx, ctx->datastart); - i = ExtractTracksFromXmi(ctx); + xmi2mid_seeksrc(ctx, ctx->datastart); + i = xmi2mid_ExtractTracksFromXmi(ctx); if (i != ctx->info.tracks) { /*_WM_ERROR_NEW("XMI error: extracted only %u out of %u tracks from XMIDI", diff --git a/src/file_reader.hpp b/src/file_reader.hpp index e0664b4..c2cfe7b 100644 --- a/src/file_reader.hpp +++ b/src/file_reader.hpp @@ -22,6 +22,10 @@ * DEALINGS IN THE SOFTWARE. */ +#pragma once +#ifndef FILE_AND_MEM_READER_HHHH +#define FILE_AND_MEM_READER_HHHH + #include <string> #include <cstdio> #ifdef _WIN32 @@ -83,6 +87,8 @@ public: */ void openFile(const char *path) { + if(m_fp) + this->close();//Close previously opened file first! #if !defined(_WIN32) || defined(__WATCOMC__) m_fp = std::fopen(path, "rb"); #else @@ -104,6 +110,8 @@ public: */ void openData(const void *mem, size_t lenght) { + if(m_fp) + this->close();//Close previously opened file first! m_fp = NULL; m_mp = mem; m_mp_size = lenght; @@ -117,6 +125,9 @@ public: */ void seek(long pos, int rel_to) { + if(!this->isValid()) + return; + if(m_fp)//If a file { std::fseek(m_fp, pos, rel_to); @@ -162,6 +173,8 @@ public: */ size_t read(void *buf, size_t num, size_t size) { + if(!this->isValid()) + return 0; if(m_fp) return std::fread(buf, num, size, m_fp); else @@ -186,6 +199,8 @@ public: */ int getc() { + if(!this->isValid()) + return -1; if(m_fp)//If a file { return std::getc(m_fp); @@ -206,6 +221,8 @@ public: */ size_t tell() { + if(!this->isValid()) + return 0; if(m_fp)//If a file return static_cast<size_t>(std::ftell(m_fp)); else//If a memory block @@ -241,6 +258,8 @@ public: */ bool eof() { + if(!this->isValid()) + return true; if(m_fp) return (std::feof(m_fp) != 0); else @@ -256,4 +275,22 @@ public: return m_file_name; } + /** + * @brief Retrieve file size + * @return Size of file in bytes + */ + size_t fileSize() + { + if(!this->isValid()) + return 0; + if(!m_fp) + return m_mp_size; //Size of memory block is well known + size_t old_pos = tell(); + seek(0, FileAndMemReader::END); + size_t file_size = tell(); + seek(old_pos, FileAndMemReader::SET); + return file_size; + } }; + +#endif /* FILE_AND_MEM_READER_HHHH */ diff --git a/src/midi_sequencer.h b/src/midi_sequencer.h new file mode 100644 index 0000000..d3b542e --- /dev/null +++ b/src/midi_sequencer.h @@ -0,0 +1,91 @@ +/* + * BW_Midi_Sequencer - MIDI Sequencer for C++ + * + * Copyright (c) 2015-2018 Vitaly Novichkov <admin@wohlnet.ru> + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#pragma once +#ifndef BISQUIT_AND_WOHLSTANDS_MIDI_SEQUENCER_HHHH +#define BISQUIT_AND_WOHLSTANDS_MIDI_SEQUENCER_HHHH + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stddef.h> +#include <stdint.h> + +struct BW_MidiRtInterface +{ + //! Raw MIDI event hook + typedef void (*RawEventHook)(void *userdata, uint8_t type, uint8_t subtype, uint8_t channel, const uint8_t *data, size_t len); + RawEventHook onEvent; + void *onEvent_userData; + + //! Library internal debug messages + typedef void (*DebugMessageHook)(void *userdata, const char *fmt, ...); + DebugMessageHook onDebugMessage; + void *onDebugMessage_userData; + + void *rtUserData; + + /* Standard MIDI events. All of them are required! */ + typedef void (*RtNoteOn)(void *userdata, uint8_t channel, uint8_t note, uint8_t velocity); + RtNoteOn rt_noteOn; + + typedef void (*RtNoteOff)(void *userdata, uint8_t channel, uint8_t note); + RtNoteOff rt_noteOff; + + typedef void (*RtNoteAfterTouch)(void *userdata, uint8_t channel, uint8_t note, uint8_t atVal); + RtNoteAfterTouch rt_noteAfterTouch; + + typedef void (*RtChannelAfterTouch)(void *userdata, uint8_t channel, uint8_t atVal); + RtChannelAfterTouch rt_channelAfterTouch; + + typedef void (*RtControlerChange)(void *userdata, uint8_t channel, uint8_t type, uint8_t value); + RtControlerChange rt_controllerChange; + + typedef void (*RtPatchChange)(void *userdata, uint8_t channel, uint8_t patch); + RtPatchChange rt_patchChange; + + typedef void (*RtPitchBend)(void *userdata, uint8_t channel, uint8_t msb, uint8_t lsb); + RtPitchBend rt_pitchBend; + + typedef void (*RtSysEx)(void *userdata, const uint8_t *msg, size_t size); + RtSysEx rt_systemExclusive; + + + /* NonStandard events. There are optional */ + typedef void (*RtRawOPL)(void *userdata, uint8_t reg, uint8_t value); + RtRawOPL rt_rawOPL; + + typedef void (*RtDeviceSwitch)(void *userdata, size_t track, const char *data, size_t length); + RtDeviceSwitch rt_deviceSwitch; + + typedef uint64_t (*RtCurrentDevice)(void *userdata, size_t track); + RtCurrentDevice rt_currentDevice; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* BISQUIT_AND_WOHLSTANDS_MIDI_SEQUENCER_HHHH */ diff --git a/src/midi_sequencer.hpp b/src/midi_sequencer.hpp new file mode 100644 index 0000000..370068b --- /dev/null +++ b/src/midi_sequencer.hpp @@ -0,0 +1,496 @@ +/* + * BW_Midi_Sequencer - MIDI Sequencer for C++ + * + * Copyright (c) 2015-2018 Vitaly Novichkov <admin@wohlnet.ru> + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#pragma once +#ifndef BISQUIT_AND_WOHLSTANDS_MIDI_SEQUENCER_HHHHPPP +#define BISQUIT_AND_WOHLSTANDS_MIDI_SEQUENCER_HHHHPPP + +#include <list> +#include <vector> + +#include "fraction.hpp" +#include "file_reader.hpp" +#include "midi_sequencer.h" + +//! Helper for unused values +#define BW_MidiSequencer_UNUSED(x) (void)x; + +class BW_MidiSequencer +{ + /** + * @brief MIDI Event utility container + */ + class MidiEvent + { + public: + MidiEvent(); + /** + * @brief Main MIDI event types + */ + enum Types + { + //! Unknown event + T_UNKNOWN = 0x00, + //! Note-Off event + T_NOTEOFF = 0x08,//size == 2 + //! Note-On event + T_NOTEON = 0x09,//size == 2 + //! Note After-Touch event + T_NOTETOUCH = 0x0A,//size == 2 + //! Controller change event + T_CTRLCHANGE = 0x0B,//size == 2 + //! Patch change event + T_PATCHCHANGE = 0x0C,//size == 1 + //! Channel After-Touch event + T_CHANAFTTOUCH = 0x0D,//size == 1 + //! Pitch-bend change event + T_WHEEL = 0x0E,//size == 2 + + //! System Exclusive message, type 1 + T_SYSEX = 0xF0,//size == len + //! Sys Com Song Position Pntr [LSB, MSB] + T_SYSCOMSPOSPTR = 0xF2,//size == 2 + //! Sys Com Song Select(Song #) [0-127] + T_SYSCOMSNGSEL = 0xF3,//size == 1 + //! System Exclusive message, type 2 + T_SYSEX2 = 0xF7,//size == len + //! Special event + T_SPECIAL = 0xFF + }; + /** + * @brief Special MIDI event sub-types + */ + enum SubTypes + { + //! Sequension number + ST_SEQNUMBER = 0x00,//size == 2 + //! Text label + ST_TEXT = 0x01,//size == len + //! Copyright notice + ST_COPYRIGHT = 0x02,//size == len + //! Sequence track title + ST_SQTRKTITLE = 0x03,//size == len + //! Instrument title + ST_INSTRTITLE = 0x04,//size == len + //! Lyrics text fragment + ST_LYRICS = 0x05,//size == len + //! MIDI Marker + ST_MARKER = 0x06,//size == len + //! Cue Point + ST_CUEPOINT = 0x07,//size == len + //! [Non-Standard] Device Switch + ST_DEVICESWITCH = 0x09,//size == len <CUSTOM> + //! MIDI Channel prefix + ST_MIDICHPREFIX = 0x20,//size == 1 + + //! End of Track event + ST_ENDTRACK = 0x2F,//size == 0 + //! Tempo change event + ST_TEMPOCHANGE = 0x51,//size == 3 + //! SMPTE offset + ST_SMPTEOFFSET = 0x54,//size == 5 + //! Time signature + ST_TIMESIGNATURE = 0x55, //size == 4 + //! Key signature + ST_KEYSIGNATURE = 0x59,//size == 2 + //! Sequencer specs + ST_SEQUENCERSPEC = 0x7F, //size == len + + /* Non-standard, internal ADLMIDI usage only */ + //! [Non-Standard] Loop Start point + ST_LOOPSTART = 0xE1,//size == 0 <CUSTOM> + //! [Non-Standard] Loop End point + ST_LOOPEND = 0xE2,//size == 0 <CUSTOM> + //! [Non-Standard] Raw OPL data + ST_RAWOPL = 0xE3//size == 0 <CUSTOM> + }; + //! Main type of event + uint8_t type; + //! Sub-type of the event + uint8_t subtype; + //! Targeted MIDI channel + uint8_t channel; + //! Is valid event + uint8_t isValid; + //! Reserved 5 bytes padding + uint8_t __padding[4]; + //! Absolute tick position (Used for the tempo calculation only) + uint64_t absPosition; + //! Raw data of this event + std::vector<uint8_t> data; + }; + + /** + * @brief A track position event contains a chain of MIDI events until next delay value + * + * Created with purpose to sort events by type in the same position + * (for example, to keep controllers always first than note on events or lower than note-off events) + */ + class MidiTrackRow + { + public: + MidiTrackRow(); + //! Clear MIDI row data + void clear(); + //! Absolute time position in seconds + double time; + //! Delay to next event in ticks + uint64_t delay; + //! Absolute position in ticks + uint64_t absPos; + //! Delay to next event in seconds + double timeDelay; + //! List of MIDI events in the current row + std::vector<MidiEvent> events; + /** + * @brief Sort events in this position + * @param noteStates Buffer of currently pressed/released note keys in the track + */ + void sortEvents(bool *noteStates = NULL); + }; + + /** + * @brief Tempo change point entry. Used in the MIDI data building function only. + */ + struct TempoChangePoint + { + uint64_t absPos; + fraction<uint64_t> tempo; + }; + //P.S. I declared it here instead of local in-function because C++98 can't process templates with locally-declared structures + + typedef std::list<MidiTrackRow> MidiTrackQueue; + + /** + * @brief Song position context + */ + struct Position + { + //! Was track began playing + bool began; + char padding[7]; + //! Waiting time before next event in seconds + double wait; + //! Absolute time position on the track in seconds + double absTimePosition; + //! Track information + struct TrackInfo + { + size_t ptr; + //! Delay to next event in a track + uint64_t delay; + //! Last handled event type + int lastHandledEvent; + char padding2[4]; + //! MIDI Events queue position iterator + MidiTrackQueue::iterator pos; + TrackInfo(): ptr(0), delay(0), lastHandledEvent(0) {} + }; + std::vector<TrackInfo> track; + Position(): began(false), wait(0.0), absTimePosition(0.0), track() + {} + }; + + //! MIDI Output interface context + const BW_MidiRtInterface *m_interface; + + /** + * @brief Build MIDI track data from the raw track data storage + * @return true if everything successfully processed, or false on any error + */ + bool buildTrackData(const std::vector<std::vector<uint8_t> > &trackData); + + /** + * @brief Parse one event from raw MIDI track stream + * @param [_inout] ptr pointer to pointer to current position on the raw data track + * @param [_in] end address to end of raw track data, needed to validate position and size + * @param [_inout] status status of the track processing + * @return Parsed MIDI event entry + */ + MidiEvent parseEvent(const uint8_t **ptr, const uint8_t *end, int &status); + + /** + * @brief Process MIDI events on the current tick moment + * @param isSeek is a seeking process + * @return returns false on reaching end of the song + */ + bool processEvents(bool isSeek = false); + + /** + * @brief Handle one event from the chain + * @param tk MIDI track + * @param evt MIDI event entry + * @param status Recent event type, -1 returned when end of track event was handled. + */ + void handleEvent(size_t tk, const MidiEvent &evt, int &status); + +public: + /** + * @brief MIDI marker entry + */ + struct MIDI_MarkerEntry + { + //! Label + std::string label; + //! Position time in seconds + double pos_time; + //! Position time in MIDI ticks + uint64_t pos_ticks; + }; + + /** + * @brief Container of one raw CMF instrument + */ + struct CmfInstrument + { + //! Raw CMF instrument data + uint8_t data[16]; + }; + + /** + * @brief The FileFormat enum + */ + enum FileFormat + { + //! MIDI format + Format_MIDI, + //! CMF format + Format_CMF, + //! Id-Software Music File + Format_IMF, + //! EA-MUS format + Format_RSXX + }; + +private: + //! Music file format type. MIDI is default. + FileFormat m_format; + + //! Current position + Position m_currentPosition; + //! Track begin position + Position m_trackBeginPosition; + //! Loop start point + Position m_loopBeginPosition; + + //! Is looping enabled or not + bool m_loopEnabled; + + //! Full song length in seconds + double m_fullSongTimeLength; + //! Delay after song playd before rejecting the output stream requests + double m_postSongWaitDelay; + + //! Loop start time + double m_loopStartTime; + //! Loop end time + double m_loopEndTime; + + //! Pre-processed track data storage + std::vector<MidiTrackQueue > m_trackData; + + //! CMF instruments + std::vector<CmfInstrument> m_cmfInstruments; + + //! Title of music + std::string m_musTitle; + //! Copyright notice of music + std::string m_musCopyright; + //! List of track titles + std::vector<std::string> m_musTrackTitles; + //! List of MIDI markers + std::vector<MIDI_MarkerEntry> m_musMarkers; + + //! Time of one tick + fraction<uint64_t> m_invDeltaTicks; + //! Current tempo + fraction<uint64_t> m_tempo; + + //! Tempo multiplier factor + double m_tempoMultiplier; + //! Is song at end + bool m_atEnd; + //! Loop start has reached + bool m_loopStart; + //! Loop end has reached, reset on handling + bool m_loopEnd; + //! Are loop points invalid? + bool m_invalidLoop; /*Loop points are invalid (loopStart after loopEnd or loopStart and loopEnd are on same place)*/ + + //! File parsing errors string (adding into m_errorString on aborting of the process) + std::string m_parsingErrorsString; + //! Common error string + std::string m_errorString; + +public: + BW_MidiSequencer(); + virtual ~BW_MidiSequencer(); + + /** + * @brief Sets the RT interface + * @param interface Pre-Initialized interface structure (pointer will be taken) + */ + void setInterface(const BW_MidiRtInterface *interface); + + /** + * @brief Returns file format type of currently loaded file + * @return File format type enumeration + */ + FileFormat getFormat(); + + /** + * @brief Get the list of CMF instruments (CMF only) + * @return Array of raw CMF instruments entries + */ + const std::vector<CmfInstrument> getRawCmfInstruments(); + + /** + * @brief Get string that describes reason of error + * @return Error string + */ + const std::string &getErrorString(); + + /** + * @brief Check is loop enabled + * @return true if loop enabled + */ + bool getLoopEnabled(); + + /** + * @brief Switch loop on/off + * @param enabled Enable loop + */ + void setLoopEnabled(bool enabled); + + /** + * @brief Get music title + * @return music title string + */ + const std::string &getMusicTitle(); + + /** + * @brief Get music copyright notice + * @return music copyright notice string + */ + const std::string &getMusicCopyright(); + + /** + * @brief Get list of track titles + * @return array of track title strings + */ + const std::vector<std::string> &getTrackTitles(); + + /** + * @brief Get list of MIDI markers + * @return Array of MIDI marker structures + */ + const std::vector<MIDI_MarkerEntry> &getMarkers(); + + /** + * @brief Is position of song at end + * @return true if end of song was reached + */ + bool positionAtEnd(); + + /** + * @brief Load MIDI file from path + * @param filename Path to file to open + * @return true if file successfully opened, false on any error + */ + bool loadMIDI(const std::string &filename); + + /** + * @brief Load MIDI file from a memory block + * @param data Pointer to memory block with MIDI data + * @param size Size of source memory block + * @return true if file successfully opened, false on any error + */ + bool loadMIDI(const void *data, size_t size); + + /** + * @brief Load MIDI file by using FileAndMemReader interface + * @param fr FileAndMemReader context with opened source file + * @return true if file successfully opened, false on any error + */ + bool loadMIDI(FileAndMemReader &fr); + + /** + * @brief Periodic tick handler. + * @param s seconds since last call + * @param granularity don't expect intervals smaller than this, in seconds + * @return desired number of seconds until next call + */ + double Tick(double s, double granularity); + + /** + * @brief Change current position to specified time position in seconds + * @param granularity don't expect intervals smaller than this, in seconds + * @param seconds Absolute time position in seconds + * @return desired number of seconds until next call of Tick() + */ + double seek(double seconds, const double granularity); + + /** + * @brief Gives current time position in seconds + * @return Current time position in seconds + */ + double tell(); + + /** + * @brief Gives time length of current song in seconds + * @return Time length of current song in seconds + */ + double timeLength(); + + /** + * @brief Gives loop start time position in seconds + * @return Loop start time position in seconds or -1 if song has no loop points + */ + double getLoopStart(); + + /** + * @brief Gives loop end time position in seconds + * @return Loop end time position in seconds or -1 if song has no loop points + */ + double getLoopEnd(); + + /** + * @brief Return to begin of current song + */ + void rewind(); + + /** + * @brief Get current tempor multiplier value + * @return + */ + double getTempoMultiplier(); + + /** + * @brief Set tempo multiplier + * @param tempo Tempo multiplier: 1.0 - original tempo. >1 - faster, <1 - slower + */ + void setTempo(double tempo); +}; + +#endif /* BISQUIT_AND_WOHLSTANDS_MIDI_SEQUENCER_HHHHPPP */ diff --git a/src/midi_sequencer_impl.hpp b/src/midi_sequencer_impl.hpp new file mode 100644 index 0000000..1e4e643 --- /dev/null +++ b/src/midi_sequencer_impl.hpp @@ -0,0 +1,1780 @@ +/* + * BW_Midi_Sequencer - MIDI Sequencer for C++ + * + * Copyright (c) 2015-2018 Vitaly Novichkov <admin@wohlnet.ru> + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include "midi_sequencer.hpp" +#include <stdio.h> +#include <memory> +#include <cstring> + +#include <assert.h> + +#ifndef BWMIDI_DISABLE_MUS_SUPPORT +#include "cvt_mus2mid.hpp" +#endif//MUS + +#ifndef BWMIDI_DISABLE_XMI_SUPPORT +#include "cvt_xmi2mid.hpp" +#endif//XMI + +/** + * @brief Utility function to read Big-Endian integer from raw binary data + * @param buffer Pointer to raw binary buffer + * @param nbytes Count of bytes to parse integer + * @return Extracted unsigned integer + */ +static inline uint64_t ReadBEint(const void *buffer, size_t nbytes) +{ + uint64_t result = 0; + const unsigned char *data = reinterpret_cast<const unsigned char *>(buffer); + + for(unsigned n = 0; n < nbytes; ++n) + result = (result << 8) + data[n]; + + return result; +} + +/** + * @brief Utility function to read Little-Endian integer from raw binary data + * @param buffer Pointer to raw binary buffer + * @param nbytes Count of bytes to parse integer + * @return Extracted unsigned integer + */ +static inline uint64_t ReadLEint(const void *buffer, size_t nbytes) +{ + uint64_t result = 0; + const unsigned char *data = reinterpret_cast<const unsigned char *>(buffer); + + for(unsigned n = 0; n < nbytes; ++n) + result = result + static_cast<uint64_t>(data[n] << (n * 8)); + + return result; +} + +/** + * @brief Standard MIDI Variable-Length numeric value parser without of validation + * @param [_inout] ptr Pointer to memory block that contains begin of variable-length value + * @return Unsigned integer that conains parsed variable-length value + */ +static inline uint64_t ReadVarLen(uint8_t **ptr) +{ + uint64_t result = 0; + for(;;) + { + uint8_t byte = *((*ptr)++); + result = (result << 7) + (byte & 0x7F); + if(!(byte & 0x80)) + break; + } + return result; +} + +/** + * @brief Secure Standard MIDI Variable-Length numeric value parser with anti-out-of-range protection + * @param [_inout] ptr Pointer to memory block that contains begin of variable-length value, will be iterated forward + * @param [_in end Pointer to end of memory block where variable-length value is stored (after end of track) + * @param [_out] ok Reference to boolean which takes result of variable-length value parsing + * @return Unsigned integer that conains parsed variable-length value + */ +static inline uint64_t ReadVarLenEx(const uint8_t **ptr, const uint8_t *end, bool &ok) +{ + uint64_t result = 0; + ok = false; + + for(;;) + { + if(*ptr >= end) + return 2; + unsigned char byte = *((*ptr)++); + result = (result << 7) + (byte & 0x7F); + if(!(byte & 0x80)) + break; + } + + ok = true; + return result; +} + +BW_MidiSequencer::MidiEvent::MidiEvent() : + type(T_UNKNOWN), + subtype(T_UNKNOWN), + channel(0), + isValid(1), + absPosition(0) +{} + +BW_MidiSequencer::MidiTrackRow::MidiTrackRow() : + time(0.0), + delay(0), + absPos(0), + timeDelay(0.0) +{} + +void BW_MidiSequencer::MidiTrackRow::clear() +{ + time = 0.0; + delay = 0; + absPos = 0; + timeDelay = 0.0; + events.clear(); +} + +void BW_MidiSequencer::MidiTrackRow::sortEvents(bool *noteStates) +{ + typedef std::vector<MidiEvent> EvtArr; + EvtArr metas; + EvtArr noteOffs; + EvtArr controllers; + EvtArr anyOther; + + metas.reserve(events.size()); + noteOffs.reserve(events.size()); + controllers.reserve(events.size()); + anyOther.reserve(events.size()); + + for(size_t i = 0; i < events.size(); i++) + { + if(events[i].type == MidiEvent::T_NOTEOFF) + noteOffs.push_back(events[i]); + else if((events[i].type == MidiEvent::T_CTRLCHANGE) + || (events[i].type == MidiEvent::T_PATCHCHANGE) + || (events[i].type == MidiEvent::T_WHEEL) + || (events[i].type == MidiEvent::T_CHANAFTTOUCH)) + { + controllers.push_back(events[i]); + } + else if((events[i].type == MidiEvent::T_SPECIAL) && (events[i].subtype == MidiEvent::ST_MARKER)) + metas.push_back(events[i]); + else + anyOther.push_back(events[i]); + } + + /* + * If Note-Off and it's Note-On is on the same row - move this damned note off down! + */ + if(noteStates) + { + std::set<size_t> markAsOn; + for(size_t i = 0; i < anyOther.size(); i++) + { + const MidiEvent e = anyOther[i]; + if(e.type == MidiEvent::T_NOTEON) + { + const size_t note_i = (e.channel * 255) + (e.data[0] & 0x7F); + //Check, was previously note is on or off + bool wasOn = noteStates[note_i]; + markAsOn.insert(note_i); + // Detect zero-length notes are following previously pressed note + int noteOffsOnSameNote = 0; + for(EvtArr::iterator j = noteOffs.begin(); j != noteOffs.end();) + { + //If note was off, and note-off on same row with note-on - move it down! + if( + ((*j).channel == e.channel) && + ((*j).data[0] == e.data[0]) + ) + { + //If note is already off OR more than one note-off on same row and same note + if(!wasOn || (noteOffsOnSameNote != 0)) + { + anyOther.push_back(*j); + j = noteOffs.erase(j); + markAsOn.erase(note_i); + continue; + } + else + { + //When same row has many note-offs on same row + //that means a zero-length note follows previous note + //it must be shuted down + noteOffsOnSameNote++; + } + } + j++; + } + } + } + + //Mark other notes as released + for(EvtArr::iterator j = noteOffs.begin(); j != noteOffs.end(); j++) + { + size_t note_i = (j->channel * 255) + (j->data[0] & 0x7F); + noteStates[note_i] = false; + } + + for(std::set<size_t>::iterator j = markAsOn.begin(); j != markAsOn.end(); j++) + noteStates[*j] = true; + } + /***********************************************************************************/ + + events.clear(); + events.insert(events.end(), noteOffs.begin(), noteOffs.end()); + events.insert(events.end(), metas.begin(), metas.end()); + events.insert(events.end(), controllers.begin(), controllers.end()); + events.insert(events.end(), anyOther.begin(), anyOther.end()); +} + +BW_MidiSequencer::BW_MidiSequencer() : + m_interface(NULL), + m_format(Format_MIDI), + m_loopEnabled(false), + m_fullSongTimeLength(0.0), + m_postSongWaitDelay(1.0), + m_loopStartTime(-1.0), + m_loopEndTime(-1.0), + m_tempoMultiplier(1.0), + m_atEnd(false), + m_loopStart(false), + m_loopEnd(false), + m_invalidLoop(false) +{} + +BW_MidiSequencer::~BW_MidiSequencer() +{} + +void BW_MidiSequencer::setInterface(const BW_MidiRtInterface *interface) +{ + // Interface must NOT be NULL + assert(interface); + + //Note ON hook is REQUIRED + assert(interface->rt_noteOn); + //Note OFF hook is REQUIRED + assert(interface->rt_noteOff); + //Note Aftertouch hook is REQUIRED + assert(interface->rt_noteAfterTouch); + //Channel Aftertouch hook is REQUIRED + assert(interface->rt_channelAfterTouch); + //Controller change hook is REQUIRED + assert(interface->rt_controllerChange); + //Patch change hook is REQUIRED + assert(interface->rt_patchChange); + //Pitch bend hook is REQUIRED + assert(interface->rt_pitchBend); + //System Exclusive hook is REQUIRED + assert(interface->rt_systemExclusive); + + m_interface = interface; +} + +BW_MidiSequencer::FileFormat BW_MidiSequencer::getFormat() +{ + return m_format; +} + +const std::vector<BW_MidiSequencer::CmfInstrument> BW_MidiSequencer::getRawCmfInstruments() +{ + return m_cmfInstruments; +} + +const std::string &BW_MidiSequencer::getErrorString() +{ + return m_errorString; +} + +bool BW_MidiSequencer::getLoopEnabled() +{ + return m_loopEnabled; +} + +void BW_MidiSequencer::setLoopEnabled(bool enabled) +{ + m_loopEnabled = enabled; +} + +const std::string &BW_MidiSequencer::getMusicTitle() +{ + return m_musTitle; +} + +const std::string &BW_MidiSequencer::getMusicCopyright() +{ + return m_musCopyright; +} + +const std::vector<std::string> &BW_MidiSequencer::getTrackTitles() +{ + return m_musTrackTitles; +} + +const std::vector<BW_MidiSequencer::MIDI_MarkerEntry> &BW_MidiSequencer::getMarkers() +{ + return m_musMarkers; +} + +bool BW_MidiSequencer::positionAtEnd() +{ + return m_atEnd; +} + +double BW_MidiSequencer::getTempoMultiplier() +{ + return m_tempoMultiplier; +} + +bool BW_MidiSequencer::buildTrackData(const std::vector<std::vector<uint8_t> > &trackData) +{ + m_fullSongTimeLength = 0.0; + m_loopStartTime = -1.0; + m_loopEndTime = -1.0; + m_musTitle.clear(); + m_musCopyright.clear(); + m_musTrackTitles.clear(); + m_musMarkers.clear(); + m_trackData.clear(); + const size_t trackCount = trackData.size(); + m_trackData.resize(trackCount, MidiTrackQueue()); + + m_invalidLoop = false; + bool gotLoopStart = false, gotLoopEnd = false, gotLoopEventInThisRow = false; + //! Tick position of loop start tag + uint64_t loopStartTicks = 0; + //! Tick position of loop end tag + uint64_t loopEndTicks = 0; + //! Full length of song in ticks + uint64_t ticksSongLength = 0; + //! Cache for error message strign + char error[150]; + + m_currentPosition.track.clear(); + m_currentPosition.track.resize(trackCount); + + //! Caches note on/off states. + bool noteStates[16 * 255]; + /* This is required to carefully detect zero-length notes * + * and avoid a move of "note-off" event over "note-on" while sort. * + * Otherwise, after sort those notes will play infinite sound */ + + //Tempo change events + std::vector<MidiEvent> tempos; + + /* + * TODO: Make this be safer for memory in case of broken input data + * which may cause going away of available track data (and then give a crash!) + * + * POST: Check this more carefully for possible vulnuabilities are can crash this + */ + for(size_t tk = 0; tk < trackCount; ++tk) + { + uint64_t abs_position = 0; + int status = 0; + MidiEvent event; + bool ok = false; + const uint8_t *end = trackData[tk].data() + trackData[tk].size(); + const uint8_t *trackPtr = trackData[tk].data(); + std::memset(noteStates, 0, sizeof(noteStates)); + + //Time delay that follows the first event in the track + { + MidiTrackRow evtPos; + if(m_format == Format_RSXX) + ok = true; + else + evtPos.delay = ReadVarLenEx(&trackPtr, end, ok); + if(!ok) + { + int len = snprintf(error, 150, "buildTrackData: Can't read variable-length value at begin of track %d.\n", (int)tk); + if((len > 0) && (len < 150)) + m_parsingErrorsString += std::string(error, (size_t)len); + return false; + } + + //HACK: Begin every track with "Reset all controllers" event to avoid controllers state break came from end of song + for(uint8_t chan = 0; chan < 16; chan++) + { + MidiEvent event; + event.type = MidiEvent::T_CTRLCHANGE; + event.channel = chan; + event.data.push_back(121); + event.data.push_back(0); + evtPos.events.push_back(event); + } + + evtPos.absPos = abs_position; + abs_position += evtPos.delay; + m_trackData[tk].push_back(evtPos); + } + + MidiTrackRow evtPos; + do + { + event = parseEvent(&trackPtr, end, status); + if(!event.isValid) + { + int len = snprintf(error, 150, "buildTrackData: Fail to parse event in the track %d.\n", (int)tk); + if((len > 0) && (len < 150)) + m_parsingErrorsString += std::string(error, (size_t)len); + return false; + } + + evtPos.events.push_back(event); + if(event.type == MidiEvent::T_SPECIAL) + { + if(event.subtype == MidiEvent::ST_TEMPOCHANGE) + { + event.absPosition = abs_position; + tempos.push_back(event); + } + else if(!m_invalidLoop && (event.subtype == MidiEvent::ST_LOOPSTART)) + { + /* + * loopStart is invalid when: + * - starts together with loopEnd + * - appears more than one time in same MIDI file + */ + if(gotLoopStart || gotLoopEventInThisRow) + m_invalidLoop = true; + else + { + gotLoopStart = true; + loopStartTicks = abs_position; + } + //In this row we got loop event, register this! + gotLoopEventInThisRow = true; + } + else if(!m_invalidLoop && (event.subtype == MidiEvent::ST_LOOPEND)) + { + /* + * loopEnd is invalid when: + * - starts before loopStart + * - starts together with loopStart + * - appars more than one time in same MIDI file + */ + if(gotLoopEnd || gotLoopEventInThisRow) + m_invalidLoop = true; + else + { + gotLoopEnd = true; + loopEndTicks = abs_position; + } + //In this row we got loop event, register this! + gotLoopEventInThisRow = true; + } + } + + if(event.subtype != MidiEvent::ST_ENDTRACK)//Don't try to read delta after EndOfTrack event! + { + evtPos.delay = ReadVarLenEx(&trackPtr, end, ok); + if(!ok) + { + /* End of track has been reached! However, there is no EOT event presented */ + event.type = MidiEvent::T_SPECIAL; + event.subtype = MidiEvent::ST_ENDTRACK; + } + } + + if((evtPos.delay > 0) || (event.subtype == MidiEvent::ST_ENDTRACK)) + { + evtPos.absPos = abs_position; + abs_position += evtPos.delay; + evtPos.sortEvents(noteStates); + m_trackData[tk].push_back(evtPos); + evtPos.clear(); + gotLoopEventInThisRow = false; + } + } + while((trackPtr <= end) && (event.subtype != MidiEvent::ST_ENDTRACK)); + + if(ticksSongLength < abs_position) + ticksSongLength = abs_position; + //Set the chain of events begin + if(m_trackData[tk].size() > 0) + m_currentPosition.track[tk].pos = m_trackData[tk].begin(); + } + + if(gotLoopStart && !gotLoopEnd) + { + gotLoopEnd = true; + loopEndTicks = ticksSongLength; + } + + //loopStart must be located before loopEnd! + if(loopStartTicks >= loopEndTicks) + m_invalidLoop = true; + + /********************************************************************************/ + //Calculate time basing on collected tempo events + /********************************************************************************/ + for(size_t tk = 0; tk < trackCount; ++tk) + { + fraction<uint64_t> currentTempo = m_tempo; + double time = 0.0; + uint64_t abs_position = 0; + size_t tempo_change_index = 0; + MidiTrackQueue &track = m_trackData[tk]; + if(track.empty()) + continue;//Empty track is useless! + +#ifdef DEBUG_TIME_CALCULATION + std::fprintf(stdout, "\n============Track %" PRIuPTR "=============\n", tk); + std::fflush(stdout); +#endif + + MidiTrackRow *posPrev = &(*(track.begin()));//First element + for(MidiTrackQueue::iterator it = track.begin(); it != track.end(); it++) + { +#ifdef DEBUG_TIME_CALCULATION + bool tempoChanged = false; +#endif + MidiTrackRow &pos = *it; + if((posPrev != &pos) && //Skip first event + (!tempos.empty()) && //Only when in-track tempo events are available + (tempo_change_index < tempos.size()) + ) + { + // If tempo event is going between of current and previous event + if(tempos[tempo_change_index].absPosition <= pos.absPos) + { + //Stop points: begin point and tempo change points are before end point + std::vector<TempoChangePoint> points; + fraction<uint64_t> t; + TempoChangePoint firstPoint = {posPrev->absPos, currentTempo}; + points.push_back(firstPoint); + + //Collect tempo change points between previous and current events + do + { + TempoChangePoint tempoMarker; + MidiEvent &tempoPoint = tempos[tempo_change_index]; + tempoMarker.absPos = tempoPoint.absPosition; + tempoMarker.tempo = m_invDeltaTicks * fraction<uint64_t>(ReadBEint(tempoPoint.data.data(), tempoPoint.data.size())); + points.push_back(tempoMarker); + tempo_change_index++; + } + while((tempo_change_index < tempos.size()) && + (tempos[tempo_change_index].absPosition <= pos.absPos)); + + // Re-calculate time delay of previous event + time -= posPrev->timeDelay; + posPrev->timeDelay = 0.0; + + for(size_t i = 0, j = 1; j < points.size(); i++, j++) + { + /* If one or more tempo events are appears between of two events, + * calculate delays between each tempo point, begin and end */ + uint64_t midDelay = 0; + //Delay between points + midDelay = points[j].absPos - points[i].absPos; + //Time delay between points + t = midDelay * currentTempo; + posPrev->timeDelay += t.value(); + + //Apply next tempo + currentTempo = points[j].tempo; +#ifdef DEBUG_TIME_CALCULATION + tempoChanged = true; +#endif + } + //Then calculate time between last tempo change point and end point + TempoChangePoint tailTempo = points.back(); + uint64_t postDelay = pos.absPos - tailTempo.absPos; + t = postDelay * currentTempo; + posPrev->timeDelay += t.value(); + + //Store Common time delay + posPrev->time = time; + time += posPrev->timeDelay; + } + } + + fraction<uint64_t> t = pos.delay * currentTempo; + pos.timeDelay = t.value(); + pos.time = time; + time += pos.timeDelay; + + //Capture markers after time value calculation + for(size_t i = 0; i < pos.events.size(); i++) + { + MidiEvent &e = pos.events[i]; + if((e.type == MidiEvent::T_SPECIAL) && (e.subtype == MidiEvent::ST_MARKER)) + { + MIDI_MarkerEntry marker; + marker.label = std::string((char *)e.data.data(), e.data.size()); + marker.pos_ticks = pos.absPos; + marker.pos_time = pos.time; + m_musMarkers.push_back(marker); + } + } + + //Capture loop points time positions + if(!m_invalidLoop) + { + // Set loop points times + if(loopStartTicks == pos.absPos) + m_loopStartTime = pos.time; + else if(loopEndTicks == pos.absPos) + m_loopEndTime = pos.time; + } + +#ifdef DEBUG_TIME_CALCULATION + std::fprintf(stdout, "= %10" PRId64 " = %10f%s\n", pos.absPos, pos.time, tempoChanged ? " <----TEMPO CHANGED" : ""); + std::fflush(stdout); +#endif + + abs_position += pos.delay; + posPrev = &pos; + } + + if(time > m_fullSongTimeLength) + m_fullSongTimeLength = time; + } + + m_fullSongTimeLength += m_postSongWaitDelay; + //Set begin of the music + m_trackBeginPosition = m_currentPosition; + //Initial loop position will begin at begin of track until passing of the loop point + m_loopBeginPosition = m_currentPosition; + + /********************************************************************************/ + //Resolve "hell of all times" of too short drum notes: + //move too short percussion note-offs far far away as possible + /********************************************************************************/ +#if 1 //Use this to record WAVEs for comparison before/after implementing of this + if(m_format == Format_MIDI)//Percussion fix is needed for MIDI only, not for IMF/RSXX or CMF + { + //! Minimal real time in seconds +#define DRUM_NOTE_MIN_TIME 0.03 + //! Minimal ticks count +#define DRUM_NOTE_MIN_TICKS 15 + struct NoteState + { + double delay; + uint64_t delayTicks; + bool isOn; + char ___pad[7]; + } drNotes[255]; + uint16_t banks[16]; + + for(size_t tk = 0; tk < trackCount; ++tk) + { + std::memset(drNotes, 0, sizeof(drNotes)); + std::memset(banks, 0, sizeof(banks)); + MidiTrackQueue &track = m_trackData[tk]; + if(track.empty()) + continue;//Empty track is useless! + + for(MidiTrackQueue::iterator it = track.begin(); it != track.end(); it++) + { + MidiTrackRow &pos = *it; + + for(ssize_t e = 0; e < (ssize_t)pos.events.size(); e++) + { + MidiEvent *et = &pos.events[(size_t)e]; + + /* Set MSB/LSB bank */ + if(et->type == MidiEvent::T_CTRLCHANGE) + { + uint8_t ctrlno = et->data[0]; + uint8_t value = et->data[1]; + switch(ctrlno) + { + case 0: // Set bank msb (GM bank) + banks[et->channel] = uint16_t(uint16_t(value) << 8) | uint16_t(banks[et->channel] & 0x00FF); + break; + case 32: // Set bank lsb (XG bank) + banks[et->channel] = (banks[et->channel] & 0xFF00) | (uint16_t(value) & 0x00FF); + break; + } + continue; + } + + bool percussion = (et->channel == 9) || + banks[et->channel] == 0x7E00 || //XG SFX1/SFX2 channel (16128 signed decimal) + banks[et->channel] == 0x7F00; //XG Percussion channel (16256 signed decimal) + if(!percussion) + continue; + + if(et->type == MidiEvent::T_NOTEON) + { + uint8_t note = et->data[0] & 0x7F; + NoteState &ns = drNotes[note]; + ns.isOn = true; + ns.delay = 0.0; + ns.delayTicks = 0; + } + else if(et->type == MidiEvent::T_NOTEOFF) + { + uint8_t note = et->data[0] & 0x7F; + NoteState &ns = drNotes[note]; + if(ns.isOn) + { + ns.isOn = false; + if(ns.delayTicks < DRUM_NOTE_MIN_TICKS || ns.delay < DRUM_NOTE_MIN_TIME)//If note is too short + { + //Move it into next event position if that possible + for(MidiTrackQueue::iterator itNext = it; + itNext != track.end(); + itNext++) + { + MidiTrackRow &posN = *itNext; + if(ns.delayTicks > DRUM_NOTE_MIN_TICKS && ns.delay > DRUM_NOTE_MIN_TIME) + { + //Put note-off into begin of next event list + posN.events.insert(posN.events.begin(), pos.events[(size_t)e]); + //Renive this event from a current row + pos.events.erase(pos.events.begin() + (int)e); + e--; + break; + } + ns.delay += posN.timeDelay; + ns.delayTicks += posN.delay; + } + } + ns.delay = 0.0; + ns.delayTicks = 0; + } + } + } + + //Append time delays to sustaining notes + for(size_t no = 0; no < 128; no++) + { + NoteState &ns = drNotes[no]; + if(ns.isOn) + { + ns.delay += pos.timeDelay; + ns.delayTicks += pos.delay; + } + } + } + } +#undef DRUM_NOTE_MIN_TIME +#undef DRUM_NOTE_MIN_TICKS + } +#endif + + return true; +} + +bool BW_MidiSequencer::processEvents(bool isSeek) +{ + if(m_currentPosition.track.size() == 0) + m_atEnd = true;//No MIDI track data to play + if(m_atEnd) + return false;//No more events in the queue + + m_loopEnd = false; + const size_t TrackCount = m_currentPosition.track.size(); + const Position RowBeginPosition(m_currentPosition); + +#ifdef DEBUG_TIME_CALCULATION + double maxTime = 0.0; +#endif + + for(size_t tk = 0; tk < TrackCount; ++tk) + { + Position::TrackInfo &track = m_currentPosition.track[tk]; + if((track.lastHandledEvent >= 0) && (track.delay <= 0)) + { + //Check is an end of track has been reached + if(track.pos == m_trackData[tk].end()) + { + track.lastHandledEvent = -1; + break; + } + + // Handle event + for(size_t i = 0; i < track.pos->events.size(); i++) + { + const MidiEvent &evt = track.pos->events[i]; +#ifdef ENABLE_BEGIN_SILENCE_SKIPPING + if(!CurrentPositionNew.began && (evt.type == MidiEvent::T_NOTEON)) + CurrentPositionNew.began = true; +#endif + if(isSeek && (evt.type == MidiEvent::T_NOTEON)) + continue; + handleEvent(tk, evt, track.lastHandledEvent); + if(m_loopEnd) + break;//Stop event handling on catching loopEnd event! + } + +#ifdef DEBUG_TIME_CALCULATION + if(maxTime < track.pos->time) + maxTime = track.pos->time; +#endif + // Read next event time (unless the track just ended) + if(track.lastHandledEvent >= 0) + { + track.delay += track.pos->delay; + track.pos++; + } + } + } + +#ifdef DEBUG_TIME_CALCULATION + std::fprintf(stdout, " \r"); + std::fprintf(stdout, "Time: %10f; Audio: %10f\r", maxTime, CurrentPositionNew.absTimePosition); + std::fflush(stdout); +#endif + + // Find shortest delay from all track + uint64_t shortest = 0; + bool shortest_no = true; + + for(size_t tk = 0; tk < TrackCount; ++tk) + { + Position::TrackInfo &track = m_currentPosition.track[tk]; + if((track.lastHandledEvent >= 0) && (shortest_no || track.delay < shortest)) + { + shortest = track.delay; + shortest_no = false; + } + } + + //if(shortest > 0) UI.PrintLn("shortest: %ld", shortest); + + // Schedule the next playevent to be processed after that delay + for(size_t tk = 0; tk < TrackCount; ++tk) + m_currentPosition.track[tk].delay -= shortest; + + fraction<uint64_t> t = shortest * m_tempo; + +#ifdef ENABLE_BEGIN_SILENCE_SKIPPING + if(CurrentPositionNew.began) +#endif + m_currentPosition.wait += t.value(); + + //if(shortest > 0) UI.PrintLn("Delay %ld (%g)", shortest, (double)t.valuel()); + if(m_loopStart) + { + m_loopBeginPosition = RowBeginPosition; + m_loopStart = false; + } + + if(shortest_no || m_loopEnd) + { + //Loop if song end or loop end point has reached + m_loopEnd = false; + shortest = 0; + if(!m_loopEnabled) + { + m_atEnd = true; //Don't handle events anymore + m_currentPosition.wait += m_postSongWaitDelay;//One second delay until stop playing + return true;//We have caugh end here! + } + m_currentPosition = m_loopBeginPosition; + } + + return true;//Has events in queue +} + +BW_MidiSequencer::MidiEvent BW_MidiSequencer::parseEvent(const uint8_t **pptr, const uint8_t *end, int &status) +{ + const uint8_t *&ptr = *pptr; + BW_MidiSequencer::MidiEvent evt; + + if(ptr + 1 > end) + { + //When track doesn't ends on the middle of event data, it's must be fine + evt.type = MidiEvent::T_SPECIAL; + evt.subtype = MidiEvent::ST_ENDTRACK; + return evt; + } + + unsigned char byte = *(ptr++); + bool ok = false; + + if(byte == MidiEvent::T_SYSEX || byte == MidiEvent::T_SYSEX2)// Ignore SysEx + { + uint64_t length = ReadVarLenEx(pptr, end, ok); + if(!ok || (ptr + length > end)) + { + m_parsingErrorsString += "parseEvent: Can't read SysEx event - Unexpected end of track data.\n"; + evt.isValid = 0; + return evt; + } + ptr += (size_t)length; + return evt; + } + + if(byte == MidiEvent::T_SPECIAL) + { + // Special event FF + uint8_t evtype = *(ptr++); + uint64_t length = ReadVarLenEx(pptr, end, ok); + if(!ok || (ptr + length > end)) + { + m_parsingErrorsString += "parseEvent: Can't read Special event - Unexpected end of track data.\n"; + evt.isValid = 0; + return evt; + } + std::string data(length ? (const char *)ptr : 0, (size_t)length); + ptr += (size_t)length; + + evt.type = byte; + evt.subtype = evtype; + evt.data.insert(evt.data.begin(), data.begin(), data.end()); + +#if 0 /* Print all tempo events */ + if(evt.subtype == MidiEvent::ST_TEMPOCHANGE) + { + if(hooks.onDebugMessage) + hooks.onDebugMessage(hooks.onDebugMessage_userData, "Temp Change: %02X%02X%02X", evt.data[0], evt.data[1], evt.data[2]); + } +#endif + + /* TODO: Store those meta-strings separately and give ability to read them + * by external functions (to display song title and copyright in the player) */ + if(evt.subtype == MidiEvent::ST_COPYRIGHT) + { + if(m_musCopyright.empty()) + { + m_musCopyright = std::string((const char *)evt.data.data(), evt.data.size()); + if(m_interface->onDebugMessage) + m_interface->onDebugMessage(m_interface->onDebugMessage_userData, "Music copyright: %s", m_musCopyright.c_str()); + } + else if(m_interface->onDebugMessage) + { + std::string str((const char *)evt.data.data(), evt.data.size()); + m_interface->onDebugMessage(m_interface->onDebugMessage_userData, "Extra copyright event: %s", str.c_str()); + } + } + else if(evt.subtype == MidiEvent::ST_SQTRKTITLE) + { + if(m_musTitle.empty()) + { + m_musTitle = std::string((const char *)evt.data.data(), evt.data.size()); + if(m_interface->onDebugMessage) + m_interface->onDebugMessage(m_interface->onDebugMessage_userData, "Music title: %s", m_musTitle.c_str()); + } + else if(m_interface->onDebugMessage) + { + //TODO: Store track titles and associate them with each track and make API to retreive them + std::string str((const char *)evt.data.data(), evt.data.size()); + m_musTrackTitles.push_back(str); + m_interface->onDebugMessage(m_interface->onDebugMessage_userData, "Track title: %s", str.c_str()); + } + } + else if(evt.subtype == MidiEvent::ST_INSTRTITLE) + { + if(m_interface->onDebugMessage) + { + std::string str((const char *)evt.data.data(), evt.data.size()); + m_interface->onDebugMessage(m_interface->onDebugMessage_userData, "Instrument: %s", str.c_str()); + } + } + else if(evt.subtype == MidiEvent::ST_MARKER) + { + //To lower + for(size_t i = 0; i < data.size(); i++) + { + if(data[i] <= 'Z' && data[i] >= 'A') + data[i] = data[i] - ('Z' - 'z'); + } + + if(data == "loopstart") + { + //Return a custom Loop Start event instead of Marker + evt.subtype = MidiEvent::ST_LOOPSTART; + evt.data.clear();//Data is not needed + return evt; + } + + if(data == "loopend") + { + //Return a custom Loop End event instead of Marker + evt.subtype = MidiEvent::ST_LOOPEND; + evt.data.clear();//Data is not needed + return evt; + } + } + + if(evtype == MidiEvent::ST_ENDTRACK) + status = -1;//Finalize track + + return evt; + } + + // Any normal event (80..EF) + if(byte < 0x80) + { + byte = static_cast<uint8_t>(status | 0x80); + ptr--; + } + + //Sys Com Song Select(Song #) [0-127] + if(byte == MidiEvent::T_SYSCOMSNGSEL) + { + if(ptr + 1 > end) + { + m_parsingErrorsString += "parseEvent: Can't read System Command Song Select event - Unexpected end of track data.\n"; + evt.isValid = 0; + return evt; + } + evt.type = byte; + evt.data.push_back(*(ptr++)); + return evt; + } + + //Sys Com Song Position Pntr [LSB, MSB] + if(byte == MidiEvent::T_SYSCOMSPOSPTR) + { + if(ptr + 2 > end) + { + m_parsingErrorsString += "parseEvent: Can't read System Command Position Pointer event - Unexpected end of track data.\n"; + evt.isValid = 0; + return evt; + } + evt.type = byte; + evt.data.push_back(*(ptr++)); + evt.data.push_back(*(ptr++)); + return evt; + } + + uint8_t midCh = byte & 0x0F, evType = (byte >> 4) & 0x0F; + status = byte; + evt.channel = midCh; + evt.type = evType; + + switch(evType) + { + case MidiEvent::T_NOTEOFF://2 byte length + case MidiEvent::T_NOTEON: + case MidiEvent::T_NOTETOUCH: + case MidiEvent::T_CTRLCHANGE: + case MidiEvent::T_WHEEL: + if(ptr + 2 > end) + { + m_parsingErrorsString += "parseEvent: Can't read regular 2-byte event - Unexpected end of track data.\n"; + evt.isValid = 0; + return evt; + } + + evt.data.push_back(*(ptr++)); + evt.data.push_back(*(ptr++)); + + if((evType == MidiEvent::T_NOTEON) && (evt.data[1] == 0)) + { + evt.type = MidiEvent::T_NOTEOFF; // Note ON with zero velocity is Note OFF! + } //111'th loopStart controller (RPG Maker and others) + else if((evType == MidiEvent::T_CTRLCHANGE) && (evt.data[0] == 111)) + { + //Change event type to custom Loop Start event and clear data + evt.type = MidiEvent::T_SPECIAL; + evt.subtype = MidiEvent::ST_LOOPSTART; + evt.data.clear(); + } + + return evt; + case MidiEvent::T_PATCHCHANGE://1 byte length + case MidiEvent::T_CHANAFTTOUCH: + if(ptr + 1 > end) + { + m_parsingErrorsString += "parseEvent: Can't read regular 1-byte event - Unexpected end of track data.\n"; + evt.isValid = 0; + return evt; + } + evt.data.push_back(*(ptr++)); + return evt; + } + + return evt; +} + +void BW_MidiSequencer::handleEvent(size_t tk, const BW_MidiSequencer::MidiEvent &evt, int &status) +{ + if(m_interface->onEvent) + { + m_interface->onEvent(m_interface->onEvent_userData, + evt.type, evt.subtype, evt.channel, + evt.data.data(), evt.data.size()); + } + + if(evt.type == MidiEvent::T_SYSEX || evt.type == MidiEvent::T_SYSEX2) // Ignore SysEx + { + //std::string data( length?(const char*) &TrackData[tk][CurrentPosition.track[tk].ptr]:0, length ); + //UI.PrintLn("SysEx %02X: %u bytes", byte, length/*, data.c_str()*/); + m_interface->rt_systemExclusive(m_interface->rtUserData, evt.data.data(), evt.data.size()); + return; + } + + if(evt.type == MidiEvent::T_SPECIAL) + { + // Special event FF + uint8_t evtype = evt.subtype; + uint64_t length = (uint64_t)evt.data.size(); + std::string data(length ? (const char *)evt.data.data() : 0, (size_t)length); + + if(evtype == MidiEvent::ST_ENDTRACK)//End Of Track + { + status = -1; + return; + } + + if(evtype == MidiEvent::ST_TEMPOCHANGE)//Tempo change + { + m_tempo = m_invDeltaTicks * fraction<uint64_t>(ReadBEint(evt.data.data(), evt.data.size())); + return; + } + + if(evtype == MidiEvent::ST_MARKER)//Meta event + { + //Do nothing! :-P + return; + } + + if(evtype == MidiEvent::ST_DEVICESWITCH) + { + if(m_interface->rt_deviceSwitch) + m_interface->rt_deviceSwitch(m_interface->rtUserData, tk, data.c_str(), data.size()); + return; + } + + //if(evtype >= 1 && evtype <= 6) + // UI.PrintLn("Meta %d: %s", evtype, data.c_str()); + + //Turn on Loop handling when loop is enabled + if(m_loopEnabled && !m_invalidLoop) + { + if(evtype == MidiEvent::ST_LOOPSTART) // Special non-spec ADLMIDI special for IMF playback: Direct poke to AdLib + { + m_loopStart = true; + return; + } + + if(evtype == MidiEvent::ST_LOOPEND) // Special non-spec ADLMIDI special for IMF playback: Direct poke to AdLib + { + m_loopEnd = true; + return; + } + } + + if(evtype == MidiEvent::ST_RAWOPL) // Special non-spec ADLMIDI special for IMF playback: Direct poke to AdLib + { + if(m_interface->rt_rawOPL) + m_interface->rt_rawOPL(m_interface->rtUserData, static_cast<uint8_t>(data[0]), static_cast<uint8_t>(data[1])); + return; + } + + return; + } + + // Any normal event (80..EF) + // if(evt.type < 0x80) + // { + // byte = static_cast<uint8_t>(CurrentPosition.track[tk].status | 0x80); + // CurrentPosition.track[tk].ptr--; + // } + + if(evt.type == MidiEvent::T_SYSCOMSNGSEL || + evt.type == MidiEvent::T_SYSCOMSPOSPTR) + return; + + /*UI.PrintLn("@%X Track %u: %02X %02X", + CurrentPosition.track[tk].ptr-1, (unsigned)tk, byte, + TrackData[tk][CurrentPosition.track[tk].ptr]);*/ + uint8_t midCh = evt.channel;//byte & 0x0F, EvType = byte >> 4; + if(m_interface->rt_currentDevice) + midCh += (uint8_t)m_interface->rt_currentDevice(m_interface->rtUserData, tk); + status = evt.type; + + switch(evt.type) + { + case MidiEvent::T_NOTEOFF: // Note off + { + uint8_t note = evt.data[0]; + m_interface->rt_noteOff(m_interface->rtUserData, midCh, note); + break; + } + + case MidiEvent::T_NOTEON: // Note on + { + uint8_t note = evt.data[0]; + uint8_t vol = evt.data[1]; + m_interface->rt_noteOn(m_interface->rtUserData, midCh, note, vol); + break; + } + + case MidiEvent::T_NOTETOUCH: // Note touch + { + uint8_t note = evt.data[0]; + uint8_t vol = evt.data[1]; + m_interface->rt_noteAfterTouch(m_interface->rtUserData, midCh, note, vol); + break; + } + + case MidiEvent::T_CTRLCHANGE: // Controller change + { + uint8_t ctrlno = evt.data[0]; + uint8_t value = evt.data[1]; + m_interface->rt_controllerChange(m_interface->rtUserData, midCh, ctrlno, value); + break; + } + + case MidiEvent::T_PATCHCHANGE: // Patch change + { + m_interface->rt_patchChange(m_interface->rtUserData, midCh, evt.data[0]); + break; + } + + case MidiEvent::T_CHANAFTTOUCH: // Channel after-touch + { + uint8_t chanat = evt.data[0]; + m_interface->rt_channelAfterTouch(m_interface->rtUserData, midCh, chanat); + break; + } + + case MidiEvent::T_WHEEL: // Wheel/pitch bend + { + uint8_t a = evt.data[0]; + uint8_t b = evt.data[1]; + m_interface->rt_pitchBend(m_interface->rtUserData, midCh, b, a); + break; + } + }//switch +} + +double BW_MidiSequencer::Tick(double s, double granularity) +{ + assert(m_interface);// MIDI output interface must be defined! + + s *= m_tempoMultiplier; +#ifdef ENABLE_BEGIN_SILENCE_SKIPPING + if(CurrentPositionNew.began) +#endif + m_currentPosition.wait -= s; + m_currentPosition.absTimePosition += s; + + int antiFreezeCounter = 10000;//Limit 10000 loops to avoid freezing + while((m_currentPosition.wait <= granularity * 0.5) && (antiFreezeCounter > 0)) + { + //std::fprintf(stderr, "wait = %g...\n", CurrentPosition.wait); + if(!processEvents()) + break; + if(m_currentPosition.wait <= 0.0) + antiFreezeCounter--; + } + + if(antiFreezeCounter <= 0) + m_currentPosition.wait += 1.0;/* Add extra 1 second when over 10000 events + with zero delay are been detected */ + + if(m_currentPosition.wait < 0.0)//Avoid negative delay value! + return 0.0; + + return m_currentPosition.wait; +} + + +double BW_MidiSequencer::seek(double seconds, const double granularity) +{ + if(seconds < 0.0) + return 0.0;//Seeking negative position is forbidden! :-P + const double granualityHalf = granularity * 0.5, + s = seconds;//m_setup.delay < m_setup.maxdelay ? m_setup.delay : m_setup.maxdelay; + + /* Attempt to go away out of song end must rewind position to begin */ + if(seconds > m_fullSongTimeLength) + { + rewind(); + return 0.0; + } + + bool loopFlagState = m_loopEnabled; + // Turn loop pooints off because it causes wrong position rememberin on a quick seek + m_loopEnabled = false; + + /* + * Seeking search is similar to regular ticking, except of next things: + * - We don't processsing arpeggio and vibrato + * - To keep correctness of the state after seek, begin every search from begin + * - All sustaining notes must be killed + * - Ignore Note-On events + */ + rewind(); + + /* + * Set "loop Start" to false to prevent overwrite of loopStart position with + * seek destinition position + * + * TODO: Detect & set loopStart position on load time to don't break loop while seeking + */ + m_loopStart = false; + + while((m_currentPosition.absTimePosition < seconds) && + (m_currentPosition.absTimePosition < m_fullSongTimeLength)) + { + m_currentPosition.wait -= s; + m_currentPosition.absTimePosition += s; + int antiFreezeCounter = 10000;//Limit 10000 loops to avoid freezing + double dstWait = m_currentPosition.wait + granualityHalf; + while((m_currentPosition.wait <= granualityHalf)/*&& (antiFreezeCounter > 0)*/) + { + //std::fprintf(stderr, "wait = %g...\n", CurrentPosition.wait); + if(!processEvents(true)) + break; + //Avoid freeze because of no waiting increasing in more than 10000 cycles + if(m_currentPosition.wait <= dstWait) + antiFreezeCounter--; + else + { + dstWait = m_currentPosition.wait + granualityHalf; + antiFreezeCounter = 10000; + } + } + if(antiFreezeCounter <= 0) + m_currentPosition.wait += 1.0;/* Add extra 1 second when over 10000 events + with zero delay are been detected */ + } + + if(m_currentPosition.wait < 0.0) + m_currentPosition.wait = 0.0; + + m_loopEnabled = loopFlagState; + return m_currentPosition.wait; +} + +double BW_MidiSequencer::tell() +{ + return m_currentPosition.absTimePosition; +} + +double BW_MidiSequencer::timeLength() +{ + return m_fullSongTimeLength; +} + +double BW_MidiSequencer::getLoopStart() +{ + return m_loopStartTime; +} + +double BW_MidiSequencer::getLoopEnd() +{ + return m_loopEndTime; +} + +void BW_MidiSequencer::rewind() +{ + m_currentPosition = m_trackBeginPosition; + m_atEnd = false; + m_loopStart = true; + m_loopEnd = false; + //invalidLoop = false;//No more needed here as this flag is set on load time +} + +void BW_MidiSequencer::setTempo(double tempo) +{ + m_tempoMultiplier = tempo; +} + +bool BW_MidiSequencer::loadMIDI(const std::string &filename) +{ + FileAndMemReader file; + file.openFile(filename.c_str()); + if(!loadMIDI(file)) + return false; + return true; +} + +bool BW_MidiSequencer::loadMIDI(const void *data, size_t size) +{ + FileAndMemReader file; + file.openData(data, size); + return loadMIDI(file); +} + +template<class T> +class BufferGuard +{ + T *m_ptr; +public: + BufferGuard() : m_ptr(NULL) + {} + + ~BufferGuard() + { + set(); + } + + void set(T *p = NULL) + { + if(m_ptr) + free(m_ptr); + m_ptr = p; + } +}; + +bool BW_MidiSequencer::loadMIDI(FileAndMemReader &fr) +{ + size_t fsize; + BW_MidiSequencer_UNUSED(fsize); + std::vector<std::vector<uint8_t> > rawTrackData; + //! Temp buffer for conversion + BufferGuard<uint8_t> cvt_buf; + m_parsingErrorsString.clear(); + + assert(m_interface);// MIDI output interface must be defined! + + if(!fr.isValid()) + { + m_errorString = "Invalid data stream!\n"; +#ifndef _WIN32 + m_errorString += std::strerror(errno); +#endif + return false; + } + + m_atEnd = false; + m_loopStart = true; + m_invalidLoop = false; + + m_format = Format_MIDI; + + bool is_GMF = false; // GMD/MUS files (ScummVM) + //bool is_MUS = false; // MUS/DMX files (Doom) + bool is_IMF = false; // IMF + bool is_CMF = false; // Creative Music format (CMF/CTMF) + bool is_RSXX = false; // RSXX, such as Cartooners + + const size_t HeaderSize = 4 + 4 + 2 + 2 + 2; // 14 + char headerBuf[HeaderSize] = ""; + size_t DeltaTicks = 192, TrackCount = 1; + +riffskip: + fsize = fr.read(headerBuf, 1, HeaderSize); + + if(std::memcmp(headerBuf, "RIFF", 4) == 0) + { + fr.seek(6l, FileAndMemReader::CUR); + goto riffskip; + } + + if(std::memcmp(headerBuf, "GMF\x1", 4) == 0) + { + // GMD/MUS files (ScummVM) + fr.seek(7 - static_cast<long>(HeaderSize), FileAndMemReader::CUR); + is_GMF = true; + } +#ifndef BWMIDI_DISABLE_MUS_SUPPORT + else if(std::memcmp(headerBuf, "MUS\x1A", 4) == 0) + { + // MUS/DMX files (Doom) + size_t mus_len = fr.fileSize(); + fr.seek(0, FileAndMemReader::SET); + uint8_t *mus = (uint8_t *)malloc(mus_len); + if(!mus) + { + m_errorString = "Out of memory!"; + return false; + } + fr.read(mus, 1, mus_len); + //Close source stream + fr.close(); + + uint8_t *mid = NULL; + uint32_t mid_len = 0; + int m2mret = Convert_mus2midi(mus, static_cast<uint32_t>(mus_len), + &mid, &mid_len, 0); + if(mus) + free(mus); + if(m2mret < 0) + { + m_errorString = "Invalid MUS/DMX data format!"; + return false; + } + cvt_buf.set(mid); + //Open converted MIDI file + fr.openData(mid, static_cast<size_t>(mid_len)); + //Re-Read header again! + goto riffskip; + } +#endif //BWMIDI_DISABLE_MUS_SUPPORT + +#ifndef BWMIDI_DISABLE_XMI_SUPPORT + else if(std::memcmp(headerBuf, "FORM", 4) == 0) + { + if(std::memcmp(headerBuf + 8, "XDIR", 4) != 0) + { + fr.close(); + m_errorString = fr.fileName() + ": Invalid format\n"; + return false; + } + + size_t mus_len = fr.fileSize(); + fr.seek(0, FileAndMemReader::SET); + + uint8_t *mus = (uint8_t*)malloc(mus_len); + if(!mus) + { + m_errorString = "Out of memory!"; + return false; + } + fr.read(mus, 1, mus_len); + //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), + &mid, &mid_len, XMIDI_CONVERT_NOCONVERSION); + if(mus) free(mus); + if(m2mret < 0) + { + m_errorString = "Invalid XMI data format!"; + return false; + } + cvt_buf.set(mid); + //Open converted MIDI file + fr.openData(mid, static_cast<size_t>(mid_len)); + //Re-Read header again! + goto riffskip; + } +#endif //BWMIDI_DISABLE_XMI_SUPPORT + + else if(std::memcmp(headerBuf, "CTMF", 4) == 0) + { + // Creative Music Format (CMF). + // When playing CTMF files, use the following commandline: + // adlmidi song8.ctmf -p -v 1 1 0 + // i.e. enable percussion mode, deeper vibrato, and use only 1 card. + is_CMF = true; + m_format = Format_CMF; + //unsigned version = ReadLEint(HeaderBuf+4, 2); + uint64_t ins_start = ReadLEint(headerBuf + 6, 2); + uint64_t mus_start = ReadLEint(headerBuf + 8, 2); + //unsigned deltas = ReadLEint(HeaderBuf+10, 2); + uint64_t ticks = ReadLEint(headerBuf + 12, 2); + // Read title, author, remarks start offsets in file + fr.read(headerBuf, 1, 6); + //unsigned long notes_starts[3] = {ReadLEint(HeaderBuf+0,2),ReadLEint(HeaderBuf+0,4),ReadLEint(HeaderBuf+0,6)}; + fr.seek(16, FileAndMemReader::CUR); // Skip the channels-in-use table + fr.read(headerBuf, 1, 4); + uint64_t ins_count = ReadLEint(headerBuf + 0, 2); //, basictempo = ReadLEint(HeaderBuf+2, 2); + fr.seek(static_cast<long>(ins_start), FileAndMemReader::SET); + + m_cmfInstruments.reserve(static_cast<size_t>(ins_count)); + for(uint64_t i = 0; i < ins_count; ++i) + { + CmfInstrument inst; + fr.read(inst.data, 1, 16); + m_cmfInstruments.push_back(inst); + } + + fr.seeku(mus_start, FileAndMemReader::SET); + TrackCount = 1; + DeltaTicks = (size_t)ticks; + } + else + { + // Try to identify RSXX format + if(headerBuf[0] == 0x7D) + { + fr.seek(0x6D, FileAndMemReader::SET); + fr.read(headerBuf, 6, 1); + if(std::memcmp(headerBuf, "rsxx}u", 6) == 0) + { + is_RSXX = true; + m_format = Format_RSXX; + fr.seek(0x7D, FileAndMemReader::SET); + TrackCount = 1; + DeltaTicks = 60; + } + } + + // Try parsing as an IMF file + if(!is_RSXX) + { + do + { + uint8_t raw[4]; + size_t end = static_cast<size_t>(headerBuf[0]) + 256 * static_cast<size_t>(headerBuf[1]); + + if(!end || (end & 3)) + break; + + size_t backup_pos = fr.tell(); + int64_t sum1 = 0, sum2 = 0; + fr.seek(2, FileAndMemReader::SET); + + for(unsigned n = 0; n < 42; ++n) + { + if(fr.read(raw, 1, 4) != 4) + break; + int64_t value1 = raw[0]; + value1 += raw[1] << 8; + sum1 += value1; + int64_t value2 = raw[2]; + value2 += raw[3] << 8; + sum2 += value2; + } + + fr.seek(static_cast<long>(backup_pos), FileAndMemReader::SET); + + if(sum1 > sum2) + { + is_IMF = true; + m_format = Format_IMF; + DeltaTicks = 1; + } + } while(false); + } + + if(!is_IMF && !is_RSXX) + { + if(std::memcmp(headerBuf, "MThd\0\0\0\6", 8) != 0) + { + fr.close(); + m_errorString = fr.fileName() + ": Invalid format, Header signature is unknown!\n"; + return false; + } + + /*size_t Fmt = ReadBEint(HeaderBuf + 8, 2);*/ + TrackCount = (size_t)ReadBEint(headerBuf + 10, 2); + DeltaTicks = (size_t)ReadBEint(headerBuf + 12, 2); + } + } + + rawTrackData.clear(); + rawTrackData.resize(TrackCount, std::vector<uint8_t>()); + m_invDeltaTicks = fraction<uint64_t>(1, 1000000l * static_cast<uint64_t>(DeltaTicks)); + if(is_CMF || is_RSXX) + m_tempo = fraction<uint64_t>(1, static_cast<uint64_t>(DeltaTicks)); + else + m_tempo = fraction<uint64_t>(1, static_cast<uint64_t>(DeltaTicks) * 2); + static const unsigned char EndTag[4] = {0xFF, 0x2F, 0x00, 0x00}; + size_t totalGotten = 0; + + for(size_t tk = 0; tk < TrackCount; ++tk) + { + // Read track header + size_t TrackLength; + + if(is_IMF) + { + //std::fprintf(stderr, "Reading IMF file...\n"); + size_t end = static_cast<size_t>(headerBuf[0]) + 256 * static_cast<size_t>(headerBuf[1]); + unsigned IMF_tempo = 1428; + static const unsigned char imf_tempo[] = {0x0,//Zero delay! + MidiEvent::T_SPECIAL, MidiEvent::ST_TEMPOCHANGE, 0x4, + static_cast<uint8_t>(IMF_tempo >> 24), + static_cast<uint8_t>(IMF_tempo >> 16), + static_cast<uint8_t>(IMF_tempo >> 8), + static_cast<uint8_t>(IMF_tempo) + }; + rawTrackData[tk].insert(rawTrackData[tk].end(), imf_tempo, imf_tempo + sizeof(imf_tempo)); + rawTrackData[tk].push_back(0x00); + fr.seek(2, FileAndMemReader::SET); + + while(fr.tell() < end && !fr.eof()) + { + uint8_t special_event_buf[5]; + uint8_t raw[4]; + special_event_buf[0] = MidiEvent::T_SPECIAL; + special_event_buf[1] = MidiEvent::ST_RAWOPL; + special_event_buf[2] = 0x02; + if(fr.read(raw, 1, 4) != 4) + break; + special_event_buf[3] = raw[0]; // port index + special_event_buf[4] = raw[1]; // port value + uint32_t delay = static_cast<uint32_t>(raw[2]); + delay += 256 * static_cast<uint32_t>(raw[3]); + totalGotten += 4; + //if(special_event_buf[3] <= 8) continue; + //fprintf(stderr, "Put %02X <- %02X, plus %04X delay\n", special_event_buf[3],special_event_buf[4], delay); + rawTrackData[tk].insert(rawTrackData[tk].end(), special_event_buf, special_event_buf + 5); + //if(delay>>21) TrackData[tk].push_back( 0x80 | ((delay>>21) & 0x7F ) ); + if(delay >> 14) + rawTrackData[tk].push_back(0x80 | ((delay >> 14) & 0x7F)); + if(delay >> 7) + rawTrackData[tk].push_back(0x80 | ((delay >> 7) & 0x7F)); + rawTrackData[tk].push_back(((delay >> 0) & 0x7F)); + } + + rawTrackData[tk].insert(rawTrackData[tk].end(), EndTag + 0, EndTag + 4); + //CurrentPosition.track[tk].delay = 0; + //CurrentPosition.began = true; + } + else + { + // Take the rest of the file + if(is_GMF || is_CMF || is_RSXX) + { + size_t pos = fr.tell(); + fr.seek(0, FileAndMemReader::END); + TrackLength = fr.tell() - pos; + fr.seek(static_cast<long>(pos), FileAndMemReader::SET); + } + //else if(is_MUS) // Read TrackLength from file position 4 + //{ + // size_t pos = fr.tell(); + // fr.seek(4, FileAndMemReader::SET); + // TrackLength = static_cast<size_t>(fr.getc()); + // TrackLength += static_cast<size_t>(fr.getc() << 8); + // fr.seek(static_cast<long>(pos), FileAndMemReader::SET); + //} + else + { + fsize = fr.read(headerBuf, 1, 8); + if(std::memcmp(headerBuf, "MTrk", 4) != 0) + { + fr.close(); + m_errorString = fr.fileName() + ": Invalid format, MTrk signature is not found!\n"; + return false; + } + TrackLength = (size_t)ReadBEint(headerBuf + 4, 4); + } + + // Read track data + rawTrackData[tk].resize(TrackLength); + fsize = fr.read(&rawTrackData[tk][0], 1, TrackLength); + totalGotten += fsize; + + if(is_GMF/*|| is_MUS*/) // Note: CMF does include the track end tag. + rawTrackData[tk].insert(rawTrackData[tk].end(), EndTag + 0, EndTag + 4); + if(is_RSXX)//Finalize raw track data with a zero + rawTrackData[tk].push_back(0); + + //bool ok = false; + //// Read next event time + //uint64_t tkDelay = ReadVarLenEx(tk, ok); + //if(ok) + // CurrentPosition.track[tk].delay = tkDelay; + //else + //{ + // std::stringstream msg; + // msg << fr._fileName << ": invalid variable length in the track " << tk << "! (error code " << tkDelay << ")"; + // ADLMIDI_ErrorString = msg.str(); + // return false; + //} + } + } + + for(size_t tk = 0; tk < TrackCount; ++tk) + totalGotten += rawTrackData[tk].size(); + + if(totalGotten == 0) + { + m_errorString = fr.fileName() + ": Empty track data"; + return false; + } + + //Build new MIDI events table + if(!buildTrackData(rawTrackData)) + { + m_errorString = fr.fileName() + ": MIDI data parsing error has occouped!\n" + m_parsingErrorsString; + return false; + } + + return true; +} |