diff options
author | Vitaly Novichkov <Wohlstand@users.noreply.github.com> | 2018-10-06 03:24:05 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-10-06 03:24:05 +0300 |
commit | 3d70e34b258b1042aeea9dca13480cbb61610999 (patch) | |
tree | 7972745dc36ca6c0fdfe2c05ba1173bb4d6bdca6 | |
parent | 5e9be04e14cca96551cd19f5ee7597a11ad98a8a (diff) | |
parent | c542a7576de33ecc9c9744cf5a4df7d094af5c47 (diff) | |
download | libADLMIDI-3d70e34b258b1042aeea9dca13480cbb61610999.tar.gz libADLMIDI-3d70e34b258b1042aeea9dca13480cbb61610999.tar.bz2 libADLMIDI-3d70e34b258b1042aeea9dca13480cbb61610999.zip |
Merge pull request #184 from jpcima/reorganize
reorganize the OPL3 chip manager code
-rw-r--r-- | src/adlmidi.cpp | 193 | ||||
-rw-r--r-- | src/adlmidi_load.cpp | 82 | ||||
-rw-r--r-- | src/adlmidi_midiplay.cpp | 189 | ||||
-rw-r--r-- | src/adlmidi_midiplay.hpp | 1016 | ||||
-rw-r--r-- | src/adlmidi_opl3.cpp | 12 | ||||
-rw-r--r-- | src/adlmidi_opl3.hpp | 321 | ||||
-rw-r--r-- | src/adlmidi_private.cpp | 15 | ||||
-rw-r--r-- | src/adlmidi_private.hpp | 1313 | ||||
-rw-r--r-- | src/adlmidi_ptr.hpp | 2 | ||||
-rw-r--r-- | src/adlmidi_sequencer.cpp | 45 | ||||
-rw-r--r-- | src/midi_sequencer.h | 2 |
11 files changed, 1657 insertions, 1533 deletions
diff --git a/src/adlmidi.cpp b/src/adlmidi.cpp index 12d0e68..541dbb4 100644 --- a/src/adlmidi.cpp +++ b/src/adlmidi.cpp @@ -21,7 +21,15 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include "adlmidi_midiplay.hpp" +#include "adlmidi_opl3.hpp" #include "adlmidi_private.hpp" +#ifndef ADLMIDI_HW_OPL +#include "chips/opl_chip_base.h" +#endif +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER +#include "midi_sequencer.hpp" +#endif /* Unify MIDI player casting and interface between ADLMIDI and OPNMIDI */ #define GET_MIDI_PLAYER(device) reinterpret_cast<MIDIplay *>((device)->adl_midiPlayer) @@ -112,13 +120,14 @@ ADLMIDI_EXPORT int adl_setNumChips(ADL_MIDIPlayer *device, int numChips) else if(play->m_setup.numFourOps < -1) play->m_setup.numFourOps = -1; - if(!play->m_synth.setupLocked()) + Synth &synth = *play->m_synth; + if(!synth.setupLocked()) { - play->m_synth.m_numChips = play->m_setup.numChips; + synth.m_numChips = play->m_setup.numChips; if(play->m_setup.numFourOps < 0) adlCalculateFourOpChannels(play, true); else - play->m_synth.m_numFourOps = static_cast<uint32_t>(play->m_setup.numFourOps); + synth.m_numFourOps = static_cast<uint32_t>(play->m_setup.numFourOps); play->partialReset(); return 0; } @@ -141,7 +150,7 @@ ADLMIDI_EXPORT int adl_getNumChipsObtained(struct ADL_MIDIPlayer *device) return -2; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); - return (int)play->m_synth.m_numChips; + return (int)play->m_synth->m_numChips; } ADLMIDI_EXPORT int adl_setBank(ADL_MIDIPlayer *device, int bank) @@ -171,8 +180,9 @@ ADLMIDI_EXPORT int adl_setBank(ADL_MIDIPlayer *device, int bank) return -1; } + Synth &synth = *play->m_synth; play->m_setup.bankId = static_cast<uint32_t>(bankno); - play->m_synth.setEmbeddedBank(play->m_setup.bankId); + synth.setEmbeddedBank(play->m_setup.bankId); play->applySetup(); return 0; @@ -203,7 +213,7 @@ ADLMIDI_EXPORT int adl_reserveBanks(ADL_MIDIPlayer *device, unsigned banks) return -1; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); - OPL3::BankMap &map = play->m_synth.m_insBanks; + Synth::BankMap &map = play->m_synth->m_insBanks; map.reserve(banks); return (int)map.capacity(); } @@ -216,13 +226,13 @@ ADLMIDI_EXPORT int adl_getBank(ADL_MIDIPlayer *device, const ADL_BankId *idp, in ADL_BankId id = *idp; if(id.lsb > 127 || id.msb > 127 || id.percussive > 1) return -1; - size_t idnumber = ((id.msb << 8) | id.lsb | (id.percussive ? size_t(OPL3::PercussionTag) : 0)); + size_t idnumber = ((id.msb << 8) | id.lsb | (id.percussive ? size_t(Synth::PercussionTag) : 0)); MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); - OPL3::BankMap &map = play->m_synth.m_insBanks; + Synth::BankMap &map = play->m_synth->m_insBanks; - OPL3::BankMap::iterator it; + Synth::BankMap::iterator it; if(!(flags & ADLMIDI_Bank_Create)) { it = map.find(idnumber); @@ -231,16 +241,16 @@ ADLMIDI_EXPORT int adl_getBank(ADL_MIDIPlayer *device, const ADL_BankId *idp, in } else { - std::pair<size_t, OPL3::Bank> value; + std::pair<size_t, Synth::Bank> value; value.first = idnumber; memset(&value.second, 0, sizeof(value.second)); for (unsigned i = 0; i < 128; ++i) value.second.ins[i].flags = adlinsdata::Flag_NoSound; - std::pair<OPL3::BankMap::iterator, bool> ir; + std::pair<Synth::BankMap::iterator, bool> ir; if(flags & ADLMIDI_Bank_CreateRt) { - ir = map.insert(value, OPL3::BankMap::do_not_expand_t()); + ir = map.insert(value, Synth::BankMap::do_not_expand_t()); if(ir.first == map.end()) return -1; } @@ -258,11 +268,11 @@ ADLMIDI_EXPORT int adl_getBankId(ADL_MIDIPlayer *device, const ADL_Bank *bank, A if(!device || !bank) return -1; - OPL3::BankMap::iterator it = OPL3::BankMap::iterator::from_ptrs(bank->pointer); - OPL3::BankMap::key_type idnumber = it->first; + Synth::BankMap::iterator it = Synth::BankMap::iterator::from_ptrs(bank->pointer); + Synth::BankMap::key_type idnumber = it->first; id->msb = (idnumber >> 8) & 127; id->lsb = idnumber & 127; - id->percussive = (idnumber & OPL3::PercussionTag) ? 1 : 0; + id->percussive = (idnumber & Synth::PercussionTag) ? 1 : 0; return 0; } @@ -273,8 +283,8 @@ ADLMIDI_EXPORT int adl_removeBank(ADL_MIDIPlayer *device, ADL_Bank *bank) MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); - OPL3::BankMap &map = play->m_synth.m_insBanks; - OPL3::BankMap::iterator it = OPL3::BankMap::iterator::from_ptrs(bank->pointer); + Synth::BankMap &map = play->m_synth->m_insBanks; + Synth::BankMap::iterator it = Synth::BankMap::iterator::from_ptrs(bank->pointer); size_t size = map.size(); map.erase(it); return (map.size() != size) ? 0 : -1; @@ -287,9 +297,9 @@ ADLMIDI_EXPORT int adl_getFirstBank(ADL_MIDIPlayer *device, ADL_Bank *bank) MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); - OPL3::BankMap &map = play->m_synth.m_insBanks; + Synth::BankMap &map = play->m_synth->m_insBanks; - OPL3::BankMap::iterator it = map.begin(); + Synth::BankMap::iterator it = map.begin(); if(it == map.end()) return -1; @@ -304,9 +314,9 @@ ADLMIDI_EXPORT int adl_getNextBank(ADL_MIDIPlayer *device, ADL_Bank *bank) MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); - OPL3::BankMap &map = play->m_synth.m_insBanks; + Synth::BankMap &map = play->m_synth->m_insBanks; - OPL3::BankMap::iterator it = OPL3::BankMap::iterator::from_ptrs(bank->pointer); + Synth::BankMap::iterator it = Synth::BankMap::iterator::from_ptrs(bank->pointer); if(++it == map.end()) return -1; @@ -319,7 +329,7 @@ ADLMIDI_EXPORT int adl_getInstrument(ADL_MIDIPlayer *device, const ADL_Bank *ban if(!device || !bank || index > 127 || !ins) return -1; - OPL3::BankMap::iterator it = OPL3::BankMap::iterator::from_ptrs(bank->pointer); + Synth::BankMap::iterator it = Synth::BankMap::iterator::from_ptrs(bank->pointer); cvt_FMIns_to_ADLI(*ins, it->second.ins[index]); ins->version = 0; return 0; @@ -333,7 +343,7 @@ ADLMIDI_EXPORT int adl_setInstrument(ADL_MIDIPlayer *device, ADL_Bank *bank, uns if(ins->version != 0) return -1; - OPL3::BankMap::iterator it = OPL3::BankMap::iterator::from_ptrs(bank->pointer); + Synth::BankMap::iterator it = Synth::BankMap::iterator::from_ptrs(bank->pointer); cvt_ADLI_to_FMIns(it->second.ins[index], *ins); return 0; } @@ -356,11 +366,11 @@ ADLMIDI_EXPORT int adl_loadEmbeddedBank(struct ADL_MIDIPlayer *device, ADL_Bank if(num < 0 || num >= maxAdlBanks()) return -1; - OPL3::BankMap::iterator it = OPL3::BankMap::iterator::from_ptrs(bank->pointer); + Synth::BankMap::iterator it = Synth::BankMap::iterator::from_ptrs(bank->pointer); size_t id = it->first; for (unsigned i = 0; i < 128; ++i) { - size_t insno = i + ((id & OPL3::PercussionTag) ? 128 : 0); + size_t insno = i + ((id & Synth::PercussionTag) ? 128 : 0); size_t adlmeta = ::banks[num][insno]; it->second.ins[i] = adlinsdata2::from_adldata(::adlins[adlmeta]); } @@ -383,14 +393,15 @@ ADLMIDI_EXPORT int adl_setNumFourOpsChn(ADL_MIDIPlayer *device, int ops4) return -1; } + Synth &synth = *play->m_synth; play->m_setup.numFourOps = ops4; - if(!play->m_synth.setupLocked()) + if(!synth.setupLocked()) { if(play->m_setup.numFourOps < 0) adlCalculateFourOpChannels(play, true); else - play->m_synth.m_numFourOps = static_cast<uint32_t>(play->m_setup.numFourOps); - play->m_synth.updateChannelCategories(); + synth.m_numFourOps = static_cast<uint32_t>(play->m_setup.numFourOps); + synth.updateChannelCategories(); } return 0; @@ -411,7 +422,7 @@ ADLMIDI_EXPORT int adl_getNumFourOpsChnObtained(struct ADL_MIDIPlayer *device) return -2; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); - return (int)play->m_synth.m_numFourOps; + return (int)play->m_synth->m_numFourOps; } @@ -420,13 +431,14 @@ ADLMIDI_EXPORT void adl_setPercMode(ADL_MIDIPlayer *device, int percmod) if(!device) return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); + Synth &synth = *play->m_synth; play->m_setup.rhythmMode = percmod; - if(!play->m_synth.setupLocked()) + if(!synth.setupLocked()) { - play->m_synth.m_rhythmMode = play->m_setup.rhythmMode < 0 ? - (play->m_synth.m_insBankSetup.adLibPercussions) : + synth.m_rhythmMode = play->m_setup.rhythmMode < 0 ? + (synth.m_insBankSetup.adLibPercussions) : (play->m_setup.rhythmMode != 0); - play->m_synth.updateChannelCategories(); + synth.updateChannelCategories(); } } @@ -435,13 +447,14 @@ ADLMIDI_EXPORT void adl_setHVibrato(ADL_MIDIPlayer *device, int hvibro) if(!device) return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); + Synth &synth = *play->m_synth; play->m_setup.deepVibratoMode = hvibro; - if(!play->m_synth.setupLocked()) + if(!synth.setupLocked()) { - play->m_synth.m_deepVibratoMode = play->m_setup.deepVibratoMode < 0 ? - play->m_synth.m_insBankSetup.deepVibrato : + synth.m_deepVibratoMode = play->m_setup.deepVibratoMode < 0 ? + synth.m_insBankSetup.deepVibrato : (play->m_setup.deepVibratoMode != 0); - play->m_synth.commitDeepFlags(); + synth.commitDeepFlags(); } } @@ -450,7 +463,7 @@ ADLMIDI_EXPORT int adl_getHVibrato(struct ADL_MIDIPlayer *device) if(!device) return -1; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); - return play->m_synth.m_deepVibratoMode; + return play->m_synth->m_deepVibratoMode; } ADLMIDI_EXPORT void adl_setHTremolo(ADL_MIDIPlayer *device, int htremo) @@ -458,13 +471,14 @@ ADLMIDI_EXPORT void adl_setHTremolo(ADL_MIDIPlayer *device, int htremo) if(!device) return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); + Synth &synth = *play->m_synth; play->m_setup.deepTremoloMode = htremo; - if(!play->m_synth.setupLocked()) + if(!synth.setupLocked()) { - play->m_synth.m_deepTremoloMode = play->m_setup.deepTremoloMode < 0 ? - play->m_synth.m_insBankSetup.deepTremolo : + synth.m_deepTremoloMode = play->m_setup.deepTremoloMode < 0 ? + synth.m_insBankSetup.deepTremolo : (play->m_setup.deepTremoloMode != 0); - play->m_synth.commitDeepFlags(); + synth.commitDeepFlags(); } } @@ -473,7 +487,7 @@ ADLMIDI_EXPORT int adl_getHTremolo(struct ADL_MIDIPlayer *device) if(!device) return -1; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); - return play->m_synth.m_deepTremoloMode; + return play->m_synth->m_deepTremoloMode; } ADLMIDI_EXPORT void adl_setScaleModulators(ADL_MIDIPlayer *device, int smod) @@ -482,11 +496,12 @@ ADLMIDI_EXPORT void adl_setScaleModulators(ADL_MIDIPlayer *device, int smod) return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); + Synth &synth = *play->m_synth; play->m_setup.scaleModulators = smod; - if(!play->m_synth.setupLocked()) + if(!synth.setupLocked()) { - play->m_synth.m_scaleModulators = play->m_setup.scaleModulators < 0 ? - play->m_synth.m_insBankSetup.scaleModulators : + synth.m_scaleModulators = play->m_setup.scaleModulators < 0 ? + synth.m_insBankSetup.scaleModulators : (play->m_setup.scaleModulators != 0); } } @@ -507,7 +522,7 @@ ADLMIDI_EXPORT void adl_setLoopEnabled(ADL_MIDIPlayer *device, int loopEn) MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - play->m_sequencer.setLoopEnabled(loopEn != 0); + play->m_sequencer->setLoopEnabled(loopEn != 0); #else ADL_UNUSED(loopEn); #endif @@ -519,7 +534,7 @@ ADLMIDI_EXPORT void adl_setSoftPanEnabled(ADL_MIDIPlayer *device, int softPanEn) return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); - play->m_synth.m_softPanning = (softPanEn != 0); + play->m_synth->m_softPanning = (softPanEn != 0); } /* !!!DEPRECATED!!! */ @@ -529,13 +544,14 @@ ADLMIDI_EXPORT void adl_setLogarithmicVolumes(struct ADL_MIDIPlayer *device, int return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); + Synth &synth = *play->m_synth; play->m_setup.logarithmicVolumes = (logvol != 0); - if(!play->m_synth.setupLocked()) + if(!synth.setupLocked()) { if(play->m_setup.logarithmicVolumes) - play->m_synth.setVolumeScaleModel(ADLMIDI_VolumeModel_NativeOPL3); + synth.setVolumeScaleModel(ADLMIDI_VolumeModel_NativeOPL3); else - play->m_synth.setVolumeScaleModel(static_cast<ADLMIDI_VolumeModels>(play->m_synth.m_volumeScale)); + synth.setVolumeScaleModel(static_cast<ADLMIDI_VolumeModels>(synth.m_volumeScale)); } } @@ -545,13 +561,14 @@ ADLMIDI_EXPORT void adl_setVolumeRangeModel(struct ADL_MIDIPlayer *device, int v return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); + Synth &synth = *play->m_synth; play->m_setup.volumeScaleModel = volumeModel; - if(!play->m_synth.setupLocked()) + if(!synth.setupLocked()) { if(play->m_setup.volumeScaleModel == ADLMIDI_VolumeModel_AUTO)//Use bank default volume model - play->m_synth.m_volumeScale = (OPL3::VolumesScale)play->m_synth.m_insBankSetup.volumeModel; + synth.m_volumeScale = (Synth::VolumesScale)synth.m_insBankSetup.volumeModel; else - play->m_synth.setVolumeScaleModel(static_cast<ADLMIDI_VolumeModels>(volumeModel)); + synth.setVolumeScaleModel(static_cast<ADLMIDI_VolumeModels>(volumeModel)); } } @@ -561,7 +578,7 @@ ADLMIDI_EXPORT int adl_getVolumeRangeModel(struct ADL_MIDIPlayer *device) return -1; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); - return play->m_synth.getVolumeScaleModel(); + return play->m_synth->getVolumeScaleModel(); } ADLMIDI_EXPORT int adl_openBankFile(struct ADL_MIDIPlayer *device, const char *filePath) @@ -675,8 +692,9 @@ ADLMIDI_EXPORT const char *adl_chipEmulatorName(struct ADL_MIDIPlayer *device) #ifndef ADLMIDI_HW_OPL MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); - if(!play->m_synth.m_chips.empty()) - return play->m_synth.m_chips[0]->emulatorName(); + Synth &synth = *play->m_synth; + if(!synth.m_chips.empty()) + return synth.m_chips[0]->emulatorName(); #else return "Hardware OPL3 chip on 0x330"; #endif @@ -708,8 +726,9 @@ ADLMIDI_EXPORT int adl_setRunAtPcmRate(ADL_MIDIPlayer *device, int enabled) { MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); + Synth &synth = *play->m_synth; play->m_setup.runAtPcmRate = (enabled != 0); - if(!play->m_synth.setupLocked()) + if(!synth.setupLocked()) play->partialReset(); return 0; } @@ -763,7 +782,7 @@ ADLMIDI_EXPORT double adl_totalTimeLength(struct ADL_MIDIPlayer *device) return -1.0; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); - return play->m_sequencer.timeLength(); + return play->m_sequencer->timeLength(); #else ADL_UNUSED(device); return -1.0; @@ -777,7 +796,7 @@ ADLMIDI_EXPORT double adl_loopStartTime(struct ADL_MIDIPlayer *device) return -1.0; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); - return play->m_sequencer.getLoopStart(); + return play->m_sequencer->getLoopStart(); #else ADL_UNUSED(device); return -1.0; @@ -791,7 +810,7 @@ ADLMIDI_EXPORT double adl_loopEndTime(struct ADL_MIDIPlayer *device) return -1.0; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); - return play->m_sequencer.getLoopEnd(); + return play->m_sequencer->getLoopEnd(); #else ADL_UNUSED(device); return -1.0; @@ -805,7 +824,7 @@ ADLMIDI_EXPORT double adl_positionTell(struct ADL_MIDIPlayer *device) return -1.0; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); - return play->m_sequencer.tell(); + return play->m_sequencer->tell(); #else ADL_UNUSED(device); return -1.0; @@ -822,7 +841,7 @@ ADLMIDI_EXPORT void adl_positionSeek(struct ADL_MIDIPlayer *device, double secon MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); play->realTime_panic(); - play->m_setup.delay = play->m_sequencer.seek(seconds, play->m_setup.mindelay); + play->m_setup.delay = play->m_sequencer->seek(seconds, play->m_setup.mindelay); play->m_setup.carry = 0.0; #else ADL_UNUSED(device); @@ -838,7 +857,7 @@ ADLMIDI_EXPORT void adl_positionRewind(struct ADL_MIDIPlayer *device) MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); play->realTime_panic(); - play->m_sequencer.rewind(); + play->m_sequencer->rewind(); #else ADL_UNUSED(device); #endif @@ -851,7 +870,7 @@ ADLMIDI_EXPORT void adl_setTempo(struct ADL_MIDIPlayer *device, double tempo) return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); - play->m_sequencer.setTempo(tempo); + play->m_sequencer->setTempo(tempo); #else ADL_UNUSED(device); ADL_UNUSED(tempo); @@ -877,7 +896,7 @@ ADLMIDI_EXPORT const char *adl_metaMusicTitle(struct ADL_MIDIPlayer *device) return ""; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); - return play->m_sequencer.getMusicTitle().c_str(); + return play->m_sequencer->getMusicTitle().c_str(); #else ADL_UNUSED(device); return ""; @@ -892,7 +911,7 @@ ADLMIDI_EXPORT const char *adl_metaMusicCopyright(struct ADL_MIDIPlayer *device) return ""; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); - return play->m_sequencer.getMusicCopyright().c_str(); + return play->m_sequencer->getMusicCopyright().c_str(); #else ADL_UNUSED(device); return ""; @@ -906,7 +925,7 @@ ADLMIDI_EXPORT size_t adl_metaTrackTitleCount(struct ADL_MIDIPlayer *device) return 0; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); - return play->m_sequencer.getTrackTitles().size(); + return play->m_sequencer->getTrackTitles().size(); #else ADL_UNUSED(device); return 0; @@ -920,7 +939,7 @@ ADLMIDI_EXPORT const char *adl_metaTrackTitle(struct ADL_MIDIPlayer *device, siz return ""; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); - const std::vector<std::string> &titles = play->m_sequencer.getTrackTitles(); + const std::vector<std::string> &titles = play->m_sequencer->getTrackTitles(); if(index >= titles.size()) return "INVALID"; return titles[index].c_str(); @@ -939,7 +958,7 @@ ADLMIDI_EXPORT size_t adl_metaMarkerCount(struct ADL_MIDIPlayer *device) return 0; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); - return play->m_sequencer.getMarkers().size(); + return play->m_sequencer->getMarkers().size(); #else ADL_UNUSED(device); return 0; @@ -962,7 +981,7 @@ ADLMIDI_EXPORT Adl_MarkerEntry adl_metaMarker(struct ADL_MIDIPlayer *device, siz MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); - const std::vector<MidiSequencer::MIDI_MarkerEntry> &markers = play->m_sequencer.getMarkers(); + const std::vector<MidiSequencer::MIDI_MarkerEntry> &markers = play->m_sequencer->getMarkers(); if(index >= markers.size()) { marker.label = "INVALID"; @@ -992,8 +1011,8 @@ ADLMIDI_EXPORT void adl_setRawEventHook(struct ADL_MIDIPlayer *device, ADL_RawEv return; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); - play->m_sequencerInterface.onEvent = rawEventHook; - play->m_sequencerInterface.onEvent_userData = userData; + play->m_sequencerInterface->onEvent = rawEventHook; + play->m_sequencerInterface->onEvent_userData = userData; #else ADL_UNUSED(device); ADL_UNUSED(rawEventHook); @@ -1022,8 +1041,8 @@ ADLMIDI_EXPORT void adl_setDebugMessageHook(struct ADL_MIDIPlayer *device, ADL_D 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; + play->m_sequencerInterface->onDebugMessage = debugMessageHook; + play->m_sequencerInterface->onDebugMessage_userData = userData; #endif } @@ -1273,7 +1292,7 @@ ADLMIDI_EXPORT int adl_playFormat(ADL_MIDIPlayer *device, int sampleCount, // setup.SkipForward -= 1; //else { - if((player->m_sequencer.positionAtEnd()) && (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; @@ -1290,16 +1309,17 @@ ADLMIDI_EXPORT int adl_playFormat(ADL_MIDIPlayer *device, int sampleCount, //fill buffer with zeros int32_t *out_buf = player->m_outBuf; std::memset(out_buf, 0, static_cast<size_t>(in_generatedPhys) * sizeof(out_buf[0])); - unsigned int chips = player->m_synth.m_numChips; + Synth &synth = *player->m_synth; + unsigned int chips = synth.m_numChips; if(chips == 1) { - player->m_synth.m_chips[0]->generate32(out_buf, (size_t)in_generatedStereo); + synth.m_chips[0]->generate32(out_buf, (size_t)in_generatedStereo); } else if(n_periodCountStereo > 0) { /* Generate data from every chip and mix result */ for(size_t card = 0; card < chips; ++card) - player->m_synth.m_chips[card]->generateAndMix32(out_buf, (size_t)in_generatedStereo); + synth.m_chips[card]->generateAndMix32(out_buf, (size_t)in_generatedStereo); } /* Process it */ @@ -1380,14 +1400,15 @@ ADLMIDI_EXPORT int adl_generateFormat(struct ADL_MIDIPlayer *device, int sampleC //fill buffer with zeros int32_t *out_buf = player->m_outBuf; std::memset(out_buf, 0, static_cast<size_t>(in_generatedPhys) * sizeof(out_buf[0])); - unsigned int chips = player->m_synth.m_numChips; + Synth &synth = *player->m_synth; + unsigned int chips = synth.m_numChips; if(chips == 1) - player->m_synth.m_chips[0]->generate32(out_buf, (size_t)in_generatedStereo); + synth.m_chips[0]->generate32(out_buf, (size_t)in_generatedStereo); else if(n_periodCountStereo > 0) { /* Generate data from every chip and mix result */ for(unsigned card = 0; card < chips; ++card) - player->m_synth.m_chips[card]->generateAndMix32(out_buf, (size_t)in_generatedStereo); + synth.m_chips[card]->generateAndMix32(out_buf, (size_t)in_generatedStereo); } /* Process it */ if(SendStereoAudio(sampleCount, in_generatedStereo, out_buf, gotten_len, out_left, out_right, format) == -1) @@ -1428,7 +1449,7 @@ ADLMIDI_EXPORT int adl_atEnd(struct ADL_MIDIPlayer *device) return 1; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); - return (int)play->m_sequencer.positionAtEnd(); + return (int)play->m_sequencer->positionAtEnd(); #else ADL_UNUSED(device); return 1; @@ -1442,7 +1463,7 @@ ADLMIDI_EXPORT size_t adl_trackCount(struct ADL_MIDIPlayer *device) return 0; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); - return play->m_sequencer.getTrackCount(); + return play->m_sequencer->getTrackCount(); #else ADL_UNUSED(device); return 0; @@ -1456,7 +1477,7 @@ ADLMIDI_EXPORT int adl_setTrackOptions(struct ADL_MIDIPlayer *device, size_t tra return -1; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); - MidiSequencer &seq = play->m_sequencer; + MidiSequencer &seq = *play->m_sequencer; unsigned enableFlag = trackOptions & 3; trackOptions &= ~3u; @@ -1497,7 +1518,7 @@ ADLMIDI_EXPORT int adl_setTriggerHandler(struct ADL_MIDIPlayer *device, ADL_Trig return -1; MidiPlayer *play = GET_MIDI_PLAYER(device); assert(play); - MidiSequencer &seq = play->m_sequencer; + MidiSequencer &seq = *play->m_sequencer; seq.setTriggerHandler(handler, userData); return 0; #else diff --git a/src/adlmidi_load.cpp b/src/adlmidi_load.cpp index 07ad875..e270f3b 100644 --- a/src/adlmidi_load.cpp +++ b/src/adlmidi_load.cpp @@ -21,8 +21,12 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include "adlmidi_midiplay.hpp" +#include "adlmidi_opl3.hpp" #include "adlmidi_private.hpp" #include "adlmidi_cvt.hpp" +#include "file_reader.hpp" +#include "midi_sequencer.hpp" #include "wopl/wopl_file.h" bool MIDIplay::LoadBank(const std::string &filename) @@ -104,16 +108,17 @@ bool MIDIplay::LoadBank(FileAndMemReader &fr) } } - m_synth.m_insBankSetup.adLibPercussions = false; - m_synth.m_insBankSetup.scaleModulators = false; - m_synth.m_insBankSetup.deepTremolo = (wopl->opl_flags & WOPL_FLAG_DEEP_TREMOLO) != 0; - m_synth.m_insBankSetup.deepVibrato = (wopl->opl_flags & WOPL_FLAG_DEEP_VIBRATO) != 0; - m_synth.m_insBankSetup.volumeModel = wopl->volume_model; + Synth &synth = *m_synth; + synth.m_insBankSetup.adLibPercussions = false; + synth.m_insBankSetup.scaleModulators = false; + synth.m_insBankSetup.deepTremolo = (wopl->opl_flags & WOPL_FLAG_DEEP_TREMOLO) != 0; + synth.m_insBankSetup.deepVibrato = (wopl->opl_flags & WOPL_FLAG_DEEP_VIBRATO) != 0; + synth.m_insBankSetup.volumeModel = wopl->volume_model; m_setup.deepTremoloMode = -1; m_setup.deepVibratoMode = -1; m_setup.volumeScaleModel = ADLMIDI_VolumeModel_AUTO; - m_synth.setEmbeddedBank(m_setup.bankId); + synth.setEmbeddedBank(m_setup.bankId); uint16_t slots_counts[2] = {wopl->banks_count_melodic, wopl->banks_count_percussion}; WOPLBank *slots_src_ins[2] = { wopl->banks_melodic, wopl->banks_percussive }; @@ -124,8 +129,8 @@ bool MIDIplay::LoadBank(FileAndMemReader &fr) { size_t bankno = (slots_src_ins[ss][i].bank_midi_msb * 256) + (slots_src_ins[ss][i].bank_midi_lsb) + - (ss ? size_t(OPL3::PercussionTag) : 0); - OPL3::Bank &bank = m_synth.m_insBanks[bankno]; + (ss ? size_t(Synth::PercussionTag) : 0); + Synth::Bank &bank = synth.m_insBanks[bankno]; for(int j = 0; j < 128; j++) { adlinsdata2 &ins = bank.ins[j]; @@ -136,7 +141,7 @@ bool MIDIplay::LoadBank(FileAndMemReader &fr) } } - m_synth.m_embeddedBank = OPL3::CustomBankTag; // Use dynamic banks! + synth.m_embeddedBank = Synth::CustomBankTag; // Use dynamic banks! //Percussion offset is count of instruments multipled to count of melodic banks applySetup(); @@ -150,7 +155,8 @@ bool MIDIplay::LoadBank(FileAndMemReader &fr) bool MIDIplay::LoadMIDI_pre() { #ifdef DISABLE_EMBEDDED_BANKS - if((m_synth.m_embeddedBank != OPL3::CustomBankTag) || m_synth.m_insBanks.empty()) + Synth &synth = *m_synth; + if((synth.m_embeddedBank != Synth::CustomBankTag) || synth.m_insBanks.empty()) { errorStringOut = "Bank is not set! Please load any instruments bank by using of adl_openBankFile() or adl_openBankData() functions!"; return false; @@ -165,11 +171,13 @@ bool MIDIplay::LoadMIDI_pre() bool MIDIplay::LoadMIDI_post() { - MidiSequencer::FileFormat format = m_sequencer.getFormat(); + Synth &synth = *m_synth; + MidiSequencer &seq = *m_sequencer; + MidiSequencer::FileFormat format = seq.getFormat(); if(format == MidiSequencer::Format_CMF) { - const std::vector<MidiSequencer::CmfInstrument> &instruments = m_sequencer.getRawCmfInstruments(); - m_synth.m_insBanks.clear();//Clean up old banks + const std::vector<MidiSequencer::CmfInstrument> &instruments = seq.getRawCmfInstruments(); + synth.m_insBanks.clear();//Clean up old banks uint16_t ins_count = static_cast<uint16_t>(instruments.size()); for(uint16_t i = 0; i < ins_count; ++i) @@ -179,12 +187,12 @@ bool MIDIplay::LoadMIDI_post() bank = ((bank & 127) + ((bank >> 7) << 8)); if(bank > 127 + (127 << 8)) break; - bank += (i % 256 < 128) ? 0 : size_t(OPL3::PercussionTag); + bank += (i % 256 < 128) ? 0 : size_t(Synth::PercussionTag); /*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]);*/ - adlinsdata2 &adlins = m_synth.m_insBanks[bank].ins[i % 128]; + adlinsdata2 &adlins = synth.m_insBanks[bank].ins[i % 128]; adldata adl; adl.modulator_E862 = ((static_cast<uint32_t>(InsData[8] & 0x07) << 24) & 0xFF000000) //WaveForm @@ -209,45 +217,45 @@ bool MIDIplay::LoadMIDI_post() adlins.voice2_fine_tune = 0.0; } - m_synth.m_embeddedBank = OPL3::CustomBankTag; // Ignore AdlBank number, use dynamic banks instead + synth.m_embeddedBank = Synth::CustomBankTag; // Ignore AdlBank number, use dynamic banks instead //std::printf("CMF deltas %u ticks %u, basictempo = %u\n", deltas, ticks, basictempo); - m_synth.m_rhythmMode = true; - m_synth.m_musicMode = OPL3::MODE_CMF; - m_synth.m_volumeScale = OPL3::VOLUME_NATIVE; + synth.m_rhythmMode = true; + synth.m_musicMode = Synth::MODE_CMF; + synth.m_volumeScale = Synth::VOLUME_NATIVE; - m_synth.m_numChips = 1; - m_synth.m_numFourOps = 0; + synth.m_numChips = 1; + synth.m_numFourOps = 0; } else if(format == MidiSequencer::Format_RSXX) { //opl.CartoonersVolumes = true; - m_synth.m_musicMode = OPL3::MODE_RSXX; - m_synth.m_volumeScale = OPL3::VOLUME_NATIVE; + synth.m_musicMode = Synth::MODE_RSXX; + synth.m_volumeScale = Synth::VOLUME_NATIVE; - m_synth.m_numChips = 1; - m_synth.m_numFourOps = 0; + synth.m_numChips = 1; + synth.m_numFourOps = 0; } else if(format == MidiSequencer::Format_IMF) { //std::fprintf(stderr, "Done reading IMF file\n"); - m_synth.m_numFourOps = 0; //Don't use 4-operator channels for IMF playing! - m_synth.m_musicMode = OPL3::MODE_IMF; + synth.m_numFourOps = 0; //Don't use 4-operator channels for IMF playing! + synth.m_musicMode = Synth::MODE_IMF; - m_synth.m_numChips = 1; - m_synth.m_numFourOps = 0; + synth.m_numChips = 1; + synth.m_numFourOps = 0; } else { - m_synth.m_numChips = m_setup.numChips; + synth.m_numChips = m_setup.numChips; if(m_setup.numFourOps < 0) adlCalculateFourOpChannels(this, true); } m_setup.tick_skip_samples_delay = 0; - m_synth.reset(m_setup.emulator, m_setup.PCM_RATE, this); // Reset OPL3 chip + synth.reset(m_setup.emulator, m_setup.PCM_RATE, this); // Reset OPL3 chip //opl.Reset(); // ...twice (just in case someone misprogrammed OPL3 previously) m_chipChannels.clear(); - m_chipChannels.resize(m_synth.m_numChannels); + m_chipChannels.resize(synth.m_numChannels); return true; } @@ -258,9 +266,10 @@ bool MIDIplay::LoadMIDI(const std::string &filename) file.openFile(filename.c_str()); if(!LoadMIDI_pre()) return false; - if(!m_sequencer.loadMIDI(file)) + MidiSequencer &seq = *m_sequencer; + if(!seq.loadMIDI(file)) { - errorStringOut = m_sequencer.getErrorString(); + errorStringOut = seq.getErrorString(); return false; } if(!LoadMIDI_post()) @@ -274,9 +283,10 @@ bool MIDIplay::LoadMIDI(const void *data, size_t size) file.openData(data, size); if(!LoadMIDI_pre()) return false; - if(!m_sequencer.loadMIDI(file)) + MidiSequencer &seq = *m_sequencer; + if(!seq.loadMIDI(file)) { - errorStringOut = m_sequencer.getErrorString(); + errorStringOut = seq.getErrorString(); return false; } if(!LoadMIDI_post()) diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index 1e1da07..1a20e9e 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -21,7 +21,10 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include "adlmidi_midiplay.hpp" +#include "adlmidi_opl3.hpp" #include "adlmidi_private.hpp" +#include "midi_sequencer.hpp" // Mapping from MIDI volume level to OPL level value. @@ -155,7 +158,10 @@ MIDIplay::MIDIplay(unsigned long sampleRate): m_setup.carry = 0.0; m_setup.tick_skip_samples_delay = 0; + m_synth.reset(new Synth); + #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER + m_sequencer.reset(new MidiSequencer); initSequencerInterface(); #endif resetMIDI(); @@ -163,51 +169,57 @@ MIDIplay::MIDIplay(unsigned long sampleRate): realTime_ResetState(); } +MIDIplay::~MIDIplay() +{ +} + void MIDIplay::applySetup() { - m_synth.m_musicMode = OPL3::MODE_MIDI; + Synth &synth = *m_synth; + + synth.m_musicMode = Synth::MODE_MIDI; m_setup.tick_skip_samples_delay = 0; - m_synth.m_runAtPcmRate = m_setup.runAtPcmRate; + synth.m_runAtPcmRate = m_setup.runAtPcmRate; #ifndef DISABLE_EMBEDDED_BANKS - if(m_synth.m_embeddedBank != OPL3::CustomBankTag) - m_synth.m_insBankSetup = adlbanksetup[m_setup.bankId]; + if(synth.m_embeddedBank != Synth::CustomBankTag) + synth.m_insBankSetup = adlbanksetup[m_setup.bankId]; #endif - m_synth.m_deepTremoloMode = m_setup.deepTremoloMode < 0 ? - m_synth.m_insBankSetup.deepTremolo : - (m_setup.deepTremoloMode != 0); - m_synth.m_deepVibratoMode = m_setup.deepVibratoMode < 0 ? - m_synth.m_insBankSetup.deepVibrato : - (m_setup.deepVibratoMode != 0); - m_synth.m_rhythmMode = m_setup.rhythmMode < 0 ? - m_synth.m_insBankSetup.adLibPercussions : - (m_setup.rhythmMode != 0); - m_synth.m_scaleModulators = m_setup.scaleModulators < 0 ? - m_synth.m_insBankSetup.scaleModulators : - (m_setup.scaleModulators != 0); + synth.m_deepTremoloMode = m_setup.deepTremoloMode < 0 ? + synth.m_insBankSetup.deepTremolo : + (m_setup.deepTremoloMode != 0); + synth.m_deepVibratoMode = m_setup.deepVibratoMode < 0 ? + synth.m_insBankSetup.deepVibrato : + (m_setup.deepVibratoMode != 0); + synth.m_rhythmMode = m_setup.rhythmMode < 0 ? + synth.m_insBankSetup.adLibPercussions : + (m_setup.rhythmMode != 0); + synth.m_scaleModulators = m_setup.scaleModulators < 0 ? + synth.m_insBankSetup.scaleModulators : + (m_setup.scaleModulators != 0); if(m_setup.logarithmicVolumes) - m_synth.setVolumeScaleModel(ADLMIDI_VolumeModel_NativeOPL3); + synth.setVolumeScaleModel(ADLMIDI_VolumeModel_NativeOPL3); else - m_synth.setVolumeScaleModel(static_cast<ADLMIDI_VolumeModels>(m_setup.volumeScaleModel)); + synth.setVolumeScaleModel(static_cast<ADLMIDI_VolumeModels>(m_setup.volumeScaleModel)); if(m_setup.volumeScaleModel == ADLMIDI_VolumeModel_AUTO)//Use bank default volume model - m_synth.m_volumeScale = (OPL3::VolumesScale)m_synth.m_insBankSetup.volumeModel; + synth.m_volumeScale = (Synth::VolumesScale)synth.m_insBankSetup.volumeModel; - m_synth.m_numChips = m_setup.numChips; + synth.m_numChips = m_setup.numChips; m_cmfPercussionMode = false; if(m_setup.numFourOps >= 0) - m_synth.m_numFourOps = m_setup.numFourOps; + synth.m_numFourOps = m_setup.numFourOps; else adlCalculateFourOpChannels(this, true); - m_synth.reset(m_setup.emulator, m_setup.PCM_RATE, this); + synth.reset(m_setup.emulator, m_setup.PCM_RATE, this); m_chipChannels.clear(); - m_chipChannels.resize(m_synth.m_numChannels); + m_chipChannels.resize(synth.m_numChannels); // Reset the arpeggio counter m_arpeggioCounter = 0; @@ -215,12 +227,13 @@ void MIDIplay::applySetup() void MIDIplay::partialReset() { + Synth &synth = *m_synth; realTime_panic(); m_setup.tick_skip_samples_delay = 0; - m_synth.m_runAtPcmRate = m_setup.runAtPcmRate; - m_synth.reset(m_setup.emulator, m_setup.PCM_RATE, this); + synth.m_runAtPcmRate = m_setup.runAtPcmRate; + synth.reset(m_setup.emulator, m_setup.PCM_RATE, this); m_chipChannels.clear(); - m_chipChannels.resize((size_t)m_synth.m_numChannels); + m_chipChannels.resize((size_t)synth.m_numChannels); } void MIDIplay::resetMIDI() @@ -240,7 +253,8 @@ void MIDIplay::resetMIDI() void MIDIplay::TickIterators(double s) { - for(uint16_t c = 0; c < m_synth.m_numChannels; ++c) + Synth &synth = *m_synth; + for(uint16_t c = 0; c < synth.m_numChannels; ++c) m_chipChannels[c].addAge(static_cast<int64_t>(s * 1e6)); updateVibrato(s); updateArpeggio(s); @@ -251,11 +265,12 @@ void MIDIplay::TickIterators(double s) void MIDIplay::realTime_ResetState() { + Synth &synth = *m_synth; for(size_t ch = 0; ch < m_midiChannels.size(); ch++) { MIDIchannel &chan = m_midiChannels[ch]; chan.resetAllControllers(); - chan.volume = (m_synth.m_musicMode == OPL3::MODE_RSXX) ? 127 : 100; + chan.volume = (synth.m_musicMode == Synth::MODE_RSXX) ? 127 : 100; chan.vibpos = 0.0; chan.lastlrpn = 0; chan.lastmrpn = 0; @@ -270,10 +285,12 @@ void MIDIplay::realTime_ResetState() bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) { + Synth &synth = *m_synth; + if(note >= 128) note = 127; - if((m_synth.m_musicMode == OPL3::MODE_RSXX) && (velocity != 0)) + if((synth.m_musicMode == Synth::MODE_RSXX) && (velocity != 0)) { // Check if this is just a note after-touch MIDIchannel::activenoteiterator i = m_midiChannels[channel].activenotes_find(note); @@ -328,17 +345,17 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) } if(isPercussion) - bank += OPL3::PercussionTag; + bank += Synth::PercussionTag; - const adlinsdata2 *ains = &OPL3::m_emptyInstrument; + const adlinsdata2 *ains = &Synth::m_emptyInstrument; //Set bank bank - const OPL3::Bank *bnk = NULL; + const Synth::Bank *bnk = NULL; bool caughtMissingBank = false; - if((bank & ~static_cast<uint16_t>(OPL3::PercussionTag)) > 0) + if((bank & ~static_cast<uint16_t>(Synth::PercussionTag)) > 0) { - OPL3::BankMap::iterator b = m_synth.m_insBanks.find(bank); - if(b != m_synth.m_insBanks.end()) + Synth::BankMap::iterator b = synth.m_insBanks.find(bank); + if(b != synth.m_insBanks.end()) bnk = &b->second; if(bnk) ains = &bnk->ins[midiins]; @@ -352,9 +369,9 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) size_t fallback = bank & ~(size_t)0x7F; if(fallback != bank) { - OPL3::BankMap::iterator b = m_synth.m_insBanks.find(fallback); + Synth::BankMap::iterator b = synth.m_insBanks.find(fallback); caughtMissingBank = false; - if(b != m_synth.m_insBanks.end()) + if(b != synth.m_insBanks.end()) bnk = &b->second; if(bnk) ains = &bnk->ins[midiins]; @@ -373,15 +390,15 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) { hooks.onDebugMessage(hooks.onDebugMessage_userData, "[%i] Playing missing %s MIDI bank %i (patch %i)", - channel, text, (bank & ~static_cast<uint16_t>(OPL3::PercussionTag)), midiins); + channel, text, (bank & ~static_cast<uint16_t>(Synth::PercussionTag)), midiins); } } //Or fall back to first bank if((ains->flags & adlinsdata::Flag_NoSound) != 0) { - OPL3::BankMap::iterator b = m_synth.m_insBanks.find(bank & OPL3::PercussionTag); - if(b != m_synth.m_insBanks.end()) + Synth::BankMap::iterator b = synth.m_insBanks.find(bank & Synth::PercussionTag); + if(b != synth.m_insBanks.end()) bnk = &b->second; if(bnk) ains = &bnk->ins[midiins]; @@ -436,7 +453,7 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) voices[1].pseudo4op = pseudo_4op; #endif /* __WATCOMC__ */ - if((m_synth.m_rhythmMode == 1) && PercussionMap[midiins & 0xFF]) + if((synth.m_rhythmMode == 1) && PercussionMap[midiins & 0xFF]) voices[1] = voices[0];//i[1] = i[0]; bool isBlankNote = (ains->flags & adlinsdata::Flag_NoSound) != 0; @@ -476,7 +493,7 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) int32_t c = -1; int32_t bs = -0x7FFFFFFFl; - for(size_t a = 0; a < (size_t)m_synth.m_numChannels; ++a) + for(size_t a = 0; a < (size_t)synth.m_numChannels; ++a) { if(ccount == 1 && static_cast<int32_t>(a) == adlchannel[0]) continue; // ^ Don't use the same channel for primary&secondary @@ -486,7 +503,7 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) // Only use regular channels uint32_t expected_mode = 0; - if(m_synth.m_rhythmMode) + if(synth.m_rhythmMode) { if(m_cmfPercussionMode) expected_mode = channel < 11 ? 0 : (3 + channel - 11); // CMF @@ -494,7 +511,7 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) expected_mode = PercussionMap[midiins & 0xFF]; } - if(m_synth.m_channelCategory[a] != expected_mode) + if(synth.m_channelCategory[a] != expected_mode) continue; } else @@ -502,7 +519,7 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) if(ccount == 0) { // Only use four-op master channels - if(m_synth.m_channelCategory[a] != OPL3::ChanCat_4op_Master) + if(synth.m_channelCategory[a] != Synth::ChanCat_4op_Master) continue; } else @@ -637,6 +654,7 @@ void MIDIplay::realTime_ChannelAfterTouch(uint8_t channel, uint8_t atVal) void MIDIplay::realTime_Controller(uint8_t channel, uint8_t type, uint8_t value) { + Synth &synth = *m_synth; if(static_cast<size_t>(channel) > m_midiChannels.size()) channel = channel % 16; switch(type) @@ -773,7 +791,7 @@ void MIDIplay::realTime_Controller(uint8_t channel, uint8_t type, uint8_t value) break; case 103: - if(m_synth.m_musicMode == OPL3::MODE_CMF) + if(synth.m_musicMode == Synth::MODE_CMF) m_cmfPercussionMode = (value != 0); break; // CMF (ctrl 0x67) rhythm mode @@ -1069,11 +1087,12 @@ size_t MIDIplay::realTime_currentDevice(size_t track) void MIDIplay::realTime_rawOPL(uint8_t reg, uint8_t value) { + Synth &synth = *m_synth; if((reg & 0xF0) == 0xC0) value |= 0x30; //std::printf("OPL poke %02X, %02X\n", reg, value); //std::fflush(stdout); - m_synth.writeReg(0, reg, value); + synth.writeReg(0, reg, value); } #if defined(ADLMIDI_AUDIO_TICK_HANDLER) @@ -1100,6 +1119,7 @@ void MIDIplay::noteUpdate(size_t midCh, unsigned props_mask, int32_t select_adlchn) { + Synth &synth = *m_synth; MIDIchannel::NoteInfo &info = *i; const int16_t noteTone = info.noteTone; const double currentTone = info.currentTone; @@ -1126,7 +1146,7 @@ void MIDIplay::noteUpdate(size_t midCh, if(props_mask & Upd_Patch) { - m_synth.setPatch(c, ins.ains); + synth.setPatch(c, ins.ains); AdlChannel::LocationData *d = m_chipChannels[c].users_find_or_create(my_loc); if(d) // inserts if necessary { @@ -1162,10 +1182,10 @@ void MIDIplay::noteUpdate(size_t midCh, if(do_erase_user && m_chipChannels[c].users_empty()) { - m_synth.noteOff(c); + synth.noteOff(c); if(props_mask & Upd_Mute) // Mute the note { - m_synth.touchNote(c, 0); + synth.touchNote(c, 0); m_chipChannels[c].koff_time_until_neglible_us = 0; } else @@ -1191,7 +1211,7 @@ void MIDIplay::noteUpdate(size_t midCh, } if(props_mask & Upd_Pan) - m_synth.setPan(c, m_midiChannels[midCh].panning); + synth.setPan(c, m_midiChannels[midCh].panning); if(props_mask & Upd_Volume) { @@ -1208,10 +1228,10 @@ void MIDIplay::noteUpdate(size_t midCh, brightness *= 2; } - switch(m_synth.m_volumeScale) + switch(synth.m_volumeScale) { default: - case OPL3::VOLUME_Generic: + case Synth::VOLUME_Generic: { volume = vol * m_masterVolume * m_midiChannels[midCh].volume * m_midiChannels[midCh].expression; @@ -1231,7 +1251,7 @@ void MIDIplay::noteUpdate(size_t midCh, } break; - case OPL3::VOLUME_NATIVE: + case Synth::VOLUME_NATIVE: { volume = vol * m_midiChannels[midCh].volume * m_midiChannels[midCh].expression; // volume = volume * m_masterVolume / (127 * 127 * 127) / 2; @@ -1239,7 +1259,7 @@ void MIDIplay::noteUpdate(size_t midCh, } break; - case OPL3::VOLUME_DMX: + case Synth::VOLUME_DMX: { volume = 2 * (m_midiChannels[midCh].volume * m_midiChannels[midCh].expression * m_masterVolume / 16129) + 1; //volume = 2 * (Ch[MidCh].volume) + 1; @@ -1247,7 +1267,7 @@ void MIDIplay::noteUpdate(size_t midCh, } break; - case OPL3::VOLUME_APOGEE: + case Synth::VOLUME_APOGEE: { volume = (m_midiChannels[midCh].volume * m_midiChannels[midCh].expression * m_masterVolume / 16129); volume = ((64 * (vol + 0x80)) * volume) >> 15; @@ -1255,7 +1275,7 @@ void MIDIplay::noteUpdate(size_t midCh, } break; - case OPL3::VOLUME_9X: + case Synth::VOLUME_9X: { //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 * m_midiChannels[midCh].volume * m_midiChannels[midCh].expression * m_masterVolume / 2048383) >> 2)]; @@ -1264,7 +1284,7 @@ void MIDIplay::noteUpdate(size_t midCh, break; } - m_synth.touchNote(c, static_cast<uint8_t>(volume), static_cast<uint8_t>(brightness)); + synth.touchNote(c, static_cast<uint8_t>(volume), static_cast<uint8_t>(brightness)); /* DEBUG ONLY!!! static uint32_t max = 0; @@ -1302,7 +1322,7 @@ void MIDIplay::noteUpdate(size_t midCh, bend += static_cast<double>(vibrato) * m_midiChannels[midCh].vibdepth * std::sin(m_midiChannels[midCh].vibpos); #define BEND_COEFFICIENT 172.4387 - m_synth.noteOn(c, c_slave, BEND_COEFFICIENT * std::exp(0.057762265 * (currentTone + bend + phase))); + synth.noteOn(c, c_slave, BEND_COEFFICIENT * std::exp(0.057762265 * (currentTone + bend + phase))); #undef BEND_COEFFICIENT if(hooks.onNote) hooks.onNote(hooks.onNote_userData, c, noteTone, midiins, vol, midibend); @@ -1340,6 +1360,7 @@ void MIDIplay::setErrorString(const std::string &err) int64_t MIDIplay::calculateChipChannelGoodness(size_t c, const MIDIchannel::NoteInfo::Phys &ins) const { + Synth &synth = *m_synth; const AdlChannel &chan = m_chipChannels[c]; int64_t koff_ms = chan.koff_time_until_neglible_us / 1000; int64_t s = -koff_ms; @@ -1350,7 +1371,7 @@ int64_t MIDIplay::calculateChipChannelGoodness(size_t c, const MIDIchannel::Note s -= 40000; // If it's same instrument, better chance to get it when no free channels if(chan.recent_ins == ins) - s = (m_synth.m_musicMode == OPL3::MODE_CMF) ? 0 : -koff_ms; + s = (synth.m_musicMode == Synth::MODE_CMF) ? 0 : -koff_ms; return s; } @@ -1395,12 +1416,12 @@ int64_t MIDIplay::calculateChipChannelGoodness(size_t c, const MIDIchannel::Note // increase the score slightly. unsigned n_evacuation_stations = 0; - for(size_t c2 = 0; c2 < static_cast<size_t>(m_synth.m_numChannels); ++c2) + for(size_t c2 = 0; c2 < static_cast<size_t>(synth.m_numChannels); ++c2) { if(c2 == c) continue; - if(m_synth.m_channelCategory[c2] - != m_synth.m_channelCategory[c]) continue; + if(synth.m_channelCategory[c2] + != synth.m_channelCategory[c]) continue; for(AdlChannel::LocationData *m = m_chipChannels[c2].users_first; m; m = m->next) { @@ -1422,6 +1443,8 @@ void MIDIplay::prepareChipChannelForNewNote(size_t c, const MIDIchannel::NoteInf { if(m_chipChannels[c].users_empty()) return; // Nothing to do + Synth &synth = *m_synth; + //bool doing_arpeggio = false; for(AdlChannel::LocationData *jnext = m_chipChannels[c].users_first; jnext;) { @@ -1458,13 +1481,14 @@ void MIDIplay::prepareChipChannelForNewNote(size_t c, const MIDIchannel::NoteInf // Keyoff the channel so that it can be retriggered, // unless the new note will be introduced as just an arpeggio. if(m_chipChannels[c].users_empty()) - m_synth.noteOff(c); + synth.noteOff(c); } void MIDIplay::killOrEvacuate(size_t from_channel, AdlChannel::LocationData *j, MIDIplay::MIDIchannel::activenoteiterator i) { + Synth &synth = *m_synth; uint32_t maxChannels = ADL_MAX_CHIPS * 18; // Before killing the note, check if it can be @@ -1472,7 +1496,7 @@ void MIDIplay::killOrEvacuate(size_t from_channel, // instrument. This helps if e.g. all channels // are full of strings and we want to do percussion. // FIXME: This does not care about four-op entanglements. - for(uint32_t c = 0; c < m_synth.m_numChannels; ++c) + for(uint32_t c = 0; c < synth.m_numChannels; ++c) { uint16_t cs = static_cast<uint16_t>(c); @@ -1480,7 +1504,7 @@ void MIDIplay::killOrEvacuate(size_t from_channel, break; if(c == from_channel) continue; - if(m_synth.m_channelCategory[c] != m_synth.m_channelCategory[from_channel]) + if(synth.m_channelCategory[c] != synth.m_channelCategory[from_channel]) continue; AdlChannel &adlch = m_chipChannels[c]; @@ -1540,7 +1564,8 @@ void MIDIplay::panic() void MIDIplay::killSustainingNotes(int32_t midCh, int32_t this_adlchn, uint32_t sustain_type) { - uint32_t first = 0, last = m_synth.m_numChannels; + Synth &synth = *m_synth; + uint32_t first = 0, last = synth.m_numChannels; if(this_adlchn >= 0) { @@ -1572,13 +1597,14 @@ void MIDIplay::killSustainingNotes(int32_t midCh, int32_t this_adlchn, uint32_t // Keyoff the channel, if there are no users left. if(m_chipChannels[c].users_empty()) - m_synth.noteOff(c); + synth.noteOff(c); } } void MIDIplay::markSostenutoNotes(int32_t midCh) { - uint32_t first = 0, last = m_synth.m_numChannels; + Synth &synth = *m_synth; + uint32_t first = 0, last = synth.m_numChannels; for(uint32_t c = first; c < last; ++c) { if(m_chipChannels[c].users_empty()) @@ -1686,6 +1712,9 @@ void MIDIplay::updateArpeggio(double) // amount = amount of time passed { // If there is an adlib channel that has multiple notes // simulated on the same channel, arpeggio them. + + Synth &synth = *m_synth; + #if 0 const unsigned desired_arpeggio_rate = 40; // Hz (upper limit) # if 1 @@ -1708,7 +1737,7 @@ void MIDIplay::updateArpeggio(double) // amount = amount of time passed ++m_arpeggioCounter; - for(uint32_t c = 0; c < m_synth.m_numChannels; ++c) + for(uint32_t c = 0; c < synth.m_numChannels; ++c) { retry_arpeggio: if(c > uint32_t(std::numeric_limits<int32_t>::max())) @@ -1790,7 +1819,7 @@ void MIDIplay::describeChannels(char *str, char *attr, size_t size) if (!str || size <= 0) return; - OPL3 &synth = m_synth; + Synth &synth = *m_synth; uint32_t numChannels = synth.m_numChannels; uint32_t index = 0; @@ -1811,11 +1840,11 @@ void MIDIplay::describeChannels(char *str, char *attr, size_t size) { switch(synth.m_channelCategory[index]) { - case OPL3::ChanCat_Regular: + case Synth::ChanCat_Regular: str[index] = '+'; break; - case OPL3::ChanCat_4op_Master: - case OPL3::ChanCat_4op_Slave: + case Synth::ChanCat_4op_Master: + case Synth::ChanCat_4op_Slave: str[index] = '#'; break; default: // rhythm-mode percussion @@ -1843,7 +1872,7 @@ struct AdlInstrumentTester::Impl uint32_t cur_gm; uint32_t ins_idx; std::vector<uint32_t> adl_ins_list; - OPL3 *opl; + Synth *opl; MIDIplay *play; }; @@ -1855,7 +1884,7 @@ ADLMIDI_EXPORT AdlInstrumentTester::AdlInstrumentTester(ADL_MIDIPlayer *device) P->cur_gm = 0; P->ins_idx = 0; P->play = play; - P->opl = play ? &play->m_synth : NULL; + P->opl = play ? play->m_synth.get() : NULL; #else ADL_UNUSED(device); #endif @@ -1885,8 +1914,8 @@ ADLMIDI_EXPORT void AdlInstrumentTester::FindAdlList() ADLMIDI_EXPORT void AdlInstrumentTester::Touch(unsigned c, unsigned volume) // Volume maxes at 127*127*127 { #ifndef DISABLE_EMBEDDED_BANKS - OPL3 *opl = P->opl; - if(opl->m_volumeScale == OPL3::VOLUME_NATIVE) + Synth *opl = P->opl; + if(opl->m_volumeScale == Synth::VOLUME_NATIVE) opl->touchNote(c, static_cast<uint8_t>(volume * 127 / (127 * 127 * 127) / 2)); else { @@ -1905,7 +1934,7 @@ ADLMIDI_EXPORT void AdlInstrumentTester::DoNote(int note) { #ifndef DISABLE_EMBEDDED_BANKS MIDIplay *play = P->play; - OPL3 *opl = P->opl; + Synth *opl = P->opl; if(P->adl_ins_list.empty()) FindAdlList(); const unsigned meta = P->adl_ins_list[P->ins_idx]; const adlinsdata2 ains = adlinsdata2::from_adldata(::adlins[meta]); @@ -1971,7 +2000,7 @@ ADLMIDI_EXPORT void AdlInstrumentTester::NextGM(int offset) ADLMIDI_EXPORT void AdlInstrumentTester::NextAdl(int offset) { #ifndef DISABLE_EMBEDDED_BANKS - //OPL3 *opl = P->opl; + //Synth *opl = P->opl; if(P->adl_ins_list.empty()) FindAdlList(); const unsigned NumBanks = (unsigned)adl_getBanksCount(); P->ins_idx = (uint32_t)((int32_t)P->ins_idx + (int32_t)P->adl_ins_list.size() + offset) % (int32_t)P->adl_ins_list.size(); diff --git a/src/adlmidi_midiplay.hpp b/src/adlmidi_midiplay.hpp new file mode 100644 index 0000000..02a45a9 --- /dev/null +++ b/src/adlmidi_midiplay.hpp @@ -0,0 +1,1016 @@ +/* + * libADLMIDI is a free MIDI to WAV conversion library with OPL3 emulation + * + * Original ADLMIDI code: Copyright (c) 2010-2014 Joel Yliluoma <bisqwit@iki.fi> + * ADLMIDI Library API: Copyright (c) 2015-2018 Vitaly Novichkov <admin@wohlnet.ru> + * + * Library is based on the ADLMIDI, a MIDI player for Linux and Windows with OPL3 emulation: + * http://iki.fi/bisqwit/source/adlmidi.html + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef ADLMIDI_MIDIPLAY_HPP +#define ADLMIDI_MIDIPLAY_HPP + +#include "adldata.hh" +#include "adlmidi_private.hpp" +#include "adlmidi_ptr.hpp" + +/** + * @brief Hooks of the internal events + */ +struct MIDIEventHooks +{ + MIDIEventHooks() : + onNote(NULL), + onNote_userData(NULL), + onDebugMessage(NULL), + onDebugMessage_userData(NULL) + {} + + //! Note on/off hooks + typedef void (*NoteHook)(void *userdata, int adlchn, int note, int ins, int pressure, double bend); + NoteHook onNote; + void *onNote_userData; + + //! Library internal debug messages + typedef void (*DebugMessageHook)(void *userdata, const char *fmt, ...); + DebugMessageHook onDebugMessage; + void *onDebugMessage_userData; +}; + +class MIDIplay +{ + friend void adl_reset(struct ADL_MIDIPlayer*); +public: + explicit MIDIplay(unsigned long sampleRate = 22050); + ~MIDIplay(); + + void applySetup(); + + void partialReset(); + void resetMIDI(); + + /**********************Internal structures and classes**********************/ + + /** + * @brief Persistent settings for each MIDI channel + */ + struct MIDIchannel + { + //! LSB Bank number + uint8_t bank_lsb, + //! MSB Bank number + bank_msb; + //! Current patch number + uint8_t patch; + //! Volume level + uint8_t volume, + //! Expression level + expression; + //! Panning level + uint8_t panning, + //! Vibrato level + vibrato, + //! Channel aftertouch level + aftertouch; + //! Portamento time + uint16_t portamento; + //! Is Pedal sustain active + bool sustain; + //! Is Soft pedal active + bool softPedal; + //! Is portamento enabled + bool portamentoEnable; + //! Source note number used by portamento + int8_t portamentoSource; // note number or -1 + //! Portamento rate + double portamentoRate; + //! Per note Aftertouch values + uint8_t noteAftertouch[128]; + //! Is note aftertouch has any non-zero value + bool noteAfterTouchInUse; + //! Reserved + char _padding[6]; + //! Pitch bend value + int bend; + //! Pitch bend sensitivity + double bendsense; + //! Pitch bend sensitivity LSB value + int bendsense_lsb, + //! Pitch bend sensitivity MSB value + bendsense_msb; + //! Vibrato position value + double vibpos, + //! Vibrato speed value + vibspeed, + //! Vibrato depth value + vibdepth; + //! Vibrato delay time + int64_t vibdelay_us; + //! Last LSB part of RPN value received + uint8_t lastlrpn, + //! Last MSB poart of RPN value received + lastmrpn; + //! Interpret RPN value as NRPN + bool nrpn; + //! Brightness level + uint8_t brightness; + + //! Is melodic channel turned into percussion + bool is_xg_percussion; + + /** + * @brief Per-Note information + */ + struct NoteInfo + { + //! Note number + uint8_t note; + //! Is note active + bool active; + //! Current pressure + uint8_t vol; + //! Note vibrato (a part of Note Aftertouch feature) + uint8_t vibrato; + //! Tone selected on noteon: + 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; + //! Is note the percussion instrument + bool isPercussion; + //! Note that plays missing instrument. Doesn't using any chip channels + bool isBlank; + //! Patch selected + const adlinsdata2 *ains; + enum + { + MaxNumPhysChans = 2, + MaxNumPhysItemCount = MaxNumPhysChans + }; + + /** + * @brief Reference to currently using chip channel + */ + struct Phys + { + //! Destination chip channel + uint16_t chip_chan; + //! ins, inde to adl[] + adldata ains; + //! Is this voice must be detunable? + bool pseudo4op; + + void assign(const Phys &oth) + { + ains = oth.ains; + pseudo4op = oth.pseudo4op; + } + bool operator==(const Phys &oth) const + { + return (ains == oth.ains) && (pseudo4op == oth.pseudo4op); + } + bool operator!=(const Phys &oth) const + { + return !operator==(oth); + } + }; + + //! List of OPL3 channels it is currently occupying. + Phys chip_channels[MaxNumPhysItemCount]; + //! Count of used channels. + unsigned chip_channels_count; + + Phys *phys_find(unsigned chip_chan) + { + Phys *ph = NULL; + for(unsigned i = 0; i < chip_channels_count && !ph; ++i) + if(chip_channels[i].chip_chan == chip_chan) + ph = &chip_channels[i]; + return ph; + } + Phys *phys_find_or_create(uint16_t chip_chan) + { + Phys *ph = phys_find(chip_chan); + if(!ph) { + if(chip_channels_count < MaxNumPhysItemCount) { + ph = &chip_channels[chip_channels_count++]; + ph->chip_chan = chip_chan; + } + } + return ph; + } + Phys *phys_ensure_find_or_create(uint16_t chip_chan) + { + Phys *ph = phys_find_or_create(chip_chan); + assert(ph); + return ph; + } + void phys_erase_at(const Phys *ph) + { + intptr_t pos = ph - chip_channels; + assert(pos < static_cast<intptr_t>(chip_channels_count)); + for(intptr_t i = pos + 1; i < static_cast<intptr_t>(chip_channels_count); ++i) + chip_channels[i - 1] = chip_channels[i]; + --chip_channels_count; + } + void phys_erase(unsigned chip_chan) + { + Phys *ph = phys_find(chip_chan); + if(ph) + phys_erase_at(ph); + } + }; + + //! Reserved + char _padding2[5]; + //! Count of gliding notes in this channel + unsigned gliding_note_count; + + //! Active notes in the channel + NoteInfo activenotes[128]; + + struct activenoteiterator + { + explicit activenoteiterator(NoteInfo *info = NULL) + : ptr(info) {} + activenoteiterator &operator++() + { + if(ptr->note == 127) + ptr = NULL; + else + for(++ptr; ptr && !ptr->active;) + ptr = (ptr->note == 127) ? NULL : (ptr + 1); + return *this; + } + activenoteiterator operator++(int) + { + activenoteiterator pos = *this; + ++*this; + return pos; + } + NoteInfo &operator*() const + { return *ptr; } + NoteInfo *operator->() const + { return ptr; } + bool operator==(activenoteiterator other) const + { return ptr == other.ptr; } + bool operator!=(activenoteiterator other) const + { return ptr != other.ptr; } + operator NoteInfo *() const + { return ptr; } + private: + NoteInfo *ptr; + }; + + activenoteiterator activenotes_begin() + { + activenoteiterator it(activenotes); + return (it->active) ? it : ++it; + } + + activenoteiterator activenotes_find(uint8_t note) + { + assert(note < 128); + return activenoteiterator( + activenotes[note].active ? &activenotes[note] : NULL); + } + + activenoteiterator activenotes_ensure_find(uint8_t note) + { + activenoteiterator it = activenotes_find(note); + assert(it); + return it; + } + + std::pair<activenoteiterator, bool> activenotes_insert(uint8_t note) + { + assert(note < 128); + NoteInfo &info = activenotes[note]; + bool inserted = !info.active; + if(inserted) info.active = true; + return std::pair<activenoteiterator, bool>(activenoteiterator(&info), inserted); + } + + void activenotes_erase(activenoteiterator pos) + { + if(pos) + pos->active = false; + } + + bool activenotes_empty() + { + return !activenotes_begin(); + } + + void activenotes_clear() + { + for(uint8_t i = 0; i < 128; ++i) { + activenotes[i].note = i; + activenotes[i].active = false; + } + } + + /** + * @brief Reset channel into initial state + */ + void reset() + { + resetAllControllers(); + patch = 0; + vibpos = 0; + bank_lsb = 0; + bank_msb = 0; + lastlrpn = 0; + lastmrpn = 0; + nrpn = false; + is_xg_percussion = false; + } + + /** + * @brief Reset all MIDI controllers into initial state + */ + void resetAllControllers() + { + bend = 0; + bendsense_msb = 2; + bendsense_lsb = 0; + updateBendSensitivity(); + volume = 100; + expression = 127; + sustain = false; + softPedal = false; + vibrato = 0; + aftertouch = 0; + std::memset(noteAftertouch, 0, 128); + noteAfterTouchInUse = false; + vibspeed = 2 * 3.141592653 * 5.0; + vibdepth = 0.5 / 127; + vibdelay_us = 0; + panning = 64; + portamento = 0; + portamentoEnable = false; + portamentoSource = -1; + portamentoRate = HUGE_VAL; + brightness = 127; + } + + /** + * @brief Has channel vibrato to process + * @return + */ + bool hasVibrato() + { + return (vibrato > 0) || (aftertouch > 0) || noteAfterTouchInUse; + } + + /** + * @brief Commit pitch bend sensitivity value from MSB and LSB + */ + void updateBendSensitivity() + { + int cent = bendsense_msb * 128 + bendsense_lsb; + bendsense = cent * (1.0 / (128 * 8192)); + } + + MIDIchannel() + { + activenotes_clear(); + gliding_note_count = 0; + reset(); + } + }; + + /** + * @brief Additional information about OPL3 channels + */ + struct AdlChannel + { + struct Location + { + uint16_t MidCh; + uint8_t note; + bool operator==(const Location &l) const + { return MidCh == l.MidCh && note == l.note; } + bool operator!=(const Location &l) const + { return !operator==(l); } + }; + struct LocationData + { + LocationData *prev, *next; + Location loc; + enum { + Sustain_None = 0x00, + Sustain_Pedal = 0x01, + Sustain_Sostenuto = 0x02, + Sustain_ANY = Sustain_Pedal | Sustain_Sostenuto + }; + uint32_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; + //! Timeout until note will be allowed to be killed by channel manager while it is on + int64_t kon_time_until_neglible_us; + int64_t vibdelay_us; + }; + + //! Time left until sounding will be muted after key off + int64_t koff_time_until_neglible_us; + + //! Recently passed instrument, improves a goodness of released but busy channel when matching + MIDIchannel::NoteInfo::Phys recent_ins; + + enum { users_max = 128 }; + LocationData *users_first, *users_free_cells; + LocationData users_cells[users_max]; + unsigned users_size; + + bool users_empty() const; + LocationData *users_find(Location loc); + LocationData *users_allocate(); + LocationData *users_find_or_create(Location loc); + LocationData *users_insert(const LocationData &x); + void users_erase(LocationData *user); + void users_clear(); + void users_assign(const LocationData *users, size_t count); + + // For channel allocation: + AdlChannel(): koff_time_until_neglible_us(0) + { + users_clear(); + std::memset(&recent_ins, 0, sizeof(MIDIchannel::NoteInfo::Phys)); + } + + AdlChannel(const AdlChannel &oth): koff_time_until_neglible_us(oth.koff_time_until_neglible_us) + { + if(oth.users_first) + { + users_first = NULL; + users_assign(oth.users_first, oth.users_size); + } + else + users_clear(); + } + + AdlChannel &operator=(const AdlChannel &oth) + { + koff_time_until_neglible_us = oth.koff_time_until_neglible_us; + users_assign(oth.users_first, oth.users_size); + return *this; + } + + /** + * @brief Increases age of active note in microseconds time + * @param us Amount time in microseconds + */ + void addAge(int64_t us); + }; + +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER + /** + * @brief MIDI files player sequencer + */ + AdlMIDI_UPtr<MidiSequencer> m_sequencer; + + /** + * @brief Interface between MIDI sequencer and this library + */ + AdlMIDI_UPtr<BW_MidiRtInterface> m_sequencerInterface; + + /** + * @brief Initialize MIDI sequencer interface + */ + void initSequencerInterface(); +#endif //ADLMIDI_DISABLE_MIDI_SEQUENCER + + struct Setup + { + int emulator; + bool runAtPcmRate; + unsigned int bankId; + int numFourOps; + unsigned int numChips; + int deepTremoloMode; + int deepVibratoMode; + int rhythmMode; + bool logarithmicVolumes; + int volumeScaleModel; + //unsigned int SkipForward; + int scaleModulators; + bool fullRangeBrightnessCC74; + + double delay; + double carry; + + /* The lag between visual content and audio content equals */ + /* the sum of these two buffers. */ + double mindelay; + double maxdelay; + + /* For internal usage */ + ssize_t tick_skip_samples_delay; /* Skip tick processing after samples count. */ + /* For internal usage */ + + unsigned long PCM_RATE; + }; + + /** + * @brief MIDI Marker entry + */ + struct MIDI_MarkerEntry + { + //! Label of marker + std::string label; + //! Absolute position in seconds + double pos_time; + //! Absolute position in ticks in the track + uint64_t pos_ticks; + }; + + //! Available MIDI Channels + std::vector<MIDIchannel> m_midiChannels; + + //! CMF Rhythm mode + bool m_cmfPercussionMode; + + //! Master volume, controlled via SysEx + uint8_t m_masterVolume; + + //! SysEx device ID + uint8_t m_sysExDeviceId; + + /** + * @brief MIDI Synthesizer mode + */ + enum SynthMode + { + Mode_GM = 0x00, + Mode_GS = 0x01, + Mode_XG = 0x02, + Mode_GM2 = 0x04 + }; + //! MIDI Synthesizer mode + uint32_t m_synthMode; + + //! Installed function hooks + MIDIEventHooks hooks; + +private: + //! Per-track MIDI devices map + std::map<std::string, size_t> m_midiDevices; + //! Current MIDI device per track + std::map<size_t /*track*/, size_t /*channel begin index*/> m_currentMidiDevice; + + //! Padding to fix CLanc code model's warning + char _padding[7]; + + //! Chip channels map + std::vector<AdlChannel> m_chipChannels; + //! Counter of arpeggio processing + size_t m_arpeggioCounter; + +#if defined(ADLMIDI_AUDIO_TICK_HANDLER) + //! Audio tick counter + uint32_t m_audioTickCounter; +#endif + + //! Local error string + std::string errorStringOut; + + //! Missing instruments catches + std::set<size_t> caugh_missing_instruments; + //! Missing melodic banks catches + std::set<size_t> caugh_missing_banks_melodic; + //! Missing percussion banks catches + std::set<size_t> caugh_missing_banks_percussion; + +public: + + const std::string &getErrorString(); + void setErrorString(const std::string &err); + + //! OPL3 Chip manager + AdlMIDI_UPtr<Synth> m_synth; + + //! Generator output buffer + int32_t m_outBuf[1024]; + + //! Synthesizer setup + Setup m_setup; + + /** + * @brief Load custom bank from file + * @param filename Path to bank file + * @return true on succes + */ + bool LoadBank(const std::string &filename); + + /** + * @brief Load custom bank from memory block + * @param data Pointer to memory block where raw bank file is stored + * @param size Size of given memory block + * @return true on succes + */ + bool LoadBank(const void *data, size_t size); + + /** + * @brief Load custom bank from opened FileAndMemReader class + * @param fr Instance with opened file + * @return true on succes + */ + bool LoadBank(FileAndMemReader &fr); + +#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER + /** + * @brief MIDI file loading pre-process + * @return true on success, false on failure + */ + bool LoadMIDI_pre(); + + /** + * @brief MIDI file loading post-process + * @return true on success, false on failure + */ + bool LoadMIDI_post(); + + /** + * @brief Load music file from a file + * @param filename Path to music file + * @return true on success, false on failure + */ + + 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); + + /** + * @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); +#endif //ADLMIDI_DISABLE_MIDI_SEQUENCER + + /** + * @brief Process extra iterators like vibrato or arpeggio + * @param s seconds since last call + */ + void TickIterators(double s); + + + /* RealTime event triggers */ + /** + * @brief Reset state of all channels + */ + void realTime_ResetState(); + + /** + * @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 + */ + bool realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity); + + /** + * @brief Note Off event + * @param channel MIDI channel + * @param note Note key (from 0 to 127) + */ + void realTime_NoteOff(uint8_t channel, uint8_t note); + + /** + * @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) + */ + void realTime_NoteAfterTouch(uint8_t channel, uint8_t note, uint8_t atVal); + + /** + * @brief Channel aftertouch event + * @param channel MIDI channel + * @param atVal After-Touch level (from 0 to 127) + */ + void realTime_ChannelAfterTouch(uint8_t channel, uint8_t atVal); + + /** + * @brief Controller Change event + * @param channel MIDI channel + * @param type Type of controller + * @param value Value of the controller (from 0 to 127) + */ + 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 msb MSB value of bank number + */ + void realTime_BankChangeMSB(uint8_t channel, uint8_t msb); + + /** + * @brief Bank Change (united value) + * @param channel MIDI channel + * @param bank Bank number value + */ + void realTime_BankChange(uint8_t channel, uint16_t bank); + + /** + * @brief Sets the Device identifier + * @param id 7-bit Device identifier + */ + void setDeviceId(uint8_t id); + + /** + * @brief System Exclusive message + * @param msg Raw SysEx Message + * @param size Length of SysEx message + * @return true if message was passed successfully. False on any errors + */ + bool realTime_SysEx(const uint8_t *msg, size_t size); + + /** + * @brief Turn off all notes and mute the sound of releasing notes + */ + void realTime_panic(); + + /** + * @brief Device switch (to extend 16-channels limit of MIDI standard) + * @param track MIDI track index + * @param data Device name + * @param length Length of device name string + */ + void realTime_deviceSwitch(size_t track, const char *data, size_t length); + + /** + * @brief Currently selected device index + * @param track MIDI track index + * @return Multiple 16 value + */ + size_t realTime_currentDevice(size_t track); + + /** + * @brief Send raw OPL chip command + * @param reg OPL Register + * @param value Value to write + */ + void realTime_rawOPL(uint8_t reg, uint8_t value); + +#if defined(ADLMIDI_AUDIO_TICK_HANDLER) + // Audio rate tick handler + void AudioTick(uint32_t chipId, uint32_t rate); +#endif + +private: + /** + * @brief Hardware manufacturer (Used for SysEx) + */ + enum + { + Manufacturer_Roland = 0x41, + Manufacturer_Yamaha = 0x43, + Manufacturer_UniversalNonRealtime = 0x7E, + Manufacturer_UniversalRealtime = 0x7F + }; + + /** + * @brief Roland Mode (Used for SysEx) + */ + enum + { + RolandMode_Request = 0x11, + RolandMode_Send = 0x12 + }; + + /** + * @brief Device model (Used for SysEx) + */ + enum + { + RolandModel_GS = 0x42, + RolandModel_SC55 = 0x45, + YamahaModel_XG = 0x4C + }; + + /** + * @brief Process generic SysEx events + * @param dev Device ID + * @param realtime Is real-time event + * @param data Raw SysEx data + * @param size Size of given SysEx data + * @return true when event was successfully handled + */ + bool doUniversalSysEx(unsigned dev, bool realtime, const uint8_t *data, size_t size); + + /** + * @brief Process events specific to Roland devices + * @param dev Device ID + * @param data Raw SysEx data + * @param size Size of given SysEx data + * @return true when event was successfully handled + */ + bool doRolandSysEx(unsigned dev, const uint8_t *data, size_t size); + + /** + * @brief Process events specific to Yamaha devices + * @param dev Device ID + * @param data Raw SysEx data + * @param size Size of given SysEx data + * @return true when event was successfully handled + */ + bool doYamahaSysEx(unsigned dev, const uint8_t *data, size_t size); + +private: + /** + * @brief Note Update properties + */ + enum + { + Upd_Patch = 0x1, + Upd_Pan = 0x2, + Upd_Volume = 0x4, + Upd_Pitch = 0x8, + Upd_All = Upd_Pan + Upd_Volume + Upd_Pitch, + Upd_Off = 0x20, + Upd_Mute = 0x40, + Upd_OffMute = Upd_Off + Upd_Mute + }; + + /** + * @brief Update active note + * @param MidCh MIDI Channel where note is processing + * @param i Iterator that points to active note in the MIDI channel + * @param props_mask Properties to update + * @param select_adlchn Specify chip channel, or -1 - all chip channels used by the note + */ + void noteUpdate(size_t midCh, + MIDIchannel::activenoteiterator i, + unsigned props_mask, + int32_t select_adlchn = -1); + + /** + * @brief Update all notes in specified MIDI channel + * @param midCh MIDI channel to update all notes in it + * @param props_mask Properties to update + */ + void noteUpdateAll(size_t midCh, unsigned props_mask); + + /** + * @brief Determine how good a candidate this adlchannel would be for playing a note from this instrument. + * @param c Wanted chip channel + * @param ins Instrument wanted to be used in this channel + * @return Calculated coodness points + */ + int64_t calculateChipChannelGoodness(size_t c, const MIDIchannel::NoteInfo::Phys &ins) const; + + /** + * @brief A new note will be played on this channel using this instrument. + * @param c Wanted chip channel + * @param ins Instrument wanted to be used in this channel + * Kill existing notes on this channel (or don't, if we do arpeggio) + */ + void prepareChipChannelForNewNote(size_t c, const MIDIchannel::NoteInfo::Phys &ins); + + /** + * @brief Kills note that uses wanted channel. When arpeggio is possible, note is evaluating to another channel + * @param from_channel Wanted chip channel + * @param j Chip channel instance + * @param i MIDI Channel active note instance + */ + void killOrEvacuate( + size_t from_channel, + AdlChannel::LocationData *j, + MIDIchannel::activenoteiterator i); + + /** + * @brief Off all notes and silence sound + */ + void panic(); + + /** + * @brief Kill note, sustaining by pedal or sostenuto + * @param MidCh MIDI channel, -1 - all MIDI channels + * @param this_adlchn Chip channel, -1 - all chip channels + * @param sustain_type Type of systain to process + */ + void killSustainingNotes(int32_t midCh = -1, + int32_t this_adlchn = -1, + uint32_t sustain_type = AdlChannel::LocationData::Sustain_ANY); + /** + * @brief Find active notes and mark them as sostenuto-sustained + * @param MidCh MIDI channel, -1 - all MIDI channels + */ + void markSostenutoNotes(int32_t midCh = -1); + + /** + * @brief Set RPN event value + * @param MidCh MIDI channel + * @param value 1 byte part of RPN value + * @param MSB is MSB or LSB part of value + */ + void setRPN(size_t midCh, unsigned value, bool MSB); + + /** + * @brief Update portamento setup in MIDI channel + * @param midCh MIDI channel where portamento needed to be updated + */ + void updatePortamento(size_t midCh); + + /** + * @brief Off the note + * @param midCh MIDI channel + * @param note Note to off + */ + void noteOff(size_t midCh, uint8_t note); + + /** + * @brief Update processing of vibrato to amount of seconds + * @param amount Amount value in seconds + */ + void updateVibrato(double amount); + + /** + * @brief Update auto-arpeggio + * @param amount Amount value in seconds [UNUSED] + */ + void updateArpeggio(double /*amount*/); + + /** + * @brief Update Portamento gliding to amount of seconds + * @param amount Amount value in seconds + */ + void updateGlide(double amount); + +public: + /** + * @brief Checks was device name used or not + * @param name Name of MIDI device + * @return Offset of the MIDI Channels, multiple to 16 + */ + size_t chooseDevice(const std::string &name); + + /** + * @brief Gets a textual description of the state of chip channels + * @param text character pointer for text + * @param attr character pointer for text attributes + * @param size number of characters available to write + */ + void describeChannels(char *text, char *attr, size_t size); +}; + +#endif // ADLMIDI_MIDIPLAY_HPP diff --git a/src/adlmidi_opl3.cpp b/src/adlmidi_opl3.cpp index f3672d3..0f977e2 100644 --- a/src/adlmidi_opl3.cpp +++ b/src/adlmidi_opl3.cpp @@ -21,6 +21,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include "adlmidi_opl3.hpp" #include "adlmidi_private.hpp" #include <stdlib.h> #include <cassert> @@ -148,6 +149,13 @@ static const uint16_t g_channelsMap[23] = Ports: ??? */ +enum +{ + OPL_PANNING_LEFT = 0x10, + OPL_PANNING_RIGHT = 0x20, + OPL_PANNING_BOTH = 0x30 +}; + static adlinsdata2 makeEmptyInstrument() { adlinsdata2 ins; @@ -181,6 +189,10 @@ OPL3::OPL3() : #endif } +OPL3::~OPL3() +{ +} + bool OPL3::setupLocked() { return (m_musicMode == MODE_CMF || diff --git a/src/adlmidi_opl3.hpp b/src/adlmidi_opl3.hpp new file mode 100644 index 0000000..c5b7fcd --- /dev/null +++ b/src/adlmidi_opl3.hpp @@ -0,0 +1,321 @@ +/* + * libADLMIDI is a free MIDI to WAV conversion library with OPL3 emulation + * + * Original ADLMIDI code: Copyright (c) 2010-2014 Joel Yliluoma <bisqwit@iki.fi> + * ADLMIDI Library API: Copyright (c) 2015-2018 Vitaly Novichkov <admin@wohlnet.ru> + * + * Library is based on the ADLMIDI, a MIDI player for Linux and Windows with OPL3 emulation: + * http://iki.fi/bisqwit/source/adlmidi.html + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef ADLMIDI_OPL3_HPP +#define ADLMIDI_OPL3_HPP + +#include "adldata.hh" +#include "adlmidi_ptr.hpp" +#include "adlmidi_private.hpp" +#include "adlmidi_bankmap.h" + +/** + * @brief OPL3 Chip management class + */ +class OPL3 +{ + friend class MIDIplay; + friend class AdlInstrumentTester; + friend int adlCalculateFourOpChannels(MIDIplay *play, bool silent); +public: + enum + { + PercussionTag = 1 << 15, + CustomBankTag = 0xFFFFFFFF + }; + + //! Total number of chip channels between all running emulators + uint32_t m_numChannels; + //! Just a padding. Reserved. + char _padding[4]; +#ifndef ADLMIDI_HW_OPL + //! Running chip emulators + std::vector<AdlMIDI_SPtr<OPLChipBase > > m_chips; +#endif + +private: + //! Cached patch data, needed by Touch() + std::vector<adldata> m_insCache; + //! Value written to B0, cached, needed by NoteOff. + /*! Contains Key on/off state, octave block and frequency number values + */ + std::vector<uint32_t> m_keyBlockFNumCache; + //! Cached BD registry value (flags register: DeepTremolo, DeepVibrato, and RhythmMode) + std::vector<uint32_t> m_regBD; + +public: + /** + * @brief MIDI bank entry + */ + struct Bank + { + //! MIDI Bank instruments + adlinsdata2 ins[128]; + }; + typedef BasicBankMap<Bank> BankMap; + //! MIDI bank instruments data + BankMap m_insBanks; + //! MIDI bank-wide setup + AdlBankSetup m_insBankSetup; + +public: + //! Blank instrument template + static const adlinsdata2 m_emptyInstrument; + //! Total number of running concurrent emulated chips + uint32_t m_numChips; + //! Currently running embedded bank number. "CustomBankTag" means usign of the custom bank. + uint32_t m_embeddedBank; + //! Total number of needed four-operator channels in all running chips + uint32_t m_numFourOps; + //! Turn global Deep Tremolo mode on + bool m_deepTremoloMode; + //! Turn global Deep Vibrato mode on + bool m_deepVibratoMode; + //! Use Rhythm Mode percussions + bool m_rhythmMode; + //! Carriers-only are scaled by default by volume level. This flag will tell to scale modulators too. + bool m_scaleModulators; + //! Run emulator at PCM rate if that possible. Reduces sounding accuracy, but decreases CPU usage on lower rates. + bool m_runAtPcmRate; + //! Enable soft panning + bool m_softPanning; + + //! Just a padding. Reserved. + char _padding2[3]; + + /** + * @brief Music playing mode + */ + enum MusicMode + { + //! MIDI mode + MODE_MIDI, + //! Id-Software Music mode + MODE_IMF, + //! Creative Music Files mode + MODE_CMF, + //! EA-MUS (a.k.a. RSXX) mode + MODE_RSXX + } m_musicMode; + + /** + * @brief Volume models enum + */ + enum VolumesScale + { + //! Generic volume model (linearization of logarithmic scale) + VOLUME_Generic, + //! OPL3 native logarithmic scale + VOLUME_NATIVE, + //! DMX volume scale logarithmic table + VOLUME_DMX, + //! Apoge Sound System volume scaling model + VOLUME_APOGEE, + //! Windows 9x driver volume scale table + VOLUME_9X + } m_volumeScale; + + //! Reserved + char _padding3[8]; + + /** + * @brief Channel categiry enumeration + */ + enum ChanCat + { + //! Regular melodic/percussion channel + ChanCat_Regular = 0, + //! Four-op master + ChanCat_4op_Master = 1, + //! Four-op slave + ChanCat_4op_Slave = 2, + //! Rhythm-mode Bass drum + ChanCat_Rhythm_Bass = 3, + //! Rhythm-mode Snare drum + ChanCat_Rhythm_Snare = 4, + //! Rhythm-mode Tom-Tom + ChanCat_Rhythm_Tom = 5, + //! Rhythm-mode Cymbal + ChanCat_Rhythm_Cymbal = 6, + //! Rhythm-mode Hi-Hat + ChanCat_Rhythm_HiHat = 7, + //! Rhythm-mode Slave channel + ChanCat_Rhythm_Slave = 8 + }; + + //! Category of the channel + /*! 1 = quad-master, 2 = quad-slave, 0 = regular + 3 = percussion BassDrum + 4 = percussion Snare + 5 = percussion Tom + 6 = percussion Crash cymbal + 7 = percussion Hihat + 8 = percussion slave + */ + std::vector<uint32_t> m_channelCategory; + + + /** + * @brief C.O. Constructor + */ + OPL3(); + + /** + * @brief C.O. Destructor + */ + ~OPL3(); + + /** + * @brief Checks are setup locked to be changed on the fly or not + * @return true when setup on the fly is locked + */ + bool setupLocked(); + + /** + * @brief Choose one of embedded banks + * @param bank ID of the bank + */ + void setEmbeddedBank(uint32_t bank); + + /** + * @brief Write data to OPL3 chip register + * @param chip Index of emulated chip. In hardware OPL3 builds, this parameter is ignored + * @param address Register address to write + * @param value Value to write + */ + void writeReg(size_t chip, uint16_t address, uint8_t value); + + /** + * @brief Write data to OPL3 chip register + * @param chip Index of emulated chip. In hardware OPL3 builds, this parameter is ignored + * @param address Register address to write + * @param value Value to write + */ + void writeRegI(size_t chip, uint32_t address, uint32_t value); + + /** + * @brief Write to soft panning control of OPL3 chip emulator + * @param chip Index of emulated chip. + * @param address Register of channel to write + * @param value Value to write + */ + void writePan(size_t chip, uint32_t address, uint32_t value); + + /** + * @brief Off the note in specified chip channel + * @param c Channel of chip (Emulated chip choosing by next formula: [c = ch + (chipId * 23)]) + */ + void noteOff(size_t c); + + /** + * @brief On the note in specified chip channel with specified frequency of the tone + * @param c1 Channel of chip [or master 4-op channel] (Emulated chip choosing by next formula: [c = ch + (chipId * 23)]) + * @param c2 Slave 4-op channel of chip, unused for 2op (Emulated chip choosing by next formula: [c = ch + (chipId * 23)]) + * @param hertz Frequency of the tone in hertzes + */ + void noteOn(size_t c1, size_t c2, double hertz); + + /** + * @brief Change setup of instrument in specified chip channel + * @param c Channel of chip (Emulated chip choosing by next formula: [c = ch + (chipId * 23)]) + * @param volume Volume level (from 0 to 63) + * @param brightness CC74 Brightness level (from 0 to 127) + */ + void touchNote(size_t c, uint8_t volume, uint8_t brightness = 127); + + /** + * @brief Set the instrument into specified chip channel + * @param c Channel of chip (Emulated chip choosing by next formula: [c = ch + (chipId * 23)]) + * @param instrument Instrument data to set into the chip channel + */ + void setPatch(size_t c, const adldata &instrument); + + /** + * @brief Set panpot position + * @param c Channel of chip (Emulated chip choosing by next formula: [c = ch + (chipId * 23)]) + * @param value 3-bit panpot value + */ + void setPan(size_t c, uint8_t value); + + /** + * @brief Shut up all chip channels + */ + void silenceAll(); + + /** + * @brief Commit updated flag states to chip registers + */ + void updateChannelCategories(); + + /** + * @brief commit deepTremolo and deepVibrato flags + */ + void commitDeepFlags(); + + /** + * @brief Set the volume scaling model + * @param volumeModel Type of volume scale model scale + */ + void setVolumeScaleModel(ADLMIDI_VolumeModels volumeModel); + + /** + * @brief Get the volume scaling model + */ + ADLMIDI_VolumeModels getVolumeScaleModel(); + + #ifndef ADLMIDI_HW_OPL + /** + * @brief Clean up all running emulated chip instances + */ + void clearChips(); + #endif + + /** + * @brief Reset chip properties and initialize them + * @param emulator Type of chip emulator + * @param PCM_RATE Output sample rate to generate on output + * @param audioTickHandler PCM-accurate clock hook + */ + void reset(int emulator, unsigned long PCM_RATE, void *audioTickHandler); +}; + +/** + * @brief Check emulator availability + * @param emulator Emulator ID (ADL_Emulator) + * @return true when emulator is available + */ +extern bool adl_isEmulatorAvailable(int emulator); + +/** + * @brief Find highest emulator + * @return The ADL_Emulator enum value which contains ID of highest emulator + */ +extern int adl_getHighestEmulator(); + +/** + * @brief Find lowest emulator + * @return The ADL_Emulator enum value which contains ID of lowest emulator + */ +extern int adl_getLowestEmulator(); + +#endif // ADLMIDI_OPL3_HPP diff --git a/src/adlmidi_private.cpp b/src/adlmidi_private.cpp index 4e8e488..b2c9b8d 100644 --- a/src/adlmidi_private.cpp +++ b/src/adlmidi_private.cpp @@ -21,6 +21,8 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include "adlmidi_midiplay.hpp" +#include "adlmidi_opl3.hpp" #include "adlmidi_private.hpp" std::string ADLMIDI_ErrorString; @@ -36,20 +38,21 @@ void adl_audioTickHandler(void *instance, uint32_t chipId, uint32_t rate) int adlCalculateFourOpChannels(MIDIplay *play, bool silent) { + Synth &synth = *play->m_synth; size_t n_fourop[2] = {0, 0}, n_total[2] = {0, 0}; //Automatically calculate how much 4-operator channels is necessary #ifndef DISABLE_EMBEDDED_BANKS - if(play->m_synth.m_embeddedBank == OPL3::CustomBankTag) + if(synth.m_embeddedBank == Synth::CustomBankTag) #endif { //For custom bank - OPL3::BankMap::iterator it = play->m_synth.m_insBanks.begin(); - OPL3::BankMap::iterator end = play->m_synth.m_insBanks.end(); + Synth::BankMap::iterator it = synth.m_insBanks.begin(); + Synth::BankMap::iterator end = synth.m_insBanks.end(); for(; it != end; ++it) { size_t bank = it->first; - size_t div = (bank & OPL3::PercussionTag) ? 1 : 0; + size_t div = (bank & Synth::PercussionTag) ? 1 : 0; for(size_t i = 0; i < 128; ++i) { adlinsdata2 &ins = it->second.ins[i]; @@ -99,10 +102,10 @@ int adlCalculateFourOpChannels(MIDIplay *play, bool silent) : (play->m_setup.NumCards == 1 ? 1 : play->m_setup.NumCards * 4); */ - play->m_synth.m_numFourOps = static_cast<unsigned>(numFourOps * play->m_synth.m_numChips); + synth.m_numFourOps = static_cast<unsigned>(numFourOps * synth.m_numChips); // Update channel categories and set up four-operator channels if(!silent) - play->m_synth.updateChannelCategories(); + synth.updateChannelCategories(); return 0; } diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp index 59ba555..62b6223 100644 --- a/src/adlmidi_private.hpp +++ b/src/adlmidi_private.hpp @@ -49,7 +49,6 @@ typedef __int64 ssize_t; # else typedef __int32 ssize_t; # endif -# define NOMINMAX 1 //Don't override std::min and std::max # else # ifdef _WIN64 typedef int64_t ssize_t; @@ -130,18 +129,20 @@ typedef int32_t ssize_t; #define INT32_MAX 0x7fffffff #endif -#include "file_reader.hpp" +class FileAndMemReader; #ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER // Rename class to avoid ABI collisions #define BW_MidiSequencer AdlMidiSequencer -#include "midi_sequencer.hpp" +class BW_MidiSequencer; typedef BW_MidiSequencer MidiSequencer; +typedef struct BW_MidiRtInterface BW_MidiRtInterface; #endif//ADLMIDI_DISABLE_MIDI_SEQUENCER -#ifndef ADLMIDI_HW_OPL -#include "chips/opl_chip_base.h" -#endif +class OPL3; +class OPLChipBase; + +typedef class OPL3 Synth; #include "adldata.hh" @@ -153,13 +154,10 @@ typedef BW_MidiSequencer MidiSequencer; #endif #include "adlmidi_ptr.hpp" -#include "adlmidi_bankmap.h" -#define ADL_UNUSED(x) (void)x +class MIDIplay; -#define OPL_PANNING_LEFT 0x10 -#define OPL_PANNING_RIGHT 0x20 -#define OPL_PANNING_BOTH 0x30 +#define ADL_UNUSED(x) (void)x #ifdef ADLMIDI_HW_OPL #define ADL_MAX_CHIPS 1 @@ -218,1280 +216,6 @@ inline int32_t adl_cvtU32(int32_t x) return (uint32_t)adl_cvtS32(x) - (uint32_t)INT32_MIN; } -struct ADL_MIDIPlayer; -class MIDIplay; -/** - * @brief OPL3 Chip management class - */ -class OPL3 -{ - friend class MIDIplay; - friend class AdlInstrumentTester; - friend int adlCalculateFourOpChannels(MIDIplay *play, bool silent); -public: - enum - { - PercussionTag = 1 << 15, - CustomBankTag = 0xFFFFFFFF - }; - - //! Total number of chip channels between all running emulators - uint32_t m_numChannels; - //! Just a padding. Reserved. - char _padding[4]; -#ifndef ADLMIDI_HW_OPL - //! Running chip emulators - std::vector<AdlMIDI_SPtr<OPLChipBase > > m_chips; -#endif - -private: - //! Cached patch data, needed by Touch() - std::vector<adldata> m_insCache; - //! Value written to B0, cached, needed by NoteOff. - /*! Contains Key on/off state, octave block and frequency number values - */ - std::vector<uint32_t> m_keyBlockFNumCache; - //! Cached BD registry value (flags register: DeepTremolo, DeepVibrato, and RhythmMode) - std::vector<uint32_t> m_regBD; - -public: - /** - * @brief MIDI bank entry - */ - struct Bank - { - //! MIDI Bank instruments - adlinsdata2 ins[128]; - }; - typedef BasicBankMap<Bank> BankMap; - //! MIDI bank instruments data - BankMap m_insBanks; - //! MIDI bank-wide setup - AdlBankSetup m_insBankSetup; - -public: - //! Blank instrument template - static const adlinsdata2 m_emptyInstrument; - //! Total number of running concurrent emulated chips - uint32_t m_numChips; - //! Currently running embedded bank number. "CustomBankTag" means usign of the custom bank. - uint32_t m_embeddedBank; - //! Total number of needed four-operator channels in all running chips - uint32_t m_numFourOps; - //! Turn global Deep Tremolo mode on - bool m_deepTremoloMode; - //! Turn global Deep Vibrato mode on - bool m_deepVibratoMode; - //! Use Rhythm Mode percussions - bool m_rhythmMode; - //! Carriers-only are scaled by default by volume level. This flag will tell to scale modulators too. - bool m_scaleModulators; - //! Run emulator at PCM rate if that possible. Reduces sounding accuracy, but decreases CPU usage on lower rates. - bool m_runAtPcmRate; - //! Enable soft panning - bool m_softPanning; - - //! Just a padding. Reserved. - char _padding2[3]; - - /** - * @brief Music playing mode - */ - enum MusicMode - { - //! MIDI mode - MODE_MIDI, - //! Id-Software Music mode - MODE_IMF, - //! Creative Music Files mode - MODE_CMF, - //! EA-MUS (a.k.a. RSXX) mode - MODE_RSXX - } m_musicMode; - - /** - * @brief Volume models enum - */ - enum VolumesScale - { - //! Generic volume model (linearization of logarithmic scale) - VOLUME_Generic, - //! OPL3 native logarithmic scale - VOLUME_NATIVE, - //! DMX volume scale logarithmic table - VOLUME_DMX, - //! Apoge Sound System volume scaling model - VOLUME_APOGEE, - //! Windows 9x driver volume scale table - VOLUME_9X - } m_volumeScale; - - //! Reserved - char _padding3[8]; - - /** - * @brief Channel categiry enumeration - */ - enum ChanCat - { - //! Regular melodic/percussion channel - ChanCat_Regular = 0, - //! Four-op master - ChanCat_4op_Master = 1, - //! Four-op slave - ChanCat_4op_Slave = 2, - //! Rhythm-mode Bass drum - ChanCat_Rhythm_Bass = 3, - //! Rhythm-mode Snare drum - ChanCat_Rhythm_Snare = 4, - //! Rhythm-mode Tom-Tom - ChanCat_Rhythm_Tom = 5, - //! Rhythm-mode Cymbal - ChanCat_Rhythm_Cymbal = 6, - //! Rhythm-mode Hi-Hat - ChanCat_Rhythm_HiHat = 7, - //! Rhythm-mode Slave channel - ChanCat_Rhythm_Slave = 8 - }; - - //! Category of the channel - /*! 1 = quad-master, 2 = quad-slave, 0 = regular - 3 = percussion BassDrum - 4 = percussion Snare - 5 = percussion Tom - 6 = percussion Crash cymbal - 7 = percussion Hihat - 8 = percussion slave - */ - std::vector<uint32_t> m_channelCategory; - - - /** - * @brief C.O. Constructor - */ - OPL3(); - - /** - * @brief Checks are setup locked to be changed on the fly or not - * @return true when setup on the fly is locked - */ - bool setupLocked(); - - /** - * @brief Choose one of embedded banks - * @param bank ID of the bank - */ - void setEmbeddedBank(uint32_t bank); - - /** - * @brief Write data to OPL3 chip register - * @param chip Index of emulated chip. In hardware OPL3 builds, this parameter is ignored - * @param address Register address to write - * @param value Value to write - */ - void writeReg(size_t chip, uint16_t address, uint8_t value); - - /** - * @brief Write data to OPL3 chip register - * @param chip Index of emulated chip. In hardware OPL3 builds, this parameter is ignored - * @param address Register address to write - * @param value Value to write - */ - void writeRegI(size_t chip, uint32_t address, uint32_t value); - - /** - * @brief Write to soft panning control of OPL3 chip emulator - * @param chip Index of emulated chip. - * @param address Register of channel to write - * @param value Value to write - */ - void writePan(size_t chip, uint32_t address, uint32_t value); - - /** - * @brief Off the note in specified chip channel - * @param c Channel of chip (Emulated chip choosing by next formula: [c = ch + (chipId * 23)]) - */ - void noteOff(size_t c); - - /** - * @brief On the note in specified chip channel with specified frequency of the tone - * @param c1 Channel of chip [or master 4-op channel] (Emulated chip choosing by next formula: [c = ch + (chipId * 23)]) - * @param c2 Slave 4-op channel of chip, unused for 2op (Emulated chip choosing by next formula: [c = ch + (chipId * 23)]) - * @param hertz Frequency of the tone in hertzes - */ - void noteOn(size_t c1, size_t c2, double hertz); - - /** - * @brief Change setup of instrument in specified chip channel - * @param c Channel of chip (Emulated chip choosing by next formula: [c = ch + (chipId * 23)]) - * @param volume Volume level (from 0 to 63) - * @param brightness CC74 Brightness level (from 0 to 127) - */ - void touchNote(size_t c, uint8_t volume, uint8_t brightness = 127); - - /** - * @brief Set the instrument into specified chip channel - * @param c Channel of chip (Emulated chip choosing by next formula: [c = ch + (chipId * 23)]) - * @param instrument Instrument data to set into the chip channel - */ - void setPatch(size_t c, const adldata &instrument); - - /** - * @brief Set panpot position - * @param c Channel of chip (Emulated chip choosing by next formula: [c = ch + (chipId * 23)]) - * @param value 3-bit panpot value - */ - void setPan(size_t c, uint8_t value); - - /** - * @brief Shut up all chip channels - */ - void silenceAll(); - - /** - * @brief Commit updated flag states to chip registers - */ - void updateChannelCategories(); - - /** - * @brief commit deepTremolo and deepVibrato flags - */ - void commitDeepFlags(); - - /** - * @brief Set the volume scaling model - * @param volumeModel Type of volume scale model scale - */ - void setVolumeScaleModel(ADLMIDI_VolumeModels volumeModel); - - /** - * @brief Get the volume scaling model - */ - ADLMIDI_VolumeModels getVolumeScaleModel(); - - #ifndef ADLMIDI_HW_OPL - /** - * @brief Clean up all running emulated chip instances - */ - void clearChips(); - #endif - - /** - * @brief Reset chip properties and initialize them - * @param emulator Type of chip emulator - * @param PCM_RATE Output sample rate to generate on output - * @param audioTickHandler PCM-accurate clock hook - */ - void reset(int emulator, unsigned long PCM_RATE, void *audioTickHandler); -}; - - -/** - * @brief Hooks of the internal events - */ -struct MIDIEventHooks -{ - MIDIEventHooks() : - onNote(NULL), - onNote_userData(NULL), - onDebugMessage(NULL), - onDebugMessage_userData(NULL) - {} - - //! Note on/off hooks - typedef void (*NoteHook)(void *userdata, int adlchn, int note, int ins, int pressure, double bend); - NoteHook onNote; - void *onNote_userData; - - //! Library internal debug messages - typedef void (*DebugMessageHook)(void *userdata, const char *fmt, ...); - DebugMessageHook onDebugMessage; - void *onDebugMessage_userData; -}; - - -class MIDIplay -{ - friend void adl_reset(struct ADL_MIDIPlayer*); -public: - explicit MIDIplay(unsigned long sampleRate = 22050); - - ~MIDIplay() - {} - - void applySetup(); - - void partialReset(); - void resetMIDI(); - - /**********************Internal structures and classes**********************/ - - /** - * @brief Persistent settings for each MIDI channel - */ - struct MIDIchannel - { - //! LSB Bank number - uint8_t bank_lsb, - //! MSB Bank number - bank_msb; - //! Current patch number - uint8_t patch; - //! Volume level - uint8_t volume, - //! Expression level - expression; - //! Panning level - uint8_t panning, - //! Vibrato level - vibrato, - //! Channel aftertouch level - aftertouch; - //! Portamento time - uint16_t portamento; - //! Is Pedal sustain active - bool sustain; - //! Is Soft pedal active - bool softPedal; - //! Is portamento enabled - bool portamentoEnable; - //! Source note number used by portamento - int8_t portamentoSource; // note number or -1 - //! Portamento rate - double portamentoRate; - //! Per note Aftertouch values - uint8_t noteAftertouch[128]; - //! Is note aftertouch has any non-zero value - bool noteAfterTouchInUse; - //! Reserved - char _padding[6]; - //! Pitch bend value - int bend; - //! Pitch bend sensitivity - double bendsense; - //! Pitch bend sensitivity LSB value - int bendsense_lsb, - //! Pitch bend sensitivity MSB value - bendsense_msb; - //! Vibrato position value - double vibpos, - //! Vibrato speed value - vibspeed, - //! Vibrato depth value - vibdepth; - //! Vibrato delay time - int64_t vibdelay_us; - //! Last LSB part of RPN value received - uint8_t lastlrpn, - //! Last MSB poart of RPN value received - lastmrpn; - //! Interpret RPN value as NRPN - bool nrpn; - //! Brightness level - uint8_t brightness; - - //! Is melodic channel turned into percussion - bool is_xg_percussion; - - /** - * @brief Per-Note information - */ - struct NoteInfo - { - //! Note number - uint8_t note; - //! Is note active - bool active; - //! Current pressure - uint8_t vol; - //! Note vibrato (a part of Note Aftertouch feature) - uint8_t vibrato; - //! Tone selected on noteon: - 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; - //! Is note the percussion instrument - bool isPercussion; - //! Note that plays missing instrument. Doesn't using any chip channels - bool isBlank; - //! Patch selected - const adlinsdata2 *ains; - enum - { - MaxNumPhysChans = 2, - MaxNumPhysItemCount = MaxNumPhysChans - }; - - /** - * @brief Reference to currently using chip channel - */ - struct Phys - { - //! Destination chip channel - uint16_t chip_chan; - //! ins, inde to adl[] - adldata ains; - //! Is this voice must be detunable? - bool pseudo4op; - - void assign(const Phys &oth) - { - ains = oth.ains; - pseudo4op = oth.pseudo4op; - } - bool operator==(const Phys &oth) const - { - return (ains == oth.ains) && (pseudo4op == oth.pseudo4op); - } - bool operator!=(const Phys &oth) const - { - return !operator==(oth); - } - }; - - //! List of OPL3 channels it is currently occupying. - Phys chip_channels[MaxNumPhysItemCount]; - //! Count of used channels. - unsigned chip_channels_count; - - Phys *phys_find(unsigned chip_chan) - { - Phys *ph = NULL; - for(unsigned i = 0; i < chip_channels_count && !ph; ++i) - if(chip_channels[i].chip_chan == chip_chan) - ph = &chip_channels[i]; - return ph; - } - Phys *phys_find_or_create(uint16_t chip_chan) - { - Phys *ph = phys_find(chip_chan); - if(!ph) { - if(chip_channels_count < MaxNumPhysItemCount) { - ph = &chip_channels[chip_channels_count++]; - ph->chip_chan = chip_chan; - } - } - return ph; - } - Phys *phys_ensure_find_or_create(uint16_t chip_chan) - { - Phys *ph = phys_find_or_create(chip_chan); - assert(ph); - return ph; - } - void phys_erase_at(const Phys *ph) - { - intptr_t pos = ph - chip_channels; - assert(pos < static_cast<intptr_t>(chip_channels_count)); - for(intptr_t i = pos + 1; i < static_cast<intptr_t>(chip_channels_count); ++i) - chip_channels[i - 1] = chip_channels[i]; - --chip_channels_count; - } - void phys_erase(unsigned chip_chan) - { - Phys *ph = phys_find(chip_chan); - if(ph) - phys_erase_at(ph); - } - }; - - //! Reserved - char _padding2[5]; - //! Count of gliding notes in this channel - unsigned gliding_note_count; - - //! Active notes in the channel - NoteInfo activenotes[128]; - - struct activenoteiterator - { - explicit activenoteiterator(NoteInfo *info = NULL) - : ptr(info) {} - activenoteiterator &operator++() - { - if(ptr->note == 127) - ptr = NULL; - else - for(++ptr; ptr && !ptr->active;) - ptr = (ptr->note == 127) ? NULL : (ptr + 1); - return *this; - } - activenoteiterator operator++(int) - { - activenoteiterator pos = *this; - ++*this; - return pos; - } - NoteInfo &operator*() const - { return *ptr; } - NoteInfo *operator->() const - { return ptr; } - bool operator==(activenoteiterator other) const - { return ptr == other.ptr; } - bool operator!=(activenoteiterator other) const - { return ptr != other.ptr; } - operator NoteInfo *() const - { return ptr; } - private: - NoteInfo *ptr; - }; - - activenoteiterator activenotes_begin() - { - activenoteiterator it(activenotes); - return (it->active) ? it : ++it; - } - - activenoteiterator activenotes_find(uint8_t note) - { - assert(note < 128); - return activenoteiterator( - activenotes[note].active ? &activenotes[note] : NULL); - } - - activenoteiterator activenotes_ensure_find(uint8_t note) - { - activenoteiterator it = activenotes_find(note); - assert(it); - return it; - } - - std::pair<activenoteiterator, bool> activenotes_insert(uint8_t note) - { - assert(note < 128); - NoteInfo &info = activenotes[note]; - bool inserted = !info.active; - if(inserted) info.active = true; - return std::pair<activenoteiterator, bool>(activenoteiterator(&info), inserted); - } - - void activenotes_erase(activenoteiterator pos) - { - if(pos) - pos->active = false; - } - - bool activenotes_empty() - { - return !activenotes_begin(); - } - - void activenotes_clear() - { - for(uint8_t i = 0; i < 128; ++i) { - activenotes[i].note = i; - activenotes[i].active = false; - } - } - - /** - * @brief Reset channel into initial state - */ - void reset() - { - resetAllControllers(); - patch = 0; - vibpos = 0; - bank_lsb = 0; - bank_msb = 0; - lastlrpn = 0; - lastmrpn = 0; - nrpn = false; - is_xg_percussion = false; - } - - /** - * @brief Reset all MIDI controllers into initial state - */ - void resetAllControllers() - { - bend = 0; - bendsense_msb = 2; - bendsense_lsb = 0; - updateBendSensitivity(); - volume = 100; - expression = 127; - sustain = false; - softPedal = false; - vibrato = 0; - aftertouch = 0; - std::memset(noteAftertouch, 0, 128); - noteAfterTouchInUse = false; - vibspeed = 2 * 3.141592653 * 5.0; - vibdepth = 0.5 / 127; - vibdelay_us = 0; - panning = 64; - portamento = 0; - portamentoEnable = false; - portamentoSource = -1; - portamentoRate = HUGE_VAL; - brightness = 127; - } - - /** - * @brief Has channel vibrato to process - * @return - */ - bool hasVibrato() - { - return (vibrato > 0) || (aftertouch > 0) || noteAfterTouchInUse; - } - - /** - * @brief Commit pitch bend sensitivity value from MSB and LSB - */ - void updateBendSensitivity() - { - int cent = bendsense_msb * 128 + bendsense_lsb; - bendsense = cent * (1.0 / (128 * 8192)); - } - - MIDIchannel() - { - activenotes_clear(); - gliding_note_count = 0; - reset(); - } - }; - - /** - * @brief Additional information about OPL3 channels - */ - struct AdlChannel - { - struct Location - { - uint16_t MidCh; - uint8_t note; - bool operator==(const Location &l) const - { return MidCh == l.MidCh && note == l.note; } - bool operator!=(const Location &l) const - { return !operator==(l); } - }; - struct LocationData - { - LocationData *prev, *next; - Location loc; - enum { - Sustain_None = 0x00, - Sustain_Pedal = 0x01, - Sustain_Sostenuto = 0x02, - Sustain_ANY = Sustain_Pedal | Sustain_Sostenuto - }; - uint32_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; - //! Timeout until note will be allowed to be killed by channel manager while it is on - int64_t kon_time_until_neglible_us; - int64_t vibdelay_us; - }; - - //! Time left until sounding will be muted after key off - int64_t koff_time_until_neglible_us; - - //! Recently passed instrument, improves a goodness of released but busy channel when matching - MIDIchannel::NoteInfo::Phys recent_ins; - - enum { users_max = 128 }; - LocationData *users_first, *users_free_cells; - LocationData users_cells[users_max]; - unsigned users_size; - - bool users_empty() const; - LocationData *users_find(Location loc); - LocationData *users_allocate(); - LocationData *users_find_or_create(Location loc); - LocationData *users_insert(const LocationData &x); - void users_erase(LocationData *user); - void users_clear(); - void users_assign(const LocationData *users, size_t count); - - // For channel allocation: - AdlChannel(): koff_time_until_neglible_us(0) - { - users_clear(); - std::memset(&recent_ins, 0, sizeof(MIDIchannel::NoteInfo::Phys)); - } - - AdlChannel(const AdlChannel &oth): koff_time_until_neglible_us(oth.koff_time_until_neglible_us) - { - if(oth.users_first) - { - users_first = NULL; - users_assign(oth.users_first, oth.users_size); - } - else - users_clear(); - } - - AdlChannel &operator=(const AdlChannel &oth) - { - koff_time_until_neglible_us = oth.koff_time_until_neglible_us; - users_assign(oth.users_first, oth.users_size); - return *this; - } - - /** - * @brief Increases age of active note in microseconds time - * @param us Amount time in microseconds - */ - void addAge(int64_t us); - }; - -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - /** - * @brief MIDI files player sequencer - */ - MidiSequencer m_sequencer; - - /** - * @brief Interface between MIDI sequencer and this library - */ - BW_MidiRtInterface m_sequencerInterface; - - /** - * @brief Initialize MIDI sequencer interface - */ - void initSequencerInterface(); -#endif //ADLMIDI_DISABLE_MIDI_SEQUENCER - - struct Setup - { - int emulator; - bool runAtPcmRate; - unsigned int bankId; - int numFourOps; - unsigned int numChips; - int deepTremoloMode; - int deepVibratoMode; - int rhythmMode; - bool logarithmicVolumes; - int volumeScaleModel; - //unsigned int SkipForward; - int scaleModulators; - bool fullRangeBrightnessCC74; - - double delay; - double carry; - - /* The lag between visual content and audio content equals */ - /* the sum of these two buffers. */ - double mindelay; - double maxdelay; - - /* For internal usage */ - ssize_t tick_skip_samples_delay; /* Skip tick processing after samples count. */ - /* For internal usage */ - - unsigned long PCM_RATE; - }; - - /** - * @brief MIDI Marker entry - */ - struct MIDI_MarkerEntry - { - //! Label of marker - std::string label; - //! Absolute position in seconds - double pos_time; - //! Absolute position in ticks in the track - uint64_t pos_ticks; - }; - - //! Available MIDI Channels - std::vector<MIDIchannel> m_midiChannels; - - //! CMF Rhythm mode - bool m_cmfPercussionMode; - - //! Master volume, controlled via SysEx - uint8_t m_masterVolume; - - //! SysEx device ID - uint8_t m_sysExDeviceId; - - /** - * @brief MIDI Synthesizer mode - */ - enum SynthMode - { - Mode_GM = 0x00, - Mode_GS = 0x01, - Mode_XG = 0x02, - Mode_GM2 = 0x04 - }; - //! MIDI Synthesizer mode - uint32_t m_synthMode; - - //! Installed function hooks - MIDIEventHooks hooks; - -private: - //! Per-track MIDI devices map - std::map<std::string, size_t> m_midiDevices; - //! Current MIDI device per track - std::map<size_t /*track*/, size_t /*channel begin index*/> m_currentMidiDevice; - - //! Padding to fix CLanc code model's warning - char _padding[7]; - - //! Chip channels map - std::vector<AdlChannel> m_chipChannels; - //! Counter of arpeggio processing - size_t m_arpeggioCounter; - -#if defined(ADLMIDI_AUDIO_TICK_HANDLER) - //! Audio tick counter - uint32_t m_audioTickCounter; -#endif - - //! Local error string - std::string errorStringOut; - - //! Missing instruments catches - std::set<size_t> caugh_missing_instruments; - //! Missing melodic banks catches - std::set<size_t> caugh_missing_banks_melodic; - //! Missing percussion banks catches - std::set<size_t> caugh_missing_banks_percussion; - -public: - - const std::string &getErrorString(); - void setErrorString(const std::string &err); - - //! OPL3 Chip manager - OPL3 m_synth; - - //! Generator output buffer - int32_t m_outBuf[1024]; - - //! Synthesizer setup - Setup m_setup; - - /** - * @brief Load custom bank from file - * @param filename Path to bank file - * @return true on succes - */ - bool LoadBank(const std::string &filename); - - /** - * @brief Load custom bank from memory block - * @param data Pointer to memory block where raw bank file is stored - * @param size Size of given memory block - * @return true on succes - */ - bool LoadBank(const void *data, size_t size); - - /** - * @brief Load custom bank from opened FileAndMemReader class - * @param fr Instance with opened file - * @return true on succes - */ - bool LoadBank(FileAndMemReader &fr); - -#ifndef ADLMIDI_DISABLE_MIDI_SEQUENCER - /** - * @brief MIDI file loading pre-process - * @return true on success, false on failure - */ - bool LoadMIDI_pre(); - - /** - * @brief MIDI file loading post-process - * @return true on success, false on failure - */ - bool LoadMIDI_post(); - - /** - * @brief Load music file from a file - * @param filename Path to music file - * @return true on success, false on failure - */ - - 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); - - /** - * @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); -#endif //ADLMIDI_DISABLE_MIDI_SEQUENCER - - /** - * @brief Process extra iterators like vibrato or arpeggio - * @param s seconds since last call - */ - void TickIterators(double s); - - - /* RealTime event triggers */ - /** - * @brief Reset state of all channels - */ - void realTime_ResetState(); - - /** - * @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 - */ - bool realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity); - - /** - * @brief Note Off event - * @param channel MIDI channel - * @param note Note key (from 0 to 127) - */ - void realTime_NoteOff(uint8_t channel, uint8_t note); - - /** - * @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) - */ - void realTime_NoteAfterTouch(uint8_t channel, uint8_t note, uint8_t atVal); - - /** - * @brief Channel aftertouch event - * @param channel MIDI channel - * @param atVal After-Touch level (from 0 to 127) - */ - void realTime_ChannelAfterTouch(uint8_t channel, uint8_t atVal); - - /** - * @brief Controller Change event - * @param channel MIDI channel - * @param type Type of controller - * @param value Value of the controller (from 0 to 127) - */ - 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 msb MSB value of bank number - */ - void realTime_BankChangeMSB(uint8_t channel, uint8_t msb); - - /** - * @brief Bank Change (united value) - * @param channel MIDI channel - * @param bank Bank number value - */ - void realTime_BankChange(uint8_t channel, uint16_t bank); - - /** - * @brief Sets the Device identifier - * @param id 7-bit Device identifier - */ - void setDeviceId(uint8_t id); - - /** - * @brief System Exclusive message - * @param msg Raw SysEx Message - * @param size Length of SysEx message - * @return true if message was passed successfully. False on any errors - */ - bool realTime_SysEx(const uint8_t *msg, size_t size); - - /** - * @brief Turn off all notes and mute the sound of releasing notes - */ - void realTime_panic(); - - /** - * @brief Device switch (to extend 16-channels limit of MIDI standard) - * @param track MIDI track index - * @param data Device name - * @param length Length of device name string - */ - void realTime_deviceSwitch(size_t track, const char *data, size_t length); - - /** - * @brief Currently selected device index - * @param track MIDI track index - * @return Multiple 16 value - */ - size_t realTime_currentDevice(size_t track); - - /** - * @brief Send raw OPL chip command - * @param reg OPL Register - * @param value Value to write - */ - void realTime_rawOPL(uint8_t reg, uint8_t value); - -#if defined(ADLMIDI_AUDIO_TICK_HANDLER) - // Audio rate tick handler - void AudioTick(uint32_t chipId, uint32_t rate); -#endif - -private: - /** - * @brief Hardware manufacturer (Used for SysEx) - */ - enum - { - Manufacturer_Roland = 0x41, - Manufacturer_Yamaha = 0x43, - Manufacturer_UniversalNonRealtime = 0x7E, - Manufacturer_UniversalRealtime = 0x7F - }; - - /** - * @brief Roland Mode (Used for SysEx) - */ - enum - { - RolandMode_Request = 0x11, - RolandMode_Send = 0x12 - }; - - /** - * @brief Device model (Used for SysEx) - */ - enum - { - RolandModel_GS = 0x42, - RolandModel_SC55 = 0x45, - YamahaModel_XG = 0x4C - }; - - /** - * @brief Process generic SysEx events - * @param dev Device ID - * @param realtime Is real-time event - * @param data Raw SysEx data - * @param size Size of given SysEx data - * @return true when event was successfully handled - */ - bool doUniversalSysEx(unsigned dev, bool realtime, const uint8_t *data, size_t size); - - /** - * @brief Process events specific to Roland devices - * @param dev Device ID - * @param data Raw SysEx data - * @param size Size of given SysEx data - * @return true when event was successfully handled - */ - bool doRolandSysEx(unsigned dev, const uint8_t *data, size_t size); - - /** - * @brief Process events specific to Yamaha devices - * @param dev Device ID - * @param data Raw SysEx data - * @param size Size of given SysEx data - * @return true when event was successfully handled - */ - bool doYamahaSysEx(unsigned dev, const uint8_t *data, size_t size); - -private: - /** - * @brief Note Update properties - */ - enum - { - Upd_Patch = 0x1, - Upd_Pan = 0x2, - Upd_Volume = 0x4, - Upd_Pitch = 0x8, - Upd_All = Upd_Pan + Upd_Volume + Upd_Pitch, - Upd_Off = 0x20, - Upd_Mute = 0x40, - Upd_OffMute = Upd_Off + Upd_Mute - }; - - /** - * @brief Update active note - * @param MidCh MIDI Channel where note is processing - * @param i Iterator that points to active note in the MIDI channel - * @param props_mask Properties to update - * @param select_adlchn Specify chip channel, or -1 - all chip channels used by the note - */ - void noteUpdate(size_t midCh, - MIDIchannel::activenoteiterator i, - unsigned props_mask, - int32_t select_adlchn = -1); - - /** - * @brief Update all notes in specified MIDI channel - * @param midCh MIDI channel to update all notes in it - * @param props_mask Properties to update - */ - void noteUpdateAll(size_t midCh, unsigned props_mask); - - /** - * @brief Determine how good a candidate this adlchannel would be for playing a note from this instrument. - * @param c Wanted chip channel - * @param ins Instrument wanted to be used in this channel - * @return Calculated coodness points - */ - int64_t calculateChipChannelGoodness(size_t c, const MIDIchannel::NoteInfo::Phys &ins) const; - - /** - * @brief A new note will be played on this channel using this instrument. - * @param c Wanted chip channel - * @param ins Instrument wanted to be used in this channel - * Kill existing notes on this channel (or don't, if we do arpeggio) - */ - void prepareChipChannelForNewNote(size_t c, const MIDIchannel::NoteInfo::Phys &ins); - - /** - * @brief Kills note that uses wanted channel. When arpeggio is possible, note is evaluating to another channel - * @param from_channel Wanted chip channel - * @param j Chip channel instance - * @param i MIDI Channel active note instance - */ - void killOrEvacuate( - size_t from_channel, - AdlChannel::LocationData *j, - MIDIchannel::activenoteiterator i); - - /** - * @brief Off all notes and silence sound - */ - void panic(); - - /** - * @brief Kill note, sustaining by pedal or sostenuto - * @param MidCh MIDI channel, -1 - all MIDI channels - * @param this_adlchn Chip channel, -1 - all chip channels - * @param sustain_type Type of systain to process - */ - void killSustainingNotes(int32_t midCh = -1, - int32_t this_adlchn = -1, - uint32_t sustain_type = AdlChannel::LocationData::Sustain_ANY); - /** - * @brief Find active notes and mark them as sostenuto-sustained - * @param MidCh MIDI channel, -1 - all MIDI channels - */ - void markSostenutoNotes(int32_t midCh = -1); - - /** - * @brief Set RPN event value - * @param MidCh MIDI channel - * @param value 1 byte part of RPN value - * @param MSB is MSB or LSB part of value - */ - void setRPN(size_t midCh, unsigned value, bool MSB); - - /** - * @brief Update portamento setup in MIDI channel - * @param midCh MIDI channel where portamento needed to be updated - */ - void updatePortamento(size_t midCh); - - /** - * @brief Off the note - * @param midCh MIDI channel - * @param note Note to off - */ - void noteOff(size_t midCh, uint8_t note); - - /** - * @brief Update processing of vibrato to amount of seconds - * @param amount Amount value in seconds - */ - void updateVibrato(double amount); - - /** - * @brief Update auto-arpeggio - * @param amount Amount value in seconds [UNUSED] - */ - void updateArpeggio(double /*amount*/); - - /** - * @brief Update Portamento gliding to amount of seconds - * @param amount Amount value in seconds - */ - void updateGlide(double amount); - -public: - /** - * @brief Checks was device name used or not - * @param name Name of MIDI device - * @return Offset of the MIDI Channels, multiple to 16 - */ - size_t chooseDevice(const std::string &name); - - /** - * @brief Gets a textual description of the state of chip channels - * @param text character pointer for text - * @param attr character pointer for text attributes - * @param size number of characters available to write - */ - void describeChannels(char *text, char *attr, size_t size); -}; - -// I think, this is useless inside of Library -/* -struct FourChars -{ - char ret[4]; - - FourChars(const char *s) - { - for(unsigned c = 0; c < 4; ++c) - ret[c] = s[c]; - } - FourChars(unsigned w) // Little-endian - { - for(unsigned c = 0; c < 4; ++c) - ret[c] = static_cast<int8_t>((w >>(c * 8)) & 0xFF); - } -}; -*/ #if defined(ADLMIDI_AUDIO_TICK_HANDLER) extern void adl_audioTickHandler(void *instance, uint32_t chipId, uint32_t rate); @@ -1505,23 +229,4 @@ extern void adl_audioTickHandler(void *instance, uint32_t chipId, uint32_t rate) */ extern int adlCalculateFourOpChannels(MIDIplay *play, bool silent = false); -/** - * @brief Check emulator availability - * @param emulator Emulator ID (ADL_Emulator) - * @return true when emulator is available - */ -extern bool adl_isEmulatorAvailable(int emulator); - -/** - * @brief Find highest emulator - * @return The ADL_Emulator enum value which contains ID of highest emulator - */ -extern int adl_getHighestEmulator(); - -/** - * @brief Find lowest emulator - * @return The ADL_Emulator enum value which contains ID of lowest emulator - */ -extern int adl_getLowestEmulator(); - #endif // ADLMIDI_PRIVATE_HPP diff --git a/src/adlmidi_ptr.hpp b/src/adlmidi_ptr.hpp index 7d1086b..aaaed78 100644 --- a/src/adlmidi_ptr.hpp +++ b/src/adlmidi_ptr.hpp @@ -54,7 +54,7 @@ class AdlMIDI_UPtr { T *m_p; public: - explicit AdlMIDI_UPtr(T *p) + explicit AdlMIDI_UPtr(T *p = NULL) : m_p(p) {} ~AdlMIDI_UPtr() { diff --git a/src/adlmidi_sequencer.cpp b/src/adlmidi_sequencer.cpp index 66b9b9a..a1fe5ab 100644 --- a/src/adlmidi_sequencer.cpp +++ b/src/adlmidi_sequencer.cpp @@ -28,6 +28,8 @@ // Inlucde MIDI sequencer class implementation #include "midi_sequencer_impl.hpp" +#include "adlmidi_midiplay.hpp" +#include "adlmidi_opl3.hpp" #include "adlmidi_private.hpp" /**************************************************** @@ -106,37 +108,42 @@ static size_t rtCurrentDevice(void *userdata, size_t track) void MIDIplay::initSequencerInterface() { - std::memset(&m_sequencerInterface, 0, sizeof(BW_MidiRtInterface)); + BW_MidiRtInterface *seq = new BW_MidiRtInterface; + m_sequencerInterface.reset(seq); - m_sequencerInterface.onDebugMessage = hooks.onDebugMessage; - m_sequencerInterface.onDebugMessage_userData = hooks.onDebugMessage_userData; + std::memset(seq, 0, sizeof(BW_MidiRtInterface)); + + seq->onDebugMessage = hooks.onDebugMessage; + seq->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; + seq->rtUserData = this; + seq->rt_noteOn = rtNoteOn; + seq->rt_noteOff = rtNoteOff; + seq->rt_noteAfterTouch = rtNoteAfterTouch; + seq->rt_channelAfterTouch = rtChannelAfterTouch; + seq->rt_controllerChange = rtControllerChange; + seq->rt_patchChange = rtPatchChange; + seq->rt_pitchBend = rtPitchBend; + seq->rt_systemExclusive = rtSysEx; /* NonStandard calls */ - m_sequencerInterface.rt_rawOPL = rtRawOPL; - m_sequencerInterface.rt_deviceSwitch = rtDeviceSwitch; - m_sequencerInterface.rt_currentDevice = rtCurrentDevice; + seq->rt_rawOPL = rtRawOPL; + seq->rt_deviceSwitch = rtDeviceSwitch; + seq->rt_currentDevice = rtCurrentDevice; /* NonStandard calls End */ - m_sequencer.setInterface(&m_sequencerInterface); + m_sequencer->setInterface(seq); } double MIDIplay::Tick(double s, double granularity) { - double ret = m_sequencer.Tick(s, granularity); + MidiSequencer &seqr = *m_sequencer; + double ret = seqr.Tick(s, granularity); - s *= m_sequencer.getTempoMultiplier(); - for(uint16_t c = 0; c < m_synth.m_numChannels; ++c) + Synth &synth = *m_synth; + s *= seqr.getTempoMultiplier(); + for(uint16_t c = 0; c < synth.m_numChannels; ++c) m_chipChannels[c].addAge(static_cast<int64_t>(s * 1e6)); updateVibrato(s); diff --git a/src/midi_sequencer.h b/src/midi_sequencer.h index c6069d7..50e7109 100644 --- a/src/midi_sequencer.h +++ b/src/midi_sequencer.h @@ -36,7 +36,7 @@ extern "C" { /** \brief Real-Time MIDI interface between Sequencer and the Synthesizer */ -typedef struct +typedef 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); |