From b2890608189a69695a5119e1be771b89278dce9b Mon Sep 17 00:00:00 2001 From: Wohlstand Date: Mon, 16 Jan 2017 08:24:19 +0300 Subject: Split adlmidi.cpp now keeps API functions only other code has been moved into separated files --- src/adlmidi.cpp | 2752 +--------------------------------------------- src/adlmidi.h | 18 +- src/adlmidi_load.cpp | 434 ++++++++ src/adlmidi_midiplay.cpp | 1360 +++++++++++++++++++++++ src/adlmidi_opl3.cpp | 515 +++++++++ src/adlmidi_private.cpp | 59 + src/adlmidi_private.hpp | 494 +++++++++ src/dbopl.cpp | 108 +- src/nukedopl3.c | 150 +-- src/nukedopl3.h | 52 +- 10 files changed, 3078 insertions(+), 2864 deletions(-) create mode 100644 src/adlmidi_load.cpp create mode 100644 src/adlmidi_midiplay.cpp create mode 100644 src/adlmidi_opl3.cpp create mode 100644 src/adlmidi_private.cpp create mode 100644 src/adlmidi_private.hpp (limited to 'src') diff --git a/src/adlmidi.cpp b/src/adlmidi.cpp index 67cd853..4c7fcf7 100644 --- a/src/adlmidi.cpp +++ b/src/adlmidi.cpp @@ -2,7 +2,7 @@ * libADLMIDI is a free MIDI to WAV conversion library with OPL3 emulation * * Original ADLMIDI code: Copyright (c) 2010-2014 Joel Yliluoma - * ADLMIDI Library API: Copyright (c) 2016 Vitaly Novichkov + * ADLMIDI Library API: Copyright (c) 2017 Vitaly Novichkov * * Library is based on the ADLMIDI, a MIDI player for Linux and Windows with OPL3 emulation: * http://iki.fi/bisqwit/source/adlmidi.html @@ -21,2685 +21,14 @@ * along with this program. If not, see . */ -// Setup compiler defines useful for exporting required public API symbols in gme.cpp -#ifndef ADLMIDI_EXPORT -#if defined (_WIN32) && defined(ADLMIDI_BUILD_DLL) -#define ADLMIDI_EXPORT __declspec(dllexport) -#elif defined (LIBADLMIDI_VISIBILITY) -#define ADLMIDI_EXPORT __attribute__((visibility ("default"))) -#else -#define ADLMIDI_EXPORT -#endif -#endif - -#ifdef _WIN32 -#undef NO_OLDNAMES -#endif -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include // vector -#include // deque -#include // exp, log, ceil -#include -#include // numeric_limit - -#include -#include - -#include "fraction.h" -#ifdef ADLMIDI_USE_DOSBOX_OPL -#include "dbopl.h" -#else -#include "nukedopl3.h" -#endif - -#include "adldata.hh" -#include "adlmidi.h" - -#ifdef ADLMIDI_buildAsApp -#define SDL_MAIN_HANDLED -#include -#endif - -class MIDIplay; - -static const unsigned MaxCards = 100; -static std::string ADLMIDI_ErrorString; - -static const unsigned short Operators[23 * 2] = -{ - // Channels 0-2 - 0x000, 0x003, 0x001, 0x004, 0x002, 0x005, // operators 0, 3, 1, 4, 2, 5 - // Channels 3-5 - 0x008, 0x00B, 0x009, 0x00C, 0x00A, 0x00D, // operators 6, 9, 7,10, 8,11 - // Channels 6-8 - 0x010, 0x013, 0x011, 0x014, 0x012, 0x015, // operators 12,15, 13,16, 14,17 - // Same for second card - 0x100, 0x103, 0x101, 0x104, 0x102, 0x105, // operators 18,21, 19,22, 20,23 - 0x108, 0x10B, 0x109, 0x10C, 0x10A, 0x10D, // operators 24,27, 25,28, 26,29 - 0x110, 0x113, 0x111, 0x114, 0x112, 0x115, // operators 30,33, 31,34, 32,35 - // Channel 18 - 0x010, 0x013, // operators 12,15 - // Channel 19 - 0x014, 0xFFF, // operator 16 - // Channel 19 - 0x012, 0xFFF, // operator 14 - // Channel 19 - 0x015, 0xFFF, // operator 17 - // Channel 19 - 0x011, 0xFFF -}; // operator 13 - -static const unsigned short Channels[23] = -{ - 0x000, 0x001, 0x002, 0x003, 0x004, 0x005, 0x006, 0x007, 0x008, // 0..8 - 0x100, 0x101, 0x102, 0x103, 0x104, 0x105, 0x106, 0x107, 0x108, // 9..17 (secondary set) - 0x006, 0x007, 0x008, 0xFFF, 0xFFF -}; // <- hw percussions, 0xFFF = no support for pitch/pan - -/* - In OPL3 mode: - 0 1 2 6 7 8 9 10 11 16 17 18 - op0 op1 op2 op12 op13 op14 op18 op19 op20 op30 op31 op32 - op3 op4 op5 op15 op16 op17 op21 op22 op23 op33 op34 op35 - 3 4 5 13 14 15 - op6 op7 op8 op24 op25 op26 - op9 op10 op11 op27 op28 op29 - Ports: - +0 +1 +2 +10 +11 +12 +100 +101 +102 +110 +111 +112 - +3 +4 +5 +13 +14 +15 +103 +104 +105 +113 +114 +115 - +8 +9 +A +108 +109 +10A - +B +C +D +10B +10C +10D - - Percussion: - bassdrum = op(0): 0xBD bit 0x10, operators 12 (0x10) and 15 (0x13) / channels 6, 6b - snare = op(3): 0xBD bit 0x08, operators 16 (0x14) / channels 7b - tomtom = op(4): 0xBD bit 0x04, operators 14 (0x12) / channels 8 - cym = op(5): 0xBD bit 0x02, operators 17 (0x17) / channels 8b - hihat = op(2): 0xBD bit 0x01, operators 13 (0x11) / channels 7 - - - In OPTi mode ("extended FM" in 82C924, 82C925, 82C931 chips): - 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 - op0 op4 op6 op10 op12 op16 op18 op22 op24 op28 op30 op34 op36 op38 op40 op42 op44 op46 - op1 op5 op7 op11 op13 op17 op19 op23 op25 op29 op31 op35 op37 op39 op41 op43 op45 op47 - op2 op8 op14 op20 op26 op32 - op3 op9 op15 op21 op27 op33 for a total of 6 quad + 12 dual - Ports: ??? -*/ - - -// 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 -}; - -struct OPL3 -{ - friend class MIDIplay; - uint32_t NumChannels; - char ____padding[4]; - ADL_MIDIPlayer *_parent; -#ifdef ADLMIDI_USE_DOSBOX_OPL - std::vector cards; -#else - std::vector<_opl3_chip> cards; -#endif - private: - std::vector ins; // index to adl[], cached, needed by Touch() - std::vector pit; // value poked to B0, cached, needed by NoteOff)( - std::vector regBD; - - std::vector dynamic_metainstruments; // Replaces adlins[] when CMF file - std::vector dynamic_instruments; // Replaces adl[] when CMF file - const unsigned DynamicInstrumentTag /* = 0x8000u*/, DynamicMetaInstrumentTag /* = 0x4000000u*/; - const adlinsdata &GetAdlMetaIns(unsigned n) - { - return (n & DynamicMetaInstrumentTag) ? - dynamic_metainstruments[n & ~DynamicMetaInstrumentTag] - : adlins[n]; - } - unsigned GetAdlMetaNumber(unsigned midiins) - { - return (AdlBank == ~0u) ? - (midiins | DynamicMetaInstrumentTag) - : banks[AdlBank][midiins]; - } - const adldata &GetAdlIns(unsigned short insno) - { - return (insno & DynamicInstrumentTag) - ? dynamic_instruments[insno & ~DynamicInstrumentTag] - : adl[insno]; - } - - public: - unsigned int NumCards; - unsigned int AdlBank; - unsigned int NumFourOps; - bool HighTremoloMode; - bool HighVibratoMode; - bool AdlPercussionMode; - bool ScaleModulators; - - bool LogarithmicVolumes; - char ___padding2[3]; - enum VolumesScale - { - VOLUME_Generic, - VOLUME_CMF, - VOLUME_DMX, - VOLUME_APOGEE, - VOLUME_9X, - } m_volumeScale; - - OPL3() : - DynamicInstrumentTag(0x8000u), - DynamicMetaInstrumentTag(0x4000000u), - NumCards(1), - AdlBank(0), - NumFourOps(0), - HighTremoloMode(false), - HighVibratoMode(false), - AdlPercussionMode(false), - LogarithmicVolumes(false), - m_volumeScale(VOLUME_Generic) - {} - char ____padding3[8]; - std::vector four_op_category; // 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 - - void Poke(size_t card, uint32_t index, uint32_t value) - { -#ifdef ADLMIDI_USE_DOSBOX_OPL - cards[card].WriteReg(index, static_cast(value)); -#else - OPL3_WriteReg(&cards[card], static_cast(index), static_cast(value)); -#endif - } - void PokeN(size_t card, uint16_t index, uint8_t value) - { -#ifdef ADLMIDI_USE_DOSBOX_OPL - cards[card].WriteReg(static_cast(index), value); -#else - OPL3_WriteReg(&cards[card], index, value); -#endif - } - void NoteOff(size_t c) - { - size_t card = c / 23, cc = c % 23; - - if(cc >= 18) - { - regBD[card] &= ~(0x10 >> (cc - 18)); - Poke(card, 0xBD, regBD[card]); - return; - } - - Poke(card, 0xB0 + Channels[cc], pit[c] & 0xDF); - } - void NoteOn(unsigned c, double hertz) // Hertz range: 0..131071 - { - unsigned card = c / 23, cc = c % 23; - unsigned x = 0x2000; - - if(hertz < 0 || hertz > 131071) // Avoid infinite loop - return; - - while(hertz >= 1023.5) - { - hertz /= 2.0; // Calculate octave - x += 0x400; - } - - x += static_cast(hertz + 0.5); - unsigned chn = Channels[cc]; - - if(cc >= 18) - { - regBD[card] |= (0x10 >> (cc - 18)); - Poke(card, 0x0BD, regBD[card]); - x &= ~0x2000u; - //x |= 0x800; // for test - } - - if(chn != 0xFFF) - { - Poke(card, 0xA0 + chn, x & 0xFF); - Poke(card, 0xB0 + chn, pit[c] = static_cast(x >> 8)); - } - } - void Touch_Real(unsigned c, unsigned volume) - { - if(volume > 63) volume = 63; - - unsigned card = c / 23, cc = c % 23; - uint16_t i = ins[c]; - unsigned o1 = Operators[cc * 2 + 0]; - unsigned o2 = Operators[cc * 2 + 1]; - const adldata &adli = GetAdlIns(i); - unsigned x = adli.modulator_40, y = adli.carrier_40; - unsigned mode = 1; // 2-op AM - - if(four_op_category[c] == 0 || four_op_category[c] == 3) - { - mode = adli.feedconn & 1; // 2-op FM or 2-op AM - } - else if(four_op_category[c] == 1 || four_op_category[c] == 2) - { - uint16_t i0, i1; - - if(four_op_category[c] == 1) - { - i0 = i; - i1 = ins[c + 3]; - mode = 2; // 4-op xx-xx ops 1&2 - } - else - { - i0 = ins[c - 3]; - i1 = i; - mode = 6; // 4-op xx-xx ops 3&4 - } - - mode += (GetAdlIns(i0).feedconn & 1) + (GetAdlIns(i1).feedconn & 1) * 2; - } - - static const bool do_ops[10][2] = - { - { false, true }, /* 2 op FM */ - { true, true }, /* 2 op AM */ - { false, false }, /* 4 op FM-FM ops 1&2 */ - { true, false }, /* 4 op AM-FM ops 1&2 */ - { false, true }, /* 4 op FM-AM ops 1&2 */ - { true, false }, /* 4 op AM-AM ops 1&2 */ - { false, true }, /* 4 op FM-FM ops 3&4 */ - { false, true }, /* 4 op AM-FM ops 3&4 */ - { false, true }, /* 4 op FM-AM ops 3&4 */ - { true, true } /* 4 op AM-AM ops 3&4 */ - }; - bool do_modulator = do_ops[ mode ][ 0 ] || ScaleModulators; - bool do_carrier = do_ops[ mode ][ 1 ] || ScaleModulators; - Poke(card, 0x40 + o1, do_modulator ? (x | 63) - volume + volume * (x & 63) / 63 : x); - - if(o2 != 0xFFF) - Poke(card, 0x40 + o2, do_carrier ? (y | 63) - volume + volume * (y & 63) / 63 : y); - - // Correct formula (ST3, AdPlug): - // 63-((63-(instrvol))/63)*chanvol - // Reduces to (tested identical): - // 63 - chanvol + chanvol*instrvol/63 - // Also (slower, floats): - // 63 + chanvol * (instrvol / 63.0 - 1) - } - /* - void Touch(unsigned c, unsigned volume) // Volume maxes at 127*127*127 - { - if(LogarithmicVolumes) - Touch_Real(c, volume * 127 / (127 * 127 * 127) / 2); - else - { - // The formula below: SOLVE(V=127^3 * 2^( (A-63.49999) / 8), A) - Touch_Real(c, volume > 8725 ? static_cast(std::log(volume) * 11.541561 + (0.5 - 104.22845)) : 0); - // The incorrect formula below: SOLVE(V=127^3 * (2^(A/63)-1), A) - //Touch_Real(c, volume>11210 ? 91.61112 * std::log(4.8819E-7*volume + 1.0)+0.5 : 0); - } - }*/ - void Patch(uint16_t c, uint16_t i) - { - uint16_t card = c / 23, cc = c % 23; - static const uint8_t data[4] = {0x20, 0x60, 0x80, 0xE0}; - ins[c] = i; - uint16_t o1 = Operators[cc * 2 + 0]; - uint16_t o2 = Operators[cc * 2 + 1]; - const adldata &adli = GetAdlIns(i); - unsigned x = adli.modulator_E862, y = adli.carrier_E862; - - for(unsigned a = 0; a < 4; ++a, x >>= 8, y >>= 8) - { - Poke(card, data[a] + o1, x & 0xFF); - - if(o2 != 0xFFF) - Poke(card, data[a] + o2, y & 0xFF); - } - } - void Pan(unsigned c, unsigned value) - { - unsigned card = c / 23, cc = c % 23; - - if(Channels[cc] != 0xFFF) - Poke(card, 0xC0 + Channels[cc], GetAdlIns(ins[c]).feedconn | value); - } - void Silence() // Silence all OPL channels. - { - for(unsigned c = 0; c < NumChannels; ++c) - { - NoteOff(c); - Touch_Real(c, 0); - } - } - - void updateFlags() - { - unsigned fours = NumFourOps; - - for(unsigned card = 0; card < NumCards; ++card) - { - Poke(card, 0x0BD, regBD[card] = (HighTremoloMode * 0x80 - + HighVibratoMode * 0x40 - + AdlPercussionMode * 0x20)); - unsigned fours_this_card = std::min(fours, 6u); - Poke(card, 0x104, (1 << fours_this_card) - 1); - fours -= fours_this_card; - } - - // Mark all channels that are reserved for four-operator function - if(AdlPercussionMode == 1) - for(unsigned a = 0; a < NumCards; ++a) - { - for(unsigned b = 0; b < 5; ++b) - four_op_category[a * 23 + 18 + b] = static_cast(b + 3); - - for(unsigned b = 0; b < 3; ++b) - four_op_category[a * 23 + 6 + b] = 8; - } - - unsigned nextfour = 0; - - for(unsigned a = 0; a < NumFourOps; ++a) - { - four_op_category[nextfour ] = 1; - four_op_category[nextfour + 3] = 2; - - switch(a % 6) - { - case 0: - case 1: - nextfour += 1; - break; - - case 2: - nextfour += 9 - 2; - break; - - case 3: - case 4: - nextfour += 1; - break; - - case 5: - nextfour += 23 - 9 - 2; - break; - } - } - } - - void ChangeVolumeRangesModel(ADLMIDI_VolumeModels volumeModel) - { - switch(volumeModel) - { - case ADLMIDI_VolumeModel_AUTO://Do nothing until restart playing - break; - - case ADLMIDI_VolumeModel_Generic: - m_volumeScale = OPL3::VOLUME_Generic; - break; - - case ADLMIDI_VolumeModel_CMF: - LogarithmicVolumes = true; - m_volumeScale = OPL3::VOLUME_CMF; - break; - - case ADLMIDI_VolumeModel_DMX: - m_volumeScale = OPL3::VOLUME_DMX; - break; - - case ADLMIDI_VolumeModel_APOGEE: - m_volumeScale = OPL3::VOLUME_APOGEE; - break; - - case ADLMIDI_VolumeModel_9X: - m_volumeScale = OPL3::VOLUME_9X; - break; - } - } - - void Reset() - { - LogarithmicVolumes = false; -#ifdef ADLMIDI_USE_DOSBOX_OPL - DBOPL::Handler emptyChip; //Constructors inside are will initialize necessary fields -#else - _opl3_chip emptyChip; - memset(&emptyChip, 0, sizeof(_opl3_chip)); -#endif - cards.clear(); - ins.clear(); - pit.clear(); - regBD.clear(); - cards.resize(NumCards, emptyChip); - NumChannels = NumCards * 23; - ins.resize(NumChannels, 189); - pit.resize(NumChannels, 0); - regBD.resize(NumCards, 0); - four_op_category.resize(NumChannels, 0); - - for(unsigned p = 0, a = 0; a < NumCards; ++a) - { - for(unsigned b = 0; b < 18; ++b) - four_op_category[p++] = 0; - - for(unsigned b = 0; b < 5; ++b) - four_op_category[p++] = 8; - } - - static const uint16_t data[] = - { - 0x004, 96, 0x004, 128, // Pulse timer - 0x105, 0, 0x105, 1, 0x105, 0, // Pulse OPL3 enable - 0x001, 32, 0x105, 1 // Enable wave, OPL3 extensions - }; - unsigned fours = NumFourOps; - - for(unsigned card = 0; card < NumCards; ++card) - { -#ifdef ADLMIDI_USE_DOSBOX_OPL - cards[card].Init(_parent->PCM_RATE); -#else - OPL3_Reset(&cards[card], static_cast(_parent->PCM_RATE)); -#endif - - for(unsigned a = 0; a < 18; ++a) Poke(card, 0xB0 + Channels[a], 0x00); - - for(unsigned a = 0; a < sizeof(data) / sizeof(*data); a += 2) - PokeN(card, data[a], static_cast(data[a + 1])); - - Poke(card, 0x0BD, regBD[card] = (HighTremoloMode * 0x80 - + HighVibratoMode * 0x40 - + AdlPercussionMode * 0x20)); - unsigned fours_this_card = std::min(fours, 6u); - Poke(card, 0x104, (1 << fours_this_card) - 1); - //fprintf(stderr, "Card %u: %u four-ops.\n", card, fours_this_card); - fours -= fours_this_card; - } - - // Mark all channels that are reserved for four-operator function - if(AdlPercussionMode == 1) - for(unsigned a = 0; a < NumCards; ++a) - { - for(unsigned b = 0; b < 5; ++b) - four_op_category[a * 23 + 18 + b] = static_cast(b + 3); - - for(unsigned b = 0; b < 3; ++b) - four_op_category[a * 23 + 6 + b] = 8; - } - - unsigned nextfour = 0; - - for(unsigned a = 0; a < NumFourOps; ++a) - { - four_op_category[nextfour ] = 1; - four_op_category[nextfour + 3] = 2; - - switch(a % 6) - { - case 0: - case 1: - nextfour += 1; - break; - - case 2: - nextfour += 9 - 2; - break; - - case 3: - case 4: - nextfour += 1; - break; - - case 5: - nextfour += 23 - 9 - 2; - break; - } - } - - /**/ - /* - In two-op mode, channels 0..8 go as follows: - Op1[port] Op2[port] - Channel 0: 00 00 03 03 - Channel 1: 01 01 04 04 - Channel 2: 02 02 05 05 - Channel 3: 06 08 09 0B - Channel 4: 07 09 10 0C - Channel 5: 08 0A 11 0D - Channel 6: 12 10 15 13 - Channel 7: 13 11 16 14 - Channel 8: 14 12 17 15 - In four-op mode, channels 0..8 go as follows: - Op1[port] Op2[port] Op3[port] Op4[port] - Channel 0: 00 00 03 03 06 08 09 0B - Channel 1: 01 01 04 04 07 09 10 0C - Channel 2: 02 02 05 05 08 0A 11 0D - Channel 3: CHANNEL 0 SLAVE - Channel 4: CHANNEL 1 SLAVE - Channel 5: CHANNEL 2 SLAVE - Channel 6: 12 10 15 13 - Channel 7: 13 11 16 14 - Channel 8: 14 12 17 15 - Same goes principally for channels 9-17 respectively. - */ - Silence(); - } -}; - -//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"; - -class MIDIplay -{ - // Information about each track - struct Position - { - bool began; - char padding[7]; - double wait; - struct TrackInfo - { - size_t ptr; - long delay; - int status; - char padding2[4]; - TrackInfo(): ptr(0), delay(0), status(0) {} - }; - std::vector track; - - Position(): began(false), wait(0.0), track() { } - } CurrentPosition, LoopBeginPosition, trackBeginPosition; - - std::map devices; - std::map current_device; - - // Persistent settings for each MIDI channel - struct MIDIchannel - { - uint16_t portamento; - uint8_t bank_lsb, bank_msb; - uint8_t patch; - uint8_t volume, expression; - uint8_t panning, vibrato, sustain; - char ____padding[6]; - double bend, bendsense; - double vibpos, vibspeed, vibdepth; - int64_t vibdelay; - uint8_t lastlrpn, lastmrpn; - bool nrpn; - struct NoteInfo - { - // Current pressure - uint8_t vol; - // Tone selected on noteon: - char ____padding[1]; - int16_t tone; - // Patch selected on noteon; index to banks[AdlBank][] - uint8_t midiins; - // Index to physical adlib data structure, adlins[] - char ____padding2[3]; - uint32_t insmeta; - char ____padding3[4]; - // List of adlib channels it is currently occupying. - std::map phys; - }; - typedef std::map activenotemap_t; - typedef activenotemap_t::iterator activenoteiterator; - char ____padding2[5]; - activenotemap_t activenotes; - - MIDIchannel() - : portamento(0), - bank_lsb(0), bank_msb(0), patch(0), - volume(100), expression(127), - panning(0x30), vibrato(0), sustain(0), - bend(0.0), bendsense(2 / 8192.0), - vibpos(0), vibspeed(2 * 3.141592653 * 5.0), - vibdepth(0.5 / 127), vibdelay(0), - lastlrpn(0), lastmrpn(0), nrpn(false), - activenotes() { } - }; - std::vector Ch; - bool cmf_percussion_mode; - - // Additional information about AdLib channels - struct AdlChannel - { - // For collisions - struct Location - { - uint16_t MidCh; - uint8_t note; - bool operator==(const Location &b) const - { - return MidCh == b.MidCh && note == b.note; - } - bool operator< (const Location &b) const - { - return MidCh < b.MidCh || (MidCh == b.MidCh && note < b.note); - } - char ____padding[1]; - }; - struct LocationData - { - bool sustained; - char ____padding[1]; - uint16_t ins; // a copy of that in phys[] - char ____padding2[4]; - int64_t kon_time_until_neglible; - int64_t vibdelay; - }; - typedef std::map users_t; - users_t users; - - // If the channel is keyoff'd - long koff_time_until_neglible; - // For channel allocation: - AdlChannel(): users(), koff_time_until_neglible(0) { } - - void AddAge(int64_t ms) - { - if(users.empty()) - koff_time_until_neglible = - std::max(koff_time_until_neglible - ms, static_cast(-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(-0x1FFFFFFFl)); - i->second.vibdelay += ms; - } - } - } - }; - public: - char ____padding[7]; - private: - std::vector ch; - - std::vector > TrackData; - public: - MIDIplay(): - cmf_percussion_mode(false), - config(NULL), - trackStart(false), - loopStart(false), - loopEnd(false), - loopStart_passed(false), - invalidLoop(false), - loopStart_hit(false) - { - devices.clear(); - } - ~MIDIplay() - {} - - ADL_MIDIPlayer *config; - std::string musTitle; - fraction InvDeltaTicks, Tempo; - bool trackStart, - loopStart, - loopEnd, - loopStart_passed /*Tells that "loopStart" already passed*/, - invalidLoop /*Loop points are invalid (loopStart after loopEnd or loopStart and loopEnd are on same place)*/, - loopStart_hit /*loopStart entry was hited in previous tick*/; - char ____padding2[2]; - OPL3 opl; - public: - static uint64_t ReadBEint(const void *buffer, size_t nbytes) - { - uint64_t result = 0; - const unsigned char *data = reinterpret_cast(buffer); - - for(unsigned n = 0; n < nbytes; ++n) - result = (result << 8) + data[n]; - - return result; - } - static uint64_t ReadLEint(const void *buffer, size_t nbytes) - { - uint64_t result = 0; - const unsigned char *data = reinterpret_cast(buffer); - - for(unsigned n = 0; n < nbytes; ++n) - result = result + static_cast(data[n] << (n * 8)); - - return result; - } - uint64_t 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; - } - uint64_t ReadVarLenEx(size_t tk, bool &ok) - { - uint64_t result = 0; - ok = false; - - for(;;) - { - if(tk >= TrackData.size()) - return 1; - - if(tk >= CurrentPosition.track.size()) - return 2; - - size_t ptr = CurrentPosition.track[tk].ptr; - - if(ptr >= TrackData[tk].size()) - return 3; - - unsigned char byte = TrackData[tk][CurrentPosition.track[tk].ptr++]; - result = (result << 7) + (byte & 0x7F); - - if(!(byte & 0x80)) break; - } - - ok = true; - return result; - } - - /* - * A little class gives able to read filedata from disk and also from a memory segment - */ - class fileReader - { - public: - enum relTo - { - SET = 0, - CUR = 1, - END = 2 - }; - - fileReader() - { - fp = NULL; - mp = NULL; - mp_size = 0; - mp_tell = 0; - } - ~fileReader() - { - close(); - } - - void openFile(const char *path) - { - fp = std::fopen(path, "rb"); - _fileName = path; - mp = NULL; - mp_size = 0; - mp_tell = 0; - } - - void openData(void *mem, size_t lenght) - { - fp = NULL; - mp = mem; - mp_size = lenght; - mp_tell = 0; - } - - void seek(long pos, int rel_to) - { - if(fp) - std::fseek(fp, pos, rel_to); - else - { - switch(rel_to) - { - case SET: - mp_tell = static_cast(pos); - break; - - case END: - mp_tell = mp_size - static_cast(pos); - break; - - case CUR: - mp_tell = mp_tell + static_cast(pos); - break; - } - - if(mp_tell > mp_size) - mp_tell = mp_size; - } - } - - inline void seeku(unsigned long pos, int rel_to) - { - seek(static_cast(pos), rel_to); - } - - size_t read(void *buf, size_t num, size_t size) - { - if(fp) - return std::fread(buf, num, size, fp); - else - { - size_t pos = 0; - size_t maxSize = static_cast(size * num); - - while((pos < maxSize) && (mp_tell < mp_size)) - { - reinterpret_cast(buf)[pos] = reinterpret_cast(mp)[mp_tell]; - mp_tell++; - pos++; - } - - return pos; - } - } - - int getc() - { - if(fp) - return std::getc(fp); - else - { - if(mp_tell >= mp_size) - return -1; - - int x = reinterpret_cast(mp)[mp_tell]; - mp_tell++; - return x; - } - } - - size_t tell() - { - if(fp) - return static_cast(std::ftell(fp)); - else - return mp_tell; - } - - void close() - { - if(fp) std::fclose(fp); - - fp = NULL; - mp = NULL; - mp_size = 0; - mp_tell = 0; - } - - bool isValid() - { - return (fp) || (mp); - } - - bool eof() - { - return mp_tell >= mp_size; - } - std::string _fileName; - std::FILE *fp; - void *mp; - size_t mp_size; - size_t mp_tell; - }; - - bool LoadMIDI(const std::string &filename) - { - fileReader file; - file.openFile(filename.c_str()); - - if(!LoadMIDI(file)) - { - std::perror(filename.c_str()); - return false; - } - - return true; - } - - bool LoadMIDI(void *data, unsigned long size) - { - fileReader file; - file.openData(data, size); - return LoadMIDI(file); - } - - bool LoadMIDI(fileReader &fr) - { -#define qqq(x) (void)x - size_t fsize; - qqq(fsize); - - //std::FILE* fr = std::fopen(filename.c_str(), "rb"); - if(!fr.isValid()) - { - ADLMIDI_ErrorString = "Invalid data stream!"; - return false; - } - - const size_t HeaderSize = 4 + 4 + 2 + 2 + 2; // 14 - char HeaderBuf[HeaderSize] = ""; -riffskip: - ; - fsize = fr.read(HeaderBuf, 1, HeaderSize); - - if(std::memcmp(HeaderBuf, "RIFF", 4) == 0) - { - fr.seek(6l, SEEK_CUR); - goto riffskip; - } - - size_t DeltaTicks = 192, TrackCount = 1; - config->stored_samples = 0; - config->backup_samples_size = 0; - opl.AdlPercussionMode = config->AdlPercussionMode; - opl.HighTremoloMode = config->HighTremoloMode; - opl.HighVibratoMode = config->HighVibratoMode; - opl.ScaleModulators = config->ScaleModulators; - opl.LogarithmicVolumes = config->LogarithmicVolumes; - opl.ChangeVolumeRangesModel(static_cast(config->VolumeModel)); - - if(config->VolumeModel == ADLMIDI_VolumeModel_AUTO) - { - switch(config->AdlBank) - { - default: - opl.m_volumeScale = OPL3::VOLUME_Generic; - break; - - case 14://Doom 2 - case 15://Heretic - case 16://Doom 1 - case 64://Raptor - opl.m_volumeScale = OPL3::VOLUME_DMX; - break; - - case 58://FatMan bank hardcoded in the Windows 9x drivers - case 65://Modded Wohlstand's Fatman bank - case 66://O'Connel's bank - opl.m_volumeScale = OPL3::VOLUME_9X; - break; - - case 62://Duke Nukem 3D - case 63://Shadow Warrior - case 69://Blood - case 70://Lee - case 71://Nam - opl.m_volumeScale = OPL3::VOLUME_APOGEE; - break; - } - } - - opl.NumCards = config->NumCards; - opl.NumFourOps = config->NumFourOps; - cmf_percussion_mode = false; - opl.Reset(); - trackStart = true; - loopStart = true; - loopStart_passed = false; - invalidLoop = false; - loopStart_hit = false; - bool is_GMF = false; // GMD/MUS files (ScummVM) - bool is_MUS = false; // MUS/DMX files (Doom) - bool is_IMF = false; // IMF - bool is_CMF = false; // Creative Music format (CMF/CTMF) - //std::vector MUS_instrumentList; - - if(std::memcmp(HeaderBuf, "GMF\x1", 4) == 0) - { - // GMD/MUS files (ScummVM) - fr.seek(7 - static_cast(HeaderSize), SEEK_CUR); - is_GMF = true; - } - else if(std::memcmp(HeaderBuf, "MUS\x1A", 4) == 0) - { - // MUS/DMX files (Doom) - uint64_t start = ReadLEint(HeaderBuf + 6, 2); - is_MUS = true; - fr.seek(static_cast(start), SEEK_SET); - } - else if(std::memcmp(HeaderBuf, "CTMF", 4) == 0) - { - opl.dynamic_instruments.clear(); - opl.dynamic_metainstruments.clear(); - // Creative Music Format (CMF). - // When playing CTMF files, use the following commandline: - // adlmidi song8.ctmf -p -v 1 1 0 - // i.e. enable percussion mode, deeper vibrato, and use only 1 card. - is_CMF = true; - //unsigned version = ReadLEint(HeaderBuf+4, 2); - uint64_t ins_start = ReadLEint(HeaderBuf + 6, 2); - uint64_t mus_start = ReadLEint(HeaderBuf + 8, 2); - //unsigned deltas = ReadLEint(HeaderBuf+10, 2); - uint64_t ticks = ReadLEint(HeaderBuf + 12, 2); - // Read title, author, remarks start offsets in file - fr.read(HeaderBuf, 1, 6); - //unsigned long notes_starts[3] = {ReadLEint(HeaderBuf+0,2),ReadLEint(HeaderBuf+0,4),ReadLEint(HeaderBuf+0,6)}; - fr.seek(16, SEEK_CUR); // Skip the channels-in-use table - fr.read(HeaderBuf, 1, 4); - uint64_t ins_count = ReadLEint(HeaderBuf + 0, 2); //, basictempo = ReadLEint(HeaderBuf+2, 2); - fr.seek(static_cast(ins_start), SEEK_SET); - - //std::printf("%u instruments\n", ins_count); - for(unsigned i = 0; i < ins_count; ++i) - { - unsigned char InsData[16]; - fr.read(InsData, 1, 16); - /*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]);*/ - struct adldata adl; - struct adlinsdata adlins; - adl.modulator_E862 = - ((static_cast(InsData[8] & 0x07) << 24) & 0xFF000000) //WaveForm - | ((static_cast(InsData[6]) << 16) & 0x00FF0000) //Sustain/Release - | ((static_cast(InsData[4]) << 8) & 0x0000FF00) //Attack/Decay - | ((static_cast(InsData[0]) << 0) & 0x000000FF); //MultKEVA - adl.carrier_E862 = - ((static_cast(InsData[9] & 0x07) << 24) & 0xFF000000) //WaveForm - | ((static_cast(InsData[7]) << 16) & 0x00FF0000) //Sustain/Release - | ((static_cast(InsData[5]) << 8) & 0x0000FF00) //Attack/Decay - | ((static_cast(InsData[1]) << 0) & 0x000000FF); //MultKEVA - adl.modulator_40 = InsData[2]; - adl.carrier_40 = InsData[3]; - adl.feedconn = InsData[10] & 0x0F; - adl.finetune = 0; - adlins.adlno1 = static_cast(opl.dynamic_instruments.size() | opl.DynamicInstrumentTag); - adlins.adlno2 = adlins.adlno1; - adlins.ms_sound_kon = 1000; - adlins.ms_sound_koff = 500; - adlins.tone = 0; - adlins.flags = 0; - adlins.fine_tune = 0.0; - opl.dynamic_metainstruments.push_back(adlins); - opl.dynamic_instruments.push_back(adl); - } - - fr.seeku(mus_start, SEEK_SET); - TrackCount = 1; - DeltaTicks = ticks; - opl.AdlBank = ~0u; // Ignore AdlBank number, use dynamic banks instead - //std::printf("CMF deltas %u ticks %u, basictempo = %u\n", deltas, ticks, basictempo); - opl.LogarithmicVolumes = true; - opl.AdlPercussionMode = true; - opl.m_volumeScale = OPL3::VOLUME_CMF; - } - else - { - // Try parsing as an IMF file - { - size_t end = static_cast(HeaderBuf[0]) + 256 * static_cast(HeaderBuf[1]); - - if(!end || (end & 3)) - goto not_imf; - - size_t backup_pos = fr.tell(); - int64_t sum1 = 0, sum2 = 0; - fr.seek(2, SEEK_SET); - - for(unsigned n = 0; n < 42; ++n) - { - int64_t value1 = fr.getc(); - value1 += fr.getc() << 8; - sum1 += value1; - int64_t value2 = fr.getc(); - value2 += fr.getc() << 8; - sum2 += value2; - } - - fr.seek(static_cast(backup_pos), SEEK_SET); - - if(sum1 > sum2) - { - is_IMF = true; - DeltaTicks = 1; - } - } - - if(!is_IMF) - { -not_imf: - - if(std::memcmp(HeaderBuf, "MThd\0\0\0\6", 8) != 0) - { -InvFmt: - fr.close(); - ADLMIDI_ErrorString = fr._fileName + ": Invalid format\n"; - return false; - } - - /*size_t Fmt =*/ ReadBEint(HeaderBuf + 8, 2); - TrackCount = ReadBEint(HeaderBuf + 10, 2); - DeltaTicks = ReadBEint(HeaderBuf + 12, 2); - } - } - - TrackData.clear(); - TrackData.resize(TrackCount, std::vector()); - CurrentPosition.track.clear(); - CurrentPosition.track.resize(TrackCount); - InvDeltaTicks = fraction(1, 1000000l * static_cast(DeltaTicks)); - //Tempo = 1000000l * InvDeltaTicks; - Tempo = fraction(1, static_cast(DeltaTicks)); - static const unsigned char EndTag[4] = {0xFF, 0x2F, 0x00, 0x00}; - int totalGotten = 0; - - for(size_t tk = 0; tk < TrackCount; ++tk) - { - // Read track header - size_t TrackLength; - - if(is_IMF) - { - //std::fprintf(stderr, "Reading IMF file...\n"); - size_t end = static_cast(HeaderBuf[0]) + 256 * static_cast(HeaderBuf[1]); - unsigned IMF_tempo = 1428; - static const unsigned char imf_tempo[] = {0xFF, 0x51, 0x4, - static_cast(IMF_tempo >> 24), - static_cast(IMF_tempo >> 16), - static_cast(IMF_tempo >> 8), - static_cast(IMF_tempo) - }; - TrackData[tk].insert(TrackData[tk].end(), imf_tempo, imf_tempo + sizeof(imf_tempo)); - TrackData[tk].push_back(0x00); - fr.seek(2, SEEK_SET); - - while(fr.tell() < end && !fr.eof()) - { - uint8_t special_event_buf[5]; - special_event_buf[0] = 0xFF; - special_event_buf[1] = 0xE3; - special_event_buf[2] = 0x02; - special_event_buf[3] = static_cast(fr.getc()); // port index - special_event_buf[4] = static_cast(fr.getc()); // port value - uint32_t delay = static_cast(fr.getc()); - delay += 256 * static_cast(fr.getc()); - totalGotten += 4; - //if(special_event_buf[3] <= 8) continue; - //fprintf(stderr, "Put %02X <- %02X, plus %04X delay\n", special_event_buf[3],special_event_buf[4], delay); - TrackData[tk].insert(TrackData[tk].end(), special_event_buf, special_event_buf + 5); - - //if(delay>>21) TrackData[tk].push_back( 0x80 | ((delay>>21) & 0x7F ) ); - if(delay >> 14) - TrackData[tk].push_back(0x80 | ((delay >> 14) & 0x7F)); - - if(delay >> 7) - TrackData[tk].push_back(0x80 | ((delay >> 7) & 0x7F)); - - TrackData[tk].push_back(((delay >> 0) & 0x7F)); - } - - TrackData[tk].insert(TrackData[tk].end(), EndTag + 0, EndTag + 4); - CurrentPosition.track[tk].delay = 0; - CurrentPosition.began = true; - //std::fprintf(stderr, "Done reading IMF file\n"); - opl.NumFourOps = 0; //Don't use 4-operator channels for IMF playing! - } - else - { - if(is_GMF || is_CMF) // Take the rest of the file - { - size_t pos = fr.tell(); - fr.seek(0, SEEK_END); - TrackLength = fr.tell() - pos; - fr.seek(static_cast(pos), SEEK_SET); - } - else if(is_MUS) // Read TrackLength from file position 4 - { - size_t pos = fr.tell(); - fr.seek(4, SEEK_SET); - TrackLength = static_cast(fr.getc()); - TrackLength += static_cast(fr.getc() << 8); - fr.seek(static_cast(pos), SEEK_SET); - } - else - { - fsize = fr.read(HeaderBuf, 1, 8); - - if(std::memcmp(HeaderBuf, "MTrk", 4) != 0) - goto InvFmt; - - TrackLength = ReadBEint(HeaderBuf + 4, 4); - } - - // Read track data - TrackData[tk].resize(TrackLength); - fsize = fr.read(&TrackData[tk][0], 1, TrackLength); - totalGotten += fsize; - - if(is_GMF || is_MUS) // Note: CMF does include the track end tag. - TrackData[tk].insert(TrackData[tk].end(), EndTag + 0, EndTag + 4); - - bool ok = false; - // Read next event time - uint64_t tkDelay = ReadVarLenEx(tk, ok); - - if(ok) - CurrentPosition.track[tk].delay = static_cast(tkDelay); - else - { - std::stringstream msg; - msg << fr._fileName << ": invalid variable length in the track " << tk << "! (error code " << tkDelay << ")"; - ADLMIDI_ErrorString = msg.str(); - return false; - } - } - } - - for(size_t tk = 0; tk < TrackCount; ++tk) - totalGotten += TrackData[tk].size(); - - if(totalGotten == 0) - { - ADLMIDI_ErrorString = fr._fileName + ": Empty track data"; - return false; - } - - opl.Reset(); // Reset AdLib - //opl.Reset(); // ...twice (just in case someone misprogrammed OPL3 previously) - ch.clear(); - ch.resize(opl.NumChannels); - return true; - } - - /* Periodic tick handler. - * Input: s = seconds since last call - * Input: granularity = don't expect intervals smaller than this, in seconds - * Output: desired number of seconds until next call - */ - double 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(s * 1000.0)); - - UpdateVibrato(s); - UpdateArpeggio(s); - return CurrentPosition.wait; - } - - private: - enum { Upd_Patch = 0x1, - Upd_Pan = 0x2, - Upd_Volume = 0x4, - Upd_Pitch = 0x8, - Upd_All = Upd_Pan + Upd_Volume + Upd_Pitch, - Upd_Off = 0x20 - }; - - void NoteUpdate - (uint16_t MidCh, - MIDIchannel::activenoteiterator i, - unsigned props_mask, - int32_t select_adlchn = -1) - { - 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::iterator - jnext = info.phys.begin(); - jnext != info.phys.end(); - ) - { - std::map::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::iterator - jnext = info.phys.begin(); - jnext != info.phys.end(); - ) - { - std::map::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(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 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 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 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) 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(data[0]), v = static_cast(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(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 bank_warnings; - - if(Ch[MidCh].bank_msb) - { - uint32_t bankid = midiins + 256 * Ch[MidCh].bank_msb; - std::set::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::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 missing_warnings; - - if(!missing_warnings.count(static_cast(midiins)) && (ains.flags & adlinsdata::Flag_NoSound)) - { - //UI.PrintLn("[%i]Playing missing instrument %i", MidCh, midiins); - missing_warnings.insert(static_cast(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(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(adlchannel[0]) + 3) - continue; - } - } - - long s = CalculateAdlChannelGoodness(a, i[ccount], MidCh); - - if(s > bs) - { - bs = s; // Best candidate wins - c = static_cast(a); - } - } - - if(c < 0) - { - //UI.PrintLn("ignored unplaceable note"); - continue; // Could not play this note. Ignore it. - } - - PrepareAdlChannelForNewNote(static_cast(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 - 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(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((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; - } - } - } - - // Determine how good a candidate this adlchannel - // would be for playing a note from this instrument. - long CalculateAdlChannelGoodness - (unsigned c, uint16_t ins, uint16_t /*MidCh*/) 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; - } - - // A new note will be played on this channel using this instrument. - // Kill existing notes on this channel (or don't, if we do arpeggio) - void 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(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 KillOrEvacuate( - size_t from_channel, - AdlChannel::users_t::iterator j, - 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(c); - - if(c > std::numeric_limits::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(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(from_channel)); - } - - void KillSustainingNotes(int32_t MidCh = -1, int32_t this_adlchn = -1) - { - uint32_t first = 0, last = opl.NumChannels; - - if(this_adlchn >= 0) - { - first = static_cast(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 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 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 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 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 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(a), Upd_Pitch); - Ch[a].vibpos += amount * Ch[a].vibspeed; - } - else - Ch[a].vibpos = 0.0; - } - - void UpdateArpeggio(double /*amount*/) // 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::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(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(c)); - } - } - } - } - - public: - uint64_t ChooseDevice(const std::string &name) - { - std::map::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; - } -}; +#include "adlmidi_private.hpp" #ifdef ADLMIDI_buildAsApp -static std::deque AudioBuffer; -static MutexType AudioBuffer_lock; - -static void SDL_AudioCallbackX(void *, Uint8 *stream, int len) -{ - SDL_LockAudio(); - short *target = (short *) stream; - AudioBuffer_lock.Lock(); - /*if(len != AudioBuffer.size()) - fprintf(stderr, "len=%d stereo samples, AudioBuffer has %u stereo samples", - len/4, (unsigned) AudioBuffer.size()/2);*/ - unsigned ate = len / 2; // number of shorts - - if(ate > AudioBuffer.size()) ate = AudioBuffer.size(); - - for(unsigned a = 0; a < ate; ++a) - target[a] = AudioBuffer[a]; - - AudioBuffer.erase(AudioBuffer.begin(), AudioBuffer.begin() + ate); - AudioBuffer_lock.Unlock(); - SDL_UnlockAudio(); -} +#define SDL_MAIN_HANDLED +#include #endif -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((w >>(c * 8)) & 0xFF); - } -}; - -int adlRefreshNumCards(ADL_MIDIPlayer *device) -{ - unsigned n_fourop[2] = {0, 0}, n_total[2] = {0, 0}; - - for(unsigned a = 0; a < 256; ++a) - { - unsigned insno = banks[device->AdlBank][a]; - - if(insno == 198) continue; - - ++n_total[a / 128]; - - if(adlins[insno].adlno1 != adlins[insno].adlno2) - ++n_fourop[a / 128]; - } - - device->NumFourOps = - (n_fourop[0] >= n_total[0] * 7 / 8) ? device->NumCards * 6 - : (n_fourop[0] < n_total[0] * 1 / 8) ? 0 - : (device->NumCards == 1 ? 1 : device->NumCards * 4); - reinterpret_cast(device->adl_midiPlayer)->opl.NumFourOps = device->NumFourOps; - - if(n_fourop[0] >= n_total[0] * 15 / 16 && device->NumFourOps == 0) - { - ADLMIDI_ErrorString = "ERROR: You have selected a bank that consists almost exclusively of four-op patches.\n" - " The results (silence + much cpu load) would be probably\n" - " not what you want, therefore ignoring the request.\n"; - return -1; - } - - return 0; -} +static const unsigned MaxCards = 100; /*---------------------------EXPORTS---------------------------*/ @@ -2744,7 +73,8 @@ ADLMIDI_EXPORT struct ADL_MIDIPlayer *adl_init(long sample_rate) ADLMIDI_EXPORT int adl_setNumCards(ADL_MIDIPlayer *device, int numCards) { - if(device == NULL) return -2; + if(device == NULL) + return -2; device->NumCards = static_cast(numCards); reinterpret_cast(device->adl_midiPlayer)->opl.NumCards = device->NumCards; @@ -3007,17 +337,20 @@ inline static void SendStereoAudio(ADL_MIDIPlayer *device, ADLMIDI_EXPORT int adl_play(ADL_MIDIPlayer *device, int sampleCount, short *out) { - if(!device) return 0; + if(!device) + return 0; sampleCount -= sampleCount % 2; //Avoid even sample requests - if(sampleCount < 0) return 0; + if(sampleCount < 0) + return 0; - if(device->QuitFlag) return 0; + if(device->QuitFlag) + return 0; ssize_t gotten_len = 0; ssize_t n_periodCountStereo = 512; - ssize_t n_periodCountPhys = n_periodCountStereo * 2; + //ssize_t n_periodCountPhys = n_periodCountStereo * 2; int left = sampleCount; while(left > 0) @@ -3038,9 +371,7 @@ ADLMIDI_EXPORT int adl_play(ADL_MIDIPlayer *device, int sampleCount, short *out) if(ate < device->backup_samples_size) { - for(int j = 0; - j < ate; - j++) + for(int j = 0; j < ate; j++) device->backup_samples[(ate - 1) - j] = device->backup_samples[(device->backup_samples_size - 1) - j]; } @@ -3052,58 +383,58 @@ ADLMIDI_EXPORT int adl_play(ADL_MIDIPlayer *device, int sampleCount, short *out) device->delay -= eat_delay; device->carry += device->PCM_RATE * eat_delay; n_periodCountStereo = static_cast(device->carry); - n_periodCountPhys = n_periodCountStereo * 2; + //n_periodCountPhys = n_periodCountStereo * 2; device->carry -= n_periodCountStereo; if(device->SkipForward > 0) device->SkipForward -= 1; else { -#ifdef ADLMIDI_USE_DOSBOX_OPL - std::vector out_buf; //int buf[n_samples * 2]; -#else + #ifdef ADLMIDI_USE_DOSBOX_OPL + std::vector out_buf; + #else std::vector out_buf; -#endif + #endif out_buf.resize(1024 /*n_samples * 2*/); + ssize_t in_generatedStereo = (n_periodCountStereo > 512) ? 512 : n_periodCountStereo; ssize_t in_generatedPhys = in_generatedStereo * 2; + //fill buffer with zeros size_t in_countStereoU = static_cast(in_generatedStereo * 2); if(device->NumCards == 1) { -#ifdef ADLMIDI_USE_DOSBOX_OPL + #ifdef ADLMIDI_USE_DOSBOX_OPL reinterpret_cast(device->adl_midiPlayer)->opl.cards[0].GenerateArr(out_buf.data(), &in_generatedStereo); in_generatedPhys = in_generatedStereo * 2; -#else + #else OPL3_GenerateStream(&(reinterpret_cast(device->adl_midiPlayer))->opl.cards[0], out_buf.data(), static_cast(in_generatedStereo)); -#endif + #endif /* Process it */ SendStereoAudio(device, sampleCount, in_generatedStereo, out_buf.data(), gotten_len, out); } else if(n_periodCountStereo > 0) { -#ifdef ADLMIDI_USE_DOSBOX_OPL + #ifdef ADLMIDI_USE_DOSBOX_OPL std::vector in_mixBuffer; in_mixBuffer.resize(1024); //n_samples * 2 ssize_t in_generatedStereo = n_periodCountStereo; -#endif + #endif memset(out_buf.data(), 0, in_countStereoU * sizeof(short)); /* Generate data from every chip and mix result */ for(unsigned card = 0; card < device->NumCards; ++card) { -#ifdef ADLMIDI_USE_DOSBOX_OPL + #ifdef ADLMIDI_USE_DOSBOX_OPL reinterpret_cast(device->adl_midiPlayer)->opl.cards[card].GenerateArr(in_mixBuffer.data(), &in_generatedStereo); in_generatedPhys = in_generatedStereo * 2; size_t in_samplesCount = static_cast(in_generatedPhys); - for(size_t a = 0; a < in_samplesCount; ++a) out_buf[a] += in_mixBuffer[a]; - -#else + #else OPL3_GenerateStreamMix(&(reinterpret_cast(device->adl_midiPlayer))->opl.cards[card], out_buf.data(), static_cast(in_generatedStereo)); -#endif + #endif } /* Process it */ @@ -3121,8 +452,29 @@ ADLMIDI_EXPORT int adl_play(ADL_MIDIPlayer *device, int sampleCount, short *out) return static_cast(gotten_len); } - #ifdef ADLMIDI_buildAsApp +static std::deque AudioBuffer; +static MutexType AudioBuffer_lock; + +static void SDL_AudioCallbackX(void *, Uint8 *stream, int len) +{ + SDL_LockAudio(); + short *target = (short *) stream; + AudioBuffer_lock.Lock(); + /*if(len != AudioBuffer.size()) + fprintf(stderr, "len=%d stereo samples, AudioBuffer has %u stereo samples", + len/4, (unsigned) AudioBuffer.size()/2);*/ + unsigned ate = len / 2; // number of shorts + + if(ate > AudioBuffer.size()) ate = AudioBuffer.size(); + + for(unsigned a = 0; a < ate; ++a) + target[a] = AudioBuffer[a]; + + AudioBuffer.erase(AudioBuffer.begin(), AudioBuffer.begin() + ate); + AudioBuffer_lock.Unlock(); + SDL_UnlockAudio(); +} int main(int argc, char **argv) { diff --git a/src/adlmidi.h b/src/adlmidi.h index 9c09028..89e853f 100644 --- a/src/adlmidi.h +++ b/src/adlmidi.h @@ -2,7 +2,7 @@ * libADLMIDI is a free MIDI to WAV conversion library with OPL3 emulation * * Original ADLMIDI code: Copyright (c) 2010-2014 Joel Yliluoma - * ADLMIDI Library API: Copyright (c) 2016 Vitaly Novichkov + * ADLMIDI Library API: Copyright (c) 2017 Vitaly Novichkov * * Library is based on the ADLMIDI, a MIDI player for Linux and Windows with OPL3 emulation: * http://iki.fi/bisqwit/source/adlmidi.html @@ -55,16 +55,16 @@ struct ADL_MIDIPlayer double delay; double carry; - // The lag between visual content and audio content equals - // the sum of these two buffers. + /* The lag between visual content and audio content equals */ + /* the sum of these two buffers. */ double mindelay; double maxdelay; - /*For internal usage*/ - int stored_samples; //num of collected samples - short backup_samples[1024]; //Backup sample storage. - int backup_samples_size; //Backup sample storage. - /*For internal usage*/ + /* For internal usage */ + int stored_samples; /* num of collected samples */ + short backup_samples[1024]; /* Backup sample storage. */ + int backup_samples_size; /* Backup sample storage. */ + /* For internal usage */ void *adl_midiPlayer; unsigned long PCM_RATE; @@ -132,4 +132,4 @@ extern int adl_play(struct ADL_MIDIPlayer *device, int sampleCount, short out[] } #endif -#endif // ADLMIDI_H +#endif /* ADLMIDI_H */ diff --git a/src/adlmidi_load.cpp b/src/adlmidi_load.cpp new file mode 100644 index 0000000..af7cf0d --- /dev/null +++ b/src/adlmidi_load.cpp @@ -0,0 +1,434 @@ +/* + * libADLMIDI is a free MIDI to WAV conversion library with OPL3 emulation + * + * Original ADLMIDI code: Copyright (c) 2010-2014 Joel Yliluoma + * ADLMIDI Library API: Copyright (c) 2017 Vitaly Novichkov + * + * 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 . + */ + +#include "adlmidi_private.hpp" + + +uint64_t MIDIplay::ReadBEint(const void *buffer, size_t nbytes) +{ + uint64_t result = 0; + const unsigned char *data = reinterpret_cast(buffer); + + for(unsigned n = 0; n < nbytes; ++n) + result = (result << 8) + data[n]; + + return result; +} + +uint64_t MIDIplay::ReadLEint(const void *buffer, size_t nbytes) +{ + uint64_t result = 0; + const unsigned char *data = reinterpret_cast(buffer); + + for(unsigned n = 0; n < nbytes; ++n) + result = result + static_cast(data[n] << (n * 8)); + + return result; +} + +uint64_t MIDIplay::ReadVarLenEx(size_t tk, bool &ok) +{ + uint64_t result = 0; + ok = false; + + for(;;) + { + if(tk >= TrackData.size()) + return 1; + + if(tk >= CurrentPosition.track.size()) + return 2; + + size_t ptr = CurrentPosition.track[tk].ptr; + + if(ptr >= TrackData[tk].size()) + return 3; + + unsigned char byte = TrackData[tk][CurrentPosition.track[tk].ptr++]; + result = (result << 7) + (byte & 0x7F); + + if(!(byte & 0x80)) break; + } + + ok = true; + return result; +} + +bool MIDIplay::LoadMIDI(const std::string &filename) +{ + fileReader file; + file.openFile(filename.c_str()); + + if(!LoadMIDI(file)) + { + std::perror(filename.c_str()); + return false; + } + + return true; +} + +bool MIDIplay::LoadMIDI(void *data, unsigned long size) +{ + fileReader file; + file.openData(data, size); + return LoadMIDI(file); +} + +bool MIDIplay::LoadMIDI(MIDIplay::fileReader &fr) +{ +#define qqq(x) (void)x + size_t fsize; + qqq(fsize); + + //std::FILE* fr = std::fopen(filename.c_str(), "rb"); + if(!fr.isValid()) + { + ADLMIDI_ErrorString = "Invalid data stream!"; + return false; + } + + const size_t HeaderSize = 4 + 4 + 2 + 2 + 2; // 14 + char HeaderBuf[HeaderSize] = ""; +riffskip: + ; + fsize = fr.read(HeaderBuf, 1, HeaderSize); + + if(std::memcmp(HeaderBuf, "RIFF", 4) == 0) + { + fr.seek(6l, SEEK_CUR); + goto riffskip; + } + + size_t DeltaTicks = 192, TrackCount = 1; + config->stored_samples = 0; + config->backup_samples_size = 0; + opl.AdlPercussionMode = config->AdlPercussionMode; + opl.HighTremoloMode = config->HighTremoloMode; + opl.HighVibratoMode = config->HighVibratoMode; + opl.ScaleModulators = config->ScaleModulators; + opl.LogarithmicVolumes = config->LogarithmicVolumes; + opl.ChangeVolumeRangesModel(static_cast(config->VolumeModel)); + + if(config->VolumeModel == ADLMIDI_VolumeModel_AUTO) + { + switch(config->AdlBank) + { + default: + opl.m_volumeScale = OPL3::VOLUME_Generic; + break; + + case 14://Doom 2 + case 15://Heretic + case 16://Doom 1 + case 64://Raptor + opl.m_volumeScale = OPL3::VOLUME_DMX; + break; + + case 58://FatMan bank hardcoded in the Windows 9x drivers + case 65://Modded Wohlstand's Fatman bank + case 66://O'Connel's bank + opl.m_volumeScale = OPL3::VOLUME_9X; + break; + + case 62://Duke Nukem 3D + case 63://Shadow Warrior + case 69://Blood + case 70://Lee + case 71://Nam + opl.m_volumeScale = OPL3::VOLUME_APOGEE; + break; + } + } + + opl.NumCards = config->NumCards; + opl.NumFourOps = config->NumFourOps; + cmf_percussion_mode = false; + opl.Reset(); + trackStart = true; + loopStart = true; + loopStart_passed = false; + invalidLoop = false; + loopStart_hit = false; + bool is_GMF = false; // GMD/MUS files (ScummVM) + bool is_MUS = false; // MUS/DMX files (Doom) + bool is_IMF = false; // IMF + bool is_CMF = false; // Creative Music format (CMF/CTMF) + //std::vector MUS_instrumentList; + + if(std::memcmp(HeaderBuf, "GMF\x1", 4) == 0) + { + // GMD/MUS files (ScummVM) + fr.seek(7 - static_cast(HeaderSize), SEEK_CUR); + is_GMF = true; + } + else if(std::memcmp(HeaderBuf, "MUS\x1A", 4) == 0) + { + // MUS/DMX files (Doom) + uint64_t start = ReadLEint(HeaderBuf + 6, 2); + is_MUS = true; + fr.seek(static_cast(start), SEEK_SET); + } + else if(std::memcmp(HeaderBuf, "CTMF", 4) == 0) + { + opl.dynamic_instruments.clear(); + opl.dynamic_metainstruments.clear(); + // Creative Music Format (CMF). + // When playing CTMF files, use the following commandline: + // adlmidi song8.ctmf -p -v 1 1 0 + // i.e. enable percussion mode, deeper vibrato, and use only 1 card. + is_CMF = true; + //unsigned version = ReadLEint(HeaderBuf+4, 2); + uint64_t ins_start = ReadLEint(HeaderBuf + 6, 2); + uint64_t mus_start = ReadLEint(HeaderBuf + 8, 2); + //unsigned deltas = ReadLEint(HeaderBuf+10, 2); + uint64_t ticks = ReadLEint(HeaderBuf + 12, 2); + // Read title, author, remarks start offsets in file + fr.read(HeaderBuf, 1, 6); + //unsigned long notes_starts[3] = {ReadLEint(HeaderBuf+0,2),ReadLEint(HeaderBuf+0,4),ReadLEint(HeaderBuf+0,6)}; + fr.seek(16, SEEK_CUR); // Skip the channels-in-use table + fr.read(HeaderBuf, 1, 4); + uint64_t ins_count = ReadLEint(HeaderBuf + 0, 2); //, basictempo = ReadLEint(HeaderBuf+2, 2); + fr.seek(static_cast(ins_start), SEEK_SET); + + //std::printf("%u instruments\n", ins_count); + for(unsigned i = 0; i < ins_count; ++i) + { + unsigned char InsData[16]; + fr.read(InsData, 1, 16); + /*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]);*/ + struct adldata adl; + struct adlinsdata adlins; + adl.modulator_E862 = + ((static_cast(InsData[8] & 0x07) << 24) & 0xFF000000) //WaveForm + | ((static_cast(InsData[6]) << 16) & 0x00FF0000) //Sustain/Release + | ((static_cast(InsData[4]) << 8) & 0x0000FF00) //Attack/Decay + | ((static_cast(InsData[0]) << 0) & 0x000000FF); //MultKEVA + adl.carrier_E862 = + ((static_cast(InsData[9] & 0x07) << 24) & 0xFF000000) //WaveForm + | ((static_cast(InsData[7]) << 16) & 0x00FF0000) //Sustain/Release + | ((static_cast(InsData[5]) << 8) & 0x0000FF00) //Attack/Decay + | ((static_cast(InsData[1]) << 0) & 0x000000FF); //MultKEVA + adl.modulator_40 = InsData[2]; + adl.carrier_40 = InsData[3]; + adl.feedconn = InsData[10] & 0x0F; + adl.finetune = 0; + adlins.adlno1 = static_cast(opl.dynamic_instruments.size() | opl.DynamicInstrumentTag); + adlins.adlno2 = adlins.adlno1; + adlins.ms_sound_kon = 1000; + adlins.ms_sound_koff = 500; + adlins.tone = 0; + adlins.flags = 0; + adlins.fine_tune = 0.0; + opl.dynamic_metainstruments.push_back(adlins); + opl.dynamic_instruments.push_back(adl); + } + + fr.seeku(mus_start, SEEK_SET); + TrackCount = 1; + DeltaTicks = ticks; + opl.AdlBank = ~0u; // Ignore AdlBank number, use dynamic banks instead + //std::printf("CMF deltas %u ticks %u, basictempo = %u\n", deltas, ticks, basictempo); + opl.LogarithmicVolumes = true; + opl.AdlPercussionMode = true; + opl.m_volumeScale = OPL3::VOLUME_CMF; + } + else + { + // Try parsing as an IMF file + { + size_t end = static_cast(HeaderBuf[0]) + 256 * static_cast(HeaderBuf[1]); + + if(!end || (end & 3)) + goto not_imf; + + size_t backup_pos = fr.tell(); + int64_t sum1 = 0, sum2 = 0; + fr.seek(2, SEEK_SET); + + for(unsigned n = 0; n < 42; ++n) + { + int64_t value1 = fr.getc(); + value1 += fr.getc() << 8; + sum1 += value1; + int64_t value2 = fr.getc(); + value2 += fr.getc() << 8; + sum2 += value2; + } + + fr.seek(static_cast(backup_pos), SEEK_SET); + + if(sum1 > sum2) + { + is_IMF = true; + DeltaTicks = 1; + } + } + + if(!is_IMF) + { +not_imf: + + if(std::memcmp(HeaderBuf, "MThd\0\0\0\6", 8) != 0) + { +InvFmt: + fr.close(); + ADLMIDI_ErrorString = fr._fileName + ": Invalid format\n"; + return false; + } + + /*size_t Fmt =*/ ReadBEint(HeaderBuf + 8, 2); + TrackCount = ReadBEint(HeaderBuf + 10, 2); + DeltaTicks = ReadBEint(HeaderBuf + 12, 2); + } + } + + TrackData.clear(); + TrackData.resize(TrackCount, std::vector()); + CurrentPosition.track.clear(); + CurrentPosition.track.resize(TrackCount); + InvDeltaTicks = fraction(1, 1000000l * static_cast(DeltaTicks)); + //Tempo = 1000000l * InvDeltaTicks; + Tempo = fraction(1, static_cast(DeltaTicks)); + static const unsigned char EndTag[4] = {0xFF, 0x2F, 0x00, 0x00}; + int totalGotten = 0; + + for(size_t tk = 0; tk < TrackCount; ++tk) + { + // Read track header + size_t TrackLength; + + if(is_IMF) + { + //std::fprintf(stderr, "Reading IMF file...\n"); + size_t end = static_cast(HeaderBuf[0]) + 256 * static_cast(HeaderBuf[1]); + unsigned IMF_tempo = 1428; + static const unsigned char imf_tempo[] = {0xFF, 0x51, 0x4, + static_cast(IMF_tempo >> 24), + static_cast(IMF_tempo >> 16), + static_cast(IMF_tempo >> 8), + static_cast(IMF_tempo) + }; + TrackData[tk].insert(TrackData[tk].end(), imf_tempo, imf_tempo + sizeof(imf_tempo)); + TrackData[tk].push_back(0x00); + fr.seek(2, SEEK_SET); + + while(fr.tell() < end && !fr.eof()) + { + uint8_t special_event_buf[5]; + special_event_buf[0] = 0xFF; + special_event_buf[1] = 0xE3; + special_event_buf[2] = 0x02; + special_event_buf[3] = static_cast(fr.getc()); // port index + special_event_buf[4] = static_cast(fr.getc()); // port value + uint32_t delay = static_cast(fr.getc()); + delay += 256 * static_cast(fr.getc()); + totalGotten += 4; + //if(special_event_buf[3] <= 8) continue; + //fprintf(stderr, "Put %02X <- %02X, plus %04X delay\n", special_event_buf[3],special_event_buf[4], delay); + TrackData[tk].insert(TrackData[tk].end(), special_event_buf, special_event_buf + 5); + + //if(delay>>21) TrackData[tk].push_back( 0x80 | ((delay>>21) & 0x7F ) ); + if(delay >> 14) + TrackData[tk].push_back(0x80 | ((delay >> 14) & 0x7F)); + + if(delay >> 7) + TrackData[tk].push_back(0x80 | ((delay >> 7) & 0x7F)); + + TrackData[tk].push_back(((delay >> 0) & 0x7F)); + } + + TrackData[tk].insert(TrackData[tk].end(), EndTag + 0, EndTag + 4); + CurrentPosition.track[tk].delay = 0; + CurrentPosition.began = true; + //std::fprintf(stderr, "Done reading IMF file\n"); + opl.NumFourOps = 0; //Don't use 4-operator channels for IMF playing! + } + else + { + if(is_GMF || is_CMF) // Take the rest of the file + { + size_t pos = fr.tell(); + fr.seek(0, SEEK_END); + TrackLength = fr.tell() - pos; + fr.seek(static_cast(pos), SEEK_SET); + } + else if(is_MUS) // Read TrackLength from file position 4 + { + size_t pos = fr.tell(); + fr.seek(4, SEEK_SET); + TrackLength = static_cast(fr.getc()); + TrackLength += static_cast(fr.getc() << 8); + fr.seek(static_cast(pos), SEEK_SET); + } + else + { + fsize = fr.read(HeaderBuf, 1, 8); + + if(std::memcmp(HeaderBuf, "MTrk", 4) != 0) + goto InvFmt; + + TrackLength = ReadBEint(HeaderBuf + 4, 4); + } + + // Read track data + TrackData[tk].resize(TrackLength); + fsize = fr.read(&TrackData[tk][0], 1, TrackLength); + totalGotten += fsize; + + if(is_GMF || is_MUS) // Note: CMF does include the track end tag. + TrackData[tk].insert(TrackData[tk].end(), EndTag + 0, EndTag + 4); + + bool ok = false; + // Read next event time + uint64_t tkDelay = ReadVarLenEx(tk, ok); + + if(ok) + CurrentPosition.track[tk].delay = static_cast(tkDelay); + else + { + std::stringstream msg; + msg << fr._fileName << ": invalid variable length in the track " << tk << "! (error code " << tkDelay << ")"; + ADLMIDI_ErrorString = msg.str(); + return false; + } + } + } + + for(size_t tk = 0; tk < TrackCount; ++tk) + totalGotten += TrackData[tk].size(); + + if(totalGotten == 0) + { + ADLMIDI_ErrorString = fr._fileName + ": Empty track data"; + return false; + } + + opl.Reset(); // Reset AdLib + //opl.Reset(); // ...twice (just in case someone misprogrammed OPL3 previously) + ch.clear(); + ch.resize(opl.NumChannels); + return true; +} 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 + * ADLMIDI Library API: Copyright (c) 2017 Vitaly Novichkov + * + * 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 . + */ + +#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(-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(-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(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::iterator + jnext = info.phys.begin(); + jnext != info.phys.end(); + ) + { + std::map::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::iterator + jnext = info.phys.begin(); + jnext != info.phys.end(); + ) + { + std::map::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(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 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) 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(data[0]), v = static_cast(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(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 bank_warnings; + + if(Ch[MidCh].bank_msb) + { + uint32_t bankid = midiins + 256 * Ch[MidCh].bank_msb; + std::set::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::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 missing_warnings; + + if(!missing_warnings.count(static_cast(midiins)) && (ains.flags & adlinsdata::Flag_NoSound)) + { + //UI.PrintLn("[%i]Playing missing instrument %i", MidCh, midiins); + missing_warnings.insert(static_cast(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(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(adlchannel[0]) + 3) + continue; + } + } + + long s = CalculateAdlChannelGoodness(a, i[ccount], MidCh); + + if(s > bs) + { + bs = s; // Best candidate wins + c = static_cast(a); + } + } + + if(c < 0) + { + //UI.PrintLn("ignored unplaceable note"); + continue; // Could not play this note. Ignore it. + } + + PrepareAdlChannelForNewNote(static_cast(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 + 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(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((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(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(c); + + if(c > std::numeric_limits::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(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(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(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(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::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::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(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(c)); + } + } + } +} diff --git a/src/adlmidi_opl3.cpp b/src/adlmidi_opl3.cpp new file mode 100644 index 0000000..a94b553 --- /dev/null +++ b/src/adlmidi_opl3.cpp @@ -0,0 +1,515 @@ +/* + * libADLMIDI is a free MIDI to WAV conversion library with OPL3 emulation + * + * Original ADLMIDI code: Copyright (c) 2010-2014 Joel Yliluoma + * ADLMIDI Library API: Copyright (c) 2017 Vitaly Novichkov + * + * 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 . + */ + +#include "adlmidi_private.hpp" + +static const unsigned short Operators[23 * 2] = +{ + // Channels 0-2 + 0x000, 0x003, 0x001, 0x004, 0x002, 0x005, // operators 0, 3, 1, 4, 2, 5 + // Channels 3-5 + 0x008, 0x00B, 0x009, 0x00C, 0x00A, 0x00D, // operators 6, 9, 7,10, 8,11 + // Channels 6-8 + 0x010, 0x013, 0x011, 0x014, 0x012, 0x015, // operators 12,15, 13,16, 14,17 + // Same for second card + 0x100, 0x103, 0x101, 0x104, 0x102, 0x105, // operators 18,21, 19,22, 20,23 + 0x108, 0x10B, 0x109, 0x10C, 0x10A, 0x10D, // operators 24,27, 25,28, 26,29 + 0x110, 0x113, 0x111, 0x114, 0x112, 0x115, // operators 30,33, 31,34, 32,35 + // Channel 18 + 0x010, 0x013, // operators 12,15 + // Channel 19 + 0x014, 0xFFF, // operator 16 + // Channel 19 + 0x012, 0xFFF, // operator 14 + // Channel 19 + 0x015, 0xFFF, // operator 17 + // Channel 19 + 0x011, 0xFFF +}; // operator 13 + +static const unsigned short Channels[23] = +{ + 0x000, 0x001, 0x002, 0x003, 0x004, 0x005, 0x006, 0x007, 0x008, // 0..8 + 0x100, 0x101, 0x102, 0x103, 0x104, 0x105, 0x106, 0x107, 0x108, // 9..17 (secondary set) + 0x006, 0x007, 0x008, 0xFFF, 0xFFF +}; // <- hw percussions, 0xFFF = no support for pitch/pan + +/* + In OPL3 mode: + 0 1 2 6 7 8 9 10 11 16 17 18 + op0 op1 op2 op12 op13 op14 op18 op19 op20 op30 op31 op32 + op3 op4 op5 op15 op16 op17 op21 op22 op23 op33 op34 op35 + 3 4 5 13 14 15 + op6 op7 op8 op24 op25 op26 + op9 op10 op11 op27 op28 op29 + Ports: + +0 +1 +2 +10 +11 +12 +100 +101 +102 +110 +111 +112 + +3 +4 +5 +13 +14 +15 +103 +104 +105 +113 +114 +115 + +8 +9 +A +108 +109 +10A + +B +C +D +10B +10C +10D + + Percussion: + bassdrum = op(0): 0xBD bit 0x10, operators 12 (0x10) and 15 (0x13) / channels 6, 6b + snare = op(3): 0xBD bit 0x08, operators 16 (0x14) / channels 7b + tomtom = op(4): 0xBD bit 0x04, operators 14 (0x12) / channels 8 + cym = op(5): 0xBD bit 0x02, operators 17 (0x17) / channels 8b + hihat = op(2): 0xBD bit 0x01, operators 13 (0x11) / channels 7 + + + In OPTi mode ("extended FM" in 82C924, 82C925, 82C931 chips): + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 + op0 op4 op6 op10 op12 op16 op18 op22 op24 op28 op30 op34 op36 op38 op40 op42 op44 op46 + op1 op5 op7 op11 op13 op17 op19 op23 op25 op29 op31 op35 op37 op39 op41 op43 op45 op47 + op2 op8 op14 op20 op26 op32 + op3 op9 op15 op21 op27 op33 for a total of 6 quad + 12 dual + Ports: ??? +*/ + + +const adlinsdata &OPL3::GetAdlMetaIns(unsigned n) +{ + return (n & DynamicMetaInstrumentTag) ? + dynamic_metainstruments[n & ~DynamicMetaInstrumentTag] + : adlins[n]; +} + +unsigned OPL3::GetAdlMetaNumber(unsigned midiins) +{ + return (AdlBank == ~0u) ? + (midiins | DynamicMetaInstrumentTag) + : banks[AdlBank][midiins]; +} + + +const adldata &OPL3::GetAdlIns(unsigned short insno) +{ + return (insno & DynamicInstrumentTag) + ? dynamic_instruments[insno & ~DynamicInstrumentTag] + : adl[insno]; +} + + +OPL3::OPL3() : + DynamicInstrumentTag(0x8000u), + DynamicMetaInstrumentTag(0x4000000u), + NumCards(1), + AdlBank(0), + NumFourOps(0), + HighTremoloMode(false), + HighVibratoMode(false), + AdlPercussionMode(false), + LogarithmicVolumes(false), + m_volumeScale(VOLUME_Generic) +{} + +void OPL3::Poke(size_t card, uint32_t index, uint32_t value) +{ + #ifdef ADLMIDI_USE_DOSBOX_OPL + cards[card].WriteReg(index, static_cast(value)); + #else + OPL3_WriteReg(&cards[card], static_cast(index), static_cast(value)); + #endif +} + +void OPL3::PokeN(size_t card, uint16_t index, uint8_t value) +{ + #ifdef ADLMIDI_USE_DOSBOX_OPL + cards[card].WriteReg(static_cast(index), value); + #else + OPL3_WriteReg(&cards[card], index, value); + #endif +} + + +void OPL3::NoteOff(size_t c) +{ + size_t card = c / 23, cc = c % 23; + + if(cc >= 18) + { + regBD[card] &= ~(0x10 >> (cc - 18)); + Poke(card, 0xBD, regBD[card]); + return; + } + + Poke(card, 0xB0 + Channels[cc], pit[c] & 0xDF); +} + +void OPL3::NoteOn(unsigned c, double hertz) // Hertz range: 0..131071 +{ + unsigned card = c / 23, cc = c % 23; + unsigned x = 0x2000; + + if(hertz < 0 || hertz > 131071) // Avoid infinite loop + return; + + while(hertz >= 1023.5) + { + hertz /= 2.0; // Calculate octave + x += 0x400; + } + + x += static_cast(hertz + 0.5); + unsigned chn = Channels[cc]; + + if(cc >= 18) + { + regBD[card] |= (0x10 >> (cc - 18)); + Poke(card, 0x0BD, regBD[card]); + x &= ~0x2000u; + //x |= 0x800; // for test + } + + if(chn != 0xFFF) + { + Poke(card, 0xA0 + chn, x & 0xFF); + Poke(card, 0xB0 + chn, pit[c] = static_cast(x >> 8)); + } +} + +void OPL3::Touch_Real(unsigned c, unsigned volume) +{ + if(volume > 63) volume = 63; + + unsigned card = c / 23, cc = c % 23; + uint16_t i = ins[c]; + unsigned o1 = Operators[cc * 2 + 0]; + unsigned o2 = Operators[cc * 2 + 1]; + const adldata &adli = GetAdlIns(i); + unsigned x = adli.modulator_40, y = adli.carrier_40; + unsigned mode = 1; // 2-op AM + + if(four_op_category[c] == 0 || four_op_category[c] == 3) + { + mode = adli.feedconn & 1; // 2-op FM or 2-op AM + } + else if(four_op_category[c] == 1 || four_op_category[c] == 2) + { + uint16_t i0, i1; + + if(four_op_category[c] == 1) + { + i0 = i; + i1 = ins[c + 3]; + mode = 2; // 4-op xx-xx ops 1&2 + } + else + { + i0 = ins[c - 3]; + i1 = i; + mode = 6; // 4-op xx-xx ops 3&4 + } + + mode += (GetAdlIns(i0).feedconn & 1) + (GetAdlIns(i1).feedconn & 1) * 2; + } + + static const bool do_ops[10][2] = + { + { false, true }, /* 2 op FM */ + { true, true }, /* 2 op AM */ + { false, false }, /* 4 op FM-FM ops 1&2 */ + { true, false }, /* 4 op AM-FM ops 1&2 */ + { false, true }, /* 4 op FM-AM ops 1&2 */ + { true, false }, /* 4 op AM-AM ops 1&2 */ + { false, true }, /* 4 op FM-FM ops 3&4 */ + { false, true }, /* 4 op AM-FM ops 3&4 */ + { false, true }, /* 4 op FM-AM ops 3&4 */ + { true, true } /* 4 op AM-AM ops 3&4 */ + }; + bool do_modulator = do_ops[ mode ][ 0 ] || ScaleModulators; + bool do_carrier = do_ops[ mode ][ 1 ] || ScaleModulators; + Poke(card, 0x40 + o1, do_modulator ? (x | 63) - volume + volume * (x & 63) / 63 : x); + + if(o2 != 0xFFF) + Poke(card, 0x40 + o2, do_carrier ? (y | 63) - volume + volume * (y & 63) / 63 : y); + + // Correct formula (ST3, AdPlug): + // 63-((63-(instrvol))/63)*chanvol + // Reduces to (tested identical): + // 63 - chanvol + chanvol*instrvol/63 + // Also (slower, floats): + // 63 + chanvol * (instrvol / 63.0 - 1) +} + +/* +void OPL3::Touch(unsigned c, unsigned volume) // Volume maxes at 127*127*127 +{ + if(LogarithmicVolumes) + Touch_Real(c, volume * 127 / (127 * 127 * 127) / 2); + else + { + // The formula below: SOLVE(V=127^3 * 2^( (A-63.49999) / 8), A) + Touch_Real(c, volume > 8725 ? static_cast(std::log(volume) * 11.541561 + (0.5 - 104.22845)) : 0); + // The incorrect formula below: SOLVE(V=127^3 * (2^(A/63)-1), A) + //Touch_Real(c, volume>11210 ? 91.61112 * std::log(4.8819E-7*volume + 1.0)+0.5 : 0); + } +}*/ + +void OPL3::Patch(uint16_t c, uint16_t i) +{ + uint16_t card = c / 23, cc = c % 23; + static const uint8_t data[4] = {0x20, 0x60, 0x80, 0xE0}; + ins[c] = i; + uint16_t o1 = Operators[cc * 2 + 0]; + uint16_t o2 = Operators[cc * 2 + 1]; + const adldata &adli = GetAdlIns(i); + unsigned x = adli.modulator_E862, y = adli.carrier_E862; + + for(unsigned a = 0; a < 4; ++a, x >>= 8, y >>= 8) + { + Poke(card, data[a] + o1, x & 0xFF); + + if(o2 != 0xFFF) + Poke(card, data[a] + o2, y & 0xFF); + } +} + +void OPL3::Pan(unsigned c, unsigned value) +{ + unsigned card = c / 23, cc = c % 23; + + if(Channels[cc] != 0xFFF) + Poke(card, 0xC0 + Channels[cc], GetAdlIns(ins[c]).feedconn | value); +} + +void OPL3::Silence() // Silence all OPL channels. +{ + for(unsigned c = 0; c < NumChannels; ++c) + { + NoteOff(c); + Touch_Real(c, 0); + } +} + +void OPL3::updateFlags() +{ + unsigned fours = NumFourOps; + + for(unsigned card = 0; card < NumCards; ++card) + { + Poke(card, 0x0BD, regBD[card] = (HighTremoloMode * 0x80 + + HighVibratoMode * 0x40 + + AdlPercussionMode * 0x20)); + unsigned fours_this_card = std::min(fours, 6u); + Poke(card, 0x104, (1 << fours_this_card) - 1); + fours -= fours_this_card; + } + + // Mark all channels that are reserved for four-operator function + if(AdlPercussionMode == 1) + for(unsigned a = 0; a < NumCards; ++a) + { + for(unsigned b = 0; b < 5; ++b) + four_op_category[a * 23 + 18 + b] = static_cast(b + 3); + + for(unsigned b = 0; b < 3; ++b) + four_op_category[a * 23 + 6 + b] = 8; + } + + unsigned nextfour = 0; + + for(unsigned a = 0; a < NumFourOps; ++a) + { + four_op_category[nextfour ] = 1; + four_op_category[nextfour + 3] = 2; + + switch(a % 6) + { + case 0: + case 1: + nextfour += 1; + break; + + case 2: + nextfour += 9 - 2; + break; + + case 3: + case 4: + nextfour += 1; + break; + + case 5: + nextfour += 23 - 9 - 2; + break; + } + } +} + +void OPL3::ChangeVolumeRangesModel(ADLMIDI_VolumeModels volumeModel) +{ + switch(volumeModel) + { + case ADLMIDI_VolumeModel_AUTO://Do nothing until restart playing + break; + + case ADLMIDI_VolumeModel_Generic: + m_volumeScale = OPL3::VOLUME_Generic; + break; + + case ADLMIDI_VolumeModel_CMF: + LogarithmicVolumes = true; + m_volumeScale = OPL3::VOLUME_CMF; + break; + + case ADLMIDI_VolumeModel_DMX: + m_volumeScale = OPL3::VOLUME_DMX; + break; + + case ADLMIDI_VolumeModel_APOGEE: + m_volumeScale = OPL3::VOLUME_APOGEE; + break; + + case ADLMIDI_VolumeModel_9X: + m_volumeScale = OPL3::VOLUME_9X; + break; + } +} + +void OPL3::Reset() +{ + LogarithmicVolumes = false; + #ifdef ADLMIDI_USE_DOSBOX_OPL + DBOPL::Handler emptyChip; //Constructors inside are will initialize necessary fields + #else + _opl3_chip emptyChip; + memset(&emptyChip, 0, sizeof(_opl3_chip)); + #endif + cards.clear(); + ins.clear(); + pit.clear(); + regBD.clear(); + cards.resize(NumCards, emptyChip); + NumChannels = NumCards * 23; + ins.resize(NumChannels, 189); + pit.resize(NumChannels, 0); + regBD.resize(NumCards, 0); + four_op_category.resize(NumChannels, 0); + + for(unsigned p = 0, a = 0; a < NumCards; ++a) + { + for(unsigned b = 0; b < 18; ++b) + four_op_category[p++] = 0; + + for(unsigned b = 0; b < 5; ++b) + four_op_category[p++] = 8; + } + + static const uint16_t data[] = + { + 0x004, 96, 0x004, 128, // Pulse timer + 0x105, 0, 0x105, 1, 0x105, 0, // Pulse OPL3 enable + 0x001, 32, 0x105, 1 // Enable wave, OPL3 extensions + }; + unsigned fours = NumFourOps; + + for(unsigned card = 0; card < NumCards; ++card) + { + #ifdef ADLMIDI_USE_DOSBOX_OPL + cards[card].Init(_parent->PCM_RATE); + #else + OPL3_Reset(&cards[card], static_cast(_parent->PCM_RATE)); + #endif + + for(unsigned a = 0; a < 18; ++a) Poke(card, 0xB0 + Channels[a], 0x00); + + for(unsigned a = 0; a < sizeof(data) / sizeof(*data); a += 2) + PokeN(card, data[a], static_cast(data[a + 1])); + + Poke(card, 0x0BD, regBD[card] = (HighTremoloMode * 0x80 + + HighVibratoMode * 0x40 + + AdlPercussionMode * 0x20)); + unsigned fours_this_card = std::min(fours, 6u); + Poke(card, 0x104, (1 << fours_this_card) - 1); + //fprintf(stderr, "Card %u: %u four-ops.\n", card, fours_this_card); + fours -= fours_this_card; + } + + // Mark all channels that are reserved for four-operator function + if(AdlPercussionMode == 1) + for(unsigned a = 0; a < NumCards; ++a) + { + for(unsigned b = 0; b < 5; ++b) + four_op_category[a * 23 + 18 + b] = static_cast(b + 3); + + for(unsigned b = 0; b < 3; ++b) + four_op_category[a * 23 + 6 + b] = 8; + } + + unsigned nextfour = 0; + + for(unsigned a = 0; a < NumFourOps; ++a) + { + four_op_category[nextfour ] = 1; + four_op_category[nextfour + 3] = 2; + + switch(a % 6) + { + case 0: + case 1: + nextfour += 1; + break; + + case 2: + nextfour += 9 - 2; + break; + + case 3: + case 4: + nextfour += 1; + break; + + case 5: + nextfour += 23 - 9 - 2; + break; + } + } + + /**/ + /* + In two-op mode, channels 0..8 go as follows: + Op1[port] Op2[port] + Channel 0: 00 00 03 03 + Channel 1: 01 01 04 04 + Channel 2: 02 02 05 05 + Channel 3: 06 08 09 0B + Channel 4: 07 09 10 0C + Channel 5: 08 0A 11 0D + Channel 6: 12 10 15 13 + Channel 7: 13 11 16 14 + Channel 8: 14 12 17 15 + In four-op mode, channels 0..8 go as follows: + Op1[port] Op2[port] Op3[port] Op4[port] + Channel 0: 00 00 03 03 06 08 09 0B + Channel 1: 01 01 04 04 07 09 10 0C + Channel 2: 02 02 05 05 08 0A 11 0D + Channel 3: CHANNEL 0 SLAVE + Channel 4: CHANNEL 1 SLAVE + Channel 5: CHANNEL 2 SLAVE + Channel 6: 12 10 15 13 + Channel 7: 13 11 16 14 + Channel 8: 14 12 17 15 + Same goes principally for channels 9-17 respectively. + */ + Silence(); +} diff --git a/src/adlmidi_private.cpp b/src/adlmidi_private.cpp new file mode 100644 index 0000000..0c18958 --- /dev/null +++ b/src/adlmidi_private.cpp @@ -0,0 +1,59 @@ +/* + * libADLMIDI is a free MIDI to WAV conversion library with OPL3 emulation + * + * Original ADLMIDI code: Copyright (c) 2010-2014 Joel Yliluoma + * ADLMIDI Library API: Copyright (c) 2017 Vitaly Novichkov + * + * 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 . + */ + +#include "adlmidi_private.hpp" + +std::string ADLMIDI_ErrorString; + +int adlRefreshNumCards(ADL_MIDIPlayer *device) +{ + unsigned n_fourop[2] = {0, 0}, n_total[2] = {0, 0}; + + for(unsigned a = 0; a < 256; ++a) + { + unsigned insno = banks[device->AdlBank][a]; + + if(insno == 198) continue; + + ++n_total[a / 128]; + + if(adlins[insno].adlno1 != adlins[insno].adlno2) + ++n_fourop[a / 128]; + } + + device->NumFourOps = + (n_fourop[0] >= n_total[0] * 7 / 8) ? device->NumCards * 6 + : (n_fourop[0] < n_total[0] * 1 / 8) ? 0 + : (device->NumCards == 1 ? 1 : device->NumCards * 4); + reinterpret_cast(device->adl_midiPlayer)->opl.NumFourOps = device->NumFourOps; + + if(n_fourop[0] >= n_total[0] * 15 / 16 && device->NumFourOps == 0) + { + ADLMIDI_ErrorString = "ERROR: You have selected a bank that consists almost exclusively of four-op patches.\n" + " The results (silence + much cpu load) would be probably\n" + " not what you want, therefore ignoring the request.\n"; + return -1; + } + + return 0; +} diff --git a/src/adlmidi_private.hpp b/src/adlmidi_private.hpp new file mode 100644 index 0000000..4f89cae --- /dev/null +++ b/src/adlmidi_private.hpp @@ -0,0 +1,494 @@ +/* + * libADLMIDI is a free MIDI to WAV conversion library with OPL3 emulation + * + * Original ADLMIDI code: Copyright (c) 2010-2014 Joel Yliluoma + * ADLMIDI Library API: Copyright (c) 2017 Vitaly Novichkov + * + * 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 . + */ + +#ifndef ADLMIDI_PRIVATE_HPP +#define ADLMIDI_PRIVATE_HPP + +// Setup compiler defines useful for exporting required public API symbols in gme.cpp +#ifndef ADLMIDI_EXPORT +#if defined (_WIN32) && defined(ADLMIDI_BUILD_DLL) +#define ADLMIDI_EXPORT __declspec(dllexport) +#elif defined (LIBADLMIDI_VISIBILITY) +#define ADLMIDI_EXPORT __attribute__((visibility ("default"))) +#else +#define ADLMIDI_EXPORT +#endif +#endif + +#ifdef _WIN32 +#undef NO_OLDNAMES +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // vector +#include // deque +#include // exp, log, ceil +#include +#include // numeric_limit + +#include +#include + +#include "fraction.h" +#ifdef ADLMIDI_USE_DOSBOX_OPL +#include "dbopl.h" +#else +#include "nukedopl3.h" +#endif + +#include "adldata.hh" +#include "adlmidi.h" + +extern std::string ADLMIDI_ErrorString; + +class MIDIplay; +struct OPL3 +{ + friend class MIDIplay; + uint32_t NumChannels; + char ____padding[4]; + ADL_MIDIPlayer *_parent; + #ifdef ADLMIDI_USE_DOSBOX_OPL + std::vector cards; + #else + std::vector<_opl3_chip> cards; + #endif +private: + std::vector ins; // index to adl[], cached, needed by Touch() + std::vector pit; // value poked to B0, cached, needed by NoteOff)( + std::vector regBD; + + std::vector dynamic_metainstruments; // Replaces adlins[] when CMF file + std::vector dynamic_instruments; // Replaces adl[] when CMF file + const unsigned DynamicInstrumentTag /* = 0x8000u*/, + DynamicMetaInstrumentTag /* = 0x4000000u*/; + const adlinsdata &GetAdlMetaIns(unsigned n); + unsigned GetAdlMetaNumber(unsigned midiins); + const adldata &GetAdlIns(unsigned short insno); + +public: + unsigned int NumCards; + unsigned int AdlBank; + unsigned int NumFourOps; + bool HighTremoloMode; + bool HighVibratoMode; + bool AdlPercussionMode; + bool ScaleModulators; + + bool LogarithmicVolumes; + char ___padding2[3]; + enum VolumesScale + { + VOLUME_Generic, + VOLUME_CMF, + VOLUME_DMX, + VOLUME_APOGEE, + VOLUME_9X, + } m_volumeScale; + + OPL3(); + char ____padding3[8]; + std::vector four_op_category; // 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 + + void Poke(size_t card, uint32_t index, uint32_t value); + void PokeN(size_t card, uint16_t index, uint8_t value); + void NoteOff(size_t c); + void NoteOn(unsigned c, double hertz); + void Touch_Real(unsigned c, unsigned volume); + //void Touch(unsigned c, unsigned volume) + + void Patch(uint16_t c, uint16_t i); + void Pan(unsigned c, unsigned value); + void Silence(); + void updateFlags(); + void ChangeVolumeRangesModel(ADLMIDI_VolumeModels volumeModel); + void Reset(); +}; + + +class MIDIplay +{ + // Information about each track + struct Position + { + bool began; + char padding[7]; + double wait; + struct TrackInfo + { + size_t ptr; + long delay; + int status; + char padding2[4]; + TrackInfo(): ptr(0), delay(0), status(0) {} + }; + std::vector track; + + Position(): began(false), wait(0.0), track() { } + } CurrentPosition, LoopBeginPosition, trackBeginPosition; + + std::map devices; + std::map current_device; + + // Persistent settings for each MIDI channel + struct MIDIchannel + { + uint16_t portamento; + uint8_t bank_lsb, bank_msb; + uint8_t patch; + uint8_t volume, expression; + uint8_t panning, vibrato, sustain; + char ____padding[6]; + double bend, bendsense; + double vibpos, vibspeed, vibdepth; + int64_t vibdelay; + uint8_t lastlrpn, lastmrpn; + bool nrpn; + struct NoteInfo + { + // Current pressure + uint8_t vol; + // Tone selected on noteon: + char ____padding[1]; + int16_t tone; + // Patch selected on noteon; index to banks[AdlBank][] + uint8_t midiins; + // Index to physical adlib data structure, adlins[] + char ____padding2[3]; + uint32_t insmeta; + char ____padding3[4]; + // List of adlib channels it is currently occupying. + std::map phys; + }; + typedef std::map activenotemap_t; + typedef activenotemap_t::iterator activenoteiterator; + char ____padding2[5]; + activenotemap_t activenotes; + + MIDIchannel() + : portamento(0), + bank_lsb(0), bank_msb(0), patch(0), + volume(100), expression(127), + panning(0x30), vibrato(0), sustain(0), + bend(0.0), bendsense(2 / 8192.0), + vibpos(0), vibspeed(2 * 3.141592653 * 5.0), + vibdepth(0.5 / 127), vibdelay(0), + lastlrpn(0), lastmrpn(0), nrpn(false), + activenotes() { } + }; + std::vector Ch; + bool cmf_percussion_mode; + + // Additional information about AdLib channels + struct AdlChannel + { + // For collisions + struct Location + { + uint16_t MidCh; + uint8_t note; + bool operator==(const Location &b) const + { + return MidCh == b.MidCh && note == b.note; + } + bool operator< (const Location &b) const + { + return MidCh < b.MidCh || (MidCh == b.MidCh && note < b.note); + } + char ____padding[1]; + }; + struct LocationData + { + bool sustained; + char ____padding[1]; + uint16_t ins; // a copy of that in phys[] + char ____padding2[4]; + int64_t kon_time_until_neglible; + int64_t vibdelay; + }; + typedef std::map users_t; + users_t users; + + // If the channel is keyoff'd + long koff_time_until_neglible; + // For channel allocation: + AdlChannel(): users(), koff_time_until_neglible(0) { } + void AddAge(int64_t ms); + }; +public: + char ____padding[7]; +private: + std::vector ch; + std::vector > TrackData; +public: + MIDIplay(); + ~MIDIplay() + {} + + ADL_MIDIPlayer *config; + std::string musTitle; + fraction InvDeltaTicks, Tempo; + bool trackStart, + loopStart, + loopEnd, + loopStart_passed /*Tells that "loopStart" already passed*/, + invalidLoop /*Loop points are invalid (loopStart after loopEnd or loopStart and loopEnd are on same place)*/, + loopStart_hit /*loopStart entry was hited in previous tick*/; + char ____padding2[2]; + OPL3 opl; +public: + static uint64_t ReadBEint(const void *buffer, size_t nbytes); + static uint64_t ReadLEint(const void *buffer, size_t nbytes); + + uint64_t ReadVarLen(size_t tk); + uint64_t ReadVarLenEx(size_t tk, bool &ok); + + /* + * A little class gives able to read filedata from disk and also from a memory segment + */ + class fileReader + { + public: + enum relTo + { + SET = 0, + CUR = 1, + END = 2 + }; + + fileReader() + { + fp = NULL; + mp = NULL; + mp_size = 0; + mp_tell = 0; + } + ~fileReader() + { + close(); + } + + void openFile(const char *path) + { + fp = std::fopen(path, "rb"); + _fileName = path; + mp = NULL; + mp_size = 0; + mp_tell = 0; + } + + void openData(void *mem, size_t lenght) + { + fp = NULL; + mp = mem; + mp_size = lenght; + mp_tell = 0; + } + + void seek(long pos, int rel_to) + { + if(fp) + std::fseek(fp, pos, rel_to); + else + { + switch(rel_to) + { + case SET: + mp_tell = static_cast(pos); + break; + + case END: + mp_tell = mp_size - static_cast(pos); + break; + + case CUR: + mp_tell = mp_tell + static_cast(pos); + break; + } + + if(mp_tell > mp_size) + mp_tell = mp_size; + } + } + + inline void seeku(unsigned long pos, int rel_to) + { + seek(static_cast(pos), rel_to); + } + + size_t read(void *buf, size_t num, size_t size) + { + if(fp) + return std::fread(buf, num, size, fp); + else + { + size_t pos = 0; + size_t maxSize = static_cast(size * num); + + while((pos < maxSize) && (mp_tell < mp_size)) + { + reinterpret_cast(buf)[pos] = reinterpret_cast(mp)[mp_tell]; + mp_tell++; + pos++; + } + + return pos; + } + } + + int getc() + { + if(fp) + return std::getc(fp); + else + { + if(mp_tell >= mp_size) + return -1; + + int x = reinterpret_cast(mp)[mp_tell]; + mp_tell++; + return x; + } + } + + size_t tell() + { + if(fp) + return static_cast(std::ftell(fp)); + else + return mp_tell; + } + + void close() + { + if(fp) std::fclose(fp); + + fp = NULL; + mp = NULL; + mp_size = 0; + mp_tell = 0; + } + + bool isValid() + { + return (fp) || (mp); + } + + bool eof() + { + return mp_tell >= mp_size; + } + std::string _fileName; + std::FILE *fp; + void *mp; + size_t mp_size; + size_t mp_tell; + }; + + bool LoadMIDI(const std::string &filename); + bool LoadMIDI(void *data, unsigned long size); + bool LoadMIDI(fileReader &fr); + + /* Periodic tick handler. + * Input: s = seconds since last call + * Input: granularity = don't expect intervals smaller than this, in seconds + * Output: desired number of seconds until next call + */ + double Tick(double s, double granularity); + +private: + enum + { + Upd_Patch = 0x1, + Upd_Pan = 0x2, + Upd_Volume = 0x4, + Upd_Pitch = 0x8, + Upd_All = Upd_Pan + Upd_Volume + Upd_Pitch, + Upd_Off = 0x20 + }; + + void NoteUpdate(uint16_t MidCh, + MIDIchannel::activenoteiterator i, + unsigned props_mask, + int32_t select_adlchn = -1); + void ProcessEvents(); + void HandleEvent(size_t tk); + + // Determine how good a candidate this adlchannel + // would be for playing a note from this instrument. + long CalculateAdlChannelGoodness(unsigned c, uint16_t ins, uint16_t /*MidCh*/) const; + + // A new note will be played on this channel using this instrument. + // Kill existing notes on this channel (or don't, if we do arpeggio) + void PrepareAdlChannelForNewNote(size_t c, int ins); + + void KillOrEvacuate( + size_t from_channel, + AdlChannel::users_t::iterator j, + MIDIchannel::activenoteiterator i); + void KillSustainingNotes(int32_t MidCh = -1, int32_t this_adlchn = -1); + void SetRPN(unsigned MidCh, unsigned value, bool MSB); + //void UpdatePortamento(unsigned MidCh) + void NoteUpdate_All(uint16_t MidCh, unsigned props_mask); + void NoteOff(uint16_t MidCh, uint8_t note); + void UpdateVibrato(double amount); + void UpdateArpeggio(double /*amount*/); + +public: + uint64_t ChooseDevice(const std::string &name); +}; + +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((w >>(c * 8)) & 0xFF); + } +}; + + +extern int adlRefreshNumCards(ADL_MIDIPlayer *device); + + +#endif // ADLMIDI_PRIVATE_HPP diff --git a/src/dbopl.cpp b/src/dbopl.cpp index e534c99..8bb3eb4 100644 --- a/src/dbopl.cpp +++ b/src/dbopl.cpp @@ -59,16 +59,16 @@ namespace DBOPL //Try to use most precision for frequencies //Else try to keep different waves in synch //#define WAVE_PRECISION 1 -#ifndef WAVE_PRECISION + #ifndef WAVE_PRECISION //Wave bits available in the top of the 32bit range //Original adlib uses 10.10, we use 10.22 #define WAVE_BITS 10 -#else + #else //Need some extra bits at the top to have room for octaves and frequency multiplier //We support to 8 times lower rate //128 * 15 * 8 = 15350, 2^13.9, so need 14 bits #define WAVE_BITS 14 -#endif + #endif #define WAVE_SH ( 32 - WAVE_BITS ) #define WAVE_MASK ( ( 1 << WAVE_SH ) - 1 ) @@ -80,13 +80,13 @@ namespace DBOPL //Maximum amount of attenuation bits //Envelope goes to 511, 9 bits -#if (DBOPL_WAVE == WAVE_TABLEMUL ) + #if (DBOPL_WAVE == WAVE_TABLEMUL ) //Uses the value directly #define ENV_BITS ( 9 ) -#else + #else //Add 3 bits here for more accuracy and would have to be shifted up either way #define ENV_BITS ( 9 ) -#endif + #endif //Limits of the envelope with those bits and when the envelope goes silent #define ENV_MIN 0 #define ENV_EXTRA ( ENV_BITS - 9 ) @@ -101,9 +101,9 @@ namespace DBOPL #define MUL_SH 16 //Check some ranges -#if ENV_EXTRA > 3 + #if ENV_EXTRA > 3 #error Too many envelope bits -#endif + #endif //How much to substract from the base value for the final attenuation @@ -141,16 +141,16 @@ namespace DBOPL 32, }; -#if ( DBOPL_WAVE == WAVE_HANDLER ) || ( DBOPL_WAVE == WAVE_TABLELOG ) + #if ( DBOPL_WAVE == WAVE_HANDLER ) || ( DBOPL_WAVE == WAVE_TABLELOG ) static Bit16u ExpTable[ 256 ]; -#endif + #endif -#if ( DBOPL_WAVE == WAVE_HANDLER ) + #if ( DBOPL_WAVE == WAVE_HANDLER ) //PI table used by WAVEHANDLER static Bit16u SinTable[ 512 ]; -#endif + #endif -#if ( DBOPL_WAVE > WAVE_HANDLER ) + #if ( DBOPL_WAVE > WAVE_HANDLER ) //Layout of the waveform table in 512 entry intervals //With overlapping waves we reduce the table to half it's size @@ -181,11 +181,11 @@ namespace DBOPL 512, 0, 0, 0, 0, 512, 512, 256, }; -#endif + #endif -#if ( DBOPL_WAVE == WAVE_TABLEMUL ) + #if ( DBOPL_WAVE == WAVE_TABLEMUL ) static Bit16u MulTable[ 384 ]; -#endif + #endif static Bit8u KslTable[ 8 * 16 ]; static Bit8u TremoloTable[ TREMOLO_TABLE ]; @@ -229,7 +229,7 @@ namespace DBOPL } } -#if ( DBOPL_WAVE == WAVE_HANDLER ) + #if ( DBOPL_WAVE == WAVE_HANDLER ) /* Generate the different waveforms out of the sine/exponetial table using handlers */ @@ -239,13 +239,13 @@ namespace DBOPL Bitu index = total & 0xff; Bitu sig = ExpTable[ index ]; Bitu exp = total >> 8; -#if 0 + #if 0 //Check if we overflow the 31 shift limit if(exp >= 32) LOG_MSG("WTF %d %d", total, exp); -#endif + #endif return (sig >> exp); }; @@ -310,7 +310,7 @@ namespace DBOPL WaveForm4, WaveForm5, WaveForm6, WaveForm7 }; -#endif + #endif /* Operator @@ -386,21 +386,21 @@ namespace DBOPL { Bit32u freq = chanData & ((1 << 10) - 1); Bit32u block = (chanData >> 10) & 0xff; -#ifdef WAVE_PRECISION + #ifdef WAVE_PRECISION block = 7 - block; waveAdd = (freq * freqMul) >> block; -#else + #else waveAdd = (freq << block) * freqMul; -#endif + #endif if(reg20 & MASK_VIBRATO) { vibStrength = (Bit8u)(freq >> 7); -#ifdef WAVE_PRECISION + #ifdef WAVE_PRECISION vibrato = (vibStrength * freqMul) >> block; -#else + #else vibrato = (vibStrength << block) * freqMul; -#endif + #endif } else { @@ -603,13 +603,13 @@ namespace DBOPL //in opl3 mode you can always selet 7 waveforms regardless of waveformselect Bit8u waveForm = val & ((0x3 & chip->waveFormMask) | (0x7 & chip->opl3Active)); regE0 = val; -#if ( DBOPL_WAVE == WAVE_HANDLER ) + #if ( DBOPL_WAVE == WAVE_HANDLER ) waveHandler = WaveHandlerTable[ waveForm ]; -#else + #else waveBase = WaveTable + WaveBaseTable[ waveForm ]; waveStart = WaveStartTable[ waveForm ] << WAVE_SH; waveMask = WaveMaskTable[ waveForm ]; -#endif + #endif } INLINE void Operator::SetState(Bit8u s) @@ -650,11 +650,11 @@ namespace DBOPL if(!keyOn) { //Restart the frequency generator -#if ( DBOPL_WAVE > WAVE_HANDLER ) + #if ( DBOPL_WAVE > WAVE_HANDLER ) waveIndex = waveStart; -#else + #else waveIndex = 0; -#endif + #endif rateIndex = 0; SetState(ATTACK); } @@ -675,20 +675,20 @@ namespace DBOPL INLINE Bits Operator::GetWave(Bitu index, Bitu vol) { -#if ( DBOPL_WAVE == WAVE_HANDLER ) + #if ( DBOPL_WAVE == WAVE_HANDLER ) return waveHandler(index, vol << (3 - ENV_EXTRA)); -#elif ( DBOPL_WAVE == WAVE_TABLEMUL ) + #elif ( DBOPL_WAVE == WAVE_TABLEMUL ) return (waveBase[ index & waveMask ] * MulTable[ vol >> ENV_EXTRA ]) >> MUL_SH; -#elif ( DBOPL_WAVE == WAVE_TABLELOG ) + #elif ( DBOPL_WAVE == WAVE_TABLELOG ) Bit32s wave = waveBase[ index & waveMask ]; Bit32u total = (wave & 0x7fff) + vol << (3 - ENV_EXTRA); Bit32s sig = ExpTable[ total & 0xff ]; Bit32u exp = total >> 8; Bit32s neg = wave >> 16; return ((sig ^ neg) - neg) >> exp; -#else + #else #error "No valid wave routine" -#endif + #endif } Bits INLINE Operator::GetSample(Bits modulation) @@ -747,7 +747,7 @@ namespace DBOPL feedback = 31; fourMask = 0; synthHandler = &Channel::BlockTemplate< sm2FM >; - }; + } void Channel::SetChanData(const Chip *chip, Bit32u data) { @@ -952,7 +952,7 @@ namespace DBOPL Bit8u val = regC0; regC0 ^= 0xff; WriteC0(chip, val); - }; + } template< bool opl3Mode> INLINE void Channel::GeneratePercussion(Chip *chip, Bit32s *output) @@ -1514,19 +1514,19 @@ namespace DBOPL tremoloIndex = 0; //With higher octave this gets shifted up //-1 since the freqCreateTable = *2 -#ifdef WAVE_PRECISION + #ifdef WAVE_PRECISION double freqScale = (1 << 7) * scale * (1 << (WAVE_SH - 1 - 10)); for(int i = 0; i < 16; i++) freqMul[i] = (Bit32u)(0.5 + freqScale * FreqCreateTable[ i ]); -#else + #else Bit32u freqScale = (Bit32u)(0.5 + scale * (1 << (WAVE_SH - 1 - 10))); for(int i = 0; i < 16; i++) freqMul[i] = freqScale * FreqCreateTable[ i ]; -#endif + #endif //-3 since the real envelope takes 8 steps to reach the single value we supply for(Bit8u i = 0; i < 76; i++) @@ -1704,7 +1704,7 @@ namespace DBOPL return; doneTables = true; -#if ( DBOPL_WAVE == WAVE_HANDLER ) || ( DBOPL_WAVE == WAVE_TABLELOG ) + #if ( DBOPL_WAVE == WAVE_HANDLER ) || ( DBOPL_WAVE == WAVE_TABLELOG ) //Exponential volume table, same as the real adlib for(int i = 0; i < 256; i++) @@ -1716,16 +1716,16 @@ namespace DBOPL ExpTable[i] *= 2; } -#endif -#if ( DBOPL_WAVE == WAVE_HANDLER ) + #endif + #if ( DBOPL_WAVE == WAVE_HANDLER ) //Add 0.5 for the trunc rounding of the integer cast //Do a PI sinetable instead of the original 0.5 PI for(int i = 0; i < 512; i++) SinTable[i] = (Bit16s)(0.5 - log10(sin((i + 0.5) * (PI / 512.0))) / log10(2.0) * 256); -#endif -#if ( DBOPL_WAVE == WAVE_TABLEMUL ) + #endif + #if ( DBOPL_WAVE == WAVE_TABLEMUL ) //Multiplication based tables for(int i = 0; i < 384; i++) @@ -1750,8 +1750,8 @@ namespace DBOPL WaveTable[ 0x6ff - i ] = -WaveTable[ 0x700 + i ]; } -#endif -#if ( DBOPL_WAVE == WAVE_TABLELOG ) + #endif + #if ( DBOPL_WAVE == WAVE_TABLELOG ) //Sine Wave Base for(int i = 0; i < 512; i++) @@ -1767,11 +1767,11 @@ namespace DBOPL WaveTable[ 0x6ff - i ] = ((Bit16s)0x8000) | i * 8; } -#endif + #endif // | |//\\|____|WAV7|//__|/\ |____|/\/\| // |\\//| | |WAV7| | \/| | | // |06 |0126|27 |7 |3 |4 |4 5 |5 | -#if (( DBOPL_WAVE == WAVE_TABLELOG ) || ( DBOPL_WAVE == WAVE_TABLEMUL )) + #if (( DBOPL_WAVE == WAVE_TABLELOG ) || ( DBOPL_WAVE == WAVE_TABLEMUL )) for(int i = 0; i < 256; i++) { @@ -1790,7 +1790,7 @@ namespace DBOPL WaveTable[ 0xf00 + i ] = WaveTable[ 0x200 + i * 2 ]; } -#endif + #endif //Create the ksl table for(int oct = 0; oct < 8; oct++) @@ -1863,7 +1863,7 @@ namespace DBOPL OpOffsetTable[i] = ChanOffsetTable[ chNum ] + blah; } -#if 0 + #if 0 //Stupid checks if table's are correct for(Bitu i = 0; i < 18; i++) @@ -1900,7 +1900,7 @@ namespace DBOPL find = find; } -#endif + #endif } Bit32u Handler::WriteAddr(Bit32u port, Bit8u val) diff --git a/src/nukedopl3.c b/src/nukedopl3.c index 1cf2745..3557df9 100644 --- a/src/nukedopl3.c +++ b/src/nukedopl3.c @@ -1,28 +1,28 @@ -// -// Copyright (C) 2013-2016 Alexey Khokholov (Nuke.YKT) -// -// 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 2 -// of the License, or (at your option) 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. -// -// -// Nuked OPL3 emulator. -// Thanks: -// MAME Development Team(Jarek Burczynski, Tatsuyuki Satoh): -// Feedback and Rhythm part calculation information. -// forums.submarine.org.uk(carbon14, opl3): -// Tremolo and phase generator calculation information. -// OPLx decapsulated(Matthew Gambrell, Olli Niemitalo): -// OPL2 ROMs. -// -// version: 1.7.4 -// +/* + * Copyright (C) 2013-2016 Alexey Khokholov (Nuke.YKT) + * + * 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 2 + * of the License, or (at your option) 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. + * + * + * Nuked OPL3 emulator. + * Thanks: + * MAME Development Team(Jarek Burczynski, Tatsuyuki Satoh): + * Feedback and Rhythm part calculation information. + * forums.submarine.org.uk(carbon14, opl3): + * Tremolo and phase generator calculation information. + * OPLx decapsulated(Matthew Gambrell, Olli Niemitalo): + * OPL2 ROMs. + * + * version: 1.7.4 + */ #include #include @@ -31,13 +31,13 @@ #define RSM_FRAC 10 -#ifdef __GNUC__ +#if defined(__GNUC__) && __USE_ISOC99 #define INLINE __attribute__((always_inline)) inline #else #define INLINE #endif -// Channel types +/* Channel types */ enum { ch_2op = 0, ch_4op = 1, @@ -45,16 +45,16 @@ enum { ch_drum = 3 }; -// Envelope key types +/* Envelope key types */ enum { egk_norm = 0x01, egk_drum = 0x02 }; -// -// logsin table -// +/* */ +/* logsin table */ +/* */ static const Bit16u logsinrom[256] = { 0x859, 0x6c3, 0x607, 0x58b, 0x52e, 0x4e4, 0x4a6, 0x471, @@ -91,9 +91,9 @@ static const Bit16u logsinrom[256] = { 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000, 0x000 }; -// -// exp table -// +/* */ +/* exp table */ +/* */ static const Bit16u exprom[256] = { 0x000, 0x003, 0x006, 0x008, 0x00b, 0x00e, 0x011, 0x014, @@ -130,19 +130,19 @@ static const Bit16u exprom[256] = { 0x3d4, 0x3da, 0x3df, 0x3e4, 0x3ea, 0x3ef, 0x3f5, 0x3fa }; -// -// freq mult table multiplied by 2 -// -// 1/2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 12, 12, 15, 15 -// +/* */ +/* freq mult table multiplied by 2 */ +/* */ +/* 1/2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 12, 12, 15, 15 */ +/* */ static const Bit8u mt[16] = { 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30 }; -// -// ksl table -// +/* */ +/* ksl table */ +/* */ static const Bit8u kslrom[16] = { 0, 32, 40, 45, 48, 51, 53, 55, 56, 58, 59, 60, 61, 62, 63, 64 @@ -152,9 +152,9 @@ static const Bit8u kslshift[4] = { 8, 1, 2, 0 }; -// -// envelope generator constants -// +/* */ +/* envelope generator constants */ +/* */ static const Bit8u eg_incstep[3][4][8] = { { @@ -185,9 +185,9 @@ static const Bit8s eg_incsh[16] = { 0, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, -1, -2 }; -// -// address decoding -// +/* */ +/* address decoding */ +/* */ static const Bit8s ad_slot[0x20] = { 0, 1, 2, 3, 4, 5, -1, -1, 6, 7, 8, 9, 10, 11, -1, -1, @@ -198,9 +198,9 @@ static const Bit8u ch_slot[18] = { 0, 1, 2, 6, 7, 8, 12, 13, 14, 18, 19, 20, 24, 25, 26, 30, 31, 32 }; -// -// Envelope generator -// +/* */ +/* Envelope generator */ +/* */ typedef Bit16s(*envelope_sinfunc)(Bit16u phase, Bit16u envelope); typedef void(*envelope_genfunc)(opl3_slot *slott); @@ -545,9 +545,9 @@ static INLINE void OPL3_EnvelopeKeyOff(opl3_slot *slot, Bit8u type) } } -// -// Phase Generator -// +/* */ +/* Phase Generator */ +/* */ static INLINE void OPL3_PhaseGenerate(opl3_slot *slot) { @@ -583,9 +583,9 @@ static INLINE void OPL3_PhaseGenerate(opl3_slot *slot) slot->pg_phase += (basefreq * mt[slot->reg_mult]) >> 1; } -// -// Noise Generator -// +/* */ +/* Noise Generator */ +/* */ static INLINE void OPL3_NoiseGenerate(opl3_chip *chip) { @@ -596,9 +596,9 @@ static INLINE void OPL3_NoiseGenerate(opl3_chip *chip) chip->noise >>= 1; } -// -// Slot -// +/* */ +/* Slot */ +/* */ static INLINE void OPL3_SlotWrite20(opl3_slot *slot, Bit8u data) { @@ -694,11 +694,11 @@ static INLINE void OPL3_SlotCalcFB(opl3_slot *slot) slot->prout = slot->out; } -// -// Channel -// +/* */ +/* Channel */ +/* */ -//static void OPL3_ChannelSetupAlg(opl3_channel *channel); +/* static void OPL3_ChannelSetupAlg(opl3_channel *channel); */ static INLINE void OPL3_ChannelSetupAlg(opl3_channel *channel) { if (channel->chtype == ch_drum) @@ -824,7 +824,7 @@ static INLINE void OPL3_ChannelUpdateRhythm(opl3_chip *chip, Bit8u data) chip->channel[chnum].chtype = ch_drum; } OPL3_ChannelSetupAlg(channel6); - //hh + /* hh */ if (chip->rhy & 0x01) { OPL3_EnvelopeKeyOn(channel7->chipslots[0], egk_drum); @@ -833,7 +833,7 @@ static INLINE void OPL3_ChannelUpdateRhythm(opl3_chip *chip, Bit8u data) { OPL3_EnvelopeKeyOff(channel7->chipslots[0], egk_drum); } - //tc + /* tc */ if (chip->rhy & 0x02) { OPL3_EnvelopeKeyOn(channel8->chipslots[1], egk_drum); @@ -842,7 +842,7 @@ static INLINE void OPL3_ChannelUpdateRhythm(opl3_chip *chip, Bit8u data) { OPL3_EnvelopeKeyOff(channel8->chipslots[1], egk_drum); } - //tom + /* tom */ if (chip->rhy & 0x04) { OPL3_EnvelopeKeyOn(channel8->chipslots[0], egk_drum); @@ -851,7 +851,7 @@ static INLINE void OPL3_ChannelUpdateRhythm(opl3_chip *chip, Bit8u data) { OPL3_EnvelopeKeyOff(channel8->chipslots[0], egk_drum); } - //sd + /* sd */ if (chip->rhy & 0x08) { OPL3_EnvelopeKeyOn(channel7->chipslots[1], egk_drum); @@ -860,7 +860,7 @@ static INLINE void OPL3_ChannelUpdateRhythm(opl3_chip *chip, Bit8u data) { OPL3_EnvelopeKeyOff(channel7->chipslots[1], egk_drum); } - //bd + /* bd */ if (chip->rhy & 0x10) { OPL3_EnvelopeKeyOn(channel6->chipslots[0], egk_drum); @@ -1079,14 +1079,14 @@ static INLINE void OPL3_GenerateRhythm1(opl3_chip *chip) phase14 = (channel7->chipslots[0]->pg_phase >> 9) & 0x3ff; phase17 = (channel8->chipslots[1]->pg_phase >> 9) & 0x3ff; phase = 0x00; - //hh tc phase bit + /* hh tc phase bit */ phasebit = ((phase14 & 0x08) | (((phase14 >> 5) ^ phase14) & 0x04) | (((phase17 >> 2) ^ phase17) & 0x08)) ? 0x01 : 0x00; - //hh + /* hh */ phase = (phasebit << 9) | (0x34 << ((phasebit ^ (chip->noise & 0x01)) << 1)); OPL3_SlotGeneratePhase(channel7->chipslots[0], phase); - //tt + /* tt */ OPL3_SlotGenerateZM(channel8->chipslots[0]); } @@ -1107,13 +1107,13 @@ static INLINE void OPL3_GenerateRhythm2(opl3_chip *chip) phase14 = (channel7->chipslots[0]->pg_phase >> 9) & 0x3ff; phase17 = (channel8->chipslots[1]->pg_phase >> 9) & 0x3ff; phase = 0x00; - //hh tc phase bit + /* hh tc phase bit */ phasebit = ((phase14 & 0x08) | (((phase14 >> 5) ^ phase14) & 0x04) | (((phase17 >> 2) ^ phase17) & 0x08)) ? 0x01 : 0x00; - //sd + /* sd */ phase = (0x100 << ((phase14 >> 8) & 0x01)) ^ ((chip->noise & 0x01) << 8); OPL3_SlotGeneratePhase(channel7->chipslots[1], phase); - //tc + /* tc */ phase = 0x100 | (phasebit << 9); OPL3_SlotGeneratePhase(channel8->chipslots[1], phase); } diff --git a/src/nukedopl3.h b/src/nukedopl3.h index 415ba3e..0a686e2 100644 --- a/src/nukedopl3.h +++ b/src/nukedopl3.h @@ -1,28 +1,28 @@ -// -// Copyright (C) 2013-2016 Alexey Khokholov (Nuke.YKT) -// -// 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 2 -// of the License, or (at your option) 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. -// -// -// Nuked OPL3 emulator. -// Thanks: -// MAME Development Team(Jarek Burczynski, Tatsuyuki Satoh): -// Feedback and Rhythm part calculation information. -// forums.submarine.org.uk(carbon14, opl3): -// Tremolo and phase generator calculation information. -// OPLx decapsulated(Matthew Gambrell, Olli Niemitalo): -// OPL2 ROMs. -// -// version: 1.7.4 -// +/* + * Copyright (C) 2013-2016 Alexey Khokholov (Nuke.YKT) + * + * 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 2 + * of the License, or (at your option) 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. + * + * + * Nuked OPL3 emulator. + * Thanks: + * MAME Development Team(Jarek Burczynski, Tatsuyuki Satoh): + * Feedback and Rhythm part calculation information. + * forums.submarine.org.uk(carbon14, opl3): + * Tremolo and phase generator calculation information. + * OPLx decapsulated(Matthew Gambrell, Olli Niemitalo): + * OPL2 ROMs. + * + * version: 1.7.4 + */ #ifndef OPL_OPL3_H #define OPL_OPL3_H @@ -128,7 +128,7 @@ struct _opl3_chip Bit32u noise; Bit16s zeromod; Bit32s mixbuff[2]; - //OPL3L + /* OPL3L */ Bit32s rateratio; Bit32s samplecnt; Bit16s oldsamples[2]; -- cgit v1.2.3