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