diff options
author | Wohlstand <admin@wohlnet.ru> | 2017-10-17 03:57:21 +0300 |
---|---|---|
committer | Wohlstand <admin@wohlnet.ru> | 2017-10-17 03:57:21 +0300 |
commit | c07b146a1a3991edd6c3233dcd1956b989303dd3 (patch) | |
tree | c48650bbdcc9d64c2b4179cc3df9fd0fcd0ecbd6 /src | |
parent | 3a1a97809d0a9b88916e5213f239fac046244241 (diff) | |
download | libADLMIDI-c07b146a1a3991edd6c3233dcd1956b989303dd3.tar.gz libADLMIDI-c07b146a1a3991edd6c3233dcd1956b989303dd3.tar.bz2 libADLMIDI-c07b146a1a3991edd6c3233dcd1956b989303dd3.zip |
EXPERIMENTAL: Support for custom banks!
TODO: Fix support for real 4-operator support for dynamical instruments which now are silent. 2-op and pseudo-4-op still work fine.
Diffstat (limited to 'src')
-rw-r--r-- | src/adlmidi.cpp | 46 | ||||
-rw-r--r-- | src/adlmidi.h | 6 | ||||
-rw-r--r-- | src/adlmidi_load.cpp | 275 | ||||
-rw-r--r-- | src/adlmidi_midiplay.cpp | 1 | ||||
-rw-r--r-- | src/adlmidi_private.hpp | 4 | ||||
-rw-r--r-- | src/midiplay/adlmidiplay.cpp | 116 |
6 files changed, 406 insertions, 42 deletions
diff --git a/src/adlmidi.cpp b/src/adlmidi.cpp index 3d9d1cb..79c1422 100644 --- a/src/adlmidi.cpp +++ b/src/adlmidi.cpp @@ -186,7 +186,7 @@ ADLMIDI_EXPORT void adl_setLogarithmicVolumes(struct ADL_MIDIPlayer *device, int reinterpret_cast<MIDIplay *>(device->adl_midiPlayer)->opl.LogarithmicVolumes = (bool)device->LogarithmicVolumes; } -ADLMIDI_EXPORT void adl_setVolumeRangeModel(ADL_MIDIPlayer *device, int volumeModel) +ADLMIDI_EXPORT void adl_setVolumeRangeModel(struct ADL_MIDIPlayer *device, int volumeModel) { if(!device) return; @@ -194,6 +194,50 @@ ADLMIDI_EXPORT void adl_setVolumeRangeModel(ADL_MIDIPlayer *device, int volumeMo reinterpret_cast<MIDIplay *>(device->adl_midiPlayer)->opl.ChangeVolumeRangesModel(static_cast<ADLMIDI_VolumeModels>(volumeModel)); } +ADLMIDI_EXPORT int adl_openBankFile(struct ADL_MIDIPlayer *device, char *filePath) +{ + ADLMIDI_ErrorString.clear(); + + if(device && device->adl_midiPlayer) + { + device->stored_samples = 0; + device->backup_samples_size = 0; + + if(!reinterpret_cast<MIDIplay *>(device->adl_midiPlayer)->LoadBank(filePath)) + { + if(ADLMIDI_ErrorString.empty()) + ADLMIDI_ErrorString = "ADL MIDI: Can't load file"; + return -1; + } + else return 0; + } + + ADLMIDI_ErrorString = "Can't load file: ADLMIDI is not initialized"; + return -1; +} + +ADLMIDI_EXPORT int adl_openBankData(struct ADL_MIDIPlayer *device, void *mem, long size) +{ + ADLMIDI_ErrorString.clear(); + + if(device && device->adl_midiPlayer) + { + device->stored_samples = 0; + device->backup_samples_size = 0; + + if(!reinterpret_cast<MIDIplay *>(device->adl_midiPlayer)->LoadBank(mem, static_cast<size_t>(size))) + { + if(ADLMIDI_ErrorString.empty()) + ADLMIDI_ErrorString = "ADL MIDI: Can't load data from memory"; + return -1; + } + else return 0; + } + + ADLMIDI_ErrorString = "Can't load file: ADL MIDI is not initialized"; + return -1; +} + ADLMIDI_EXPORT int adl_openFile(ADL_MIDIPlayer *device, char *filePath) { ADLMIDI_ErrorString.clear(); diff --git a/src/adlmidi.h b/src/adlmidi.h index 89e853f..752e307 100644 --- a/src/adlmidi.h +++ b/src/adlmidi.h @@ -106,6 +106,12 @@ extern void adl_setLogarithmicVolumes(struct ADL_MIDIPlayer *device, int logvol) /*Set different volume range model */ extern void adl_setVolumeRangeModel(struct ADL_MIDIPlayer *device, int volumeModel); +/*Load WOPL bank file from File System*/ +extern int adl_openBankFile(struct ADL_MIDIPlayer *device, char *filePath); + +/*Load WOPL bank file from memory data*/ +extern int adl_openBankData(struct ADL_MIDIPlayer *device, void *mem, long size); + /*Returns string which contains last error message*/ extern const char *adl_errorString(); diff --git a/src/adlmidi_load.cpp b/src/adlmidi_load.cpp index c21249c..5258dc9 100644 --- a/src/adlmidi_load.cpp +++ b/src/adlmidi_load.cpp @@ -76,6 +76,275 @@ uint64_t MIDIplay::ReadVarLenEx(size_t tk, bool &ok) return result; } +bool MIDIplay::LoadBank(const std::string &filename) +{ + fileReader file; + file.openFile(filename.c_str()); + return LoadBank(file); +} + +bool MIDIplay::LoadBank(void *data, unsigned long size) +{ + fileReader file; + file.openData(data, size); + return LoadBank(file); +} + + + +/* WOPL-needed misc functions */ +static uint16_t toUint16LE(const uint8_t *arr) +{ + uint16_t num = arr[0]; + num |= ((arr[1] << 8) & 0xFF00); + return num; +} + +static uint16_t toUint16BE(const uint8_t *arr) +{ + uint16_t num = arr[1]; + num |= ((arr[0] << 8) & 0xFF00); + return num; +} + +static int16_t toSint16BE(const uint8_t *arr) +{ + int16_t num = *reinterpret_cast<const int8_t *>(&arr[0]); + num *= 1 << 8; + num |= arr[1]; + return num; +} + +static const char *wopl3_magic = "WOPL3-BANK\0"; +static const uint16_t wopl_latest_version = 2; + +enum WOPL_InstrumentFlags +{ + WOPL_Flags_NONE = 0, + WOPL_Flag_Enable4OP = 0x01, + WOPL_Flag_Pseudo4OP = 0x02, +}; + +struct WOPL_Inst +{ + bool fourOps; + char padding[7]; + struct adlinsdata adlins; + struct adldata op[2]; +}; + +static bool readInstrument(MIDIplay::fileReader &file, WOPL_Inst &ins, bool isPercussion = false) +{ + uint8_t idata[62]; + if(file.read(idata, 1, 62) != 62) + return false; + + //strncpy(ins.name, char_p(idata), 32); + ins.op[0].finetune = (int8_t)toSint16BE(idata + 32); + ins.op[1].finetune = (int8_t)toSint16BE(idata + 34); + //ins.velocity_offset = int8_t(idata[36]); + + ins.adlins.voice2_fine_tune = 0.0; + int8_t voice2_fine_tune = int8_t(idata[37]); + if(voice2_fine_tune != 0) + { + if(voice2_fine_tune == 1) + ins.adlins.voice2_fine_tune = 0.000025; + else if(voice2_fine_tune == -1) + ins.adlins.voice2_fine_tune = -0.000025; + else + ins.adlins.voice2_fine_tune = ((voice2_fine_tune * 15.625) / 1000.0); + } + + ins.adlins.tone = isPercussion ? idata[38] : 0; + + /* TODO: add those fields into next version of WOPL format + * and re-generate those values on file save! */ + ins.adlins.ms_sound_kon = 1000; + ins.adlins.ms_sound_koff = 500; + /* ----------------------------------------------------- */ + + uint8_t flags = idata[39]; + ins.adlins.flags = (flags & WOPL_Flag_Enable4OP) && (flags & WOPL_Flag_Pseudo4OP) ? adlinsdata::Flag_Pseudo4op : 0; + ins.fourOps = (flags & WOPL_Flag_Enable4OP) || (flags & WOPL_Flag_Pseudo4OP); + + ins.op[0].feedconn = (idata[40]); + ins.op[1].feedconn = (idata[41]); + + for(size_t op = 0, slt = 0; op < 4; op++, slt++) + { + size_t off = 42 + size_t(op) * 5; + // ins.setAVEKM(op, idata[off + 0]);//AVEKM + // ins.setAtDec(op, idata[off + 2]);//AtDec + // ins.setSusRel(op, idata[off + 3]);//SusRel + // ins.setWaveForm(op, idata[off + 4]);//WaveForm + // ins.setKSLL(op, idata[off + 1]);//KSLL + ins.op[slt].carrier_E862 = + ((static_cast<uint32_t>(idata[off + 4]) << 24) & 0xFF000000) //WaveForm + | ((static_cast<uint32_t>(idata[off + 3]) << 16) & 0x00FF0000) //SusRel + | ((static_cast<uint32_t>(idata[off + 2]) << 8) & 0x0000FF00) //AtDec + | ((static_cast<uint32_t>(idata[off + 0]) << 0) & 0x000000FF); //AVEKM + ins.op[slt].carrier_40 = idata[off + 1];//KSLL + + op++; + off = 42 + size_t(op) * 5; + ins.op[slt].modulator_E862 = + ((static_cast<uint32_t>(idata[off + 4]) << 24) & 0xFF000000) //WaveForm + | ((static_cast<uint32_t>(idata[off + 3]) << 16) & 0x00FF0000) //SusRel + | ((static_cast<uint32_t>(idata[off + 2]) << 8) & 0x0000FF00) //AtDec + | ((static_cast<uint32_t>(idata[off + 0]) << 0) & 0x000000FF); //AVEKM + ins.op[slt].modulator_40 = idata[off + 1];//KSLL + } + return true; +} + +bool MIDIplay::LoadBank(MIDIplay::fileReader &fr) +{ +#define qqq(x) (void)x + size_t fsize; + qqq(fsize); + if(!fr.isValid()) + { + ADLMIDI_ErrorString = "Custom bank: Invalid data stream!"; + return false; + } + + char magic[32]; + memset(magic, 0, 32); + + uint16_t version = 0; + + uint16_t count_melodic_banks = 1; + uint16_t count_percusive_banks = 1; + + if(fr.read(magic, 1, 11) != 11) + { + ADLMIDI_ErrorString = "Custom bank: Can't read magic number!"; + return false; + } + + if(strncmp(magic, wopl3_magic, 11) != 0) + { + ADLMIDI_ErrorString = "Custom bank: Invalid magic number!"; + return false; + } + + uint8_t version_raw[2]; + if(fr.read(version_raw, 1, 2) != 2) + { + ADLMIDI_ErrorString = "Custom bank: Can't read version!"; + return false; + } + + version = toUint16LE(version_raw); + if(version > wopl_latest_version) + { + ADLMIDI_ErrorString = "Custom bank: Unsupported WOPL version!"; + return false; + } + + uint8_t head[6]; + memset(head, 0, 6); + if(fr.read(head, 1, 6) != 6) + { + ADLMIDI_ErrorString = "Custom bank: Can't read header!"; + return false; + } + + count_melodic_banks = toUint16BE(head); + count_percusive_banks = toUint16BE(head + 2); + + if((count_melodic_banks < 1) || (count_percusive_banks < 1)) + { + ADLMIDI_ErrorString = "Custom bank: Too few banks in this file!"; + return false; + } + + /*UNUSED YET*/ + //bool default_deep_vibrato = ((head[4]>>0) & 0x01); + //bool default_deep_tremolo = ((head[4]>>1) & 0x01); + + //5'th byte reserved for Deep-Tremolo and Deep-Vibrato flags + //6'th byte reserved for ADLMIDI's default volume model + + if(version >= 2)//Read bank meta-entries + { + for(uint16_t i = 0; i < count_melodic_banks; i++) + { + uint8_t bank_meta[34]; + if(fr.read(bank_meta, 1, 34) != 34) + { + ADLMIDI_ErrorString = "Custom bank: Fail to read melodic bank meta-data!"; + return false; + } + //strncpy(bankMeta.name, char_p(bank_meta), 32); + //bankMeta.lsb = bank_meta[32]; + //bankMeta.msb = bank_meta[33]; + } + + for(uint16_t i = 0; i < count_percusive_banks; i++) + { + uint8_t bank_meta[34]; + if(fr.read(bank_meta, 1, 34) != 34) + { + ADLMIDI_ErrorString = "Custom bank: Fail to read melodic bank meta-data!"; + return false; + } + //strncpy(bankMeta.name, char_p(bank_meta), 32); + //bankMeta.lsb = bank_meta[32]; + //bankMeta.msb = bank_meta[33]; + } + } + + opl.dynamic_metainstruments.clear(); + opl.dynamic_instruments.clear(); + + uint16_t total = 128 * count_melodic_banks; + bool readPercussion = false; + +tryAgain: + for(uint16_t i = 0; i < total; i++) + { + WOPL_Inst ins; + memset(&ins, 0, sizeof(WOPL_Inst)); + if(!readInstrument(fr, ins, readPercussion)) + { + opl.dynamic_metainstruments.clear(); + opl.dynamic_instruments.clear(); + ADLMIDI_ErrorString = "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) + { + 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) + { + total = 128 * count_percusive_banks; + readPercussion = true; + goto tryAgain; + } + + opl.AdlBank = ~0u; // Use dynamic banks! + + return true; +} + bool MIDIplay::LoadMIDI(const std::string &filename) { fileReader file; @@ -99,7 +368,7 @@ bool MIDIplay::LoadMIDI(void *data, unsigned long size) bool MIDIplay::LoadMIDI(MIDIplay::fileReader &fr) { - #define qqq(x) (void)x +#define qqq(x) (void)x size_t fsize; qqq(fsize); //! Temp buffer for conversion @@ -197,7 +466,7 @@ riffskip: fr.seek(0, SEEK_END); size_t mus_len = fr.tell(); fr.seek(0, SEEK_SET); - uint8_t *mus = (uint8_t*)malloc(mus_len); + uint8_t *mus = (uint8_t *)malloc(mus_len); if(!mus) { ADLMIDI_ErrorString = "Out of memory!"; @@ -235,7 +504,7 @@ riffskip: fr.seek(0, SEEK_END); size_t mus_len = fr.tell(); fr.seek(0, SEEK_SET); - uint8_t *mus = (uint8_t*)malloc(mus_len); + uint8_t *mus = (uint8_t *)malloc(mus_len); if(!mus) { ADLMIDI_ErrorString = "Out of memory!"; diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index 1214ef5..24dd60f 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -324,7 +324,6 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) for(uint32_t a = 0; a < opl.NumChannels; ++a) { if(ccount == 1 && static_cast<int32_t>(a) == adlchannel[0]) continue; - // ^ Don't use the same channel for primary&secondary if(i[0] == i[1] || pseudo_4op) diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp index 5b28ea6..17863d3 100644 --- a/src/adlmidi_private.hpp +++ b/src/adlmidi_private.hpp @@ -454,6 +454,10 @@ public: size_t mp_tell; }; + bool LoadBank(const std::string &filename); + bool LoadBank(void *data, unsigned long size); + bool LoadBank(fileReader &fr); + bool LoadMIDI(const std::string &filename); bool LoadMIDI(void *data, unsigned long size); bool LoadMIDI(fileReader &fr); diff --git a/src/midiplay/adlmidiplay.cpp b/src/midiplay/adlmidiplay.cpp index 34bf885..ca22025 100644 --- a/src/midiplay/adlmidiplay.cpp +++ b/src/midiplay/adlmidiplay.cpp @@ -11,29 +11,38 @@ class MutexType { - SDL_mutex* mut; + SDL_mutex *mut; public: MutexType() : mut(SDL_CreateMutex()) { } - ~MutexType() { SDL_DestroyMutex(mut); } - void Lock() { SDL_mutexP(mut); } - void Unlock() { SDL_mutexV(mut); } + ~MutexType() + { + SDL_DestroyMutex(mut); + } + void Lock() + { + SDL_mutexP(mut); + } + void Unlock() + { + SDL_mutexV(mut); + } }; static std::deque<short> AudioBuffer; static MutexType AudioBuffer_lock; -static void SDL_AudioCallbackX(void*, Uint8* stream, int len) +static void SDL_AudioCallbackX(void *, Uint8 *stream, int len) { SDL_LockAudio(); - short* target = (short*) stream; + short *target = (short *) stream; AudioBuffer_lock.Lock(); /*if(len != AudioBuffer.size()) fprintf(stderr, "len=%d stereo samples, AudioBuffer has %u stereo samples", len/4, (unsigned) AudioBuffer.size()/2);*/ - unsigned ate = len/2; // number of shorts + unsigned ate = len / 2; // number of shorts if(ate > AudioBuffer.size()) ate = AudioBuffer.size(); - for(unsigned a=0; a<ate; ++a) + for(unsigned a = 0; a < ate; ++a) { target[a] = AudioBuffer[a]; } @@ -42,31 +51,45 @@ static void SDL_AudioCallbackX(void*, Uint8* stream, int len) SDL_UnlockAudio(); } - +static bool is_number(const std::string &s) +{ + std::string::const_iterator it = s.begin(); + while(it != s.end() && std::isdigit(*it)) ++it; + return !s.empty() && it == s.end(); +} #undef main -int main(int argc, char** argv) +int main(int argc, char **argv) { if(argc < 2 || std::string(argv[1]) == "--help" || std::string(argv[1]) == "-h") { std::printf( - "Usage: adlmidi <midifilename> [ <options> ] [ <banknumber> [ <numcards> [ <numfourops>] ] ]\n" + "Usage: adlmidi <midifilename> [ <options> ] [ <bank> [ <numcards> [ <numfourops>] ] ]\n" " -p Enables adlib percussion instrument mode\n" " -t Enables tremolo amplification mode\n" " -v Enables vibrato amplification mode\n" " -s Enables scaling of modulator volumes\n" " -nl Quit without looping\n" " -w Write WAV file rather than playing\n" + "\n" + "Where <bank> - number of embeeded bank or filepath to custom WOPL bank file\n" + "\n" + "Note: To create WOPL bank files use OPL Bank Editor you can get here: \n" + "https://github.com/Wohlstand/OPL3BankEditor\n" + "\n" ); -/* - for(unsigned a=0; a<sizeof(banknames)/sizeof(*banknames); ++a) - std::printf("%10s%2u = %s\n", - a?"":"Banks:", - a, - banknames[a]); -*/ + + int banksCount = adl_getBanksCount(); + const char* const* banknames = adl_getBankNames(); + + std::printf(" Available embedded banks by number:\n\n"); + + for(int a = 0; a < banksCount; ++a) + std::printf("%10s%2u = %s\n", a ? "" : "Banks:", a, banknames[a]); + std::printf( + "\n" " Use banks 2-5 to play Descent \"q\" soundtracks.\n" " Look up the relevant bank number from descent.sng.\n" "\n" @@ -76,7 +99,7 @@ int main(int argc, char** argv) " The Doom & Hexen sets require one or two, while\n" " Miles four-op set requires the maximum of numcards*6.\n" "\n" - ); + ); return 0; } @@ -107,12 +130,12 @@ int main(int argc, char** argv) } if(spec.samples != obtained.samples) std::fprintf(stderr, "Wanted (samples=%u,rate=%u,channels=%u); obtained (samples=%u,rate=%u,channels=%u)\n", - spec.samples, spec.freq, spec.channels, - obtained.samples,obtained.freq,obtained.channels); + spec.samples, spec.freq, spec.channels, + obtained.samples, obtained.freq, obtained.channels); - ADL_MIDIPlayer* myDevice; + ADL_MIDIPlayer *myDevice; myDevice = adl_init(44100); - if(myDevice==NULL) + if(myDevice == NULL) { std::fprintf(stderr, "Failed to init MIDI device!\n"); return 1; @@ -136,17 +159,36 @@ int main(int argc, char** argv) else break; std::copy(argv + (had_option ? 4 : 3), argv + argc, - argv+2); + argv + 2); argc -= (had_option ? 2 : 1); } if(argc >= 3) { - int bankno = std::atoi(argv[2]); - if(adl_setBank(myDevice, bankno)!=0) + if(is_number(argv[2])) { - std::fprintf(stderr,"%s", adl_errorString()); - return 0; + int bankno = std::atoi(argv[2]); + if(adl_setBank(myDevice, bankno) != 0) + { + std::fprintf(stderr, "%s\n", adl_errorString()); + std::fflush(stderr); + return 0; + } + } + else + { + std::fprintf(stdout, "Loading custom bank file %s...", argv[2]); + std::fflush(stdout); + if(adl_openBankFile(myDevice, argv[2]) != 0) + { + std::fprintf(stdout, "FAILED!\n"); + std::fflush(stdout); + std::fprintf(stderr, "%s\n", adl_errorString()); + std::fflush(stderr); + return 0; + } + std::fprintf(stdout, "OK!\n"); + std::fflush(stdout); } } @@ -160,14 +202,14 @@ int main(int argc, char** argv) } if(argc >= 5) { - if(adl_setNumFourOpsChn(myDevice, std::atoi(argv[4]))!=0) + if(adl_setNumFourOpsChn(myDevice, std::atoi(argv[4])) != 0) { std::fprintf(stderr, "%s\n", adl_errorString()); return 0; } } - if(adl_openFile(myDevice, argv[1])!=0) + if(adl_openFile(myDevice, argv[1]) != 0) { std::fprintf(stderr, "%s\n", adl_errorString()); return 2; @@ -179,17 +221,17 @@ int main(int argc, char** argv) { short buff[4096]; unsigned long gotten = adl_play(myDevice, 4096, buff); - if(gotten<=0) break; + if(gotten <= 0) break; AudioBuffer_lock.Lock(); - size_t pos = AudioBuffer.size(); - AudioBuffer.resize(pos + gotten); - for(unsigned long p = 0; p < gotten; ++p) - AudioBuffer[pos+p] = buff[p]; + size_t pos = AudioBuffer.size(); + AudioBuffer.resize(pos + gotten); + for(unsigned long p = 0; p < gotten; ++p) + AudioBuffer[pos + p] = buff[p]; AudioBuffer_lock.Unlock(); - const SDL_AudioSpec& spec_ = obtained; - while(AudioBuffer.size() > spec_.samples + (spec_.freq*2) * OurHeadRoomLength) + const SDL_AudioSpec &spec_ = obtained; + while(AudioBuffer.size() > spec_.samples + (spec_.freq * 2) * OurHeadRoomLength) { SDL_Delay(1); } |