• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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