aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md7
-rw-r--r--fm_banks/wopl_files/lostvik.woplbin0 -> 31899 bytes
-rw-r--r--src/adlmidi.cpp8
-rw-r--r--src/adlmidi_load.cpp43
-rw-r--r--src/adlmidi_midiplay.cpp27
-rw-r--r--src/adlmidi_opl3.cpp17
-rw-r--r--src/adlmidi_private.hpp22
-rw-r--r--utils/midiplay/adlmidiplay.cpp41
8 files changed, 108 insertions, 57 deletions
diff --git a/README.md b/README.md
index b7e4635..47cd036 100644
--- a/README.md
+++ b/README.md
@@ -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
new file mode 100644
index 0000000..fe19ef9
--- /dev/null
+++ b/fm_banks/wopl_files/lostvik.wopl
Binary files differ
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;
}