From 91558f59359eedef8d67dacacb838da498e3c3ef Mon Sep 17 00:00:00 2001 From: Wohlstand Date: Fri, 23 May 2025 21:35:53 +0300 Subject: Improve stability of 2-voice instruments on single OPL2 --- README.md | 1 + src/adlmidi_midiplay.cpp | 54 +++++++++++++++++++++++++++++++++++++++++++++--- src/adlmidi_midiplay.hpp | 7 +++++++ 3 files changed, 59 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2fcf372..5b6cac1 100644 --- a/README.md +++ b/README.md @@ -200,6 +200,7 @@ To build that example you will need to have installed SDL2 library. * Added Nuked OPL2 and OPL3 Low-Level emulators (Kept disabled by default because they are too heavy for ordinary processors). * Fixed a dead loop that might happen when final tone gets lower than zero. * Added possibility to play the same note multiple times at the same MIDI channel (Resolved playback of some music, like Heretic's E1M6). + * Dual-voice 2-op instruments will be squashed to single 2-op voice when available chip channels are overflow (The stability of DMX-oriented music has been improved). ## 1.5.1 2022-10-31 * Added an ability to disable the automatical arpeggio diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index b27ed13..2383d26 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -513,6 +513,9 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) for(uint32_t ccount = 0; ccount < MIDIchannel::NoteInfo::MaxNumPhysChans; ++ccount) { + int32_t c = -1; + int32_t bs = -0x7FFFFFFFl; + if(ccount == 1) { if(voices[0] == voices[1]) @@ -520,9 +523,18 @@ bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) if(adlchannel[0] == -1) break; // No secondary if primary failed } - - int32_t c = -1; - int32_t bs = -0x7FFFFFFFl; + else if(!m_setup.enableAutoArpeggio && + (is_2op || pseudo_4op) && + (ains->flags & OplInstMeta::Mask_RhythmMode) == 0) + { + if(killSecondVoicesIfOverflow(c)) + { + adlchannel[0] = c; + adlchannel[1] = -1; + voices[1] = voices[0]; + break; + } + } for(size_t a = 0; a < (size_t)synth.m_numChannels; ++a) { @@ -1529,6 +1541,42 @@ int64_t MIDIplay::calculateChipChannelGoodness(size_t c, const MIDIchannel::Note return s; } +bool MIDIplay::killSecondVoicesIfOverflow(int32_t &new_chan) +{ + Synth &synth = *m_synth; + bool ret = false; + + int free2op = 0; + + for(size_t a = 0; a < (size_t)synth.m_numChannels; ++a) + { + if(synth.m_channelCategory[a] != OPL3::ChanCat_Regular) + continue; + + AdlChannel &chan = m_chipChannels[a]; + + if(chan.users.empty()) + ++free2op; + else if(chan.recent_ins.pseudo4op) + new_chan = a; + } + + if(!free2op && new_chan >= 0) + { + AdlChannel::users_iterator j = &m_chipChannels[new_chan].users.front(); + AdlChannel::LocationData &jd = j->value; + MIDIchannel::notes_iterator i(m_midiChannels[jd.loc.MidCh].ensure_find_activenote(jd.loc.note)); + + if(i->value.chip_channels_count == 2) + { + m_chipChannels[new_chan].users.erase(j); + i->value.chip_channels_count = 1; + ret = true; + } + } + + return ret; +} void MIDIplay::prepareChipChannelForNewNote(size_t c, const MIDIchannel::NoteInfo::Phys &ins) { diff --git a/src/adlmidi_midiplay.hpp b/src/adlmidi_midiplay.hpp index 6ab0915..e5058b9 100644 --- a/src/adlmidi_midiplay.hpp +++ b/src/adlmidi_midiplay.hpp @@ -962,6 +962,13 @@ private: */ int64_t calculateChipChannelGoodness(size_t c, const MIDIchannel::NoteInfo::Phys &ins) const; + /** + * @brief If no free chip channels, try to kill at least one second voice of pseudo-4-op instruments and steal the released channel + * @param new_chan Value of released chip channel to reuse + * @return true if any channel was been stolen, or false when nothing happen + */ + bool killSecondVoicesIfOverflow(int32_t &new_chan); + /** * @brief A new note will be played on this channel using this instrument. * @param c Wanted chip channel -- cgit v1.2.3