xref: /aosp_15_r20/frameworks/native/libs/vibrator/ExternalVibrationUtils.cpp (revision 38e8c45f13ce32b0dcecb25141ffecaf386fa17f)
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