1 /* 2 * Copyright (C) 2019 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 17 #ifndef ANDROID_AUDIO_UTILS_BALANCE_H 18 #define ANDROID_AUDIO_UTILS_BALANCE_H 19 20 #include <math.h> /* expf */ 21 #include <sstream> 22 #include <system/audio.h> 23 #include <vector> 24 25 namespace android::audio_utils { 26 27 class Balance { 28 public: 29 /** 30 * \brief Balance processing of left-right volume on audio data. 31 * 32 * Allows processing of audio data with a single balance parameter from [-1, 1]. 33 * For efficiency, the class caches balance and channel mask data between calls; 34 * hence, use by multiple threads will require caller locking. 35 * 36 * \param ramp whether to ramp volume or not. 37 * \param curve a monotonic increasing function f: [0, 1] -> [a, b] 38 * which represents the volume steps from an input domain of [0, 1] to 39 * an output range [a, b] (ostensibly also from 0 to 1). 40 * If [a, b] is not [0, 1], it is normalized to [0, 1]. 41 * Curve is typically a convex function, some possible examples: 42 * [](float x) { return expf(2.f * x); } 43 * or 44 * [](float x) { return x * (x + 0.2f); } 45 */ 46 explicit Balance( 47 bool ramp = true, 48 std::function<float(float)> curve = [](float x) { return x * (x + 0.2f); }) mRamp(ramp)49 : mRamp(ramp) 50 , mCurve(normalize(std::move(curve))) { } 51 52 /** 53 * \brief Sets whether the process ramps left-right volume changes. 54 * 55 * The default value is true. 56 * The ramp will take place, if needed, on the following process() 57 * using the current balance and volume as the starting point. 58 * 59 * Toggling ramp off and then back on will reset the ramp starting point. 60 * 61 * \param ramp whether ramping is used to smooth volume changes. 62 */ setRamp(bool ramp)63 void setRamp(bool ramp) { 64 if (ramp == mRamp) return; // no change 65 mRamp = ramp; 66 if (mRamp) { // use current volume and balance as starting point. 67 mRampVolumes = mVolumes; 68 mRampBalance = mBalance; 69 } 70 } 71 72 /** 73 * \brief Sets the channel mask for data passed in. 74 * 75 * setChannelMask() must called before process() to set 76 * a valid output audio channel mask. 77 * 78 * \param channelMask the audio output channel mask to use. 79 * Invalid channel masks are ignored. 80 * 81 */ 82 void setChannelMask(audio_channel_mask_t channelMask); 83 84 85 /** 86 * \brief Sets the left-right balance parameter. 87 * 88 * setBalance() should be called before process() to set 89 * the balance. The initial value is 0.f (no action). 90 * 91 * \param balance from -1.f (left) to 0.f (center) to 1.f (right). 92 * 93 */ 94 void setBalance(float balance); 95 96 /** 97 * \brief Processes balance for audio data. 98 * 99 * setChannelMask() should be called at least once before calling process() 100 * to set the channel mask. A balance of 0.f or a channel mask of 101 * less than 2 channels will return with the buffer untouched. 102 * 103 * \param buffer pointer to the audio data to be modified in-place. 104 * \param frames number of frames of audio data to convert. 105 * 106 */ 107 void process(float *buffer, size_t frames); 108 109 /** 110 * \brief Computes the stereo gains for left and right channels. 111 * 112 * Implementation detail (may change): 113 * This is not an energy preserving balance (e.g. using sin/cos cross fade or some such). 114 * Rather balance preserves full gain on left and right when balance is 0.f, 115 * and decreases the right or left as one changes the balance parameter. 116 * 117 * \param balance from -1.f (left) to 0.f (center) to 1.f (right). 118 * \param left pointer to the float where the left gain will be stored. 119 * \param right pointer to the float where the right gain will be stored. 120 */ 121 void computeStereoBalance(float balance, float *left, float *right) const; 122 123 /** 124 * \brief Creates a std::string representation of Balance object for logging. 125 * 126 * \return string representation of Balance object 127 */ 128 std::string toString() const; 129 130 private: 131 132 /** 133 * \brief Normalizes f: [0, 1] -> [a, b] to g: [0, 1] -> [0, 1]. 134 * 135 * A helper function to normalize a float volume function. 136 * g(0) is exactly zero, but g(1) may not necessarily be 1 since we 137 * use reciprocal multiplication instead of division to scale. 138 * 139 * \param f a function from [0, 1] -> [a, b] 140 * \return g a function from [0, 1] -> [0, 1] as a linear function of f. 141 */ 142 template<typename T> normalize(std::function<T (T)> f)143 static std::function<T(T)> normalize(std::function<T(T)> f) { 144 const T f0 = f(0); 145 const T r = T(1) / (f(1) - f0); // reciprocal multiplication 146 147 if (f0 != T(0) || // must be exactly 0 at 0, since we promise g(0) == 0 148 fabs(r - T(1)) > std::numeric_limits<T>::epsilon() * 3) { // some fudge allowed on r. 149 return [f, f0, r](T x) { return r * (f(x) - f0); }; 150 } 151 // no translation required. 152 return f; 153 } 154 155 // setBalance() changes mBalance and mVolumes based on the channel geometry information. 156 float mBalance = 0.f; // balance: -1.f (left), 0.f (center), 1.f (right) 157 std::vector<float> mVolumes; // per channel, the volume adjustment due to balance. 158 159 // setChannelMask() updates mChannelMask, mChannelCount, and mSides to cache the geometry 160 // and then calls setBalance() to update mVolumes. 161 162 audio_channel_mask_t mChannelMask = AUDIO_CHANNEL_INVALID; 163 size_t mChannelCount = 0; // from mChannelMask, 0 means no processing done. 164 165 std::vector<int> mSides; // per channel, the side (0 = left, 1 = right, 2 = center) 166 // only used for channel position masks. 167 168 // Ramping variables 169 bool mRamp; // whether ramp is enabled. 170 float mRampBalance = 0.f; // last (starting) balance to begin ramp. 171 std::vector<float> mRampVolumes; // last (starting) volumes to begin ramp, clear for no ramp. 172 173 const std::function<float(float)> mCurve; // monotone volume transfer func [0, 1] -> [0, 1] 174 }; 175 176 } // namespace android::audio_utils 177 178 #endif // !ANDROID_AUDIO_UTILS_BALANCE_H 179