aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorVitaly Novichkov <admin@wohlnet.ru>2018-06-24 21:48:02 +0300
committerVitaly Novichkov <admin@wohlnet.ru>2018-06-24 21:48:02 +0300
commite3a353a6b84b47ad4204b110249bc74b16554985 (patch)
tree2dbc8cc9489557d7136a83b88905bbf52db91d07 /src
parentaaa70d5425a6c3130daa34053bd17d9165f571db (diff)
parent76eb1a12ef6e6a3f8c7f6c0a226fc5b35dd99536 (diff)
downloadlibADLMIDI-e3a353a6b84b47ad4204b110249bc74b16554985.tar.gz
libADLMIDI-e3a353a6b84b47ad4204b110249bc74b16554985.tar.bz2
libADLMIDI-e3a353a6b84b47ad4204b110249bc74b16554985.zip
Merge branch 'master' into stable
Diffstat (limited to 'src')
-rw-r--r--src/adlmidi.cpp535
-rw-r--r--src/adlmidi_load.cpp465
-rw-r--r--src/adlmidi_midiplay.cpp1854
-rw-r--r--src/adlmidi_mus2mid.h49
-rw-r--r--src/adlmidi_private.cpp2
-rw-r--r--src/adlmidi_private.hpp629
-rw-r--r--src/adlmidi_sequencer.cpp151
-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.hpp300
-rw-r--r--src/midi_sequencer.h134
-rw-r--r--src/midi_sequencer.hpp501
-rw-r--r--src/midi_sequencer_impl.hpp1852
14 files changed, 4337 insertions, 2728 deletions
diff --git a/src/adlmidi.cpp b/src/adlmidi.cpp
index 9210b5b..f627ce3 100644
--- a/src/adlmidi.cpp
+++ b/src/adlmidi.cpp
@@ -31,6 +31,10 @@
#define MaxCards_STR "100"
#endif
+/* Unify MIDI player casting and interface between ADLMIDI and OPNMIDI */
+#define GET_MIDI_PLAYER(device) reinterpret_cast<MIDIplay *>((device)->adl_midiPlayer)
+typedef MIDIplay MidiPlayer;
+
static ADL_Version adl_version = {
ADLMIDI_VERSION_MAJOR,
ADLMIDI_VERSION_MINOR,
@@ -68,12 +72,23 @@ ADLMIDI_EXPORT struct ADL_MIDIPlayer *adl_init(long sample_rate)
return midi_device;
}
+ADLMIDI_EXPORT int adl_setDeviceIdentifier(ADL_MIDIPlayer *device, unsigned id)
+{
+ if(!device || id > 0x0f)
+ return -1;
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ if(!play)
+ return -1;
+ play->setDeviceId(id);
+ return 0;
+}
+
ADLMIDI_EXPORT int adl_setNumChips(ADL_MIDIPlayer *device, int numCards)
{
if(device == NULL)
return -2;
- MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
#ifdef ADLMIDI_HW_OPL
(void)numCards;
play->m_setup.NumCards = 1;
@@ -96,7 +111,7 @@ ADLMIDI_EXPORT int adl_getNumChips(struct ADL_MIDIPlayer *device)
{
if(device == NULL)
return -2;
- MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
if(play)
return (int)play->m_setup.NumCards;
return -2;
@@ -104,19 +119,19 @@ 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;
if(bankno < 0)
bankno = 0;
- MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
if(static_cast<uint32_t>(bankno) >= NumBanks)
{
char errBuf[150];
@@ -130,7 +145,7 @@ ADLMIDI_EXPORT int adl_setBank(ADL_MIDIPlayer *device, int bank)
play->applySetup();
return adlRefreshNumCards(device);
- #endif
+#endif
}
ADLMIDI_EXPORT int adl_getBanksCount()
@@ -147,7 +162,7 @@ ADLMIDI_EXPORT int adl_reserveBanks(ADL_MIDIPlayer *device, unsigned banks)
{
if(!device)
return -1;
- MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
OPL3::BankMap &map = play->opl.dynamic_banks;
map.reserve(banks);
return (int)map.capacity();
@@ -163,7 +178,7 @@ ADLMIDI_EXPORT int adl_getBank(ADL_MIDIPlayer *device, const ADL_BankId *idp, in
return -1;
unsigned idnumber = (id.msb << 8) | id.lsb | (id.percussive ? OPL3::PercussionTag : 0);
- MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
OPL3::BankMap &map = play->opl.dynamic_banks;
OPL3::BankMap::iterator it;
@@ -215,7 +230,7 @@ ADLMIDI_EXPORT int adl_removeBank(ADL_MIDIPlayer *device, ADL_Bank *bank)
if(!device || !bank)
return -1;
- MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
OPL3::BankMap &map = play->opl.dynamic_banks;
OPL3::BankMap::iterator it = OPL3::BankMap::iterator::from_ptrs(bank->pointer);
size_t size = map.size();
@@ -228,7 +243,7 @@ ADLMIDI_EXPORT int adl_getFirstBank(ADL_MIDIPlayer *device, ADL_Bank *bank)
if(!device)
return -1;
- MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
OPL3::BankMap &map = play->opl.dynamic_banks;
OPL3::BankMap::iterator it = map.begin();
@@ -244,7 +259,7 @@ ADLMIDI_EXPORT int adl_getNextBank(ADL_MIDIPlayer *device, ADL_Bank *bank)
if(!device)
return -1;
- MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
OPL3::BankMap &map = play->opl.dynamic_banks;
OPL3::BankMap::iterator it = OPL3::BankMap::iterator::from_ptrs(bank->pointer);
@@ -283,7 +298,7 @@ ADLMIDI_EXPORT int adl_setNumFourOpsChn(ADL_MIDIPlayer *device, int ops4)
{
if(!device)
return -1;
- MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
if((unsigned int)ops4 > 6 * play->m_setup.NumCards)
{
char errBuff[250];
@@ -302,7 +317,7 @@ ADLMIDI_EXPORT int adl_getNumFourOpsChn(struct ADL_MIDIPlayer *device)
{
if(!device)
return -1;
- MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
if(play)
return (int)play->m_setup.NumFourOps;
return -1;
@@ -311,7 +326,7 @@ ADLMIDI_EXPORT int adl_getNumFourOpsChn(struct ADL_MIDIPlayer *device)
ADLMIDI_EXPORT void adl_setPercMode(ADL_MIDIPlayer *device, int percmod)
{
if(!device) return;
- MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
play->m_setup.AdlPercussionMode = percmod;
play->opl.AdlPercussionMode = play->m_setup.AdlPercussionMode < 0 ?
play->opl.dynamic_bank_setup.adLibPercussions :
@@ -322,7 +337,7 @@ ADLMIDI_EXPORT void adl_setPercMode(ADL_MIDIPlayer *device, int percmod)
ADLMIDI_EXPORT void adl_setHVibrato(ADL_MIDIPlayer *device, int hvibro)
{
if(!device) return;
- MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
play->m_setup.HighVibratoMode = hvibro;
play->opl.HighVibratoMode = play->m_setup.HighVibratoMode < 0 ?
play->opl.dynamic_bank_setup.deepVibrato :
@@ -333,7 +348,7 @@ ADLMIDI_EXPORT void adl_setHVibrato(ADL_MIDIPlayer *device, int hvibro)
ADLMIDI_EXPORT void adl_setHTremolo(ADL_MIDIPlayer *device, int htremo)
{
if(!device) return;
- MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
play->m_setup.HighTremoloMode = htremo;
play->opl.HighTremoloMode = play->m_setup.HighTremoloMode < 0 ?
play->opl.dynamic_bank_setup.deepTremolo :
@@ -343,8 +358,11 @@ ADLMIDI_EXPORT void adl_setHTremolo(ADL_MIDIPlayer *device, int htremo)
ADLMIDI_EXPORT void adl_setScaleModulators(ADL_MIDIPlayer *device, int smod)
{
- if(!device) return;
- MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
+ if(!device)
+ return;
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ if(!play)
+ return;
play->m_setup.ScaleModulators = smod;
play->opl.ScaleModulators = play->m_setup.ScaleModulators < 0 ?
play->opl.dynamic_bank_setup.scaleModulators :
@@ -353,23 +371,36 @@ ADLMIDI_EXPORT void adl_setScaleModulators(ADL_MIDIPlayer *device, int smod)
ADLMIDI_EXPORT void adl_setFullRangeBrightness(struct ADL_MIDIPlayer *device, int fr_brightness)
{
- if(!device) return;
- MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
+ if(!device)
+ return;
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ if(!play)
+ return;
play->m_setup.fullRangeBrightnessCC74 = (fr_brightness != 0);
}
ADLMIDI_EXPORT void adl_setLoopEnabled(ADL_MIDIPlayer *device, int loopEn)
{
- if(!device) return;
- MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
- play->m_setup.loopingIsEnabled = (loopEn != 0);
+ if(!device)
+ return;
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ 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;
- MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
+ if(!device)
+ return;
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ if(!play)
+ return;
play->m_setup.LogarithmicVolumes = (logvol != 0);
if(play->m_setup.LogarithmicVolumes)
play->opl.ChangeVolumeRangesModel(ADLMIDI_VolumeModel_NativeOPL3);
@@ -379,8 +410,11 @@ ADLMIDI_EXPORT void adl_setLogarithmicVolumes(struct ADL_MIDIPlayer *device, int
ADLMIDI_EXPORT void adl_setVolumeRangeModel(struct ADL_MIDIPlayer *device, int volumeModel)
{
- if(!device) return;
- MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
+ if(!device)
+ return;
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ if(!play)
+ return;
play->m_setup.VolumeModel = volumeModel;
if(play->m_setup.VolumeModel == ADLMIDI_VolumeModel_AUTO)//Use bank default volume model
play->opl.m_volumeScale = (OPL3::VolumesScale)play->opl.dynamic_bank_setup.volumeModel;
@@ -392,7 +426,7 @@ ADLMIDI_EXPORT int adl_openBankFile(struct ADL_MIDIPlayer *device, const char *f
{
if(device && device->adl_midiPlayer)
{
- MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
play->m_setup.tick_skip_samples_delay = 0;
if(!play->LoadBank(filePath))
{
@@ -412,7 +446,9 @@ ADLMIDI_EXPORT int adl_openBankData(struct ADL_MIDIPlayer *device, const void *m
{
if(device && device->adl_midiPlayer)
{
- MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ if(!play)
+ return -1;
play->m_setup.tick_skip_samples_delay = 0;
if(!play->LoadBank(mem, static_cast<size_t>(size)))
{
@@ -432,7 +468,9 @@ ADLMIDI_EXPORT int adl_openFile(ADL_MIDIPlayer *device, const char *filePath)
{
if(device && device->adl_midiPlayer)
{
- MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ if(!play)
+ return -1;
#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER
play->m_setup.tick_skip_samples_delay = 0;
if(!play->LoadMIDI(filePath))
@@ -444,7 +482,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
@@ -458,7 +496,9 @@ ADLMIDI_EXPORT int adl_openData(ADL_MIDIPlayer *device, const void *mem, unsigne
{
if(device && device->adl_midiPlayer)
{
- MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ if(!play)
+ return -1;
#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER
play->m_setup.tick_skip_samples_delay = 0;
if(!play->LoadMIDI(mem, static_cast<size_t>(size)))
@@ -470,7 +510,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
@@ -489,13 +530,13 @@ ADLMIDI_EXPORT const char *adl_chipEmulatorName(struct ADL_MIDIPlayer *device)
{
if(device)
{
- #ifndef ADLMIDI_HW_OPL
- MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
+#ifndef ADLMIDI_HW_OPL
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
if(play && !play->opl.cardsOP2.empty())
return play->opl.cardsOP2[0]->emulatorName();
#else
return "Hardware OPL3 chip on 0x330";
- #endif
+#endif
}
return "Unknown";
}
@@ -504,8 +545,11 @@ ADLMIDI_EXPORT int adl_switchEmulator(struct ADL_MIDIPlayer *device, int emulato
{
if(device)
{
- MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
- if(play && (emulator >= 0) && (emulator < ADLMIDI_EMU_end))
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ assert(play);
+ if(!play)
+ return -1;
+ if((emulator >= 0) && (emulator < ADLMIDI_EMU_end))
{
play->m_setup.emulator = emulator;
adl_reset(device);
@@ -521,7 +565,7 @@ ADLMIDI_EXPORT int adl_setRunAtPcmRate(ADL_MIDIPlayer *device, int enabled)
{
if(device)
{
- MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
if(play)
{
play->m_setup.runAtPcmRate = (enabled != 0);
@@ -556,7 +600,7 @@ ADLMIDI_EXPORT const char *adl_errorInfo(struct ADL_MIDIPlayer *device)
{
if(!device)
return adl_errorString();
- MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
if(!play)
return adl_errorString();
return play->getErrorString().c_str();
@@ -564,22 +608,16 @@ 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)
{
- if(device->adl_midiPlayer)
- delete reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
+ if(!device)
+ return;
+ MIDIplay * play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
+ if(play)
+ delete play;
device->adl_midiPlayer = NULL;
free(device);
device = NULL;
@@ -589,7 +627,7 @@ ADLMIDI_EXPORT void adl_reset(struct ADL_MIDIPlayer *device)
{
if(!device)
return;
- MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
play->m_setup.tick_skip_samples_delay = 0;
play->opl.runAtPcmRate = play->m_setup.runAtPcmRate;
play->opl.Reset(play->m_setup.emulator, play->m_setup.PCM_RATE, play);
@@ -599,121 +637,173 @@ ADLMIDI_EXPORT void adl_reset(struct ADL_MIDIPlayer *device)
ADLMIDI_EXPORT double adl_totalTimeLength(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)->timeLength();
- #else
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ if(!play)
+ return -1.0;
+ return play->m_sequencer.timeLength();
+#else
+ ADL_UNUSED(device);
return -1.0;
- #endif
+#endif
}
ADLMIDI_EXPORT double adl_loopStartTime(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)->getLoopStart();
- #else
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ if(!play)
+ return -1.0;
+ return play->m_sequencer.getLoopStart();
+#else
+ ADL_UNUSED(device);
return -1.0;
- #endif
+#endif
}
ADLMIDI_EXPORT double adl_loopEndTime(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)->getLoopEnd();
- #else
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ if(!play)
+ return -1.0;
+ return play->m_sequencer.getLoopEnd();
+#else
+ ADL_UNUSED(device);
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
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ 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
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ 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
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ 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
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ 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
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ 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
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ 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();
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ 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
- MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
- if(index >= play->musTrackTitles.size())
+ if(!device)
+ return "";
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ 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 +811,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();
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ if(!play)
+ return 0;
+ return play->m_sequencer.getMarkers().size();
#else
+ ADL_UNUSED(device);
return 0;
#endif
}
@@ -733,9 +827,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
- MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
- if(!device || !play || (index >= play->musMarkers.size()))
+#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ 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 +839,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;
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ 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 */
@@ -772,7 +874,7 @@ ADLMIDI_EXPORT void adl_setNoteHook(struct ADL_MIDIPlayer *device, ADL_NoteHook
{
if(!device)
return;
- MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
play->hooks.onNote = noteHook;
play->hooks.onNote_userData = userData;
}
@@ -782,12 +884,18 @@ ADLMIDI_EXPORT void adl_setDebugMessageHook(struct ADL_MIDIPlayer *device, ADL_D
{
if(!device)
return;
- MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
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
+
+# ifndef __WATCOMC__
template <class Dst>
static void CopySamplesRaw(ADL_UInt8 *dstLeft, ADL_UInt8 *dstRight, const int32_t *src,
size_t frameCount, unsigned sampleOffset)
@@ -919,7 +1027,61 @@ static int SendStereoAudio(int samples_requested,
return 0;
}
-#endif
+# else // __WATCOMC__
+
+/*
+ Workaround for OpenWattcom where templates are declared above are causing compiler to be crashed
+*/
+static void CopySamplesTransformed(ADL_UInt8 *dstLeft, ADL_UInt8 *dstRight, const int32_t *src,
+ size_t frameCount, unsigned sampleOffset,
+ int32_t(&transform)(int32_t))
+{
+ for(size_t i = 0; i < frameCount; ++i) {
+ *(int16_t *)(dstLeft + (i * sampleOffset)) = (int16_t)transform(src[2 * i]);
+ *(int16_t *)(dstRight + (i * sampleOffset)) = (int16_t)transform(src[(2 * i) + 1]);
+ }
+}
+
+static int SendStereoAudio(int samples_requested,
+ ssize_t in_size,
+ int32_t *_in,
+ ssize_t out_pos,
+ ADL_UInt8 *left,
+ ADL_UInt8 *right,
+ const ADLMIDI_AudioFormat *format)
+{
+ if(!in_size)
+ return 0;
+ size_t outputOffset = static_cast<size_t>(out_pos);
+ size_t inSamples = static_cast<size_t>(in_size * 2);
+ size_t maxSamples = static_cast<size_t>(samples_requested) - outputOffset;
+ size_t toCopy = std::min(maxSamples, inSamples);
+
+ ADLMIDI_SampleType sampleType = format->type;
+ const unsigned containerSize = format->containerSize;
+ const unsigned sampleOffset = format->sampleOffset;
+
+ left += (outputOffset / 2) * sampleOffset;
+ right += (outputOffset / 2) * sampleOffset;
+
+ if(sampleType == ADLMIDI_SampleType_U16)
+ {
+ switch(containerSize) {
+ case sizeof(int16_t):
+ CopySamplesTransformed(left, right, _in, toCopy / 2, sampleOffset, adl_cvtS16);
+ break;
+ default:
+ return -1;
+ }
+ }
+ else
+ return -1;
+ return 0;
+}
+# endif // __WATCOM__
+
+#endif // ADLMIDI_HW_OPL
+
ADLMIDI_EXPORT int adl_play(struct ADL_MIDIPlayer *device, int sampleCount, short *out)
{
@@ -930,23 +1092,24 @@ 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;
if(!device)
return 0;
- MIDIplay *player = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
- MIDIplay::Setup &setup = player->m_setup;
+ MidiPlayer *player = GET_MIDI_PLAYER(device);
+ MidiPlayer::Setup &setup = player->m_setup;
ssize_t gotten_len = 0;
ssize_t n_periodCountStereo = 512;
@@ -975,7 +1138,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 +1187,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,22 +1200,22 @@ 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;
if(!device)
return 0;
- MIDIplay *player = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
- MIDIplay::Setup &setup = player->m_setup;
+ MidiPlayer *player = GET_MIDI_PLAYER(device);
+ MidiPlayer::Setup &setup = player->m_setup;
ssize_t gotten_len = 0;
ssize_t n_periodCountStereo = 512;
@@ -1101,12 +1261,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)
@@ -1114,12 +1274,14 @@ ADLMIDI_EXPORT double adl_tickEvents(struct ADL_MIDIPlayer *device, double secon
#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER
if(!device)
return -1.0;
- MIDIplay *player = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
- if(!player)
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ if(!play)
return -1.0;
- return player->Tick(seconds, granuality);
+ return play->Tick(seconds, granuality);
#else
- (void)seconds; (void)granuality;
+ ADL_UNUSED(device);
+ ADL_UNUSED(seconds);
+ ADL_UNUSED(granuality);
return -1.0;
#endif
}
@@ -1129,11 +1291,12 @@ ADLMIDI_EXPORT int adl_atEnd(struct ADL_MIDIPlayer *device)
#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER
if(!device)
return 1;
- MIDIplay *player = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
- if(!player)
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ if(!play)
return 1;
- return (int)player->atEnd;
+ return (int)play->m_sequencer.positionAtEnd();
#else
+ ADL_UNUSED(device);
return 1;
#endif
}
@@ -1142,128 +1305,138 @@ ADLMIDI_EXPORT void adl_panic(struct ADL_MIDIPlayer *device)
{
if(!device)
return;
- MIDIplay *player = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
- if(!player)
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ if(!play)
return;
- player->realTime_panic();
+ play->realTime_panic();
}
ADLMIDI_EXPORT void adl_rt_resetState(struct ADL_MIDIPlayer *device)
{
if(!device)
return;
- MIDIplay *player = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
- if(!player)
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ if(!play)
return;
- player->realTime_ResetState();
+ play->realTime_ResetState();
}
ADLMIDI_EXPORT int adl_rt_noteOn(struct ADL_MIDIPlayer *device, ADL_UInt8 channel, ADL_UInt8 note, ADL_UInt8 velocity)
{
if(!device)
return 0;
- MIDIplay *player = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
- if(!player)
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ if(!play)
return 0;
- return (int)player->realTime_NoteOn(channel, note, velocity);
+ return (int)play->realTime_NoteOn(channel, note, velocity);
}
ADLMIDI_EXPORT void adl_rt_noteOff(struct ADL_MIDIPlayer *device, ADL_UInt8 channel, ADL_UInt8 note)
{
if(!device)
return;
- MIDIplay *player = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
- if(!player)
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ if(!play)
return;
- player->realTime_NoteOff(channel, note);
+ play->realTime_NoteOff(channel, note);
}
ADLMIDI_EXPORT void adl_rt_noteAfterTouch(struct ADL_MIDIPlayer *device, ADL_UInt8 channel, ADL_UInt8 note, ADL_UInt8 atVal)
{
if(!device)
return;
- MIDIplay *player = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
- if(!player)
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ if(!play)
return;
- player->realTime_NoteAfterTouch(channel, note, atVal);
+ play->realTime_NoteAfterTouch(channel, note, atVal);
}
ADLMIDI_EXPORT void adl_rt_channelAfterTouch(struct ADL_MIDIPlayer *device, ADL_UInt8 channel, ADL_UInt8 atVal)
{
if(!device)
return;
- MIDIplay *player = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
- if(!player)
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ if(!play)
return;
- player->realTime_ChannelAfterTouch(channel, atVal);
+ play->realTime_ChannelAfterTouch(channel, atVal);
}
ADLMIDI_EXPORT void adl_rt_controllerChange(struct ADL_MIDIPlayer *device, ADL_UInt8 channel, ADL_UInt8 type, ADL_UInt8 value)
{
if(!device)
return;
- MIDIplay *player = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
- if(!player)
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ if(!play)
return;
- player->realTime_Controller(channel, type, value);
+ play->realTime_Controller(channel, type, value);
}
ADLMIDI_EXPORT void adl_rt_patchChange(struct ADL_MIDIPlayer *device, ADL_UInt8 channel, ADL_UInt8 patch)
{
if(!device)
return;
- MIDIplay *player = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
- if(!player)
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ if(!play)
return;
- player->realTime_PatchChange(channel, patch);
+ play->realTime_PatchChange(channel, patch);
}
ADLMIDI_EXPORT void adl_rt_pitchBend(struct ADL_MIDIPlayer *device, ADL_UInt8 channel, ADL_UInt16 pitch)
{
if(!device)
return;
- MIDIplay *player = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
- if(!player)
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ if(!play)
return;
- player->realTime_PitchBend(channel, pitch);
+ play->realTime_PitchBend(channel, pitch);
}
ADLMIDI_EXPORT void adl_rt_pitchBendML(struct ADL_MIDIPlayer *device, ADL_UInt8 channel, ADL_UInt8 msb, ADL_UInt8 lsb)
{
if(!device)
return;
- MIDIplay *player = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
- if(!player)
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ if(!play)
return;
- player->realTime_PitchBend(channel, msb, lsb);
+ play->realTime_PitchBend(channel, msb, lsb);
}
ADLMIDI_EXPORT void adl_rt_bankChangeLSB(struct ADL_MIDIPlayer *device, ADL_UInt8 channel, ADL_UInt8 lsb)
{
if(!device)
return;
- MIDIplay *player = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
- if(!player)
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ if(!play)
return;
- player->realTime_BankChangeLSB(channel, lsb);
+ play->realTime_BankChangeLSB(channel, lsb);
}
ADLMIDI_EXPORT void adl_rt_bankChangeMSB(struct ADL_MIDIPlayer *device, ADL_UInt8 channel, ADL_UInt8 msb)
{
if(!device)
return;
- MIDIplay *player = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
- if(!player)
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ if(!play)
return;
- player->realTime_BankChangeMSB(channel, msb);
+ play->realTime_BankChangeMSB(channel, msb);
}
ADLMIDI_EXPORT void adl_rt_bankChange(struct ADL_MIDIPlayer *device, ADL_UInt8 channel, ADL_SInt16 bank)
{
if(!device)
return;
- MIDIplay *player = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer);
- if(!player)
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ if(!play)
return;
- player->realTime_BankChange(channel, (uint16_t)bank);
+ play->realTime_BankChange(channel, (uint16_t)bank);
+}
+
+ADLMIDI_EXPORT int adl_rt_systemExclusive(struct ADL_MIDIPlayer *device, const ADL_UInt8 *msg, size_t size)
+{
+ if(!device)
+ return -1;
+ MidiPlayer *play = GET_MIDI_PLAYER(device);
+ if(!play)
+ return -1;
+ return play->realTime_SysEx(msg, size);
}
diff --git a/src/adlmidi_load.cpp b/src/adlmidi_load.cpp
index 7f69e25..9487188 100644
--- a/src/adlmidi_load.cpp
+++ b/src/adlmidi_load.cpp
@@ -24,50 +24,16 @@
#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)
{
- fileReader file;
+ FileAndMemReader file;
file.openFile(filename.c_str());
return LoadBank(file);
}
bool MIDIplay::LoadBank(const void *data, size_t size)
{
- fileReader file;
+ FileAndMemReader file;
file.openData(data, size);
return LoadBank(file);
}
@@ -138,7 +104,7 @@ static void cvt_FMIns_to_generic(WOPLI &ins, const adlinsdata2 &in)
ins.second_voice_detune = -1;
else
{
- long value = lround(voice2_fine_tune * (1000.0 / 15.625));
+ long value = static_cast<long>(round(voice2_fine_tune * (1000.0 / 15.625)));
value = (value < -128) ? -128 : value;
value = (value > +127) ? +127 : value;
ins.second_voice_detune = static_cast<int8_t>(value);
@@ -194,7 +160,7 @@ void cvt_FMIns_to_ADLI(ADL_Instrument &ins, const adlinsdata2 &in)
cvt_FMIns_to_generic(ins, in);
}
-bool MIDIplay::LoadBank(MIDIplay::fileReader &fr)
+bool MIDIplay::LoadBank(FileAndMemReader &fr)
{
int err = 0;
WOPLFile *wopl = NULL;
@@ -207,9 +173,8 @@ bool MIDIplay::LoadBank(MIDIplay::fileReader &fr)
}
// Read complete bank file into the memory
- fr.seek(0, SEEK_END);
- fsize = fr.tell();
- fr.seek(0, SEEK_SET);
+ fsize = fr.fileSize();
+ fr.seek(0, FileAndMemReader::SET);
// Allocate necessary memory block
raw_file_data = (char*)malloc(fsize);
if(!raw_file_data)
@@ -293,185 +258,44 @@ bool MIDIplay::LoadBank(MIDIplay::fileReader &fr)
}
#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER
-bool MIDIplay::LoadMIDI(const std::string &filename)
-{
- fileReader file;
- file.openFile(filename.c_str());
- if(!LoadMIDI(file))
- return false;
- return true;
-}
-bool MIDIplay::LoadMIDI(const void *data, size_t size)
+bool MIDIplay::LoadMIDI_pre()
{
- fileReader file;
- file.openData(data, size);
- return LoadMIDI(file);
-}
-
-bool MIDIplay::LoadMIDI(MIDIplay::fileReader &fr)
-{
- 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;
+ caugh_missing_instruments.clear();
+ caugh_missing_banks_melodic.clear();
+ caugh_missing_banks_percussion.clear();
- 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, 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]);*/
@@ -494,230 +318,69 @@ riffskip:
adlins.adl[0] = adl;
adlins.adl[1] = adl;
adlins.ms_sound_kon = 1000;
- adlins.ms_sound_koff = 500;
+ adlins.ms_sound_koff = 0;
adlins.tone = 0;
adlins.flags = 0;
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 89c1a73..190a521 100644
--- a/src/adlmidi_midiplay.cpp
+++ b/src/adlmidi_midiplay.cpp
@@ -93,6 +93,8 @@ static const uint8_t PercussionMap[256] =
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
+enum { MasterVolumeDefault = 127 };
+
inline bool isXgPercChannel(uint8_t msb, uint8_t lsb)
{
return (msb == 0x7E || msb == 0x7F) && (lsb == 0);
@@ -115,583 +117,14 @@ 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),
- m_audioTickCounter(0)
-#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)
+ m_masterVolume(MasterVolumeDefault),
+ m_sysExDeviceId(0),
+ m_synthMode(Mode_XG),
+ m_arpeggioCounter(0)
+#if defined(ADLMIDI_AUDIO_TICK_HANDLER)
+ , m_audioTickCounter(0)
#endif
{
devices.clear();
@@ -712,13 +145,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();
@@ -726,6 +161,8 @@ MIDIplay::MIDIplay(unsigned long sampleRate):
void MIDIplay::applySetup()
{
+ opl.m_musicMode = OPL3::MODE_MIDI;
+
m_setup.tick_skip_samples_delay = 0;
opl.runAtPcmRate = m_setup.runAtPcmRate;
@@ -745,10 +182,12 @@ void MIDIplay::applySetup()
opl.ScaleModulators = m_setup.ScaleModulators < 0 ?
opl.dynamic_bank_setup.scaleModulators :
(m_setup.ScaleModulators != 0);
+
if(m_setup.LogarithmicVolumes)
opl.ChangeVolumeRangesModel(ADLMIDI_VolumeModel_NativeOPL3);
- opl.m_musicMode = OPL3::MODE_MIDI;
- opl.ChangeVolumeRangesModel(static_cast<ADLMIDI_VolumeModels>(m_setup.VolumeModel));
+ else
+ opl.ChangeVolumeRangesModel(static_cast<ADLMIDI_VolumeModels>(m_setup.VolumeModel));
+
if(m_setup.VolumeModel == ADLMIDI_VolumeModel_AUTO)//Use bank default volume model
opl.m_volumeScale = (OPL3::VolumesScale)opl.dynamic_bank_setup.volumeModel;
@@ -764,191 +203,16 @@ 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)
+void MIDIplay::TickIterators(double s)
{
- 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(CurrentPositionNew.wait < 0.0)//Avoid negative delay value!
- return 0.0;
-
- return CurrentPositionNew.wait;
-}
-#endif /* ADLMIDI_DISABLE_MIDI_SEQUENCER */
-
-void MIDIplay::TickIteratos(double s)
-{
- for(uint16_t c = 0; c < opl.NumChannels; ++c)
- ch[c].AddAge(static_cast<int64_t>(s * 1000.0));
- UpdateVibrato(s);
- UpdateArpeggio(s);
-}
-
-#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;
+#if !defined(ADLMIDI_AUDIO_TICK_HANDLER)
+ UpdateGlide(s);
+#endif
}
-#endif /* ADLMIDI_DISABLE_MIDI_SEQUENCER */
void MIDIplay::realTime_ResetState()
{
@@ -961,9 +225,12 @@ void MIDIplay::realTime_ResetState()
chan.lastlrpn = 0;
chan.lastmrpn = 0;
chan.nrpn = false;
+ if((m_synthMode & Mode_GS) != 0)// Reset custom drum channels on GS
+ chan.is_xg_percussion = false;
NoteUpdate_All(uint16_t(ch), Upd_All);
NoteUpdate_All(uint16_t(ch), Upd_Off);
}
+ m_masterVolume = MasterVolumeDefault;
}
bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity)
@@ -996,32 +263,40 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity)
MIDIchannel &midiChan = Ch[channel];
size_t midiins = midiChan.patch;
- bool isPercussion = (channel % 16 == 9);
- bool isXgPercussion = false;
+ bool isPercussion = (channel % 16 == 9) || midiChan.is_xg_percussion;
uint16_t bank = 0;
if(midiChan.bank_msb || midiChan.bank_lsb)
{
- bank = (uint16_t(midiChan.bank_msb) * 256) + uint16_t(midiChan.bank_lsb);
- //0x7E00 - XG SFX1/SFX2 channel (16128 signed decimal)
- //0x7F00 - XG Percussion channel (16256 signed decimal)
- if(bank == 0x7E00 || bank == 0x7F00)
- {
- //Let XG SFX1/SFX2 bank will have LSB==1 (128...255 range in WOPN file)
- //Let XG Percussion bank will use (0...127 range in WOPN file)
- bank = (uint16_t)midiins + ((bank == 0x7E00) ? 128 : 0); // MIDI instrument defines the patch
- midiins = note; // Percussion instrument
- isXgPercussion = true;
- isPercussion = false;
- }
+ if((m_synthMode & Mode_GS) != 0) //in GS mode ignore LSB
+ bank = (uint16_t(midiChan.bank_msb) * 256);
+ else
+ bank = (uint16_t(midiChan.bank_msb) * 256) + uint16_t(midiChan.bank_lsb);
}
if(isPercussion)
{
- bank = (uint16_t)midiins; // MIDI instrument defines the patch
+ // == XG bank numbers ==
+ // 0x7E00 - XG "SFX Kits" SFX1/SFX2 channel (16128 signed decimal)
+ // 0x7F00 - XG "Drum Kits" Percussion channel (16256 signed decimal)
+
+ // MIDI instrument defines the patch:
+ if((m_synthMode & Mode_XG) != 0)
+ {
+ // Let XG SFX1/SFX2 bank will go in 128...255 range of LSB in WOPN file)
+ // Let XG Percussion bank will use (0...127 LSB range in WOPN file)
+
+ // Choose: SFX or Drum Kits
+ bank = (uint16_t)midiins + ((bank == 0x7E00) ? 128 : 0);
+ }
+ else
+ {
+ bank = (uint16_t)midiins;
+ }
midiins = note; // Percussion instrument
}
- if(isPercussion || isXgPercussion)
+
+ if(isPercussion)
bank += OPL3::PercussionTag;
const adlinsdata2 *ains = &OPL3::emptyInstrument;
@@ -1038,9 +313,9 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity)
ains = &bnk->ins[midiins];
else if(hooks.onDebugMessage)
{
- std::set<uint16_t> &missing = (isPercussion || isXgPercussion) ?
+ std::set<uint16_t> &missing = (isPercussion) ?
caugh_missing_banks_percussion : caugh_missing_banks_melodic;
- const char *text = (isPercussion || isXgPercussion) ?
+ const char *text = (isPercussion) ?
"percussion" : "melodic";
if(missing.insert(bank).second)
hooks.onDebugMessage(hooks.onDebugMessage_userData, "[%i] Playing missing %s MIDI bank %i (patch %i)", channel, text, bank, midiins);
@@ -1057,25 +332,15 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity)
ains = &bnk->ins[midiins];
}
- /*
- if(MidCh%16 == 9 || (midiins != 32 && midiins != 46 && midiins != 48 && midiins != 50))
- break; // HACK
- if(midiins == 46) vol = (vol*7)/10; // HACK
- if(midiins == 48 || midiins == 50) vol /= 4; // HACK
- */
- //if(midiins == 56) vol = vol*6/10; // HACK
- //int meta = banks[opl.AdlBank][midiins];
-
int16_t tone = note;
-
- if(!isPercussion && !isXgPercussion && (bank > 0)) // For non-zero banks
+ if(!isPercussion && (bank > 0)) // For non-zero banks
{
if(ains->flags & adlinsdata::Flag_NoSound)
{
if(hooks.onDebugMessage)
{
if(caugh_missing_instruments.insert(static_cast<uint8_t>(midiins)).second)
- hooks.onDebugMessage(hooks.onDebugMessage_userData, "[%i] Caugh a blank instrument %i (offset %i) in the MIDI bank %u", channel, Ch[channel].patch, midiins, bank);
+ hooks.onDebugMessage(hooks.onDebugMessage_userData, "[%i] Caught a blank instrument %i (offset %i) in the MIDI bank %u", channel, Ch[channel].patch, midiins, bank);
}
bank = 0;
midiins = midiChan.patch;
@@ -1204,16 +469,37 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity)
//if(hooks.onDebugMessage)
// hooks.onDebugMessage(hooks.onDebugMessage_userData, "i1=%d:%d, i2=%d:%d", i[0],adlchannel[0], i[1],adlchannel[1]);
+ if(midiChan.softPedal) // Apply Soft Pedal level reducing
+ velocity = static_cast<uint8_t>(std::floor(static_cast<float>(velocity) * 0.8f));
+
// Allocate active note for MIDI channel
std::pair<MIDIchannel::activenoteiterator, bool>
ir = midiChan.activenotes_insert(note);
ir.first->vol = velocity;
ir.first->vibrato = midiChan.noteAftertouch[note];
- ir.first->tone = tone;
+ ir.first->noteTone = tone;
+ ir.first->currentTone = tone;
+ ir.first->glideRate = HUGE_VAL;
ir.first->midiins = midiins;
ir.first->ains = ains;
ir.first->chip_channels_count = 0;
+ int8_t currentPortamentoSource = midiChan.portamentoSource;
+ double currentPortamentoRate = midiChan.portamentoRate;
+ bool portamentoEnable =
+ midiChan.portamentoEnable && currentPortamentoRate != HUGE_VAL && !isPercussion;
+ // Record the last note on MIDI channel as source of portamento
+ midiChan.portamentoSource = static_cast<int8_t>(note);
+ // midiChan.portamentoSource = portamentoEnable ? (int8_t)note : (int8_t)-1;
+
+ // Enable gliding on portamento note
+ if (portamentoEnable && currentPortamentoSource >= 0)
+ {
+ ir.first->currentTone = currentPortamentoSource;
+ ir.first->glideRate = currentPortamentoRate;
+ ++midiChan.gliding_note_count;
+ }
+
for(unsigned ccount = 0; ccount < MIDIchannel::NoteInfo::MaxNumPhysChans; ++ccount)
{
int32_t c = adlchannel[ccount];
@@ -1222,6 +508,7 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity)
uint16_t chipChan = static_cast<uint16_t>(adlchannel[ccount]);
ir.first->phys_ensure_find_or_create(chipChan)->assign(voices[ccount]);
}
+
NoteUpdate(channel, ir.first, Upd_All | Upd_Patch);
return true;
}
@@ -1271,27 +558,29 @@ void MIDIplay::realTime_Controller(uint8_t channel, uint8_t type, uint8_t value)
case 0: // Set bank msb (GM bank)
Ch[channel].bank_msb = value;
- Ch[channel].is_xg_percussion = isXgPercChannel(Ch[channel].bank_msb, Ch[channel].bank_lsb);
+ if((m_synthMode & Mode_GS) == 0)// Don't use XG drums on GS synth mode
+ Ch[channel].is_xg_percussion = isXgPercChannel(Ch[channel].bank_msb, Ch[channel].bank_lsb);
break;
case 32: // Set bank lsb (XG bank)
Ch[channel].bank_lsb = value;
- Ch[channel].is_xg_percussion = isXgPercChannel(Ch[channel].bank_msb, Ch[channel].bank_lsb);
+ if((m_synthMode & Mode_GS) == 0)// Don't use XG drums on GS synth mode
+ Ch[channel].is_xg_percussion = isXgPercChannel(Ch[channel].bank_msb, Ch[channel].bank_lsb);
break;
case 5: // Set portamento msb
Ch[channel].portamento = static_cast<uint16_t>((Ch[channel].portamento & 0x7F) | (value << 7));
- //UpdatePortamento(MidCh);
+ UpdatePortamento(channel);
break;
case 37: // Set portamento lsb
Ch[channel].portamento = (Ch[channel].portamento & 0x3F80) | (value);
- //UpdatePortamento(MidCh);
+ UpdatePortamento(channel);
break;
case 65: // Enable/disable portamento
- // value >= 64 ? enabled : disabled
- //UpdatePortamento(MidCh);
+ Ch[channel].portamentoEnable = value >= 64;
+ UpdatePortamento(channel);
break;
case 7: // Change volume
@@ -1305,8 +594,20 @@ void MIDIplay::realTime_Controller(uint8_t channel, uint8_t type, uint8_t value)
break;
case 64: // Enable/disable sustain
- Ch[channel].sustain = value;
- if(!value) KillSustainingNotes(channel);
+ Ch[channel].sustain = (value >= 64);
+ if(!Ch[channel].sustain)
+ KillSustainingNotes(channel, -1, AdlChannel::LocationData::Sustain_Pedal);
+ break;
+
+ case 66: // Enable/disable sostenuto
+ if(value >= 64) //Find notes and mark them as sostenutoed
+ MarkSostenutoNotes(channel);
+ else
+ KillSustainingNotes(channel, -1, AdlChannel::LocationData::Sustain_Sostenuto);
+ break;
+
+ case 67: // Enable/disable soft-pedal
+ Ch[channel].softPedal = (value >= 64);
break;
case 11: // Change expression (another volume factor)
@@ -1324,14 +625,13 @@ void MIDIplay::realTime_Controller(uint8_t channel, uint8_t type, uint8_t value)
case 121: // Reset all controllers
Ch[channel].resetAllControllers();
- //UpdatePortamento(MidCh);
NoteUpdate_All(channel, Upd_Pan + Upd_Volume + Upd_Pitch);
// Kill all sustained notes
- KillSustainingNotes(channel);
+ KillSustainingNotes(channel, -1, AdlChannel::LocationData::Sustain_ANY);
break;
case 120: // All sounds off
- NoteUpdate_All(channel, Upt_OffMute);
+ NoteUpdate_All(channel, Upd_OffMute);
break;
case 123: // All notes off
@@ -1433,19 +733,270 @@ void MIDIplay::realTime_BankChange(uint8_t channel, uint16_t bank)
Ch[channel].bank_msb = uint8_t((bank >> 8) & 0xFF);
}
+void MIDIplay::setDeviceId(uint8_t id)
+{
+ m_sysExDeviceId = id;
+}
+
+bool MIDIplay::realTime_SysEx(const uint8_t *msg, size_t size)
+{
+ if(size < 4 || msg[0] != 0xF0 || msg[size - 1] != 0xF7)
+ return false;
+
+ unsigned manufacturer = msg[1];
+ unsigned dev = msg[2];
+ msg += 3;
+ size -= 4;
+
+ switch(manufacturer)
+ {
+ default:
+ break;
+ case Manufacturer_UniversalNonRealtime:
+ case Manufacturer_UniversalRealtime:
+ return doUniversalSysEx(
+ dev, manufacturer == Manufacturer_UniversalRealtime, msg, size);
+ case Manufacturer_Roland:
+ return doRolandSysEx(dev, msg, size);
+ case Manufacturer_Yamaha:
+ return doYamahaSysEx(dev, msg, size);
+ }
+
+ return false;
+}
+
+bool MIDIplay::doUniversalSysEx(unsigned dev, bool realtime, const uint8_t *data, size_t size)
+{
+ bool devicematch = dev == 0x7F || dev == m_sysExDeviceId;
+ if(size < 2 || !devicematch)
+ return false;
+
+ unsigned address =
+ (((unsigned)data[0] & 0x7F) << 8) |
+ (((unsigned)data[1] & 0x7F));
+ data += 2;
+ size -= 2;
+
+ switch(((unsigned)realtime << 16) | address)
+ {
+ case (0 << 16) | 0x0901: // GM System On
+ if(hooks.onDebugMessage)
+ hooks.onDebugMessage(hooks.onDebugMessage_userData, "SysEx: GM System On");
+ m_synthMode = Mode_GM;
+ realTime_ResetState();
+ return true;
+ case (0 << 16) | 0x0902: // GM System Off
+ if(hooks.onDebugMessage)
+ hooks.onDebugMessage(hooks.onDebugMessage_userData, "SysEx: GM System Off");
+ m_synthMode = Mode_XG;//TODO: TEMPORARY, make something RIGHT
+ realTime_ResetState();
+ return true;
+ case (1 << 16) | 0x0401: // MIDI Master Volume
+ if(size != 2)
+ break;
+ unsigned volume =
+ (((unsigned)data[0] & 0x7F)) |
+ (((unsigned)data[1] & 0x7F) << 7);
+ m_masterVolume = volume >> 7;
+ for(size_t ch = 0; ch < Ch.size(); ch++)
+ NoteUpdate_All(uint16_t(ch), Upd_Volume);
+ return true;
+ }
+
+ return false;
+}
+
+bool MIDIplay::doRolandSysEx(unsigned dev, const uint8_t *data, size_t size)
+{
+ bool devicematch = dev == 0x7F || (dev & 0x0F) == m_sysExDeviceId;
+ if(size < 6 || !devicematch)
+ return false;
+
+ unsigned model = data[0] & 0x7F;
+ unsigned mode = data[1] & 0x7F;
+ unsigned checksum = data[size - 1] & 0x7F;
+ data += 2;
+ size -= 3;
+
+#if !defined(ADLMIDI_SKIP_ROLAND_CHECKSUM)
+ {
+ unsigned checkvalue = 0;
+ for(size_t i = 0; i < size; ++i)
+ checkvalue += data[i] & 0x7F;
+ checkvalue = (128 - (checkvalue & 127)) & 127;
+ if(checkvalue != checksum)
+ {
+ if(hooks.onDebugMessage)
+ hooks.onDebugMessage(hooks.onDebugMessage_userData, "SysEx: Caught invalid roland SysEx message!");
+ return false;
+ }
+ }
+#endif
+
+ unsigned address =
+ (((unsigned)data[0] & 0x7F) << 16) |
+ (((unsigned)data[1] & 0x7F) << 8) |
+ (((unsigned)data[2] & 0x7F));
+ unsigned target_channel = 0;
+
+ /* F0 41 10 42 12 40 00 7F 00 41 F7 */
+
+ if((address & 0xFFF0FF) == 0x401015) // Turn channel 1 into percussion
+ {
+ address = 0x401015;
+ target_channel = data[1] & 0x0F;
+ }
+
+ data += 3;
+ size -= 3;
+
+ if(mode != RolandMode_Send) // don't have MIDI-Out reply ability
+ return false;
+
+ // Mode Set
+ // F0 {41 10 42 12} {40 00 7F} {00 41} F7
+
+ // Custom drum channels
+ // F0 {41 10 42 12} {40 1<ch> 15} {<state> <sum>} F7
+
+ switch((model << 24) | address)
+ {
+ case (RolandModel_GS << 24) | 0x00007F: // System Mode Set
+ {
+ if(size != 1 || (dev & 0xF0) != 0x10)
+ break;
+ unsigned mode = data[0] & 0x7F;
+ ADL_UNUSED(mode);//TODO: Hook this correctly!
+ if(hooks.onDebugMessage)
+ hooks.onDebugMessage(hooks.onDebugMessage_userData, "SysEx: Caught Roland System Mode Set: %02X", mode);
+ m_synthMode = Mode_GS;
+ realTime_ResetState();
+ return true;
+ }
+ case (RolandModel_GS << 24) | 0x40007F: // Mode Set
+ {
+ if(size != 1 || (dev & 0xF0) != 0x10)
+ break;
+ unsigned value = data[0] & 0x7F;
+ ADL_UNUSED(value);//TODO: Hook this correctly!
+ if(hooks.onDebugMessage)
+ hooks.onDebugMessage(hooks.onDebugMessage_userData, "SysEx: Caught Roland Mode Set: %02X", value);
+ m_synthMode = Mode_GS;
+ realTime_ResetState();
+ return true;
+ }
+ case (RolandModel_GS << 24) | 0x401015: // Percussion channel
+ {
+ if(size != 1 || (dev & 0xF0) != 0x10)
+ break;
+ if(Ch.size() < 16)
+ break;
+ unsigned value = data[0] & 0x7F;
+ const uint8_t channels_map[16] =
+ {
+ 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15
+ };
+ if(hooks.onDebugMessage)
+ hooks.onDebugMessage(hooks.onDebugMessage_userData,
+ "SysEx: Caught Roland Percussion set: %02X on channel %u (from %X)",
+ value, channels_map[target_channel], target_channel);
+ Ch[channels_map[target_channel]].is_xg_percussion = ((value == 0x01)) || ((value == 0x02));
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool MIDIplay::doYamahaSysEx(unsigned dev, const uint8_t *data, size_t size)
+{
+ bool devicematch = dev == 0x7F || (dev & 0x0F) == m_sysExDeviceId;
+ if(size < 1 || !devicematch)
+ return false;
+
+ unsigned model = data[0] & 0x7F;
+ ++data;
+ --size;
+
+ switch((model << 8) | (dev & 0xF0))
+ {
+ case (YamahaModel_XG << 8) | 0x10: // parameter change
+ {
+ if(size < 3)
+ break;
+
+ unsigned address =
+ (((unsigned)data[0] & 0x7F) << 16) |
+ (((unsigned)data[1] & 0x7F) << 8) |
+ (((unsigned)data[2] & 0x7F));
+ data += 3;
+ size -= 3;
+
+ switch(address)
+ {
+ case 0x00007E: // XG System On
+ if(size != 1)
+ break;
+ unsigned value = data[0] & 0x7F;
+ ADL_UNUSED(value);//TODO: Hook this correctly!
+ if(hooks.onDebugMessage)
+ hooks.onDebugMessage(hooks.onDebugMessage_userData, "SysEx: Caught Yamaha XG System On: %02X", value);
+ m_synthMode = Mode_XG;
+ realTime_ResetState();
+ return true;
+ }
+
+ break;
+ }
+ }
+
+ return false;
+}
+
void MIDIplay::realTime_panic()
{
Panic();
- KillSustainingNotes(-1, -1);
+ KillSustainingNotes(-1, -1, AdlChannel::LocationData::Sustain_ANY);
+}
+
+void MIDIplay::realTime_deviceSwitch(size_t track, const char *data, size_t length)
+{
+ const std::string indata(data, length);
+ current_device[track] = ChooseDevice(indata);
}
-void MIDIplay::AudioTick(uint32_t chipId, uint32_t /*rate*/)
+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)
{
if(chipId != 0) // do first chip ticks only
return;
- /*uint32_t tickNumber = */m_audioTickCounter++;
+ uint32_t tickNumber = m_audioTickCounter++;
+ double timeDelta = 1.0 / rate;
+
+ enum { portamentoInterval = 32 }; // for efficiency, set rate limit on pitch updates
+
+ if(tickNumber % portamentoInterval == 0)
+ {
+ double portamentoDelta = timeDelta * portamentoInterval;
+ UpdateGlide(portamentoDelta);
+ }
}
+#endif
void MIDIplay::NoteUpdate(uint16_t MidCh,
MIDIplay::MIDIchannel::activenoteiterator i,
@@ -1453,7 +1004,8 @@ void MIDIplay::NoteUpdate(uint16_t MidCh,
int32_t select_adlchn)
{
MIDIchannel::NoteInfo &info = *i;
- const int16_t tone = info.tone;
+ const int16_t noteTone = info.noteTone;
+ const double currentTone = info.currentTone;
const uint8_t vol = info.vol;
const int midiins = static_cast<int>(info.midiins);
const adlinsdata2 &ains = *info.ains;
@@ -1474,7 +1026,7 @@ void MIDIplay::NoteUpdate(uint16_t MidCh,
AdlChannel::LocationData *d = ch[c].users_find_or_create(my_loc);
if(d) // inserts if necessary
{
- d->sustained = false;
+ d->sustained = AdlChannel::LocationData::Sustain_None;
d->vibdelay = 0;
d->fixed_sustain = (ains.ms_sound_kon == static_cast<uint16_t>(adlNoteOnMaxTime));
d->kon_time_until_neglible = ains.ms_sound_kon;
@@ -1493,17 +1045,17 @@ void MIDIplay::NoteUpdate(uint16_t MidCh,
if(props_mask & Upd_Off) // note off
{
- if(Ch[MidCh].sustain == 0)
+ if(!Ch[MidCh].sustain)
{
AdlChannel::LocationData *k = ch[c].users_find(my_loc);
-
- if(k)
+ bool do_erase_user = (k && ((k->sustained & AdlChannel::LocationData::Sustain_Sostenuto) == 0));
+ if(do_erase_user)
ch[c].users_erase(k);
if(hooks.onNote)
- hooks.onNote(hooks.onNote_userData, c, tone, midiins, 0, 0.0);
+ hooks.onNote(hooks.onNote_userData, c, noteTone, midiins, 0, 0.0);
- if(ch[c].users_empty())
+ if(do_erase_user && ch[c].users_empty())
{
opl.NoteOff(c);
if(props_mask & Upd_Mute) // Mute the note
@@ -1523,9 +1075,9 @@ void MIDIplay::NoteUpdate(uint16_t MidCh,
// Also will avoid overwriting it very soon.
AdlChannel::LocationData *d = ch[c].users_find_or_create(my_loc);
if(d)
- d->sustained = true; // note: not erased!
+ d->sustained |= AdlChannel::LocationData::Sustain_Pedal; // note: not erased!
if(hooks.onNote)
- hooks.onNote(hooks.onNote_userData, c, tone, midiins, -1, 0.0);
+ hooks.onNote(hooks.onNote_userData, c, noteTone, midiins, -1, 0.0);
}
info.phys_erase_at(&ins); // decrements channel count
@@ -1553,10 +1105,10 @@ void MIDIplay::NoteUpdate(uint16_t MidCh,
switch(opl.m_volumeScale)
{
-
+ default:
case OPL3::VOLUME_Generic:
{
- volume = vol * Ch[MidCh].volume * Ch[MidCh].expression;
+ volume = vol * m_masterVolume * Ch[MidCh].volume * Ch[MidCh].expression;
/* If the channel has arpeggio, the effective volume of
* *this* instrument is actually lower due to timesharing.
@@ -1567,64 +1119,60 @@ void MIDIplay::NoteUpdate(uint16_t MidCh,
*/
//volume = (int)(volume * std::sqrt( (double) ch[c].users.size() ));
- // The formula below: SOLVE(V=127^3 * 2^( (A-63.49999) / 8), A)
- volume = volume > 8725 ? static_cast<uint32_t>(std::log(static_cast<double>(volume)) * 11.541561 + (0.5 - 104.22845)) : 0;
- // The incorrect formula below: SOLVE(V=127^3 * (2^(A/63)-1), A)
- //opl.Touch_Real(c, volume>11210 ? 91.61112 * std::log(4.8819E-7*volume + 1.0)+0.5 : 0);
-
- opl.Touch_Real(c, volume, brightness);
- //opl.Touch(c, volume);
+ // The formula below: SOLVE(V=127^4 * 2^( (A-63.49999) / 8), A)
+ volume = volume > (8725 * 127) ? static_cast<uint32_t>(std::log(static_cast<double>(volume)) * 11.541560327111707 - 1.601379199767093e+02) : 0;
+ // The incorrect formula below: SOLVE(V=127^4 * (2^(A/63)-1), A)
+ //opl.Touch_Real(c, volume>(11210*127) ? 91.61112 * std::log((4.8819E-7/127)*volume + 1.0)+0.5 : 0);
}
break;
case OPL3::VOLUME_NATIVE:
{
volume = vol * Ch[MidCh].volume * Ch[MidCh].expression;
- volume = volume * 127 / (127 * 127 * 127) / 2;
- opl.Touch_Real(c, volume, brightness);
+ // volume = volume * m_masterVolume / (127 * 127 * 127) / 2;
+ volume = (volume * m_masterVolume) / 4096766;
}
break;
case OPL3::VOLUME_DMX:
{
- volume = 2 * ((Ch[MidCh].volume * Ch[MidCh].expression) * 127 / 16129) + 1;
+ volume = 2 * (Ch[MidCh].volume * Ch[MidCh].expression * m_masterVolume / 16129) + 1;
//volume = 2 * (Ch[MidCh].volume) + 1;
volume = (DMX_volume_mapping_table[(vol < 128) ? vol : 127] * volume) >> 9;
- opl.Touch_Real(c, volume, brightness);
}
break;
case OPL3::VOLUME_APOGEE:
{
- volume = ((Ch[MidCh].volume * Ch[MidCh].expression) * 127 / 16129);
+ volume = (Ch[MidCh].volume * Ch[MidCh].expression * m_masterVolume / 16129);
volume = ((64 * (vol + 0x80)) * volume) >> 15;
//volume = ((63 * (vol + 0x80)) * Ch[MidCh].volume) >> 15;
- opl.Touch_Real(c, volume, brightness);
}
break;
case OPL3::VOLUME_9X:
{
- //volume = 63 - W9X_volume_mapping_table[(((vol * Ch[MidCh].volume /** Ch[MidCh].expression*/) * 127 / 16129 /*2048383*/) >> 2)];
- volume = 63 - W9X_volume_mapping_table[(((vol * Ch[MidCh].volume * Ch[MidCh].expression) * 127 / 2048383) >> 2)];
+ //volume = 63 - W9X_volume_mapping_table[(((vol * Ch[MidCh].volume /** Ch[MidCh].expression*/) * m_masterVolume / 16129 /*2048383*/) >> 2)];
+ volume = 63 - W9X_volume_mapping_table[((vol * Ch[MidCh].volume * Ch[MidCh].expression * m_masterVolume / 2048383) >> 2)];
//volume = W9X_volume_mapping_table[vol >> 2] + volume;
- opl.Touch_Real(c, volume, brightness);
}
break;
}
+ opl.Touch_Real(c, volume, brightness);
+
/* 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)
@@ -1632,7 +1180,7 @@ void MIDIplay::NoteUpdate(uint16_t MidCh,
AdlChannel::LocationData *d = ch[c].users_find(my_loc);
// Don't bend a sustained note
- if(!d || !d->sustained)
+ if(!d || (d->sustained == AdlChannel::LocationData::Sustain_None))
{
double midibend = Ch[MidCh].bend * Ch[MidCh].bendsense;
double bend = midibend + ins.ains.finetune;
@@ -1648,355 +1196,22 @@ void MIDIplay::NoteUpdate(uint16_t MidCh,
if(vibrato && (!d || d->vibdelay >= Ch[MidCh].vibdelay))
bend += static_cast<double>(vibrato) * Ch[MidCh].vibdepth * std::sin(Ch[MidCh].vibpos);
-#ifdef ADLMIDI_USE_DOSBOX_OPL
-# define BEND_COEFFICIENT 172.00093
-#else
-# define BEND_COEFFICIENT 172.4387
-#endif
- opl.NoteOn(c, BEND_COEFFICIENT * std::exp(0.057762265 * (static_cast<double>(tone) + bend + phase)));
+#define BEND_COEFFICIENT 172.4387
+ opl.NoteOn(c, BEND_COEFFICIENT * std::exp(0.057762265 * (currentTone + bend + phase)));
#undef BEND_COEFFICIENT
if(hooks.onNote)
- hooks.onNote(hooks.onNote_userData, c, tone, midiins, vol, midibend);
+ hooks.onNote(hooks.onNote_userData, c, noteTone, midiins, vol, midibend);
}
}
}
if(info.chip_channels_count == 0)
- Ch[MidCh].activenotes_erase(i);
-}
-
-#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;
+ if(i->glideRate != HUGE_VAL)
+ --Ch[MidCh].gliding_note_count;
+ Ch[MidCh].activenotes_erase(i);
}
-
- return evt;
}
-#endif /* ADLMIDI_DISABLE_MIDI_SEQUENCER */
const std::string &MIDIplay::getErrorString()
{
@@ -2008,168 +1223,9 @@ 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;
+ int64_t s = (opl.m_musicMode != OPL3::MODE_CMF) ? -ch[c].koff_time_until_neglible : 0;
// Same midi-instrument = some stability
//if(c == MidCh) s += 4;
@@ -2177,7 +1233,7 @@ int64_t MIDIplay::CalculateAdlChannelGoodness(size_t c, const MIDIchannel::NoteI
{
s -= 4000;
- if(!j->sustained)
+ if(j->sustained == AdlChannel::LocationData::Sustain_None)
s -= j->kon_time_until_neglible;
else
s -= (j->kon_time_until_neglible / 2);
@@ -2223,7 +1279,7 @@ int64_t MIDIplay::CalculateAdlChannelGoodness(size_t c, const MIDIchannel::NoteI
for(AdlChannel::LocationData *m = ch[c2].users_first; m; m = m->next)
{
- if(m->sustained) continue;
+ if(m->sustained != AdlChannel::LocationData::Sustain_None) continue;
if(m->vibdelay >= 200) continue;
if(m->ins != j->ins) continue;
n_evacuation_stations += 1;
@@ -2247,7 +1303,7 @@ void MIDIplay::PrepareAdlChannelForNewNote(size_t c, const MIDIchannel::NoteInfo
AdlChannel::LocationData *j = jnext;
jnext = jnext->next;
- if(!j->sustained)
+ if(j->sustained == AdlChannel::LocationData::Sustain_None)
{
// Collision: Kill old note,
// UNLESS we're going to do arpeggio
@@ -2272,7 +1328,7 @@ void MIDIplay::PrepareAdlChannelForNewNote(size_t c, const MIDIchannel::NoteInfo
// Kill all sustained notes on this channel
// Don't keep them for arpeggio, because arpeggio requires
// an intact "activenotes" record. This is a design flaw.
- KillSustainingNotes(-1, static_cast<int32_t>(c));
+ KillSustainingNotes(-1, static_cast<int32_t>(c), AdlChannel::LocationData::Sustain_ANY);
// Keyoff the channel so that it can be retriggered,
// unless the new note will be introduced as just an arpeggio.
@@ -2314,11 +1370,11 @@ void MIDIplay::KillOrEvacuate(size_t from_channel,
{
hooks.onNote(hooks.onNote_userData,
(int)from_channel,
- i->tone,
+ i->noteTone,
static_cast<int>(i->midiins), 0, 0.0);
hooks.onNote(hooks.onNote_userData,
(int)c,
- i->tone,
+ i->noteTone,
static_cast<int>(i->midiins),
i->vol, 0.0);
}
@@ -2355,7 +1411,7 @@ void MIDIplay::Panic()
}
}
-void MIDIplay::KillSustainingNotes(int32_t MidCh, int32_t this_adlchn)
+void MIDIplay::KillSustainingNotes(int32_t MidCh, int32_t this_adlchn, uint8_t sustain_type)
{
uint32_t first = 0, last = opl.NumChannels;
@@ -2365,9 +1421,10 @@ void MIDIplay::KillSustainingNotes(int32_t MidCh, int32_t this_adlchn)
last = first + 1;
}
- for(unsigned c = first; c < last; ++c)
+ for(uint32_t c = first; c < last; ++c)
{
- if(ch[c].users_empty()) continue; // Nothing to do
+ if(ch[c].users_empty())
+ continue; // Nothing to do
for(AdlChannel::LocationData *jnext = ch[c].users_first; jnext;)
{
@@ -2375,12 +1432,14 @@ void MIDIplay::KillSustainingNotes(int32_t MidCh, int32_t this_adlchn)
jnext = jnext->next;
if((MidCh < 0 || j->loc.MidCh == MidCh)
- && j->sustained)
+ && ((j->sustained & sustain_type) != 0))
{
int midiins = '?';
if(hooks.onNote)
hooks.onNote(hooks.onNote_userData, (int)c, j->loc.note, midiins, 0, 0.0);
- ch[c].users_erase(j);
+ j->sustained &= ~sustain_type;
+ if((j->sustained == AdlChannel::LocationData::Sustain_None))
+ ch[c].users_erase(j);//Remove only when note is clean from any holders
}
}
@@ -2390,6 +1449,24 @@ void MIDIplay::KillSustainingNotes(int32_t MidCh, int32_t this_adlchn)
}
}
+void MIDIplay::MarkSostenutoNotes(int32_t MidCh)
+{
+ uint32_t first = 0, last = opl.NumChannels;
+ for(uint32_t c = first; c < last; ++c)
+ {
+ if(ch[c].users_empty())
+ continue; // Nothing to do
+
+ for(AdlChannel::LocationData *jnext = ch[c].users_first; jnext;)
+ {
+ AdlChannel::LocationData *j = jnext;
+ jnext = jnext->next;
+ if((j->loc.MidCh == MidCh) && (j->sustained == AdlChannel::LocationData::Sustain_None))
+ j->sustained |= AdlChannel::LocationData::Sustain_Sostenuto;
+ }
+ }
+}
+
void MIDIplay::SetRPN(unsigned MidCh, unsigned value, bool MSB)
{
bool nrpn = Ch[MidCh].nrpn;
@@ -2405,17 +1482,26 @@ void MIDIplay::SetRPN(unsigned MidCh, unsigned value, bool MSB)
Ch[MidCh].bendsense_lsb = value;
Ch[MidCh].updateBendSensitivity();
break;
- case 0x0108 + 1*0x10000 + 1*0x20000: // Vibrato speed
- if(value == 64) Ch[MidCh].vibspeed = 1.0;
- else if(value < 100) Ch[MidCh].vibspeed = 1.0 / (1.6e-2 * (value ? value : 1));
- else Ch[MidCh].vibspeed = 1.0 / (0.051153846 * value - 3.4965385);
- Ch[MidCh].vibspeed *= 2 * 3.141592653 * 5.0;
+ case 0x0108 + 1*0x10000 + 1*0x20000:
+ if((m_synthMode & Mode_XG) != 0) // Vibrato speed
+ {
+ if(value == 64) Ch[MidCh].vibspeed = 1.0;
+ else if(value < 100) Ch[MidCh].vibspeed = 1.0 / (1.6e-2 * (value ? value : 1));
+ else Ch[MidCh].vibspeed = 1.0 / (0.051153846 * value - 3.4965385);
+ Ch[MidCh].vibspeed *= 2 * 3.141592653 * 5.0;
+ }
break;
- case 0x0109 + 1*0x10000 + 1*0x20000: // Vibrato depth
- Ch[MidCh].vibdepth = ((value - 64) * 0.15) * 0.01;
+ case 0x0109 + 1*0x10000 + 1*0x20000:
+ if((m_synthMode & Mode_XG) != 0) // Vibrato depth
+ {
+ Ch[MidCh].vibdepth = ((value - 64) * 0.15) * 0.01;
+ }
break;
- case 0x010A + 1*0x10000 + 1*0x20000: // Vibrato delay in millisecons
- Ch[MidCh].vibdelay = value ? int64_t(0.2092 * std::exp(0.0795 * (double)value)) : 0;
+ case 0x010A + 1*0x10000 + 1*0x20000:
+ if((m_synthMode & Mode_XG) != 0) // Vibrato delay in millisecons
+ {
+ Ch[MidCh].vibdelay = value ? int64_t(0.2092 * std::exp(0.0795 * (double)value)) : 0;
+ }
break;
default:/* UI.PrintLn("%s %04X <- %d (%cSB) (ch %u)",
"NRPN"+!nrpn, addr, value, "LM"[MSB], MidCh);*/
@@ -2423,15 +1509,14 @@ void MIDIplay::SetRPN(unsigned MidCh, unsigned value, bool MSB)
}
}
-//void MIDIplay::UpdatePortamento(unsigned MidCh)
-//{
-// // mt = 2^(portamento/2048) * (1.0 / 5000.0)
-// /*
-// double mt = std::exp(0.00033845077 * Ch[MidCh].portamento);
-// NoteUpdate_All(MidCh, Upd_Pitch);
-// */
-// //UI.PrintLn("Portamento %u: %u (unimplemented)", MidCh, Ch[MidCh].portamento);
-//}
+void MIDIplay::UpdatePortamento(unsigned MidCh)
+{
+ double rate = HUGE_VAL;
+ uint16_t midival = Ch[MidCh].portamento;
+ if(Ch[MidCh].portamentoEnable && midival > 0)
+ rate = 350.0 * std::pow(2.0, -0.062 * (1.0 / 128) * midival);
+ Ch[MidCh].portamentoRate = rate;
+}
void MIDIplay::NoteUpdate_All(uint16_t MidCh, unsigned props_mask)
{
@@ -2467,9 +1552,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);
@@ -2532,7 +1614,7 @@ retry_arpeggio:
n = 0; n < count; ++n)
i = i->next;
- if(i->sustained == false)
+ if(i->sustained == AdlChannel::LocationData::Sustain_None)
{
if(i->kon_time_until_neglible <= 0l)
{
@@ -2554,6 +1636,38 @@ retry_arpeggio:
}
}
+void MIDIplay::UpdateGlide(double amount)
+{
+ size_t num_channels = Ch.size();
+
+ for(size_t channel = 0; channel < num_channels; ++channel)
+ {
+ MIDIchannel &midiChan = Ch[channel];
+ if(midiChan.gliding_note_count == 0)
+ continue;
+
+ for(MIDIchannel::activenoteiterator it = midiChan.activenotes_begin();
+ it; ++it)
+ {
+ double finalTone = it->noteTone;
+ double previousTone = it->currentTone;
+
+ bool directionUp = previousTone < finalTone;
+ double toneIncr = amount * (directionUp ? +it->glideRate : -it->glideRate);
+
+ double currentTone = previousTone + toneIncr;
+ bool glideFinished = !(directionUp ? (currentTone < finalTone) : (currentTone > finalTone));
+ currentTone = glideFinished ? finalTone : currentTone;
+
+ if(currentTone != previousTone)
+ {
+ it->currentTone = currentTone;
+ NoteUpdate(static_cast<uint16_t>(channel), it, Upd_Pitch);
+ }
+ }
+ }
+}
+
#ifndef ADLMIDI_DISABLE_CPP_EXTRAS
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.cpp b/src/adlmidi_private.cpp
index 1ee7c4a..3bc73a4 100644
--- a/src/adlmidi_private.cpp
+++ b/src/adlmidi_private.cpp
@@ -27,10 +27,12 @@ std::string ADLMIDI_ErrorString;
// Generator callback on audio rate ticks
+#if defined(ADLMIDI_AUDIO_TICK_HANDLER)
void adl_audioTickHandler(void *instance, uint32_t chipId, uint32_t rate)
{
reinterpret_cast<MIDIplay *>(instance)->AudioTick(chipId, rate);
}
+#endif
int adlRefreshNumCards(ADL_MIDIPlayer *device)
{
diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp
index 0e63b5a..f34404d 100644
--- a/src/adlmidi_private.hpp
+++ b/src/adlmidi_private.hpp
@@ -39,7 +39,7 @@
#define ADLMIDI_UNSTABLE_API
#ifdef _WIN32
-#define NOMINMAX
+#define NOMINMAX 1
#endif
#if defined(_WIN32) && !defined(__WATCOMC__)
@@ -51,7 +51,7 @@ typedef __int64 ssize_t;
# else
typedef __int32 ssize_t;
# endif
-# define NOMINMAX //Don't override std::min and std::max
+# define NOMINMAX 1 //Don't override std::min and std::max
# else
# ifdef _WIN64
typedef int64_t ssize_t;
@@ -131,7 +131,15 @@ typedef int32_t ssize_t;
#define INT32_MAX 0x7fffffff
#endif
-#include "fraction.hpp"
+#include "file_reader.hpp"
+
+#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER
+// Rename class to avoid ABI collisions
+#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
@@ -199,7 +207,6 @@ inline int32_t adl_cvtU32(int32_t x)
return (uint32_t)adl_cvtS32(x) - (uint32_t)INT32_MIN;
}
-class MIDIplay;
struct ADL_MIDIPlayer;
class OPL3
{
@@ -208,7 +215,6 @@ public:
friend class AdlInstrumentTester;
uint32_t NumChannels;
char ____padding[4];
- ADL_MIDIPlayer *_parent;
#ifndef ADLMIDI_HW_OPL
std::vector<AdlMIDI_SPtr<OPLChipBase > > cardsOP2;
#endif
@@ -306,17 +312,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);
@@ -334,7 +334,7 @@ class MIDIplay
{
friend void adl_reset(struct ADL_MIDIPlayer*);
public:
- MIDIplay(unsigned long sampleRate = 22050);
+ explicit MIDIplay(unsigned long sampleRate = 22050);
~MIDIplay()
{}
@@ -343,164 +343,19 @@ public:
/**********************Internal structures and classes**********************/
- /**
- * @brief A little class gives able to read filedata from disk and also from a memory segment
- */
- class fileReader
- {
- public:
- enum relTo
- {
- SET = 0,
- CUR = 1,
- END = 2
- };
-
- fileReader()
- {
- fp = NULL;
- mp = NULL;
- mp_size = 0;
- mp_tell = 0;
- }
- ~fileReader()
- {
- close();
- }
-
- void openFile(const char *path)
- {
- #if !defined(_WIN32) || defined(__WATCOMC__)
- fp = std::fopen(path, "rb");
- #else
- wchar_t widePath[MAX_PATH];
- int size = MultiByteToWideChar(CP_UTF8, 0, path, (int)std::strlen(path), widePath, MAX_PATH);
- widePath[size] = '\0';
- fp = _wfopen(widePath, L"rb");
- #endif
- _fileName = path;
- mp = NULL;
- mp_size = 0;
- mp_tell = 0;
- }
-
- void openData(const void *mem, size_t lenght)
- {
- fp = NULL;
- mp = mem;
- mp_size = lenght;
- mp_tell = 0;
- }
-
- void seek(long pos, int rel_to)
- {
- if(fp)
- std::fseek(fp, pos, rel_to);
- else
- {
- switch(rel_to)
- {
- case SET:
- mp_tell = static_cast<size_t>(pos);
- break;
-
- case END:
- mp_tell = mp_size - static_cast<size_t>(pos);
- break;
-
- case CUR:
- mp_tell = mp_tell + static_cast<size_t>(pos);
- break;
- }
-
- if(mp_tell > mp_size)
- mp_tell = mp_size;
- }
- }
-
- inline void seeku(uint64_t pos, int rel_to)
- {
- seek(static_cast<long>(pos), rel_to);
- }
-
- size_t read(void *buf, size_t num, size_t size)
- {
- if(fp)
- return std::fread(buf, num, size, fp);
- else
- {
- size_t pos = 0;
- size_t maxSize = static_cast<size_t>(size * num);
-
- while((pos < maxSize) && (mp_tell < mp_size))
- {
- reinterpret_cast<unsigned char *>(buf)[pos] = reinterpret_cast<unsigned const char *>(mp)[mp_tell];
- mp_tell++;
- pos++;
- }
-
- return pos;
- }
- }
-
- int getc()
- {
- if(fp)
- return std::getc(fp);
- else
- {
- if(mp_tell >= mp_size) return -1;
- int x = reinterpret_cast<unsigned const char *>(mp)[mp_tell];
- mp_tell++;
- return x;
- }
- }
-
- size_t tell()
- {
- if(fp)
- return static_cast<size_t>(std::ftell(fp));
- else
- return mp_tell;
- }
-
- void close()
- {
- if(fp) std::fclose(fp);
-
- fp = NULL;
- mp = NULL;
- mp_size = 0;
- mp_tell = 0;
- }
-
- bool isValid()
- {
- return (fp) || (mp);
- }
-
- bool eof()
- {
- if(fp)
- return (std::feof(fp) != 0);
- else
- return mp_tell >= mp_size;
- }
- std::string _fileName;
- std::FILE *fp;
- const void *mp;
- size_t mp_size;
- size_t mp_tell;
- };
-
// Persistent settings for each MIDI channel
struct MIDIchannel
{
- uint16_t portamento;
uint8_t bank_lsb, bank_msb;
uint8_t patch;
uint8_t volume, expression;
- uint8_t panning, vibrato, aftertouch, sustain;
+ uint8_t panning, vibrato, aftertouch;
+ uint16_t portamento;
+ bool sustain;
+ bool softPedal;
+ bool portamentoEnable;
+ int8_t portamentoSource; // note number or -1
+ double portamentoRate;
//! Per note Aftertouch values
uint8_t noteAftertouch[128];
//! Is note aftertouch has any non-zero value
@@ -524,8 +379,11 @@ public:
// Note vibrato (a part of Note Aftertouch feature)
uint8_t vibrato;
// Tone selected on noteon:
- int16_t tone;
- char ____padding2[4];
+ int16_t noteTone;
+ // Current tone (!= noteTone if gliding note)
+ double currentTone;
+ // Gliding rate
+ double glideRate;
// Patch selected on noteon; index to bank.ins[]
size_t midiins;
// Patch selected
@@ -604,6 +462,7 @@ public:
}
};
char ____padding2[5];
+ unsigned gliding_note_count;
NoteInfo activenotes[128];
struct activenoteiterator
@@ -707,7 +566,8 @@ public:
updateBendSensitivity();
volume = 100;
expression = 127;
- sustain = 0;
+ sustain = false;
+ softPedal = false;
vibrato = 0;
aftertouch = 0;
std::memset(noteAftertouch, 0, 128);
@@ -717,6 +577,9 @@ public:
vibdelay = 0;
panning = OPL_PANNING_BOTH;
portamento = 0;
+ portamentoEnable = false;
+ portamentoSource = -1;
+ portamentoRate = HUGE_VAL;
brightness = 127;
}
bool hasVibrato()
@@ -731,6 +594,7 @@ public:
MIDIchannel()
{
activenotes_clear();
+ gliding_note_count = 0;
reset();
}
};
@@ -751,8 +615,14 @@ public:
{
LocationData *prev, *next;
Location loc;
- bool sustained;
- char ____padding[7];
+ enum {
+ Sustain_None = 0x00,
+ Sustain_Pedal = 0x01,
+ Sustain_Sostenuto = 0x02,
+ Sustain_ANY = Sustain_Pedal | Sustain_Sostenuto,
+ };
+ uint8_t sustained;
+ char ____padding[6];
MIDIchannel::NoteInfo::Phys ins; // a copy of that in phys[]
//! Has fixed sustain, don't iterate "on" timeout
bool fixed_sustain;
@@ -807,130 +677,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
{
@@ -945,7 +705,6 @@ public:
bool LogarithmicVolumes;
int VolumeModel;
//unsigned int SkipForward;
- bool loopingIsEnabled;
int ScaleModulators;
bool fullRangeBrightnessCC74;
@@ -973,6 +732,17 @@ public:
std::vector<MIDIchannel> Ch;
bool cmf_percussion_mode;
+ uint8_t m_masterVolume;
+ uint8_t m_sysExDeviceId;
+
+ enum SynthMode
+ {
+ Mode_GM = 0x00,
+ Mode_GS = 0x01,
+ Mode_XG = 0x02,
+ Mode_GM2 = 0x04,
+ };
+ uint32_t m_synthMode;
MIDIEventHooks hooks;
@@ -987,34 +757,14 @@ private:
//! Counter of arpeggio processing
size_t m_arpeggioCounter;
+#if defined(ADLMIDI_AUDIO_TICK_HANDLER)
//! Audio tick counter
uint32_t m_audioTickCounter;
-
-#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
@@ -1022,90 +772,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(fileReader &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(fileReader &fr);
/**
* @brief Periodic tick handler.
@@ -1114,81 +823,172 @@ 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 Sets the Device identifier
+ * @param id 7-bit Device identifier
+ */
+ void setDeviceId(uint8_t id);
+
+ /**
+ * @brief System Exclusive message
+ * @param msg Raw SysEx Message
+ * @param size Length of SysEx message
+ * @return true if message was passed successfully. False on any errors
+ */
+ bool realTime_SysEx(const uint8_t *msg, size_t size);
+
+ /**
+ * @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);
+#endif
+
+private:
+ enum
+ {
+ Manufacturer_Roland = 0x41,
+ Manufacturer_Yamaha = 0x43,
+ Manufacturer_UniversalNonRealtime = 0x7E,
+ Manufacturer_UniversalRealtime = 0x7F
+ };
+ enum
+ {
+ RolandMode_Request = 0x11,
+ RolandMode_Send = 0x12
+ };
+ enum
+ {
+ RolandModel_GS = 0x42,
+ RolandModel_SC55 = 0x45,
+ YamahaModel_XG = 0x4C
+ };
+
+ bool doUniversalSysEx(unsigned dev, bool realtime, const uint8_t *data, size_t size);
+ bool doRolandSysEx(unsigned dev, const uint8_t *data, size_t size);
+ bool doYamahaSysEx(unsigned dev, const uint8_t *data, size_t size);
private:
enum
@@ -1200,7 +1000,7 @@ private:
Upd_All = Upd_Pan + Upd_Volume + Upd_Pitch,
Upd_Off = 0x20,
Upd_Mute = 0x40,
- Upt_OffMute = Upd_Off + Upd_Mute
+ Upd_OffMute = Upd_Off + Upd_Mute
};
void NoteUpdate(uint16_t MidCh,
@@ -1208,11 +1008,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;
@@ -1226,14 +1021,18 @@ private:
AdlChannel::LocationData *j,
MIDIchannel::activenoteiterator i);
void Panic();
- void KillSustainingNotes(int32_t MidCh = -1, int32_t this_adlchn = -1);
+ void KillSustainingNotes(int32_t MidCh = -1,
+ int32_t this_adlchn = -1,
+ uint8_t sustain_type = AdlChannel::LocationData::Sustain_ANY);
+ void MarkSostenutoNotes(int32_t MidCh = -1);
void SetRPN(unsigned MidCh, unsigned value, bool MSB);
- //void UpdatePortamento(unsigned MidCh)
+ void UpdatePortamento(unsigned MidCh);
void NoteUpdate_All(uint16_t MidCh, unsigned props_mask);
void NoteOff(uint16_t MidCh, uint8_t note);
void UpdateVibrato(double amount);
void UpdateArpeggio(double /*amount*/);
+ void UpdateGlide(double amount);
public:
uint64_t ChooseDevice(const std::string &name);
@@ -1258,7 +1057,9 @@ struct FourChars
};
*/
+#if defined(ADLMIDI_AUDIO_TICK_HANDLER)
extern void adl_audioTickHandler(void *instance, uint32_t chipId, uint32_t rate);
+#endif
extern int adlRefreshNumCards(ADL_MIDIPlayer *device);
diff --git a/src/adlmidi_sequencer.cpp b/src/adlmidi_sequencer.cpp
new file mode 100644
index 0000000..b8bf3dd
--- /dev/null
+++ b/src/adlmidi_sequencer.cpp
@@ -0,0 +1,151 @@
+/*
+ * 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
+
+// Rename class to avoid ABI collisions
+#define BW_MidiSequencer AdlMidiSequencer
+// Inlucde MIDI sequencer class implementation
+#include "midi_sequencer_impl.hpp"
+
+#include "adlmidi_private.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);
+ context->realTime_SysEx(msg, size);
+}
+
+
+/* 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
new file mode 100644
index 0000000..7d13262
--- /dev/null
+++ b/src/file_reader.hpp
@@ -0,0 +1,300 @@
+/*
+ * FileAndMemoryReader - a tiny helper to utify file reading from a disk and memory block
+ *
+ * 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 FILE_AND_MEM_READER_HHHH
+#define FILE_AND_MEM_READER_HHHH
+
+#include <string> // std::string
+#include <cstdio> // std::fopen, std::fread, std::fseek, std::ftell, std::fclose, std::feof
+#include <stdint.h> // uint*_t
+#include <stddef.h> // size_t and friends
+#ifdef _WIN32
+#define NOMINMAX 1
+#include <cstring> // std::strlen
+#include <windows.h> // MultiByteToWideChar
+#endif
+
+/**
+ * @brief A little class gives able to read filedata from disk and also from a memory segment
+ */
+class FileAndMemReader
+{
+ //! Currently loaded filename (empty for a memory blocks)
+ std::string m_file_name;
+ //! File reader descriptor
+ std::FILE *m_fp;
+
+ //! Memory pointer descriptor
+ const void *m_mp;
+ //! Size of memory block
+ size_t m_mp_size;
+ //! Cursor position in the memory block
+ size_t m_mp_tell;
+
+public:
+ /**
+ * @brief Relation direction
+ */
+ enum relTo
+ {
+ //! At begin position
+ SET = SEEK_SET,
+ //! At current position
+ CUR = SEEK_CUR,
+ //! At end position
+ END = SEEK_END
+ };
+
+ /**
+ * @brief C.O.: It's a constructor!
+ */
+ FileAndMemReader() :
+ m_fp(NULL),
+ m_mp(NULL),
+ m_mp_size(0),
+ m_mp_tell(0)
+ {}
+
+ /**
+ * @brief C.O.: It's a destructor!
+ */
+ ~FileAndMemReader()
+ {
+ close();
+ }
+
+ /**
+ * @brief Open file from a disk
+ * @param path Path to the file in UTF-8 (even on Windows!)
+ */
+ 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
+ wchar_t widePath[MAX_PATH];
+ int size = MultiByteToWideChar(CP_UTF8, 0, path, static_cast<int>(std::strlen(path)), widePath, MAX_PATH);
+ widePath[size] = '\0';
+ m_fp = _wfopen(widePath, L"rb");
+#endif
+ m_file_name = path;
+ m_mp = NULL;
+ m_mp_size = 0;
+ m_mp_tell = 0;
+ }
+
+ /**
+ * @brief Open file from memory block
+ * @param mem Pointer to the memory block
+ * @param lenght Size of given block
+ */
+ 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;
+ m_mp_tell = 0;
+ }
+
+ /**
+ * @brief Seek to given position
+ * @param pos Offset or position
+ * @param rel_to Relation (at begin, at current, or at end)
+ */
+ void seek(long pos, int rel_to)
+ {
+ if(!this->isValid())
+ return;
+
+ if(m_fp)//If a file
+ {
+ std::fseek(m_fp, pos, rel_to);
+ }
+ else//If a memory block
+ {
+ switch(rel_to)
+ {
+ case SET:
+ m_mp_tell = static_cast<size_t>(pos);
+ break;
+
+ case END:
+ m_mp_tell = m_mp_size - static_cast<size_t>(pos);
+ break;
+
+ case CUR:
+ m_mp_tell = m_mp_tell + static_cast<size_t>(pos);
+ break;
+ }
+
+ if(m_mp_tell > m_mp_size)
+ m_mp_tell = m_mp_size;
+ }
+ }
+
+ /**
+ * @brief Seek to given position (unsigned integer 64 as relation. Negative values not supported)
+ * @param pos Offset or position
+ * @param rel_to Relation (at begin, at current, or at end)
+ */
+ inline void seeku(uint64_t pos, int rel_to)
+ {
+ this->seek(static_cast<long>(pos), rel_to);
+ }
+
+ /**
+ * @brief Read the buffer from a file
+ * @param buf Pointer to the destination memory block
+ * @param num Number of elements
+ * @param size Size of one element
+ * @return Size
+ */
+ 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
+ {
+ size_t pos = 0;
+ size_t maxSize = static_cast<size_t>(size * num);
+
+ while((pos < maxSize) && (m_mp_tell < m_mp_size))
+ {
+ reinterpret_cast<uint8_t *>(buf)[pos] = reinterpret_cast<const uint8_t *>(m_mp)[m_mp_tell];
+ m_mp_tell++;
+ pos++;
+ }
+
+ return pos / num;
+ }
+ }
+
+ /**
+ * @brief Get one byte and seek forward
+ * @return Readed byte or EOF (a.k.a. -1)
+ */
+ int getc()
+ {
+ if(!this->isValid())
+ return -1;
+ if(m_fp)//If a file
+ {
+ return std::getc(m_fp);
+ }
+ else //If a memory block
+ {
+ if(m_mp_tell >= m_mp_size)
+ return -1;
+ int x = reinterpret_cast<const uint8_t *>(m_mp)[m_mp_tell];
+ m_mp_tell++;
+ return x;
+ }
+ }
+
+ /**
+ * @brief Returns current offset of cursor in a file
+ * @return Offset position
+ */
+ 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
+ return m_mp_tell;
+ }
+
+ /**
+ * @brief Close the file
+ */
+ void close()
+ {
+ if(m_fp)
+ std::fclose(m_fp);
+
+ m_fp = NULL;
+ m_mp = NULL;
+ m_mp_size = 0;
+ m_mp_tell = 0;
+ }
+
+ /**
+ * @brief Is file instance valid
+ * @return true if vaild
+ */
+ bool isValid()
+ {
+ return (m_fp) || (m_mp);
+ }
+
+ /**
+ * @brief Is End Of File?
+ * @return true if end of file was reached
+ */
+ bool eof()
+ {
+ if(!this->isValid())
+ return true;
+ if(m_fp)
+ return (std::feof(m_fp) != 0);
+ else
+ return m_mp_tell >= m_mp_size;
+ }
+
+ /**
+ * @brief Get a current file name
+ * @return File name of currently loaded file
+ */
+ const std::string &fileName()
+ {
+ 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 = this->tell();
+ seek(0l, FileAndMemReader::END);
+ size_t file_size = this->tell();
+ seek(static_cast<long>(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..afbffed
--- /dev/null
+++ b/src/midi_sequencer.h
@@ -0,0 +1,134 @@
+/*
+ * 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>
+
+/**
+ \brief Real-Time MIDI interface between Sequencer and the Synthesizer
+ */
+typedef struct
+{
+ /*! 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);
+ /*! MIDI event hook which catches all MIDI events */
+ RawEventHook onEvent;
+ /*! User data which will be passed through On-Event hook */
+ void *onEvent_userData;
+
+ /*! Library internal debug messages */
+ typedef void (*DebugMessageHook)(void *userdata, const char *fmt, ...);
+ /*! Debug message hook */
+ DebugMessageHook onDebugMessage;
+ /*! User data which will be passed through Debug Message hook */
+ void *onDebugMessage_userData;
+
+ /*! MIDI Run Time event calls user data */
+ void *rtUserData;
+
+
+ /***************************************************
+ * Standard MIDI events. All of them are required! *
+ ***************************************************/
+
+ /*! Note-On MIDI event */
+ typedef void (*RtNoteOn)(void *userdata, uint8_t channel, uint8_t note, uint8_t velocity);
+ /*! Note-On MIDI event hook */
+ RtNoteOn rt_noteOn;
+
+ /*! Note-Off MIDI event */
+ typedef void (*RtNoteOff)(void *userdata, uint8_t channel, uint8_t note);
+ /*! Note-Off MIDI event hook */
+ RtNoteOff rt_noteOff;
+
+ /*! Note aftertouch MIDI event */
+ typedef void (*RtNoteAfterTouch)(void *userdata, uint8_t channel, uint8_t note, uint8_t atVal);
+ /*! Note aftertouch MIDI event hook */
+ RtNoteAfterTouch rt_noteAfterTouch;
+
+ /*! Channel aftertouch MIDI event */
+ typedef void (*RtChannelAfterTouch)(void *userdata, uint8_t channel, uint8_t atVal);
+ /*! Channel aftertouch MIDI event hook */
+ RtChannelAfterTouch rt_channelAfterTouch;
+
+ /*! Controller change MIDI event */
+ typedef void (*RtControlerChange)(void *userdata, uint8_t channel, uint8_t type, uint8_t value);
+ /*! Controller change MIDI event hook */
+ RtControlerChange rt_controllerChange;
+
+ /*! Patch change MIDI event */
+ typedef void (*RtPatchChange)(void *userdata, uint8_t channel, uint8_t patch);
+ /*! Patch change MIDI event hook */
+ RtPatchChange rt_patchChange;
+
+ /*! Pitch bend MIDI event */
+ typedef void (*RtPitchBend)(void *userdata, uint8_t channel, uint8_t msb, uint8_t lsb);
+ /*! Pitch bend MIDI event hook */
+ RtPitchBend rt_pitchBend;
+
+ /*! System Exclusive MIDI event */
+ typedef void (*RtSysEx)(void *userdata, const uint8_t *msg, size_t size);
+ /*! System Exclusive MIDI event hook */
+ RtSysEx rt_systemExclusive;
+
+
+ /*******************
+ * Optional events *
+ *******************/
+
+ /*! Device Switch MIDI event */
+ typedef void (*RtDeviceSwitch)(void *userdata, size_t track, const char *data, size_t length);
+ /*! Device Switch MIDI event hook */
+ RtDeviceSwitch rt_deviceSwitch;
+
+ /*! Get the channels offset for current MIDI device */
+ typedef uint64_t (*RtCurrentDevice)(void *userdata, size_t track);
+ /*! Get the channels offset for current MIDI device hook. Returms multiple to 16 value. */
+ RtCurrentDevice rt_currentDevice;
+
+
+ /******************************************
+ * NonStandard events. There are optional *
+ ******************************************/
+
+ /*! [Non-Standard] Pass raw OPL3 data to the chip (when playing IMF files) */
+ typedef void (*RtRawOPL)(void *userdata, uint8_t reg, uint8_t value);
+ /*! [Non-Standard] Pass raw OPL3 data to the chip hook */
+ RtRawOPL rt_rawOPL;
+
+} BW_MidiRtInterface;
+
+#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..89b4619
--- /dev/null
+++ b/src/midi_sequencer.hpp
@@ -0,0 +1,501 @@
+/*
+ * 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;
+ //! Reserved
+ 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
+ {
+ //! Delay to next event in a track
+ uint64_t delay;
+ //! Last handled event type
+ int32_t lastHandledEvent;
+ //! Reserved
+ char __padding2[4];
+ //! MIDI Events queue position iterator
+ MidiTrackQueue::iterator pos;
+
+ TrackInfo() :
+ 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, int32_t &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 *intrf);
+
+ /**
+ * @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..501de8f
--- /dev/null
+++ b/src/midi_sequencer_impl.hpp
@@ -0,0 +1,1852 @@
+/*
+ * 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 <iterator> // std::back_inserter
+#include <algorithm> // std::copy
+#include <set>
+#include <assert.h>
+
+#if defined(_WIN32) && !defined(__WATCOMC__)
+# ifdef _MSC_VER
+# ifdef _WIN64
+typedef __int64 ssize_t;
+# else
+typedef __int32 ssize_t;
+# endif
+# else
+# ifdef _WIN64
+typedef int64_t ssize_t;
+# else
+typedef int32_t ssize_t;
+# endif
+# endif
+#endif
+
+#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 uint8_t *data = reinterpret_cast<const uint8_t *>(buffer);
+
+ for(size_t 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 uint8_t *data = reinterpret_cast<const uint8_t *>(buffer);
+
+ for(size_t n = 0; n < nbytes; ++n)
+ result = result + static_cast<uint64_t>(data[n] << (n * 8));
+
+ 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 sysEx;
+ EvtArr metas;
+ EvtArr noteOffs;
+ EvtArr controllers;
+ EvtArr anyOther;
+
+ for(size_t i = 0; i < events.size(); i++)
+ {
+ if(events[i].type == MidiEvent::T_NOTEOFF)
+ {
+ if(noteOffs.capacity() == 0)
+ noteOffs.reserve(events.size());
+ noteOffs.push_back(events[i]);
+ }
+ else if(events[i].type == MidiEvent::T_SYSEX ||
+ events[i].type == MidiEvent::T_SYSEX2)
+ {
+ if(sysEx.capacity() == 0)
+ sysEx.reserve(events.size());
+ sysEx.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))
+ {
+ if(controllers.capacity() == 0)
+ controllers.reserve(events.size());
+ controllers.push_back(events[i]);
+ }
+ else if((events[i].type == MidiEvent::T_SPECIAL) && (events[i].subtype == MidiEvent::ST_MARKER))
+ {
+ if(metas.capacity() == 0)
+ metas.reserve(events.size());
+ metas.push_back(events[i]);
+ }
+ else
+ {
+ if(anyOther.capacity() == 0)
+ anyOther.reserve(events.size());
+ 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();
+ if(!sysEx.empty())
+ events.insert(events.end(), sysEx.begin(), sysEx.end());
+ if(!noteOffs.empty())
+ events.insert(events.end(), noteOffs.begin(), noteOffs.end());
+ if(!metas.empty())
+ events.insert(events.end(), metas.begin(), metas.end());
+ if(!controllers.empty())
+ events.insert(events.end(), controllers.begin(), controllers.end());
+ if(!anyOther.empty())
+ 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 *intrf)
+{
+ // Interface must NOT be NULL
+ assert(intrf);
+
+ //Note ON hook is REQUIRED
+ assert(intrf->rt_noteOn);
+ //Note OFF hook is REQUIRED
+ assert(intrf->rt_noteOff);
+ //Note Aftertouch hook is REQUIRED
+ assert(intrf->rt_noteAfterTouch);
+ //Channel Aftertouch hook is REQUIRED
+ assert(intrf->rt_channelAfterTouch);
+ //Controller change hook is REQUIRED
+ assert(intrf->rt_controllerChange);
+ //Patch change hook is REQUIRED
+ assert(intrf->rt_patchChange);
+ //Pitch bend hook is REQUIRED
+ assert(intrf->rt_pitchBend);
+ //System Exclusive hook is REQUIRED
+ assert(intrf->rt_systemExclusive);
+
+ m_interface = intrf;
+}
+
+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, m_currentPosition.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;
+ }
+ evt.type = MidiEvent::T_SYSEX;
+ evt.data.clear();
+ evt.data.push_back(byte);
+ std::copy(ptr, ptr + length, std::back_inserter(evt.data));
+ 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 track, const BW_MidiSequencer::MidiEvent &evt, int32_t &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[track][CurrentPosition.track[track].ptr]:0, length );
+ //UI.PrintLn("SysEx %02X: %u bytes", byte, length/*, data.c_str()*/);
+#if 0
+ std::fputs("SysEx:", stderr);
+ for(size_t i = 0; i < evt.data.size(); ++i)
+ std::fprintf(stderr, " %02X", evt.data[i]);
+ std::fputc('\n', stderr);
+#endif
+ 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->onDebugMessage)
+ m_interface->onDebugMessage(m_interface->onDebugMessage_userData, "Switching another device: %s", data.c_str());
+ if(m_interface->rt_deviceSwitch)
+ m_interface->rt_deviceSwitch(m_interface->rtUserData, track, 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 MIDI loop Start point
+ {
+ m_loopStart = true;
+ return;
+ }
+
+ if(evtype == MidiEvent::ST_LOOPEND) // Special non-spec MIDI loop End point
+ {
+ 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[track].status | 0x80);
+ // CurrentPosition.track[track].ptr--;
+ // }
+
+ if(evt.type == MidiEvent::T_SYSCOMSNGSEL ||
+ evt.type == MidiEvent::T_SYSCOMSPOSPTR)
+ return;
+
+ /*UI.PrintLn("@%X Track %u: %02X %02X",
+ CurrentPosition.track[track].ptr-1, (unsigned)track, byte,
+ TrackData[track][CurrentPosition.track[track].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, track);
+ 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(fsize < headerSize)
+ {
+ m_errorString = "Unexpected end of file at header!\n";
+ return false;
+ }
+
+ 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;
+ }
+ fsize = fr.read(mus, 1, mus_len);
+ if(fsize < mus_len)
+ {
+ fr.close();
+ m_errorString = "Failed to read MUS file data!\n";
+ return false;
+ }
+
+ //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;
+ }
+ fsize = fr.read(mus, 1, mus_len);
+ if(fsize < mus_len)
+ {
+ fr.close();
+ m_errorString = "Failed to read XMI file data!\n";
+ return false;
+ }
+
+ //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
+ fsize = fr.read(headerBuf, 1, 6);
+ if(fsize < 6)
+ {
+ fr.close();
+ m_errorString = "Unexpected file ending on attempt to read CTMF header!";
+ return false;
+ }
+
+ //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
+ fsize = fr.read(headerBuf, 1, 4);
+ if(fsize < 4)
+ {
+ fr.close();
+ m_errorString = "Unexpected file ending on attempt to read CMF instruments block header!";
+ return false;
+ }
+
+ 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;
+ fsize = fr.read(inst.data, 1, 16);
+ if(fsize < 16)
+ {
+ fr.close();
+ m_errorString = "Unexpected file ending on attempt to read CMF instruments raw data!";
+ return false;
+ }
+ 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, 1, 6);
+ 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
+ {
+ fsize = fr.read(headerBuf, 1, 8);
+ if((fsize < 8) || (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);
+ if(fsize < trackLength)
+ {
+ fr.close();
+ m_errorString = fr.fileName() + ": Unexpected file ending while getting raw track data!\n";
+ return false;
+ }
+ 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;
+}