Files
scummvm-cursorfix/engines/zvision/sound/volume_manager.cpp
2026-02-02 04:50:13 +01:00

292 lines
12 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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
* (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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/debug.h"
#include "zvision/detection.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/sound/volume_manager.h"
namespace ZVision {
// Power law with exponent 1.5.
static constexpr uint8 powerLaw[256] = {
0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4,
4, 4, 5, 5, 6, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11,
11, 12, 12, 13, 14, 14, 15, 15, 16, 16, 17, 18, 18, 19, 20, 20,
21, 21, 22, 23, 23, 24, 25, 26, 26, 27, 28, 28, 29, 30, 31, 31,
32, 33, 34, 34, 35, 36, 37, 37, 38, 39, 40, 41, 41, 42, 43, 44,
45, 46, 46, 47, 48, 49, 50, 51, 52, 53, 53, 54, 55, 56, 57, 58,
59, 60, 61, 62, 63, 64, 65, 65, 66, 67, 68, 69, 70, 71, 72, 73,
74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 88, 89, 90,
91, 92, 93, 94, 95, 96, 97, 98, 99,100,102,103,104,105,106,107,
108,109,110,112,113,114,115,116,117,119,120,121,122,123,124,126,
127,128,129,130,132,133,134,135,136,138,139,140,141,142,144,145,
146,147,149,150,151,152,154,155,156,158,159,160,161,163,164,165,
167,168,169,171,172,173,174,176,177,178,180,181,182,184,185,187,
188,189,191,192,193,195,196,197,199,200,202,203,204,206,207,209,
210,211,213,214,216,217,218,220,221,223,224,226,227,228,230,231,
233,234,236,237,239,240,242,243,245,246,248,249,251,252,254,255
};
static constexpr uint8 logPower[256] = {
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,
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,
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, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3,
3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8,
8, 9, 9, 10, 10, 11, 11, 12, 13, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24, 26, 27, 29, 30, 32, 34, 36, 38, 40, 42, 45,
47, 50, 52, 55, 58, 62, 65, 69, 73, 77, 81, 86, 90, 96,101,107,
113,119,126,133,140,148,156,165,174,184,194,205,217,229,241,255
};
// */
static constexpr uint8 logAmplitude[256] = {
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, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5,
5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 8, 8,
8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 12, 12,
2, 13, 13, 13, 14, 14, 15, 15, 15, 16, 16, 17, 17, 18, 18, 19,
19, 20, 20, 21, 21, 22, 23, 23, 24, 24, 25, 26, 27, 27, 28, 29,
30, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 45,
46, 47, 48, 50, 51, 52, 54, 55, 57, 58, 60, 62, 63, 65, 67, 69,
71, 73, 75, 77, 79, 81, 83, 86, 88, 90, 93, 96, 98,101,104,107,
110,113,116,119,122,126,129,133,136,140,144,148,152,156,160,165,
169,174,179,184,189,194,200,205,211,217,222,229,235,241,248,255
};
/*/
// Old system; this is wrong, caused bug #7176; cloister fountain (value 50 in-game, 127/255) inaudible
// Using linear volume served as a temporary fix, but causes other sounds not to play at correct amplitudes (e.g. beehive, door singing in ZGI)
static constexpr uint8 logAmplitude[256] = {
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,
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,
0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4,
4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7,
8, 8, 8, 9, 9, 9, 10, 10, 11, 11, 11, 12, 12, 13, 13, 14,
14, 15, 15, 16, 16, 17, 18, 18, 19, 20, 21, 21, 22, 23, 24, 25,
26, 27, 28, 29, 30, 31, 32, 33, 34, 36, 37, 38, 40, 41, 43, 45,
46, 48, 50, 52, 53, 55, 57, 60, 62, 64, 67, 69, 72, 74, 77, 80,
83, 86, 89, 92, 96, 99,103,107,111,115,119,123,128,133,137,143,
148,153,159,165,171,177,184,191,198,205,212,220,228,237,245,255
};
// */
/*
Estimated relative amplitude of a point sound source as it circles the listener's head from front to rear, due to ear pinna shape.
Maximum attenuation -5dB when fully to rear. Seems to give a reasonably realistic effect when tested on the Nemesis cloister fountain.
Should be applied AFTER volume profile is applied to script files.
Generating function:
for 0 < theta < 90, amp = 255;
for 90 < theta < 180, amp = 255*10^(1-(cos(2*(theta-90))/4))
where theta is the azimuth, in degrees, of the sound source relative to straight ahead of listener
Source: Own work; crude and naive model that is probably not remotely scientifically accurate, but good enough for a 30-year-old game.
*/
static constexpr uint8 directionalAmplitude[181] = {
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,254,254,253,
252,251,249,248,246,245,243,241,238,236,234,231,228,226,223,220,
217,214,211,208,204,201,198,195,191,188,185,181,178,175,171,168,
165,162,158,155,152,149,146,143,141,138,135,132,130,127,125,122,
120,118,116,113,111,109,108,106,104,102,101, 99, 98, 96, 95, 93,
92, 91, 90, 89, 88, 87, 86, 85, 85, 84, 83, 83, 82, 82, 82, 81,
81, 81, 81, 81, 81
};
VolumeManager::VolumeManager(ZVision *engine, volumeScaling mode) :
_mode(mode) {
}
uint8 VolumeManager::convert(uint8 inputValue) {
return convert(inputValue, _mode);
}
uint8 VolumeManager::convert(uint8 inputValue, Math::Angle azimuth, uint8 directionality) {
return convert(inputValue, _mode, azimuth, directionality);
}
uint8 VolumeManager::convert(uint8 inputValue, volumeScaling &mode, Math::Angle azimuth, uint8 directionality) {
uint8 index = abs(round(azimuth.getDegrees(-180)));
uint32 output = convert(inputValue, mode);
uint32 directionalOutput = (output * directionalAmplitude[index]) * directionality;
directionalOutput /= 0xFF;
output *= (0xFF - directionality);
output = (output + directionalOutput) / 0xFF;
debugC(4, kDebugSound, "Directionally converted output %d", output);
return output;
}
uint8 VolumeManager::convert(uint8 inputValue, volumeScaling &mode) {
if (inputValue > _scriptScale)
inputValue = _scriptScale;
uint32 scaledInput = inputValue * 0xFF;
scaledInput /= _scriptScale;
uint8 output = 0;
switch (mode) {
case kVolumeLogPower:
output = logPower[scaledInput];
break;
case kVolumeLogAmplitude:
output = logAmplitude[scaledInput];
break;
case kVolumePowerLaw:
output = powerLaw[scaledInput];
break;
case kVolumeParabolic:
scaledInput *= scaledInput;
output = scaledInput / 0xFF;
break;
case kVolumeCubic:
scaledInput *= scaledInput * scaledInput;
output = scaledInput / 0xFE01;
break;
case kVolumeQuartic:
scaledInput *= scaledInput;
scaledInput *= scaledInput;
output = scaledInput / 0xFD02FF;
break;
case kVolumeLinear:
default:
output = scaledInput;
break;
}
debugC(4, kDebugSound, "Scripted volume %d, scaled volume %d, converted output %d", inputValue, scaledInput, output);
return output;
}
#if defined(USE_MPEG2) && defined(USE_A52)
double VolumeManager::getVobAmplification(Common::String fileName) const {
// For some reason, we get much lower volume in the hi-res videos than
// in the low-res ones. So we artificially boost the volume. This is an
// approximation, but I've tried to match the old volumes reasonably
// well.
//
// Some of these will cause audio clipping. Hopefully not enough to be
// noticeable.
double amplification = 0.0;
if (fileName == "em00d011.vob") {
// The finale.
amplification = 10.0;
} else if (fileName == "em00d021.vob") {
// Jack's escape and arrival at Flathead Mesa.
amplification = 9.0;
} else if (fileName == "em00d032.vob") {
// The Grand Inquisitor's speech.
amplification = 11.0;
} else if (fileName == "em00d122.vob") {
// Jack orders you to the radio tower.
amplification = 17.0;
} else if (fileName == "em3ed012.vob") {
// The Grand Inquisitor gets the Coconut of Quendor.
amplification = 12.0;
} else if (fileName == "g000d101.vob") {
// Griff gets captured.
amplification = 11.0;
} else if (fileName == "g000d111.vob") {
// Brog gets totemized. The music seems to be mixed much softer
// in this than in the low-resolution version.
amplification = 12.0;
} else if (fileName == "g000d122.vob") {
// Lucy gets captured.
amplification = 14.0;
} else if (fileName == "g000d302.vob") {
// The Grand Inquisitor visits Jack in his cell.
amplification = 13.0;
} else if (fileName == "g000d312.vob") {
// You get captured.
amplification = 14.0;
} else if (fileName == "g000d411.vob") {
// Propaganda On Parade. No need to make it as loud as the
// low-resolution version.
amplification = 11.0;
} else if (fileName == "pe1ed012.vob") {
// Jack lets you in with the lantern.
amplification = 14.0;
} else if (fileName.hasPrefix("pe1ed")) {
// Jack answers the door. Several different ways.
amplification = 17.0;
} else if (fileName == "pe5ed052.vob") {
// You get killed by the guards
amplification = 12.0;
} else if (fileName == "pe6ed012.vob") {
// Jack gets captured by the guards
amplification = 17.0;
} else if (fileName == "pp1ed022.vob") {
// Jack examines the lantern
amplification = 10.0;
} else if (fileName == "qb1ed012.vob") {
// Lucy gets invited to the back room
amplification = 17.0;
} else if (fileName.hasPrefix("qe1ed")) {
// Floyd answers the door. Several different ways.
amplification = 17.0;
} else if (fileName == "qs1ed011.vob") {
// Jack explains the rules of the game.
amplification = 16.0;
} else if (fileName == "qs1ed021.vob") {
// Jack loses the game.
amplification = 14.0;
} else if (fileName == "uc1gd012.vob") {
// Y'Gael appears.
amplification = 12.0;
} else if (fileName == "ue1ud012.vob") {
// Jack gets totemized... or what?
amplification = 12.0;
} else if (fileName == "ue2qd012.vob") {
// Jack agrees to totemization.
amplification = 10.0;
} else if (fileName == "g000d981.vob") {
// The Enterprise logo. Has no low-res version. Its volume is
// louder than the other logo animations.
amplification = 6.2;
} else if (fileName.hasPrefix("g000d")) {
// The Dolby Digital and Activision logos. They have no low-res
// versions, but I've used the low-resolution Activision logo
// (slightly different) as reference.
amplification = 8.5;
}
return amplification;
}
#endif
} // End of namespace ZVision