blob: ca13afcbaf35806f1173fc748ca582667d413871 [file] [log] [blame]
jiabin06871bb2020-06-30 21:27:52 -07001/*
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 */
Lais Andrade045376e2024-07-24 15:35:07 +010016#define LOG_TAG "ExternalVibrationUtils"
17
jiabin06871bb2020-06-30 21:27:52 -070018#include <cstring>
19
Lais Andradefd0e14c2024-07-30 15:57:23 +010020#include <android_os_vibrator.h>
21
22#include <algorithm>
jiabin06871bb2020-06-30 21:27:52 -070023#include <math.h>
24
Lais Andrade045376e2024-07-24 15:35:07 +010025#include <log/log.h>
jiabin06871bb2020-06-30 21:27:52 -070026#include <vibrator/ExternalVibrationUtils.h>
27
28namespace android::os {
29
30namespace {
31static constexpr float HAPTIC_SCALE_VERY_LOW_RATIO = 2.0f / 3.0f;
32static constexpr float HAPTIC_SCALE_LOW_RATIO = 3.0f / 4.0f;
33static constexpr float HAPTIC_MAX_AMPLITUDE_FLOAT = 1.0f;
Lais Andradefd0e14c2024-07-30 15:57:23 +010034static constexpr float SCALE_GAMMA = 0.65f; // Same as VibrationEffect.SCALE_GAMMA
Lais Andrade045376e2024-07-24 15:35:07 +010035static constexpr float SCALE_LEVEL_GAIN = 1.4f; // Same as VibrationConfig.DEFAULT_SCALE_LEVEL_GAIN
jiabin06871bb2020-06-30 21:27:52 -070036
Lais Andradefd0e14c2024-07-30 15:57:23 +010037float getOldHapticScaleGamma(HapticLevel level) {
Ahmad Khalil2cd80352024-02-05 12:15:28 +000038 switch (level) {
39 case HapticLevel::VERY_LOW:
jiabin06871bb2020-06-30 21:27:52 -070040 return 2.0f;
Ahmad Khalil2cd80352024-02-05 12:15:28 +000041 case HapticLevel::LOW:
jiabin06871bb2020-06-30 21:27:52 -070042 return 1.5f;
Ahmad Khalil2cd80352024-02-05 12:15:28 +000043 case HapticLevel::HIGH:
jiabin06871bb2020-06-30 21:27:52 -070044 return 0.5f;
Ahmad Khalil2cd80352024-02-05 12:15:28 +000045 case HapticLevel::VERY_HIGH:
jiabin06871bb2020-06-30 21:27:52 -070046 return 0.25f;
47 default:
48 return 1.0f;
49 }
50}
51
Lais Andradefd0e14c2024-07-30 15:57:23 +010052float getOldHapticMaxAmplitudeRatio(HapticLevel level) {
Ahmad Khalil2cd80352024-02-05 12:15:28 +000053 switch (level) {
54 case HapticLevel::VERY_LOW:
jiabin06871bb2020-06-30 21:27:52 -070055 return HAPTIC_SCALE_VERY_LOW_RATIO;
Ahmad Khalil2cd80352024-02-05 12:15:28 +000056 case HapticLevel::LOW:
jiabin06871bb2020-06-30 21:27:52 -070057 return HAPTIC_SCALE_LOW_RATIO;
Ahmad Khalil2cd80352024-02-05 12:15:28 +000058 case HapticLevel::NONE:
59 case HapticLevel::HIGH:
60 case HapticLevel::VERY_HIGH:
jiabin06871bb2020-06-30 21:27:52 -070061 return 1.0f;
62 default:
63 return 0.0f;
64 }
65}
66
Lais Andrade045376e2024-07-24 15:35:07 +010067/* Same as VibrationScaler.getScaleFactor */
68float 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;
Lais Andradefd0e14c2024-07-30 15:57:23 +010095 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
108float 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
114float applyNewHapticScale(float value, float scaleFactor) {
Lais Andrade045376e2024-07-24 15:35:07 +0100115 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 }
Lais Andradefd0e14c2024-07-30 15:57:23 +0100123 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
Lais Andraded9451112021-07-02 00:15:33 +0100146void applyHapticScale(float* buffer, size_t length, HapticScale scale) {
Ahmad Khalil2cd80352024-02-05 12:15:28 +0000147 if (scale.isScaleMute()) {
Lais Andraded9451112021-07-02 00:15:33 +0100148 memset(buffer, 0, length * sizeof(float));
149 return;
150 }
Ahmad Khalil2cd80352024-02-05 12:15:28 +0000151 if (scale.isScaleNone()) {
Lais Andraded9451112021-07-02 00:15:33 +0100152 return;
153 }
Ahmad Khalil2cd80352024-02-05 12:15:28 +0000154 HapticLevel hapticLevel = scale.getLevel();
Lais Andrade045376e2024-07-24 15:35:07 +0100155 float scaleFactor = getHapticScaleFactor(scale);
Ahmad Khalil2cd80352024-02-05 12:15:28 +0000156 float adaptiveScaleFactor = scale.getAdaptiveScaleFactor();
Lais Andradefd0e14c2024-07-30 15:57:23 +0100157 float oldGamma = getOldHapticScaleGamma(hapticLevel);
158 float oldMaxAmplitudeRatio = getOldHapticMaxAmplitudeRatio(hapticLevel);
Ahmad Khalil2cd80352024-02-05 12:15:28 +0000159
Lais Andraded9451112021-07-02 00:15:33 +0100160 for (size_t i = 0; i < length; i++) {
Ahmad Khalil2cd80352024-02-05 12:15:28 +0000161 if (hapticLevel != HapticLevel::NONE) {
Lais Andrade045376e2024-07-24 15:35:07 +0100162 if (android_os_vibrator_fix_audio_coupled_haptics_scaling() ||
163 android_os_vibrator_haptics_scale_v2_enabled()) {
Lais Andradefd0e14c2024-07-30 15:57:23 +0100164 buffer[i] = applyNewHapticScale(buffer[i], scaleFactor);
165 } else {
166 buffer[i] = applyOldHapticScale(buffer[i], oldGamma, oldMaxAmplitudeRatio);
167 }
Ahmad Khalil2cd80352024-02-05 12:15:28 +0000168 }
169
Lais Andradeee09df52024-08-09 17:53:14 +0100170 if (adaptiveScaleFactor >= 0 && adaptiveScaleFactor != 1.0f) {
Ahmad Khalil2cd80352024-02-05 12:15:28 +0000171 buffer[i] *= adaptiveScaleFactor;
172 }
Lais Andraded9451112021-07-02 00:15:33 +0100173 }
174}
175
176void 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
jiabin06871bb2020-06-30 21:27:52 -0700189} // namespace
190
191bool isValidHapticScale(HapticScale scale) {
Ahmad Khalil2cd80352024-02-05 12:15:28 +0000192 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:
jiabin06871bb2020-06-30 21:27:52 -0700199 return true;
200 }
201 return false;
202}
203
Lais Andraded9451112021-07-02 00:15:33 +0100204void scaleHapticData(float* buffer, size_t length, HapticScale scale, float limit) {
205 if (isValidHapticScale(scale)) {
206 applyHapticScale(buffer, length, scale);
jiabin06871bb2020-06-30 21:27:52 -0700207 }
Lais Andraded9451112021-07-02 00:15:33 +0100208 clipHapticData(buffer, length, limit);
jiabin06871bb2020-06-30 21:27:52 -0700209}
210
211} // namespace android::os