From dff692fff4a9908094312cda6e4c16d98431babf Mon Sep 17 00:00:00 2001 From: JP Cimalando Date: Tue, 19 Jun 2018 16:34:52 +0200 Subject: polyphonic portamento --- src/adlmidi_midiplay.cpp | 95 ++++++++++++++++++++++++++++++++++++------------ src/adlmidi_private.hpp | 19 +++++++--- 2 files changed, 85 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index 89c1a73..6dad2a9 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -1209,11 +1209,26 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) 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; + bool portamentoEnable = midiChan.portamentoEnable && + !isPercussion && !isXgPercussion; + // Record the last note on MIDI channel as source of portamento + 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 = midiChan.portamentoRate; + } + for(unsigned ccount = 0; ccount < MIDIchannel::NoteInfo::MaxNumPhysChans; ++ccount) { int32_t c = adlchannel[ccount]; @@ -1222,6 +1237,7 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) uint16_t chipChan = static_cast(adlchannel[ccount]); ir.first->phys_ensure_find_or_create(chipChan)->assign(voices[ccount]); } + NoteUpdate(channel, ir.first, Upd_All | Upd_Patch); return true; } @@ -1281,17 +1297,17 @@ void MIDIplay::realTime_Controller(uint8_t channel, uint8_t type, uint8_t value) case 5: // Set portamento msb Ch[channel].portamento = static_cast((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 @@ -1324,7 +1340,6 @@ 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); @@ -1439,12 +1454,44 @@ void MIDIplay::realTime_panic() KillSustainingNotes(-1, -1); } -void MIDIplay::AudioTick(uint32_t chipId, uint32_t /*rate*/) +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; + + for(unsigned channel = 0; channel < 16; ++channel) + { + MIDIchannel &midiChan = Ch[channel]; + for(MIDIchannel::activenoteiterator it = midiChan.activenotes_begin(); + it; ++it) + { + double finalTone = it->noteTone; + double previousTone = it->currentTone; + + bool directionUp = previousTone < finalTone; + double toneIncr = portamentoDelta * (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(channel, it, Upd_Pitch); + } + } + } + } } void MIDIplay::NoteUpdate(uint16_t MidCh, @@ -1453,7 +1500,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(info.midiins); const adlinsdata2 &ains = *info.ains; @@ -1501,7 +1549,7 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, 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()) { @@ -1525,7 +1573,7 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, if(d) d->sustained = true; // 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 @@ -1653,10 +1701,10 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, #else # define BEND_COEFFICIENT 172.4387 #endif - opl.NoteOn(c, BEND_COEFFICIENT * std::exp(0.057762265 * (static_cast(tone) + bend + phase))); + 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); } } } @@ -2314,11 +2362,11 @@ void MIDIplay::KillOrEvacuate(size_t from_channel, { hooks.onNote(hooks.onNote_userData, (int)from_channel, - i->tone, + i->noteTone, static_cast(i->midiins), 0, 0.0); hooks.onNote(hooks.onNote_userData, (int)c, - i->tone, + i->noteTone, static_cast(i->midiins), i->vol, 0.0); } @@ -2423,15 +2471,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::exp2(-0.062 * (1.0 / 128) * midival); + Ch[MidCh].portamentoRate = rate; +} void MIDIplay::NoteUpdate_All(uint16_t MidCh, unsigned props_mask) { diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp index 0e63b5a..93817cd 100644 --- a/src/adlmidi_private.hpp +++ b/src/adlmidi_private.hpp @@ -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() {} @@ -496,11 +496,14 @@ public: // 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; + uint16_t portamento; + 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 +527,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 @@ -717,6 +723,9 @@ public: vibdelay = 0; panning = OPL_PANNING_BOTH; portamento = 0; + portamentoEnable = false; + portamentoSource = -1; + portamentoRate = HUGE_VAL; brightness = 127; } bool hasVibrato() @@ -1228,7 +1237,7 @@ private: void Panic(); void KillSustainingNotes(int32_t MidCh = -1, int32_t this_adlchn = -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); -- cgit v1.2.3 From 2f26855c27aaffee0472edf43dbc5ac5ca1a5162 Mon Sep 17 00:00:00 2001 From: JP Cimalando Date: Tue, 19 Jun 2018 21:15:45 +0200 Subject: allow portamento to be updated by MIDI::Tick --- src/adlmidi_midiplay.cpp | 66 +++++++++++++++++++++++++++++------------------- src/adlmidi_private.cpp | 2 ++ src/adlmidi_private.hpp | 7 +++++ 3 files changed, 49 insertions(+), 26 deletions(-) (limited to 'src') diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index 6dad2a9..f52d936 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -680,8 +680,10 @@ bool MIDIplay::buildTrackData() MIDIplay::MIDIplay(unsigned long sampleRate): cmf_percussion_mode(false), - m_arpeggioCounter(0), - m_audioTickCounter(0) + m_arpeggioCounter(0) +#if defined(ADLMIDI_AUDIO_TICK_HANDLER) + , m_audioTickCounter(0) +#endif #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER , fullSongTimeLength(0.0), postSongWaitDelay(1.0), @@ -825,6 +827,9 @@ double MIDIplay::Tick(double s, double granularity) UpdateVibrato(s); UpdateArpeggio(s); +#if !defined(ADLMIDI_AUDIO_TICK_HANDLER) + UpdateGlide(s); +#endif if(CurrentPositionNew.wait < 0.0)//Avoid negative delay value! return 0.0; @@ -839,6 +844,9 @@ void MIDIplay::TickIteratos(double s) ch[c].AddAge(static_cast(s * 1000.0)); UpdateVibrato(s); UpdateArpeggio(s); +#if !defined(ADLMIDI_AUDIO_TICK_HANDLER) + UpdateGlide(s); +#endif } #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER @@ -1454,6 +1462,7 @@ void MIDIplay::realTime_panic() KillSustainingNotes(-1, -1); } +#if defined(ADLMIDI_AUDIO_TICK_HANDLER) void MIDIplay::AudioTick(uint32_t chipId, uint32_t rate) { if(chipId != 0) // do first chip ticks only @@ -1467,32 +1476,10 @@ void MIDIplay::AudioTick(uint32_t chipId, uint32_t rate) if(tickNumber % portamentoInterval == 0) { double portamentoDelta = timeDelta * portamentoInterval; - - for(unsigned channel = 0; channel < 16; ++channel) - { - MIDIchannel &midiChan = Ch[channel]; - for(MIDIchannel::activenoteiterator it = midiChan.activenotes_begin(); - it; ++it) - { - double finalTone = it->noteTone; - double previousTone = it->currentTone; - - bool directionUp = previousTone < finalTone; - double toneIncr = portamentoDelta * (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(channel, it, Upd_Pitch); - } - } - } + UpdateGlide(portamentoDelta); } } +#endif void MIDIplay::NoteUpdate(uint16_t MidCh, MIDIplay::MIDIchannel::activenoteiterator i, @@ -2601,6 +2588,33 @@ retry_arpeggio: } } +void MIDIplay::UpdateGlide(double amount) +{ + for(unsigned channel = 0; channel < 16; ++channel) + { + MIDIchannel &midiChan = Ch[channel]; + 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(channel, it, Upd_Pitch); + } + } + } +} + #ifndef ADLMIDI_DISABLE_CPP_EXTRAS 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(instance)->AudioTick(chipId, rate); } +#endif int adlRefreshNumCards(ADL_MIDIPlayer *device) { diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp index 93817cd..7aad63b 100644 --- a/src/adlmidi_private.hpp +++ b/src/adlmidi_private.hpp @@ -996,8 +996,10 @@ private: //! Counter of arpeggio processing size_t m_arpeggioCounter; +#if defined(ADLMIDI_AUDIO_TICK_HANDLER) //! Audio tick counter uint32_t m_audioTickCounter; +#endif #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER std::vector > TrackData; @@ -1196,8 +1198,10 @@ public: void realTime_panic(); +#if defined(ADLMIDI_AUDIO_TICK_HANDLER) // Audio rate tick handler void AudioTick(uint32_t chipId, uint32_t rate); +#endif private: enum @@ -1243,6 +1247,7 @@ private: void UpdateVibrato(double amount); void UpdateArpeggio(double /*amount*/); + void UpdateGlide(double amount); public: uint64_t ChooseDevice(const std::string &name); @@ -1267,7 +1272,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); -- cgit v1.2.3 From 94470c7e544a8086b02aa12e7361b64bacb44ee6 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Tue, 19 Jun 2018 22:42:42 +0300 Subject: Fixed DJGPP build --- src/adlmidi_midiplay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index f52d936..0a4639a 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -2463,7 +2463,7 @@ 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::exp2(-0.062 * (1.0 / 128) * midival); + rate = 350.0 * std::pow(2.0, -0.062 * (1.0 / 128) * midival); Ch[MidCh].portamentoRate = rate; } -- cgit v1.2.3 From 6586740caacfb17210a26e9618c31c54ec0da703 Mon Sep 17 00:00:00 2001 From: JP Cimalando Date: Tue, 19 Jun 2018 22:13:40 +0200 Subject: avoid portamento work when no notes have it on channel --- src/adlmidi_midiplay.cpp | 14 ++++++++++++-- src/adlmidi_private.hpp | 2 ++ 2 files changed, 14 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index 0a4639a..2f186a6 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -1225,7 +1225,9 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) ir.first->chip_channels_count = 0; int8_t currentPortamentoSource = midiChan.portamentoSource; - bool portamentoEnable = midiChan.portamentoEnable && + double currentPortamentoRate = midiChan.portamentoRate; + bool portamentoEnable = + midiChan.portamentoEnable && currentPortamentoRate != HUGE_VAL && !isPercussion && !isXgPercussion; // Record the last note on MIDI channel as source of portamento midiChan.portamentoSource = portamentoEnable ? (int8_t)note : (int8_t)-1; @@ -1234,7 +1236,8 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) if (portamentoEnable && currentPortamentoSource >= 0) { ir.first->currentTone = currentPortamentoSource; - ir.first->glideRate = midiChan.portamentoRate; + ir.first->glideRate = currentPortamentoRate; + ++midiChan.gliding_note_count; } for(unsigned ccount = 0; ccount < MIDIchannel::NoteInfo::MaxNumPhysChans; ++ccount) @@ -1697,7 +1700,11 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, } if(info.chip_channels_count == 0) + { + if(i->glideRate != HUGE_VAL) + --Ch[MidCh].gliding_note_count; Ch[MidCh].activenotes_erase(i); + } } #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER @@ -2593,6 +2600,9 @@ void MIDIplay::UpdateGlide(double amount) for(unsigned channel = 0; channel < 16; ++channel) { MIDIchannel &midiChan = Ch[channel]; + if(midiChan.gliding_note_count == 0) + continue; + for(MIDIchannel::activenoteiterator it = midiChan.activenotes_begin(); it; ++it) { diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp index 7aad63b..fda629d 100644 --- a/src/adlmidi_private.hpp +++ b/src/adlmidi_private.hpp @@ -610,6 +610,7 @@ public: } }; char ____padding2[5]; + unsigned gliding_note_count; NoteInfo activenotes[128]; struct activenoteiterator @@ -740,6 +741,7 @@ public: MIDIchannel() { activenotes_clear(); + gliding_note_count = 0; reset(); } }; -- cgit v1.2.3 From 8326a355c45d1d038934264be5f00c7dc5ac33c7 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Wed, 20 Jun 2018 19:11:51 +0300 Subject: Move file reader into the separated header file --- src/adlmidi_load.cpp | 22 ++-- src/adlmidi_private.hpp | 155 +---------------------------- src/file_reader.hpp | 259 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 273 insertions(+), 163 deletions(-) create mode 100644 src/file_reader.hpp (limited to 'src') diff --git a/src/adlmidi_load.cpp b/src/adlmidi_load.cpp index 7f69e25..10bea21 100644 --- a/src/adlmidi_load.cpp +++ b/src/adlmidi_load.cpp @@ -60,14 +60,14 @@ uint64_t MIDIplay::ReadLEint(const void *buffer, size_t nbytes) 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); } @@ -194,7 +194,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; @@ -295,7 +295,7 @@ bool MIDIplay::LoadBank(MIDIplay::fileReader &fr) #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER bool MIDIplay::LoadMIDI(const std::string &filename) { - fileReader file; + FileAndMemReader file; file.openFile(filename.c_str()); if(!LoadMIDI(file)) return false; @@ -304,12 +304,12 @@ bool MIDIplay::LoadMIDI(const std::string &filename) bool MIDIplay::LoadMIDI(const void *data, size_t size) { - fileReader file; + FileAndMemReader file; file.openData(data, size); return LoadMIDI(file); } -bool MIDIplay::LoadMIDI(MIDIplay::fileReader &fr) +bool MIDIplay::LoadMIDI(FileAndMemReader &fr) { size_t fsize; ADL_UNUSED(fsize); @@ -406,7 +406,7 @@ riffskip: if(std::memcmp(HeaderBuf + 8, "XDIR", 4) != 0) { fr.close(); - errorStringOut = fr._fileName + ": Invalid format\n"; + errorStringOut = fr.fileName() + ": Invalid format\n"; return false; } @@ -570,7 +570,7 @@ riffskip: if(std::memcmp(HeaderBuf, "MThd\0\0\0\6", 8) != 0) { fr.close(); - errorStringOut = fr._fileName + ": Invalid format, Header signature is unknown!\n"; + errorStringOut = fr.fileName() + ": Invalid format, Header signature is unknown!\n"; return false; } @@ -667,7 +667,7 @@ riffskip: if(std::memcmp(HeaderBuf, "MTrk", 4) != 0) { fr.close(); - errorStringOut = fr._fileName + ": Invalid format, MTrk signature is not found!\n"; + errorStringOut = fr.fileName() + ": Invalid format, MTrk signature is not found!\n"; return false; } TrackLength = (size_t)ReadBEint(HeaderBuf + 4, 4); @@ -703,14 +703,14 @@ riffskip: if(totalGotten == 0) { - errorStringOut = fr._fileName + ": Empty track data"; + errorStringOut = fr.fileName() + ": Empty track data"; return false; } //Build new MIDI events table if(!buildTrackData()) { - errorStringOut = fr._fileName + ": MIDI data parsing error has occouped!\n" + errorString; + errorStringOut = fr.fileName() + ": MIDI data parsing error has occouped!\n" + errorString; return false; } diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp index fda629d..13160b2 100644 --- a/src/adlmidi_private.hpp +++ b/src/adlmidi_private.hpp @@ -131,6 +131,7 @@ typedef int32_t ssize_t; #define INT32_MAX 0x7fffffff #endif +#include "file_reader.hpp" #include "fraction.hpp" #ifndef ADLMIDI_HW_OPL #include "chips/opl_chip_base.h" @@ -343,156 +344,6 @@ 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(pos); - break; - - case END: - mp_tell = mp_size - static_cast(pos); - break; - - case CUR: - mp_tell = mp_tell + static_cast(pos); - break; - } - - if(mp_tell > mp_size) - mp_tell = mp_size; - } - } - - inline void seeku(uint64_t pos, int rel_to) - { - seek(static_cast(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 * num); - - while((pos < maxSize) && (mp_tell < mp_size)) - { - reinterpret_cast(buf)[pos] = reinterpret_cast(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(mp)[mp_tell]; - mp_tell++; - return x; - } - } - - size_t tell() - { - if(fp) - return static_cast(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 { @@ -1113,12 +964,12 @@ public: bool LoadBank(const std::string &filename); bool LoadBank(const void *data, size_t size); - bool LoadBank(fileReader &fr); + bool LoadBank(FileAndMemReader &fr); #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER bool LoadMIDI(const std::string &filename); bool LoadMIDI(const void *data, size_t size); - bool LoadMIDI(fileReader &fr); + bool LoadMIDI(FileAndMemReader &fr); /** * @brief Periodic tick handler. diff --git a/src/file_reader.hpp b/src/file_reader.hpp new file mode 100644 index 0000000..ce4307b --- /dev/null +++ b/src/file_reader.hpp @@ -0,0 +1,259 @@ +/* + * FileAndMemoryReader - a tiny helper to utify file reading from a disk and memory block + * + * Copyright (c) 2015-2018 Vitaly Novichkov + * + * 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 +#include +#ifdef _WIN32 +#include // 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 = 0, + //! At current position + CUR = 1, + //! At end position + END = 2 + }; + + /** + * @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 !defined(_WIN32) || defined(__WATCOMC__) + m_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 + 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) + { + 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(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(pos); + break; + + case END: + m_mp_tell = m_mp_size - static_cast(pos); + break; + + case CUR: + m_mp_tell = m_mp_tell + static_cast(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(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(m_fp) + return std::fread(buf, num, size, m_fp); + else + { + size_t pos = 0; + size_t maxSize = static_cast(size * num); + + while((pos < maxSize) && (m_mp_tell < m_mp_size)) + { + reinterpret_cast(buf)[pos] = reinterpret_cast(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(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(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(m_fp)//If a file + return static_cast(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(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; + } + +}; -- cgit v1.2.3 From a0fa0cded46bdc94293f02d4070a69bcae5478bb Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Wed, 20 Jun 2018 19:17:01 +0300 Subject: Avoid possible failure to compile for Windows --- src/file_reader.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/file_reader.hpp b/src/file_reader.hpp index ce4307b..e0664b4 100644 --- a/src/file_reader.hpp +++ b/src/file_reader.hpp @@ -83,14 +83,14 @@ public: */ void openFile(const char *path) { - #if !defined(_WIN32) || defined(__WATCOMC__) +#if !defined(_WIN32) || defined(__WATCOMC__) m_fp = std::fopen(path, "rb"); - #else +#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 + m_fp = _wfopen(widePath, L"rb"); +#endif m_file_name = path; m_mp = NULL; m_mp_size = 0; -- cgit v1.2.3 From 252e65097e9b8a815485fc03e4358698803f1a73 Mon Sep 17 00:00:00 2001 From: JP Cimalando Date: Wed, 20 Jun 2018 21:45:54 +0200 Subject: basic framework of sysex handling and sequencer support --- src/adlmidi.cpp | 21 ++++++ src/adlmidi_midiplay.cpp | 178 +++++++++++++++++++++++++++++++++++++++++++++++ src/adlmidi_private.hpp | 29 ++++++++ 3 files changed, 228 insertions(+) (limited to 'src') diff --git a/src/adlmidi.cpp b/src/adlmidi.cpp index 9210b5b..e3d2326 100644 --- a/src/adlmidi.cpp +++ b/src/adlmidi.cpp @@ -68,6 +68,17 @@ 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; + MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + if(!play) + return -1; + play->setDeviceId(id); + return 0; +} + ADLMIDI_EXPORT int adl_setNumChips(ADL_MIDIPlayer *device, int numCards) { if(device == NULL) @@ -1267,3 +1278,13 @@ ADLMIDI_EXPORT void adl_rt_bankChange(struct ADL_MIDIPlayer *device, ADL_UInt8 c return; player->realTime_BankChange(channel, (uint16_t)bank); } + +ADLMIDI_EXPORT int adl_rt_systemExclusive(struct ADL_MIDIPlayer *device, const ADL_UInt8 *msg, unsigned size) +{ + if(!device) + return -1; + MIDIplay *player = reinterpret_cast(device->adl_midiPlayer); + if(!player) + return -1; + return player->realTime_SysEx(msg, size); +} diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index 2f186a6..e0cacfa 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -680,6 +680,7 @@ bool MIDIplay::buildTrackData() MIDIplay::MIDIplay(unsigned long sampleRate): cmf_percussion_mode(false), + m_sysExDeviceId(0), m_arpeggioCounter(0) #if defined(ADLMIDI_AUDIO_TICK_HANDLER) , m_audioTickCounter(0) @@ -1459,6 +1460,172 @@ 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, unsigned 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, unsigned 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 + /*TODO*/ + return true; + case (0 << 16) | 0x0902: // GM System Off + /*TODO*/ + return true; + case (1 << 16) | 0x0401: // MIDI Master Volume + if(size != 2) + break; + unsigned volume = + (((unsigned)data[0] & 0x7F)) | + (((unsigned)data[1] & 0x7F) << 7); + /*TODO*/ + (void)volume; + return true; + } + + return false; +} + +bool MIDIplay::doRolandSysEx(unsigned dev, const uint8_t *data, unsigned 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(unsigned i = 0; i < size; ++i) + checkvalue += data[i] & 0x7F; + checkvalue = (128 - (checkvalue & 127)) & 127; + if(checkvalue != checksum) + return false; + } +#endif + + unsigned address = + (((unsigned)data[0] & 0x7F) << 16) | + (((unsigned)data[1] & 0x7F) << 8) | + (((unsigned)data[2] & 0x7F)); + data += 3; + size -= 3; + + if(mode != RolandMode_Send) // don't have MIDI-Out reply ability + return false; + + switch((model << 24) | address) + { + case (RolandModel_GS << 24) | 0x00007F: // System Mode Set + { + if(size != 1 || (dev & 0xF0) != 0x10) + break; + unsigned mode = data[0] & 0x7F; + /*TODO*/ + (void)mode; + return true; + } + case (RolandModel_GS << 24) | 0x40007F: // Mode Set + { + if(size != 1 || (dev & 0xF0) != 0x10) + break; + unsigned value = data[0] & 0x7F; + /*TODO*/ + (void)value; + return true; + } + } + + return false; +} + +bool MIDIplay::doYamahaSysEx(unsigned dev, const uint8_t *data, unsigned 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; + /*TODO*/ + (void)value; + return true; + } + + break; + } + } + + return false; +} + void MIDIplay::realTime_panic() { Panic(); @@ -1845,6 +2012,10 @@ MIDIplay::MidiEvent MIDIplay::parseEvent(uint8_t **pptr, uint8_t *end, int &stat 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; } @@ -2067,6 +2238,13 @@ void MIDIplay::HandleEvent(size_t tk, const MIDIplay::MidiEvent &evt, int &statu { //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()*/); +#if 0 + fputs("SysEx:", stderr); + for(size_t i = 0; i < evt.data.size(); ++i) + fprintf(stderr, " %02X", evt.data[i]); + fputc('\n', stderr); +#endif + realTime_SysEx(evt.data.data(), (unsigned)evt.data.size()); return; } diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp index fda629d..4b3ab10 100644 --- a/src/adlmidi_private.hpp +++ b/src/adlmidi_private.hpp @@ -108,6 +108,7 @@ typedef int32_t ssize_t; #include #include +#include /* * Workaround for some compilers are has no those macros in their headers! @@ -984,6 +985,7 @@ public: std::vector Ch; bool cmf_percussion_mode; + uint8_t m_sysExDeviceId; MIDIEventHooks hooks; @@ -1198,6 +1200,9 @@ public: void realTime_BankChangeMSB(uint8_t channel, uint8_t msb); void realTime_BankChange(uint8_t channel, uint16_t bank); + void setDeviceId(uint8_t id); + bool realTime_SysEx(const uint8_t *msg, unsigned size); + void realTime_panic(); #if defined(ADLMIDI_AUDIO_TICK_HANDLER) @@ -1205,6 +1210,30 @@ public: 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, unsigned size); + bool doRolandSysEx(unsigned dev, const uint8_t *data, unsigned size); + bool doYamahaSysEx(unsigned dev, const uint8_t *data, unsigned size); + private: enum { -- cgit v1.2.3 From 9f0f9e4e374798851da39335406ac4bafad81297 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Thu, 21 Jun 2018 04:23:14 +0300 Subject: Move MIDI sequencer into completely separated class TODO: - implement C bindings for most of class functions - test it in work on any different synthesizer --- src/adlmidi.cpp | 246 +++--- src/adlmidi_load.cpp | 450 ++--------- src/adlmidi_midiplay.cpp | 1292 +------------------------------ src/adlmidi_mus2mid.c | 451 ----------- src/adlmidi_mus2mid.h | 49 -- src/adlmidi_private.hpp | 370 +++------ src/adlmidi_sequencer.cpp | 153 ++++ src/adlmidi_xmi2mid.c | 1106 --------------------------- src/adlmidi_xmi2mid.h | 60 -- src/cvt_mus2mid.hpp | 461 +++++++++++ src/cvt_xmi2mid.hpp | 1123 +++++++++++++++++++++++++++ src/file_reader.hpp | 37 + src/midi_sequencer.h | 91 +++ src/midi_sequencer.hpp | 496 ++++++++++++ src/midi_sequencer_impl.hpp | 1780 +++++++++++++++++++++++++++++++++++++++++++ 15 files changed, 4504 insertions(+), 3661 deletions(-) delete mode 100644 src/adlmidi_mus2mid.c delete mode 100644 src/adlmidi_mus2mid.h create mode 100644 src/adlmidi_sequencer.cpp delete mode 100644 src/adlmidi_xmi2mid.c delete mode 100644 src/adlmidi_xmi2mid.h create mode 100644 src/cvt_mus2mid.hpp create mode 100644 src/cvt_xmi2mid.hpp create mode 100644 src/midi_sequencer.h create mode 100644 src/midi_sequencer.hpp create mode 100644 src/midi_sequencer_impl.hpp (limited to 'src') diff --git a/src/adlmidi.cpp b/src/adlmidi.cpp index 9210b5b..69755d3 100644 --- a/src/adlmidi.cpp +++ b/src/adlmidi.cpp @@ -104,12 +104,12 @@ ADLMIDI_EXPORT int adl_getNumChips(struct ADL_MIDIPlayer *device) ADLMIDI_EXPORT int adl_setBank(ADL_MIDIPlayer *device, int bank) { - #ifdef DISABLE_EMBEDDED_BANKS +#ifdef DISABLE_EMBEDDED_BANKS ADL_UNUSED(device); ADL_UNUSED(bank); ADLMIDI_ErrorString = "This build of libADLMIDI has no embedded banks. Please load bank by using of adl_openBankFile() or adl_openBankData() functions instead of adl_setBank()"; return -1; - #else +#else const uint32_t NumBanks = static_cast(maxAdlBanks()); int32_t bankno = bank; @@ -130,7 +130,7 @@ ADLMIDI_EXPORT int adl_setBank(ADL_MIDIPlayer *device, int bank) play->applySetup(); return adlRefreshNumCards(device); - #endif +#endif } ADLMIDI_EXPORT int adl_getBanksCount() @@ -355,20 +355,30 @@ ADLMIDI_EXPORT void adl_setFullRangeBrightness(struct ADL_MIDIPlayer *device, in { if(!device) return; MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + if(!play) + return; play->m_setup.fullRangeBrightnessCC74 = (fr_brightness != 0); } ADLMIDI_EXPORT void adl_setLoopEnabled(ADL_MIDIPlayer *device, int loopEn) { - if(!device) return; + if(!device) + return; MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); - play->m_setup.loopingIsEnabled = (loopEn != 0); + if(!play) + return; +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER + play->m_sequencer.setLoopEnabled(loopEn != 0); +#else + ADL_UNUSED(loopEn); +#endif } /* !!!DEPRECATED!!! */ ADLMIDI_EXPORT void adl_setLogarithmicVolumes(struct ADL_MIDIPlayer *device, int logvol) { - if(!device) return; + if(!device) + return; MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); play->m_setup.LogarithmicVolumes = (logvol != 0); if(play->m_setup.LogarithmicVolumes) @@ -379,7 +389,8 @@ ADLMIDI_EXPORT void adl_setLogarithmicVolumes(struct ADL_MIDIPlayer *device, int ADLMIDI_EXPORT void adl_setVolumeRangeModel(struct ADL_MIDIPlayer *device, int volumeModel) { - if(!device) return; + if(!device) + return; MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); play->m_setup.VolumeModel = volumeModel; if(play->m_setup.VolumeModel == ADLMIDI_VolumeModel_AUTO)//Use bank default volume model @@ -444,7 +455,7 @@ ADLMIDI_EXPORT int adl_openFile(ADL_MIDIPlayer *device, const char *filePath) } else return 0; #else - (void)filePath; + ADL_UNUSED(filePath); play->setErrorString("ADLMIDI: MIDI Sequencer is not supported in this build of library!"); return -1; #endif //ADLMIDI_DISABLE_MIDI_SEQUENCER @@ -470,7 +481,8 @@ ADLMIDI_EXPORT int adl_openData(ADL_MIDIPlayer *device, const void *mem, unsigne } else return 0; #else - (void)mem;(void)size; + ADL_UNUSED(mem); + ADL_UNUSED(size); play->setErrorString("ADLMIDI: MIDI Sequencer is not supported in this build of library!"); return -1; #endif //ADLMIDI_DISABLE_MIDI_SEQUENCER @@ -564,16 +576,7 @@ ADLMIDI_EXPORT const char *adl_errorInfo(struct ADL_MIDIPlayer *device) ADLMIDI_EXPORT const char *adl_getMusicTitle(struct ADL_MIDIPlayer *device) { - if(!device) - return ""; - MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); - if(!play) - return ""; - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - return play->musTitle.c_str(); - #else - return ""; - #endif + return adl_metaMusicTitle(device); } ADLMIDI_EXPORT void adl_close(struct ADL_MIDIPlayer *device) @@ -601,119 +604,159 @@ ADLMIDI_EXPORT double adl_totalTimeLength(struct ADL_MIDIPlayer *device) { if(!device) return -1.0; - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - return reinterpret_cast(device->adl_midiPlayer)->timeLength(); - #else +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER + return reinterpret_cast(device->adl_midiPlayer)->m_sequencer.timeLength(); +#else return -1.0; - #endif +#endif } ADLMIDI_EXPORT double adl_loopStartTime(struct ADL_MIDIPlayer *device) { if(!device) return -1.0; - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - return reinterpret_cast(device->adl_midiPlayer)->getLoopStart(); - #else +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER + return reinterpret_cast(device->adl_midiPlayer)->m_sequencer.getLoopStart(); +#else return -1.0; - #endif +#endif } ADLMIDI_EXPORT double adl_loopEndTime(struct ADL_MIDIPlayer *device) { if(!device) return -1.0; - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - return reinterpret_cast(device->adl_midiPlayer)->getLoopEnd(); - #else +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER + return reinterpret_cast(device->adl_midiPlayer)->m_sequencer.getLoopEnd(); +#else return -1.0; - #endif +#endif } ADLMIDI_EXPORT double adl_positionTell(struct ADL_MIDIPlayer *device) { +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER if(!device) return -1.0; - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - return reinterpret_cast(device->adl_midiPlayer)->tell(); - #else + MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + if(!play) + return -1.0; + return play->m_sequencer.tell(); +#else + ADL_UNUSED(device); return -1.0; - #endif +#endif } ADLMIDI_EXPORT void adl_positionSeek(struct ADL_MIDIPlayer *device, double seconds) { +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER + if(seconds < 0.0) + return;//Seeking negative position is forbidden! :-P if(!device) return; - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - reinterpret_cast(device->adl_midiPlayer)->seek(seconds); - #endif + MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + if(!play) + return; + play->realTime_panic(); + play->m_setup.delay = play->m_sequencer.seek(seconds, play->m_setup.mindelay); + play->m_setup.carry = 0.0; +#else + ADL_UNUSED(device); + ADL_UNUSED(seconds); +#endif } ADLMIDI_EXPORT void adl_positionRewind(struct ADL_MIDIPlayer *device) { +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER if(!device) return; - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - reinterpret_cast(device->adl_midiPlayer)->rewind(); - #endif + MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + if(!play) + return; + play->realTime_panic(); + play->m_sequencer.rewind(); +#else + ADL_UNUSED(device); +#endif } ADLMIDI_EXPORT void adl_setTempo(struct ADL_MIDIPlayer *device, double tempo) { +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER if(!device || (tempo <= 0.0)) return; - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - reinterpret_cast(device->adl_midiPlayer)->setTempo(tempo); - #endif + MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + if(!play) + return; + play->m_sequencer.setTempo(tempo); +#else + ADL_UNUSED(device); + ADL_UNUSED(tempo); +#endif } ADLMIDI_EXPORT const char *adl_metaMusicTitle(struct ADL_MIDIPlayer *device) { +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER if(!device) return ""; - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - return reinterpret_cast(device->adl_midiPlayer)->musTitle.c_str(); - #else + MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + if(!play) + return ""; + return play->m_sequencer.getMusicTitle().c_str(); +#else + ADL_UNUSED(device); return ""; - #endif +#endif } ADLMIDI_EXPORT const char *adl_metaMusicCopyright(struct ADL_MIDIPlayer *device) { +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER if(!device) return ""; - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - return reinterpret_cast(device->adl_midiPlayer)->musCopyright.c_str(); - #else + MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + if(!play) + return ""; + return play->m_sequencer.getMusicCopyright().c_str(); +#else + ADL_UNUSED(device); return ""; - #endif +#endif } ADLMIDI_EXPORT size_t adl_metaTrackTitleCount(struct ADL_MIDIPlayer *device) { +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER if(!device) return 0; -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - return reinterpret_cast(device->adl_midiPlayer)->musTrackTitles.size(); + MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + if(!play) + return 0; + return play->m_sequencer.getTrackTitles().size(); #else + ADL_UNUSED(device); return 0; #endif } ADLMIDI_EXPORT const char *adl_metaTrackTitle(struct ADL_MIDIPlayer *device, size_t index) { - if(!device) - return 0; #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER + if(!device) + return ""; MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); - if(index >= play->musTrackTitles.size()) + const std::vector &titles = play->m_sequencer.getTrackTitles(); + if(index >= titles.size()) return "INVALID"; - return play->musTrackTitles[index].c_str(); + return titles[index].c_str(); #else - (void)device; (void)index; + ADL_UNUSED(device); + ADL_UNUSED(index); return "NOT SUPPORTED"; #endif } @@ -721,11 +764,15 @@ ADLMIDI_EXPORT const char *adl_metaTrackTitle(struct ADL_MIDIPlayer *device, siz ADLMIDI_EXPORT size_t adl_metaMarkerCount(struct ADL_MIDIPlayer *device) { +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER if(!device) return 0; -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - return reinterpret_cast(device->adl_midiPlayer)->musMarkers.size(); + MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + if(!play) + return 0; + return play->m_sequencer.getMarkers().size(); #else + ADL_UNUSED(device); return 0; #endif } @@ -733,9 +780,10 @@ ADLMIDI_EXPORT size_t adl_metaMarkerCount(struct ADL_MIDIPlayer *device) ADLMIDI_EXPORT Adl_MarkerEntry adl_metaMarker(struct ADL_MIDIPlayer *device, size_t index) { struct Adl_MarkerEntry marker; - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); - if(!device || !play || (index >= play->musMarkers.size())) + const std::vector &markers = play->m_sequencer.getMarkers(); + if(!device || !play || (index >= markers.size())) { marker.label = "INVALID"; marker.pos_time = 0.0; @@ -744,27 +792,34 @@ ADLMIDI_EXPORT Adl_MarkerEntry adl_metaMarker(struct ADL_MIDIPlayer *device, siz } else { - MIDIplay::MIDI_MarkerEntry &mk = play->musMarkers[index]; + const MidiSequencer::MIDI_MarkerEntry &mk = markers[index]; marker.label = mk.label.c_str(); marker.pos_time = mk.pos_time; marker.pos_ticks = (unsigned long)mk.pos_ticks; } - #else - (void)device; (void)index; +#else + ADL_UNUSED(device); + ADL_UNUSED(index); marker.label = "NOT SUPPORTED"; marker.pos_time = 0.0; marker.pos_ticks = 0; - #endif +#endif return marker; } ADLMIDI_EXPORT void adl_setRawEventHook(struct ADL_MIDIPlayer *device, ADL_RawEventHook rawEventHook, void *userData) { +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER if(!device) return; MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); - play->hooks.onEvent = rawEventHook; - play->hooks.onEvent_userData = userData; + play->m_sequencerInterface.onEvent = rawEventHook; + play->m_sequencerInterface.onEvent_userData = userData; +#else + ADL_UNUSED(device); + ADL_UNUSED(rawEventHook); + ADL_UNUSED(userData); +#endif } /* Set note hook */ @@ -785,6 +840,10 @@ ADLMIDI_EXPORT void adl_setDebugMessageHook(struct ADL_MIDIPlayer *device, ADL_D MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); play->hooks.onDebugMessage = debugMessageHook; play->hooks.onDebugMessage_userData = userData; +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER + play->m_sequencerInterface.onDebugMessage = debugMessageHook; + play->m_sequencerInterface.onDebugMessage_userData = userData; +#endif } #ifndef ADLMIDI_HW_OPL @@ -930,15 +989,16 @@ ADLMIDI_EXPORT int adl_playFormat(ADL_MIDIPlayer *device, int sampleCount, ADL_UInt8 *out_left, ADL_UInt8 *out_right, const ADLMIDI_AudioFormat *format) { - #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - #ifdef ADLMIDI_HW_OPL - (void)device; - (void)sampleCount; - (void)out_left; - (void)out_right; - (void)format; +#if defined(ADLMIDI_DISABLE_MIDI_SEQUENCER) || defined(ADLMIDI_HW_OPL) + ADL_UNUSED(device); + ADL_UNUSED(sampleCount); + ADL_UNUSED(out_left); + ADL_UNUSED(out_right); + ADL_UNUSED(format); return 0; - #else +#endif + +#if !defined(ADLMIDI_DISABLE_MIDI_SEQUENCER) && !defined(ADLMIDI_HW_OPL) sampleCount -= sampleCount % 2; //Avoid even sample requests if(sampleCount < 0) return 0; @@ -975,7 +1035,7 @@ ADLMIDI_EXPORT int adl_playFormat(ADL_MIDIPlayer *device, int sampleCount, // setup.SkipForward -= 1; //else { - if((player->atEnd) && (setup.delay <= 0.0)) + if((player->m_sequencer.positionAtEnd()) && (setup.delay <= 0.0)) break;//Stop to fetch samples at reaching the song end with disabled loop ssize_t leftSamples = left / 2; @@ -1024,10 +1084,7 @@ ADLMIDI_EXPORT int adl_playFormat(ADL_MIDIPlayer *device, int sampleCount, } return static_cast(gotten_len); - #endif - #else - return 0; - #endif //ADLMIDI_DISABLE_MIDI_SEQUENCER +#endif //ADLMIDI_DISABLE_MIDI_SEQUENCER } @@ -1040,14 +1097,14 @@ ADLMIDI_EXPORT int adl_generateFormat(struct ADL_MIDIPlayer *device, int sampleC ADL_UInt8 *out_left, ADL_UInt8 *out_right, const ADLMIDI_AudioFormat *format) { - #ifdef ADLMIDI_HW_OPL - (void)device; - (void)sampleCount; - (void)out_left; - (void)out_right; - (void)format; +#ifdef ADLMIDI_HW_OPL + ADL_UNUSED(device); + ADL_UNUSED(sampleCount); + ADL_UNUSED(out_left); + ADL_UNUSED(out_right); + ADL_UNUSED(format); return 0; - #else +#else sampleCount -= sampleCount % 2; //Avoid even sample requests if(sampleCount < 0) return 0; @@ -1101,12 +1158,12 @@ ADLMIDI_EXPORT int adl_generateFormat(struct ADL_MIDIPlayer *device, int sampleC gotten_len += (in_generatedPhys) /* - setup.stored_samples*/; } - player->TickIteratos(eat_delay); + player->TickIterators(eat_delay); }//... } return static_cast(gotten_len); - #endif +#endif } ADLMIDI_EXPORT double adl_tickEvents(struct ADL_MIDIPlayer *device, double seconds, double granuality) @@ -1119,7 +1176,9 @@ ADLMIDI_EXPORT double adl_tickEvents(struct ADL_MIDIPlayer *device, double secon return -1.0; return player->Tick(seconds, granuality); #else - (void)seconds; (void)granuality; + ADL_UNUSED(device); + ADL_UNUSED(seconds); + ADL_UNUSED(granuality); return -1.0; #endif } @@ -1132,8 +1191,9 @@ ADLMIDI_EXPORT int adl_atEnd(struct ADL_MIDIPlayer *device) MIDIplay *player = reinterpret_cast(device->adl_midiPlayer); if(!player) return 1; - return (int)player->atEnd; + return (int)player->m_sequencer.positionAtEnd(); #else + ADL_UNUSED(device); return 1; #endif } diff --git a/src/adlmidi_load.cpp b/src/adlmidi_load.cpp index 10bea21..3e51b77 100644 --- a/src/adlmidi_load.cpp +++ b/src/adlmidi_load.cpp @@ -24,40 +24,6 @@ #include "adlmidi_private.hpp" #include "wopl/wopl_file.h" -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER -# ifndef ADLMIDI_DISABLE_MUS_SUPPORT -# include "adlmidi_mus2mid.h" -# endif//MUS -# ifndef ADLMIDI_DISABLE_XMI_SUPPORT -# include "adlmidi_xmi2mid.h" -# endif//XMI -#endif //ADLMIDI_DISABLE_MIDI_SEQUENCER - -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER -uint64_t MIDIplay::ReadBEint(const void *buffer, size_t nbytes) -{ - uint64_t result = 0; - const unsigned char *data = reinterpret_cast(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(buffer); - - for(unsigned n = 0; n < nbytes; ++n) - result = result + static_cast(data[n] << (n * 8)); - - return result; -} - -#endif - bool MIDIplay::LoadBank(const std::string &filename) { FileAndMemReader file; @@ -293,185 +259,44 @@ bool MIDIplay::LoadBank(FileAndMemReader &fr) } #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER -bool MIDIplay::LoadMIDI(const std::string &filename) -{ - FileAndMemReader file; - file.openFile(filename.c_str()); - if(!LoadMIDI(file)) - return false; - return true; -} - -bool MIDIplay::LoadMIDI(const void *data, size_t size) -{ - FileAndMemReader file; - file.openData(data, size); - return LoadMIDI(file); -} -bool MIDIplay::LoadMIDI(FileAndMemReader &fr) +bool MIDIplay::LoadMIDI_pre() { - size_t fsize; - ADL_UNUSED(fsize); - //! Temp buffer for conversion - AdlMIDI_CPtr cvt_buf; - errorString.clear(); - - #ifdef DISABLE_EMBEDDED_BANKS +#ifdef DISABLE_EMBEDDED_BANKS if((opl.AdlBank != ~0u) || opl.dynamic_banks.empty()) { errorStringOut = "Bank is not set! Please load any instruments bank by using of adl_openBankFile() or adl_openBankData() functions!"; return false; } - #endif - - if(!fr.isValid()) - { - errorStringOut = "Invalid data stream!\n"; - #ifndef _WIN32 - errorStringOut += std::strerror(errno); - #endif - return false; - } - +#endif /**** Set all properties BEFORE starting of actial file reading! ****/ applySetup(); - atEnd = false; - loopStart = true; - invalidLoop = false; - - bool is_GMF = false; // GMD/MUS files (ScummVM) - //bool is_MUS = false; // MUS/DMX files (Doom) - bool is_IMF = false; // IMF - bool is_CMF = false; // Creative Music format (CMF/CTMF) - bool is_RSXX = false; // RSXX, such as Cartooners - - const size_t HeaderSize = 4 + 4 + 2 + 2 + 2; // 14 - char HeaderBuf[HeaderSize] = ""; - size_t DeltaTicks = 192, TrackCount = 1; - -riffskip: - fsize = fr.read(HeaderBuf, 1, HeaderSize); + caugh_missing_instruments.clear(); + caugh_missing_banks_melodic.clear(); + caugh_missing_banks_percussion.clear(); - if(std::memcmp(HeaderBuf, "RIFF", 4) == 0) - { - fr.seek(6l, SEEK_CUR); - goto riffskip; - } + return true; +} - if(std::memcmp(HeaderBuf, "GMF\x1", 4) == 0) - { - // GMD/MUS files (ScummVM) - fr.seek(7 - static_cast(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(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(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 &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(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(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(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(ins_start), SEEK_SET); - - //std::printf("%u instruments\n", ins_count); - for(unsigned i = 0; i < ins_count; ++i) - { - unsigned bank = i / 256; + const uint8_t *InsData = instruments[i].data; + uint16_t bank = i / 256; bank = (bank & 127) + ((bank >> 7) << 8); if(bank > 127 + (127 << 8)) break; bank += (i % 256 < 128) ? 0 : OPL3::PercussionTag; - unsigned char InsData[16]; - fr.read(InsData, 1, 16); /*std::printf("Ins %3u: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\n", i, InsData[0],InsData[1],InsData[2],InsData[3], InsData[4],InsData[5],InsData[6],InsData[7], InsData[8],InsData[9],InsData[10],InsData[11], InsData[12],InsData[13],InsData[14],InsData[15]);*/ @@ -500,224 +325,63 @@ riffskip: adlins.voice2_fine_tune = 0.0; } - fr.seeku(mus_start, SEEK_SET); - TrackCount = 1; - DeltaTicks = (size_t)ticks; opl.AdlBank = ~0u; // Ignore AdlBank number, use dynamic banks instead //std::printf("CMF deltas %u ticks %u, basictempo = %u\n", deltas, ticks, basictempo); opl.AdlPercussionMode = true; opl.m_musicMode = OPL3::MODE_CMF; opl.m_volumeScale = OPL3::VOLUME_NATIVE; } - else + else if(format == MidiSequencer::Format_RSXX) { - // Try to identify RSXX format - if(HeaderBuf[0] == 0x7D) - { - fr.seek(0x6D, SEEK_SET); - fr.read(HeaderBuf, 6, 1); - if(std::memcmp(HeaderBuf, "rsxx}u", 6) == 0) - { - is_RSXX = true; - fr.seek(0x7D, SEEK_SET); - TrackCount = 1; - DeltaTicks = 60; - //opl.CartoonersVolumes = true; - opl.m_musicMode = OPL3::MODE_RSXX; - opl.m_volumeScale = OPL3::VOLUME_NATIVE; - } - } - - // Try parsing as an IMF file - if(!is_RSXX) - { - do - { - uint8_t raw[4]; - size_t end = static_cast(HeaderBuf[0]) + 256 * static_cast(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(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()); - InvDeltaTicks = fraction(1, 1000000l * static_cast(DeltaTicks)); - if(is_CMF || is_RSXX) - Tempo = fraction(1, static_cast(DeltaTicks)); - else - Tempo = fraction(1, static_cast(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(HeaderBuf[0]) + 256 * static_cast(HeaderBuf[1]); - unsigned IMF_tempo = 1428; - static const unsigned char imf_tempo[] = {0x0,//Zero delay! - MidiEvent::T_SPECIAL, MidiEvent::ST_TEMPOCHANGE, 0x4, - static_cast(IMF_tempo >> 24), - static_cast(IMF_tempo >> 16), - static_cast(IMF_tempo >> 8), - static_cast(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(raw[2]); - delay += 256 * static_cast(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(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(fr.getc()); - // TrackLength += static_cast(fr.getc() << 8); - // fr.seek(static_cast(pos), SEEK_SET); - //} - else - { - fsize = fr.read(HeaderBuf, 1, 8); - if(std::memcmp(HeaderBuf, "MTrk", 4) != 0) - { - fr.close(); - errorStringOut = fr.fileName() + ": Invalid format, MTrk signature is not found!\n"; - return false; - } - TrackLength = (size_t)ReadBEint(HeaderBuf + 4, 4); - } - - // Read track data - TrackData[tk].resize(TrackLength); - fsize = fr.read(&TrackData[tk][0], 1, TrackLength); - totalGotten += fsize; - - if(is_GMF/*|| is_MUS*/) // Note: CMF does include the track end tag. - TrackData[tk].insert(TrackData[tk].end(), EndTag + 0, EndTag + 4); - if(is_RSXX)//Finalize raw track data with a zero - TrackData[tk].push_back(0); - - //bool ok = false; - //// Read next event time - //uint64_t tkDelay = ReadVarLenEx(tk, ok); - //if(ok) - // CurrentPosition.track[tk].delay = tkDelay; - //else - //{ - // std::stringstream msg; - // msg << fr._fileName << ": invalid variable length in the track " << tk << "! (error code " << tkDelay << ")"; - // ADLMIDI_ErrorString = msg.str(); - // return false; - //} - } + //std::fprintf(stderr, "Done reading IMF file\n"); + opl.NumFourOps = 0; //Don't use 4-operator channels for IMF playing! + opl.m_musicMode = OPL3::MODE_IMF; } - for(size_t tk = 0; tk < TrackCount; ++tk) - totalGotten += TrackData[tk].size(); + opl.Reset(m_setup.emulator, m_setup.PCM_RATE, this); // Reset OPL3 chip + //opl.Reset(); // ...twice (just in case someone misprogrammed OPL3 previously) + ch.clear(); + ch.resize(opl.NumChannels); + + return true; +} - if(totalGotten == 0) +bool MIDIplay::LoadMIDI(const std::string &filename) +{ + FileAndMemReader file; + file.openFile(filename.c_str()); + if(!LoadMIDI_pre()) + return false; + if(!m_sequencer.loadMIDI(file)) { - errorStringOut = fr.fileName() + ": Empty track data"; + errorStringOut = m_sequencer.getErrorString(); return false; } + if(!LoadMIDI_post()) + return false; + return true; +} - //Build new MIDI events table - if(!buildTrackData()) +bool MIDIplay::LoadMIDI(const void *data, size_t size) +{ + FileAndMemReader file; + file.openData(data, size); + if(!LoadMIDI_pre()) + return false; + if(!m_sequencer.loadMIDI(file)) { - errorStringOut = fr.fileName() + ": MIDI data parsing error has occouped!\n" + errorString; + errorStringOut = m_sequencer.getErrorString(); return false; } - - opl.Reset(m_setup.emulator, m_setup.PCM_RATE, this); // Reset OPL3 chip - //opl.Reset(); // ...twice (just in case someone misprogrammed OPL3 previously) - ch.clear(); - ch.resize(opl.NumChannels); + if(!LoadMIDI_post()) + return false; return true; } -#endif //ADLMIDI_DISABLE_MIDI_SEQUENCER + +#endif /* ADLMIDI_DISABLE_MIDI_SEQUENCER */ diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index 2f186a6..5b7f0d9 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -115,586 +115,12 @@ void MIDIplay::AdlChannel::AddAge(int64_t ms) } } -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - -MIDIplay::MidiEvent::MidiEvent() : - type(T_UNKNOWN), - subtype(T_UNKNOWN), - channel(0), - isValid(1), - absPosition(0) -{} - - -MIDIplay::MidiTrackRow::MidiTrackRow() : - time(0.0), - delay(0), - absPos(0), - timeDelay(0.0) -{} - -void MIDIplay::MidiTrackRow::reset() -{ - time = 0.0; - delay = 0; - absPos = 0; - timeDelay = 0.0; - events.clear(); -} - -void MIDIplay::MidiTrackRow::sortEvents(bool *noteStates) -{ - typedef std::vector 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 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::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 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 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 points; - fraction 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(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 t = pos.delay * currentTempo; - pos.timeDelay = t.value(); - pos.time = time; - time += pos.timeDelay; - - //Capture markers after time value calculation - for(size_t i = 0; i < pos.events.size(); i++) - { - MidiEvent &e = pos.events[i]; - if((e.type == MidiEvent::T_SPECIAL) && (e.subtype == MidiEvent::ST_MARKER)) - { - MIDI_MarkerEntry marker; - marker.label = std::string((char *)e.data.data(), e.data.size()); - marker.pos_ticks = pos.absPos; - marker.pos_time = pos.time; - musMarkers.push_back(marker); - } - } - - //Capture loop points time positions - if(!invalidLoop) - { - // Set loop points times - if(loopStartTicks == pos.absPos) - loopStartTime = pos.time; - else if(loopEndTicks == pos.absPos) - loopEndTime = pos.time; - } - -#ifdef DEBUG_TIME_CALCULATION - std::fprintf(stdout, "= %10" PRId64 " = %10f%s\n", pos.absPos, pos.time, tempoChanged ? " <----TEMPO CHANGED" : ""); - std::fflush(stdout); -#endif - - abs_position += pos.delay; - posPrev = &pos; - } - - if(time > fullSongTimeLength) - fullSongTimeLength = time; - } - - fullSongTimeLength += postSongWaitDelay; - //Set begin of the music - trackBeginPositionNew = CurrentPositionNew; - //Initial loop position will begin at begin of track until passing of the loop point - LoopBeginPositionNew = CurrentPositionNew; - - /********************************************************************************/ - //Resolve "hell of all times" of too short drum notes: - //move too short percussion note-offs far far away as possible - /********************************************************************************/ -#if 1 //Use this to record WAVEs for comparison before/after implementing of this - if(opl.m_musicMode == OPL3::MODE_MIDI)//Percussion fix is needed for MIDI only, not for IMF/RSXX or CMF - { - //! Minimal real time in seconds -#define DRUM_NOTE_MIN_TIME 0.03 - //! Minimal ticks count -#define DRUM_NOTE_MIN_TICKS 15 - struct NoteState - { - double delay; - uint64_t delayTicks; - bool isOn; - char ___pad[7]; - } drNotes[255]; - uint16_t banks[16]; - - for(size_t tk = 0; tk < trackCount; ++tk) - { - std::memset(drNotes, 0, sizeof(drNotes)); - std::memset(banks, 0, sizeof(banks)); - MidiTrackQueue &track = trackDataNew[tk]; - if(track.empty()) - continue;//Empty track is useless! - - for(MidiTrackQueue::iterator it = track.begin(); it != track.end(); it++) - { - MidiTrackRow &pos = *it; - - for(ssize_t e = 0; e < (ssize_t)pos.events.size(); e++) - { - MidiEvent *et = &pos.events[(size_t)e]; - - /* Set MSB/LSB bank */ - if(et->type == MidiEvent::T_CTRLCHANGE) - { - uint8_t ctrlno = et->data[0]; - uint8_t value = et->data[1]; - switch(ctrlno) - { - case 0: // Set bank msb (GM bank) - banks[et->channel] = uint16_t(uint16_t(value) << 8) | uint16_t(banks[et->channel] & 0x00FF); - break; - case 32: // Set bank lsb (XG bank) - banks[et->channel] = (banks[et->channel] & 0xFF00) | (uint16_t(value) & 0x00FF); - break; - } - continue; - } - - bool percussion = (et->channel == 9) || - banks[et->channel] == 0x7E00 || //XG SFX1/SFX2 channel (16128 signed decimal) - banks[et->channel] == 0x7F00; //XG Percussion channel (16256 signed decimal) - if(!percussion) - continue; - - if(et->type == MidiEvent::T_NOTEON) - { - uint8_t note = et->data[0] & 0x7F; - NoteState &ns = drNotes[note]; - ns.isOn = true; - ns.delay = 0.0; - ns.delayTicks = 0; - } - else if(et->type == MidiEvent::T_NOTEOFF) - { - uint8_t note = et->data[0] & 0x7F; - NoteState &ns = drNotes[note]; - if(ns.isOn) - { - ns.isOn = false; - if(ns.delayTicks < DRUM_NOTE_MIN_TICKS || ns.delay < DRUM_NOTE_MIN_TIME)//If note is too short - { - //Move it into next event position if that possible - for(MidiTrackQueue::iterator itNext = it; - itNext != track.end(); - itNext++) - { - MidiTrackRow &posN = *itNext; - if(ns.delayTicks > DRUM_NOTE_MIN_TICKS && ns.delay > DRUM_NOTE_MIN_TIME) - { - //Put note-off into begin of next event list - posN.events.insert(posN.events.begin(), pos.events[(size_t)e]); - //Renive this event from a current row - pos.events.erase(pos.events.begin() + (int)e); - e--; - break; - } - ns.delay += posN.timeDelay; - ns.delayTicks += posN.delay; - } - } - ns.delay = 0.0; - ns.delayTicks = 0; - } - } - } - - //Append time delays to sustaining notes - for(size_t no = 0; no < 128; no++) - { - NoteState &ns = drNotes[no]; - if(ns.isOn) - { - ns.delay += pos.timeDelay; - ns.delayTicks += pos.delay; - } - } - } - } -#undef DRUM_NOTE_MIN_TIME -#undef DRUM_NOTE_MIN_TICKS - } -#endif - - return true; -} -#endif - - MIDIplay::MIDIplay(unsigned long sampleRate): cmf_percussion_mode(false), m_arpeggioCounter(0) #if defined(ADLMIDI_AUDIO_TICK_HANDLER) , m_audioTickCounter(0) #endif -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - , fullSongTimeLength(0.0), - postSongWaitDelay(1.0), - loopStartTime(-1.0), - loopEndTime(-1.0), - tempoMultiplier(1.0), - atEnd(false), - loopStart(false), - loopEnd(false), - invalidLoop(false) -#endif { devices.clear(); @@ -714,13 +140,15 @@ MIDIplay::MIDIplay(unsigned long sampleRate): m_setup.LogarithmicVolumes = false; m_setup.VolumeModel = ADLMIDI_VolumeModel_AUTO; //m_setup.SkipForward = 0; - m_setup.loopingIsEnabled = false; m_setup.ScaleModulators = -1; m_setup.fullRangeBrightnessCC74 = false; m_setup.delay = 0.0; m_setup.carry = 0.0; m_setup.tick_skip_samples_delay = 0; +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER + initSequencerInterface(); +#endif applySetup(); ChooseDevice("none"); realTime_ResetState(); @@ -766,79 +194,7 @@ void MIDIplay::applySetup() m_arpeggioCounter = 0; } -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER -uint64_t MIDIplay::ReadVarLen(uint8_t **ptr) -{ - uint64_t result = 0; - for(;;) - { - uint8_t byte = *((*ptr)++); - result = (result << 7) + (byte & 0x7F); - if(!(byte & 0x80)) - break; - } - return result; -} - -uint64_t MIDIplay::ReadVarLenEx(uint8_t **ptr, uint8_t *end, bool &ok) -{ - uint64_t result = 0; - ok = false; - - for(;;) - { - if(*ptr >= end) - return 2; - unsigned char byte = *((*ptr)++); - result = (result << 7) + (byte & 0x7F); - if(!(byte & 0x80)) - break; - } - - ok = true; - return result; -} - -double MIDIplay::Tick(double s, double granularity) -{ - s *= tempoMultiplier; -#ifdef ENABLE_BEGIN_SILENCE_SKIPPING - if(CurrentPositionNew.began) -#endif - CurrentPositionNew.wait -= s; - CurrentPositionNew.absTimePosition += s; - - int antiFreezeCounter = 10000;//Limit 10000 loops to avoid freezing - while((CurrentPositionNew.wait <= granularity * 0.5) && (antiFreezeCounter > 0)) - { - //std::fprintf(stderr, "wait = %g...\n", CurrentPosition.wait); - if(!ProcessEventsNew()) - break; - if(CurrentPositionNew.wait <= 0.0) - antiFreezeCounter--; - } - - if(antiFreezeCounter <= 0) - CurrentPositionNew.wait += 1.0;/* Add extra 1 second when over 10000 events - with zero delay are been detected */ - - for(uint16_t c = 0; c < opl.NumChannels; ++c) - ch[c].AddAge(static_cast(s * 1000.0)); - - UpdateVibrato(s); - UpdateArpeggio(s); -#if !defined(ADLMIDI_AUDIO_TICK_HANDLER) - UpdateGlide(s); -#endif - - if(CurrentPositionNew.wait < 0.0)//Avoid negative delay value! - return 0.0; - - return CurrentPositionNew.wait; -} -#endif /* ADLMIDI_DISABLE_MIDI_SEQUENCER */ - -void MIDIplay::TickIteratos(double s) +void MIDIplay::TickIterators(double s) { for(uint16_t c = 0; c < opl.NumChannels; ++c) ch[c].AddAge(static_cast(s * 1000.0)); @@ -849,115 +205,6 @@ void MIDIplay::TickIteratos(double s) #endif } -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - -void MIDIplay::seek(double seconds) -{ - if(seconds < 0.0) - return;//Seeking negative position is forbidden! :-P - const double granularity = m_setup.mindelay, - granualityHalf = granularity * 0.5, - s = seconds;//m_setup.delay < m_setup.maxdelay ? m_setup.delay : m_setup.maxdelay; - - /* Attempt to go away out of song end must rewind position to begin */ - if(seconds > fullSongTimeLength) - { - rewind(); - return; - } - - bool loopFlagState = m_setup.loopingIsEnabled; - // Turn loop pooints off because it causes wrong position rememberin on a quick seek - m_setup.loopingIsEnabled = false; - - /* - * Seeking search is similar to regular ticking, except of next things: - * - We don't processsing arpeggio and vibrato - * - To keep correctness of the state after seek, begin every search from begin - * - All sustaining notes must be killed - * - Ignore Note-On events - */ - rewind(); - - /* - * Set "loop Start" to false to prevent overwrite of loopStart position with - * seek destinition position - * - * TODO: Detect & set loopStart position on load time to don't break loop while seeking - */ - loopStart = false; - - while((CurrentPositionNew.absTimePosition < seconds) && - (CurrentPositionNew.absTimePosition < fullSongTimeLength)) - { - CurrentPositionNew.wait -= s; - CurrentPositionNew.absTimePosition += s; - int antiFreezeCounter = 10000;//Limit 10000 loops to avoid freezing - double dstWait = CurrentPositionNew.wait + granualityHalf; - while((CurrentPositionNew.wait <= granualityHalf)/*&& (antiFreezeCounter > 0)*/) - { - //std::fprintf(stderr, "wait = %g...\n", CurrentPosition.wait); - if(!ProcessEventsNew(true)) - break; - //Avoid freeze because of no waiting increasing in more than 10000 cycles - if(CurrentPositionNew.wait <= dstWait) - antiFreezeCounter--; - else - { - dstWait = CurrentPositionNew.wait + granualityHalf; - antiFreezeCounter = 10000; - } - } - if(antiFreezeCounter <= 0) - CurrentPositionNew.wait += 1.0;/* Add extra 1 second when over 10000 events - with zero delay are been detected */ - } - - if(CurrentPositionNew.wait < 0.0) - CurrentPositionNew.wait = 0.0; - - m_setup.loopingIsEnabled = loopFlagState; - m_setup.delay = CurrentPositionNew.wait; - m_setup.carry = 0.0; -} - -double MIDIplay::tell() -{ - return CurrentPositionNew.absTimePosition; -} - -double MIDIplay::timeLength() -{ - return fullSongTimeLength; -} - -double MIDIplay::getLoopStart() -{ - return loopStartTime; -} - -double MIDIplay::getLoopEnd() -{ - return loopEndTime; -} - -void MIDIplay::rewind() -{ - Panic(); - KillSustainingNotes(-1, -1); - CurrentPositionNew = trackBeginPositionNew; - atEnd = false; - loopStart = true; - loopEnd = false; - //invalidLoop = false;//No more needed here as this flag is set on load time -} - -void MIDIplay::setTempo(double tempo) -{ - tempoMultiplier = tempo; -} -#endif /* ADLMIDI_DISABLE_MIDI_SEQUENCER */ - void MIDIplay::realTime_ResetState() { for(size_t ch = 0; ch < Ch.size(); ch++) @@ -1465,6 +712,26 @@ void MIDIplay::realTime_panic() KillSustainingNotes(-1, -1); } +void MIDIplay::realTime_deviceSwitch(size_t track, const char *data, size_t length) +{ + const std::string indata(data, length); + current_device[track] = ChooseDevice(indata); +} + +uint64_t MIDIplay::realTime_currentDevice(size_t track) +{ + return current_device[track]; +} + +void MIDIplay::realTime_rawOPL(uint8_t reg, uint8_t value) +{ + if((reg & 0xF0) == 0xC0) + value |= 0x30; + //std::printf("OPL poke %02X, %02X\n", reg, value); + //std::fflush(stdout); + opl.Poke(0, reg, value); +} + #if defined(ADLMIDI_AUDIO_TICK_HANDLER) void MIDIplay::AudioTick(uint32_t chipId, uint32_t rate) { @@ -1652,17 +919,17 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, } /* DEBUG ONLY!!! - static uint32_t max = 0; + static uint32_t max = 0; - if(volume == 0) - max = 0; + if(volume == 0) + max = 0; - if(volume > max) - max = volume; + if(volume > max) + max = volume; - printf("%d\n", max); - fflush(stdout); - */ + printf("%d\n", max); + fflush(stdout); + */ } if(props_mask & Upd_Pitch) @@ -1707,339 +974,6 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, } } -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER -bool MIDIplay::ProcessEventsNew(bool isSeek) -{ - if(CurrentPositionNew.track.size() == 0) - atEnd = true;//No MIDI track data to play - if(atEnd) - return false;//No more events in the queue - - loopEnd = false; - const size_t TrackCount = CurrentPositionNew.track.size(); - const PositionNew RowBeginPosition(CurrentPositionNew); - -#ifdef DEBUG_TIME_CALCULATION - double maxTime = 0.0; -#endif - - for(size_t tk = 0; tk < TrackCount; ++tk) - { - PositionNew::TrackInfo &track = CurrentPositionNew.track[tk]; - if((track.status >= 0) && (track.delay <= 0)) - { - //Check is an end of track has been reached - if(track.pos == trackDataNew[tk].end()) - { - track.status = -1; - break; - } - - // Handle event - for(size_t i = 0; i < track.pos->events.size(); i++) - { - const MidiEvent &evt = track.pos->events[i]; -#ifdef ENABLE_BEGIN_SILENCE_SKIPPING - if(!CurrentPositionNew.began && (evt.type == MidiEvent::T_NOTEON)) - CurrentPositionNew.began = true; -#endif - if(isSeek && (evt.type == MidiEvent::T_NOTEON)) - continue; - HandleEvent(tk, evt, track.status); - if(loopEnd) - break;//Stop event handling on catching loopEnd event! - } - -#ifdef DEBUG_TIME_CALCULATION - if(maxTime < track.pos->time) - maxTime = track.pos->time; -#endif - // Read next event time (unless the track just ended) - if(track.status >= 0) - { - track.delay += track.pos->delay; - track.pos++; - } - } - } - -#ifdef DEBUG_TIME_CALCULATION - std::fprintf(stdout, " \r"); - std::fprintf(stdout, "Time: %10f; Audio: %10f\r", maxTime, CurrentPositionNew.absTimePosition); - std::fflush(stdout); -#endif - - // Find shortest delay from all track - uint64_t shortest = 0; - bool shortest_no = true; - - for(size_t tk = 0; tk < TrackCount; ++tk) - { - PositionNew::TrackInfo &track = CurrentPositionNew.track[tk]; - if((track.status >= 0) && (shortest_no || track.delay < shortest)) - { - shortest = track.delay; - shortest_no = false; - } - } - - //if(shortest > 0) UI.PrintLn("shortest: %ld", shortest); - - // Schedule the next playevent to be processed after that delay - for(size_t tk = 0; tk < TrackCount; ++tk) - CurrentPositionNew.track[tk].delay -= shortest; - - fraction 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(status | 0x80); - ptr--; - } - - //Sys Com Song Select(Song #) [0-127] - if(byte == MidiEvent::T_SYSCOMSNGSEL) - { - if(ptr + 1 > end) - { - errorString += "parseEvent: Can't read System Command Song Select event - Unexpected end of track data.\n"; - evt.isValid = 0; - return evt; - } - evt.type = byte; - evt.data.push_back(*(ptr++)); - return evt; - } - - //Sys Com Song Position Pntr [LSB, MSB] - if(byte == MidiEvent::T_SYSCOMSPOSPTR) - { - if(ptr + 2 > end) - { - errorString += "parseEvent: Can't read System Command Position Pointer event - Unexpected end of track data.\n"; - evt.isValid = 0; - return evt; - } - evt.type = byte; - evt.data.push_back(*(ptr++)); - evt.data.push_back(*(ptr++)); - return evt; - } - - uint8_t midCh = byte & 0x0F, evType = (byte >> 4) & 0x0F; - status = byte; - evt.channel = midCh; - evt.type = evType; - - switch(evType) - { - case MidiEvent::T_NOTEOFF://2 byte length - case MidiEvent::T_NOTEON: - case MidiEvent::T_NOTETOUCH: - case MidiEvent::T_CTRLCHANGE: - case MidiEvent::T_WHEEL: - if(ptr + 2 > end) - { - errorString += "parseEvent: Can't read regular 2-byte event - Unexpected end of track data.\n"; - evt.isValid = 0; - return evt; - } - - evt.data.push_back(*(ptr++)); - evt.data.push_back(*(ptr++)); - - /* TODO: Implement conversion of RSXX's note volumes out of synthesizer */ - /*if((opl.m_musicMode == OPL3::MODE_RSXX) && (evType == MidiEvent::T_NOTEON) && (evt.data[1] != 0)) - { - //NOT WORKING YET - evt.type = MidiEvent::T_NOTETOUCH; - } - else */if((evType == MidiEvent::T_NOTEON) && (evt.data[1] == 0)) - { - evt.type = MidiEvent::T_NOTEOFF; // Note ON with zero velocity is Note OFF! - } //111'th loopStart controller (RPG Maker and others) - else if((evType == MidiEvent::T_CTRLCHANGE) && (evt.data[0] == 111)) - { - //Change event type to custom Loop Start event and clear data - evt.type = MidiEvent::T_SPECIAL; - evt.subtype = MidiEvent::ST_LOOPSTART; - evt.data.clear(); - } - - return evt; - case MidiEvent::T_PATCHCHANGE://1 byte length - case MidiEvent::T_CHANAFTTOUCH: - if(ptr + 1 > end) - { - errorString += "parseEvent: Can't read regular 1-byte event - Unexpected end of track data.\n"; - evt.isValid = 0; - return evt; - } - evt.data.push_back(*(ptr++)); - return evt; - } - - return evt; -} -#endif /* ADLMIDI_DISABLE_MIDI_SEQUENCER */ - const std::string &MIDIplay::getErrorString() { return errorStringOut; @@ -2050,165 +984,6 @@ void MIDIplay::setErrorString(const std::string &err) errorStringOut = err; } -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER -void MIDIplay::HandleEvent(size_t tk, const MIDIplay::MidiEvent &evt, int &status) -{ - if(hooks.onEvent) - { - hooks.onEvent(hooks.onEvent_userData, - evt.type, - evt.subtype, - evt.channel, - evt.data.data(), - evt.data.size()); - } - - if(evt.type == MidiEvent::T_SYSEX || evt.type == MidiEvent::T_SYSEX2) // Ignore SysEx - { - //std::string data( length?(const char*) &TrackData[tk][CurrentPosition.track[tk].ptr]:0, length ); - //UI.PrintLn("SysEx %02X: %u bytes", byte, length/*, data.c_str()*/); - return; - } - - if(evt.type == MidiEvent::T_SPECIAL) - { - // Special event FF - uint8_t evtype = evt.subtype; - uint64_t length = (uint64_t)evt.data.size(); - std::string data(length ? (const char *)evt.data.data() : 0, (size_t)length); - - if(evtype == MidiEvent::ST_ENDTRACK)//End Of Track - { - status = -1; - return; - } - - if(evtype == MidiEvent::ST_TEMPOCHANGE)//Tempo change - { - Tempo = InvDeltaTicks * fraction(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(data[0]), v = static_cast(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(CurrentPosition.track[tk].status | 0x80); - // CurrentPosition.track[tk].ptr--; - // } - - if(evt.type == MidiEvent::T_SYSCOMSNGSEL || - evt.type == MidiEvent::T_SYSCOMSPOSPTR) - return; - - /*UI.PrintLn("@%X Track %u: %02X %02X", - CurrentPosition.track[tk].ptr-1, (unsigned)tk, byte, - TrackData[tk][CurrentPosition.track[tk].ptr]);*/ - uint8_t midCh = evt.channel;//byte & 0x0F, EvType = byte >> 4; - midCh += (uint8_t)current_device[tk]; - status = evt.type; - - switch(evt.type) - { - case MidiEvent::T_NOTEOFF: // Note off - { - uint8_t note = evt.data[0]; - realTime_NoteOff(midCh, note); - break; - } - - case MidiEvent::T_NOTEON: // Note on - { - uint8_t note = evt.data[0]; - uint8_t vol = evt.data[1]; - /*if(*/ realTime_NoteOn(midCh, note, vol); /*)*/ - //CurrentPosition.began = true; - break; - } - - case MidiEvent::T_NOTETOUCH: // Note touch - { - uint8_t note = evt.data[0]; - uint8_t vol = evt.data[1]; - realTime_NoteAfterTouch(midCh, note, vol); - break; - } - - case MidiEvent::T_CTRLCHANGE: // Controller change - { - uint8_t ctrlno = evt.data[0]; - uint8_t value = evt.data[1]; - realTime_Controller(midCh, ctrlno, value); - break; - } - - case MidiEvent::T_PATCHCHANGE: // Patch change - realTime_PatchChange(midCh, evt.data[0]); - break; - - case MidiEvent::T_CHANAFTTOUCH: // Channel after-touch - { - // TODO: Verify, is this correct action? - uint8_t vol = evt.data[0]; - realTime_ChannelAfterTouch(midCh, vol); - break; - } - - case MidiEvent::T_WHEEL: // Wheel/pitch bend - { - uint8_t a = evt.data[0]; - uint8_t b = evt.data[1]; - realTime_PitchBend(midCh, b, a); - break; - } - } -} -#endif /* ADLMIDI_DISABLE_MIDI_SEQUENCER */ - int64_t MIDIplay::CalculateAdlChannelGoodness(size_t c, const MIDIchannel::NoteInfo::Phys &ins, uint16_t) const { int64_t s = -ch[c].koff_time_until_neglible; @@ -2508,9 +1283,6 @@ void MIDIplay::UpdateVibrato(double amount) } } - - - uint64_t MIDIplay::ChooseDevice(const std::string &name) { std::map::iterator i = devices.find(name); diff --git a/src/adlmidi_mus2mid.c b/src/adlmidi_mus2mid.c deleted file mode 100644 index 3f3e1b8..0000000 --- a/src/adlmidi_mus2mid.c +++ /dev/null @@ -1,451 +0,0 @@ -/* - * MUS2MIDI: MUS to MIDI Library - * - * Copyright (C) 2014 Bret Curtis - * Copyright (C) WildMIDI Developers 2015-2016 - * ADLMIDI Library API: Copyright (c) 2015-2018 Vitaly Novichkov - * - * 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. - */ - -#include -#include -#include -#include "adlmidi_mus2mid.h" - -#define 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 */ -#endif - -#define TEMPO 0x00068A1B /* MPQN: 60000000 / 140BPM (140Hz) = 428571 */ - /* 0x000D1436 -> MPQN: 60000000 / 70BPM (70Hz) = 857142 */ - -#define 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 */ - -/* New - * QLS: MPQN/1000000 = 0.428571 - * TDPS: QLS/PPQN = 0.428571/136 = 0.003151257 - * PPQN: 136 - * - * QLS: MPQN/1000000 = 0.428571 - * TDPS: QLS/PPQN = 0.428571/257 = 0.001667591 - * PPQN: 257 - * - * QLS: MPQN/1000000 = 0.857142 - * TDPS: QLS/PPQN = 0.857142/267 = 0.00321027 - * PPQN: 267 - * - * QLS: MPQN/1000000 = 0.857142 - * TDPS: QLS/PPQN = 0.857142/505 = 0.001697311 - * PPQN: 505 - * - * Old - * QLS: MPQN/1000000 = 1.745673 - * TDPS: QLS/PPQN = 1.745673 / 89 = 0.019614303 (seconds per tick) - * PPQN: (TDPS = QLS/PPQN) (0.019614303 = 1.745673/PPQN) (0.019614303*PPQN = 1.745673) (PPQN = 89.000001682) - * - */ - -#define MUSEVENT_KEYOFF 0 -#define MUSEVENT_KEYON 1 -#define MUSEVENT_PITCHWHEEL 2 -#define MUSEVENT_CHANNELMODE 3 -#define MUSEVENT_CONTROLLERCHANGE 4 -#define MUSEVENT_END 6 - -#define MIDI_MAXCHANNELS 16 - -static char MUS_ID[] = { 'M', 'U', 'S', 0x1A }; - -static uint8_t midimap[] = -{/* MIDI Number Description */ - 0, /* 0 program change */ - 0, /* 1 bank selection */ - 0x01, /* 2 Modulation pot (frequency vibrato depth) */ - 0x07, /* 3 Volume: 0-silent, ~100-normal, 127-loud */ - 0x0A, /* 4 Pan (balance) pot: 0-left, 64-center (default), 127-right */ - 0x0B, /* 5 Expression pot */ - 0x5B, /* 6 Reverb depth */ - 0x5D, /* 7 Chorus depth */ - 0x40, /* 8 Sustain pedal */ - 0x43, /* 9 Soft pedal */ - 0x78, /* 10 All sounds off */ - 0x7B, /* 11 All notes off */ - 0x7E, /* 12 Mono (use numchannels + 1) */ - 0x7F, /* 13 Poly */ - 0x79, /* 14 reset all controllers */ -}; - -typedef struct MUSHeader { - char ID[4]; /* identifier: "MUS" 0x1A */ - uint16_t scoreLen; - uint16_t scoreStart; - uint16_t channels; /* count of primary channels */ - uint16_t sec_channels; /* count of secondary channels */ - uint16_t instrCnt; -} MUSHeader ; -#define MUS_HEADERSIZE 14 - -typedef struct MidiHeaderChunk { - char name[4]; - int32_t length; - int16_t format; /* make 0 */ - int16_t ntracks;/* make 1 */ - int16_t division; /* 0xe250 ?? */ -} MidiHeaderChunk; -#define MIDI_HEADERSIZE 14 - -typedef struct MidiTrackChunk { - char name[4]; - int32_t length; -} MidiTrackChunk; -#define TRK_CHUNKSIZE 8 - -struct mus_ctx { - uint8_t *src, *src_ptr; - uint32_t srcsize; - uint32_t datastart; - uint8_t *dst, *dst_ptr; - uint32_t dstsize, dstrem; -}; - -#define DST_CHUNK 8192 -static void 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->dstsize += DST_CHUNK; - ctx->dstrem += DST_CHUNK; - ctx->dst_ptr = ctx->dst + pos; -} - -static void write1(struct mus_ctx *ctx, uint32_t val) -{ - if (ctx->dstrem < 1) - resize_dst(ctx); - *ctx->dst_ptr++ = val & 0xff; - ctx->dstrem--; -} - -static void write2(struct mus_ctx *ctx, uint32_t val) -{ - if (ctx->dstrem < 2) - 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) -{ - if (ctx->dstrem < 4) - resize_dst(ctx); - *ctx->dst_ptr++ = (val>>24)&0xff; - *ctx->dst_ptr++ = (val>>16)&0xff; - *ctx->dst_ptr++ = (val>>8) & 0xff; - *ctx->dst_ptr++ = val & 0xff; - ctx->dstrem -= 4; -} - -static void seekdst(struct mus_ctx *ctx, uint32_t pos) { - ctx->dst_ptr = ctx->dst + pos; - while (ctx->dstsize < pos) - resize_dst(ctx); - ctx->dstrem = ctx->dstsize - pos; -} - -static void 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); - ctx->dstrem = (uint32_t)(ctx->dstsize - newpos); -} - -static uint32_t 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) -{ - int32_t buffer, count = 0; - - buffer = value & 0x7f; - while ((value >>= 7) > 0) { - buffer <<= 8; - buffer += 0x80; - buffer += (value & 0x7f); - } - - while (1) { - ++count; - *out = (uint8_t)buffer; - ++out; - if (buffer & 0x80) - buffer >>= 8; - else - break; - } - 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)) - -int AdlMidi_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; - - if (insize < MUS_HEADERSIZE) { - /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too short)", 0);*/ - return (-1); - } - - if (!frequency) - frequency = 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]); - - if (memcmp(header.ID, MUS_ID, 4)) { - /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_NOT_MUS, NULL, 0);*/ - return (-1); - } - if (insize < (uint32_t)header.scoreLen + (uint32_t)header.scoreStart) { - /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too short)", 0);*/ - return (-1); - } - /* channel #15 should be excluded in the numchannels field: */ - if (header.channels > MIDI_MAXCHANNELS - 1) { - /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_INVALID, NULL, 0);*/ - return (-1); - } - - memset(&ctx, 0, sizeof(struct mus_ctx)); - ctx.src = ctx.src_ptr = in; - ctx.srcsize = insize; - - ctx.dst = 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) { - 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 */ - - /* 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); - - /* 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); - - /* Percussions channel starts out at full volume */ - write1(&ctx, 0x00); - write1(&ctx, 0xB9); - write1(&ctx, 0x07); - write1(&ctx, 127); - - /* get current position in source, and end of position */ - cur = in + header.scoreStart; - end = cur + header.scoreLen; - - currentChannel = 0; - delta_time = 0; - - /* main loop */ - while(cur < end){ - /*printf("LOOP DEBUG: %d\r\n",iterator++);*/ - uint8_t channel; - uint8_t event; - uint8_t temp_buffer[32]; /* temp buffer for current iterator */ - uint8_t *out_local = temp_buffer; - uint8_t status, bit1, bit2, bitc = 2; - - /* read in current bit */ - event = *cur++; - channel = (event & 15); /* current channel */ - - /* write variable length delta time */ - out_local += writevarlen(delta_time, out_local); - - /* set all channels to 127 (max) volume */ - if (channelMap[channel] < 0) { - *out_local++ = 0xB0 + currentChannel; - *out_local++ = 0x07; - *out_local++ = 127; - *out_local++ = 0x00; - channelMap[channel] = currentChannel++; - if (currentChannel == 9) - ++currentChannel; - } - status = channelMap[channel]; - - /* handle events */ - switch ((event & 122) >> 4){ - case MUSEVENT_KEYOFF: - status |= 0x80; - bit1 = *cur++; - bit2 = 0x40; - break; - case MUSEVENT_KEYON: - status |= 0x90; - bit1 = *cur & 127; - if (*cur++ & 128) /* volume bit? */ - channel_volume[channelMap[channel]] = *cur++; - bit2 = channel_volume[channelMap[channel]]; - break; - case MUSEVENT_PITCHWHEEL: - status |= 0xE0; - bit1 = (*cur & 1) >> 6; - bit2 = (*cur++ >> 1) & 127; - break; - case MUSEVENT_CHANNELMODE: - status |= 0xB0; - if (*cur >= sizeof(midimap) / sizeof(midimap[0])) { - /*_WM_ERROR_NEW("%s:%i: can't map %u to midi", - __FUNCTION__, __LINE__, *cur);*/ - goto _end; - } - bit1 = midimap[*cur++]; - bit2 = (*cur++ == 12) ? header.channels + 1 : 0x00; - break; - case MUSEVENT_CONTROLLERCHANGE: - if (*cur == 0) { - cur++; - status |= 0xC0; - bit1 = *cur++; - bit2 = 0;/* silence bogus warnings */ - bitc = 1; - } else { - status |= 0xB0; - if (*cur >= sizeof(midimap) / sizeof(midimap[0])) { - /*_WM_ERROR_NEW("%s:%i: can't map %u to midi", - __FUNCTION__, __LINE__, *cur);*/ - goto _end; - } - bit1 = midimap[*cur++]; - bit2 = *cur++; - } - break; - case MUSEVENT_END: /* End */ - status = 0xff; - bit1 = 0x2f; - bit2 = 0x00; - if (cur != end) { /* should we error here or report-only? */ - /*_WM_DEBUG_MSG("%s:%i: MUS buffer off by %ld bytes", - __FUNCTION__, __LINE__, (long)(cur - end));*/ - } - break; - case 5:/* Unknown */ - case 7:/* Unknown */ - default:/* shouldn't happen */ - /*_WM_ERROR_NEW("%s:%i: unrecognized event (%u)", - __FUNCTION__, __LINE__, event);*/ - goto _end; - } - - /* write it out */ - *out_local++ = status; - *out_local++ = bit1; - if (bitc == 2) - *out_local++ = bit2; - - /* write out our temp buffer */ - if (out_local != temp_buffer) - { - if (ctx.dstrem < sizeof(temp_buffer)) - resize_dst(&ctx); - - memcpy(ctx.dst_ptr, temp_buffer, out_local - temp_buffer); - ctx.dst_ptr += out_local - temp_buffer; - ctx.dstrem -= (uint32_t)(out_local - temp_buffer); - } - - if (event & 128) { - delta_time = 0; - do { - delta_time = (int32_t)((delta_time * 128 + (*cur & 127)) * (140.0 / (double)frequency)); - } while ((*cur++ & 128)); - } else { - delta_time = 0; - } - } - - /* 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 */ - - *out = ctx.dst; - *outsize = ctx.dstsize - ctx.dstrem; - ret = 0; - -_end: /* cleanup */ - if (ret < 0) { - free(ctx.dst); - *out = NULL; - *outsize = 0; - } - - return (ret); -} - 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 - -#ifdef __DJGPP__ -typedef signed char int8_t; -typedef unsigned char uint8_t; -typedef signed short int16_t; -typedef unsigned short uint16_t; -typedef signed long int32_t; -typedef unsigned long uint32_t; -#endif - -#ifdef __cplusplus -extern "C" -{ -#endif - -int AdlMidi_mus2midi(uint8_t *in, uint32_t insize, - uint8_t **out, uint32_t *outsize, - uint16_t frequency); - -#ifdef __cplusplus -} -#endif - -#endif /* MUSLIB_H */ diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp index 13160b2..446bae0 100644 --- a/src/adlmidi_private.hpp +++ b/src/adlmidi_private.hpp @@ -132,7 +132,14 @@ typedef int32_t ssize_t; #endif #include "file_reader.hpp" -#include "fraction.hpp" + +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER +// Rename class to avoid ABI conflicts +#define BW_MidiSequencer AdlMidiSequencer +#include "midi_sequencer.hpp" +typedef BW_MidiSequencer MidiSequencer; +#endif//ADLMIDI_DISABLE_MIDI_SEQUENCER + #ifndef ADLMIDI_HW_OPL #include "chips/opl_chip_base.h" #endif @@ -307,17 +314,11 @@ public: struct MIDIEventHooks { MIDIEventHooks() : - onEvent(NULL), - onEvent_userData(NULL), onNote(NULL), onNote_userData(NULL), onDebugMessage(NULL), onDebugMessage_userData(NULL) {} - //! Raw MIDI event hook - typedef void (*RawEventHook)(void *userdata, uint8_t type, uint8_t subtype, uint8_t channel, const uint8_t *data, size_t len); - RawEventHook onEvent; - void *onEvent_userData; //! Note on/off hooks typedef void (*NoteHook)(void *userdata, int adlchn, int note, int ins, int pressure, double bend); @@ -669,130 +670,20 @@ public: #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER /** - * @brief MIDI Event utility container + * @brief MIDI files player sequencer */ - class MidiEvent - { - public: - MidiEvent(); - - enum Types - { - T_UNKNOWN = 0x00, - T_NOTEOFF = 0x08,//size == 2 - T_NOTEON = 0x09,//size == 2 - T_NOTETOUCH = 0x0A,//size == 2 - T_CTRLCHANGE = 0x0B,//size == 2 - T_PATCHCHANGE = 0x0C,//size == 1 - T_CHANAFTTOUCH = 0x0D,//size == 1 - T_WHEEL = 0x0E,//size == 2 - - T_SYSEX = 0xF0,//size == len - T_SYSCOMSPOSPTR = 0xF2,//size == 2 - T_SYSCOMSNGSEL = 0xF3,//size == 1 - T_SYSEX2 = 0xF7,//size == len - T_SPECIAL = 0xFF - }; - enum SubTypes - { - ST_SEQNUMBER = 0x00,//size == 2 - ST_TEXT = 0x01,//size == len - ST_COPYRIGHT = 0x02,//size == len - ST_SQTRKTITLE = 0x03,//size == len - ST_INSTRTITLE = 0x04,//size == len - ST_LYRICS = 0x05,//size == len - ST_MARKER = 0x06,//size == len - ST_CUEPOINT = 0x07,//size == len - ST_DEVICESWITCH = 0x09,//size == len - 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 - ST_LOOPEND = 0xE2,//size == 0 - ST_RAWOPL = 0xE3//size == 0 - }; - //! 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 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 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 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 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 track; - PositionNew(): began(false), wait(0.0), absTimePosition(0.0), track() - {} - }; -#endif//ADLMIDI_DISABLE_MIDI_SEQUENCER + void initSequencerInterface(); +#endif //ADLMIDI_DISABLE_MIDI_SEQUENCER struct Setup { @@ -807,7 +698,6 @@ public: bool LogarithmicVolumes; int VolumeModel; //unsigned int SkipForward; - bool loopingIsEnabled; int ScaleModulators; bool fullRangeBrightnessCC74; @@ -854,31 +744,9 @@ private: uint32_t m_audioTickCounter; #endif -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - std::vector > 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 trackDataNew; -#endif - //! Missing instruments catches std::set caugh_missing_instruments; //! Missing melodic banks catches @@ -886,90 +754,49 @@ private: //! Missing percussion banks catches std::set 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 musTrackTitles; - std::vector musMarkers; - - fraction InvDeltaTicks, Tempo; - //! Tempo multiplier - double tempoMultiplier; - bool atEnd, - loopStart, - loopEnd, - invalidLoop; /*Loop points are invalid (loopStart after loopEnd or loopStart and loopEnd are on same place)*/ - char ____padding2[2]; -#endif OPL3 opl; int32_t outBuf[1024]; Setup m_setup; + bool LoadBank(const std::string &filename); + bool LoadBank(const void *data, size_t size); + bool LoadBank(FileAndMemReader &fr); + #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER /** - * @brief Utility function to read Big-Endian integer from raw binary data - * @param buffer Pointer to raw binary buffer - * @param nbytes Count of bytes to parse integer - * @return Extracted unsigned integer + * @brief MIDI file loading pre-process + * @return true on success, false on failure */ - static uint64_t ReadBEint(const void *buffer, size_t nbytes); + bool LoadMIDI_pre(); /** - * @brief Utility function to read Little-Endian integer from raw binary data - * @param buffer Pointer to raw binary buffer - * @param nbytes Count of bytes to parse integer - * @return Extracted unsigned integer + * @brief MIDI file loading post-process + * @return true on success, false on failure */ - static uint64_t ReadLEint(const void *buffer, size_t nbytes); + bool LoadMIDI_post(); /** - * @brief Standard MIDI Variable-Length numeric value parser without of validation - * @param [_inout] ptr Pointer to memory block that contains begin of variable-length value - * @return Unsigned integer that conains parsed variable-length value - */ - uint64_t ReadVarLen(uint8_t **ptr); - /** - * @brief Secure Standard MIDI Variable-Length numeric value parser with anti-out-of-range protection - * @param [_inout] ptr Pointer to memory block that contains begin of variable-length value, will be iterated forward - * @param [_in end Pointer to end of memory block where variable-length value is stored (after end of track) - * @param [_out] ok Reference to boolean which takes result of variable-length value parsing - * @return Unsigned integer that conains parsed variable-length value + * @brief Load music file from a file + * @param filename Path to music file + * @return true on success, false on failure */ - uint64_t ReadVarLenEx(uint8_t **ptr, uint8_t *end, bool &ok); -#endif - - bool LoadBank(const std::string &filename); - bool LoadBank(const void *data, size_t size); - bool LoadBank(FileAndMemReader &fr); -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER bool LoadMIDI(const std::string &filename); + + /** + * @brief Load music file from the memory block + * @param data pointer to the memory block + * @param size size of memory block + * @return true on success, false on failure + */ bool LoadMIDI(const void *data, size_t size); - bool LoadMIDI(FileAndMemReader &fr); /** * @brief Periodic tick handler. @@ -978,79 +805,129 @@ public: * @return desired number of seconds until next call */ double Tick(double s, double granularity); -#endif +#endif //ADLMIDI_DISABLE_MIDI_SEQUENCER /** * @brief Process extra iterators like vibrato or arpeggio * @param s seconds since last call */ - void TickIteratos(double s); + void TickIterators(double s); -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - /** - * @brief Change current position to specified time position in seconds - * @param seconds Absolute time position in seconds - */ - void seek(double seconds); + /* RealTime event triggers */ /** - * @brief Gives current time position in seconds - * @return Current time position in seconds + * @brief Reset state of all channels */ - double tell(); + void realTime_ResetState(); /** - * @brief Gives time length of current song in seconds - * @return Time length of current song in seconds + * @brief Note On event + * @param channel MIDI channel + * @param note Note key (from 0 to 127) + * @param velocity Velocity level (from 0 to 127) + * @return true if Note On event was accepted */ - double timeLength(); + bool realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity); /** - * @brief Gives loop start time position in seconds - * @return Loop start time position in seconds or -1 if song has no loop points + * @brief Note Off event + * @param channel MIDI channel + * @param note Note key (from 0 to 127) */ - double getLoopStart(); + void realTime_NoteOff(uint8_t channel, uint8_t note); /** - * @brief Gives loop end time position in seconds - * @return Loop end time position in seconds or -1 if song has no loop points + * @brief Note aftertouch event + * @param channel MIDI channel + * @param note Note key (from 0 to 127) + * @param atVal After-Touch level (from 0 to 127) */ - double getLoopEnd(); + void realTime_NoteAfterTouch(uint8_t channel, uint8_t note, uint8_t atVal); /** - * @brief Return to begin of current song + * @brief Channel aftertouch event + * @param channel MIDI channel + * @param atVal After-Touch level (from 0 to 127) */ - void rewind(); + void realTime_ChannelAfterTouch(uint8_t channel, uint8_t atVal); /** - * @brief Set tempo multiplier - * @param tempo Tempo multiplier: 1.0 - original tempo. >1 - faster, <1 - slower + * @brief Controller Change event + * @param channel MIDI channel + * @param type Type of controller + * @param value Value of the controller (from 0 to 127) */ - void setTempo(double tempo); -#endif//ADLMIDI_DISABLE_MIDI_SEQUENCER - - /* RealTime event triggers */ - void realTime_ResetState(); - - bool realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity); - void realTime_NoteOff(uint8_t channel, uint8_t note); - - void realTime_NoteAfterTouch(uint8_t channel, uint8_t note, uint8_t atVal); - void realTime_ChannelAfterTouch(uint8_t channel, uint8_t atVal); - void realTime_Controller(uint8_t channel, uint8_t type, uint8_t value); + /** + * @brief Patch change + * @param channel MIDI channel + * @param patch Patch Number (from 0 to 127) + */ void realTime_PatchChange(uint8_t channel, uint8_t patch); + /** + * @brief Pitch bend change + * @param channel MIDI channel + * @param pitch Concoctated raw pitch value + */ void realTime_PitchBend(uint8_t channel, uint16_t pitch); + + /** + * @brief Pitch bend change + * @param channel MIDI channel + * @param msb MSB of pitch value + * @param lsb LSB of pitch value + */ void realTime_PitchBend(uint8_t channel, uint8_t msb, uint8_t lsb); + /** + * @brief LSB Bank Change CC + * @param channel MIDI channel + * @param lsb LSB value of bank number + */ void realTime_BankChangeLSB(uint8_t channel, uint8_t lsb); + + /** + * @brief MSB Bank Change CC + * @param channel MIDI channel + * @param lsb MSB value of bank number + */ void realTime_BankChangeMSB(uint8_t channel, uint8_t msb); + + /** + * @brief Bank Change (united value) + * @param channel MIDI channel + * @param bank Bank number value + */ void realTime_BankChange(uint8_t channel, uint16_t bank); + /** + * @brief Turn off all notes and mute the sound of releasing notes + */ void realTime_panic(); + /** + * @brief Device switch (to extend 16-channels limit of MIDI standard) + * @param track MIDI track index + * @param data Device name + * @param length Length of device name string + */ + void realTime_deviceSwitch(size_t track, const char *data, size_t length); + /** + * @brief Currently selected device index + * @param track MIDI track index + * @return Multiple 16 value + */ + uint64_t realTime_currentDevice(size_t track); + + /** + * @brief Send raw OPL chip command + * @param reg OPL Register + * @param value Value to write + */ + void realTime_rawOPL(uint8_t reg, uint8_t value); + #if defined(ADLMIDI_AUDIO_TICK_HANDLER) // Audio rate tick handler void AudioTick(uint32_t chipId, uint32_t rate); @@ -1074,11 +951,6 @@ private: unsigned props_mask, int32_t select_adlchn = -1); -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - bool ProcessEventsNew(bool isSeek = false); - void HandleEvent(size_t tk, const MidiEvent &evt, int &status); -#endif - // Determine how good a candidate this adlchannel // would be for playing a note from this instrument. int64_t CalculateAdlChannelGoodness(size_t c, const MIDIchannel::NoteInfo::Phys &ins, uint16_t /*MidCh*/) const; diff --git a/src/adlmidi_sequencer.cpp b/src/adlmidi_sequencer.cpp new file mode 100644 index 0000000..8bc3569 --- /dev/null +++ b/src/adlmidi_sequencer.cpp @@ -0,0 +1,153 @@ +/* + * libADLMIDI is a free MIDI to WAV conversion library with OPL3 emulation + * + * Original ADLMIDI code: Copyright (c) 2010-2014 Joel Yliluoma + * ADLMIDI Library API: Copyright (c) 2015-2018 Vitaly Novichkov + * + * 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 . + */ + +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER + +#include "adlmidi_private.hpp" + +// Inlucde MIDI sequencer class implementation +#include "midi_sequencer_impl.hpp" + + +/**************************************************** + * Real-Time MIDI calls proxies * + ****************************************************/ + +static void rtNoteOn(void *userdata, uint8_t channel, uint8_t note, uint8_t velocity) +{ + MIDIplay *context = reinterpret_cast(userdata); + context->realTime_NoteOn(channel, note, velocity); +} + +static void rtNoteOff(void *userdata, uint8_t channel, uint8_t note) +{ + MIDIplay *context = reinterpret_cast(userdata); + context->realTime_NoteOff(channel, note); +} + +static void rtNoteAfterTouch(void *userdata, uint8_t channel, uint8_t note, uint8_t atVal) +{ + MIDIplay *context = reinterpret_cast(userdata); + context->realTime_NoteAfterTouch(channel, note, atVal); +} + +static void rtChannelAfterTouch(void *userdata, uint8_t channel, uint8_t atVal) +{ + MIDIplay *context = reinterpret_cast(userdata); + context->realTime_ChannelAfterTouch(channel, atVal); +} + +static void rtControllerChange(void *userdata, uint8_t channel, uint8_t type, uint8_t value) +{ + MIDIplay *context = reinterpret_cast(userdata); + context->realTime_Controller(channel, type, value); +} + +static void rtPatchChange(void *userdata, uint8_t channel, uint8_t patch) +{ + MIDIplay *context = reinterpret_cast(userdata); + context->realTime_PatchChange(channel, patch); +} + +static void rtPitchBend(void *userdata, uint8_t channel, uint8_t msb, uint8_t lsb) +{ + MIDIplay *context = reinterpret_cast(userdata); + context->realTime_PitchBend(channel, msb, lsb); +} + +static void rtSysEx(void *userdata, const uint8_t *msg, size_t size) +{ + MIDIplay *context = reinterpret_cast(userdata); + ADL_UNUSED(context); + ADL_UNUSED(msg); + ADL_UNUSED(size); + /* TODO: pass SysEx HERE! */ +} + + +/* NonStandard calls */ +static void rtRawOPL(void *userdata, uint8_t reg, uint8_t value) +{ + MIDIplay *context = reinterpret_cast(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(userdata); + context->realTime_deviceSwitch(track, data, length); +} + +static uint64_t rtCurrentDevice(void *userdata, size_t track) +{ + MIDIplay *context = reinterpret_cast(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(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.c b/src/adlmidi_xmi2mid.c deleted file mode 100644 index 3389709..0000000 --- a/src/adlmidi_xmi2mid.c +++ /dev/null @@ -1,1106 +0,0 @@ -/* - * XMIDI: Miles XMIDI to MID Library - * - * Copyright (C) 2001 Ryan Nunn - * Copyright (C) 2014 Bret Curtis - * Copyright (C) WildMIDI Developers 2015-2016 - * Copyright (c) 2015-2018 Vitaly Novichkov - * - * 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 */ - -#include -#include -#include -#include - -#include "adlmidi_xmi2mid.h" - -/* 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 { - int32_t time; - uint8_t status; - uint8_t data[2]; - uint32_t len; - uint8_t *buffer; - struct _midi_event *next; -} midi_event; - -typedef struct { - uint16_t type; - uint16_t tracks; -} midi_descriptor; - -struct xmi_ctx { - uint8_t *src, *src_ptr; - uint32_t srcsize; - uint32_t datastart; - uint8_t *dst, *dst_ptr; - uint32_t dstsize, dstrem; - uint32_t convert_type; - midi_descriptor info; - int bank127[16]; - midi_event **events; - signed short *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, - const int32_t time, const uint8_t status, const int size); -static int32_t ConvertSystemMessage(struct 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 uint32_t read1(struct xmi_ctx *ctx) -{ - uint8_t b0; - b0 = *ctx->src_ptr++; - return (b0); -} - -static uint32_t read2(struct xmi_ctx *ctx) -{ - uint8_t b0, b1; - b0 = *ctx->src_ptr++; - b1 = *ctx->src_ptr++; - return (b0 + ((uint32_t)b1 << 8)); -} - -static uint32_t read4(struct xmi_ctx *ctx) -{ - uint8_t b0, b1, b2, b3; - b3 = *ctx->src_ptr++; - b2 = *ctx->src_ptr++; - b1 = *ctx->src_ptr++; - b0 = *ctx->src_ptr++; - 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) -{ - memcpy(b, ctx->src_ptr, len); - ctx->src_ptr += len; -} - -#define DST_CHUNK 8192 -static void resize_dst(struct xmi_ctx *ctx) { - uint32_t pos = (uint32_t)(ctx->dst_ptr - ctx->dst); - ctx->dst = 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) -{ - if (ctx->dstrem < 1) - resize_dst(ctx); - *ctx->dst_ptr++ = val & 0xff; - ctx->dstrem--; -} - -static void write2(struct xmi_ctx *ctx, uint32_t val) -{ - if (ctx->dstrem < 2) - 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) -{ - if (ctx->dstrem < 4) - resize_dst(ctx); - *ctx->dst_ptr++ = (val>>24)&0xff; - *ctx->dst_ptr++ = (val>>16)&0xff; - *ctx->dst_ptr++ = (val>>8) & 0xff; - *ctx->dst_ptr++ = val & 0xff; - ctx->dstrem -= 4; -} - -static void seeksrc(struct xmi_ctx *ctx, uint32_t pos) { - ctx->src_ptr = ctx->src + pos; -} - -static void seekdst(struct xmi_ctx *ctx, uint32_t pos) { - ctx->dst_ptr = ctx->dst + pos; - while (ctx->dstsize < pos) - resize_dst(ctx); - ctx->dstrem = ctx->dstsize - pos; -} - -static void skipsrc(struct xmi_ctx *ctx, int32_t pos) { - ctx->src_ptr += pos; -} - -static void skipdst(struct 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); - ctx->dstrem = (uint32_t)(ctx->dstsize - newpos); -} - -static uint32_t getsrcsize(struct xmi_ctx *ctx) { - return (ctx->srcsize); -} - -static uint32_t getsrcpos(struct xmi_ctx *ctx) { - return (uint32_t)(ctx->src_ptr - ctx->src); -} - -static uint32_t getdstpos(struct xmi_ctx *ctx) { - return (uint32_t)(ctx->dst_ptr - ctx->dst); -} - -/* This is a default set of patches to convert from MT32 to GM - * The index is the MT32 Patch number and the value is the GM Patch - * 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] = { - 0, /* 0 Piano 1 */ - 1, /* 1 Piano 2 */ - 2, /* 2 Piano 3 (synth) */ - 4, /* 3 EPiano 1 */ - 4, /* 4 EPiano 2 */ - 5, /* 5 EPiano 3 */ - 5, /* 6 EPiano 4 */ - 3, /* 7 Honkytonk */ - 16, /* 8 Organ 1 */ - 17, /* 9 Organ 2 */ - 18, /* 10 Organ 3 */ - 16, /* 11 Organ 4 */ - 19, /* 12 Pipe Organ 1 */ - 19, /* 13 Pipe Organ 2 */ - 19, /* 14 Pipe Organ 3 */ - 21, /* 15 Accordion */ - 6, /* 16 Harpsichord 1 */ - 6, /* 17 Harpsichord 2 */ - 6, /* 18 Harpsichord 3 */ - 7, /* 19 Clavinet 1 */ - 7, /* 20 Clavinet 2 */ - 7, /* 21 Clavinet 3 */ - 8, /* 22 Celesta 1 */ - 8, /* 23 Celesta 2 */ - 62, /* 24 Synthbrass 1 (62) */ - 63, /* 25 Synthbrass 2 (63) */ - 62, /* 26 Synthbrass 3 Bank 8 */ - 63, /* 27 Synthbrass 4 Bank 8 */ - 38, /* 28 Synthbass 1 */ - 39, /* 29 Synthbass 2 */ - 38, /* 30 Synthbass 3 Bank 8 */ - 39, /* 31 Synthbass 4 Bank 8 */ - 88, /* 32 Fantasy */ - 90, /* 33 Harmonic Pan - No equiv closest is polysynth(90) :( */ - 52, /* 34 Choral ?? Currently set to SynthVox(54). Should it be ChoirAhhs(52)??? */ - 92, /* 35 Glass */ - 97, /* 36 Soundtrack */ - 99, /* 37 Atmosphere */ - 14, /* 38 Warmbell, sounds kind of like crystal(98) perhaps Tubular Bells(14) would be better. It is! */ - 54, /* 39 FunnyVox, sounds alot like Bagpipe(109) and Shania(111) */ - 98, /* 40 EchoBell, no real equiv, sounds like Crystal(98) */ - 96, /* 41 IceRain */ - 68, /* 42 Oboe 2001, no equiv, just patching it to normal oboe(68) */ - 95, /* 43 EchoPans, no equiv, setting to SweepPad */ - 81, /* 44 DoctorSolo Bank 8 */ - 87, /* 45 SchoolDaze, no real equiv */ - 112,/* 46 Bell Singer */ - 80, /* 47 SquareWave */ - 48, /* 48 Strings 1 */ - 48, /* 49 Strings 2 - should be 49 */ - 44, /* 50 Strings 3 (Synth) - Experimental set to Tremollo Strings - should be 50 */ - 45, /* 51 Pizzicato Strings */ - 40, /* 52 Violin 1 */ - 40, /* 53 Violin 2 ? Viola */ - 42, /* 54 Cello 1 */ - 42, /* 55 Cello 2 */ - 43, /* 56 Contrabass */ - 46, /* 57 Harp 1 */ - 46, /* 58 Harp 2 */ - 24, /* 59 Guitar 1 (Nylon) */ - 25, /* 60 Guitar 2 (Steel) */ - 26, /* 61 Elec Guitar 1 */ - 27, /* 62 Elec Guitar 2 */ - 104,/* 63 Sitar */ - 32, /* 64 Acou Bass 1 */ - 32, /* 65 Acou Bass 2 */ - 33, /* 66 Elec Bass 1 */ - 34, /* 67 Elec Bass 2 */ - 36, /* 68 Slap Bass 1 */ - 37, /* 69 Slap Bass 2 */ - 35, /* 70 Fretless Bass 1 */ - 35, /* 71 Fretless Bass 2 */ - 73, /* 72 Flute 1 */ - 73, /* 73 Flute 2 */ - 72, /* 74 Piccolo 1 */ - 72, /* 75 Piccolo 2 */ - 74, /* 76 Recorder */ - 75, /* 77 Pan Pipes */ - 64, /* 78 Sax 1 */ - 65, /* 79 Sax 2 */ - 66, /* 80 Sax 3 */ - 67, /* 81 Sax 4 */ - 71, /* 82 Clarinet 1 */ - 71, /* 83 Clarinet 2 */ - 68, /* 84 Oboe */ - 69, /* 85 English Horn (Cor Anglais) */ - 70, /* 86 Bassoon */ - 22, /* 87 Harmonica */ - 56, /* 88 Trumpet 1 */ - 56, /* 89 Trumpet 2 */ - 57, /* 90 Trombone 1 */ - 57, /* 91 Trombone 2 */ - 60, /* 92 French Horn 1 */ - 60, /* 93 French Horn 2 */ - 58, /* 94 Tuba */ - 61, /* 95 Brass Section 1 */ - 61, /* 96 Brass Section 2 */ - 11, /* 97 Vibes 1 */ - 11, /* 98 Vibes 2 */ - 99, /* 99 Syn Mallet Bank 1 */ - 112,/* 100 WindBell no real equiv Set to TinkleBell(112) */ - 9, /* 101 Glockenspiel */ - 14, /* 102 Tubular Bells */ - 13, /* 103 Xylophone */ - 12, /* 104 Marimba */ - 107,/* 105 Koto */ - 111,/* 106 Sho?? set to Shanai(111) */ - 77, /* 107 Shakauhachi */ - 78, /* 108 Whistle 1 */ - 78, /* 109 Whistle 2 */ - 76, /* 110 Bottle Blow */ - 76, /* 111 Breathpipe no real equiv set to bottle blow(76) */ - 47, /* 112 Timpani */ - 117,/* 113 Melodic Tom */ - 116,/* 114 Deap Snare no equiv, set to Taiko(116) */ - 118,/* 115 Electric Perc 1 */ - 118,/* 116 Electric Perc 2 */ - 116,/* 117 Taiko */ - 115,/* 118 Taiko Rim, no real equiv, set to Woodblock(115) */ - 119,/* 119 Cymbal, no real equiv, set to reverse cymbal(119) */ - 115,/* 120 Castanets, no real equiv, in GM set to Woodblock(115) */ - 112,/* 121 Triangle, no real equiv, set to TinkleBell(112) */ - 55, /* 122 Orchestral Hit */ - 124,/* 123 Telephone */ - 123,/* 124 BirdTweet */ - 94, /* 125 Big Notes Pad no equiv, set to halo pad (94) */ - 98, /* 126 Water Bell set to Crystal Pad(98) */ - 121 /* 127 Jungle Tune set to Breath Noise */ -}; - -/* Same as above, except include patch changes - * so GS instruments can be used */ -static const char mt32asgs[256] = { - 0, 0, /* 0 Piano 1 */ - 1, 0, /* 1 Piano 2 */ - 2, 0, /* 2 Piano 3 (synth) */ - 4, 0, /* 3 EPiano 1 */ - 4, 0, /* 4 EPiano 2 */ - 5, 0, /* 5 EPiano 3 */ - 5, 0, /* 6 EPiano 4 */ - 3, 0, /* 7 Honkytonk */ - 16, 0, /* 8 Organ 1 */ - 17, 0, /* 9 Organ 2 */ - 18, 0, /* 10 Organ 3 */ - 16, 0, /* 11 Organ 4 */ - 19, 0, /* 12 Pipe Organ 1 */ - 19, 0, /* 13 Pipe Organ 2 */ - 19, 0, /* 14 Pipe Organ 3 */ - 21, 0, /* 15 Accordion */ - 6, 0, /* 16 Harpsichord 1 */ - 6, 0, /* 17 Harpsichord 2 */ - 6, 0, /* 18 Harpsichord 3 */ - 7, 0, /* 19 Clavinet 1 */ - 7, 0, /* 20 Clavinet 2 */ - 7, 0, /* 21 Clavinet 3 */ - 8, 0, /* 22 Celesta 1 */ - 8, 0, /* 23 Celesta 2 */ - 62, 0, /* 24 Synthbrass 1 (62) */ - 63, 0, /* 25 Synthbrass 2 (63) */ - 62, 0, /* 26 Synthbrass 3 Bank 8 */ - 63, 0, /* 27 Synthbrass 4 Bank 8 */ - 38, 0, /* 28 Synthbass 1 */ - 39, 0, /* 29 Synthbass 2 */ - 38, 0, /* 30 Synthbass 3 Bank 8 */ - 39, 0, /* 31 Synthbass 4 Bank 8 */ - 88, 0, /* 32 Fantasy */ - 90, 0, /* 33 Harmonic Pan - No equiv closest is polysynth(90) :( */ - 52, 0, /* 34 Choral ?? Currently set to SynthVox(54). Should it be ChoirAhhs(52)??? */ - 92, 0, /* 35 Glass */ - 97, 0, /* 36 Soundtrack */ - 99, 0, /* 37 Atmosphere */ - 14, 0, /* 38 Warmbell, sounds kind of like crystal(98) perhaps Tubular Bells(14) would be better. It is! */ - 54, 0, /* 39 FunnyVox, sounds alot like Bagpipe(109) and Shania(111) */ - 98, 0, /* 40 EchoBell, no real equiv, sounds like Crystal(98) */ - 96, 0, /* 41 IceRain */ - 68, 0, /* 42 Oboe 2001, no equiv, just patching it to normal oboe(68) */ - 95, 0, /* 43 EchoPans, no equiv, setting to SweepPad */ - 81, 0, /* 44 DoctorSolo Bank 8 */ - 87, 0, /* 45 SchoolDaze, no real equiv */ - 112, 0, /* 46 Bell Singer */ - 80, 0, /* 47 SquareWave */ - 48, 0, /* 48 Strings 1 */ - 48, 0, /* 49 Strings 2 - should be 49 */ - 44, 0, /* 50 Strings 3 (Synth) - Experimental set to Tremollo Strings - should be 50 */ - 45, 0, /* 51 Pizzicato Strings */ - 40, 0, /* 52 Violin 1 */ - 40, 0, /* 53 Violin 2 ? Viola */ - 42, 0, /* 54 Cello 1 */ - 42, 0, /* 55 Cello 2 */ - 43, 0, /* 56 Contrabass */ - 46, 0, /* 57 Harp 1 */ - 46, 0, /* 58 Harp 2 */ - 24, 0, /* 59 Guitar 1 (Nylon) */ - 25, 0, /* 60 Guitar 2 (Steel) */ - 26, 0, /* 61 Elec Guitar 1 */ - 27, 0, /* 62 Elec Guitar 2 */ - 104, 0, /* 63 Sitar */ - 32, 0, /* 64 Acou Bass 1 */ - 32, 0, /* 65 Acou Bass 2 */ - 33, 0, /* 66 Elec Bass 1 */ - 34, 0, /* 67 Elec Bass 2 */ - 36, 0, /* 68 Slap Bass 1 */ - 37, 0, /* 69 Slap Bass 2 */ - 35, 0, /* 70 Fretless Bass 1 */ - 35, 0, /* 71 Fretless Bass 2 */ - 73, 0, /* 72 Flute 1 */ - 73, 0, /* 73 Flute 2 */ - 72, 0, /* 74 Piccolo 1 */ - 72, 0, /* 75 Piccolo 2 */ - 74, 0, /* 76 Recorder */ - 75, 0, /* 77 Pan Pipes */ - 64, 0, /* 78 Sax 1 */ - 65, 0, /* 79 Sax 2 */ - 66, 0, /* 80 Sax 3 */ - 67, 0, /* 81 Sax 4 */ - 71, 0, /* 82 Clarinet 1 */ - 71, 0, /* 83 Clarinet 2 */ - 68, 0, /* 84 Oboe */ - 69, 0, /* 85 English Horn (Cor Anglais) */ - 70, 0, /* 86 Bassoon */ - 22, 0, /* 87 Harmonica */ - 56, 0, /* 88 Trumpet 1 */ - 56, 0, /* 89 Trumpet 2 */ - 57, 0, /* 90 Trombone 1 */ - 57, 0, /* 91 Trombone 2 */ - 60, 0, /* 92 French Horn 1 */ - 60, 0, /* 93 French Horn 2 */ - 58, 0, /* 94 Tuba */ - 61, 0, /* 95 Brass Section 1 */ - 61, 0, /* 96 Brass Section 2 */ - 11, 0, /* 97 Vibes 1 */ - 11, 0, /* 98 Vibes 2 */ - 99, 0, /* 99 Syn Mallet Bank 1 */ - 112, 0, /* 100 WindBell no real equiv Set to TinkleBell(112) */ - 9, 0, /* 101 Glockenspiel */ - 14, 0, /* 102 Tubular Bells */ - 13, 0, /* 103 Xylophone */ - 12, 0, /* 104 Marimba */ - 107, 0, /* 105 Koto */ - 111, 0, /* 106 Sho?? set to Shanai(111) */ - 77, 0, /* 107 Shakauhachi */ - 78, 0, /* 108 Whistle 1 */ - 78, 0, /* 109 Whistle 2 */ - 76, 0, /* 110 Bottle Blow */ - 76, 0, /* 111 Breathpipe no real equiv set to bottle blow(76) */ - 47, 0, /* 112 Timpani */ - 117, 0, /* 113 Melodic Tom */ - 116, 0, /* 114 Deap Snare no equiv, set to Taiko(116) */ - 118, 0, /* 115 Electric Perc 1 */ - 118, 0, /* 116 Electric Perc 2 */ - 116, 0, /* 117 Taiko */ - 115, 0, /* 118 Taiko Rim, no real equiv, set to Woodblock(115) */ - 119, 0, /* 119 Cymbal, no real equiv, set to reverse cymbal(119) */ - 115, 0, /* 120 Castanets, no real equiv, in GM set to Woodblock(115) */ - 112, 0, /* 121 Triangle, no real equiv, set to TinkleBell(112) */ - 55, 0, /* 122 Orchestral Hit */ - 124, 0, /* 123 Telephone */ - 123, 0, /* 124 BirdTweet */ - 94, 0, /* 125 Big Notes Pad no equiv, set to halo pad (94) */ - 98, 0, /* 126 Water Bell set to Crystal Pad(98) */ - 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; - unsigned int i; - int ret = -1; - - if (convert_type > XMIDI_CONVERT_MT32_TO_GS) { - /*_WM_ERROR_NEW("%s:%i: %d is an invalid conversion type.", __FUNCTION__, __LINE__, convert_type);*/ - return (ret); - } - - memset(&ctx, 0, sizeof(struct xmi_ctx)); - ctx.src = ctx.src_ptr = in; - ctx.srcsize = insize; - ctx.convert_type = convert_type; - - if (ParseXMI(&ctx) < 0) { - /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_NOT_XMI, NULL, 0);*/ - goto _end; - } - - if (ExtractTracks(&ctx) < 0) { - /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_NOT_MIDI, NULL, 0);*/ - goto _end; - } - - ctx.dst = 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'); - - write4(&ctx, 6); - - write2(&ctx, ctx.info.type); - write2(&ctx, ctx.info.tracks); - write2(&ctx, ctx.timing[0]);/* write divisions from track0 */ - - for (i = 0; i < ctx.info.tracks; i++) - ConvertListToMTrk(&ctx, ctx.events[i]); - *out = ctx.dst; - *outsize = ctx.dstsize - ctx.dstrem; - ret = 0; - -_end: /* cleanup */ - if (ret < 0) { - free(ctx.dst); - *out = NULL; - *outsize = 0; - } - if (ctx.events) { - for (i = 0; i < ctx.info.tracks; i++) - DeleteEventList(ctx.events[i]); - free(ctx.events); - } - free(ctx.timing); - - return (ret); -} - -static void DeleteEventList(midi_event *mlist) { - midi_event *event; - midi_event *next; - - next = mlist; - - while ((event = next) != NULL) { - next = event->next; - free(event->buffer); - free(event); - } -} - -/* Sets current to the new event and updates list */ -static void CreateNewEvent(struct xmi_ctx *ctx, int32_t time) { - if (!ctx->list) { - ctx->list = ctx->current = calloc(1, sizeof(midi_event)); - ctx->current->time = (time < 0)? 0 : time; - return; - } - - if (time < 0) { - midi_event *event = calloc(1, sizeof(midi_event)); - event->next = ctx->list; - ctx->list = ctx->current = event; - return; - } - - if (ctx->current->time > time) - ctx->current = ctx->list; - - while (ctx->current->next) { - if (ctx->current->next->time > time) { - midi_event *event = calloc(1, sizeof(midi_event)); - event->next = ctx->current->next; - ctx->current->next = event; - ctx->current = event; - ctx->current->time = time; - return; - } - - ctx->current = ctx->current->next; - } - - ctx->current->next = 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) { - int i; - uint32_t data; - - *quant = 0; - for (i = 0; i < 4; i++) { - data = read1(ctx); - *quant <<= 7; - *quant |= data & 0x7F; - - if (!(data & 0x80)) { - i++; - break; - } - } - return (i); -} - -/* XMIDI Delta Variable Length Quantity */ -static int GetVLQ2(struct xmi_ctx *ctx, uint32_t *quant) { - int i; - int32_t data; - - *quant = 0; - for (i = 0; i < 4; i++) { - data = read1(ctx); - if (data & 0x80) { - skipsrc(ctx, -1); - break; - } - *quant += data; - } - return (i); -} - -static int PutVLQ(struct xmi_ctx *ctx, uint32_t value) { - int32_t buffer; - int i = 1, j; - buffer = value & 0x7F; - while (value >>= 7) { - buffer <<= 8; - buffer |= ((value & 0x7F) | 0x80); - i++; - } - for (j = 0; j < i; j++) { - write1(ctx, buffer & 0xFF); - buffer >>= 8; - } - - return (i); -} - -/* Converts Events - * - * Source is at the first data byte - * size 1 is single data byte - * 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, - const uint8_t status, const int size) { - uint32_t delta = 0; - int32_t data; - midi_event *prev; - int i; - - data = read1(ctx); - - /*HACK!*/ - if (((status >> 4) == 0xB) && (status & 0xF) != 9 && (data == 114)) { - data = 32; /*Change XMI 114 controller into XG bank*/ - } - - /* Bank changes are handled here */ - if ((status >> 4) == 0xB && data == 0) { - data = read1(ctx); - - ctx->bank127[status & 0xF] = 0; - - if ( ctx->convert_type == XMIDI_CONVERT_MT32_TO_GM || - ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS || - ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS127 || - (ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS127DRUM - && (status & 0xF) == 9) ) - return (2); - - CreateNewEvent(ctx, time); - ctx->current->status = status; - ctx->current->data[0] = 0; - ctx->current->data[1] = data == 127 ? 0 : data;/*HACK:*/ - - if (ctx->convert_type == XMIDI_CONVERT_GS127_TO_GS && data == 127) - ctx->bank127[status & 0xF] = 1; - - return (2); - } - - /* Handling for patch change mt32 conversion, probably should go elsewhere */ - if ((status >> 4) == 0xC && (status&0xF) != 9 - && ctx->convert_type != XMIDI_CONVERT_NOCONVERSION) - { - if (ctx->convert_type == XMIDI_CONVERT_MT32_TO_GM) - { - data = 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); - ctx->current->status = 0xB0 | (status&0xF); - ctx->current->data[0] = 0; - ctx->current->data[1] = mt32asgs[data*2+1]; - - data = mt32asgs[data*2]; - } - else if (ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS127) - { - CreateNewEvent (ctx, time); - ctx->current->status = 0xB0 | (status&0xF); - ctx->current->data[0] = 0; - ctx->current->data[1] = 127; - } - } - /* Drum track handling */ - 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); - ctx->current->status = 0xB9; - ctx->current->data[0] = 0; - ctx->current->data[1] = 127; - } - - CreateNewEvent(ctx, time); - ctx->current->status = status; - - ctx->current->data[0] = data; - - if (size == 1) - return (1); - - ctx->current->data[1] = read1(ctx); - - if (size == 2) - return (2); - - /* XMI Note On handling */ - prev = ctx->current; - i = GetVLQ(ctx, &delta); - CreateNewEvent(ctx, time + delta * 3); - - ctx->current->status = status; - ctx->current->data[0] = data; - ctx->current->data[1] = 0; - ctx->current = prev; - - return (i + 2); -} - -/* Simple routine to convert system messages */ -static int32_t ConvertSystemMessage(struct xmi_ctx *ctx, const int32_t time, - const uint8_t status) { - int32_t i = 0; - - CreateNewEvent(ctx, time); - ctx->current->status = status; - - /* Handling of Meta events */ - if (status == 0xFF) { - ctx->current->data[0] = read1(ctx); - i++; - } - - i += 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); - - return (i + ctx->current->len); -} - -/* XMIDI and Midi to List - * Returns XMIDI PPQN */ -static int32_t ConvertFiletoList(struct 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); - - /* Set Drum track to correct setting if required */ - if (ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS127) { - 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); - time += data * 3; - - status = read1(ctx); - - switch (status >> 4) { - case MIDI_STATUS_NOTE_ON: - 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); - break; - - /* 1 byte data */ - case MIDI_STATUS_PROG_CHANGE: - case MIDI_STATUS_PRESSURE: - ConvertEvent(ctx, time, status, 1); - break; - - case MIDI_STATUS_SYSEX: - if (status == 0xFF) { - int32_t pos = getsrcpos(ctx); - uint32_t dat = 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); - tempo *= 3; - tempo_set = 1; - } else if (dat == 0x51 && tempo_set) /* Skip any other tempo changes */ - { - GetVLQ(ctx, &dat); - skipsrc(ctx, dat); - break; - } - - seeksrc(ctx, pos); - } - ConvertSystemMessage(ctx, time, status); - break; - - default: - break; - } - } - return ((tempo * 3) / 25000); -} - -/* 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) { - int32_t time = 0; - midi_event *event; - uint32_t delta; - uint8_t last_status = 0; - uint32_t i = 8; - uint32_t j; - uint32_t size_pos, cur_pos; - int end = 0; - - write1(ctx, 'M'); - write1(ctx, 'T'); - write1(ctx, 'r'); - write1(ctx, 'k'); - - size_pos = getdstpos(ctx); - skipdst(ctx, 4); - - for (event = mlist; event && !end; event = event->next) { - delta = (event->time - time); - time = event->time; - - i += PutVLQ(ctx, delta); - - if ((event->status != last_status) || (event->status >= 0xF0)) { - write1(ctx, event->status); - i++; - } - - last_status = event->status; - - switch (event->status >> 4) { - /* 2 bytes data - * Note off, Note on, Aftertouch, Controller and Pitch Wheel */ - case 0x8: - case 0x9: - case 0xA: - case 0xB: - case 0xE: - write1(ctx, event->data[0]); - write1(ctx, event->data[1]); - i += 2; - break; - - /* 1 bytes data - * Program Change and Channel Pressure */ - case 0xC: - case 0xD: - write1(ctx, event->data[0]); - i++; - break; - - /* Variable length - * SysEx */ - case 0xF: - if (event->status == 0xFF) { - if (event->data[0] == 0x2f) - end = 1; - write1(ctx, event->data[0]); - i++; - } - i += PutVLQ(ctx, event->len); - if (event->len) { - for (j = 0; j < event->len; j++) { - write1(ctx, event->buffer[j]); - i++; - } - } - break; - - /* Never occur */ - default: - /*_WM_DEBUG_MSG("%s: unrecognized event", __FUNCTION__);*/ - break; - } - } - - cur_pos = getdstpos(ctx); - seekdst(ctx, size_pos); - write4(ctx, i - 8); - seekdst(ctx, cur_pos); - - return (i); -} - -/* Assumes correct xmidi */ -static uint32_t ExtractTracksFromXmi(struct 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) { - /* Read first 4 bytes of name */ - copy(ctx, buf, 4); - len = read4(ctx); - - /* Skip the FORM entries */ - if (!memcmp(buf, "FORM", 4)) { - skipsrc(ctx, 4); - copy(ctx, buf, 4); - len = read4(ctx); - } - - if (memcmp(buf, "EVNT", 4)) { - skipsrc(ctx, (len + 1) & ~1); - continue; - } - - ctx->list = NULL; - begin = getsrcpos(ctx); - - /* Convert it */ - if (!(ppqn = ConvertFiletoList(ctx))) { - /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, NULL, 0);*/ - break; - } - ctx->timing[num] = ppqn; - ctx->events[num] = ctx->list; - - /* Increment Counter */ - num++; - - /* go to start of next track */ - seeksrc(ctx, begin + ((len + 1) & ~1)); - } - - /* Return how many were converted */ - return (num); -} - -static int ParseXMI(struct xmi_ctx *ctx) { - uint32_t i; - uint32_t start; - uint32_t len; - uint32_t chunk_len; - uint32_t file_size; - char buf[32]; - - file_size = getsrcsize(ctx); - if (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); - - /* Could be XMIDI */ - if (!memcmp(buf, "FORM", 4)) { - /* Read length of */ - len = read4(ctx); - - start = getsrcpos(ctx); - if (start + 4 > file_size) - goto badfile; - - /* Read 4 bytes of type */ - copy(ctx, buf, 4); - - /* XDIRless XMIDI, we can handle them here. */ - if (!memcmp(buf, "XMID", 4)) { - /*_WM_DEBUG_MSG("Warning: XMIDI without XDIR");*/ - ctx->info.tracks = 1; - } - /* Not an XMIDI that we recognise */ - else if (memcmp(buf, "XDIR", 4)) { - goto badfile; - } - else { /* Seems Valid */ - ctx->info.tracks = 0; - - for (i = 4; i < len; i++) { - /* check too short files */ - if (getsrcpos(ctx) + 10 > file_size) - break; - - /* Read 4 bytes of type */ - copy(ctx, buf, 4); - - /* Read length of chunk */ - chunk_len = read4(ctx); - - /* Add eight bytes */ - i += 8; - - if (memcmp(buf, "INFO", 4)) { - /* Must align */ - skipsrc(ctx, (chunk_len + 1) & ~1); - i += (chunk_len + 1) & ~1; - continue; - } - - /* Must be at least 2 bytes long */ - if (chunk_len < 2) - break; - - ctx->info.tracks = read2(ctx); - break; - } - - /* Didn't get to fill the header */ - if (ctx->info.tracks == 0) { - goto badfile; - } - - /* Ok now to start part 2 - * Goto the right place */ - seeksrc(ctx, start + ((len + 1) & ~1)); - if (getsrcpos(ctx) + 12 > file_size) - goto badfile; - - /* Read 4 bytes of type */ - copy(ctx, buf, 4); - - if (memcmp(buf, "CAT ", 4)) { - /*_WM_ERROR_NEW("XMI error: expected \"CAT \", found \"%c%c%c%c\".", - buf[0], buf[1], buf[2], buf[3]);*/ - return (-1); - } - - /* Now read length of this track */ - read4(ctx); - - /* Read 4 bytes of type */ - copy(ctx, buf, 4); - - if (memcmp(buf, "XMID", 4)) { - /*_WM_ERROR_NEW("XMI error: expected \"XMID\", found \"%c%c%c%c\".", - buf[0], buf[1], buf[2], buf[3]);*/ - return (-1); - } - - /* Valid XMID */ - ctx->datastart = getsrcpos(ctx); - return (0); - } - } - - return (-1); -} - -static int ExtractTracks(struct xmi_ctx *ctx) { - uint32_t i; - - ctx->events = calloc(ctx->info.tracks, sizeof(midi_event*)); - ctx->timing = 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); - - if (i != ctx->info.tracks) { - /*_WM_ERROR_NEW("XMI error: extracted only %u out of %u tracks from XMIDI", - ctx->info.tracks, i);*/ - return (-1); - } - - return (0); -} - 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 - -#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/cvt_mus2mid.hpp b/src/cvt_mus2mid.hpp new file mode 100644 index 0000000..5a465c2 --- /dev/null +++ b/src/cvt_mus2mid.hpp @@ -0,0 +1,461 @@ +/* + * MUS2MIDI: MUS to MIDI Library + * + * Copyright (C) 2014 Bret Curtis + * Copyright (C) WildMIDI Developers 2015-2016 + * ADLMIDI Library API: Copyright (c) 2015-2018 Vitaly Novichkov + * + * 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. + */ + +#include +#include +#include +#include + +#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 MUS_FREQUENCY 140 /* default Hz or BPM */ + +#if 0 /* older units: */ +#define MUS_TEMPO 0x001aa309 /* MPQN: 60000000 / 34.37Hz = 1745673 */ +#define MUS_DIVISION 0x0059 /* 89 -- used by many mus2midi converters */ +#endif + +#define MUS_TEMPO 0x00068A1B /* MPQN: 60000000 / 140BPM (140Hz) = 428571 */ + /* 0x000D1436 -> MPQN: 60000000 / 70BPM (70Hz) = 857142 */ + +#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 */ + +/* New + * QLS: MPQN/1000000 = 0.428571 + * TDPS: QLS/PPQN = 0.428571/136 = 0.003151257 + * PPQN: 136 + * + * QLS: MPQN/1000000 = 0.428571 + * TDPS: QLS/PPQN = 0.428571/257 = 0.001667591 + * PPQN: 257 + * + * QLS: MPQN/1000000 = 0.857142 + * TDPS: QLS/PPQN = 0.857142/267 = 0.00321027 + * PPQN: 267 + * + * QLS: MPQN/1000000 = 0.857142 + * TDPS: QLS/PPQN = 0.857142/505 = 0.001697311 + * PPQN: 505 + * + * Old + * QLS: MPQN/1000000 = 1.745673 + * TDPS: QLS/PPQN = 1.745673 / 89 = 0.019614303 (seconds per tick) + * PPQN: (TDPS = QLS/PPQN) (0.019614303 = 1.745673/PPQN) (0.019614303*PPQN = 1.745673) (PPQN = 89.000001682) + * + */ + +#define MUSEVENT_KEYOFF 0 +#define MUSEVENT_KEYON 1 +#define MUSEVENT_PITCHWHEEL 2 +#define MUSEVENT_CHANNELMODE 3 +#define MUSEVENT_CONTROLLERCHANGE 4 +#define MUSEVENT_END 6 + +#define MUS_MIDI_MAXCHANNELS 16 + +static char MUS_ID[] = { 'M', 'U', 'S', 0x1A }; + +static uint8_t mus_midimap[] = +{/* MIDI Number Description */ + 0, /* 0 program change */ + 0, /* 1 bank selection */ + 0x01, /* 2 Modulation pot (frequency vibrato depth) */ + 0x07, /* 3 Volume: 0-silent, ~100-normal, 127-loud */ + 0x0A, /* 4 Pan (balance) pot: 0-left, 64-center (default), 127-right */ + 0x0B, /* 5 Expression pot */ + 0x5B, /* 6 Reverb depth */ + 0x5D, /* 7 Chorus depth */ + 0x40, /* 8 Sustain pedal */ + 0x43, /* 9 Soft pedal */ + 0x78, /* 10 All sounds off */ + 0x7B, /* 11 All notes off */ + 0x7E, /* 12 Mono (use numchannels + 1) */ + 0x7F, /* 13 Poly */ + 0x79, /* 14 reset all controllers */ +}; + +typedef struct MUSHeader { + char ID[4]; /* identifier: "MUS" 0x1A */ + uint16_t scoreLen; + uint16_t scoreStart; + uint16_t channels; /* count of primary channels */ + uint16_t sec_channels; /* count of secondary channels */ + uint16_t instrCnt; +} MUSHeader ; +#define MUS_HEADERSIZE 14 + +typedef struct MidiHeaderChunk { + char name[4]; + int32_t length; + int16_t format; /* make 0 */ + int16_t ntracks;/* make 1 */ + int16_t division; /* 0xe250 ?? */ +} MidiHeaderChunk; +#define MIDI_HEADERSIZE 14 + +typedef struct MidiTrackChunk { + char name[4]; + int32_t length; +} MidiTrackChunk; +#define TRK_CHUNKSIZE 8 + +struct mus_ctx { + uint8_t *src, *src_ptr; + uint32_t srcsize; + uint32_t datastart; + uint8_t *dst, *dst_ptr; + uint32_t dstsize, dstrem; +}; + +#define DST_CHUNK 8192 +static void mus2mid_resize_dst(struct mus_ctx *ctx) { + uint32_t pos = (uint32_t)(ctx->dst_ptr - ctx->dst); + 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 mus2mid_write1(struct mus_ctx *ctx, uint32_t val) +{ + if (ctx->dstrem < 1) + mus2mid_resize_dst(ctx); + *ctx->dst_ptr++ = val & 0xff; + ctx->dstrem--; +} + +static void mus2mid_write2(struct mus_ctx *ctx, uint32_t val) +{ + if (ctx->dstrem < 2) + mus2mid_resize_dst(ctx); + *ctx->dst_ptr++ = (val>>8) & 0xff; + *ctx->dst_ptr++ = val & 0xff; + ctx->dstrem -= 2; +} + +static void mus2mid_write4(struct mus_ctx *ctx, uint32_t val) +{ + if (ctx->dstrem < 4) + mus2mid_resize_dst(ctx); + *ctx->dst_ptr++ = (val>>24)&0xff; + *ctx->dst_ptr++ = (val>>16)&0xff; + *ctx->dst_ptr++ = (val>>8) & 0xff; + *ctx->dst_ptr++ = val & 0xff; + ctx->dstrem -= 4; +} + +static void mus2mid_seekdst(struct mus_ctx *ctx, uint32_t pos) { + ctx->dst_ptr = ctx->dst + pos; + while (ctx->dstsize < pos) + mus2mid_resize_dst(ctx); + ctx->dstrem = ctx->dstsize - 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) + mus2mid_resize_dst(ctx); + ctx->dstrem = (uint32_t)(ctx->dstsize - newpos); +} + +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 mus2mid_writevarlen(int32_t value, uint8_t *out) +{ + int32_t buffer, count = 0; + + buffer = value & 0x7f; + while ((value >>= 7) > 0) { + buffer <<= 8; + buffer += 0x80; + buffer += (value & 0x7f); + } + + while (1) { + ++count; + *out = (uint8_t)buffer; + ++out; + if (buffer & 0x80) + buffer >>= 8; + else + break; + } + return (count); +} + +#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)) + +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[MUS_MIDI_MAXCHANNELS]; + int channelMap[MUS_MIDI_MAXCHANNELS], currentChannel; + + if (insize < MUS_HEADERSIZE) { + /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too short)", 0);*/ + return (-1); + } + + if (!frequency) + frequency = MUS_FREQUENCY; + + /* read the MUS header and set our location */ + memcpy(header.ID, in, 4); + 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);*/ + return (-1); + } + if (insize < (uint32_t)header.scoreLen + (uint32_t)header.scoreStart) { + /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, "(too short)", 0);*/ + return (-1); + } + /* channel #15 should be excluded in the numchannels field: */ + if (header.channels > MUS_MIDI_MAXCHANNELS - 1) { + /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_INVALID, NULL, 0);*/ + return (-1); + } + + memset(&ctx, 0, sizeof(struct mus_ctx)); + ctx.src = ctx.src_ptr = in; + ctx.srcsize = insize; + + 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 < 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 */ + 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 = 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 */ + 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 */ + 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; + end = cur + header.scoreLen; + + currentChannel = 0; + delta_time = 0; + + /* main loop */ + while(cur < end){ + /*printf("LOOP DEBUG: %d\r\n",iterator++);*/ + uint8_t channel; + uint8_t event; + uint8_t temp_buffer[32]; /* temp buffer for current iterator */ + uint8_t *out_local = temp_buffer; + uint8_t status, bit1, bit2, bitc = 2; + + /* read in current bit */ + event = *cur++; + channel = (event & 15); /* current channel */ + + /* write variable length delta time */ + out_local += mus2mid_writevarlen(delta_time, out_local); + + /* set all channels to 127 (max) volume */ + if (channelMap[channel] < 0) { + *out_local++ = 0xB0 + currentChannel; + *out_local++ = 0x07; + *out_local++ = 127; + *out_local++ = 0x00; + channelMap[channel] = currentChannel++; + if (currentChannel == 9) + ++currentChannel; + } + status = channelMap[channel]; + + /* handle events */ + switch ((event & 122) >> 4){ + case MUSEVENT_KEYOFF: + status |= 0x80; + bit1 = *cur++; + bit2 = 0x40; + break; + case MUSEVENT_KEYON: + status |= 0x90; + bit1 = *cur & 127; + if (*cur++ & 128) /* volume bit? */ + channel_volume[channelMap[channel]] = *cur++; + bit2 = channel_volume[channelMap[channel]]; + break; + case MUSEVENT_PITCHWHEEL: + status |= 0xE0; + bit1 = (*cur & 1) >> 6; + bit2 = (*cur++ >> 1) & 127; + break; + case MUSEVENT_CHANNELMODE: + status |= 0xB0; + 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 = mus_midimap[*cur++]; + bit2 = (*cur++ == 12) ? header.channels + 1 : 0x00; + break; + case MUSEVENT_CONTROLLERCHANGE: + if (*cur == 0) { + cur++; + status |= 0xC0; + bit1 = *cur++; + bit2 = 0;/* silence bogus warnings */ + bitc = 1; + } else { + status |= 0xB0; + 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 = mus_midimap[*cur++]; + bit2 = *cur++; + } + break; + case MUSEVENT_END: /* End */ + status = 0xff; + bit1 = 0x2f; + bit2 = 0x00; + if (cur != end) { /* should we error here or report-only? */ + /*_WM_DEBUG_MSG("%s:%i: MUS buffer off by %ld bytes", + __FUNCTION__, __LINE__, (long)(cur - end));*/ + } + break; + case 5:/* Unknown */ + case 7:/* Unknown */ + default:/* shouldn't happen */ + /*_WM_ERROR_NEW("%s:%i: unrecognized event (%u)", + __FUNCTION__, __LINE__, event);*/ + goto _end; + } + + /* write it out */ + *out_local++ = status; + *out_local++ = bit1; + if (bitc == 2) + *out_local++ = bit2; + + /* write out our temp buffer */ + if (out_local != temp_buffer) + { + if (ctx.dstrem < sizeof(temp_buffer)) + mus2mid_resize_dst(&ctx); + + memcpy(ctx.dst_ptr, temp_buffer, out_local - temp_buffer); + ctx.dst_ptr += out_local - temp_buffer; + ctx.dstrem -= (uint32_t)(out_local - temp_buffer); + } + + if (event & 128) { + delta_time = 0; + do { + delta_time = (int32_t)((delta_time * 128 + (*cur & 127)) * (140.0 / (double)frequency)); + } while ((*cur++ & 128)); + } else { + delta_time = 0; + } + } + + /* write out track length */ + 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; + ret = 0; + +_end: /* cleanup */ + if (ret < 0) { + free(ctx.dst); + *out = NULL; + *outsize = 0; + } + + return (ret); +} + diff --git a/src/cvt_xmi2mid.hpp b/src/cvt_xmi2mid.hpp new file mode 100644 index 0000000..c164e3d --- /dev/null +++ b/src/cvt_xmi2mid.hpp @@ -0,0 +1,1123 @@ +/* + * XMIDI: Miles XMIDI to MID Library + * + * Copyright (C) 2001 Ryan Nunn + * Copyright (C) 2014 Bret Curtis + * Copyright (C) WildMIDI Developers 2015-2016 + * Copyright (c) 2015-2018 Vitaly Novichkov + * + * 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 */ + +#include +#include +#include +#include +#include + +#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 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 _xmi2mid_midi_event *next; +} midi_event; + +typedef struct { + uint16_t type; + uint16_t tracks; +} midi_descriptor; + +struct xmi2mid_xmi_ctx { + uint8_t *src, *src_ptr; + uint32_t srcsize; + uint32_t datastart; + uint8_t *dst, *dst_ptr; + uint32_t dstsize, dstrem; + uint32_t convert_type; + midi_descriptor info; + int bank127[16]; + midi_event **events; + int16_t *timing; + midi_event *list; + midi_event *current; +}; + +/* forward declarations of private functions */ +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 xmi2mid_ConvertSystemMessage(struct xmi2mid_xmi_ctx *ctx, + const int32_t time, const uint8_t status); +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 xmi2mid_read1(struct xmi2mid_xmi_ctx *ctx) +{ + uint8_t b0; + b0 = *ctx->src_ptr++; + return (b0); +} + +static uint32_t xmi2mid_read2(struct xmi2mid_xmi_ctx *ctx) +{ + uint8_t b0, b1; + b0 = *ctx->src_ptr++; + b1 = *ctx->src_ptr++; + return (b0 + ((uint32_t)b1 << 8)); +} + +static uint32_t xmi2mid_read4(struct xmi2mid_xmi_ctx *ctx) +{ + uint8_t b0, b1, b2, b3; + b3 = *ctx->src_ptr++; + b2 = *ctx->src_ptr++; + b1 = *ctx->src_ptr++; + b0 = *ctx->src_ptr++; + return (b0 + ((uint32_t)b1<<8) + ((uint32_t)b2<<16) + ((uint32_t)b3<<24)); +} + +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 xmi2mid_resize_dst(struct xmi2mid_xmi_ctx *ctx) { + uint32_t pos = (uint32_t)(ctx->dst_ptr - ctx->dst); + 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 xmi2mid_write1(struct xmi2mid_xmi_ctx *ctx, uint32_t val) +{ + if (ctx->dstrem < 1) + xmi2mid_resize_dst(ctx); + *ctx->dst_ptr++ = val & 0xff; + ctx->dstrem--; +} + +static void xmi2mid_write2(struct xmi2mid_xmi_ctx *ctx, uint32_t val) +{ + if (ctx->dstrem < 2) + xmi2mid_resize_dst(ctx); + *ctx->dst_ptr++ = (val>>8) & 0xff; + *ctx->dst_ptr++ = val & 0xff; + ctx->dstrem -= 2; +} + +static void xmi2mid_write4(struct xmi2mid_xmi_ctx *ctx, uint32_t val) +{ + if (ctx->dstrem < 4) + xmi2mid_resize_dst(ctx); + *ctx->dst_ptr++ = (val>>24)&0xff; + *ctx->dst_ptr++ = (val>>16)&0xff; + *ctx->dst_ptr++ = (val>>8) & 0xff; + *ctx->dst_ptr++ = val & 0xff; + ctx->dstrem -= 4; +} + +static void xmi2mid_seeksrc(struct xmi2mid_xmi_ctx *ctx, uint32_t pos) { + ctx->src_ptr = ctx->src + pos; +} + +static void xmi2mid_seekdst(struct xmi2mid_xmi_ctx *ctx, uint32_t pos) { + ctx->dst_ptr = ctx->dst + pos; + while (ctx->dstsize < pos) + xmi2mid_resize_dst(ctx); + ctx->dstrem = ctx->dstsize - pos; +} + +static void xmi2mid_skipsrc(struct xmi2mid_xmi_ctx *ctx, int32_t pos) { + ctx->src_ptr += 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) + xmi2mid_resize_dst(ctx); + ctx->dstrem = (uint32_t)(ctx->dstsize - newpos); +} + +static uint32_t xmi2mid_getsrcsize(struct xmi2mid_xmi_ctx *ctx) { + return (ctx->srcsize); +} + +static uint32_t xmi2mid_getsrcpos(struct xmi2mid_xmi_ctx *ctx) { + return (uint32_t)(ctx->src_ptr - ctx->src); +} + +static uint32_t xmi2mid_getdstpos(struct xmi2mid_xmi_ctx *ctx) { + return (uint32_t)(ctx->dst_ptr - ctx->dst); +} + +/* This is a default set of patches to convert from MT32 to GM + * The index is the MT32 Patch number and the value is the GM Patch + * This is only suitable for music that doesn't do timbre changes + * XMIDIs that contain Timbre changes will not convert properly. + */ +static const char xmi2mid_mt32asgm[128] = { + 0, /* 0 Piano 1 */ + 1, /* 1 Piano 2 */ + 2, /* 2 Piano 3 (synth) */ + 4, /* 3 EPiano 1 */ + 4, /* 4 EPiano 2 */ + 5, /* 5 EPiano 3 */ + 5, /* 6 EPiano 4 */ + 3, /* 7 Honkytonk */ + 16, /* 8 Organ 1 */ + 17, /* 9 Organ 2 */ + 18, /* 10 Organ 3 */ + 16, /* 11 Organ 4 */ + 19, /* 12 Pipe Organ 1 */ + 19, /* 13 Pipe Organ 2 */ + 19, /* 14 Pipe Organ 3 */ + 21, /* 15 Accordion */ + 6, /* 16 Harpsichord 1 */ + 6, /* 17 Harpsichord 2 */ + 6, /* 18 Harpsichord 3 */ + 7, /* 19 Clavinet 1 */ + 7, /* 20 Clavinet 2 */ + 7, /* 21 Clavinet 3 */ + 8, /* 22 Celesta 1 */ + 8, /* 23 Celesta 2 */ + 62, /* 24 Synthbrass 1 (62) */ + 63, /* 25 Synthbrass 2 (63) */ + 62, /* 26 Synthbrass 3 Bank 8 */ + 63, /* 27 Synthbrass 4 Bank 8 */ + 38, /* 28 Synthbass 1 */ + 39, /* 29 Synthbass 2 */ + 38, /* 30 Synthbass 3 Bank 8 */ + 39, /* 31 Synthbass 4 Bank 8 */ + 88, /* 32 Fantasy */ + 90, /* 33 Harmonic Pan - No equiv closest is polysynth(90) :( */ + 52, /* 34 Choral ?? Currently set to SynthVox(54). Should it be ChoirAhhs(52)??? */ + 92, /* 35 Glass */ + 97, /* 36 Soundtrack */ + 99, /* 37 Atmosphere */ + 14, /* 38 Warmbell, sounds kind of like crystal(98) perhaps Tubular Bells(14) would be better. It is! */ + 54, /* 39 FunnyVox, sounds alot like Bagpipe(109) and Shania(111) */ + 98, /* 40 EchoBell, no real equiv, sounds like Crystal(98) */ + 96, /* 41 IceRain */ + 68, /* 42 Oboe 2001, no equiv, just patching it to normal oboe(68) */ + 95, /* 43 EchoPans, no equiv, setting to SweepPad */ + 81, /* 44 DoctorSolo Bank 8 */ + 87, /* 45 SchoolDaze, no real equiv */ + 112,/* 46 Bell Singer */ + 80, /* 47 SquareWave */ + 48, /* 48 Strings 1 */ + 48, /* 49 Strings 2 - should be 49 */ + 44, /* 50 Strings 3 (Synth) - Experimental set to Tremollo Strings - should be 50 */ + 45, /* 51 Pizzicato Strings */ + 40, /* 52 Violin 1 */ + 40, /* 53 Violin 2 ? Viola */ + 42, /* 54 Cello 1 */ + 42, /* 55 Cello 2 */ + 43, /* 56 Contrabass */ + 46, /* 57 Harp 1 */ + 46, /* 58 Harp 2 */ + 24, /* 59 Guitar 1 (Nylon) */ + 25, /* 60 Guitar 2 (Steel) */ + 26, /* 61 Elec Guitar 1 */ + 27, /* 62 Elec Guitar 2 */ + 104,/* 63 Sitar */ + 32, /* 64 Acou Bass 1 */ + 32, /* 65 Acou Bass 2 */ + 33, /* 66 Elec Bass 1 */ + 34, /* 67 Elec Bass 2 */ + 36, /* 68 Slap Bass 1 */ + 37, /* 69 Slap Bass 2 */ + 35, /* 70 Fretless Bass 1 */ + 35, /* 71 Fretless Bass 2 */ + 73, /* 72 Flute 1 */ + 73, /* 73 Flute 2 */ + 72, /* 74 Piccolo 1 */ + 72, /* 75 Piccolo 2 */ + 74, /* 76 Recorder */ + 75, /* 77 Pan Pipes */ + 64, /* 78 Sax 1 */ + 65, /* 79 Sax 2 */ + 66, /* 80 Sax 3 */ + 67, /* 81 Sax 4 */ + 71, /* 82 Clarinet 1 */ + 71, /* 83 Clarinet 2 */ + 68, /* 84 Oboe */ + 69, /* 85 English Horn (Cor Anglais) */ + 70, /* 86 Bassoon */ + 22, /* 87 Harmonica */ + 56, /* 88 Trumpet 1 */ + 56, /* 89 Trumpet 2 */ + 57, /* 90 Trombone 1 */ + 57, /* 91 Trombone 2 */ + 60, /* 92 French Horn 1 */ + 60, /* 93 French Horn 2 */ + 58, /* 94 Tuba */ + 61, /* 95 Brass Section 1 */ + 61, /* 96 Brass Section 2 */ + 11, /* 97 Vibes 1 */ + 11, /* 98 Vibes 2 */ + 99, /* 99 Syn Mallet Bank 1 */ + 112,/* 100 WindBell no real equiv Set to TinkleBell(112) */ + 9, /* 101 Glockenspiel */ + 14, /* 102 Tubular Bells */ + 13, /* 103 Xylophone */ + 12, /* 104 Marimba */ + 107,/* 105 Koto */ + 111,/* 106 Sho?? set to Shanai(111) */ + 77, /* 107 Shakauhachi */ + 78, /* 108 Whistle 1 */ + 78, /* 109 Whistle 2 */ + 76, /* 110 Bottle Blow */ + 76, /* 111 Breathpipe no real equiv set to bottle blow(76) */ + 47, /* 112 Timpani */ + 117,/* 113 Melodic Tom */ + 116,/* 114 Deap Snare no equiv, set to Taiko(116) */ + 118,/* 115 Electric Perc 1 */ + 118,/* 116 Electric Perc 2 */ + 116,/* 117 Taiko */ + 115,/* 118 Taiko Rim, no real equiv, set to Woodblock(115) */ + 119,/* 119 Cymbal, no real equiv, set to reverse cymbal(119) */ + 115,/* 120 Castanets, no real equiv, in GM set to Woodblock(115) */ + 112,/* 121 Triangle, no real equiv, set to TinkleBell(112) */ + 55, /* 122 Orchestral Hit */ + 124,/* 123 Telephone */ + 123,/* 124 BirdTweet */ + 94, /* 125 Big Notes Pad no equiv, set to halo pad (94) */ + 98, /* 126 Water Bell set to Crystal Pad(98) */ + 121 /* 127 Jungle Tune set to Breath Noise */ +}; + +/* Same as above, except include patch changes + * so GS instruments can be used */ +static const char xmi2mid_mt32asgs[256] = { + 0, 0, /* 0 Piano 1 */ + 1, 0, /* 1 Piano 2 */ + 2, 0, /* 2 Piano 3 (synth) */ + 4, 0, /* 3 EPiano 1 */ + 4, 0, /* 4 EPiano 2 */ + 5, 0, /* 5 EPiano 3 */ + 5, 0, /* 6 EPiano 4 */ + 3, 0, /* 7 Honkytonk */ + 16, 0, /* 8 Organ 1 */ + 17, 0, /* 9 Organ 2 */ + 18, 0, /* 10 Organ 3 */ + 16, 0, /* 11 Organ 4 */ + 19, 0, /* 12 Pipe Organ 1 */ + 19, 0, /* 13 Pipe Organ 2 */ + 19, 0, /* 14 Pipe Organ 3 */ + 21, 0, /* 15 Accordion */ + 6, 0, /* 16 Harpsichord 1 */ + 6, 0, /* 17 Harpsichord 2 */ + 6, 0, /* 18 Harpsichord 3 */ + 7, 0, /* 19 Clavinet 1 */ + 7, 0, /* 20 Clavinet 2 */ + 7, 0, /* 21 Clavinet 3 */ + 8, 0, /* 22 Celesta 1 */ + 8, 0, /* 23 Celesta 2 */ + 62, 0, /* 24 Synthbrass 1 (62) */ + 63, 0, /* 25 Synthbrass 2 (63) */ + 62, 0, /* 26 Synthbrass 3 Bank 8 */ + 63, 0, /* 27 Synthbrass 4 Bank 8 */ + 38, 0, /* 28 Synthbass 1 */ + 39, 0, /* 29 Synthbass 2 */ + 38, 0, /* 30 Synthbass 3 Bank 8 */ + 39, 0, /* 31 Synthbass 4 Bank 8 */ + 88, 0, /* 32 Fantasy */ + 90, 0, /* 33 Harmonic Pan - No equiv closest is polysynth(90) :( */ + 52, 0, /* 34 Choral ?? Currently set to SynthVox(54). Should it be ChoirAhhs(52)??? */ + 92, 0, /* 35 Glass */ + 97, 0, /* 36 Soundtrack */ + 99, 0, /* 37 Atmosphere */ + 14, 0, /* 38 Warmbell, sounds kind of like crystal(98) perhaps Tubular Bells(14) would be better. It is! */ + 54, 0, /* 39 FunnyVox, sounds alot like Bagpipe(109) and Shania(111) */ + 98, 0, /* 40 EchoBell, no real equiv, sounds like Crystal(98) */ + 96, 0, /* 41 IceRain */ + 68, 0, /* 42 Oboe 2001, no equiv, just patching it to normal oboe(68) */ + 95, 0, /* 43 EchoPans, no equiv, setting to SweepPad */ + 81, 0, /* 44 DoctorSolo Bank 8 */ + 87, 0, /* 45 SchoolDaze, no real equiv */ + 112, 0, /* 46 Bell Singer */ + 80, 0, /* 47 SquareWave */ + 48, 0, /* 48 Strings 1 */ + 48, 0, /* 49 Strings 2 - should be 49 */ + 44, 0, /* 50 Strings 3 (Synth) - Experimental set to Tremollo Strings - should be 50 */ + 45, 0, /* 51 Pizzicato Strings */ + 40, 0, /* 52 Violin 1 */ + 40, 0, /* 53 Violin 2 ? Viola */ + 42, 0, /* 54 Cello 1 */ + 42, 0, /* 55 Cello 2 */ + 43, 0, /* 56 Contrabass */ + 46, 0, /* 57 Harp 1 */ + 46, 0, /* 58 Harp 2 */ + 24, 0, /* 59 Guitar 1 (Nylon) */ + 25, 0, /* 60 Guitar 2 (Steel) */ + 26, 0, /* 61 Elec Guitar 1 */ + 27, 0, /* 62 Elec Guitar 2 */ + 104, 0, /* 63 Sitar */ + 32, 0, /* 64 Acou Bass 1 */ + 32, 0, /* 65 Acou Bass 2 */ + 33, 0, /* 66 Elec Bass 1 */ + 34, 0, /* 67 Elec Bass 2 */ + 36, 0, /* 68 Slap Bass 1 */ + 37, 0, /* 69 Slap Bass 2 */ + 35, 0, /* 70 Fretless Bass 1 */ + 35, 0, /* 71 Fretless Bass 2 */ + 73, 0, /* 72 Flute 1 */ + 73, 0, /* 73 Flute 2 */ + 72, 0, /* 74 Piccolo 1 */ + 72, 0, /* 75 Piccolo 2 */ + 74, 0, /* 76 Recorder */ + 75, 0, /* 77 Pan Pipes */ + 64, 0, /* 78 Sax 1 */ + 65, 0, /* 79 Sax 2 */ + 66, 0, /* 80 Sax 3 */ + 67, 0, /* 81 Sax 4 */ + 71, 0, /* 82 Clarinet 1 */ + 71, 0, /* 83 Clarinet 2 */ + 68, 0, /* 84 Oboe */ + 69, 0, /* 85 English Horn (Cor Anglais) */ + 70, 0, /* 86 Bassoon */ + 22, 0, /* 87 Harmonica */ + 56, 0, /* 88 Trumpet 1 */ + 56, 0, /* 89 Trumpet 2 */ + 57, 0, /* 90 Trombone 1 */ + 57, 0, /* 91 Trombone 2 */ + 60, 0, /* 92 French Horn 1 */ + 60, 0, /* 93 French Horn 2 */ + 58, 0, /* 94 Tuba */ + 61, 0, /* 95 Brass Section 1 */ + 61, 0, /* 96 Brass Section 2 */ + 11, 0, /* 97 Vibes 1 */ + 11, 0, /* 98 Vibes 2 */ + 99, 0, /* 99 Syn Mallet Bank 1 */ + 112, 0, /* 100 WindBell no real equiv Set to TinkleBell(112) */ + 9, 0, /* 101 Glockenspiel */ + 14, 0, /* 102 Tubular Bells */ + 13, 0, /* 103 Xylophone */ + 12, 0, /* 104 Marimba */ + 107, 0, /* 105 Koto */ + 111, 0, /* 106 Sho?? set to Shanai(111) */ + 77, 0, /* 107 Shakauhachi */ + 78, 0, /* 108 Whistle 1 */ + 78, 0, /* 109 Whistle 2 */ + 76, 0, /* 110 Bottle Blow */ + 76, 0, /* 111 Breathpipe no real equiv set to bottle blow(76) */ + 47, 0, /* 112 Timpani */ + 117, 0, /* 113 Melodic Tom */ + 116, 0, /* 114 Deap Snare no equiv, set to Taiko(116) */ + 118, 0, /* 115 Electric Perc 1 */ + 118, 0, /* 116 Electric Perc 2 */ + 116, 0, /* 117 Taiko */ + 115, 0, /* 118 Taiko Rim, no real equiv, set to Woodblock(115) */ + 119, 0, /* 119 Cymbal, no real equiv, set to reverse cymbal(119) */ + 115, 0, /* 120 Castanets, no real equiv, in GM set to Woodblock(115) */ + 112, 0, /* 121 Triangle, no real equiv, set to TinkleBell(112) */ + 55, 0, /* 122 Orchestral Hit */ + 124, 0, /* 123 Telephone */ + 123, 0, /* 124 BirdTweet */ + 94, 0, /* 125 Big Notes Pad no equiv, set to halo pad (94) */ + 98, 0, /* 126 Water Bell set to Crystal Pad(98) */ + 121, 0 /* 127 Jungle Tune set to Breath Noise */ +}; + +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; + + if (convert_type > XMIDI_CONVERT_MT32_TO_GS) { + /*_WM_ERROR_NEW("%s:%i: %d is an invalid conversion type.", __FUNCTION__, __LINE__, convert_type);*/ + return (ret); + } + + memset(&ctx, 0, sizeof(struct xmi2mid_xmi_ctx)); + ctx.src = ctx.src_ptr = in; + ctx.srcsize = insize; + ctx.convert_type = convert_type; + + if (xmi2mid_ParseXMI(&ctx) < 0) { + /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_NOT_XMI, NULL, 0);*/ + goto _end; + } + + if (xmi2mid_ExtractTracks(&ctx) < 0) { + /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_NOT_MIDI, NULL, 0);*/ + goto _end; + } + + ctx.dst = (uint8_t*)malloc(DST_CHUNK); + ctx.dst_ptr = ctx.dst; + ctx.dstsize = DST_CHUNK; + ctx.dstrem = DST_CHUNK; + + /* Header is 14 bytes long and add the rest as well */ + xmi2mid_write1(&ctx, 'M'); + xmi2mid_write1(&ctx, 'T'); + xmi2mid_write1(&ctx, 'h'); + xmi2mid_write1(&ctx, 'd'); + + xmi2mid_write4(&ctx, 6); + + xmi2mid_write2(&ctx, ctx.info.type); + xmi2mid_write2(&ctx, ctx.info.tracks); + xmi2mid_write2(&ctx, ctx.timing[0]);/* write divisions from track0 */ + + for (i = 0; i < ctx.info.tracks; i++) + xmi2mid_ConvertListToMTrk(&ctx, ctx.events[i]); + *out = ctx.dst; + *outsize = ctx.dstsize - ctx.dstrem; + ret = 0; + +_end: /* cleanup */ + if (ret < 0) { + free(ctx.dst); + *out = NULL; + *outsize = 0; + } + if (ctx.events) { + for (i = 0; i < ctx.info.tracks; i++) + xmi2mid_DeleteEventList(ctx.events[i]); + free(ctx.events); + } + free(ctx.timing); + + return (ret); +} + +static void xmi2mid_DeleteEventList(midi_event *mlist) { + midi_event *event; + midi_event *next; + + next = mlist; + + while ((event = next) != NULL) { + next = event->next; + free(event->buffer); + free(event); + } +} + +/* Sets current to the new event and updates list */ +static void xmi2mid_CreateNewEvent(struct xmi2mid_xmi_ctx *ctx, int32_t time) { + if (!ctx->list) { + 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 = (midi_event *)calloc(1, sizeof(midi_event)); + event->next = ctx->list; + ctx->list = ctx->current = event; + return; + } + + if (ctx->current->time > time) + ctx->current = ctx->list; + + while (ctx->current->next) { + if (ctx->current->next->time > time) { + midi_event *event = (midi_event *)calloc(1, sizeof(midi_event)); + event->next = ctx->current->next; + ctx->current->next = event; + ctx->current = event; + ctx->current->time = time; + return; + } + + ctx->current = ctx->current->next; + } + + 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 xmi2mid_GetVLQ(struct xmi2mid_xmi_ctx *ctx, uint32_t *quant) { + int i; + uint32_t data; + + *quant = 0; + for (i = 0; i < 4; i++) { + data = xmi2mid_read1(ctx); + *quant <<= 7; + *quant |= data & 0x7F; + + if (!(data & 0x80)) { + i++; + break; + } + } + return (i); +} + +/* XMIDI Delta Variable Length Quantity */ +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 = xmi2mid_read1(ctx); + if (data & 0x80) { + xmi2mid_skipsrc(ctx, -1); + break; + } + *quant += data; + } + return (i); +} + +static int xmi2mid_PutVLQ(struct xmi2mid_xmi_ctx *ctx, uint32_t value) { + int32_t buffer; + int i = 1, j; + buffer = value & 0x7F; + while (value >>= 7) { + buffer <<= 8; + buffer |= ((value & 0x7F) | 0x80); + i++; + } + for (j = 0; j < i; j++) { + xmi2mid_write1(ctx, buffer & 0xFF); + buffer >>= 8; + } + + return (i); +} + +/* Converts Events + * + * Source is at the first data byte + * size 1 is single data byte + * size 2 is dual data byte + * size 3 is XMI Note on + * Returns bytes converted */ +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 = xmi2mid_read1(ctx); + + /*HACK!*/ + if (((status >> 4) == 0xB) && (status & 0xF) != 9 && (data == 114)) { + data = 32; /*Change XMI 114 controller into XG bank*/ + } + + /* Bank changes are handled here */ + if ((status >> 4) == 0xB && data == 0) { + data = xmi2mid_read1(ctx); + + ctx->bank127[status & 0xF] = 0; + + if ( ctx->convert_type == XMIDI_CONVERT_MT32_TO_GM || + ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS || + ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS127 || + (ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS127DRUM + && (status & 0xF) == 9) ) + return (2); + + xmi2mid_CreateNewEvent(ctx, time); + ctx->current->status = status; + ctx->current->data[0] = 0; + ctx->current->data[1] = data == 127 ? 0 : data;/*HACK:*/ + + if (ctx->convert_type == XMIDI_CONVERT_GS127_TO_GS && data == 127) + ctx->bank127[status & 0xF] = 1; + + return (2); + } + + /* Handling for patch change mt32 conversion, probably should go elsewhere */ + if ((status >> 4) == 0xC && (status&0xF) != 9 + && ctx->convert_type != XMIDI_CONVERT_NOCONVERSION) + { + if (ctx->convert_type == XMIDI_CONVERT_MT32_TO_GM) + { + 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) + { + xmi2mid_CreateNewEvent (ctx, time); + ctx->current->status = 0xB0 | (status&0xF); + ctx->current->data[0] = 0; + ctx->current->data[1] = xmi2mid_mt32asgs[data*2+1]; + + data = xmi2mid_mt32asgs[data*2]; + } + else if (ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS127) + { + xmi2mid_CreateNewEvent (ctx, time); + ctx->current->status = 0xB0 | (status&0xF); + ctx->current->data[0] = 0; + ctx->current->data[1] = 127; + } + } + /* Drum track handling */ + else if ((status >> 4) == 0xC && (status&0xF) == 9 && + (ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS127DRUM || ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS127)) + { + xmi2mid_CreateNewEvent (ctx, time); + ctx->current->status = 0xB9; + ctx->current->data[0] = 0; + ctx->current->data[1] = 127; + } + + xmi2mid_CreateNewEvent(ctx, time); + ctx->current->status = status; + + ctx->current->data[0] = data; + + if (size == 1) + return (1); + + ctx->current->data[1] = xmi2mid_read1(ctx); + + if (size == 2) + return (2); + + /* XMI Note On handling */ + prev = ctx->current; + i = xmi2mid_GetVLQ(ctx, &delta); + xmi2mid_CreateNewEvent(ctx, time + delta * 3); + + ctx->current->status = status; + ctx->current->data[0] = data; + ctx->current->data[1] = 0; + ctx->current = prev; + + return (i + 2); +} + +/* Simple routine to convert system messages */ +static int32_t xmi2mid_ConvertSystemMessage(struct xmi2mid_xmi_ctx *ctx, const int32_t time, + const uint8_t status) { + int32_t i = 0; + + xmi2mid_CreateNewEvent(ctx, time); + ctx->current->status = status; + + /* Handling of Meta events */ + if (status == 0xFF) { + ctx->current->data[0] = xmi2mid_read1(ctx); + i++; + } + + i += xmi2mid_GetVLQ(ctx, &ctx->current->len); + + if (!ctx->current->len) + return (i); + + 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 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 = xmi2mid_getsrcsize(ctx); + + /* Set Drum track to correct setting if required */ + if (ctx->convert_type == XMIDI_CONVERT_MT32_TO_GS127) { + xmi2mid_CreateNewEvent(ctx, 0); + ctx->current->status = 0xB9; + ctx->current->data[0] = 0; + ctx->current->data[1] = 127; + } + + while (!end && xmi2mid_getsrcpos(ctx) < file_size) { + xmi2mid_GetVLQ2(ctx, &data); + time += data * 3; + + status = xmi2mid_read1(ctx); + + switch (status >> 4) { + case XMI2MID_MIDI_STATUS_NOTE_ON: + xmi2mid_ConvertEvent(ctx, time, status, 3); + break; + + /* 2 byte data */ + 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 XMI2MID_MIDI_STATUS_PROG_CHANGE: + case XMI2MID_MIDI_STATUS_PRESSURE: + xmi2mid_ConvertEvent(ctx, time, status, 1); + break; + + case XMI2MID_MIDI_STATUS_SYSEX: + if (status == 0xFF) { + 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 */ + { + 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 */ + { + xmi2mid_GetVLQ(ctx, &dat); + xmi2mid_skipsrc(ctx, dat); + break; + } + + xmi2mid_seeksrc(ctx, pos); + } + xmi2mid_ConvertSystemMessage(ctx, time, status); + break; + + default: + break; + } + } + return ((tempo * 3) / 25000); +} + +/* Converts and event list to a MTrk + * Returns bytes of the array + * buf can be NULL */ +static uint32_t xmi2mid_ConvertListToMTrk(struct xmi2mid_xmi_ctx *ctx, midi_event *mlist) { + int32_t time = 0; + midi_event *event; + uint32_t delta; + uint8_t last_status = 0; + uint32_t i = 8; + uint32_t j; + uint32_t size_pos, cur_pos; + int end = 0; + + xmi2mid_write1(ctx, 'M'); + xmi2mid_write1(ctx, 'T'); + xmi2mid_write1(ctx, 'r'); + xmi2mid_write1(ctx, 'k'); + + 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 += xmi2mid_PutVLQ(ctx, delta); + + if ((event->status != last_status) || (event->status >= 0xF0)) { + xmi2mid_write1(ctx, event->status); + i++; + } + + last_status = event->status; + + switch (event->status >> 4) { + /* 2 bytes data + * Note off, Note on, Aftertouch, Controller and Pitch Wheel */ + case 0x8: + case 0x9: + case 0xA: + case 0xB: + case 0xE: + xmi2mid_write1(ctx, event->data[0]); + xmi2mid_write1(ctx, event->data[1]); + i += 2; + break; + + /* 1 bytes data + * Program Change and Channel Pressure */ + case 0xC: + case 0xD: + xmi2mid_write1(ctx, event->data[0]); + i++; + break; + + /* Variable length + * SysEx */ + case 0xF: + if (event->status == 0xFF) { + if (event->data[0] == 0x2f) + end = 1; + xmi2mid_write1(ctx, event->data[0]); + i++; + } + i += xmi2mid_PutVLQ(ctx, event->len); + if (event->len) { + for (j = 0; j < event->len; j++) { + xmi2mid_write1(ctx, event->buffer[j]); + i++; + } + } + break; + + /* Never occur */ + default: + /*_WM_DEBUG_MSG("%s: unrecognized event", __FUNCTION__);*/ + break; + } + } + + 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 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 (xmi2mid_getsrcpos(ctx) < xmi2mid_getsrcsize(ctx) && num != ctx->info.tracks) { + /* Read first 4 bytes of name */ + xmi2mid_copy(ctx, buf, 4); + len = xmi2mid_read4(ctx); + + /* Skip the FORM entries */ + if (!memcmp(buf, "FORM", 4)) { + xmi2mid_skipsrc(ctx, 4); + xmi2mid_copy(ctx, buf, 4); + len = xmi2mid_read4(ctx); + } + + if (memcmp(buf, "EVNT", 4)) { + xmi2mid_skipsrc(ctx, (len + 1) & ~1); + continue; + } + + ctx->list = NULL; + begin = xmi2mid_getsrcpos(ctx); + + /* Convert it */ + if (!(ppqn = xmi2mid_ConvertFiletoList(ctx))) { + /*_WM_GLOBAL_ERROR(__FUNCTION__, __LINE__, WM_ERR_CORUPT, NULL, 0);*/ + break; + } + ctx->timing[num] = ppqn; + ctx->events[num] = ctx->list; + + /* Increment Counter */ + num++; + + /* go to start of next track */ + xmi2mid_seeksrc(ctx, begin + ((len + 1) & ~1)); + } + + /* Return how many were converted */ + return (num); +} + +static int xmi2mid_ParseXMI(struct xmi2mid_xmi_ctx *ctx) { + uint32_t i; + uint32_t start; + uint32_t len; + uint32_t chunk_len; + uint32_t file_size; + char buf[32]; + + 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 */ + xmi2mid_copy(ctx, buf, 4); + + /* Could be XMIDI */ + if (!memcmp(buf, "FORM", 4)) { + /* Read length of */ + len = xmi2mid_read4(ctx); + + start = xmi2mid_getsrcpos(ctx); + if (start + 4 > file_size) + goto badfile; + + /* Read 4 bytes of type */ + xmi2mid_copy(ctx, buf, 4); + + /* XDIRless XMIDI, we can handle them here. */ + if (!memcmp(buf, "XMID", 4)) { + /*_WM_DEBUG_MSG("Warning: XMIDI without XDIR");*/ + ctx->info.tracks = 1; + } + /* Not an XMIDI that we recognise */ + else if (memcmp(buf, "XDIR", 4)) { + goto badfile; + } + else { /* Seems Valid */ + ctx->info.tracks = 0; + + for (i = 4; i < len; i++) { + /* check too short files */ + if (xmi2mid_getsrcpos(ctx) + 10 > file_size) + break; + + /* Read 4 bytes of type */ + xmi2mid_copy(ctx, buf, 4); + + /* Read length of chunk */ + chunk_len = xmi2mid_read4(ctx); + + /* Add eight bytes */ + i += 8; + + if (memcmp(buf, "INFO", 4)) { + /* Must align */ + xmi2mid_skipsrc(ctx, (chunk_len + 1) & ~1); + i += (chunk_len + 1) & ~1; + continue; + } + + /* Must be at least 2 bytes long */ + if (chunk_len < 2) + break; + + ctx->info.tracks = xmi2mid_read2(ctx); + break; + } + + /* Didn't get to fill the header */ + if (ctx->info.tracks == 0) { + goto badfile; + } + + /* Ok now to start part 2 + * Goto the right place */ + xmi2mid_seeksrc(ctx, start + ((len + 1) & ~1)); + if (xmi2mid_getsrcpos(ctx) + 12 > file_size) + goto badfile; + + /* Read 4 bytes of type */ + xmi2mid_copy(ctx, buf, 4); + + if (memcmp(buf, "CAT ", 4)) { + /*_WM_ERROR_NEW("XMI error: expected \"CAT \", found \"%c%c%c%c\".", + buf[0], buf[1], buf[2], buf[3]);*/ + return (-1); + } + + /* Now read length of this track */ + xmi2mid_read4(ctx); + + /* Read 4 bytes of type */ + xmi2mid_copy(ctx, buf, 4); + + if (memcmp(buf, "XMID", 4)) { + /*_WM_ERROR_NEW("XMI error: expected \"XMID\", found \"%c%c%c%c\".", + buf[0], buf[1], buf[2], buf[3]);*/ + return (-1); + } + + /* Valid XMID */ + ctx->datastart = xmi2mid_getsrcpos(ctx); + return (0); + } + } + + return (-1); +} + +static int xmi2mid_ExtractTracks(struct xmi2mid_xmi_ctx *ctx) { + uint32_t i; + + ctx->events = (midi_event **)calloc(ctx->info.tracks, sizeof(midi_event*)); + ctx->timing = (int16_t *)calloc(ctx->info.tracks, sizeof(int16_t)); + /* type-2 for multi-tracks, type-0 otherwise */ + ctx->info.type = (ctx->info.tracks > 1)? 2 : 0; + + 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", + ctx->info.tracks, i);*/ + return (-1); + } + + return (0); +} + diff --git a/src/file_reader.hpp b/src/file_reader.hpp index e0664b4..c2cfe7b 100644 --- a/src/file_reader.hpp +++ b/src/file_reader.hpp @@ -22,6 +22,10 @@ * DEALINGS IN THE SOFTWARE. */ +#pragma once +#ifndef FILE_AND_MEM_READER_HHHH +#define FILE_AND_MEM_READER_HHHH + #include #include #ifdef _WIN32 @@ -83,6 +87,8 @@ public: */ void openFile(const char *path) { + if(m_fp) + this->close();//Close previously opened file first! #if !defined(_WIN32) || defined(__WATCOMC__) m_fp = std::fopen(path, "rb"); #else @@ -104,6 +110,8 @@ public: */ void openData(const void *mem, size_t lenght) { + if(m_fp) + this->close();//Close previously opened file first! m_fp = NULL; m_mp = mem; m_mp_size = lenght; @@ -117,6 +125,9 @@ public: */ void seek(long pos, int rel_to) { + if(!this->isValid()) + return; + if(m_fp)//If a file { std::fseek(m_fp, pos, rel_to); @@ -162,6 +173,8 @@ public: */ size_t read(void *buf, size_t num, size_t size) { + if(!this->isValid()) + return 0; if(m_fp) return std::fread(buf, num, size, m_fp); else @@ -186,6 +199,8 @@ public: */ int getc() { + if(!this->isValid()) + return -1; if(m_fp)//If a file { return std::getc(m_fp); @@ -206,6 +221,8 @@ public: */ size_t tell() { + if(!this->isValid()) + return 0; if(m_fp)//If a file return static_cast(std::ftell(m_fp)); else//If a memory block @@ -241,6 +258,8 @@ public: */ bool eof() { + if(!this->isValid()) + return true; if(m_fp) return (std::feof(m_fp) != 0); else @@ -256,4 +275,22 @@ public: return m_file_name; } + /** + * @brief Retrieve file size + * @return Size of file in bytes + */ + size_t fileSize() + { + if(!this->isValid()) + return 0; + if(!m_fp) + return m_mp_size; //Size of memory block is well known + size_t old_pos = tell(); + seek(0, FileAndMemReader::END); + size_t file_size = tell(); + seek(old_pos, FileAndMemReader::SET); + return file_size; + } }; + +#endif /* FILE_AND_MEM_READER_HHHH */ diff --git a/src/midi_sequencer.h b/src/midi_sequencer.h new file mode 100644 index 0000000..d3b542e --- /dev/null +++ b/src/midi_sequencer.h @@ -0,0 +1,91 @@ +/* + * BW_Midi_Sequencer - MIDI Sequencer for C++ + * + * Copyright (c) 2015-2018 Vitaly Novichkov + * + * 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 +#include + +struct BW_MidiRtInterface +{ + //! Raw MIDI event hook + typedef void (*RawEventHook)(void *userdata, uint8_t type, uint8_t subtype, uint8_t channel, const uint8_t *data, size_t len); + RawEventHook onEvent; + void *onEvent_userData; + + //! Library internal debug messages + typedef void (*DebugMessageHook)(void *userdata, const char *fmt, ...); + DebugMessageHook onDebugMessage; + void *onDebugMessage_userData; + + void *rtUserData; + + /* Standard MIDI events. All of them are required! */ + typedef void (*RtNoteOn)(void *userdata, uint8_t channel, uint8_t note, uint8_t velocity); + RtNoteOn rt_noteOn; + + typedef void (*RtNoteOff)(void *userdata, uint8_t channel, uint8_t note); + RtNoteOff rt_noteOff; + + typedef void (*RtNoteAfterTouch)(void *userdata, uint8_t channel, uint8_t note, uint8_t atVal); + RtNoteAfterTouch rt_noteAfterTouch; + + typedef void (*RtChannelAfterTouch)(void *userdata, uint8_t channel, uint8_t atVal); + RtChannelAfterTouch rt_channelAfterTouch; + + typedef void (*RtControlerChange)(void *userdata, uint8_t channel, uint8_t type, uint8_t value); + RtControlerChange rt_controllerChange; + + typedef void (*RtPatchChange)(void *userdata, uint8_t channel, uint8_t patch); + RtPatchChange rt_patchChange; + + typedef void (*RtPitchBend)(void *userdata, uint8_t channel, uint8_t msb, uint8_t lsb); + RtPitchBend rt_pitchBend; + + typedef void (*RtSysEx)(void *userdata, const uint8_t *msg, size_t size); + RtSysEx rt_systemExclusive; + + + /* NonStandard events. There are optional */ + typedef void (*RtRawOPL)(void *userdata, uint8_t reg, uint8_t value); + RtRawOPL rt_rawOPL; + + typedef void (*RtDeviceSwitch)(void *userdata, size_t track, const char *data, size_t length); + RtDeviceSwitch rt_deviceSwitch; + + typedef uint64_t (*RtCurrentDevice)(void *userdata, size_t track); + RtCurrentDevice rt_currentDevice; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* BISQUIT_AND_WOHLSTANDS_MIDI_SEQUENCER_HHHH */ diff --git a/src/midi_sequencer.hpp b/src/midi_sequencer.hpp new file mode 100644 index 0000000..370068b --- /dev/null +++ b/src/midi_sequencer.hpp @@ -0,0 +1,496 @@ +/* + * BW_Midi_Sequencer - MIDI Sequencer for C++ + * + * Copyright (c) 2015-2018 Vitaly Novichkov + * + * 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 +#include + +#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 + //! 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 + //! [Non-Standard] Loop End point + ST_LOOPEND = 0xE2,//size == 0 + //! [Non-Standard] Raw OPL data + ST_RAWOPL = 0xE3//size == 0 + }; + //! 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 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 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 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 MidiTrackQueue; + + /** + * @brief Song position context + */ + struct Position + { + //! Was track began playing + bool began; + char padding[7]; + //! Waiting time before next event in seconds + double wait; + //! Absolute time position on the track in seconds + double absTimePosition; + //! Track information + struct TrackInfo + { + size_t ptr; + //! Delay to next event in a track + uint64_t delay; + //! Last handled event type + int lastHandledEvent; + char padding2[4]; + //! MIDI Events queue position iterator + MidiTrackQueue::iterator pos; + TrackInfo(): ptr(0), delay(0), lastHandledEvent(0) {} + }; + std::vector 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 > &trackData); + + /** + * @brief Parse one event from raw MIDI track stream + * @param [_inout] ptr pointer to pointer to current position on the raw data track + * @param [_in] end address to end of raw track data, needed to validate position and size + * @param [_inout] status status of the track processing + * @return Parsed MIDI event entry + */ + MidiEvent parseEvent(const uint8_t **ptr, const uint8_t *end, int &status); + + /** + * @brief Process MIDI events on the current tick moment + * @param isSeek is a seeking process + * @return returns false on reaching end of the song + */ + bool processEvents(bool isSeek = false); + + /** + * @brief Handle one event from the chain + * @param tk MIDI track + * @param evt MIDI event entry + * @param status Recent event type, -1 returned when end of track event was handled. + */ + void handleEvent(size_t tk, const MidiEvent &evt, int &status); + +public: + /** + * @brief MIDI marker entry + */ + struct MIDI_MarkerEntry + { + //! Label + std::string label; + //! Position time in seconds + double pos_time; + //! Position time in MIDI ticks + uint64_t pos_ticks; + }; + + /** + * @brief Container of one raw CMF instrument + */ + struct CmfInstrument + { + //! Raw CMF instrument data + uint8_t data[16]; + }; + + /** + * @brief The FileFormat enum + */ + enum FileFormat + { + //! MIDI format + Format_MIDI, + //! CMF format + Format_CMF, + //! Id-Software Music File + Format_IMF, + //! EA-MUS format + Format_RSXX + }; + +private: + //! Music file format type. MIDI is default. + FileFormat m_format; + + //! Current position + Position m_currentPosition; + //! Track begin position + Position m_trackBeginPosition; + //! Loop start point + Position m_loopBeginPosition; + + //! Is looping enabled or not + bool m_loopEnabled; + + //! Full song length in seconds + double m_fullSongTimeLength; + //! Delay after song playd before rejecting the output stream requests + double m_postSongWaitDelay; + + //! Loop start time + double m_loopStartTime; + //! Loop end time + double m_loopEndTime; + + //! Pre-processed track data storage + std::vector m_trackData; + + //! CMF instruments + std::vector m_cmfInstruments; + + //! Title of music + std::string m_musTitle; + //! Copyright notice of music + std::string m_musCopyright; + //! List of track titles + std::vector m_musTrackTitles; + //! List of MIDI markers + std::vector m_musMarkers; + + //! Time of one tick + fraction m_invDeltaTicks; + //! Current tempo + fraction m_tempo; + + //! Tempo multiplier factor + double m_tempoMultiplier; + //! Is song at end + bool m_atEnd; + //! Loop start has reached + bool m_loopStart; + //! Loop end has reached, reset on handling + bool m_loopEnd; + //! Are loop points invalid? + bool m_invalidLoop; /*Loop points are invalid (loopStart after loopEnd or loopStart and loopEnd are on same place)*/ + + //! File parsing errors string (adding into m_errorString on aborting of the process) + std::string m_parsingErrorsString; + //! Common error string + std::string m_errorString; + +public: + BW_MidiSequencer(); + virtual ~BW_MidiSequencer(); + + /** + * @brief Sets the RT interface + * @param interface Pre-Initialized interface structure (pointer will be taken) + */ + void setInterface(const BW_MidiRtInterface *interface); + + /** + * @brief Returns file format type of currently loaded file + * @return File format type enumeration + */ + FileFormat getFormat(); + + /** + * @brief Get the list of CMF instruments (CMF only) + * @return Array of raw CMF instruments entries + */ + const std::vector 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 &getTrackTitles(); + + /** + * @brief Get list of MIDI markers + * @return Array of MIDI marker structures + */ + const std::vector &getMarkers(); + + /** + * @brief Is position of song at end + * @return true if end of song was reached + */ + bool positionAtEnd(); + + /** + * @brief Load MIDI file from path + * @param filename Path to file to open + * @return true if file successfully opened, false on any error + */ + bool loadMIDI(const std::string &filename); + + /** + * @brief Load MIDI file from a memory block + * @param data Pointer to memory block with MIDI data + * @param size Size of source memory block + * @return true if file successfully opened, false on any error + */ + bool loadMIDI(const void *data, size_t size); + + /** + * @brief Load MIDI file by using FileAndMemReader interface + * @param fr FileAndMemReader context with opened source file + * @return true if file successfully opened, false on any error + */ + bool loadMIDI(FileAndMemReader &fr); + + /** + * @brief Periodic tick handler. + * @param s seconds since last call + * @param granularity don't expect intervals smaller than this, in seconds + * @return desired number of seconds until next call + */ + double Tick(double s, double granularity); + + /** + * @brief Change current position to specified time position in seconds + * @param granularity don't expect intervals smaller than this, in seconds + * @param seconds Absolute time position in seconds + * @return desired number of seconds until next call of Tick() + */ + double seek(double seconds, const double granularity); + + /** + * @brief Gives current time position in seconds + * @return Current time position in seconds + */ + double tell(); + + /** + * @brief Gives time length of current song in seconds + * @return Time length of current song in seconds + */ + double timeLength(); + + /** + * @brief Gives loop start time position in seconds + * @return Loop start time position in seconds or -1 if song has no loop points + */ + double getLoopStart(); + + /** + * @brief Gives loop end time position in seconds + * @return Loop end time position in seconds or -1 if song has no loop points + */ + double getLoopEnd(); + + /** + * @brief Return to begin of current song + */ + void rewind(); + + /** + * @brief Get current tempor multiplier value + * @return + */ + double getTempoMultiplier(); + + /** + * @brief Set tempo multiplier + * @param tempo Tempo multiplier: 1.0 - original tempo. >1 - faster, <1 - slower + */ + void setTempo(double tempo); +}; + +#endif /* BISQUIT_AND_WOHLSTANDS_MIDI_SEQUENCER_HHHHPPP */ diff --git a/src/midi_sequencer_impl.hpp b/src/midi_sequencer_impl.hpp new file mode 100644 index 0000000..1e4e643 --- /dev/null +++ b/src/midi_sequencer_impl.hpp @@ -0,0 +1,1780 @@ +/* + * BW_Midi_Sequencer - MIDI Sequencer for C++ + * + * Copyright (c) 2015-2018 Vitaly Novichkov + * + * 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 +#include +#include + +#include + +#ifndef BWMIDI_DISABLE_MUS_SUPPORT +#include "cvt_mus2mid.hpp" +#endif//MUS + +#ifndef BWMIDI_DISABLE_XMI_SUPPORT +#include "cvt_xmi2mid.hpp" +#endif//XMI + +/** + * @brief Utility function to read Big-Endian integer from raw binary data + * @param buffer Pointer to raw binary buffer + * @param nbytes Count of bytes to parse integer + * @return Extracted unsigned integer + */ +static inline uint64_t ReadBEint(const void *buffer, size_t nbytes) +{ + uint64_t result = 0; + const unsigned char *data = reinterpret_cast(buffer); + + for(unsigned n = 0; n < nbytes; ++n) + result = (result << 8) + data[n]; + + return result; +} + +/** + * @brief Utility function to read Little-Endian integer from raw binary data + * @param buffer Pointer to raw binary buffer + * @param nbytes Count of bytes to parse integer + * @return Extracted unsigned integer + */ +static inline uint64_t ReadLEint(const void *buffer, size_t nbytes) +{ + uint64_t result = 0; + const unsigned char *data = reinterpret_cast(buffer); + + for(unsigned n = 0; n < nbytes; ++n) + result = result + static_cast(data[n] << (n * 8)); + + return result; +} + +/** + * @brief Standard MIDI Variable-Length numeric value parser without of validation + * @param [_inout] ptr Pointer to memory block that contains begin of variable-length value + * @return Unsigned integer that conains parsed variable-length value + */ +static inline uint64_t ReadVarLen(uint8_t **ptr) +{ + uint64_t result = 0; + for(;;) + { + uint8_t byte = *((*ptr)++); + result = (result << 7) + (byte & 0x7F); + if(!(byte & 0x80)) + break; + } + return result; +} + +/** + * @brief Secure Standard MIDI Variable-Length numeric value parser with anti-out-of-range protection + * @param [_inout] ptr Pointer to memory block that contains begin of variable-length value, will be iterated forward + * @param [_in end Pointer to end of memory block where variable-length value is stored (after end of track) + * @param [_out] ok Reference to boolean which takes result of variable-length value parsing + * @return Unsigned integer that conains parsed variable-length value + */ +static inline uint64_t ReadVarLenEx(const uint8_t **ptr, const uint8_t *end, bool &ok) +{ + uint64_t result = 0; + ok = false; + + for(;;) + { + if(*ptr >= end) + return 2; + unsigned char byte = *((*ptr)++); + result = (result << 7) + (byte & 0x7F); + if(!(byte & 0x80)) + break; + } + + ok = true; + return result; +} + +BW_MidiSequencer::MidiEvent::MidiEvent() : + type(T_UNKNOWN), + subtype(T_UNKNOWN), + channel(0), + isValid(1), + absPosition(0) +{} + +BW_MidiSequencer::MidiTrackRow::MidiTrackRow() : + time(0.0), + delay(0), + absPos(0), + timeDelay(0.0) +{} + +void BW_MidiSequencer::MidiTrackRow::clear() +{ + time = 0.0; + delay = 0; + absPos = 0; + timeDelay = 0.0; + events.clear(); +} + +void BW_MidiSequencer::MidiTrackRow::sortEvents(bool *noteStates) +{ + typedef std::vector 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 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::iterator j = markAsOn.begin(); j != markAsOn.end(); j++) + noteStates[*j] = true; + } + /***********************************************************************************/ + + events.clear(); + events.insert(events.end(), noteOffs.begin(), noteOffs.end()); + events.insert(events.end(), metas.begin(), metas.end()); + events.insert(events.end(), controllers.begin(), controllers.end()); + events.insert(events.end(), anyOther.begin(), anyOther.end()); +} + +BW_MidiSequencer::BW_MidiSequencer() : + m_interface(NULL), + m_format(Format_MIDI), + m_loopEnabled(false), + m_fullSongTimeLength(0.0), + m_postSongWaitDelay(1.0), + m_loopStartTime(-1.0), + m_loopEndTime(-1.0), + m_tempoMultiplier(1.0), + m_atEnd(false), + m_loopStart(false), + m_loopEnd(false), + m_invalidLoop(false) +{} + +BW_MidiSequencer::~BW_MidiSequencer() +{} + +void BW_MidiSequencer::setInterface(const BW_MidiRtInterface *interface) +{ + // Interface must NOT be NULL + assert(interface); + + //Note ON hook is REQUIRED + assert(interface->rt_noteOn); + //Note OFF hook is REQUIRED + assert(interface->rt_noteOff); + //Note Aftertouch hook is REQUIRED + assert(interface->rt_noteAfterTouch); + //Channel Aftertouch hook is REQUIRED + assert(interface->rt_channelAfterTouch); + //Controller change hook is REQUIRED + assert(interface->rt_controllerChange); + //Patch change hook is REQUIRED + assert(interface->rt_patchChange); + //Pitch bend hook is REQUIRED + assert(interface->rt_pitchBend); + //System Exclusive hook is REQUIRED + assert(interface->rt_systemExclusive); + + m_interface = interface; +} + +BW_MidiSequencer::FileFormat BW_MidiSequencer::getFormat() +{ + return m_format; +} + +const std::vector BW_MidiSequencer::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 &BW_MidiSequencer::getTrackTitles() +{ + return m_musTrackTitles; +} + +const std::vector &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 > &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 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 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 points; + fraction 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(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 t = pos.delay * currentTempo; + pos.timeDelay = t.value(); + pos.time = time; + time += pos.timeDelay; + + //Capture markers after time value calculation + for(size_t i = 0; i < pos.events.size(); i++) + { + MidiEvent &e = pos.events[i]; + if((e.type == MidiEvent::T_SPECIAL) && (e.subtype == MidiEvent::ST_MARKER)) + { + MIDI_MarkerEntry marker; + marker.label = std::string((char *)e.data.data(), e.data.size()); + marker.pos_ticks = pos.absPos; + marker.pos_time = pos.time; + m_musMarkers.push_back(marker); + } + } + + //Capture loop points time positions + if(!m_invalidLoop) + { + // Set loop points times + if(loopStartTicks == pos.absPos) + m_loopStartTime = pos.time; + else if(loopEndTicks == pos.absPos) + m_loopEndTime = pos.time; + } + +#ifdef DEBUG_TIME_CALCULATION + std::fprintf(stdout, "= %10" PRId64 " = %10f%s\n", pos.absPos, pos.time, tempoChanged ? " <----TEMPO CHANGED" : ""); + std::fflush(stdout); +#endif + + abs_position += pos.delay; + posPrev = &pos; + } + + if(time > m_fullSongTimeLength) + m_fullSongTimeLength = time; + } + + m_fullSongTimeLength += m_postSongWaitDelay; + //Set begin of the music + m_trackBeginPosition = m_currentPosition; + //Initial loop position will begin at begin of track until passing of the loop point + m_loopBeginPosition = m_currentPosition; + + /********************************************************************************/ + //Resolve "hell of all times" of too short drum notes: + //move too short percussion note-offs far far away as possible + /********************************************************************************/ +#if 1 //Use this to record WAVEs for comparison before/after implementing of this + if(m_format == Format_MIDI)//Percussion fix is needed for MIDI only, not for IMF/RSXX or CMF + { + //! Minimal real time in seconds +#define DRUM_NOTE_MIN_TIME 0.03 + //! Minimal ticks count +#define DRUM_NOTE_MIN_TICKS 15 + struct NoteState + { + double delay; + uint64_t delayTicks; + bool isOn; + char ___pad[7]; + } drNotes[255]; + uint16_t banks[16]; + + for(size_t tk = 0; tk < trackCount; ++tk) + { + std::memset(drNotes, 0, sizeof(drNotes)); + std::memset(banks, 0, sizeof(banks)); + MidiTrackQueue &track = m_trackData[tk]; + if(track.empty()) + continue;//Empty track is useless! + + for(MidiTrackQueue::iterator it = track.begin(); it != track.end(); it++) + { + MidiTrackRow &pos = *it; + + for(ssize_t e = 0; e < (ssize_t)pos.events.size(); e++) + { + MidiEvent *et = &pos.events[(size_t)e]; + + /* Set MSB/LSB bank */ + if(et->type == MidiEvent::T_CTRLCHANGE) + { + uint8_t ctrlno = et->data[0]; + uint8_t value = et->data[1]; + switch(ctrlno) + { + case 0: // Set bank msb (GM bank) + banks[et->channel] = uint16_t(uint16_t(value) << 8) | uint16_t(banks[et->channel] & 0x00FF); + break; + case 32: // Set bank lsb (XG bank) + banks[et->channel] = (banks[et->channel] & 0xFF00) | (uint16_t(value) & 0x00FF); + break; + } + continue; + } + + bool percussion = (et->channel == 9) || + banks[et->channel] == 0x7E00 || //XG SFX1/SFX2 channel (16128 signed decimal) + banks[et->channel] == 0x7F00; //XG Percussion channel (16256 signed decimal) + if(!percussion) + continue; + + if(et->type == MidiEvent::T_NOTEON) + { + uint8_t note = et->data[0] & 0x7F; + NoteState &ns = drNotes[note]; + ns.isOn = true; + ns.delay = 0.0; + ns.delayTicks = 0; + } + else if(et->type == MidiEvent::T_NOTEOFF) + { + uint8_t note = et->data[0] & 0x7F; + NoteState &ns = drNotes[note]; + if(ns.isOn) + { + ns.isOn = false; + if(ns.delayTicks < DRUM_NOTE_MIN_TICKS || ns.delay < DRUM_NOTE_MIN_TIME)//If note is too short + { + //Move it into next event position if that possible + for(MidiTrackQueue::iterator itNext = it; + itNext != track.end(); + itNext++) + { + MidiTrackRow &posN = *itNext; + if(ns.delayTicks > DRUM_NOTE_MIN_TICKS && ns.delay > DRUM_NOTE_MIN_TIME) + { + //Put note-off into begin of next event list + posN.events.insert(posN.events.begin(), pos.events[(size_t)e]); + //Renive this event from a current row + pos.events.erase(pos.events.begin() + (int)e); + e--; + break; + } + ns.delay += posN.timeDelay; + ns.delayTicks += posN.delay; + } + } + ns.delay = 0.0; + ns.delayTicks = 0; + } + } + } + + //Append time delays to sustaining notes + for(size_t no = 0; no < 128; no++) + { + NoteState &ns = drNotes[no]; + if(ns.isOn) + { + ns.delay += pos.timeDelay; + ns.delayTicks += pos.delay; + } + } + } + } +#undef DRUM_NOTE_MIN_TIME +#undef DRUM_NOTE_MIN_TICKS + } +#endif + + return true; +} + +bool BW_MidiSequencer::processEvents(bool isSeek) +{ + if(m_currentPosition.track.size() == 0) + m_atEnd = true;//No MIDI track data to play + if(m_atEnd) + return false;//No more events in the queue + + m_loopEnd = false; + const size_t TrackCount = m_currentPosition.track.size(); + const Position RowBeginPosition(m_currentPosition); + +#ifdef DEBUG_TIME_CALCULATION + double maxTime = 0.0; +#endif + + for(size_t tk = 0; tk < TrackCount; ++tk) + { + Position::TrackInfo &track = m_currentPosition.track[tk]; + if((track.lastHandledEvent >= 0) && (track.delay <= 0)) + { + //Check is an end of track has been reached + if(track.pos == m_trackData[tk].end()) + { + track.lastHandledEvent = -1; + break; + } + + // Handle event + for(size_t i = 0; i < track.pos->events.size(); i++) + { + const MidiEvent &evt = track.pos->events[i]; +#ifdef ENABLE_BEGIN_SILENCE_SKIPPING + if(!CurrentPositionNew.began && (evt.type == MidiEvent::T_NOTEON)) + CurrentPositionNew.began = true; +#endif + if(isSeek && (evt.type == MidiEvent::T_NOTEON)) + continue; + handleEvent(tk, evt, track.lastHandledEvent); + if(m_loopEnd) + break;//Stop event handling on catching loopEnd event! + } + +#ifdef DEBUG_TIME_CALCULATION + if(maxTime < track.pos->time) + maxTime = track.pos->time; +#endif + // Read next event time (unless the track just ended) + if(track.lastHandledEvent >= 0) + { + track.delay += track.pos->delay; + track.pos++; + } + } + } + +#ifdef DEBUG_TIME_CALCULATION + std::fprintf(stdout, " \r"); + std::fprintf(stdout, "Time: %10f; Audio: %10f\r", maxTime, CurrentPositionNew.absTimePosition); + std::fflush(stdout); +#endif + + // Find shortest delay from all track + uint64_t shortest = 0; + bool shortest_no = true; + + for(size_t tk = 0; tk < TrackCount; ++tk) + { + Position::TrackInfo &track = m_currentPosition.track[tk]; + if((track.lastHandledEvent >= 0) && (shortest_no || track.delay < shortest)) + { + shortest = track.delay; + shortest_no = false; + } + } + + //if(shortest > 0) UI.PrintLn("shortest: %ld", shortest); + + // Schedule the next playevent to be processed after that delay + for(size_t tk = 0; tk < TrackCount; ++tk) + m_currentPosition.track[tk].delay -= shortest; + + fraction t = shortest * m_tempo; + +#ifdef ENABLE_BEGIN_SILENCE_SKIPPING + if(CurrentPositionNew.began) +#endif + m_currentPosition.wait += t.value(); + + //if(shortest > 0) UI.PrintLn("Delay %ld (%g)", shortest, (double)t.valuel()); + if(m_loopStart) + { + m_loopBeginPosition = RowBeginPosition; + m_loopStart = false; + } + + if(shortest_no || m_loopEnd) + { + //Loop if song end or loop end point has reached + m_loopEnd = false; + shortest = 0; + if(!m_loopEnabled) + { + m_atEnd = true; //Don't handle events anymore + m_currentPosition.wait += m_postSongWaitDelay;//One second delay until stop playing + return true;//We have caugh end here! + } + m_currentPosition = m_loopBeginPosition; + } + + return true;//Has events in queue +} + +BW_MidiSequencer::MidiEvent BW_MidiSequencer::parseEvent(const uint8_t **pptr, const uint8_t *end, int &status) +{ + const uint8_t *&ptr = *pptr; + BW_MidiSequencer::MidiEvent evt; + + if(ptr + 1 > end) + { + //When track doesn't ends on the middle of event data, it's must be fine + evt.type = MidiEvent::T_SPECIAL; + evt.subtype = MidiEvent::ST_ENDTRACK; + return evt; + } + + unsigned char byte = *(ptr++); + bool ok = false; + + if(byte == MidiEvent::T_SYSEX || byte == MidiEvent::T_SYSEX2)// Ignore SysEx + { + uint64_t length = ReadVarLenEx(pptr, end, ok); + if(!ok || (ptr + length > end)) + { + m_parsingErrorsString += "parseEvent: Can't read SysEx event - Unexpected end of track data.\n"; + evt.isValid = 0; + return evt; + } + ptr += (size_t)length; + return evt; + } + + if(byte == MidiEvent::T_SPECIAL) + { + // Special event FF + uint8_t evtype = *(ptr++); + uint64_t length = ReadVarLenEx(pptr, end, ok); + if(!ok || (ptr + length > end)) + { + m_parsingErrorsString += "parseEvent: Can't read Special event - Unexpected end of track data.\n"; + evt.isValid = 0; + return evt; + } + std::string data(length ? (const char *)ptr : 0, (size_t)length); + ptr += (size_t)length; + + evt.type = byte; + evt.subtype = evtype; + evt.data.insert(evt.data.begin(), data.begin(), data.end()); + +#if 0 /* Print all tempo events */ + if(evt.subtype == MidiEvent::ST_TEMPOCHANGE) + { + if(hooks.onDebugMessage) + hooks.onDebugMessage(hooks.onDebugMessage_userData, "Temp Change: %02X%02X%02X", evt.data[0], evt.data[1], evt.data[2]); + } +#endif + + /* TODO: Store those meta-strings separately and give ability to read them + * by external functions (to display song title and copyright in the player) */ + if(evt.subtype == MidiEvent::ST_COPYRIGHT) + { + if(m_musCopyright.empty()) + { + m_musCopyright = std::string((const char *)evt.data.data(), evt.data.size()); + if(m_interface->onDebugMessage) + m_interface->onDebugMessage(m_interface->onDebugMessage_userData, "Music copyright: %s", m_musCopyright.c_str()); + } + else if(m_interface->onDebugMessage) + { + std::string str((const char *)evt.data.data(), evt.data.size()); + m_interface->onDebugMessage(m_interface->onDebugMessage_userData, "Extra copyright event: %s", str.c_str()); + } + } + else if(evt.subtype == MidiEvent::ST_SQTRKTITLE) + { + if(m_musTitle.empty()) + { + m_musTitle = std::string((const char *)evt.data.data(), evt.data.size()); + if(m_interface->onDebugMessage) + m_interface->onDebugMessage(m_interface->onDebugMessage_userData, "Music title: %s", m_musTitle.c_str()); + } + else if(m_interface->onDebugMessage) + { + //TODO: Store track titles and associate them with each track and make API to retreive them + std::string str((const char *)evt.data.data(), evt.data.size()); + m_musTrackTitles.push_back(str); + m_interface->onDebugMessage(m_interface->onDebugMessage_userData, "Track title: %s", str.c_str()); + } + } + else if(evt.subtype == MidiEvent::ST_INSTRTITLE) + { + if(m_interface->onDebugMessage) + { + std::string str((const char *)evt.data.data(), evt.data.size()); + m_interface->onDebugMessage(m_interface->onDebugMessage_userData, "Instrument: %s", str.c_str()); + } + } + else if(evt.subtype == MidiEvent::ST_MARKER) + { + //To lower + for(size_t i = 0; i < data.size(); i++) + { + if(data[i] <= 'Z' && data[i] >= 'A') + data[i] = data[i] - ('Z' - 'z'); + } + + if(data == "loopstart") + { + //Return a custom Loop Start event instead of Marker + evt.subtype = MidiEvent::ST_LOOPSTART; + evt.data.clear();//Data is not needed + return evt; + } + + if(data == "loopend") + { + //Return a custom Loop End event instead of Marker + evt.subtype = MidiEvent::ST_LOOPEND; + evt.data.clear();//Data is not needed + return evt; + } + } + + if(evtype == MidiEvent::ST_ENDTRACK) + status = -1;//Finalize track + + return evt; + } + + // Any normal event (80..EF) + if(byte < 0x80) + { + byte = static_cast(status | 0x80); + ptr--; + } + + //Sys Com Song Select(Song #) [0-127] + if(byte == MidiEvent::T_SYSCOMSNGSEL) + { + if(ptr + 1 > end) + { + m_parsingErrorsString += "parseEvent: Can't read System Command Song Select event - Unexpected end of track data.\n"; + evt.isValid = 0; + return evt; + } + evt.type = byte; + evt.data.push_back(*(ptr++)); + return evt; + } + + //Sys Com Song Position Pntr [LSB, MSB] + if(byte == MidiEvent::T_SYSCOMSPOSPTR) + { + if(ptr + 2 > end) + { + m_parsingErrorsString += "parseEvent: Can't read System Command Position Pointer event - Unexpected end of track data.\n"; + evt.isValid = 0; + return evt; + } + evt.type = byte; + evt.data.push_back(*(ptr++)); + evt.data.push_back(*(ptr++)); + return evt; + } + + uint8_t midCh = byte & 0x0F, evType = (byte >> 4) & 0x0F; + status = byte; + evt.channel = midCh; + evt.type = evType; + + switch(evType) + { + case MidiEvent::T_NOTEOFF://2 byte length + case MidiEvent::T_NOTEON: + case MidiEvent::T_NOTETOUCH: + case MidiEvent::T_CTRLCHANGE: + case MidiEvent::T_WHEEL: + if(ptr + 2 > end) + { + m_parsingErrorsString += "parseEvent: Can't read regular 2-byte event - Unexpected end of track data.\n"; + evt.isValid = 0; + return evt; + } + + evt.data.push_back(*(ptr++)); + evt.data.push_back(*(ptr++)); + + if((evType == MidiEvent::T_NOTEON) && (evt.data[1] == 0)) + { + evt.type = MidiEvent::T_NOTEOFF; // Note ON with zero velocity is Note OFF! + } //111'th loopStart controller (RPG Maker and others) + else if((evType == MidiEvent::T_CTRLCHANGE) && (evt.data[0] == 111)) + { + //Change event type to custom Loop Start event and clear data + evt.type = MidiEvent::T_SPECIAL; + evt.subtype = MidiEvent::ST_LOOPSTART; + evt.data.clear(); + } + + return evt; + case MidiEvent::T_PATCHCHANGE://1 byte length + case MidiEvent::T_CHANAFTTOUCH: + if(ptr + 1 > end) + { + m_parsingErrorsString += "parseEvent: Can't read regular 1-byte event - Unexpected end of track data.\n"; + evt.isValid = 0; + return evt; + } + evt.data.push_back(*(ptr++)); + return evt; + } + + return evt; +} + +void BW_MidiSequencer::handleEvent(size_t tk, const BW_MidiSequencer::MidiEvent &evt, int &status) +{ + if(m_interface->onEvent) + { + m_interface->onEvent(m_interface->onEvent_userData, + evt.type, evt.subtype, evt.channel, + evt.data.data(), evt.data.size()); + } + + if(evt.type == MidiEvent::T_SYSEX || evt.type == MidiEvent::T_SYSEX2) // Ignore SysEx + { + //std::string data( length?(const char*) &TrackData[tk][CurrentPosition.track[tk].ptr]:0, length ); + //UI.PrintLn("SysEx %02X: %u bytes", byte, length/*, data.c_str()*/); + m_interface->rt_systemExclusive(m_interface->rtUserData, evt.data.data(), evt.data.size()); + return; + } + + if(evt.type == MidiEvent::T_SPECIAL) + { + // Special event FF + uint8_t evtype = evt.subtype; + uint64_t length = (uint64_t)evt.data.size(); + std::string data(length ? (const char *)evt.data.data() : 0, (size_t)length); + + if(evtype == MidiEvent::ST_ENDTRACK)//End Of Track + { + status = -1; + return; + } + + if(evtype == MidiEvent::ST_TEMPOCHANGE)//Tempo change + { + m_tempo = m_invDeltaTicks * fraction(ReadBEint(evt.data.data(), evt.data.size())); + return; + } + + if(evtype == MidiEvent::ST_MARKER)//Meta event + { + //Do nothing! :-P + return; + } + + if(evtype == MidiEvent::ST_DEVICESWITCH) + { + if(m_interface->rt_deviceSwitch) + m_interface->rt_deviceSwitch(m_interface->rtUserData, tk, data.c_str(), data.size()); + return; + } + + //if(evtype >= 1 && evtype <= 6) + // UI.PrintLn("Meta %d: %s", evtype, data.c_str()); + + //Turn on Loop handling when loop is enabled + if(m_loopEnabled && !m_invalidLoop) + { + if(evtype == MidiEvent::ST_LOOPSTART) // Special non-spec ADLMIDI special for IMF playback: Direct poke to AdLib + { + m_loopStart = true; + return; + } + + if(evtype == MidiEvent::ST_LOOPEND) // Special non-spec ADLMIDI special for IMF playback: Direct poke to AdLib + { + m_loopEnd = true; + return; + } + } + + if(evtype == MidiEvent::ST_RAWOPL) // Special non-spec ADLMIDI special for IMF playback: Direct poke to AdLib + { + if(m_interface->rt_rawOPL) + m_interface->rt_rawOPL(m_interface->rtUserData, static_cast(data[0]), static_cast(data[1])); + return; + } + + return; + } + + // Any normal event (80..EF) + // if(evt.type < 0x80) + // { + // byte = static_cast(CurrentPosition.track[tk].status | 0x80); + // CurrentPosition.track[tk].ptr--; + // } + + if(evt.type == MidiEvent::T_SYSCOMSNGSEL || + evt.type == MidiEvent::T_SYSCOMSPOSPTR) + return; + + /*UI.PrintLn("@%X Track %u: %02X %02X", + CurrentPosition.track[tk].ptr-1, (unsigned)tk, byte, + TrackData[tk][CurrentPosition.track[tk].ptr]);*/ + uint8_t midCh = evt.channel;//byte & 0x0F, EvType = byte >> 4; + if(m_interface->rt_currentDevice) + midCh += (uint8_t)m_interface->rt_currentDevice(m_interface->rtUserData, tk); + status = evt.type; + + switch(evt.type) + { + case MidiEvent::T_NOTEOFF: // Note off + { + uint8_t note = evt.data[0]; + m_interface->rt_noteOff(m_interface->rtUserData, midCh, note); + break; + } + + case MidiEvent::T_NOTEON: // Note on + { + uint8_t note = evt.data[0]; + uint8_t vol = evt.data[1]; + m_interface->rt_noteOn(m_interface->rtUserData, midCh, note, vol); + break; + } + + case MidiEvent::T_NOTETOUCH: // Note touch + { + uint8_t note = evt.data[0]; + uint8_t vol = evt.data[1]; + m_interface->rt_noteAfterTouch(m_interface->rtUserData, midCh, note, vol); + break; + } + + case MidiEvent::T_CTRLCHANGE: // Controller change + { + uint8_t ctrlno = evt.data[0]; + uint8_t value = evt.data[1]; + m_interface->rt_controllerChange(m_interface->rtUserData, midCh, ctrlno, value); + break; + } + + case MidiEvent::T_PATCHCHANGE: // Patch change + { + m_interface->rt_patchChange(m_interface->rtUserData, midCh, evt.data[0]); + break; + } + + case MidiEvent::T_CHANAFTTOUCH: // Channel after-touch + { + uint8_t chanat = evt.data[0]; + m_interface->rt_channelAfterTouch(m_interface->rtUserData, midCh, chanat); + break; + } + + case MidiEvent::T_WHEEL: // Wheel/pitch bend + { + uint8_t a = evt.data[0]; + uint8_t b = evt.data[1]; + m_interface->rt_pitchBend(m_interface->rtUserData, midCh, b, a); + break; + } + }//switch +} + +double BW_MidiSequencer::Tick(double s, double granularity) +{ + assert(m_interface);// MIDI output interface must be defined! + + s *= m_tempoMultiplier; +#ifdef ENABLE_BEGIN_SILENCE_SKIPPING + if(CurrentPositionNew.began) +#endif + m_currentPosition.wait -= s; + m_currentPosition.absTimePosition += s; + + int antiFreezeCounter = 10000;//Limit 10000 loops to avoid freezing + while((m_currentPosition.wait <= granularity * 0.5) && (antiFreezeCounter > 0)) + { + //std::fprintf(stderr, "wait = %g...\n", CurrentPosition.wait); + if(!processEvents()) + break; + if(m_currentPosition.wait <= 0.0) + antiFreezeCounter--; + } + + if(antiFreezeCounter <= 0) + m_currentPosition.wait += 1.0;/* Add extra 1 second when over 10000 events + with zero delay are been detected */ + + if(m_currentPosition.wait < 0.0)//Avoid negative delay value! + return 0.0; + + return m_currentPosition.wait; +} + + +double BW_MidiSequencer::seek(double seconds, const double granularity) +{ + if(seconds < 0.0) + return 0.0;//Seeking negative position is forbidden! :-P + const double granualityHalf = granularity * 0.5, + s = seconds;//m_setup.delay < m_setup.maxdelay ? m_setup.delay : m_setup.maxdelay; + + /* Attempt to go away out of song end must rewind position to begin */ + if(seconds > m_fullSongTimeLength) + { + rewind(); + return 0.0; + } + + bool loopFlagState = m_loopEnabled; + // Turn loop pooints off because it causes wrong position rememberin on a quick seek + m_loopEnabled = false; + + /* + * Seeking search is similar to regular ticking, except of next things: + * - We don't processsing arpeggio and vibrato + * - To keep correctness of the state after seek, begin every search from begin + * - All sustaining notes must be killed + * - Ignore Note-On events + */ + rewind(); + + /* + * Set "loop Start" to false to prevent overwrite of loopStart position with + * seek destinition position + * + * TODO: Detect & set loopStart position on load time to don't break loop while seeking + */ + m_loopStart = false; + + while((m_currentPosition.absTimePosition < seconds) && + (m_currentPosition.absTimePosition < m_fullSongTimeLength)) + { + m_currentPosition.wait -= s; + m_currentPosition.absTimePosition += s; + int antiFreezeCounter = 10000;//Limit 10000 loops to avoid freezing + double dstWait = m_currentPosition.wait + granualityHalf; + while((m_currentPosition.wait <= granualityHalf)/*&& (antiFreezeCounter > 0)*/) + { + //std::fprintf(stderr, "wait = %g...\n", CurrentPosition.wait); + if(!processEvents(true)) + break; + //Avoid freeze because of no waiting increasing in more than 10000 cycles + if(m_currentPosition.wait <= dstWait) + antiFreezeCounter--; + else + { + dstWait = m_currentPosition.wait + granualityHalf; + antiFreezeCounter = 10000; + } + } + if(antiFreezeCounter <= 0) + m_currentPosition.wait += 1.0;/* Add extra 1 second when over 10000 events + with zero delay are been detected */ + } + + if(m_currentPosition.wait < 0.0) + m_currentPosition.wait = 0.0; + + m_loopEnabled = loopFlagState; + return m_currentPosition.wait; +} + +double BW_MidiSequencer::tell() +{ + return m_currentPosition.absTimePosition; +} + +double BW_MidiSequencer::timeLength() +{ + return m_fullSongTimeLength; +} + +double BW_MidiSequencer::getLoopStart() +{ + return m_loopStartTime; +} + +double BW_MidiSequencer::getLoopEnd() +{ + return m_loopEndTime; +} + +void BW_MidiSequencer::rewind() +{ + m_currentPosition = m_trackBeginPosition; + m_atEnd = false; + m_loopStart = true; + m_loopEnd = false; + //invalidLoop = false;//No more needed here as this flag is set on load time +} + +void BW_MidiSequencer::setTempo(double tempo) +{ + m_tempoMultiplier = tempo; +} + +bool BW_MidiSequencer::loadMIDI(const std::string &filename) +{ + FileAndMemReader file; + file.openFile(filename.c_str()); + if(!loadMIDI(file)) + return false; + return true; +} + +bool BW_MidiSequencer::loadMIDI(const void *data, size_t size) +{ + FileAndMemReader file; + file.openData(data, size); + return loadMIDI(file); +} + +template +class 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 > rawTrackData; + //! Temp buffer for conversion + BufferGuard cvt_buf; + m_parsingErrorsString.clear(); + + assert(m_interface);// MIDI output interface must be defined! + + if(!fr.isValid()) + { + m_errorString = "Invalid data stream!\n"; +#ifndef _WIN32 + m_errorString += std::strerror(errno); +#endif + return false; + } + + m_atEnd = false; + m_loopStart = true; + m_invalidLoop = false; + + m_format = Format_MIDI; + + bool is_GMF = false; // GMD/MUS files (ScummVM) + //bool is_MUS = false; // MUS/DMX files (Doom) + bool is_IMF = false; // IMF + bool is_CMF = false; // Creative Music format (CMF/CTMF) + bool is_RSXX = false; // RSXX, such as Cartooners + + const size_t HeaderSize = 4 + 4 + 2 + 2 + 2; // 14 + char headerBuf[HeaderSize] = ""; + size_t DeltaTicks = 192, TrackCount = 1; + +riffskip: + fsize = fr.read(headerBuf, 1, HeaderSize); + + if(std::memcmp(headerBuf, "RIFF", 4) == 0) + { + fr.seek(6l, FileAndMemReader::CUR); + goto riffskip; + } + + if(std::memcmp(headerBuf, "GMF\x1", 4) == 0) + { + // GMD/MUS files (ScummVM) + fr.seek(7 - static_cast(HeaderSize), FileAndMemReader::CUR); + is_GMF = true; + } +#ifndef BWMIDI_DISABLE_MUS_SUPPORT + else if(std::memcmp(headerBuf, "MUS\x1A", 4) == 0) + { + // MUS/DMX files (Doom) + size_t mus_len = fr.fileSize(); + fr.seek(0, FileAndMemReader::SET); + uint8_t *mus = (uint8_t *)malloc(mus_len); + if(!mus) + { + m_errorString = "Out of memory!"; + return false; + } + fr.read(mus, 1, mus_len); + //Close source stream + fr.close(); + + uint8_t *mid = NULL; + uint32_t mid_len = 0; + int m2mret = Convert_mus2midi(mus, static_cast(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(mid_len)); + //Re-Read header again! + goto riffskip; + } +#endif //BWMIDI_DISABLE_MUS_SUPPORT + +#ifndef BWMIDI_DISABLE_XMI_SUPPORT + else if(std::memcmp(headerBuf, "FORM", 4) == 0) + { + if(std::memcmp(headerBuf + 8, "XDIR", 4) != 0) + { + fr.close(); + m_errorString = fr.fileName() + ": Invalid format\n"; + return false; + } + + size_t mus_len = fr.fileSize(); + fr.seek(0, FileAndMemReader::SET); + + uint8_t *mus = (uint8_t*)malloc(mus_len); + if(!mus) + { + m_errorString = "Out of memory!"; + return false; + } + fr.read(mus, 1, mus_len); + //Close source stream + fr.close(); + + uint8_t *mid = NULL; + uint32_t mid_len = 0; + int m2mret = Convert_xmi2midi(mus, static_cast(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(mid_len)); + //Re-Read header again! + goto riffskip; + } +#endif //BWMIDI_DISABLE_XMI_SUPPORT + + else if(std::memcmp(headerBuf, "CTMF", 4) == 0) + { + // Creative Music Format (CMF). + // When playing CTMF files, use the following commandline: + // adlmidi song8.ctmf -p -v 1 1 0 + // i.e. enable percussion mode, deeper vibrato, and use only 1 card. + is_CMF = true; + m_format = Format_CMF; + //unsigned version = ReadLEint(HeaderBuf+4, 2); + uint64_t ins_start = ReadLEint(headerBuf + 6, 2); + uint64_t mus_start = ReadLEint(headerBuf + 8, 2); + //unsigned deltas = ReadLEint(HeaderBuf+10, 2); + uint64_t ticks = ReadLEint(headerBuf + 12, 2); + // Read title, author, remarks start offsets in file + fr.read(headerBuf, 1, 6); + //unsigned long notes_starts[3] = {ReadLEint(HeaderBuf+0,2),ReadLEint(HeaderBuf+0,4),ReadLEint(HeaderBuf+0,6)}; + fr.seek(16, FileAndMemReader::CUR); // Skip the channels-in-use table + fr.read(headerBuf, 1, 4); + uint64_t ins_count = ReadLEint(headerBuf + 0, 2); //, basictempo = ReadLEint(HeaderBuf+2, 2); + fr.seek(static_cast(ins_start), FileAndMemReader::SET); + + m_cmfInstruments.reserve(static_cast(ins_count)); + for(uint64_t i = 0; i < ins_count; ++i) + { + CmfInstrument inst; + fr.read(inst.data, 1, 16); + m_cmfInstruments.push_back(inst); + } + + fr.seeku(mus_start, FileAndMemReader::SET); + TrackCount = 1; + DeltaTicks = (size_t)ticks; + } + else + { + // Try to identify RSXX format + if(headerBuf[0] == 0x7D) + { + fr.seek(0x6D, FileAndMemReader::SET); + fr.read(headerBuf, 6, 1); + if(std::memcmp(headerBuf, "rsxx}u", 6) == 0) + { + is_RSXX = true; + m_format = Format_RSXX; + fr.seek(0x7D, FileAndMemReader::SET); + TrackCount = 1; + DeltaTicks = 60; + } + } + + // Try parsing as an IMF file + if(!is_RSXX) + { + do + { + uint8_t raw[4]; + size_t end = static_cast(headerBuf[0]) + 256 * static_cast(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(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()); + m_invDeltaTicks = fraction(1, 1000000l * static_cast(DeltaTicks)); + if(is_CMF || is_RSXX) + m_tempo = fraction(1, static_cast(DeltaTicks)); + else + m_tempo = fraction(1, static_cast(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(headerBuf[0]) + 256 * static_cast(headerBuf[1]); + unsigned IMF_tempo = 1428; + static const unsigned char imf_tempo[] = {0x0,//Zero delay! + MidiEvent::T_SPECIAL, MidiEvent::ST_TEMPOCHANGE, 0x4, + static_cast(IMF_tempo >> 24), + static_cast(IMF_tempo >> 16), + static_cast(IMF_tempo >> 8), + static_cast(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(raw[2]); + delay += 256 * static_cast(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(pos), FileAndMemReader::SET); + } + //else if(is_MUS) // Read TrackLength from file position 4 + //{ + // size_t pos = fr.tell(); + // fr.seek(4, FileAndMemReader::SET); + // TrackLength = static_cast(fr.getc()); + // TrackLength += static_cast(fr.getc() << 8); + // fr.seek(static_cast(pos), FileAndMemReader::SET); + //} + else + { + fsize = fr.read(headerBuf, 1, 8); + if(std::memcmp(headerBuf, "MTrk", 4) != 0) + { + fr.close(); + m_errorString = fr.fileName() + ": Invalid format, MTrk signature is not found!\n"; + return false; + } + TrackLength = (size_t)ReadBEint(headerBuf + 4, 4); + } + + // Read track data + rawTrackData[tk].resize(TrackLength); + fsize = fr.read(&rawTrackData[tk][0], 1, TrackLength); + totalGotten += fsize; + + if(is_GMF/*|| is_MUS*/) // Note: CMF does include the track end tag. + rawTrackData[tk].insert(rawTrackData[tk].end(), EndTag + 0, EndTag + 4); + if(is_RSXX)//Finalize raw track data with a zero + rawTrackData[tk].push_back(0); + + //bool ok = false; + //// Read next event time + //uint64_t tkDelay = ReadVarLenEx(tk, ok); + //if(ok) + // CurrentPosition.track[tk].delay = tkDelay; + //else + //{ + // std::stringstream msg; + // msg << fr._fileName << ": invalid variable length in the track " << tk << "! (error code " << tkDelay << ")"; + // ADLMIDI_ErrorString = msg.str(); + // return false; + //} + } + } + + for(size_t tk = 0; tk < TrackCount; ++tk) + totalGotten += rawTrackData[tk].size(); + + if(totalGotten == 0) + { + m_errorString = fr.fileName() + ": Empty track data"; + return false; + } + + //Build new MIDI events table + if(!buildTrackData(rawTrackData)) + { + m_errorString = fr.fileName() + ": MIDI data parsing error has occouped!\n" + m_parsingErrorsString; + return false; + } + + return true; +} -- cgit v1.2.3 From 2f23710e928876c785f19689ac13f81511c0c7ae Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Thu, 21 Jun 2018 04:24:31 +0300 Subject: Remove second Bend coefficient I have tested 14'th bank on DosBox emulator in current state, and seems everything is fine, no any hi-hats distortions. --- src/adlmidi_midiplay.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'src') diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index 5b7f0d9..f539b16 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -953,11 +953,7 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, if(vibrato && (!d || d->vibdelay >= Ch[MidCh].vibdelay)) bend += static_cast(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 +#define BEND_COEFFICIENT 172.4387 opl.NoteOn(c, BEND_COEFFICIENT * std::exp(0.057762265 * (currentTone + bend + phase))); #undef BEND_COEFFICIENT if(hooks.onNote) -- cgit v1.2.3 From 0e6debe44d6e03c495d670762c1a7d6e8acb12c8 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Thu, 21 Jun 2018 04:38:06 +0300 Subject: Fixed C++98 bulid lround is C99 and C++11, not C++98! --- src/adlmidi_load.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/adlmidi_load.cpp b/src/adlmidi_load.cpp index 3e51b77..d7b1e52 100644 --- a/src/adlmidi_load.cpp +++ b/src/adlmidi_load.cpp @@ -104,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(round(voice2_fine_tune * (1000.0 / 15.625))); value = (value < -128) ? -128 : value; value = (value > +127) ? +127 : value; ins.second_voice_detune = static_cast(value); -- cgit v1.2.3 From 51ffa56a7f30679ecb40d76725a94b9c954546a1 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Thu, 21 Jun 2018 04:41:54 +0300 Subject: Attempt to fix a Windows build --- src/midi_sequencer.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/midi_sequencer.h b/src/midi_sequencer.h index d3b542e..2a2b543 100644 --- a/src/midi_sequencer.h +++ b/src/midi_sequencer.h @@ -33,7 +33,7 @@ extern "C" { #include #include -struct BW_MidiRtInterface +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); @@ -82,7 +82,7 @@ struct BW_MidiRtInterface typedef uint64_t (*RtCurrentDevice)(void *userdata, size_t track); RtCurrentDevice rt_currentDevice; -}; +} BW_MidiRtInterface; #ifdef __cplusplus } -- cgit v1.2.3 From b9304bc1342ccfa645ffb422fbaad1965e64178f Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Thu, 21 Jun 2018 04:43:55 +0300 Subject: Second attempt to fix Windows build --- src/midi_sequencer.hpp | 2 +- src/midi_sequencer_impl.hpp | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/midi_sequencer.hpp b/src/midi_sequencer.hpp index 370068b..a8e04d0 100644 --- a/src/midi_sequencer.hpp +++ b/src/midi_sequencer.hpp @@ -351,7 +351,7 @@ public: * @brief Sets the RT interface * @param interface Pre-Initialized interface structure (pointer will be taken) */ - void setInterface(const BW_MidiRtInterface *interface); + void setInterface(const BW_MidiRtInterface *intrf); /** * @brief Returns file format type of currently loaded file diff --git a/src/midi_sequencer_impl.hpp b/src/midi_sequencer_impl.hpp index 1e4e643..1d627fe 100644 --- a/src/midi_sequencer_impl.hpp +++ b/src/midi_sequencer_impl.hpp @@ -252,29 +252,29 @@ BW_MidiSequencer::BW_MidiSequencer() : BW_MidiSequencer::~BW_MidiSequencer() {} -void BW_MidiSequencer::setInterface(const BW_MidiRtInterface *interface) +void BW_MidiSequencer::setInterface(const BW_MidiRtInterface *intrf) { // Interface must NOT be NULL - assert(interface); + assert(intrf); //Note ON hook is REQUIRED - assert(interface->rt_noteOn); + assert(intrf->rt_noteOn); //Note OFF hook is REQUIRED - assert(interface->rt_noteOff); + assert(intrf->rt_noteOff); //Note Aftertouch hook is REQUIRED - assert(interface->rt_noteAfterTouch); + assert(intrf->rt_noteAfterTouch); //Channel Aftertouch hook is REQUIRED - assert(interface->rt_channelAfterTouch); + assert(intrf->rt_channelAfterTouch); //Controller change hook is REQUIRED - assert(interface->rt_controllerChange); + assert(intrf->rt_controllerChange); //Patch change hook is REQUIRED - assert(interface->rt_patchChange); + assert(intrf->rt_patchChange); //Pitch bend hook is REQUIRED - assert(interface->rt_pitchBend); + assert(intrf->rt_pitchBend); //System Exclusive hook is REQUIRED - assert(interface->rt_systemExclusive); + assert(intrf->rt_systemExclusive); - m_interface = interface; + m_interface = intrf; } BW_MidiSequencer::FileFormat BW_MidiSequencer::getFormat() -- cgit v1.2.3 From 96fef8f4aa43bc60b54e765da20d465a6830e0c7 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Thu, 21 Jun 2018 04:53:22 +0300 Subject: Fix MSVC's warning C4267 in the file reader --- src/file_reader.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/file_reader.hpp b/src/file_reader.hpp index c2cfe7b..0f630df 100644 --- a/src/file_reader.hpp +++ b/src/file_reader.hpp @@ -285,10 +285,10 @@ public: return 0; if(!m_fp) return m_mp_size; //Size of memory block is well known - size_t old_pos = tell(); - seek(0, FileAndMemReader::END); - size_t file_size = tell(); - seek(old_pos, FileAndMemReader::SET); + size_t old_pos = this->tell(); + seek(0l, FileAndMemReader::END); + size_t file_size = this->tell(); + seek(static_cast(old_pos), FileAndMemReader::SET); return file_size; } }; -- cgit v1.2.3 From f20f618327deba5091a384eb749fd4b4f7c10a2f Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Thu, 21 Jun 2018 05:01:50 +0300 Subject: Fixed build of MIDI sequencer out of pre-included private ADLMIDI's header --- src/adlmidi_private.hpp | 2 +- src/adlmidi_sequencer.cpp | 5 +++-- src/midi_sequencer_impl.hpp | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp index 446bae0..06d21ab 100644 --- a/src/adlmidi_private.hpp +++ b/src/adlmidi_private.hpp @@ -134,7 +134,7 @@ typedef int32_t ssize_t; #include "file_reader.hpp" #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER -// Rename class to avoid ABI conflicts +// Rename class to avoid ABI collisions #define BW_MidiSequencer AdlMidiSequencer #include "midi_sequencer.hpp" typedef BW_MidiSequencer MidiSequencer; diff --git a/src/adlmidi_sequencer.cpp b/src/adlmidi_sequencer.cpp index 8bc3569..fcc1380 100644 --- a/src/adlmidi_sequencer.cpp +++ b/src/adlmidi_sequencer.cpp @@ -23,11 +23,12 @@ #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER -#include "adlmidi_private.hpp" - +// 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 * diff --git a/src/midi_sequencer_impl.hpp b/src/midi_sequencer_impl.hpp index 1d627fe..8c0e3dd 100644 --- a/src/midi_sequencer_impl.hpp +++ b/src/midi_sequencer_impl.hpp @@ -26,6 +26,7 @@ #include #include #include +#include #include -- cgit v1.2.3 From 86038a9ccafd7e848e67535f7995d86601cb1e0f Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Thu, 21 Jun 2018 05:07:47 +0300 Subject: Remove useless "_parent" pointer from OPL3 class --- src/adlmidi_private.hpp | 2 -- 1 file changed, 2 deletions(-) (limited to 'src') diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp index 06d21ab..b80d2f2 100644 --- a/src/adlmidi_private.hpp +++ b/src/adlmidi_private.hpp @@ -207,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 { @@ -216,7 +215,6 @@ public: friend class AdlInstrumentTester; uint32_t NumChannels; char ____padding[4]; - ADL_MIDIPlayer *_parent; #ifndef ADLMIDI_HW_OPL std::vector > cardsOP2; #endif -- cgit v1.2.3 From 6b0ca0b306b23a313491742f0c78debb49a037e9 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Thu, 21 Jun 2018 05:47:20 +0300 Subject: Attempt to fix build on Windows --- src/file_reader.hpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/file_reader.hpp b/src/file_reader.hpp index 0f630df..f601cee 100644 --- a/src/file_reader.hpp +++ b/src/file_reader.hpp @@ -26,9 +26,10 @@ #ifndef FILE_AND_MEM_READER_HHHH #define FILE_AND_MEM_READER_HHHH -#include -#include +#include // std::string +#include // std::fopen, std::fread, std::fseek, std::ftell, std::fclose, std::feof #ifdef _WIN32 +#include // std::strlen #include // MultiByteToWideChar #endif @@ -93,7 +94,7 @@ public: m_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); + int size = MultiByteToWideChar(CP_UTF8, 0, path, static_cast(std::strlen(path)), widePath, MAX_PATH); widePath[size] = '\0'; m_fp = _wfopen(widePath, L"rb"); #endif -- cgit v1.2.3 From cca01359c3f7acc5cfd016d59561fae41b72793e Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Thu, 21 Jun 2018 05:49:47 +0300 Subject: Fix for "NOMINMAX" redefinition warning on Windows --- src/adlmidi_private.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp index b80d2f2..058a431 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; -- cgit v1.2.3 From 34f55ea7bad2f177d1a50708f7c269d90d9aa2f0 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Thu, 21 Jun 2018 06:00:23 +0300 Subject: Fix MSVC build because of missing `ssize_t` typedef --- src/midi_sequencer_impl.hpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/midi_sequencer_impl.hpp b/src/midi_sequencer_impl.hpp index 8c0e3dd..6f62e0b 100644 --- a/src/midi_sequencer_impl.hpp +++ b/src/midi_sequencer_impl.hpp @@ -27,9 +27,24 @@ #include #include #include - #include +#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 -- cgit v1.2.3 From 2fcf34cdc43a7ef546887448f49c4cfa084ba935 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Thu, 21 Jun 2018 08:04:31 +0300 Subject: MIDI Sequencer: Small clean-up --- src/adlmidi.cpp | 5 +- src/midi_sequencer.hpp | 17 ++++--- src/midi_sequencer_impl.hpp | 109 +++++++++++++++++++++++++++++--------------- 3 files changed, 88 insertions(+), 43 deletions(-) (limited to 'src') diff --git a/src/adlmidi.cpp b/src/adlmidi.cpp index 69755d3..efd716c 100644 --- a/src/adlmidi.cpp +++ b/src/adlmidi.cpp @@ -517,7 +517,10 @@ ADLMIDI_EXPORT int adl_switchEmulator(struct ADL_MIDIPlayer *device, int emulato if(device) { MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); - if(play && (emulator >= 0) && (emulator < ADLMIDI_EMU_end)) + assert(play); + if(!play) + return -1; + if((emulator >= 0) && (emulator < ADLMIDI_EMU_end)) { play->m_setup.emulator = emulator; adl_reset(device); diff --git a/src/midi_sequencer.hpp b/src/midi_sequencer.hpp index a8e04d0..89b4619 100644 --- a/src/midi_sequencer.hpp +++ b/src/midi_sequencer.hpp @@ -189,7 +189,8 @@ class BW_MidiSequencer { //! Was track began playing bool began; - char padding[7]; + //! Reserved + char __padding[7]; //! Waiting time before next event in seconds double wait; //! Absolute time position on the track in seconds @@ -197,15 +198,19 @@ class BW_MidiSequencer //! Track information struct TrackInfo { - size_t ptr; //! Delay to next event in a track uint64_t delay; //! Last handled event type - int lastHandledEvent; - char padding2[4]; + int32_t lastHandledEvent; + //! Reserved + char __padding2[4]; //! MIDI Events queue position iterator MidiTrackQueue::iterator pos; - TrackInfo(): ptr(0), delay(0), lastHandledEvent(0) {} + + TrackInfo() : + delay(0), + lastHandledEvent(0) + {} }; std::vector track; Position(): began(false), wait(0.0), absTimePosition(0.0), track() @@ -243,7 +248,7 @@ class BW_MidiSequencer * @param evt MIDI event entry * @param status Recent event type, -1 returned when end of track event was handled. */ - void handleEvent(size_t tk, const MidiEvent &evt, int &status); + void handleEvent(size_t tk, const MidiEvent &evt, int32_t &status); public: /** diff --git a/src/midi_sequencer_impl.hpp b/src/midi_sequencer_impl.hpp index 6f62e0b..b3bfefa 100644 --- a/src/midi_sequencer_impl.hpp +++ b/src/midi_sequencer_impl.hpp @@ -59,7 +59,7 @@ typedef int32_t ssize_t; * @param nbytes Count of bytes to parse integer * @return Extracted unsigned integer */ -static inline uint64_t ReadBEint(const void *buffer, size_t nbytes) +static inline uint64_t readBEint(const void *buffer, size_t nbytes) { uint64_t result = 0; const unsigned char *data = reinterpret_cast(buffer); @@ -76,7 +76,7 @@ static inline uint64_t ReadBEint(const void *buffer, size_t nbytes) * @param nbytes Count of bytes to parse integer * @return Extracted unsigned integer */ -static inline uint64_t ReadLEint(const void *buffer, size_t nbytes) +static inline uint64_t readLEint(const void *buffer, size_t nbytes) { uint64_t result = 0; const unsigned char *data = reinterpret_cast(buffer); @@ -573,7 +573,7 @@ bool BW_MidiSequencer::buildTrackData(const std::vector > & TempoChangePoint tempoMarker; MidiEvent &tempoPoint = tempos[tempo_change_index]; tempoMarker.absPos = tempoPoint.absPosition; - tempoMarker.tempo = m_invDeltaTicks * fraction(ReadBEint(tempoPoint.data.data(), tempoPoint.data.size())); + tempoMarker.tempo = m_invDeltaTicks * fraction(readBEint(tempoPoint.data.data(), tempoPoint.data.size())); points.push_back(tempoMarker); tempo_change_index++; } @@ -839,7 +839,7 @@ bool BW_MidiSequencer::processEvents(bool isSeek) #ifdef DEBUG_TIME_CALCULATION std::fprintf(stdout, " \r"); - std::fprintf(stdout, "Time: %10f; Audio: %10f\r", maxTime, CurrentPositionNew.absTimePosition); + std::fprintf(stdout, "Time: %10f; Audio: %10f\r", maxTime, m_currentPosition.absTimePosition); std::fflush(stdout); #endif @@ -1107,7 +1107,7 @@ BW_MidiSequencer::MidiEvent BW_MidiSequencer::parseEvent(const uint8_t **pptr, c return evt; } -void BW_MidiSequencer::handleEvent(size_t tk, const BW_MidiSequencer::MidiEvent &evt, int &status) +void BW_MidiSequencer::handleEvent(size_t tk, const BW_MidiSequencer::MidiEvent &evt, int32_t &status) { if(m_interface->onEvent) { @@ -1139,7 +1139,7 @@ void BW_MidiSequencer::handleEvent(size_t tk, const BW_MidiSequencer::MidiEvent if(evtype == MidiEvent::ST_TEMPOCHANGE)//Tempo change { - m_tempo = m_invDeltaTicks * fraction(ReadBEint(evt.data.data(), evt.data.size())); + m_tempo = m_invDeltaTicks * fraction(readBEint(evt.data.data(), evt.data.size())); return; } @@ -1463,12 +1463,17 @@ bool BW_MidiSequencer::loadMIDI(FileAndMemReader &fr) 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] = ""; + 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); + 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) { @@ -1479,7 +1484,7 @@ riffskip: if(std::memcmp(headerBuf, "GMF\x1", 4) == 0) { // GMD/MUS files (ScummVM) - fr.seek(7 - static_cast(HeaderSize), FileAndMemReader::CUR); + fr.seek(7 - static_cast(headerSize), FileAndMemReader::CUR); is_GMF = true; } #ifndef BWMIDI_DISABLE_MUS_SUPPORT @@ -1494,7 +1499,14 @@ riffskip: m_errorString = "Out of memory!"; return false; } - fr.read(mus, 1, mus_len); + 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(); @@ -1536,7 +1548,14 @@ riffskip: m_errorString = "Out of memory!"; return false; } - fr.read(mus, 1, mus_len); + 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(); @@ -1567,23 +1586,43 @@ riffskip: 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); + 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); + uint64_t ticks = readLEint(headerBuf + 12, 2); // Read title, author, remarks start offsets in file - fr.read(headerBuf, 1, 6); + 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 - fr.read(headerBuf, 1, 4); - uint64_t ins_count = ReadLEint(headerBuf + 0, 2); //, basictempo = ReadLEint(HeaderBuf+2, 2); + 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(ins_start), FileAndMemReader::SET); m_cmfInstruments.reserve(static_cast(ins_count)); for(uint64_t i = 0; i < ins_count; ++i) { CmfInstrument inst; - fr.read(inst.data, 1, 16); + 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); } @@ -1597,7 +1636,7 @@ riffskip: if(headerBuf[0] == 0x7D) { fr.seek(0x6D, FileAndMemReader::SET); - fr.read(headerBuf, 6, 1); + fr.read(headerBuf, 1, 6); if(std::memcmp(headerBuf, "rsxx}u", 6) == 0) { is_RSXX = true; @@ -1656,8 +1695,8 @@ riffskip: } /*size_t Fmt = ReadBEint(HeaderBuf + 8, 2);*/ - TrackCount = (size_t)ReadBEint(headerBuf + 10, 2); - DeltaTicks = (size_t)ReadBEint(headerBuf + 12, 2); + TrackCount = (size_t)readBEint(headerBuf + 10, 2); + DeltaTicks = (size_t)readBEint(headerBuf + 12, 2); } } @@ -1674,7 +1713,7 @@ riffskip: for(size_t tk = 0; tk < TrackCount; ++tk) { // Read track header - size_t TrackLength; + size_t trackLength; if(is_IMF) { @@ -1728,32 +1767,30 @@ riffskip: { size_t pos = fr.tell(); fr.seek(0, FileAndMemReader::END); - TrackLength = fr.tell() - pos; + trackLength = fr.tell() - pos; fr.seek(static_cast(pos), FileAndMemReader::SET); } - //else if(is_MUS) // Read TrackLength from file position 4 - //{ - // size_t pos = fr.tell(); - // fr.seek(4, FileAndMemReader::SET); - // TrackLength = static_cast(fr.getc()); - // TrackLength += static_cast(fr.getc() << 8); - // fr.seek(static_cast(pos), FileAndMemReader::SET); - //} else { fsize = fr.read(headerBuf, 1, 8); - if(std::memcmp(headerBuf, "MTrk", 4) != 0) + 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); + trackLength = (size_t)readBEint(headerBuf + 4, 4); } // Read track data - rawTrackData[tk].resize(TrackLength); - fsize = fr.read(&rawTrackData[tk][0], 1, TrackLength); + 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. -- cgit v1.2.3 From 85b4a6621ec960314b568cc767e77a20e178c5de Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Thu, 21 Jun 2018 08:08:52 +0300 Subject: FileReader: directly assign SET/CUR/END enum into SEEK_* macros to match --- src/file_reader.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/file_reader.hpp b/src/file_reader.hpp index f601cee..e12e591 100644 --- a/src/file_reader.hpp +++ b/src/file_reader.hpp @@ -57,11 +57,11 @@ public: enum relTo { //! At begin position - SET = 0, + SET = SEEK_SET, //! At current position - CUR = 1, + CUR = SEEK_CUR, //! At end position - END = 2 + END = SEEK_END }; /** -- cgit v1.2.3 From 8917fd60694ef2a0757d6e250faea37482b34956 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Thu, 21 Jun 2018 08:58:49 +0300 Subject: Small clean-up in a custom bank loading function --- src/adlmidi_load.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/adlmidi_load.cpp b/src/adlmidi_load.cpp index d7b1e52..41f1837 100644 --- a/src/adlmidi_load.cpp +++ b/src/adlmidi_load.cpp @@ -173,9 +173,8 @@ bool MIDIplay::LoadBank(FileAndMemReader &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) -- cgit v1.2.3 From ebee7962d7a2691e38d585dac9b9c0e3d286364c Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Thu, 21 Jun 2018 09:43:34 +0300 Subject: SysEx: Use `size_t` for size values instead of `unsigned int` --- src/adlmidi.cpp | 2 +- src/adlmidi_midiplay.cpp | 10 +++++----- src/adlmidi_private.hpp | 8 ++++---- src/adlmidi_sequencer.cpp | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/adlmidi.cpp b/src/adlmidi.cpp index e77278c..c3c07a7 100644 --- a/src/adlmidi.cpp +++ b/src/adlmidi.cpp @@ -1342,7 +1342,7 @@ ADLMIDI_EXPORT void adl_rt_bankChange(struct ADL_MIDIPlayer *device, ADL_UInt8 c player->realTime_BankChange(channel, (uint16_t)bank); } -ADLMIDI_EXPORT int adl_rt_systemExclusive(struct ADL_MIDIPlayer *device, const ADL_UInt8 *msg, unsigned size) +ADLMIDI_EXPORT int adl_rt_systemExclusive(struct ADL_MIDIPlayer *device, const ADL_UInt8 *msg, size_t size) { if(!device) return -1; diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index 8b4f27f..ba5b1ef 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -712,7 +712,7 @@ void MIDIplay::setDeviceId(uint8_t id) m_sysExDeviceId = id; } -bool MIDIplay::realTime_SysEx(const uint8_t *msg, unsigned size) +bool MIDIplay::realTime_SysEx(const uint8_t *msg, size_t size) { if(size < 4 || msg[0] != 0xF0 || msg[size - 1] != 0xF7) return false; @@ -739,7 +739,7 @@ bool MIDIplay::realTime_SysEx(const uint8_t *msg, unsigned size) return false; } -bool MIDIplay::doUniversalSysEx(unsigned dev, bool realtime, const uint8_t *data, unsigned size) +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) @@ -773,7 +773,7 @@ bool MIDIplay::doUniversalSysEx(unsigned dev, bool realtime, const uint8_t *data return false; } -bool MIDIplay::doRolandSysEx(unsigned dev, const uint8_t *data, unsigned size) +bool MIDIplay::doRolandSysEx(unsigned dev, const uint8_t *data, size_t size) { bool devicematch = dev == 0x7F || (dev & 0x0F) == m_sysExDeviceId; if(size < 6 || !devicematch) @@ -788,7 +788,7 @@ bool MIDIplay::doRolandSysEx(unsigned dev, const uint8_t *data, unsigned size) #if !defined(ADLMIDI_SKIP_ROLAND_CHECKSUM) { unsigned checkvalue = 0; - for(unsigned i = 0; i < size; ++i) + for(size_t i = 0; i < size; ++i) checkvalue += data[i] & 0x7F; checkvalue = (128 - (checkvalue & 127)) & 127; if(checkvalue != checksum) @@ -831,7 +831,7 @@ bool MIDIplay::doRolandSysEx(unsigned dev, const uint8_t *data, unsigned size) return false; } -bool MIDIplay::doYamahaSysEx(unsigned dev, const uint8_t *data, unsigned size) +bool MIDIplay::doYamahaSysEx(unsigned dev, const uint8_t *data, size_t size) { bool devicematch = dev == 0x7F || (dev & 0x0F) == m_sysExDeviceId; if(size < 1 || !devicematch) diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp index 67f1ccb..280232f 100644 --- a/src/adlmidi_private.hpp +++ b/src/adlmidi_private.hpp @@ -914,7 +914,7 @@ public: * @param size Length of SysEx message * @return true if message was passed successfully. False on any errors */ - bool realTime_SysEx(const uint8_t *msg, unsigned size); + bool realTime_SysEx(const uint8_t *msg, size_t size); /** * @brief Turn off all notes and mute the sound of releasing notes @@ -967,9 +967,9 @@ private: YamahaModel_XG = 0x4C }; - bool doUniversalSysEx(unsigned dev, bool realtime, const uint8_t *data, unsigned size); - bool doRolandSysEx(unsigned dev, const uint8_t *data, unsigned size); - bool doYamahaSysEx(unsigned dev, const uint8_t *data, unsigned size); + 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 diff --git a/src/adlmidi_sequencer.cpp b/src/adlmidi_sequencer.cpp index ac983d0..b8bf3dd 100644 --- a/src/adlmidi_sequencer.cpp +++ b/src/adlmidi_sequencer.cpp @@ -79,7 +79,7 @@ static void rtPitchBend(void *userdata, uint8_t channel, uint8_t msb, uint8_t ls static void rtSysEx(void *userdata, const uint8_t *msg, size_t size) { MIDIplay *context = reinterpret_cast(userdata); - context->realTime_SysEx(msg, (unsigned)size); + context->realTime_SysEx(msg, size); } -- cgit v1.2.3 From b2259a0ad2da07729eb285eda4a833e5b0f1f7eb Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Thu, 21 Jun 2018 14:11:48 +0300 Subject: Attempt to fix MSVC 2015 build --- src/adlmidi_private.hpp | 1 - src/midi_sequencer_impl.hpp | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp index 280232f..54d69b8 100644 --- a/src/adlmidi_private.hpp +++ b/src/adlmidi_private.hpp @@ -108,7 +108,6 @@ typedef int32_t ssize_t; #include #include -#include /* * Workaround for some compilers are has no those macros in their headers! diff --git a/src/midi_sequencer_impl.hpp b/src/midi_sequencer_impl.hpp index 7ecefa2..92be133 100644 --- a/src/midi_sequencer_impl.hpp +++ b/src/midi_sequencer_impl.hpp @@ -26,6 +26,8 @@ #include #include #include +#include // std::back_inserter +#include // std::copy #include #include -- cgit v1.2.3 From 8075701abfe8e5b1fc6705eb2b1d35b407784c82 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Fri, 22 Jun 2018 03:35:06 +0300 Subject: Portamento must use previously played note in a channel like S-YXG50 does --- src/adlmidi_midiplay.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index ba5b1ef..089fd43 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -478,7 +478,8 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) midiChan.portamentoEnable && currentPortamentoRate != HUGE_VAL && !isPercussion && !isXgPercussion; // Record the last note on MIDI channel as source of portamento - midiChan.portamentoSource = portamentoEnable ? (int8_t)note : (int8_t)-1; + midiChan.portamentoSource = static_cast(note); + // midiChan.portamentoSource = portamentoEnable ? (int8_t)note : (int8_t)-1; // Enable gliding on portamento note if (portamentoEnable && currentPortamentoSource >= 0) -- cgit v1.2.3 From be2e41fe5d800520ee50ca93729c8633407fb1fc Mon Sep 17 00:00:00 2001 From: JP Cimalando Date: Fri, 22 Jun 2018 17:30:34 +0200 Subject: handle sysex resets --- src/adlmidi_midiplay.cpp | 10 +++++----- src/adlmidi_private.hpp | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index 089fd43..bd979bd 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -606,7 +606,7 @@ void MIDIplay::realTime_Controller(uint8_t channel, uint8_t type, uint8_t value) break; case 120: // All sounds off - NoteUpdate_All(channel, Upt_OffMute); + NoteUpdate_All(channel, Upd_OffMute); break; case 123: // All notes off @@ -755,10 +755,10 @@ bool MIDIplay::doUniversalSysEx(unsigned dev, bool realtime, const uint8_t *data switch(((unsigned)realtime << 16) | address) { case (0 << 16) | 0x0901: // GM System On - /*TODO*/ + realTime_ResetState(); return true; case (0 << 16) | 0x0902: // GM System Off - /*TODO*/ + realTime_ResetState(); return true; case (1 << 16) | 0x0401: // MIDI Master Volume if(size != 2) @@ -814,8 +814,8 @@ bool MIDIplay::doRolandSysEx(unsigned dev, const uint8_t *data, size_t size) if(size != 1 || (dev & 0xF0) != 0x10) break; unsigned mode = data[0] & 0x7F; - /*TODO*/ (void)mode; + realTime_ResetState(); return true; } case (RolandModel_GS << 24) | 0x40007F: // Mode Set @@ -823,8 +823,8 @@ bool MIDIplay::doRolandSysEx(unsigned dev, const uint8_t *data, size_t size) if(size != 1 || (dev & 0xF0) != 0x10) break; unsigned value = data[0] & 0x7F; - /*TODO*/ (void)value; + realTime_ResetState(); return true; } } diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp index 54d69b8..b7bdc17 100644 --- a/src/adlmidi_private.hpp +++ b/src/adlmidi_private.hpp @@ -980,7 +980,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, -- cgit v1.2.3 From 16831e97a68049d27329db4630dcfbfa58b1d8fd Mon Sep 17 00:00:00 2001 From: JP Cimalando Date: Fri, 22 Jun 2018 17:58:31 +0200 Subject: handle MIDI master volume --- src/adlmidi_midiplay.cpp | 29 +++++++++++++++++------------ src/adlmidi_private.hpp | 1 + 2 files changed, 18 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index bd979bd..3aa307d 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); @@ -117,6 +119,7 @@ void MIDIplay::AdlChannel::AddAge(int64_t ms) MIDIplay::MIDIplay(unsigned long sampleRate): cmf_percussion_mode(false), + m_masterVolume(MasterVolumeDefault), m_sysExDeviceId(0), m_arpeggioCounter(0) #if defined(ADLMIDI_AUDIO_TICK_HANDLER) @@ -220,6 +223,7 @@ void MIDIplay::realTime_ResetState() 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) @@ -766,8 +770,9 @@ bool MIDIplay::doUniversalSysEx(unsigned dev, bool realtime, const uint8_t *data unsigned volume = (((unsigned)data[0] & 0x7F)) | (((unsigned)data[1] & 0x7F) << 7); - /*TODO*/ - (void)volume; + m_masterVolume = volume >> 7; + for(size_t ch = 0; ch < Ch.size(); ch++) + NoteUpdate_All(uint16_t(ch), Upd_Volume); return true; } @@ -1029,7 +1034,7 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, 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. @@ -1040,10 +1045,10 @@ 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(std::log(static_cast(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); + // The formula below: SOLVE(V=127^4 * 2^( (A-63.49999) / 8), A) + volume = volume > (8725 * 127) ? static_cast(std::log(static_cast(volume) * (1.0 / 127.0)) * 11.541561 + (0.5 - 104.22845)) : 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); opl.Touch_Real(c, volume, brightness); //opl.Touch(c, volume); @@ -1053,14 +1058,14 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, case OPL3::VOLUME_NATIVE: { volume = vol * Ch[MidCh].volume * Ch[MidCh].expression; - volume = volume * 127 / (127 * 127 * 127) / 2; + volume = volume * m_masterVolume / (127 * 127 * 127) / 2; opl.Touch_Real(c, volume, brightness); } 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); @@ -1069,7 +1074,7 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, 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); @@ -1078,8 +1083,8 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, 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); } diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp index b7bdc17..71f0068 100644 --- a/src/adlmidi_private.hpp +++ b/src/adlmidi_private.hpp @@ -723,6 +723,7 @@ public: std::vector Ch; bool cmf_percussion_mode; + uint8_t m_masterVolume; uint8_t m_sysExDeviceId; MIDIEventHooks hooks; -- cgit v1.2.3 From faaab13482d1e8334712232b7f64a59ec8ae6f07 Mon Sep 17 00:00:00 2001 From: JP Cimalando Date: Fri, 22 Jun 2018 18:41:31 +0200 Subject: Yamaha XG reset --- src/adlmidi_midiplay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index 3aa307d..a6fc2a5 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -867,8 +867,8 @@ bool MIDIplay::doYamahaSysEx(unsigned dev, const uint8_t *data, size_t size) if(size != 1) break; unsigned value = data[0] & 0x7F; - /*TODO*/ (void)value; + realTime_ResetState(); return true; } -- cgit v1.2.3 From 2e88f9b9303ce1b9ef5512d6b84b3bb190dbfe75 Mon Sep 17 00:00:00 2001 From: JP Cimalando Date: Fri, 22 Jun 2018 19:08:17 +0200 Subject: simplify the volume formula --- src/adlmidi_midiplay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index a6fc2a5..5637da4 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -1046,7 +1046,7 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, //volume = (int)(volume * std::sqrt( (double) ch[c].users.size() )); // The formula below: SOLVE(V=127^4 * 2^( (A-63.49999) / 8), A) - volume = volume > (8725 * 127) ? static_cast(std::log(static_cast(volume) * (1.0 / 127.0)) * 11.541561 + (0.5 - 104.22845)) : 0; + volume = volume > (8725 * 127) ? static_cast(std::log(static_cast(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); -- cgit v1.2.3 From 51081828bbc756f81ac4b5c58cd3605a31047f61 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Fri, 22 Jun 2018 21:22:44 +0300 Subject: Move `opl.Touch_Real()` call out of volume model switch --- src/adlmidi_midiplay.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index 5637da4..625f8f7 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -1049,9 +1049,6 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, volume = volume > (8725 * 127) ? static_cast(std::log(static_cast(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); - - opl.Touch_Real(c, volume, brightness); - //opl.Touch(c, volume); } break; @@ -1059,7 +1056,6 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, { volume = vol * Ch[MidCh].volume * Ch[MidCh].expression; volume = volume * m_masterVolume / (127 * 127 * 127) / 2; - opl.Touch_Real(c, volume, brightness); } break; @@ -1068,7 +1064,6 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, 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; @@ -1077,7 +1072,6 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, 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; @@ -1086,11 +1080,12 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, //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; -- cgit v1.2.3 From db96b37884c4e32f2d6b7cd1745f829e1c2cb564 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Fri, 22 Jun 2018 21:23:01 +0300 Subject: Added some debug message hooks into SysEx processors --- src/adlmidi_midiplay.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index 625f8f7..336c40c 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -759,9 +759,13 @@ bool MIDIplay::doUniversalSysEx(unsigned dev, bool realtime, const uint8_t *data switch(((unsigned)realtime << 16) | address) { case (0 << 16) | 0x0901: // GM System On + if(hooks.onDebugMessage) + hooks.onDebugMessage(hooks.onDebugMessage_userData, "SysEx: GM System On"); realTime_ResetState(); return true; case (0 << 16) | 0x0902: // GM System Off + if(hooks.onDebugMessage) + hooks.onDebugMessage(hooks.onDebugMessage_userData, "SysEx: GM System Off"); realTime_ResetState(); return true; case (1 << 16) | 0x0401: // MIDI Master Volume @@ -819,7 +823,9 @@ bool MIDIplay::doRolandSysEx(unsigned dev, const uint8_t *data, size_t size) if(size != 1 || (dev & 0xF0) != 0x10) break; unsigned mode = data[0] & 0x7F; - (void)mode; + ADL_UNUSED(mode); + if(hooks.onDebugMessage) + hooks.onDebugMessage(hooks.onDebugMessage_userData, "SysEx: Caugh Roland System Mode Set: %02X", mode); realTime_ResetState(); return true; } @@ -828,7 +834,9 @@ bool MIDIplay::doRolandSysEx(unsigned dev, const uint8_t *data, size_t size) if(size != 1 || (dev & 0xF0) != 0x10) break; unsigned value = data[0] & 0x7F; - (void)value; + ADL_UNUSED(value); + if(hooks.onDebugMessage) + hooks.onDebugMessage(hooks.onDebugMessage_userData, "SysEx: Caugh Roland Mode Set: %02X", value); realTime_ResetState(); return true; } @@ -867,7 +875,9 @@ bool MIDIplay::doYamahaSysEx(unsigned dev, const uint8_t *data, size_t size) if(size != 1) break; unsigned value = data[0] & 0x7F; - (void)value; + ADL_UNUSED(value); + if(hooks.onDebugMessage) + hooks.onDebugMessage(hooks.onDebugMessage_userData, "SysEx: Caugh Yamaha XG System On: %02X", value); realTime_ResetState(); return true; } -- cgit v1.2.3 From a8fa66e8444da96b39699489bf75587a0ae94721 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Fri, 22 Jun 2018 21:30:00 +0300 Subject: Use Generic volume model by default when VM value has received some junk --- src/adlmidi_midiplay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index 336c40c..8a4e8cd 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -1041,7 +1041,7 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, switch(opl.m_volumeScale) { - + default: case OPL3::VOLUME_Generic: { volume = vol * m_masterVolume * Ch[MidCh].volume * Ch[MidCh].expression; -- cgit v1.2.3 From be1cd07a227783553406e144e9235901e792c5b1 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Fri, 22 Jun 2018 22:31:18 +0300 Subject: Sequencer: give SysEx events have highest priority while re-ordering --- src/midi_sequencer_impl.hpp | 41 ++++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/midi_sequencer_impl.hpp b/src/midi_sequencer_impl.hpp index 92be133..0e71c2a 100644 --- a/src/midi_sequencer_impl.hpp +++ b/src/midi_sequencer_impl.hpp @@ -160,31 +160,48 @@ void BW_MidiSequencer::MidiTrackRow::clear() void BW_MidiSequencer::MidiTrackRow::sortEvents(bool *noteStates) { typedef std::vector EvtArr; + EvtArr sysEx; 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) + { + 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]); + } } /* @@ -246,10 +263,16 @@ void BW_MidiSequencer::MidiTrackRow::sortEvents(bool *noteStates) /***********************************************************************************/ 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()); + 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() : -- cgit v1.2.3 From 2f30eb0596d3fc9788ffe99b72a09ceca596b792 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Fri, 22 Jun 2018 22:33:36 +0300 Subject: Fix the typo in "Caught" word in debug messages --- src/adlmidi_midiplay.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index 8a4e8cd..eca5282 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -335,7 +335,7 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) if(hooks.onDebugMessage) { if(caugh_missing_instruments.insert(static_cast(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; @@ -825,7 +825,7 @@ bool MIDIplay::doRolandSysEx(unsigned dev, const uint8_t *data, size_t size) unsigned mode = data[0] & 0x7F; ADL_UNUSED(mode); if(hooks.onDebugMessage) - hooks.onDebugMessage(hooks.onDebugMessage_userData, "SysEx: Caugh Roland System Mode Set: %02X", mode); + hooks.onDebugMessage(hooks.onDebugMessage_userData, "SysEx: Caught Roland System Mode Set: %02X", mode); realTime_ResetState(); return true; } @@ -836,7 +836,7 @@ bool MIDIplay::doRolandSysEx(unsigned dev, const uint8_t *data, size_t size) unsigned value = data[0] & 0x7F; ADL_UNUSED(value); if(hooks.onDebugMessage) - hooks.onDebugMessage(hooks.onDebugMessage_userData, "SysEx: Caugh Roland Mode Set: %02X", value); + hooks.onDebugMessage(hooks.onDebugMessage_userData, "SysEx: Caught Roland Mode Set: %02X", value); realTime_ResetState(); return true; } @@ -877,7 +877,7 @@ bool MIDIplay::doYamahaSysEx(unsigned dev, const uint8_t *data, size_t size) unsigned value = data[0] & 0x7F; ADL_UNUSED(value); if(hooks.onDebugMessage) - hooks.onDebugMessage(hooks.onDebugMessage_userData, "SysEx: Caugh Yamaha XG System On: %02X", value); + hooks.onDebugMessage(hooks.onDebugMessage_userData, "SysEx: Caught Yamaha XG System On: %02X", value); realTime_ResetState(); return true; } -- cgit v1.2.3 From 159bb5b202cd088db920ccc073d952122dea85ba Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Fri, 22 Jun 2018 23:25:13 +0300 Subject: Added support for synthesizer mode - in GS mode, RPN XG-related vibrato depth events will be ignored (GS does using NRPN values are stored separately and are NOT handled) - in GS mode ignore LSB value of the bank number --- src/adlmidi_midiplay.cpp | 40 +++++++++++++++++++++++++++++----------- src/adlmidi_private.hpp | 9 +++++++++ 2 files changed, 38 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index eca5282..dd5c9bf 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -121,6 +121,7 @@ MIDIplay::MIDIplay(unsigned long sampleRate): cmf_percussion_mode(false), m_masterVolume(MasterVolumeDefault), m_sysExDeviceId(0), + m_synthMode(Mode_XG), m_arpeggioCounter(0) #if defined(ADLMIDI_AUDIO_TICK_HANDLER) , m_audioTickCounter(0) @@ -262,10 +263,13 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) uint16_t bank = 0; if(midiChan.bank_msb || midiChan.bank_lsb) { - bank = (uint16_t(midiChan.bank_msb) * 256) + uint16_t(midiChan.bank_lsb); + 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); //0x7E00 - XG SFX1/SFX2 channel (16128 signed decimal) //0x7F00 - XG Percussion channel (16256 signed decimal) - if(bank == 0x7E00 || bank == 0x7F00) + if(((m_synthMode & Mode_XG) != 0) && (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) @@ -761,11 +765,13 @@ bool MIDIplay::doUniversalSysEx(unsigned dev, bool realtime, const uint8_t *data 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 @@ -826,6 +832,7 @@ bool MIDIplay::doRolandSysEx(unsigned dev, const uint8_t *data, size_t size) ADL_UNUSED(mode); if(hooks.onDebugMessage) hooks.onDebugMessage(hooks.onDebugMessage_userData, "SysEx: Caught Roland System Mode Set: %02X", mode); + m_synthMode = Mode_GS; realTime_ResetState(); return true; } @@ -837,6 +844,7 @@ bool MIDIplay::doRolandSysEx(unsigned dev, const uint8_t *data, size_t size) ADL_UNUSED(value); if(hooks.onDebugMessage) hooks.onDebugMessage(hooks.onDebugMessage_userData, "SysEx: Caught Roland Mode Set: %02X", value); + m_synthMode = Mode_GS; realTime_ResetState(); return true; } @@ -878,6 +886,7 @@ bool MIDIplay::doYamahaSysEx(unsigned dev, const uint8_t *data, size_t size) ADL_UNUSED(value); if(hooks.onDebugMessage) hooks.onDebugMessage(hooks.onDebugMessage_userData, "SysEx: Caught Yamaha XG System On: %02X", value); + m_synthMode = Mode_XG; realTime_ResetState(); return true; } @@ -1396,17 +1405,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);*/ diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp index 71f0068..ec194c6 100644 --- a/src/adlmidi_private.hpp +++ b/src/adlmidi_private.hpp @@ -726,6 +726,15 @@ public: 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; private: -- cgit v1.2.3 From de7550a4cc643b37449eb791f09b625bf1af17fb Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Fri, 22 Jun 2018 23:27:07 +0300 Subject: Added some TODOs [ci skip] --- src/adlmidi_midiplay.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index dd5c9bf..3faa299 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -829,7 +829,7 @@ bool MIDIplay::doRolandSysEx(unsigned dev, const uint8_t *data, size_t size) if(size != 1 || (dev & 0xF0) != 0x10) break; unsigned mode = data[0] & 0x7F; - ADL_UNUSED(mode); + 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; @@ -841,7 +841,7 @@ bool MIDIplay::doRolandSysEx(unsigned dev, const uint8_t *data, size_t size) if(size != 1 || (dev & 0xF0) != 0x10) break; unsigned value = data[0] & 0x7F; - ADL_UNUSED(value); + 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; @@ -883,7 +883,7 @@ bool MIDIplay::doYamahaSysEx(unsigned dev, const uint8_t *data, size_t size) if(size != 1) break; unsigned value = data[0] & 0x7F; - ADL_UNUSED(value); + 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; -- cgit v1.2.3 From 12f0c3565a0d7b59629dbb2800df5e887ff6fde3 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Sat, 23 Jun 2018 01:45:02 +0300 Subject: Added little documentation for the sequencer event hooks --- src/midi_sequencer.h | 55 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 49 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/midi_sequencer.h b/src/midi_sequencer.h index 2a2b543..afbffed 100644 --- a/src/midi_sequencer.h +++ b/src/midi_sequencer.h @@ -33,55 +33,98 @@ extern "C" { #include #include +/** + \brief Real-Time MIDI interface between Sequencer and the Synthesizer + */ typedef struct { - //! Raw MIDI event hook + /*! 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 + /*! 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! */ + + /*************************************************** + * 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; - /* NonStandard events. There are optional */ - typedef void (*RtRawOPL)(void *userdata, uint8_t reg, uint8_t value); - RtRawOPL rt_rawOPL; + /******************* + * 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 -- cgit v1.2.3 From be42c3c566c6f8b72f3ae9b526571a2c58326379 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Sat, 23 Jun 2018 05:14:32 +0300 Subject: GS way of custom drum channels now is working! --- src/adlmidi_midiplay.cpp | 99 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 67 insertions(+), 32 deletions(-) (limited to 'src') diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index 3faa299..8664955 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -221,6 +221,8 @@ 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); } @@ -257,8 +259,7 @@ 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) @@ -267,25 +268,30 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) bank = (uint16_t(midiChan.bank_msb) * 256); else 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(((m_synthMode & Mode_XG) != 0) && (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(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; @@ -302,9 +308,9 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) ains = &bnk->ins[midiins]; else if(hooks.onDebugMessage) { - std::set &missing = (isPercussion || isXgPercussion) ? + std::set &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); @@ -321,18 +327,8 @@ 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) { @@ -483,8 +479,7 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) int8_t currentPortamentoSource = midiChan.portamentoSource; double currentPortamentoRate = midiChan.portamentoRate; bool portamentoEnable = - midiChan.portamentoEnable && currentPortamentoRate != HUGE_VAL && - !isPercussion && !isXgPercussion; + midiChan.portamentoEnable && currentPortamentoRate != HUGE_VAL && !isPercussion; // Record the last note on MIDI channel as source of portamento midiChan.portamentoSource = static_cast(note); // midiChan.portamentoSource = portamentoEnable ? (int8_t)note : (int8_t)-1; @@ -555,12 +550,14 @@ 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 @@ -808,7 +805,11 @@ bool MIDIplay::doRolandSysEx(unsigned dev, const uint8_t *data, size_t size) 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 @@ -816,12 +817,28 @@ bool MIDIplay::doRolandSysEx(unsigned dev, const uint8_t *data, size_t size) (((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 15} { } F7 + switch((model << 24) | address) { case (RolandModel_GS << 24) | 0x00007F: // System Mode Set @@ -848,6 +865,24 @@ bool MIDIplay::doRolandSysEx(unsigned dev, const uint8_t *data, size_t size) 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; -- cgit v1.2.3 From f6c6b70c488ba392236e1a4f5d2a32ceda00bf24 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Sat, 23 Jun 2018 06:40:27 +0300 Subject: Added CC66 Sostenuto support! Sostenuto is the pedal hold that does hold of only currently playing notes and doesn't holds notes are will begin after turning of sostenuto on, unlike to the Pedal (CC64 Sustain) event. --- src/adlmidi_midiplay.cpp | 71 ++++++++++++++++++++++++++++++++++-------------- src/adlmidi_private.hpp | 20 ++++++++++---- 2 files changed, 65 insertions(+), 26 deletions(-) (limited to 'src') diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index 8664955..d4bb4ff 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -586,8 +586,16 @@ 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 11: // Change expression (another volume factor) @@ -607,7 +615,7 @@ void MIDIplay::realTime_Controller(uint8_t channel, uint8_t type, uint8_t value) Ch[channel].resetAllControllers(); 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 @@ -936,7 +944,7 @@ bool MIDIplay::doYamahaSysEx(unsigned dev, const uint8_t *data, size_t size) 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) @@ -1006,7 +1014,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(adlNoteOnMaxTime)); d->kon_time_until_neglible = ains.ms_sound_kon; @@ -1025,17 +1033,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, 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 @@ -1055,7 +1063,7 @@ 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, noteTone, midiins, -1, 0.0); } @@ -1159,7 +1167,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; @@ -1212,7 +1220,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); @@ -1258,7 +1266,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; @@ -1282,7 +1290,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 @@ -1307,7 +1315,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(c)); + KillSustainingNotes(-1, static_cast(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. @@ -1390,7 +1398,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; @@ -1400,9 +1408,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;) { @@ -1410,12 +1419,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 } } @@ -1425,6 +1436,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; @@ -1572,7 +1601,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) { diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp index ec194c6..1d68bae 100644 --- a/src/adlmidi_private.hpp +++ b/src/adlmidi_private.hpp @@ -349,8 +349,9 @@ public: 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 portamentoEnable; int8_t portamentoSource; // note number or -1 double portamentoRate; @@ -564,7 +565,7 @@ public: updateBendSensitivity(); volume = 100; expression = 127; - sustain = 0; + sustain = false; vibrato = 0; aftertouch = 0; std::memset(noteAftertouch, 0, 128); @@ -612,8 +613,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; @@ -1011,7 +1018,10 @@ 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 NoteUpdate_All(uint16_t MidCh, unsigned props_mask); -- cgit v1.2.3 From a04d79cb15e38a66f72014f47aa9f3301c3aeab0 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Sat, 23 Jun 2018 07:16:01 +0300 Subject: Fix Watcom compilation of file_reader.hpp --- src/file_reader.hpp | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src') diff --git a/src/file_reader.hpp b/src/file_reader.hpp index e12e591..2c57386 100644 --- a/src/file_reader.hpp +++ b/src/file_reader.hpp @@ -28,6 +28,8 @@ #include // std::string #include // std::fopen, std::fread, std::fseek, std::ftell, std::fclose, std::feof +#include // uint*_t +#include // size_t and friends #ifdef _WIN32 #include // std::strlen #include // MultiByteToWideChar -- cgit v1.2.3 From b8e088ffed1b6ceb73dea84f5724879c309bc554 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Sat, 23 Jun 2018 07:34:39 +0300 Subject: Implement a workaround for crashing Watcom compiler --- src/adlmidi.cpp | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/adlmidi.cpp b/src/adlmidi.cpp index c3c07a7..673a86e 100644 --- a/src/adlmidi.cpp +++ b/src/adlmidi.cpp @@ -861,6 +861,8 @@ ADLMIDI_EXPORT void adl_setDebugMessageHook(struct ADL_MIDIPlayer *device, ADL_D } #ifndef ADLMIDI_HW_OPL + +# ifndef __WATCOMC__ template static void CopySamplesRaw(ADL_UInt8 *dstLeft, ADL_UInt8 *dstRight, const int32_t *src, size_t frameCount, unsigned sampleOffset) @@ -992,7 +994,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(out_pos); + size_t inSamples = static_cast(in_size * 2); + size_t maxSamples = static_cast(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) { -- cgit v1.2.3 From 05a7a496341fa747a3af4e2cc040779772bc1594 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Sat, 23 Jun 2018 08:06:13 +0300 Subject: Another Watcom build fix, however, it doesn't fixes stack-related troubles --- src/file_reader.hpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/file_reader.hpp b/src/file_reader.hpp index 2c57386..7d13262 100644 --- a/src/file_reader.hpp +++ b/src/file_reader.hpp @@ -31,6 +31,7 @@ #include // uint*_t #include // size_t and friends #ifdef _WIN32 +#define NOMINMAX 1 #include // std::strlen #include // MultiByteToWideChar #endif -- cgit v1.2.3 From fbd0ecea7c7315b37338a4c7621f50df15b460b2 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Sat, 23 Jun 2018 08:09:30 +0300 Subject: MidiSequencer: Some another refactoring of function names --- src/midi_sequencer_impl.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/midi_sequencer_impl.hpp b/src/midi_sequencer_impl.hpp index 0e71c2a..c699db5 100644 --- a/src/midi_sequencer_impl.hpp +++ b/src/midi_sequencer_impl.hpp @@ -94,7 +94,7 @@ static inline uint64_t readLEint(const void *buffer, size_t nbytes) * @param [_inout] ptr Pointer to memory block that contains begin of variable-length value * @return Unsigned integer that conains parsed variable-length value */ -static inline uint64_t ReadVarLen(uint8_t **ptr) +static inline uint64_t readVarLen(uint8_t **ptr) { uint64_t result = 0; for(;;) @@ -114,7 +114,7 @@ static inline uint64_t ReadVarLen(uint8_t **ptr) * @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) +static inline uint64_t readVarLenEx(const uint8_t **ptr, const uint8_t *end, bool &ok) { uint64_t result = 0; ok = false; @@ -431,7 +431,7 @@ bool BW_MidiSequencer::buildTrackData(const std::vector > & if(m_format == Format_RSXX) ok = true; else - evtPos.delay = ReadVarLenEx(&trackPtr, end, ok); + 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); @@ -515,7 +515,7 @@ bool BW_MidiSequencer::buildTrackData(const std::vector > & if(event.subtype != MidiEvent::ST_ENDTRACK)//Don't try to read delta after EndOfTrack event! { - evtPos.delay = ReadVarLenEx(&trackPtr, end, ok); + evtPos.delay = readVarLenEx(&trackPtr, end, ok); if(!ok) { /* End of track has been reached! However, there is no EOT event presented */ @@ -937,7 +937,7 @@ BW_MidiSequencer::MidiEvent BW_MidiSequencer::parseEvent(const uint8_t **pptr, c if(byte == MidiEvent::T_SYSEX || byte == MidiEvent::T_SYSEX2)// Ignore SysEx { - uint64_t length = ReadVarLenEx(pptr, end, ok); + 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"; @@ -956,7 +956,7 @@ BW_MidiSequencer::MidiEvent BW_MidiSequencer::parseEvent(const uint8_t **pptr, c { // Special event FF uint8_t evtype = *(ptr++); - uint64_t length = ReadVarLenEx(pptr, end, ok); + 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"; -- cgit v1.2.3 From 128c07275d7e48675113f67e2cb945b53109cb7b Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Sat, 23 Jun 2018 08:28:38 +0300 Subject: Small clean-up of the MIDI sequencer's code --- src/midi_sequencer_impl.hpp | 48 +++++++++++++++------------------------------ 1 file changed, 16 insertions(+), 32 deletions(-) (limited to 'src') diff --git a/src/midi_sequencer_impl.hpp b/src/midi_sequencer_impl.hpp index c699db5..501de8f 100644 --- a/src/midi_sequencer_impl.hpp +++ b/src/midi_sequencer_impl.hpp @@ -64,9 +64,9 @@ typedef int32_t ssize_t; static inline uint64_t readBEint(const void *buffer, size_t nbytes) { uint64_t result = 0; - const unsigned char *data = reinterpret_cast(buffer); + const uint8_t *data = reinterpret_cast(buffer); - for(unsigned n = 0; n < nbytes; ++n) + for(size_t n = 0; n < nbytes; ++n) result = (result << 8) + data[n]; return result; @@ -81,32 +81,14 @@ static inline uint64_t readBEint(const void *buffer, size_t nbytes) static inline uint64_t readLEint(const void *buffer, size_t nbytes) { uint64_t result = 0; - const unsigned char *data = reinterpret_cast(buffer); + const uint8_t *data = reinterpret_cast(buffer); - for(unsigned n = 0; n < nbytes; ++n) + for(size_t n = 0; n < nbytes; ++n) result = result + static_cast(data[n] << (n * 8)); return result; } -/** - * @brief Standard MIDI Variable-Length numeric value parser without of validation - * @param [_inout] ptr Pointer to memory block that contains begin of variable-length value - * @return Unsigned integer that conains parsed variable-length value - */ -static inline uint64_t readVarLen(uint8_t **ptr) -{ - uint64_t result = 0; - for(;;) - { - uint8_t byte = *((*ptr)++); - result = (result << 7) + (byte & 0x7F); - if(!(byte & 0x80)) - break; - } - return result; -} - /** * @brief Secure Standard MIDI Variable-Length numeric value parser with anti-out-of-range protection * @param [_inout] ptr Pointer to memory block that contains begin of variable-length value, will be iterated forward @@ -1136,7 +1118,7 @@ BW_MidiSequencer::MidiEvent BW_MidiSequencer::parseEvent(const uint8_t **pptr, c return evt; } -void BW_MidiSequencer::handleEvent(size_t tk, const BW_MidiSequencer::MidiEvent &evt, int32_t &status) +void BW_MidiSequencer::handleEvent(size_t track, const BW_MidiSequencer::MidiEvent &evt, int32_t &status) { if(m_interface->onEvent) { @@ -1147,7 +1129,7 @@ void BW_MidiSequencer::handleEvent(size_t tk, const BW_MidiSequencer::MidiEvent 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 ); + //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); @@ -1186,8 +1168,10 @@ void BW_MidiSequencer::handleEvent(size_t tk, const BW_MidiSequencer::MidiEvent 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, tk, data.c_str(), data.size()); + m_interface->rt_deviceSwitch(m_interface->rtUserData, track, data.c_str(), data.size()); return; } @@ -1197,13 +1181,13 @@ void BW_MidiSequencer::handleEvent(size_t tk, const BW_MidiSequencer::MidiEvent //Turn on Loop handling when loop is enabled if(m_loopEnabled && !m_invalidLoop) { - if(evtype == MidiEvent::ST_LOOPSTART) // Special non-spec ADLMIDI special for IMF playback: Direct poke to AdLib + if(evtype == MidiEvent::ST_LOOPSTART) // Special non-spec MIDI loop Start point { m_loopStart = true; return; } - if(evtype == MidiEvent::ST_LOOPEND) // Special non-spec ADLMIDI special for IMF playback: Direct poke to AdLib + if(evtype == MidiEvent::ST_LOOPEND) // Special non-spec MIDI loop End point { m_loopEnd = true; return; @@ -1223,8 +1207,8 @@ void BW_MidiSequencer::handleEvent(size_t tk, const BW_MidiSequencer::MidiEvent // Any normal event (80..EF) // if(evt.type < 0x80) // { - // byte = static_cast(CurrentPosition.track[tk].status | 0x80); - // CurrentPosition.track[tk].ptr--; + // byte = static_cast(CurrentPosition.track[track].status | 0x80); + // CurrentPosition.track[track].ptr--; // } if(evt.type == MidiEvent::T_SYSCOMSNGSEL || @@ -1232,11 +1216,11 @@ void BW_MidiSequencer::handleEvent(size_t tk, const BW_MidiSequencer::MidiEvent return; /*UI.PrintLn("@%X Track %u: %02X %02X", - CurrentPosition.track[tk].ptr-1, (unsigned)tk, byte, - TrackData[tk][CurrentPosition.track[tk].ptr]);*/ + 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, tk); + midCh += (uint8_t)m_interface->rt_currentDevice(m_interface->rtUserData, track); status = evt.type; switch(evt.type) -- cgit v1.2.3 From efbbd382b84b73d706a16fb54c51bf48df7b22cf Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Sat, 23 Jun 2018 09:01:35 +0300 Subject: Added support for CC67-SoftPedal --- src/adlmidi_midiplay.cpp | 7 +++++++ src/adlmidi_private.hpp | 2 ++ 2 files changed, 9 insertions(+) (limited to 'src') diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index d4bb4ff..a044859 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -464,6 +464,9 @@ 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(std::floor(static_cast(velocity) * 0.8f)); + // Allocate active note for MIDI channel std::pair ir = midiChan.activenotes_insert(note); @@ -598,6 +601,10 @@ void MIDIplay::realTime_Controller(uint8_t channel, uint8_t type, uint8_t value) 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) Ch[channel].expression = value; NoteUpdate_All(channel, Upd_Volume); diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp index 1d68bae..840ba21 100644 --- a/src/adlmidi_private.hpp +++ b/src/adlmidi_private.hpp @@ -352,6 +352,7 @@ public: uint8_t panning, vibrato, aftertouch; uint16_t portamento; bool sustain; + bool softPedal; bool portamentoEnable; int8_t portamentoSource; // note number or -1 double portamentoRate; @@ -566,6 +567,7 @@ public: volume = 100; expression = 127; sustain = false; + softPedal = false; vibrato = 0; aftertouch = 0; std::memset(noteAftertouch, 0, 128); -- cgit v1.2.3 From 60fa4f03f30553fc5440c0934cc1b7406094526c Mon Sep 17 00:00:00 2001 From: JP Cimalando Date: Sat, 23 Jun 2018 12:41:12 +0200 Subject: glide: handle for all the midi channels --- src/adlmidi_midiplay.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index a044859..b2497ca 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -1632,7 +1632,9 @@ retry_arpeggio: void MIDIplay::UpdateGlide(double amount) { - for(unsigned channel = 0; channel < 16; ++channel) + unsigned num_channels = Ch.size(); + + for(unsigned channel = 0; channel < num_channels; ++channel) { MIDIchannel &midiChan = Ch[channel]; if(midiChan.gliding_note_count == 0) -- cgit v1.2.3 From e248d9ddc8ba3023e265a18d44fb5dca6193f8ec Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Sat, 23 Jun 2018 23:29:29 +0300 Subject: Little clean-up and beautify --- src/adlmidi.cpp | 243 +++++++++++++++++++++++++++-------------------- src/adlmidi_midiplay.cpp | 18 ++-- src/adlmidi_private.hpp | 1 + 3 files changed, 151 insertions(+), 111 deletions(-) (limited to 'src') diff --git a/src/adlmidi.cpp b/src/adlmidi.cpp index 673a86e..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((device)->adl_midiPlayer) +typedef MIDIplay MidiPlayer; + static ADL_Version adl_version = { ADLMIDI_VERSION_MAJOR, ADLMIDI_VERSION_MINOR, @@ -72,7 +76,7 @@ ADLMIDI_EXPORT int adl_setDeviceIdentifier(ADL_MIDIPlayer *device, unsigned id) { if(!device || id > 0x0f) return -1; - MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + MidiPlayer *play = GET_MIDI_PLAYER(device); if(!play) return -1; play->setDeviceId(id); @@ -84,7 +88,7 @@ ADLMIDI_EXPORT int adl_setNumChips(ADL_MIDIPlayer *device, int numCards) if(device == NULL) return -2; - MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + MidiPlayer *play = GET_MIDI_PLAYER(device); #ifdef ADLMIDI_HW_OPL (void)numCards; play->m_setup.NumCards = 1; @@ -107,7 +111,7 @@ ADLMIDI_EXPORT int adl_getNumChips(struct ADL_MIDIPlayer *device) { if(device == NULL) return -2; - MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + MidiPlayer *play = GET_MIDI_PLAYER(device); if(play) return (int)play->m_setup.NumCards; return -2; @@ -127,7 +131,7 @@ ADLMIDI_EXPORT int adl_setBank(ADL_MIDIPlayer *device, int bank) if(bankno < 0) bankno = 0; - MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + MidiPlayer *play = GET_MIDI_PLAYER(device); if(static_cast(bankno) >= NumBanks) { char errBuf[150]; @@ -158,7 +162,7 @@ ADLMIDI_EXPORT int adl_reserveBanks(ADL_MIDIPlayer *device, unsigned banks) { if(!device) return -1; - MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + MidiPlayer *play = GET_MIDI_PLAYER(device); OPL3::BankMap &map = play->opl.dynamic_banks; map.reserve(banks); return (int)map.capacity(); @@ -174,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(device->adl_midiPlayer); + MidiPlayer *play = GET_MIDI_PLAYER(device); OPL3::BankMap &map = play->opl.dynamic_banks; OPL3::BankMap::iterator it; @@ -226,7 +230,7 @@ ADLMIDI_EXPORT int adl_removeBank(ADL_MIDIPlayer *device, ADL_Bank *bank) if(!device || !bank) return -1; - MIDIplay *play = reinterpret_cast(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(); @@ -239,7 +243,7 @@ ADLMIDI_EXPORT int adl_getFirstBank(ADL_MIDIPlayer *device, ADL_Bank *bank) if(!device) return -1; - MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + MidiPlayer *play = GET_MIDI_PLAYER(device); OPL3::BankMap &map = play->opl.dynamic_banks; OPL3::BankMap::iterator it = map.begin(); @@ -255,7 +259,7 @@ ADLMIDI_EXPORT int adl_getNextBank(ADL_MIDIPlayer *device, ADL_Bank *bank) if(!device) return -1; - MIDIplay *play = reinterpret_cast(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); @@ -294,7 +298,7 @@ ADLMIDI_EXPORT int adl_setNumFourOpsChn(ADL_MIDIPlayer *device, int ops4) { if(!device) return -1; - MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + MidiPlayer *play = GET_MIDI_PLAYER(device); if((unsigned int)ops4 > 6 * play->m_setup.NumCards) { char errBuff[250]; @@ -313,7 +317,7 @@ ADLMIDI_EXPORT int adl_getNumFourOpsChn(struct ADL_MIDIPlayer *device) { if(!device) return -1; - MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + MidiPlayer *play = GET_MIDI_PLAYER(device); if(play) return (int)play->m_setup.NumFourOps; return -1; @@ -322,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(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 : @@ -333,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(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 : @@ -344,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(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 : @@ -354,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(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 : @@ -364,8 +371,9 @@ 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(device->adl_midiPlayer); + if(!device) + return; + MidiPlayer *play = GET_MIDI_PLAYER(device); if(!play) return; play->m_setup.fullRangeBrightnessCC74 = (fr_brightness != 0); @@ -375,7 +383,7 @@ ADLMIDI_EXPORT void adl_setLoopEnabled(ADL_MIDIPlayer *device, int loopEn) { if(!device) return; - MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + MidiPlayer *play = GET_MIDI_PLAYER(device); if(!play) return; #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER @@ -390,7 +398,9 @@ ADLMIDI_EXPORT void adl_setLogarithmicVolumes(struct ADL_MIDIPlayer *device, int { if(!device) return; - MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + 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); @@ -402,7 +412,9 @@ ADLMIDI_EXPORT void adl_setVolumeRangeModel(struct ADL_MIDIPlayer *device, int v { if(!device) return; - MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + 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; @@ -414,7 +426,7 @@ ADLMIDI_EXPORT int adl_openBankFile(struct ADL_MIDIPlayer *device, const char *f { if(device && device->adl_midiPlayer) { - MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + MidiPlayer *play = GET_MIDI_PLAYER(device); play->m_setup.tick_skip_samples_delay = 0; if(!play->LoadBank(filePath)) { @@ -434,7 +446,9 @@ ADLMIDI_EXPORT int adl_openBankData(struct ADL_MIDIPlayer *device, const void *m { if(device && device->adl_midiPlayer) { - MIDIplay *play = reinterpret_cast(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))) { @@ -454,7 +468,9 @@ ADLMIDI_EXPORT int adl_openFile(ADL_MIDIPlayer *device, const char *filePath) { if(device && device->adl_midiPlayer) { - MIDIplay *play = reinterpret_cast(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)) @@ -480,7 +496,9 @@ ADLMIDI_EXPORT int adl_openData(ADL_MIDIPlayer *device, const void *mem, unsigne { if(device && device->adl_midiPlayer) { - MIDIplay *play = reinterpret_cast(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))) @@ -512,13 +530,13 @@ ADLMIDI_EXPORT const char *adl_chipEmulatorName(struct ADL_MIDIPlayer *device) { if(device) { - #ifndef ADLMIDI_HW_OPL - MIDIplay *play = reinterpret_cast(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"; } @@ -527,7 +545,7 @@ ADLMIDI_EXPORT int adl_switchEmulator(struct ADL_MIDIPlayer *device, int emulato { if(device) { - MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); if(!play) return -1; @@ -547,7 +565,7 @@ ADLMIDI_EXPORT int adl_setRunAtPcmRate(ADL_MIDIPlayer *device, int enabled) { if(device) { - MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + MidiPlayer *play = GET_MIDI_PLAYER(device); if(play) { play->m_setup.runAtPcmRate = (enabled != 0); @@ -582,7 +600,7 @@ ADLMIDI_EXPORT const char *adl_errorInfo(struct ADL_MIDIPlayer *device) { if(!device) return adl_errorString(); - MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + MidiPlayer *play = GET_MIDI_PLAYER(device); if(!play) return adl_errorString(); return play->getErrorString().c_str(); @@ -595,8 +613,11 @@ ADLMIDI_EXPORT const char *adl_getMusicTitle(struct ADL_MIDIPlayer *device) ADLMIDI_EXPORT void adl_close(struct ADL_MIDIPlayer *device) { - if(device->adl_midiPlayer) - delete reinterpret_cast(device->adl_midiPlayer); + if(!device) + return; + MIDIplay * play = reinterpret_cast(device->adl_midiPlayer); + if(play) + delete play; device->adl_midiPlayer = NULL; free(device); device = NULL; @@ -606,7 +627,7 @@ ADLMIDI_EXPORT void adl_reset(struct ADL_MIDIPlayer *device) { if(!device) return; - MIDIplay *play = reinterpret_cast(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); @@ -616,33 +637,45 @@ 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(device->adl_midiPlayer)->m_sequencer.timeLength(); + MidiPlayer *play = GET_MIDI_PLAYER(device); + if(!play) + return -1.0; + return play->m_sequencer.timeLength(); #else + ADL_UNUSED(device); return -1.0; #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(device->adl_midiPlayer)->m_sequencer.getLoopStart(); + MidiPlayer *play = GET_MIDI_PLAYER(device); + if(!play) + return -1.0; + return play->m_sequencer.getLoopStart(); #else + ADL_UNUSED(device); return -1.0; #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(device->adl_midiPlayer)->m_sequencer.getLoopEnd(); + MidiPlayer *play = GET_MIDI_PLAYER(device); + if(!play) + return -1.0; + return play->m_sequencer.getLoopEnd(); #else + ADL_UNUSED(device); return -1.0; #endif } @@ -652,7 +685,7 @@ ADLMIDI_EXPORT double adl_positionTell(struct ADL_MIDIPlayer *device) #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER if(!device) return -1.0; - MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + MidiPlayer *play = GET_MIDI_PLAYER(device); if(!play) return -1.0; return play->m_sequencer.tell(); @@ -669,7 +702,7 @@ ADLMIDI_EXPORT void adl_positionSeek(struct ADL_MIDIPlayer *device, double secon return;//Seeking negative position is forbidden! :-P if(!device) return; - MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + MidiPlayer *play = GET_MIDI_PLAYER(device); if(!play) return; play->realTime_panic(); @@ -686,7 +719,7 @@ ADLMIDI_EXPORT void adl_positionRewind(struct ADL_MIDIPlayer *device) #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER if(!device) return; - MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + MidiPlayer *play = GET_MIDI_PLAYER(device); if(!play) return; play->realTime_panic(); @@ -701,7 +734,7 @@ ADLMIDI_EXPORT void adl_setTempo(struct ADL_MIDIPlayer *device, double tempo) #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER if(!device || (tempo <= 0.0)) return; - MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + MidiPlayer *play = GET_MIDI_PLAYER(device); if(!play) return; play->m_sequencer.setTempo(tempo); @@ -717,7 +750,7 @@ ADLMIDI_EXPORT const char *adl_metaMusicTitle(struct ADL_MIDIPlayer *device) #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER if(!device) return ""; - MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + MidiPlayer *play = GET_MIDI_PLAYER(device); if(!play) return ""; return play->m_sequencer.getMusicTitle().c_str(); @@ -733,7 +766,7 @@ ADLMIDI_EXPORT const char *adl_metaMusicCopyright(struct ADL_MIDIPlayer *device) #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER if(!device) return ""; - MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + MidiPlayer *play = GET_MIDI_PLAYER(device); if(!play) return ""; return play->m_sequencer.getMusicCopyright().c_str(); @@ -748,7 +781,7 @@ ADLMIDI_EXPORT size_t adl_metaTrackTitleCount(struct ADL_MIDIPlayer *device) #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER if(!device) return 0; - MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + MidiPlayer *play = GET_MIDI_PLAYER(device); if(!play) return 0; return play->m_sequencer.getTrackTitles().size(); @@ -763,7 +796,7 @@ ADLMIDI_EXPORT const char *adl_metaTrackTitle(struct ADL_MIDIPlayer *device, siz #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER if(!device) return ""; - MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + MidiPlayer *play = GET_MIDI_PLAYER(device); const std::vector &titles = play->m_sequencer.getTrackTitles(); if(index >= titles.size()) return "INVALID"; @@ -781,7 +814,7 @@ ADLMIDI_EXPORT size_t adl_metaMarkerCount(struct ADL_MIDIPlayer *device) #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER if(!device) return 0; - MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + MidiPlayer *play = GET_MIDI_PLAYER(device); if(!play) return 0; return play->m_sequencer.getMarkers().size(); @@ -795,7 +828,7 @@ ADLMIDI_EXPORT Adl_MarkerEntry adl_metaMarker(struct ADL_MIDIPlayer *device, siz { struct Adl_MarkerEntry marker; #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + MidiPlayer *play = GET_MIDI_PLAYER(device); const std::vector &markers = play->m_sequencer.getMarkers(); if(!device || !play || (index >= markers.size())) { @@ -826,7 +859,7 @@ ADLMIDI_EXPORT void adl_setRawEventHook(struct ADL_MIDIPlayer *device, ADL_RawEv #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER if(!device) return; - MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + MidiPlayer *play = GET_MIDI_PLAYER(device); play->m_sequencerInterface.onEvent = rawEventHook; play->m_sequencerInterface.onEvent_userData = userData; #else @@ -841,7 +874,7 @@ ADLMIDI_EXPORT void adl_setNoteHook(struct ADL_MIDIPlayer *device, ADL_NoteHook { if(!device) return; - MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + MidiPlayer *play = GET_MIDI_PLAYER(device); play->hooks.onNote = noteHook; play->hooks.onNote_userData = userData; } @@ -851,7 +884,7 @@ ADLMIDI_EXPORT void adl_setDebugMessageHook(struct ADL_MIDIPlayer *device, ADL_D { if(!device) return; - MIDIplay *play = reinterpret_cast(device->adl_midiPlayer); + MidiPlayer *play = GET_MIDI_PLAYER(device); play->hooks.onDebugMessage = debugMessageHook; play->hooks.onDebugMessage_userData = userData; #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER @@ -1075,8 +1108,8 @@ ADLMIDI_EXPORT int adl_playFormat(ADL_MIDIPlayer *device, int sampleCount, if(!device) return 0; - MIDIplay *player = reinterpret_cast(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; @@ -1181,8 +1214,8 @@ ADLMIDI_EXPORT int adl_generateFormat(struct ADL_MIDIPlayer *device, int sampleC if(!device) return 0; - MIDIplay *player = reinterpret_cast(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; @@ -1241,10 +1274,10 @@ 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(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 ADL_UNUSED(device); ADL_UNUSED(seconds); @@ -1258,10 +1291,10 @@ ADLMIDI_EXPORT int adl_atEnd(struct ADL_MIDIPlayer *device) #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER if(!device) return 1; - MIDIplay *player = reinterpret_cast(device->adl_midiPlayer); - if(!player) + MidiPlayer *play = GET_MIDI_PLAYER(device); + if(!play) return 1; - return (int)player->m_sequencer.positionAtEnd(); + return (int)play->m_sequencer.positionAtEnd(); #else ADL_UNUSED(device); return 1; @@ -1272,138 +1305,138 @@ ADLMIDI_EXPORT void adl_panic(struct ADL_MIDIPlayer *device) { if(!device) return; - MIDIplay *player = reinterpret_cast(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(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(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(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(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(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(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(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(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(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(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(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(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; - MIDIplay *player = reinterpret_cast(device->adl_midiPlayer); - if(!player) + MidiPlayer *play = GET_MIDI_PLAYER(device); + if(!play) return -1; - return player->realTime_SysEx(msg, size); + return play->realTime_SysEx(msg, size); } diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index b2497ca..1012b77 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -161,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; @@ -180,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(m_setup.VolumeModel)); + else + opl.ChangeVolumeRangesModel(static_cast(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; @@ -291,6 +295,7 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) } midiins = note; // Percussion instrument } + if(isPercussion) bank += OPL3::PercussionTag; @@ -1124,7 +1129,8 @@ void MIDIplay::NoteUpdate(uint16_t MidCh, case OPL3::VOLUME_NATIVE: { volume = vol * Ch[MidCh].volume * Ch[MidCh].expression; - volume = volume * m_masterVolume / (127 * 127 * 127) / 2; + // volume = volume * m_masterVolume / (127 * 127 * 127) / 2; + volume = (volume * m_masterVolume) / 4096766; } break; @@ -1632,9 +1638,9 @@ retry_arpeggio: void MIDIplay::UpdateGlide(double amount) { - unsigned num_channels = Ch.size(); + size_t num_channels = Ch.size(); - for(unsigned channel = 0; channel < num_channels; ++channel) + for(size_t channel = 0; channel < num_channels; ++channel) { MIDIchannel &midiChan = Ch[channel]; if(midiChan.gliding_note_count == 0) @@ -1656,7 +1662,7 @@ void MIDIplay::UpdateGlide(double amount) if(currentTone != previousTone) { it->currentTone = currentTone; - NoteUpdate(channel, it, Upd_Pitch); + NoteUpdate(static_cast(channel), it, Upd_Pitch); } } } diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp index 840ba21..f34404d 100644 --- a/src/adlmidi_private.hpp +++ b/src/adlmidi_private.hpp @@ -946,6 +946,7 @@ public: * @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 -- cgit v1.2.3 From d519f6816e6dbc27ecfa81d2493ae4f40be7f497 Mon Sep 17 00:00:00 2001 From: Vitaly Novichkov Date: Sun, 24 Jun 2018 02:10:25 +0300 Subject: Make CMF files sound more correctly (Especially, Dyna Blaster and Xargon musics are sounding too messy. This fix allows to resolve that) --- src/adlmidi_load.cpp | 2 +- src/adlmidi_midiplay.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/adlmidi_load.cpp b/src/adlmidi_load.cpp index 41f1837..9487188 100644 --- a/src/adlmidi_load.cpp +++ b/src/adlmidi_load.cpp @@ -318,7 +318,7 @@ bool MIDIplay::LoadMIDI_post() 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; diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index 1012b77..190a521 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -1225,7 +1225,7 @@ void MIDIplay::setErrorString(const std::string &err) 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; -- cgit v1.2.3