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