aboutsummaryrefslogtreecommitdiff
path: root/src/adlmidi_midiplay.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/adlmidi_midiplay.cpp')
-rw-r--r--src/adlmidi_midiplay.cpp1360
1 files changed, 1360 insertions, 0 deletions
diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp
new file mode 100644
index 0000000..21f334a
--- /dev/null
+++ b/src/adlmidi_midiplay.cpp
@@ -0,0 +1,1360 @@
+/*
+ * 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) 2017 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/>.
+ */
+
+#include "adlmidi_private.hpp"
+
+
+// Mapping from MIDI volume level to OPL level value.
+
+static const uint32_t DMX_volume_mapping_table[] =
+{
+ 0, 1, 3, 5, 6, 8, 10, 11,
+ 13, 14, 16, 17, 19, 20, 22, 23,
+ 25, 26, 27, 29, 30, 32, 33, 34,
+ 36, 37, 39, 41, 43, 45, 47, 49,
+ 50, 52, 54, 55, 57, 59, 60, 61,
+ 63, 64, 66, 67, 68, 69, 71, 72,
+ 73, 74, 75, 76, 77, 79, 80, 81,
+ 82, 83, 84, 84, 85, 86, 87, 88,
+ 89, 90, 91, 92, 92, 93, 94, 95,
+ 96, 96, 97, 98, 99, 99, 100, 101,
+ 101, 102, 103, 103, 104, 105, 105, 106,
+ 107, 107, 108, 109, 109, 110, 110, 111,
+ 112, 112, 113, 113, 114, 114, 115, 115,
+ 116, 117, 117, 118, 118, 119, 119, 120,
+ 120, 121, 121, 122, 122, 123, 123, 123,
+ 124, 124, 125, 125, 126, 126, 127, 127,
+ //Protection entries to avoid crash if value more than 127
+ 127, 127, 127, 127, 127, 127, 127, 127,
+ 127, 127, 127, 127, 127, 127, 127, 127,
+ 127, 127, 127, 127, 127, 127, 127, 127,
+ 127, 127, 127, 127, 127, 127, 127, 127,
+ 127, 127, 127, 127, 127, 127, 127, 127,
+ 127, 127, 127, 127, 127, 127, 127, 127,
+ 127, 127, 127, 127, 127, 127, 127, 127,
+ 127, 127, 127, 127, 127, 127, 127, 127,
+ 127, 127, 127, 127, 127, 127, 127, 127,
+ 127, 127, 127, 127, 127, 127, 127, 127,
+ 127, 127, 127, 127, 127, 127, 127, 127,
+ 127, 127, 127, 127, 127, 127, 127, 127,
+ 127, 127, 127, 127, 127, 127, 127, 127,
+ 127, 127, 127, 127, 127, 127, 127, 127,
+ 127, 127, 127, 127, 127, 127, 127, 127,
+ 127, 127, 127, 127, 127, 127, 127, 127,
+};
+
+static const uint8_t W9X_volume_mapping_table[32] =
+{
+ 63, 63, 40, 36, 32, 28, 23, 21,
+ 19, 17, 15, 14, 13, 12, 11, 10,
+ 9, 8, 7, 6, 5, 5, 4, 4,
+ 3, 3, 2, 2, 1, 1, 0, 0
+};
+
+
+//static const char MIDIsymbols[256+1] =
+//"PPPPPPhcckmvmxbd" // Ins 0-15
+//"oooooahoGGGGGGGG" // Ins 16-31
+//"BBBBBBBBVVVVVHHM" // Ins 32-47
+//"SSSSOOOcTTTTTTTT" // Ins 48-63
+//"XXXXTTTFFFFFFFFF" // Ins 64-79
+//"LLLLLLLLpppppppp" // Ins 80-95
+//"XXXXXXXXGGGGGTSS" // Ins 96-111
+//"bbbbMMMcGXXXXXXX" // Ins 112-127
+//"????????????????" // Prc 0-15
+//"????????????????" // Prc 16-31
+//"???DDshMhhhCCCbM" // Prc 32-47
+//"CBDMMDDDMMDDDDDD" // Prc 48-63
+//"DDDDDDDDDDDDDDDD" // Prc 64-79
+//"DD??????????????" // Prc 80-95
+//"????????????????" // Prc 96-111
+//"????????????????"; // Prc 112-127
+
+static const uint8_t PercussionMap[256] =
+ "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"//GM
+ "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" // 3 = bass drum
+ "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" // 4 = snare
+ "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" // 5 = tom
+ "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" // 6 = cymbal
+ "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" // 7 = hihat
+ "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+ "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+ "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"//GP0
+ "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"//GP16
+ //2 3 4 5 6 7 8 940 1 2 3 4 5 6 7
+ "\0\0\0\3\3\7\4\7\4\5\7\5\7\5\7\5"//GP32
+ //8 950 1 2 3 4 5 6 7 8 960 1 2 3
+ "\5\6\5\6\6\0\5\6\0\6\0\6\5\5\5\5"//GP48
+ //4 5 6 7 8 970 1 2 3 4 5 6 7 8 9
+ "\5\0\0\0\0\0\7\0\0\0\0\0\0\0\0\0"//GP64
+ "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+ "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
+ "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
+
+void MIDIplay::AdlChannel::AddAge(int64_t ms)
+{
+ if(users.empty())
+ koff_time_until_neglible =
+ std::max(koff_time_until_neglible - ms, static_cast<int64_t>(-0x1FFFFFFFl));
+ else
+ {
+ koff_time_until_neglible = 0;
+
+ for(users_t::iterator i = users.begin(); i != users.end(); ++i)
+ {
+ i->second.kon_time_until_neglible =
+ std::max(i->second.kon_time_until_neglible - ms, static_cast<int64_t>(-0x1FFFFFFFl));
+ i->second.vibdelay += ms;
+ }
+ }
+}
+
+MIDIplay::MIDIplay():
+ cmf_percussion_mode(false),
+ config(NULL),
+ trackStart(false),
+ loopStart(false),
+ loopEnd(false),
+ loopStart_passed(false),
+ invalidLoop(false),
+ loopStart_hit(false)
+{
+ devices.clear();
+}
+
+uint64_t MIDIplay::ReadVarLen(size_t tk)
+{
+ uint64_t result = 0;
+
+ for(;;)
+ {
+ unsigned char byte = TrackData[tk][CurrentPosition.track[tk].ptr++];
+ result = (result << 7) + (byte & 0x7F);
+
+ if(!(byte & 0x80)) break;
+ }
+
+ return result;
+}
+
+
+double MIDIplay::Tick(double s, double granularity)
+{
+ if(CurrentPosition.began) CurrentPosition.wait -= s;
+
+ int AntiFreezeCounter = 10000;//Limit 10000 loops to avoid freezing
+
+ while((CurrentPosition.wait <= granularity * 0.5) && (AntiFreezeCounter > 0))
+ {
+ //std::fprintf(stderr, "wait = %g...\n", CurrentPosition.wait);
+ ProcessEvents();
+
+ if(CurrentPosition.wait <= 0.0)
+ AntiFreezeCounter--;
+ }
+
+ if(AntiFreezeCounter <= 0)
+ CurrentPosition.wait += 1.0;/* Add extra 1 second when over 10000 events
+
+ with zero delay are been detected */
+
+ for(uint16_t c = 0; c < opl.NumChannels; ++c)
+ ch[c].AddAge(static_cast<int64_t>(s * 1000.0));
+
+ UpdateVibrato(s);
+ UpdateArpeggio(s);
+ return CurrentPosition.wait;
+}
+
+
+void MIDIplay::NoteUpdate(uint16_t MidCh,
+ MIDIplay::MIDIchannel::activenoteiterator i,
+ unsigned props_mask,
+ int32_t select_adlchn)
+{
+ MIDIchannel::NoteInfo &info = i->second;
+ const int16_t tone = info.tone;
+ const uint8_t vol = info.vol;
+ //const int midiins = info.midiins;
+ const uint32_t insmeta = info.insmeta;
+ const adlinsdata &ains = opl.GetAdlMetaIns(insmeta);
+ AdlChannel::Location my_loc;
+ my_loc.MidCh = MidCh;
+ my_loc.note = i->first;
+
+ for(std::map<uint16_t, uint16_t>::iterator
+ jnext = info.phys.begin();
+ jnext != info.phys.end();
+ )
+ {
+ std::map<uint16_t, uint16_t>::iterator j(jnext++);
+ uint16_t c = j->first;
+ uint16_t ins = j->second;
+
+ if(select_adlchn >= 0 && c != select_adlchn) continue;
+
+ if(props_mask & Upd_Patch)
+ {
+ opl.Patch(c, ins);
+ AdlChannel::LocationData &d = ch[c].users[my_loc];
+ d.sustained = false; // inserts if necessary
+ d.vibdelay = 0;
+ d.kon_time_until_neglible = ains.ms_sound_kon;
+ d.ins = ins;
+ }
+ }
+
+ for(std::map<unsigned short, unsigned short>::iterator
+ jnext = info.phys.begin();
+ jnext != info.phys.end();
+ )
+ {
+ std::map<unsigned short, unsigned short>::iterator j(jnext++);
+ uint16_t c = j->first;
+ uint16_t ins = j->second;
+
+ if(select_adlchn >= 0 && c != select_adlchn) continue;
+
+ if(props_mask & Upd_Off) // note off
+ {
+ if(Ch[MidCh].sustain == 0)
+ {
+ AdlChannel::users_t::iterator k = ch[c].users.find(my_loc);
+
+ if(k != ch[c].users.end())
+ ch[c].users.erase(k);
+
+ //UI.IllustrateNote(c, tone, midiins, 0, 0.0);
+
+ if(ch[c].users.empty())
+ {
+ opl.NoteOff(c);
+ ch[c].koff_time_until_neglible =
+ ains.ms_sound_koff;
+ }
+ }
+ else
+ {
+ // Sustain: Forget about the note, but don't key it off.
+ // Also will avoid overwriting it very soon.
+ AdlChannel::LocationData &d = ch[c].users[my_loc];
+ d.sustained = true; // note: not erased!
+ //UI.IllustrateNote(c, tone, midiins, -1, 0.0);
+ }
+
+ info.phys.erase(j);
+ continue;
+ }
+
+ if(props_mask & Upd_Pan)
+ opl.Pan(c, Ch[MidCh].panning);
+
+ if(props_mask & Upd_Volume)
+ {
+ uint32_t volume;
+
+ switch(opl.m_volumeScale)
+ {
+ case OPL3::VOLUME_Generic:
+ case OPL3::VOLUME_CMF:
+ {
+ volume = vol * Ch[MidCh].volume * Ch[MidCh].expression;
+
+ /* If the channel has arpeggio, the effective volume of
+ * *this* instrument is actually lower due to timesharing.
+ * To compensate, add extra volume that corresponds to the
+ * time this note is *not* heard.
+ * Empirical tests however show that a full equal-proportion
+ * increment sounds wrong. Therefore, using the square root.
+ */
+ //volume = (int)(volume * std::sqrt( (double) ch[c].users.size() ));
+
+ if(opl.LogarithmicVolumes)
+ volume = volume * 127 / (127 * 127 * 127) / 2;
+ else
+ {
+ // The formula below: SOLVE(V=127^3 * 2^( (A-63.49999) / 8), A)
+ volume = volume > 8725 ? static_cast<unsigned int>(std::log(volume) * 11.541561 + (0.5 - 104.22845)) : 0;
+ // The incorrect formula below: SOLVE(V=127^3 * (2^(A/63)-1), A)
+ //opl.Touch_Real(c, volume>11210 ? 91.61112 * std::log(4.8819E-7*volume + 1.0)+0.5 : 0);
+ }
+
+ opl.Touch_Real(c, volume);
+ //opl.Touch(c, volume);
+ }
+ break;
+
+ case OPL3::VOLUME_DMX:
+ {
+ volume = 2 * ((Ch[MidCh].volume * Ch[MidCh].expression) * 127 / 16129) + 1;
+ //volume = 2 * (Ch[MidCh].volume) + 1;
+ volume = (DMX_volume_mapping_table[vol] * volume) >> 9;
+ opl.Touch_Real(c, volume);
+ }
+ break;
+
+ case OPL3::VOLUME_APOGEE:
+ {
+ volume = ((Ch[MidCh].volume * Ch[MidCh].expression) * 127 / 16129);
+ volume = ((64 * (vol + 0x80)) * volume) >> 15;
+ //volume = ((63 * (vol + 0x80)) * Ch[MidCh].volume) >> 15;
+ opl.Touch_Real(c, volume);
+ }
+ break;
+
+ case OPL3::VOLUME_9X:
+ {
+ //volume = 63 - W9X_volume_mapping_table[(((vol * Ch[MidCh].volume /** Ch[MidCh].expression*/) * 127 / 16129 /*2048383*/) >> 2)];
+ volume = 63 - W9X_volume_mapping_table[(((vol * Ch[MidCh].volume * Ch[MidCh].expression) * 127 / 2048383) >> 2)];
+ //volume = W9X_volume_mapping_table[vol >> 2] + volume;
+ opl.Touch_Real(c, volume);
+ }
+ break;
+ }
+
+ /* DEBUG ONLY!!!
+ static uint32_t max = 0;
+
+ if(volume == 0)
+ max = 0;
+
+ if(volume > max)
+ max = volume;
+
+ printf("%d\n", max);
+ fflush(stdout);
+ */
+ }
+
+ if(props_mask & Upd_Pitch)
+ {
+ AdlChannel::LocationData &d = ch[c].users[my_loc];
+
+ // Don't bend a sustained note
+ if(!d.sustained)
+ {
+ double bend = Ch[MidCh].bend + opl.GetAdlIns(ins).finetune;
+ double phase = 0.0;
+
+ if((ains.flags & adlinsdata::Flag_Pseudo4op) && ins == ains.adlno2)
+ {
+ phase = ains.fine_tune;//0.125; // Detune the note slightly (this is what Doom does)
+ }
+
+ if(Ch[MidCh].vibrato && d.vibdelay >= Ch[MidCh].vibdelay)
+ bend += Ch[MidCh].vibrato * Ch[MidCh].vibdepth * std::sin(Ch[MidCh].vibpos);
+
+ #ifdef ADLMIDI_USE_DOSBOX_OPL
+#define BEND_COEFFICIENT 172.00093
+ #else
+#define BEND_COEFFICIENT 172.4387
+ #endif
+ opl.NoteOn(c, BEND_COEFFICIENT * std::exp(0.057762265 * (tone + bend + phase)));
+#undef BEND_COEFFICIENT
+ }
+ }
+ }
+
+ if(info.phys.empty())
+ Ch[MidCh].activenotes.erase(i);
+}
+
+
+void MIDIplay::ProcessEvents()
+{
+ loopEnd = false;
+ const size_t TrackCount = TrackData.size();
+ const Position RowBeginPosition(CurrentPosition);
+
+ for(size_t tk = 0; tk < TrackCount; ++tk)
+ {
+ if(CurrentPosition.track[tk].status >= 0
+ && CurrentPosition.track[tk].delay <= 0)
+ {
+ // Handle event
+ HandleEvent(tk);
+
+ // Read next event time (unless the track just ended)
+ if(CurrentPosition.track[tk].ptr >= TrackData[tk].size())
+ CurrentPosition.track[tk].status = -1;
+
+ if(CurrentPosition.track[tk].status >= 0)
+ CurrentPosition.track[tk].delay += ReadVarLen(tk);
+ }
+ }
+
+ // Find shortest delay from all track
+ long shortest = -1;
+
+ for(size_t tk = 0; tk < TrackCount; ++tk)
+ if(CurrentPosition.track[tk].status >= 0
+ && (shortest == -1
+ || CurrentPosition.track[tk].delay < shortest))
+ shortest = CurrentPosition.track[tk].delay;
+
+ //if(shortest > 0) UI.PrintLn("shortest: %ld", shortest);
+
+ // Schedule the next playevent to be processed after that delay
+ for(size_t tk = 0; tk < TrackCount; ++tk)
+ CurrentPosition.track[tk].delay -= shortest;
+
+ fraction<long> t = shortest * Tempo;
+
+ if(CurrentPosition.began) CurrentPosition.wait += t.valuel();
+
+ //if(shortest > 0) UI.PrintLn("Delay %ld (%g)", shortest, (double)t.valuel());
+
+ /*
+ if(CurrentPosition.track[0].ptr > 8119) loopEnd = true;
+ // ^HACK: CHRONO TRIGGER LOOP
+ */
+
+ if(loopStart_hit && (loopStart || loopEnd)) //Avoid invalid loops
+ {
+ invalidLoop = true;
+ loopStart = false;
+ loopEnd = false;
+ LoopBeginPosition = trackBeginPosition;
+ }
+ else
+ loopStart_hit = false;
+
+ if(loopStart)
+ {
+ if(trackStart)
+ {
+ trackBeginPosition = RowBeginPosition;
+ trackStart = false;
+ }
+
+ LoopBeginPosition = RowBeginPosition;
+ loopStart = false;
+ loopStart_hit = true;
+ }
+
+ if(shortest < 0 || loopEnd)
+ {
+ // Loop if song end reached
+ loopEnd = false;
+ CurrentPosition = LoopBeginPosition;
+ shortest = 0;
+
+ if(opl._parent->QuitWithoutLooping == 1)
+ {
+ opl._parent->QuitFlag = 1;
+ //^ HACK: QUIT WITHOUT LOOPING
+ }
+ }
+}
+
+void MIDIplay::HandleEvent(size_t tk)
+{
+ unsigned char byte = TrackData[tk][CurrentPosition.track[tk].ptr++];
+
+ if(byte == 0xF7 || byte == 0xF0) // Ignore SysEx
+ {
+ uint64_t length = ReadVarLen(tk);
+ //std::string data( length?(const char*) &TrackData[tk][CurrentPosition.track[tk].ptr]:0, length );
+ CurrentPosition.track[tk].ptr += length;
+ //UI.PrintLn("SysEx %02X: %u bytes", byte, length/*, data.c_str()*/);
+ return;
+ }
+
+ if(byte == 0xFF)
+ {
+ // Special event FF
+ unsigned char evtype = TrackData[tk][CurrentPosition.track[tk].ptr++];
+ unsigned long length = ReadVarLen(tk);
+ std::string data(length ? (const char *) &TrackData[tk][CurrentPosition.track[tk].ptr] : 0, length);
+ CurrentPosition.track[tk].ptr += length;
+
+ if(evtype == 0x2F)
+ {
+ CurrentPosition.track[tk].status = -1;
+ return;
+ }
+
+ if(evtype == 0x51)
+ {
+ Tempo = InvDeltaTicks * fraction<long>((long) ReadBEint(data.data(), data.size()));
+ return;
+ }
+
+ if(evtype == 6)
+ {
+ for(size_t i = 0; i < data.size(); i++)
+ {
+ if(data[i] <= 'Z' && data[i] >= 'A')
+ data[i] = data[i] - ('Z' - 'z');
+ }
+
+ if((data == "loopstart") && (!invalidLoop))
+ {
+ loopStart = true;
+ loopStart_passed = true;
+ }
+
+ if((data == "loopend") && (!invalidLoop))
+ {
+ if((loopStart_passed) && (!loopStart))
+ loopEnd = true;
+ else
+ invalidLoop = true;
+ }
+ }
+
+ if(evtype == 9)
+ current_device[tk] = ChooseDevice(data);
+
+ //if(evtype >= 1 && evtype <= 6)
+ // UI.PrintLn("Meta %d: %s", evtype, data.c_str());
+
+ if(evtype == 0xE3) // Special non-spec ADLMIDI special for IMF playback: Direct poke to AdLib
+ {
+ uint8_t i = static_cast<uint8_t>(data[0]), v = static_cast<uint8_t>(data[1]);
+
+ if((i & 0xF0) == 0xC0)
+ v |= 0x30;
+
+ //std::printf("OPL poke %02X, %02X\n", i, v);
+ //std::fflush(stdout);
+ opl.PokeN(0, i, v);
+ }
+
+ return;
+ }
+
+ // Any normal event (80..EF)
+ if(byte < 0x80)
+ {
+ byte = static_cast<uint8_t>(CurrentPosition.track[tk].status | 0x80);
+ CurrentPosition.track[tk].ptr--;
+ }
+
+ if(byte == 0xF3)
+ {
+ CurrentPosition.track[tk].ptr += 1;
+ return;
+ }
+
+ if(byte == 0xF2)
+ {
+ CurrentPosition.track[tk].ptr += 2;
+ return;
+ }
+
+ /*UI.PrintLn("@%X Track %u: %02X %02X",
+ CurrentPosition.track[tk].ptr-1, (unsigned)tk, byte,
+ TrackData[tk][CurrentPosition.track[tk].ptr]);*/
+ uint8_t MidCh = byte & 0x0F, EvType = byte >> 4;
+ MidCh += current_device[tk];
+ CurrentPosition.track[tk].status = byte;
+
+ switch(EvType)
+ {
+ case 0x8: // Note off
+ case 0x9: // Note on
+ {
+ uint8_t note = TrackData[tk][CurrentPosition.track[tk].ptr++];
+ uint8_t vol = TrackData[tk][CurrentPosition.track[tk].ptr++];
+ //if(MidCh != 9) note -= 12; // HACK
+ NoteOff(MidCh, note);
+
+ // On Note on, Keyoff the note first, just in case keyoff
+ // was omitted; this fixes Dance of sugar-plum fairy
+ // by Microsoft. Now that we've done a Keyoff,
+ // check if we still need to do a Keyon.
+ // vol=0 and event 8x are both Keyoff-only.
+ if(vol == 0 || EvType == 0x8) break;
+
+ uint8_t midiins = Ch[MidCh].patch;
+
+ if(MidCh % 16 == 9)
+ midiins = 128 + note; // Percussion instrument
+
+ /*
+ if(MidCh%16 == 9 || (midiins != 32 && midiins != 46 && midiins != 48 && midiins != 50))
+ break; // HACK
+ if(midiins == 46) vol = (vol*7)/10; // HACK
+ if(midiins == 48 || midiins == 50) vol /= 4; // HACK
+ */
+ //if(midiins == 56) vol = vol*6/10; // HACK
+ static std::set<uint32_t> bank_warnings;
+
+ if(Ch[MidCh].bank_msb)
+ {
+ uint32_t bankid = midiins + 256 * Ch[MidCh].bank_msb;
+ std::set<uint32_t>::iterator
+ i = bank_warnings.lower_bound(bankid);
+
+ if(i == bank_warnings.end() || *i != bankid)
+ {
+ ADLMIDI_ErrorString.clear();
+ std::stringstream s;
+ s << "[" << MidCh << "]Bank " << Ch[MidCh].bank_msb <<
+ " undefined, patch=" << ((midiins & 128) ? 'P' : 'M') << (midiins & 127);
+ ADLMIDI_ErrorString = s.str();
+ bank_warnings.insert(i, bankid);
+ }
+ }
+
+ if(Ch[MidCh].bank_lsb)
+ {
+ unsigned bankid = Ch[MidCh].bank_lsb * 65536;
+ std::set<unsigned>::iterator
+ i = bank_warnings.lower_bound(bankid);
+
+ if(i == bank_warnings.end() || *i != bankid)
+ {
+ ADLMIDI_ErrorString.clear();
+ std::stringstream s;
+ s << "[" << MidCh << "]Bank lsb " << Ch[MidCh].bank_lsb << " undefined";
+ ADLMIDI_ErrorString = s.str();
+ bank_warnings.insert(i, bankid);
+ }
+ }
+
+ //int meta = banks[opl.AdlBank][midiins];
+ const uint32_t meta = opl.GetAdlMetaNumber(midiins);
+ const adlinsdata &ains = opl.GetAdlMetaIns(meta);
+ int16_t tone = note;
+
+ if(ains.tone)
+ {
+ if(ains.tone < 20)
+ tone += ains.tone;
+ else if(ains.tone < 128)
+ tone = ains.tone;
+ else
+ tone -= ains.tone - 128;
+ }
+
+ uint16_t i[2] = { ains.adlno1, ains.adlno2 };
+ bool pseudo_4op = ains.flags & adlinsdata::Flag_Pseudo4op;
+
+ if((opl.AdlPercussionMode == 1) && PercussionMap[midiins & 0xFF]) i[1] = i[0];
+
+ static std::set<uint8_t> missing_warnings;
+
+ if(!missing_warnings.count(static_cast<uint8_t>(midiins)) && (ains.flags & adlinsdata::Flag_NoSound))
+ {
+ //UI.PrintLn("[%i]Playing missing instrument %i", MidCh, midiins);
+ missing_warnings.insert(static_cast<uint8_t>(midiins));
+ }
+
+ // Allocate AdLib channel (the physical sound channel for the note)
+ int32_t adlchannel[2] = { -1, -1 };
+
+ for(uint32_t ccount = 0; ccount < 2; ++ccount)
+ {
+ if(ccount == 1)
+ {
+ if(i[0] == i[1]) break; // No secondary channel
+
+ if(adlchannel[0] == -1)
+ break; // No secondary if primary failed
+ }
+
+ int32_t c = -1;
+ long bs = -0x7FFFFFFFl;
+
+ for(uint32_t a = 0; a < opl.NumChannels; ++a)
+ {
+ if(ccount == 1 && static_cast<int32_t>(a) == adlchannel[0]) continue;
+
+ // ^ Don't use the same channel for primary&secondary
+
+ if(i[0] == i[1] || pseudo_4op)
+ {
+ // Only use regular channels
+ uint8_t expected_mode = 0;
+
+ if(opl.AdlPercussionMode == 1)
+ {
+ if(cmf_percussion_mode)
+ expected_mode = MidCh < 11 ? 0 : (3 + MidCh - 11); // CMF
+ else
+ expected_mode = PercussionMap[midiins & 0xFF];
+ }
+
+ if(opl.four_op_category[a] != expected_mode)
+ continue;
+ }
+ else
+ {
+ if(ccount == 0)
+ {
+ // Only use four-op master channels
+ if(opl.four_op_category[a] != 1)
+ continue;
+ }
+ else
+ {
+ // The secondary must be played on a specific channel.
+ if(a != static_cast<uint32_t>(adlchannel[0]) + 3)
+ continue;
+ }
+ }
+
+ long s = CalculateAdlChannelGoodness(a, i[ccount], MidCh);
+
+ if(s > bs)
+ {
+ bs = s; // Best candidate wins
+ c = static_cast<int32_t>(a);
+ }
+ }
+
+ if(c < 0)
+ {
+ //UI.PrintLn("ignored unplaceable note");
+ continue; // Could not play this note. Ignore it.
+ }
+
+ PrepareAdlChannelForNewNote(static_cast<size_t>(c), i[ccount]);
+ adlchannel[ccount] = c;
+ }
+
+ if(adlchannel[0] < 0 && adlchannel[1] < 0)
+ {
+ // The note could not be played, at all.
+ break;
+ }
+
+ //UI.PrintLn("i1=%d:%d, i2=%d:%d", i[0],adlchannel[0], i[1],adlchannel[1]);
+ // Allocate active note for MIDI channel
+ std::pair<MIDIchannel::activenoteiterator, bool>
+ ir = Ch[MidCh].activenotes.insert(
+ std::make_pair(note, MIDIchannel::NoteInfo()));
+ ir.first->second.vol = vol;
+ ir.first->second.tone = tone;
+ ir.first->second.midiins = midiins;
+ ir.first->second.insmeta = meta;
+
+ for(unsigned ccount = 0; ccount < 2; ++ccount)
+ {
+ int32_t c = adlchannel[ccount];
+
+ if(c < 0)
+ continue;
+
+ ir.first->second.phys[ static_cast<uint16_t>(adlchannel[ccount]) ] = i[ccount];
+ }
+
+ CurrentPosition.began = true;
+ NoteUpdate(MidCh, ir.first, Upd_All | Upd_Patch);
+ break;
+ }
+
+ case 0xA: // Note touch
+ {
+ uint8_t note = TrackData[tk][CurrentPosition.track[tk].ptr++];
+ uint8_t vol = TrackData[tk][CurrentPosition.track[tk].ptr++];
+ MIDIchannel::activenoteiterator
+ i = Ch[MidCh].activenotes.find(note);
+
+ if(i == Ch[MidCh].activenotes.end())
+ {
+ // Ignore touch if note is not active
+ break;
+ }
+
+ i->second.vol = vol;
+ NoteUpdate(MidCh, i, Upd_Volume);
+ break;
+ }
+
+ case 0xB: // Controller change
+ {
+ uint8_t ctrlno = TrackData[tk][CurrentPosition.track[tk].ptr++];
+ uint8_t value = TrackData[tk][CurrentPosition.track[tk].ptr++];
+
+ switch(ctrlno)
+ {
+ case 1: // Adjust vibrato
+ //UI.PrintLn("%u:vibrato %d", MidCh,value);
+ Ch[MidCh].vibrato = value;
+ break;
+
+ case 0: // Set bank msb (GM bank)
+ Ch[MidCh].bank_msb = value;
+ break;
+
+ case 32: // Set bank lsb (XG bank)
+ Ch[MidCh].bank_lsb = value;
+ break;
+
+ case 5: // Set portamento msb
+ Ch[MidCh].portamento = static_cast<uint16_t>((Ch[MidCh].portamento & 0x7F) | (value << 7));
+ //UpdatePortamento(MidCh);
+ break;
+
+ case 37: // Set portamento lsb
+ Ch[MidCh].portamento = (Ch[MidCh].portamento & 0x3F80) | (value);
+ //UpdatePortamento(MidCh);
+ break;
+
+ case 65: // Enable/disable portamento
+ // value >= 64 ? enabled : disabled
+ //UpdatePortamento(MidCh);
+ break;
+
+ case 7: // Change volume
+ Ch[MidCh].volume = value;
+ NoteUpdate_All(MidCh, Upd_Volume);
+ break;
+
+ case 64: // Enable/disable sustain
+ Ch[MidCh].sustain = value;
+
+ if(!value) KillSustainingNotes(MidCh);
+
+ break;
+
+ case 11: // Change expression (another volume factor)
+ Ch[MidCh].expression = value;
+ NoteUpdate_All(MidCh, Upd_Volume);
+ break;
+
+ case 10: // Change panning
+ Ch[MidCh].panning = 0x00;
+
+ if(value < 64 + 32) Ch[MidCh].panning |= 0x10;
+
+ if(value >= 64 - 32) Ch[MidCh].panning |= 0x20;
+
+ NoteUpdate_All(MidCh, Upd_Pan);
+ break;
+
+ case 121: // Reset all controllers
+ Ch[MidCh].bend = 0;
+ Ch[MidCh].volume = 100;
+ Ch[MidCh].expression = 100;
+ Ch[MidCh].sustain = 0;
+ Ch[MidCh].vibrato = 0;
+ Ch[MidCh].vibspeed = 2 * 3.141592653 * 5.0;
+ Ch[MidCh].vibdepth = 0.5 / 127;
+ Ch[MidCh].vibdelay = 0;
+ Ch[MidCh].panning = 0x30;
+ Ch[MidCh].portamento = 0;
+ //UpdatePortamento(MidCh);
+ NoteUpdate_All(MidCh, Upd_Pan + Upd_Volume + Upd_Pitch);
+ // Kill all sustained notes
+ KillSustainingNotes(MidCh);
+ break;
+
+ case 123: // All notes off
+ NoteUpdate_All(MidCh, Upd_Off);
+ break;
+
+ case 91:
+ break; // Reverb effect depth. We don't do per-channel reverb.
+
+ case 92:
+ break; // Tremolo effect depth. We don't do...
+
+ case 93:
+ break; // Chorus effect depth. We don't do.
+
+ case 94:
+ break; // Celeste effect depth. We don't do.
+
+ case 95:
+ break; // Phaser effect depth. We don't do.
+
+ case 98:
+ Ch[MidCh].lastlrpn = value;
+ Ch[MidCh].nrpn = true;
+ break;
+
+ case 99:
+ Ch[MidCh].lastmrpn = value;
+ Ch[MidCh].nrpn = true;
+ break;
+
+ case 100:
+ Ch[MidCh].lastlrpn = value;
+ Ch[MidCh].nrpn = false;
+ break;
+
+ case 101:
+ Ch[MidCh].lastmrpn = value;
+ Ch[MidCh].nrpn = false;
+ break;
+
+ case 113:
+ break; // Related to pitch-bender, used by missimp.mid in Duke3D
+
+ case 6:
+ SetRPN(MidCh, value, true);
+ break;
+
+ case 38:
+ SetRPN(MidCh, value, false);
+ break;
+
+ case 103:
+ cmf_percussion_mode = value;
+ break; // CMF (ctrl 0x67) rhythm mode
+
+ case 111://LoopStart unofficial controller
+ if(!invalidLoop)
+ {
+ loopStart = true;
+ loopStart_passed = true;
+ }
+
+ break;
+
+ default:
+ break;
+ //UI.PrintLn("Ctrl %d <- %d (ch %u)", ctrlno, value, MidCh);
+ }
+
+ break;
+ }
+
+ case 0xC: // Patch change
+ Ch[MidCh].patch = TrackData[tk][CurrentPosition.track[tk].ptr++];
+ break;
+
+ case 0xD: // Channel after-touch
+ {
+ // TODO: Verify, is this correct action?
+ uint8_t vol = TrackData[tk][CurrentPosition.track[tk].ptr++];
+
+ for(MIDIchannel::activenoteiterator
+ i = Ch[MidCh].activenotes.begin();
+ i != Ch[MidCh].activenotes.end();
+ ++i)
+ {
+ // Set this pressure to all active notes on the channel
+ i->second.vol = vol;
+ }
+
+ NoteUpdate_All(MidCh, Upd_Volume);
+ break;
+ }
+
+ case 0xE: // Wheel/pitch bend
+ {
+ int a = TrackData[tk][CurrentPosition.track[tk].ptr++];
+ int b = TrackData[tk][CurrentPosition.track[tk].ptr++];
+ Ch[MidCh].bend = (a + b * 128 - 8192) * Ch[MidCh].bendsense;
+ NoteUpdate_All(MidCh, Upd_Pitch);
+ break;
+ }
+ }
+}
+
+long MIDIplay::CalculateAdlChannelGoodness(unsigned c, uint16_t ins, uint16_t) const
+{
+ long s = -ch[c].koff_time_until_neglible;
+
+ // Same midi-instrument = some stability
+ //if(c == MidCh) s += 4;
+ for(AdlChannel::users_t::const_iterator
+ j = ch[c].users.begin();
+ j != ch[c].users.end();
+ ++j)
+ {
+ s -= 4000;
+
+ if(!j->second.sustained)
+ s -= j->second.kon_time_until_neglible;
+ else
+ s -= j->second.kon_time_until_neglible / 2;
+
+ MIDIchannel::activenotemap_t::const_iterator
+ k = Ch[j->first.MidCh].activenotes.find(j->first.note);
+
+ if(k != Ch[j->first.MidCh].activenotes.end())
+ {
+ // Same instrument = good
+ if(j->second.ins == ins)
+ {
+ s += 300;
+
+ // Arpeggio candidate = even better
+ if(j->second.vibdelay < 70
+ || j->second.kon_time_until_neglible > 20000)
+ s += 0;
+ }
+
+ // Percussion is inferior to melody
+ s += 50 * (k->second.midiins / 128);
+ /*
+ if(k->second.midiins >= 25
+ && k->second.midiins < 40
+ && j->second.ins != ins)
+ {
+ s -= 14000; // HACK: Don't clobber the bass or the guitar
+ }
+ */
+ }
+
+ // If there is another channel to which this note
+ // can be evacuated to in the case of congestion,
+ // increase the score slightly.
+ unsigned n_evacuation_stations = 0;
+
+ for(unsigned c2 = 0; c2 < opl.NumChannels; ++c2)
+ {
+ if(c2 == c) continue;
+
+ if(opl.four_op_category[c2]
+ != opl.four_op_category[c]) continue;
+
+ for(AdlChannel::users_t::const_iterator
+ m = ch[c2].users.begin();
+ m != ch[c2].users.end();
+ ++m)
+ {
+ if(m->second.sustained) continue;
+
+ if(m->second.vibdelay >= 200) continue;
+
+ if(m->second.ins != j->second.ins) continue;
+
+ n_evacuation_stations += 1;
+ }
+ }
+
+ s += n_evacuation_stations * 4;
+ }
+
+ return s;
+}
+
+
+void MIDIplay::PrepareAdlChannelForNewNote(size_t c, int ins)
+{
+ if(ch[c].users.empty()) return; // Nothing to do
+
+ //bool doing_arpeggio = false;
+ for(AdlChannel::users_t::iterator
+ jnext = ch[c].users.begin();
+ jnext != ch[c].users.end();
+ )
+ {
+ AdlChannel::users_t::iterator j(jnext++);
+
+ if(!j->second.sustained)
+ {
+ // Collision: Kill old note,
+ // UNLESS we're going to do arpeggio
+ MIDIchannel::activenoteiterator i
+ (Ch[j->first.MidCh].activenotes.find(j->first.note));
+
+ // Check if we can do arpeggio.
+ if((j->second.vibdelay < 70
+ || j->second.kon_time_until_neglible > 20000)
+ && j->second.ins == ins)
+ {
+ // Do arpeggio together with this note.
+ //doing_arpeggio = true;
+ continue;
+ }
+
+ KillOrEvacuate(c, j, i);
+ // ^ will also erase j from ch[c].users.
+ }
+ }
+
+ // Kill all sustained notes on this channel
+ // Don't keep them for arpeggio, because arpeggio requires
+ // an intact "activenotes" record. This is a design flaw.
+ KillSustainingNotes(-1, static_cast<int32_t>(c));
+
+ // Keyoff the channel so that it can be retriggered,
+ // unless the new note will be introduced as just an arpeggio.
+ if(ch[c].users.empty())
+ opl.NoteOff(c);
+}
+
+void MIDIplay::KillOrEvacuate(size_t from_channel, AdlChannel::users_t::iterator j, MIDIplay::MIDIchannel::activenoteiterator i)
+{
+ // Before killing the note, check if it can be
+ // evacuated to another channel as an arpeggio
+ // 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 < opl.NumChannels; ++c)
+ {
+ uint16_t cs = static_cast<uint16_t>(c);
+
+ if(c > std::numeric_limits<uint32_t>::max())
+ break;
+
+ if(c == from_channel)
+ continue;
+
+ if(opl.four_op_category[c]
+ != opl.four_op_category[from_channel]
+ ) continue;
+
+ for(AdlChannel::users_t::iterator
+ m = ch[c].users.begin();
+ m != ch[c].users.end();
+ ++m)
+ {
+ if(m->second.vibdelay >= 200
+ && m->second.kon_time_until_neglible < 10000) continue;
+
+ if(m->second.ins != j->second.ins) continue;
+
+ // the note can be moved here!
+ // UI.IllustrateNote(
+ // from_channel,
+ // i->second.tone,
+ // i->second.midiins, 0, 0.0);
+ // UI.IllustrateNote(
+ // c,
+ // i->second.tone,
+ // i->second.midiins,
+ // i->second.vol,
+ // 0.0);
+ i->second.phys.erase(static_cast<uint16_t>(from_channel));
+ i->second.phys[cs] = j->second.ins;
+ ch[cs].users.insert(*j);
+ ch[from_channel].users.erase(j);
+ return;
+ }
+ }
+
+ /*UI.PrintLn(
+ "collision @%u: [%ld] <- ins[%3u]",
+ c,
+ //ch[c].midiins<128?'M':'P', ch[c].midiins&127,
+ ch[c].age, //adlins[ch[c].insmeta].ms_sound_kon,
+ ins
+ );*/
+ // Kill it
+ NoteUpdate(j->first.MidCh,
+ i,
+ Upd_Off,
+ static_cast<int32_t>(from_channel));
+}
+
+void MIDIplay::KillSustainingNotes(int32_t MidCh, int32_t this_adlchn)
+{
+ uint32_t first = 0, last = opl.NumChannels;
+
+ if(this_adlchn >= 0)
+ {
+ first = static_cast<uint32_t>(this_adlchn);
+ last = first + 1;
+ }
+
+ for(unsigned c = first; c < last; ++c)
+ {
+ if(ch[c].users.empty()) continue; // Nothing to do
+
+ for(AdlChannel::users_t::iterator
+ jnext = ch[c].users.begin();
+ jnext != ch[c].users.end();
+ )
+ {
+ AdlChannel::users_t::iterator j(jnext++);
+
+ if((MidCh < 0 || j->first.MidCh == MidCh)
+ && j->second.sustained)
+ {
+ //int midiins = '?';
+ //UI.IllustrateNote(c, j->first.note, midiins, 0, 0.0);
+ ch[c].users.erase(j);
+ }
+ }
+
+ // Keyoff the channel, if there are no users left.
+ if(ch[c].users.empty())
+ opl.NoteOff(c);
+ }
+}
+
+void MIDIplay::SetRPN(unsigned MidCh, unsigned value, bool MSB)
+{
+ bool nrpn = Ch[MidCh].nrpn;
+ unsigned addr = Ch[MidCh].lastmrpn * 0x100 + Ch[MidCh].lastlrpn;
+
+ switch(addr + nrpn * 0x10000 + MSB * 0x20000)
+ {
+ case 0x0000 + 0*0x10000 + 1*0x20000: // Pitch-bender sensitivity
+ Ch[MidCh].bendsense = value / 8192.0;
+ break;
+
+ case 0x0108 + 1*0x10000 + 1*0x20000: // Vibrato speed
+ if(value == 64)
+ Ch[MidCh].vibspeed = 1.0;
+ else if(value < 100)
+ Ch[MidCh].vibspeed = 1.0 / (1.6e-2 * (value ? value : 1));
+ else
+ Ch[MidCh].vibspeed = 1.0 / (0.051153846 * value - 3.4965385);
+
+ Ch[MidCh].vibspeed *= 2 * 3.141592653 * 5.0;
+ break;
+
+ case 0x0109 + 1*0x10000 + 1*0x20000: // Vibrato depth
+ Ch[MidCh].vibdepth = ((value - 64) * 0.15) * 0.01;
+ break;
+
+ case 0x010A + 1*0x10000 + 1*0x20000: // Vibrato delay in millisecons
+ Ch[MidCh].vibdelay =
+ value ? long(0.2092 * std::exp(0.0795 * value)) : 0.0;
+ break;
+
+ default:/* UI.PrintLn("%s %04X <- %d (%cSB) (ch %u)",
+ "NRPN"+!nrpn, addr, value, "LM"[MSB], MidCh);*/
+ break;
+ }
+}
+
+//void MIDIplay::UpdatePortamento(unsigned MidCh)
+//{
+// // mt = 2^(portamento/2048) * (1.0 / 5000.0)
+// /*
+// double mt = std::exp(0.00033845077 * Ch[MidCh].portamento);
+// NoteUpdate_All(MidCh, Upd_Pitch);
+// */
+// //UI.PrintLn("Portamento %u: %u (unimplemented)", MidCh, Ch[MidCh].portamento);
+//}
+
+void MIDIplay::NoteUpdate_All(uint16_t MidCh, unsigned props_mask)
+{
+ for(MIDIchannel::activenoteiterator
+ i = Ch[MidCh].activenotes.begin();
+ i != Ch[MidCh].activenotes.end();
+ )
+ {
+ MIDIchannel::activenoteiterator j(i++);
+ NoteUpdate(MidCh, j, props_mask);
+ }
+}
+
+void MIDIplay::NoteOff(uint16_t MidCh, uint8_t note)
+{
+ MIDIchannel::activenoteiterator
+ i = Ch[MidCh].activenotes.find(note);
+
+ if(i != Ch[MidCh].activenotes.end())
+ NoteUpdate(MidCh, i, Upd_Off);
+}
+
+
+void MIDIplay::UpdateVibrato(double amount)
+{
+ for(size_t a = 0, b = Ch.size(); a < b; ++a)
+ if(Ch[a].vibrato && !Ch[a].activenotes.empty())
+ {
+ NoteUpdate_All(static_cast<uint16_t>(a), Upd_Pitch);
+ Ch[a].vibpos += amount * Ch[a].vibspeed;
+ }
+ else
+ Ch[a].vibpos = 0.0;
+}
+
+
+
+
+uint64_t MIDIplay::ChooseDevice(const std::string &name)
+{
+ std::map<std::string, uint64_t>::iterator i = devices.find(name);
+
+ if(i != devices.end())
+ return i->second;
+
+ size_t n = devices.size() * 16;
+ devices.insert(std::make_pair(name, n));
+ Ch.resize(n + 16);
+ return n;
+}
+
+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.
+ #if 0
+ const unsigned desired_arpeggio_rate = 40; // Hz (upper limit)
+ #if 1
+ static unsigned cache = 0;
+ amount = amount; // Ignore amount. Assume we get a constant rate.
+ cache += MaxSamplesAtTime * desired_arpeggio_rate;
+
+ if(cache < PCM_RATE) return;
+
+ cache %= PCM_RATE;
+ #else
+ static double arpeggio_cache = 0;
+ arpeggio_cache += amount * desired_arpeggio_rate;
+
+ if(arpeggio_cache < 1.0) return;
+
+ arpeggio_cache = 0.0;
+ #endif
+ #endif
+ static unsigned arpeggio_counter = 0;
+ ++arpeggio_counter;
+
+ for(uint32_t c = 0; c < opl.NumChannels; ++c)
+ {
+retry_arpeggio:
+
+ if(c > std::numeric_limits<int32_t>::max())
+ break;
+
+ size_t n_users = ch[c].users.size();
+
+ if(n_users > 1)
+ {
+ AdlChannel::users_t::const_iterator i = ch[c].users.begin();
+ size_t rate_reduction = 3;
+
+ if(n_users >= 3)
+ rate_reduction = 2;
+
+ if(n_users >= 4)
+ rate_reduction = 1;
+
+ std::advance(i, (arpeggio_counter / rate_reduction) % n_users);
+
+ if(i->second.sustained == false)
+ {
+ if(i->second.kon_time_until_neglible <= 0l)
+ {
+ NoteUpdate(
+ i->first.MidCh,
+ Ch[ i->first.MidCh ].activenotes.find(i->first.note),
+ Upd_Off,
+ static_cast<int32_t>(c));
+ goto retry_arpeggio;
+ }
+
+ NoteUpdate(
+ i->first.MidCh,
+ Ch[ i->first.MidCh ].activenotes.find(i->first.note),
+ Upd_Pitch | Upd_Volume | Upd_Pan,
+ static_cast<int32_t>(c));
+ }
+ }
+ }
+}