diff options
-rw-r--r-- | README.md | 7 | ||||
-rw-r--r-- | fm_banks/wopl_files/lostvik.wopl | bin | 0 -> 31899 bytes | |||
-rw-r--r-- | src/adlmidi.cpp | 8 | ||||
-rw-r--r-- | src/adlmidi_load.cpp | 43 | ||||
-rw-r--r-- | src/adlmidi_midiplay.cpp | 27 | ||||
-rw-r--r-- | src/adlmidi_opl3.cpp | 17 | ||||
-rw-r--r-- | src/adlmidi_private.hpp | 22 | ||||
-rw-r--r-- | utils/midiplay/adlmidiplay.cpp | 41 |
8 files changed, 108 insertions, 57 deletions
@@ -106,16 +106,15 @@ To build that example you will need to have installed SDL2 library. * [ADLMIDI Player for Android](https://github.com/Wohlstand/ADLMIDI-Player-Java/releases) - a little MIDI-player for Android which uses libADLMIDI to play MIDI files and provides flexible GUI with ability to change bank, flags, number of emulated chips, etc. # Todo -* Reimplement original ADLMIDI with using of new hooks of libADLMIDI * Implement WOPL Version 3 which will contain pre-calculated `ms_sound_kon` and `ms_sound_koff` values per every instrument. -* Implement multi-bank to support GS or XG standards. +* Check out for XG/GS standards to provide a support to use any channels as percussion and also check some of SysEx commands. * Add support of MIDI Format 2 files (FL Studio made MIDI-files are wired and opening of those files making lossy of tempo and some meta-information events) * Support of real-time listening of incoming MIDI-commands. That will allow to use library as software MIDI Output device to play any MIDI via this library. # Changelog -## 1.3.0 2017-10-17 -WIP- +## 1.3.0 2017-10-17 * "gen_adldata" tool now supports WOPL banks format which supports a full set of libADLMIDI features * Added support for custom banks are loadable in runtime without rebuilding of "adldata.cpp" banks database * Smooth finalizing of song when loop is disabled (old ugly hack has been removed :wink:) @@ -138,7 +137,7 @@ To build that example you will need to have installed SDL2 library. * Added new functions: adl_linkedLibraryVersion(), adl_errorInfo(), adl_tickEvents(), and adl_generate() * Error string is no more global, now every ADL_MIDIPlayer instance has own thread-safe error info that can be retreived by using adl_errorInfo() function. The adl_errorString() will return library initialization errors only; * Added ะก++ Extra public API which now includes instrument testing feature (which is required by classic ADLMIDI utility) - * ... + * Multi-bank WOPL files now supported! Feel free to implement GS or XG - compatible bank ## 1.2.1 2017-07-30 * Minor fixes diff --git a/fm_banks/wopl_files/lostvik.wopl b/fm_banks/wopl_files/lostvik.wopl Binary files differnew file mode 100644 index 0000000..fe19ef9 --- /dev/null +++ b/fm_banks/wopl_files/lostvik.wopl diff --git a/src/adlmidi.cpp b/src/adlmidi.cpp index dc9c980..8ee5996 100644 --- a/src/adlmidi.cpp +++ b/src/adlmidi.cpp @@ -93,10 +93,7 @@ ADLMIDI_EXPORT int adl_setBank(ADL_MIDIPlayer *device, int bank) bankno = 0; MIDIplay *play = reinterpret_cast<MIDIplay *>(device->adl_midiPlayer); - play->m_setup.AdlBank = static_cast<uint32_t>(bankno); - play->opl.AdlBank = play->m_setup.AdlBank; - - if(play->m_setup.AdlBank >= NumBanks) + if(static_cast<uint32_t>(bankno) >= NumBanks) { std::stringstream s; s << "bank number may only be 0.." << (NumBanks - 1) << ".\n"; @@ -104,6 +101,9 @@ ADLMIDI_EXPORT int adl_setBank(ADL_MIDIPlayer *device, int bank) return -1; } + play->m_setup.AdlBank = static_cast<uint32_t>(bankno); + play->opl.setEmbeddedBank(play->m_setup.AdlBank); + return adlRefreshNumCards(device); #endif } diff --git a/src/adlmidi_load.cpp b/src/adlmidi_load.cpp index 76765dc..9b28810 100644 --- a/src/adlmidi_load.cpp +++ b/src/adlmidi_load.cpp @@ -241,6 +241,9 @@ bool MIDIplay::LoadBank(MIDIplay::fileReader &fr) //6'th byte reserved for ADLMIDI's default volume model m_setup.VolumeModel = (int)head[5]; + opl.dynamic_melodic_banks.clear(); + opl.dynamic_percussion_banks.clear(); + if(version >= 2)//Read bank meta-entries { for(uint16_t i = 0; i < count_melodic_banks; i++) @@ -251,6 +254,9 @@ bool MIDIplay::LoadBank(MIDIplay::fileReader &fr) errorStringOut = "Custom bank: Fail to read melodic bank meta-data!"; return false; } + uint16_t bank = uint16_t(bank_meta[33]) * 256 + uint16_t(bank_meta[32]); + size_t offset = opl.dynamic_melodic_banks.size(); + opl.dynamic_melodic_banks[bank] = offset; //strncpy(bankMeta.name, char_p(bank_meta), 32); //bankMeta.lsb = bank_meta[32]; //bankMeta.msb = bank_meta[33]; @@ -264,15 +270,16 @@ bool MIDIplay::LoadBank(MIDIplay::fileReader &fr) errorStringOut = "Custom bank: Fail to read melodic bank meta-data!"; return false; } + uint16_t bank = uint16_t(bank_meta[33]) * 256 + uint16_t(bank_meta[32]); + size_t offset = opl.dynamic_percussion_banks.size(); + opl.dynamic_percussion_banks[bank] = offset; //strncpy(bankMeta.name, char_p(bank_meta), 32); //bankMeta.lsb = bank_meta[32]; //bankMeta.msb = bank_meta[33]; } } - opl.AdlBank = m_setup.AdlBank; - opl.dynamic_metainstruments.clear(); - opl.dynamic_instruments.clear(); + opl.setEmbeddedBank(m_setup.AdlBank); uint16_t total = 128 * count_melodic_banks; bool readPercussion = false; @@ -284,28 +291,24 @@ tryAgain: memset(&ins, 0, sizeof(WOPL_Inst)); if(!readInstrument(fr, ins, readPercussion)) { - opl.dynamic_metainstruments.clear(); - opl.dynamic_instruments.clear(); + opl.setEmbeddedBank(m_setup.AdlBank); errorStringOut = "Custom bank: Fail to read instrument!"; return false; } - if(i < 128) //Only first bank! + /* + * 0..127 - melodic, 128...255 - percussion. + * TODO: Make separated melodic and drum arrays and make MIDI bank ID support + */ + ins.adlins.adlno1 = static_cast<uint16_t>(opl.dynamic_instruments.size() | opl.DynamicInstrumentTag); + opl.dynamic_instruments.push_back(ins.op[0]); + ins.adlins.adlno2 = ins.adlins.adlno1; + if(ins.fourOps) { - /* - * 0..127 - melodic, 128...255 - percussion. - * TODO: Make separated melodic and drum arrays and make MIDI bank ID support - */ - ins.adlins.adlno1 = static_cast<uint16_t>(opl.dynamic_instruments.size() | opl.DynamicInstrumentTag); - opl.dynamic_instruments.push_back(ins.op[0]); - ins.adlins.adlno2 = ins.adlins.adlno1; - if(ins.fourOps) - { - ins.adlins.adlno2 = static_cast<uint16_t>(opl.dynamic_instruments.size() | opl.DynamicInstrumentTag); - opl.dynamic_instruments.push_back(ins.op[1]); - } - opl.dynamic_metainstruments.push_back(ins.adlins); + ins.adlins.adlno2 = static_cast<uint16_t>(opl.dynamic_instruments.size() | opl.DynamicInstrumentTag); + opl.dynamic_instruments.push_back(ins.op[1]); } + opl.dynamic_metainstruments.push_back(ins.adlins); } if(!readPercussion) @@ -316,6 +319,8 @@ tryAgain: } opl.AdlBank = ~0u; // Use dynamic banks! + //Percussion offset is count of instruments multipled to count of melodic banks + opl.dynamic_percussion_offset = 128 * count_melodic_banks; return true; } diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index 7d1ba75..8794d9f 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -866,10 +866,29 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) if(velocity == 0) return false; - uint8_t midiins = Ch[channel].patch; + size_t midiins = Ch[channel].patch; + bool isPercussion = (channel % 16 == 9); - if(channel % 16 == 9) - midiins = 128 + note; // Percussion instrument + if(isPercussion) + midiins = opl.dynamic_percussion_offset + note; // Percussion instrument + + //Set bank bank + if(Ch[channel].bank_msb || Ch[channel].bank_lsb) + { + uint16_t bank = (uint16_t(Ch[channel].bank_msb) * 256) + uint16_t(Ch[channel].bank_lsb); + if(isPercussion) + { + OPL3::BankMap::iterator b = opl.dynamic_percussion_banks.find(bank); + if(b != opl.dynamic_percussion_banks.end()) + midiins += b->second * 128; + } + else + { + OPL3::BankMap::iterator b = opl.dynamic_melodic_banks.find(bank); + if(b != opl.dynamic_melodic_banks.end()) + midiins += b->second * 128; + } + } /* if(MidCh%16 == 9 || (midiins != 32 && midiins != 46 && midiins != 48 && midiins != 50)) @@ -921,7 +940,7 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) */ //int meta = banks[opl.AdlBank][midiins]; - const uint32_t meta = opl.GetAdlMetaNumber(midiins); + const size_t meta = opl.GetAdlMetaNumber(midiins); const adlinsdata &ains = opl.GetAdlMetaIns(meta); int16_t tone = note; diff --git a/src/adlmidi_opl3.cpp b/src/adlmidi_opl3.cpp index ce26ba6..9db3803 100644 --- a/src/adlmidi_opl3.cpp +++ b/src/adlmidi_opl3.cpp @@ -115,30 +115,39 @@ static const unsigned short Channels[23] = */ -const adlinsdata &OPL3::GetAdlMetaIns(unsigned n) +const adlinsdata &OPL3::GetAdlMetaIns(size_t n) { return (n & DynamicMetaInstrumentTag) ? dynamic_metainstruments[n & ~DynamicMetaInstrumentTag] : adlins[n]; } -unsigned OPL3::GetAdlMetaNumber(unsigned midiins) +size_t OPL3::GetAdlMetaNumber(size_t midiins) { return (AdlBank == ~0u) ? (midiins | DynamicMetaInstrumentTag) : banks[AdlBank][midiins]; } - const adldata &OPL3::GetAdlIns(size_t insno) { return (insno & DynamicInstrumentTag) ? dynamic_instruments[insno & ~DynamicInstrumentTag] - : adl[insno]; + : adl[insno]; +} + +void OPL3::setEmbeddedBank(unsigned int bank) +{ + AdlBank = bank; + //Embedded banks are supports 128:128 GM set only + dynamic_percussion_offset = 128; + dynamic_melodic_banks.clear(); + dynamic_percussion_banks.clear(); } OPL3::OPL3() : + dynamic_percussion_offset(128), DynamicInstrumentTag(0x8000u), DynamicMetaInstrumentTag(0x4000000u), NumCards(1), diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp index 698dad5..dbea5e6 100644 --- a/src/adlmidi_private.hpp +++ b/src/adlmidi_private.hpp @@ -141,8 +141,9 @@ public: }; class MIDIplay; -struct OPL3 +class OPL3 { +public: friend class MIDIplay; uint32_t NumChannels; char ____padding[4]; @@ -162,13 +163,19 @@ private: friend int adlRefreshNumCards(ADL_MIDIPlayer *device); std::vector<adlinsdata> dynamic_metainstruments; // Replaces adlins[] when CMF file std::vector<adldata> dynamic_instruments; // Replaces adl[] when CMF file + size_t dynamic_percussion_offset; + typedef std::map<uint16_t, size_t> BankMap; + BankMap dynamic_melodic_banks; + BankMap dynamic_percussion_banks; const unsigned DynamicInstrumentTag /* = 0x8000u*/, DynamicMetaInstrumentTag /* = 0x4000000u*/; public: - const adlinsdata &GetAdlMetaIns(unsigned n); - unsigned GetAdlMetaNumber(unsigned midiins); + const adlinsdata &GetAdlMetaIns(size_t n); + size_t GetAdlMetaNumber(size_t midiins); const adldata &GetAdlIns(size_t insno); + void setEmbeddedBank(unsigned int bank); + //! Total number of running concurrent emulated chips unsigned int NumCards; //! Currently running embedded bank number. "~0" means usign of the custom bank. @@ -446,14 +453,14 @@ public: { // Current pressure uint8_t vol; - // Tone selected on noteon: char ____padding[1]; + // Tone selected on noteon: int16_t tone; + char ____padding2[4]; // Patch selected on noteon; index to banks[AdlBank][] - uint8_t midiins; + size_t midiins; // Index to physical adlib data structure, adlins[] - char ____padding2[3]; - uint32_t insmeta; + size_t insmeta; struct Phys { //! ins, inde to adl[] @@ -471,7 +478,6 @@ public: } }; typedef std::map<uint16_t, Phys> PhysMap; - char ____padding3[4]; // List of OPL3 channels it is currently occupying. std::map<uint16_t /*adlchn*/, Phys> phys; }; diff --git a/utils/midiplay/adlmidiplay.cpp b/utils/midiplay/adlmidiplay.cpp index 9719532..1174e3f 100644 --- a/utils/midiplay/adlmidiplay.cpp +++ b/utils/midiplay/adlmidiplay.cpp @@ -132,7 +132,9 @@ int main(int argc, char **argv) "\n" ); + // Get count of embedded banks (no initialization needed) int banksCount = adl_getBanksCount(); + //Get pointer to list of embedded bank names const char *const *banknames = adl_getBankNames(); if(banksCount > 0) @@ -185,6 +187,7 @@ int main(int argc, char **argv) spec.callback = SDL_AudioCallbackX; ADL_MIDIPlayer *myDevice; + //Initialize libADLMIDI and create the instance (you can initialize multiple of them!) myDevice = adl_init(44100); if(myDevice == NULL) { @@ -192,30 +195,34 @@ int main(int argc, char **argv) return 1; } + //Set internal debug messages hook to print all libADLMIDI's internal debug messages adl_setDebugMessageHook(myDevice, debugPrint, NULL); + /* + * Set library options by parsing of command line arguments + */ bool recordWave = false; int loopEnabled = 1; - while(argc > 2) { bool had_option = false; if(!std::strcmp("-p", argv[2])) - adl_setPercMode(myDevice, 1); + adl_setPercMode(myDevice, 1);//Turn on AdLib percussion mode else if(!std::strcmp("-v", argv[2])) - adl_setHVibrato(myDevice, 1); + adl_setHVibrato(myDevice, 1);//Turn on deep vibrato else if(!std::strcmp("-w", argv[2])) - recordWave = true; + recordWave = true;//Record library output into WAV file else if(!std::strcmp("-t", argv[2])) - adl_setHTremolo(myDevice, 1); + adl_setHTremolo(myDevice, 1);//Turn on deep tremolo else if(!std::strcmp("-nl", argv[2])) - loopEnabled = 0; + loopEnabled = 0; //Enable loop else if(!std::strcmp("-s", argv[2])) - adl_setScaleModulators(myDevice, 1); + adl_setScaleModulators(myDevice, 1);//Turn on modulators scaling by volume else break; - std::copy(argv + (had_option ? 4 : 3), argv + argc, + std::copy(argv + (had_option ? 4 : 3), + argv + argc, argv + 2); argc -= (had_option ? 2 : 1); } @@ -223,11 +230,11 @@ int main(int argc, char **argv) //Turn loop on/off (for WAV recording loop must be disabled!) adl_setLoopEnabled(myDevice, recordWave ? 0 : loopEnabled); #ifdef DEBUG_TRACE_ALL_EVENTS + //Hook all MIDI events are ticking while generating an output buffer if(!recordWave) adl_setRawEventHook(myDevice, debugPrintEvent, NULL); #endif - std::fprintf(stdout, " - %s OPL3 Emulator in use\n", adl_emulatorName()); if(!recordWave) @@ -252,9 +259,10 @@ int main(int argc, char **argv) if(is_number(argv[2])) { int bankno = std::atoi(argv[2]); + //Choose one of embedded banks if(adl_setBank(myDevice, bankno) != 0) { - printError(adl_errorString()); + printError(adl_errorInfo(myDevice)); return 1; } std::fprintf(stdout, " - Use embedded bank #%d [%s]\n", bankno, adl_getBankNames()[bankno]); @@ -263,11 +271,13 @@ int main(int argc, char **argv) { std::fprintf(stdout, " - Use custom bank [%s]...", argv[2]); std::fflush(stdout); + //Open external bank file (WOPL format is supported) + //to create or edit them, use OPL3 Bank Editor you can take here https://github.com/Wohlstand/OPL3BankEditor if(adl_openBankFile(myDevice, argv[2]) != 0) { std::fprintf(stdout, "FAILED!\n"); std::fflush(stdout); - printError(adl_errorString()); + printError(adl_errorInfo(myDevice)); return 1; } std::fprintf(stdout, "OK!\n"); @@ -278,26 +288,29 @@ int main(int argc, char **argv) if(argc >= 4) numOfChips = std::atoi(argv[3]); + //Set count of concurrent emulated chips count to excite channels limit of one chip if(adl_setNumCards(myDevice, numOfChips) != 0) { - printError(adl_errorString()); + printError(adl_errorInfo(myDevice)); return 1; } std::fprintf(stdout, " - Number of chips %d\n", numOfChips); if(argc >= 5) { + //Set total count of 4-operator channels between all emulated chips if(adl_setNumFourOpsChn(myDevice, std::atoi(argv[4])) != 0) { - printError(adl_errorString()); + printError(adl_errorInfo(myDevice)); return 1; } std::fprintf(stdout, " - Number of four-ops %s\n", argv[4]); } + //Open MIDI file to play if(adl_openFile(myDevice, argv[1]) != 0) { - printError(adl_errorString()); + printError(adl_errorInfo(myDevice)); return 2; } |