diff options
author | Vitaly Novichkov <admin@wohlnet.ru> | 2018-06-21 04:23:14 +0300 |
---|---|---|
committer | Vitaly Novichkov <admin@wohlnet.ru> | 2018-06-21 04:23:14 +0300 |
commit | 9f0f9e4e374798851da39335406ac4bafad81297 (patch) | |
tree | 28b744d0e52afc29c1d27f06b227e3101a8844a8 /src | |
parent | a0fa0cded46bdc94293f02d4070a69bcae5478bb (diff) | |
download | libADLMIDI-9f0f9e4e374798851da39335406ac4bafad81297.tar.gz libADLMIDI-9f0f9e4e374798851da39335406ac4bafad81297.tar.bz2 libADLMIDI-9f0f9e4e374798851da39335406ac4bafad81297.zip |
Move MIDI sequencer into completely separated class
TODO:
- implement C bindings for most of class functions
- test it in work on any different synthesizer
Diffstat (limited to 'src')
-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 |
13 files changed, 3200 insertions, 2357 deletions
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; +} |