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