1 /*
2 * Copyright (C) 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 #define LOG_TAG "ExternalVibrationUtils"
17
18 #include <cstring>
19
20 #include <android_os_vibrator.h>
21
22 #include <algorithm>
23 #include <math.h>
24
25 #include <log/log.h>
26 #include <vibrator/ExternalVibrationUtils.h>
27
28 namespace android::os {
29
30 namespace {
31 static constexpr float HAPTIC_SCALE_VERY_LOW_RATIO = 2.0f / 3.0f;
32 static constexpr float HAPTIC_SCALE_LOW_RATIO = 3.0f / 4.0f;
33 static constexpr float HAPTIC_MAX_AMPLITUDE_FLOAT = 1.0f;
34 static constexpr float SCALE_GAMMA = 0.65f; // Same as VibrationEffect.SCALE_GAMMA
35 static constexpr float SCALE_LEVEL_GAIN = 1.4f; // Same as VibrationConfig.DEFAULT_SCALE_LEVEL_GAIN
36
getOldHapticScaleGamma(HapticLevel level)37 float getOldHapticScaleGamma(HapticLevel level) {
38 switch (level) {
39 case HapticLevel::VERY_LOW:
40 return 2.0f;
41 case HapticLevel::LOW:
42 return 1.5f;
43 case HapticLevel::HIGH:
44 return 0.5f;
45 case HapticLevel::VERY_HIGH:
46 return 0.25f;
47 default:
48 return 1.0f;
49 }
50 }
51
getOldHapticMaxAmplitudeRatio(HapticLevel level)52 float getOldHapticMaxAmplitudeRatio(HapticLevel level) {
53 switch (level) {
54 case HapticLevel::VERY_LOW:
55 return HAPTIC_SCALE_VERY_LOW_RATIO;
56 case HapticLevel::LOW:
57 return HAPTIC_SCALE_LOW_RATIO;
58 case HapticLevel::NONE:
59 case HapticLevel::HIGH:
60 case HapticLevel::VERY_HIGH:
61 return 1.0f;
62 default:
63 return 0.0f;
64 }
65 }
66
67 /* Same as VibrationScaler.getScaleFactor */
getHapticScaleFactor(HapticScale scale)68 float getHapticScaleFactor(HapticScale scale) {
69 if (android_os_vibrator_haptics_scale_v2_enabled()) {
70 if (scale.getScaleFactor() >= 0) {
71 // ExternalVibratorService provided the scale factor, use it.
72 return scale.getScaleFactor();
73 }
74
75 HapticLevel level = scale.getLevel();
76 switch (level) {
77 case HapticLevel::MUTE:
78 return 0.0f;
79 case HapticLevel::NONE:
80 return 1.0f;
81 default:
82 float scaleFactor = powf(SCALE_LEVEL_GAIN, static_cast<int32_t>(level));
83 if (scaleFactor <= 0) {
84 ALOGE("Invalid scale factor %.2f for level %d, using fallback to 1.0",
85 scaleFactor, static_cast<int32_t>(level));
86 scaleFactor = 1.0f;
87 }
88 return scaleFactor;
89 }
90 }
91 // Same as VibrationScaler.SCALE_FACTOR_*
92 switch (scale.getLevel()) {
93 case HapticLevel::MUTE:
94 return 0.0f;
95 case HapticLevel::VERY_LOW:
96 return 0.6f;
97 case HapticLevel::LOW:
98 return 0.8f;
99 case HapticLevel::HIGH:
100 return 1.2f;
101 case HapticLevel::VERY_HIGH:
102 return 1.4f;
103 default:
104 return 1.0f;
105 }
106 }
107
applyOldHapticScale(float value,float gamma,float maxAmplitudeRatio)108 float applyOldHapticScale(float value, float gamma, float maxAmplitudeRatio) {
109 float sign = value >= 0 ? 1.0 : -1.0;
110 return powf(fabsf(value / HAPTIC_MAX_AMPLITUDE_FLOAT), gamma)
111 * maxAmplitudeRatio * HAPTIC_MAX_AMPLITUDE_FLOAT * sign;
112 }
113
applyNewHapticScale(float value,float scaleFactor)114 float applyNewHapticScale(float value, float scaleFactor) {
115 if (android_os_vibrator_haptics_scale_v2_enabled()) {
116 if (scaleFactor <= 1 || value == 0) {
117 return value * scaleFactor;
118 } else {
119 // Using S * x / (1 + (S - 1) * x^2) as the scale up function to converge to 1.0.
120 return (value * scaleFactor) / (1 + (scaleFactor - 1) * value * value);
121 }
122 }
123 float scale = powf(scaleFactor, 1.0f / SCALE_GAMMA);
124 if (scaleFactor <= 1) {
125 // Scale down is simply a gamma corrected application of scaleFactor to the intensity.
126 // Scale up requires a different curve to ensure the intensity will not become > 1.
127 return value * scale;
128 }
129
130 float sign = value >= 0 ? 1.0f : -1.0f;
131 float extraScale = powf(scaleFactor, 4.0f - scaleFactor);
132 float x = fabsf(value) * scale * extraScale;
133 float maxX = scale * extraScale; // scaled x for intensity == 1
134
135 float expX = expf(x);
136 float expMaxX = expf(maxX);
137
138 // Using f = tanh as the scale up function so the max value will converge.
139 // a = 1/f(maxX), used to scale f so that a*f(maxX) = 1 (the value will converge to 1).
140 float a = (expMaxX + 1.0f) / (expMaxX - 1.0f);
141 float fx = (expX - 1.0f) / (expX + 1.0f);
142
143 return sign * std::clamp(a * fx, 0.0f, 1.0f);
144 }
145
applyHapticScale(float * buffer,size_t length,HapticScale scale)146 void applyHapticScale(float* buffer, size_t length, HapticScale scale) {
147 if (scale.isScaleMute()) {
148 memset(buffer, 0, length * sizeof(float));
149 return;
150 }
151 if (scale.isScaleNone()) {
152 return;
153 }
154 HapticLevel hapticLevel = scale.getLevel();
155 float scaleFactor = getHapticScaleFactor(scale);
156 float adaptiveScaleFactor = scale.getAdaptiveScaleFactor();
157 float oldGamma = getOldHapticScaleGamma(hapticLevel);
158 float oldMaxAmplitudeRatio = getOldHapticMaxAmplitudeRatio(hapticLevel);
159
160 for (size_t i = 0; i < length; i++) {
161 if (hapticLevel != HapticLevel::NONE) {
162 if (android_os_vibrator_fix_audio_coupled_haptics_scaling() ||
163 android_os_vibrator_haptics_scale_v2_enabled()) {
164 buffer[i] = applyNewHapticScale(buffer[i], scaleFactor);
165 } else {
166 buffer[i] = applyOldHapticScale(buffer[i], oldGamma, oldMaxAmplitudeRatio);
167 }
168 }
169
170 if (adaptiveScaleFactor >= 0 && adaptiveScaleFactor != 1.0f) {
171 buffer[i] *= adaptiveScaleFactor;
172 }
173 }
174 }
175
clipHapticData(float * buffer,size_t length,float limit)176 void clipHapticData(float* buffer, size_t length, float limit) {
177 if (isnan(limit) || limit == 0) {
178 return;
179 }
180 limit = fabsf(limit);
181 for (size_t i = 0; i < length; i++) {
182 float sign = buffer[i] >= 0 ? 1.0 : -1.0;
183 if (fabsf(buffer[i]) > limit) {
184 buffer[i] = limit * sign;
185 }
186 }
187 }
188
189 } // namespace
190
isValidHapticScale(HapticScale scale)191 bool isValidHapticScale(HapticScale scale) {
192 switch (scale.getLevel()) {
193 case HapticLevel::MUTE:
194 case HapticLevel::VERY_LOW:
195 case HapticLevel::LOW:
196 case HapticLevel::NONE:
197 case HapticLevel::HIGH:
198 case HapticLevel::VERY_HIGH:
199 return true;
200 }
201 return false;
202 }
203
scaleHapticData(float * buffer,size_t length,HapticScale scale,float limit)204 void scaleHapticData(float* buffer, size_t length, HapticScale scale, float limit) {
205 if (isValidHapticScale(scale)) {
206 applyHapticScale(buffer, length, scale);
207 }
208 clipHapticData(buffer, length, limit);
209 }
210
211 } // namespace android::os
212