aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorWohlstand <admin@wohlnet.ru>2020-08-21 13:31:53 +0300
committerWohlstand <admin@wohlnet.ru>2020-08-21 13:31:53 +0300
commit0f6e197d1f2243e1fb9415e1ea2209c39b972650 (patch)
tree5dc9e14cd51e914b200737815961e4b0d76079d9 /src
parentb073a8029a46b646f6d593a56ffbe449dd582e39 (diff)
downloadlibADLMIDI-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.cpp123
-rw-r--r--src/adlmidi_midiplay.hpp3
-rw-r--r--src/adlmidi_opl3.cpp201
-rw-r--r--src/adlmidi_opl3.hpp10
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