aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md1
-rw-r--r--src/adlmidi_midiplay.cpp54
-rw-r--r--src/adlmidi_midiplay.hpp7
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
@@ -963,6 +963,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
* @param ins Instrument wanted to be used in this channel