aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorVitaly Novichkov <admin@wohlnet.ru>2018-06-21 04:23:14 +0300
committerVitaly Novichkov <admin@wohlnet.ru>2018-06-21 04:23:14 +0300
commit9f0f9e4e374798851da39335406ac4bafad81297 (patch)
tree28b744d0e52afc29c1d27f06b227e3101a8844a8 /src
parenta0fa0cded46bdc94293f02d4070a69bcae5478bb (diff)
downloadlibADLMIDI-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.cpp246
-rw-r--r--src/adlmidi_load.cpp450
-rw-r--r--src/adlmidi_midiplay.cpp1292
-rw-r--r--src/adlmidi_mus2mid.h49
-rw-r--r--src/adlmidi_private.hpp370
-rw-r--r--src/adlmidi_sequencer.cpp153
-rw-r--r--src/adlmidi_xmi2mid.h60
-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.hpp37
-rw-r--r--src/midi_sequencer.h91
-rw-r--r--src/midi_sequencer.hpp496
-rw-r--r--src/midi_sequencer_impl.hpp1780
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;
+}