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