diff options
author | Wohlstand <admin@wohlnet.ru> | 2020-08-21 13:31:53 +0300 |
---|---|---|
committer | Wohlstand <admin@wohlnet.ru> | 2020-08-21 13:31:53 +0300 |
commit | 0f6e197d1f2243e1fb9415e1ea2209c39b972650 (patch) | |
tree | 5dc9e14cd51e914b200737815961e4b0d76079d9 /src | |
parent | b073a8029a46b646f6d593a56ffbe449dd582e39 (diff) | |
download | libADLMIDI-0f6e197d1f2243e1fb9415e1ea2209c39b972650.tar.gz libADLMIDI-0f6e197d1f2243e1fb9415e1ea2209c39b972650.tar.bz2 libADLMIDI-0f6e197d1f2243e1fb9415e1ea2209c39b972650.zip |
More accurate DMX volume model
Also, move entire code of volume model from MIDIPlay into OPL3
Diffstat (limited to 'src')
-rw-r--r-- | src/adlmidi_midiplay.cpp | 123 | ||||
-rw-r--r-- | src/adlmidi_midiplay.hpp | 3 | ||||
-rw-r--r-- | src/adlmidi_opl3.cpp | 201 | ||||
-rw-r--r-- | src/adlmidi_opl3.hpp | 10 |
4 files changed, 192 insertions, 145 deletions
diff --git a/src/adlmidi_midiplay.cpp b/src/adlmidi_midiplay.cpp index 61bdf95..6ed9b28 100644 --- a/src/adlmidi_midiplay.cpp +++ b/src/adlmidi_midiplay.cpp @@ -29,38 +29,6 @@ // Minimum life time of percussion notes static const double drum_note_min_time = 0.03; - -// Mapping from MIDI volume level to OPL level value. - -static const uint_fast32_t DMX_volume_mapping_table[128] = -{ - 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, -}; - -static const uint_fast32_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 @@ -110,7 +78,6 @@ void MIDIplay::AdlChannel::addAge(int64_t us) MIDIplay::MIDIplay(unsigned long sampleRate): m_cmfPercussionMode(false), - m_masterVolume(MasterVolumeDefault), m_sysExDeviceId(0), m_synthMode(Mode_XG), m_arpeggioCounter(0) @@ -219,7 +186,8 @@ void MIDIplay::partialReset() void MIDIplay::resetMIDI() { - m_masterVolume = MasterVolumeDefault; + Synth &synth = *m_synth; + synth.m_masterVolume = MasterVolumeDefault; m_sysExDeviceId = 0; m_synthMode = Mode_XG; m_arpeggioCounter = 0; @@ -294,7 +262,7 @@ void MIDIplay::realTime_ResetState() noteUpdateAll(uint16_t(ch), Upd_All); noteUpdateAll(uint16_t(ch), Upd_Off); } - m_masterVolume = MasterVolumeDefault; + synth.m_masterVolume = MasterVolumeDefault; } bool MIDIplay::realTime_NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) @@ -964,7 +932,8 @@ bool MIDIplay::doUniversalSysEx(unsigned dev, bool realtime, const uint8_t *data unsigned volume = (((unsigned)data[0] & 0x7F)) | (((unsigned)data[1] & 0x7F) << 7); - m_masterVolume = static_cast<uint8_t>(volume >> 7); + if(m_synth.get()) + m_synth->m_masterVolume = static_cast<uint8_t>(volume >> 7); for(size_t ch = 0; ch < m_midiChannels.size(); ch++) noteUpdateAll(uint16_t(ch), Upd_Volume); return true; @@ -1240,7 +1209,7 @@ void MIDIplay::noteUpdate(size_t midCh, synth.noteOff(c); if(props_mask & Upd_Mute) // Mute the note { - synth.touchNote(c, 0); + synth.touchNote(c, 0, 0, 0); m_chipChannels[c].koff_time_until_neglible_us = 0; } else @@ -1270,9 +1239,9 @@ void MIDIplay::noteUpdate(size_t midCh, if(props_mask & Upd_Volume) { - uint_fast32_t volume; - bool is_percussion = (midCh == 9) || m_midiChannels[midCh].is_xg_percussion; - uint_fast32_t brightness = is_percussion ? 127 : m_midiChannels[midCh].brightness; + const MIDIchannel &ch = m_midiChannels[midCh]; + bool is_percussion = (midCh == 9) || ch.is_xg_percussion; + uint_fast32_t brightness = is_percussion ? 127 : ch.brightness; if(!m_setup.fullRangeBrightnessCC74) { @@ -1283,63 +1252,11 @@ void MIDIplay::noteUpdate(size_t midCh, brightness *= 2; } - switch(synth.m_volumeScale) - { - default: - case Synth::VOLUME_Generic: - { - volume = vol * m_masterVolume * m_midiChannels[midCh].volume * m_midiChannels[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() )); - - // The formula below: SOLVE(V=127^4 * 2^( (A-63.49999) / 8), A) - volume = volume > (8725 * 127) ? static_cast<uint_fast32_t>(std::log(static_cast<double>(volume)) * 11.541560327111707 - 1.601379199767093e+02) : 0; - // The incorrect formula below: SOLVE(V=127^4 * (2^(A/63)-1), A) - //opl.Touch_Real(c, volume>(11210*127) ? 91.61112 * std::log((4.8819E-7/127)*volume + 1.0)+0.5 : 0); - } - break; - - case Synth::VOLUME_NATIVE: - { - volume = vol * m_midiChannels[midCh].volume * m_midiChannels[midCh].expression; - // volume = volume * m_masterVolume / (127 * 127 * 127) / 2; - volume = (volume * m_masterVolume) / 4096766; - } - break; - - case Synth::VOLUME_DMX: - { - volume = (m_midiChannels[midCh].volume * m_midiChannels[midCh].expression * m_masterVolume) / 16129; - volume = (DMX_volume_mapping_table[volume] + 1) << 1; - volume = (DMX_volume_mapping_table[(vol < 128) ? vol : 127] * volume) >> 9; - } - break; - - case Synth::VOLUME_APOGEE: - { - volume = (m_midiChannels[midCh].volume * m_midiChannels[midCh].expression * m_masterVolume / 16129); - volume = ((64 * (vol + 0x80)) * volume) >> 15; - //volume = ((63 * (vol + 0x80)) * Ch[MidCh].volume) >> 15; - } - break; - - case Synth::VOLUME_9X: - { - //volume = 63 - W9X_volume_mapping_table[(((vol * Ch[MidCh].volume /** Ch[MidCh].expression*/) * m_masterVolume / 16129 /*2048383*/) >> 2)]; - volume = 63 - W9X_volume_mapping_table[((vol * m_midiChannels[midCh].volume * m_midiChannels[midCh].expression * m_masterVolume / 2048383) >> 2)]; - //volume = W9X_volume_mapping_table[vol >> 2] + volume; - } - break; - } - - synth.touchNote(c, static_cast<uint8_t>(volume), static_cast<uint8_t>(brightness)); + synth.touchNote(c, + vol, + ch.volume, + ch.expression, + static_cast<uint8_t>(brightness)); /* DEBUG ONLY!!! static uint32_t max = 0; @@ -1994,15 +1911,7 @@ ADLMIDI_EXPORT void AdlInstrumentTester::Touch(unsigned c, unsigned volume) // V { #ifndef DISABLE_EMBEDDED_BANKS Synth *opl = P->opl; - if(opl->m_volumeScale == Synth::VOLUME_NATIVE) - opl->touchNote(c, static_cast<uint8_t>(volume * 127 / (127 * 127 * 127) / 2)); - else - { - // The formula below: SOLVE(V=127^3 * 2^( (A-63.49999) / 8), A) - opl->touchNote(c, static_cast<uint8_t>(volume > 8725 ? static_cast<unsigned int>(std::log((double)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); - } + opl->touchNote(c, volume, 127, 127); #else ADL_UNUSED(c); ADL_UNUSED(volume); @@ -2057,7 +1966,7 @@ ADLMIDI_EXPORT void AdlInstrumentTester::DoNote(int note) { if(adlchannel[c] < 0) continue; opl->setPatch(static_cast<size_t>(adlchannel[c]), ains.adl[c]); - opl->touchNote(static_cast<size_t>(adlchannel[c]), 63); + opl->touchNote(static_cast<size_t>(adlchannel[c]), 127, 127, 127); opl->setPan(static_cast<size_t>(adlchannel[c]), 0x30); opl->noteOn(static_cast<size_t>(adlchannel[c]), static_cast<size_t>(adlchannel[1]), hertz); } diff --git a/src/adlmidi_midiplay.hpp b/src/adlmidi_midiplay.hpp index 9cf35b1..f7732fe 100644 --- a/src/adlmidi_midiplay.hpp +++ b/src/adlmidi_midiplay.hpp @@ -538,9 +538,6 @@ public: //! CMF Rhythm mode bool m_cmfPercussionMode; - //! Master volume, controlled via SysEx - uint8_t m_masterVolume; - //! SysEx device ID uint8_t m_sysExDeviceId; diff --git a/src/adlmidi_opl3.cpp b/src/adlmidi_opl3.cpp index 7ba4a13..887dd09 100644 --- a/src/adlmidi_opl3.cpp +++ b/src/adlmidi_opl3.cpp @@ -191,6 +191,41 @@ static const uint16_t g_channelsMapPan[NUM_OF_CHANNELS] = Ports: ??? */ +// Mapping from MIDI volume level to OPL level value. + +static const uint_fast32_t DMX_volume_mapping_table[128] = +{ + 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, +}; + +static const uint_fast32_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 +}; + +enum +{ + MasterVolumeDefault = 127 +}; + enum { OPL_PANNING_LEFT = 0x10, @@ -198,6 +233,8 @@ enum OPL_PANNING_BOTH = 0x30 }; + + static adlinsdata2 makeEmptyInstrument() { adlinsdata2 ins; @@ -215,6 +252,7 @@ OPL3::OPL3() : m_deepVibratoMode(false), m_rhythmMode(false), m_softPanning(false), + m_masterVolume(MasterVolumeDefault), m_musicMode(MODE_MIDI), m_volumeScale(VOLUME_Generic) { @@ -418,11 +456,12 @@ void OPL3::noteOn(size_t c1, size_t c2, double hertz) // Hertz range: 0..131071 } } -void OPL3::touchNote(size_t c, uint8_t volume, uint8_t brightness) +void OPL3::touchNote(size_t c, + uint_fast32_t velocity, + uint_fast32_t channelVolume, + uint_fast32_t channelExpression, + uint8_t brightness) { - if(volume > 63) - volume = 63; - size_t chip = c / NUM_OF_CHANNELS, cc = c % NUM_OF_CHANNELS; const adldata &adli = m_insCache[c]; size_t cmf_offset = ((m_musicMode == MODE_CMF) && cc >= OPL3_CHANNELS_RHYTHM_BASE) ? 10 : 0; @@ -431,6 +470,94 @@ void OPL3::touchNote(size_t c, uint8_t volume, uint8_t brightness) uint8_t x = adli.modulator_40, y = adli.carrier_40; uint32_t mode = 1; // 2-op AM + uint_fast32_t kslMod = x & 0xC0; + uint_fast32_t kslCar = y & 0xC0; + uint_fast32_t tlMod = x & 63; + uint_fast32_t tlCar = y & 63; + + uint_fast32_t modulator; + uint_fast32_t carrier; + + uint_fast32_t volume; + + bool do_modulator; + bool do_carrier; + + 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 */ + }; + + + switch(m_volumeScale) + { + default: + case Synth::VOLUME_Generic: + { + volume = velocity * m_masterVolume * channelVolume * channelExpression; + + /* 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() )); + + // The formula below: SOLVE(V=127^4 * 2^( (A-63.49999) / 8), A) + volume = volume > (8725 * 127) ? static_cast<uint_fast32_t>(std::log(static_cast<double>(volume)) * 11.541560327111707 - 1.601379199767093e+02) : 0; + // The incorrect formula below: SOLVE(V=127^4 * (2^(A/63)-1), A) + //opl.Touch_Real(c, volume>(11210*127) ? 91.61112 * std::log((4.8819E-7/127)*volume + 1.0)+0.5 : 0); + } + break; + + case Synth::VOLUME_NATIVE: + { + volume = velocity * channelVolume * channelExpression; + // volume = volume * m_masterVolume / (127 * 127 * 127) / 2; + volume = (volume * m_masterVolume) / 4096766; + } + break; + + case Synth::VOLUME_DMX: + { + volume = (channelVolume * channelExpression * m_masterVolume) / 16129; + volume = (DMX_volume_mapping_table[volume] + 1) << 1; + volume = (DMX_volume_mapping_table[(velocity < 128) ? velocity : 127] * volume) >> 9; + } + break; + + case Synth::VOLUME_APOGEE: + { + volume = (channelVolume * channelExpression * m_masterVolume / 16129); + volume = ((64 * (velocity + 0x80)) * volume) >> 15; + //volume = ((63 * (velocity + 0x80)) * Ch[MidCh].volume) >> 15; + } + break; + + case Synth::VOLUME_9X: + { + //volume = 63 - W9X_volume_mapping_table[(((velocity * Ch[MidCh].volume /** Ch[MidCh].expression*/) * m_masterVolume / 16129 /*2048383*/) >> 2)]; + volume = 63 - W9X_volume_mapping_table[((velocity * channelVolume * channelExpression * m_masterVolume / 2048383) >> 2)]; + //volume = W9X_volume_mapping_table[vol >> 2] + volume; + } + break; + } + + if(volume > 63) + volume = 63; + + if(m_channelCategory[c] == ChanCat_Regular || m_channelCategory[c] == ChanCat_Rhythm_Bass) { @@ -457,50 +584,58 @@ void OPL3::touchNote(size_t c, uint8_t volume, uint8_t brightness) mode += (i0->feedconn & 1) + (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 */ - }; if(m_musicMode == MODE_RSXX) { - if(o1 != 0xFFF) - writeRegI(chip, 0x40 + o1, x); - if(o2 != 0xFFF) - writeRegI(chip, 0x40 + o2, y - volume / 2); + tlCar -= volume / 2; + } + else if(m_volumeScale == Synth::VOLUME_DMX && mode <= 1) + { + do_modulator = do_ops[mode][ 0 ] || m_scaleModulators; + + tlCar = (63 - volume); + + if(do_modulator) + { + if(tlMod < tlCar) + tlMod = tlCar; + } + + if(brightness != 127) + { + brightness = static_cast<uint8_t>(::round(127.0 * ::sqrt((static_cast<double>(brightness)) * (1.0 / 127.0))) / 2.0); + if(!do_modulator) + tlMod = 63 - brightness + (brightness * tlMod) / 63; + } } else { - bool do_modulator = do_ops[ mode ][ 0 ] || m_scaleModulators; - bool do_carrier = do_ops[ mode ][ 1 ] || m_scaleModulators; + do_modulator = do_ops[ mode ][ 0 ] || m_scaleModulators; + do_carrier = do_ops[ mode ][ 1 ] || m_scaleModulators; - uint32_t modulator = do_modulator ? (x | 63) - volume + volume * (x & 63) / 63 : x; - uint32_t carrier = do_carrier ? (y | 63) - volume + volume * (y & 63) / 63 : y; + if(do_modulator) + tlMod = 63 - volume + (volume * tlMod) / 63; + if(do_carrier) + tlCar = 63 - volume + (volume * tlCar) / 63; if(brightness != 127) { brightness = static_cast<uint8_t>(::round(127.0 * ::sqrt((static_cast<double>(brightness)) * (1.0 / 127.0))) / 2.0); if(!do_modulator) - modulator = (modulator | 63) - brightness + brightness * (modulator & 63) / 63; + tlMod = 63 - brightness + (brightness * tlMod) / 63; if(!do_carrier) - carrier = (carrier | 63) - brightness + brightness * (carrier & 63) / 63; + tlCar = 63 - brightness + (brightness * tlCar) / 63; } - - if(o1 != 0xFFF) - writeRegI(chip, 0x40 + o1, modulator); - if(o2 != 0xFFF) - writeRegI(chip, 0x40 + o2, carrier); } + modulator = (kslMod & 0xC0) | (tlMod & 63); + carrier = (kslCar & 0xC0) | (tlCar & 63); + + if(o1 != 0xFFF) + writeRegI(chip, 0x40 + o1, modulator); + if(o2 != 0xFFF) + writeRegI(chip, 0x40 + o2, carrier); + // Correct formula (ST3, AdPlug): // 63-((63-(instrvol))/63)*chanvol // Reduces to (tested identical): @@ -572,7 +707,7 @@ void OPL3::silenceAll() // Silence all OPL channels. for(size_t c = 0; c < m_numChannels; ++c) { noteOff(c); - touchNote(c, 0); + touchNote(c, 0, 0, 0); } } diff --git a/src/adlmidi_opl3.hpp b/src/adlmidi_opl3.hpp index b97b5d3..a12069e 100644 --- a/src/adlmidi_opl3.hpp +++ b/src/adlmidi_opl3.hpp @@ -110,6 +110,8 @@ public: bool m_runAtPcmRate; //! Enable soft panning bool m_softPanning; + //! Master volume, controlled via SysEx (0...127) + uint8_t m_masterVolume; //! Just a padding. Reserved. char _padding2[3]; @@ -249,10 +251,14 @@ public: /** * @brief Change setup of instrument in specified chip channel * @param c Channel of chip (Emulated chip choosing by next formula: [c = ch + (chipId * 23)]) - * @param volume Volume level (from 0 to 63) + * @param volume Volume level (from 0 to 127) * @param brightness CC74 Brightness level (from 0 to 127) */ - void touchNote(size_t c, uint8_t volume, uint8_t brightness = 127); + void touchNote(size_t c, + uint_fast32_t velocity, + uint_fast32_t channelVolume = 127, + uint_fast32_t channelExpression = 127, + uint8_t brightness = 127); /** * @brief Set the instrument into specified chip channel |