blob: c9d95e445c153f1727bba2670c62d69fd4eb0938 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ANDROID_AUDIO_UTILS_BALANCE_H
#define ANDROID_AUDIO_UTILS_BALANCE_H
#include <audio_utils/channels.h>
#include <functional>
#include <math.h> /* expf */
#include <sstream>
#include <vector>
namespace android::audio_utils {
class Balance {
public:
/**
* \brief Balance processing of left-right volume on audio data.
*
* Allows processing of audio data with a single balance parameter from [-1, 1].
* For efficiency, the class caches balance and channel mask data between calls;
* hence, use by multiple threads will require caller locking.
*
* \param ramp whether to ramp volume or not.
* \param curve a monotonic increasing function f: [0, 1] -> [a, b]
* which represents the volume steps from an input domain of [0, 1] to
* an output range [a, b] (ostensibly also from 0 to 1).
* If [a, b] is not [0, 1], it is normalized to [0, 1].
* Curve is typically a convex function, some possible examples:
* [](float x) { return expf(2.f * x); }
* or
* [](float x) { return x * (x + 0.2f); }
*/
explicit Balance(
bool ramp = true,
std::function<float(float)> curve = [](float x) { return x * (x + 0.2f); })
: mRamp(ramp)
, mCurve(normalize(std::move(curve))) { }
/**
* \brief Sets whether the process ramps left-right volume changes.
*
* The default value is true.
* The ramp will take place, if needed, on the following process()
* using the current balance and volume as the starting point.
*
* Toggling ramp off and then back on will reset the ramp starting point.
*
* \param ramp whether ramping is used to smooth volume changes.
*/
void setRamp(bool ramp) {
if (ramp == mRamp) return; // no change
mRamp = ramp;
if (mRamp) { // use current volume and balance as starting point.
mRampVolumes = mVolumes;
mRampBalance = mBalance;
}
}
/**
* \brief Returns whether volume change is ramped.
*/
bool getRamp() const {
return mRamp;
}
/**
* \brief Sets the channel mask for data passed in.
*
* setChannelMask() must called before process() to set
* a valid output audio channel mask.
*
* \param channelMask the audio output channel mask to use.
* Invalid channel masks are ignored.
*
*/
void setChannelMask(audio_channel_mask_t channelMask);
/**
* \brief Sets the left-right balance parameter.
*
* setBalance() should be called before process() to set
* the balance. The initial value is 0.f (no action).
*
* \param balance from -1.f (left) to 0.f (center) to 1.f (right).
*
*/
void setBalance(float balance);
/**
* \brief Returns the current balance.
*/
float getBalance() const {
return mBalance;
}
/**
* \brief Processes balance for audio data.
*
* setChannelMask() should be called at least once before calling process()
* to set the channel mask. A balance of 0.f or a channel mask of
* less than 2 channels will return with the buffer untouched.
*
* \param buffer pointer to the audio data to be modified in-place.
* \param frames number of frames of audio data to convert.
*
*/
void process(float *buffer, size_t frames);
/**
* \brief Computes the stereo gains for left and right channels.
*
* Implementation detail (may change):
* This is not an energy preserving balance (e.g. using sin/cos cross fade or some such).
* Rather balance preserves full gain on left and right when balance is 0.f,
* and decreases the right or left as one changes the balance parameter.
*
* \param balance from -1.f (left) to 0.f (center) to 1.f (right).
* \param left pointer to the float where the left gain will be stored.
* \param right pointer to the float where the right gain will be stored.
*/
void computeStereoBalance(float balance, float *left, float *right) const;
/**
* \brief Creates a std::string representation of Balance object for logging.
*
* \return string representation of Balance object
*/
std::string toString() const;
private:
/**
* \brief Normalizes f: [0, 1] -> [a, b] to g: [0, 1] -> [0, 1].
*
* A helper function to normalize a float volume function.
* g(0) is exactly zero, but g(1) may not necessarily be 1 since we
* use reciprocal multiplication instead of division to scale.
*
* \param f a function from [0, 1] -> [a, b]
* \return g a function from [0, 1] -> [0, 1] as a linear function of f.
*/
template<typename T>
static std::function<T(T)> normalize(std::function<T(T)> f) {
const T f0 = f(0);
const T r = T(1) / (f(1) - f0); // reciprocal multiplication
if (f0 != T(0) || // must be exactly 0 at 0, since we promise g(0) == 0
fabs(r - T(1)) > std::numeric_limits<T>::epsilon() * 3) { // some fudge allowed on r.
return [f, f0, r](T x) { return r * (f(x) - f0); };
}
// no translation required.
return f;
}
// setBalance() changes mBalance and mVolumes based on the channel geometry information.
float mBalance = 0.f; // balance: -1.f (left), 0.f (center), 1.f (right)
std::vector<float> mVolumes; // per channel, the volume adjustment due to balance.
// setChannelMask() updates mChannelMask, mChannelCount, and mSides to cache the geometry
// and then calls setBalance() to update mVolumes.
audio_channel_mask_t mChannelMask = AUDIO_CHANNEL_INVALID;
size_t mChannelCount = 0; // from mChannelMask, 0 means no processing done.
std::vector<android::audio_utils::channels::AUDIO_GEOMETRY_SIDE> mSides;
// Ramping variables
bool mRamp; // whether ramp is enabled.
float mRampBalance = 0.f; // last (starting) balance to begin ramp.
std::vector<float> mRampVolumes; // last (starting) volumes to begin ramp, clear for no ramp.
const std::function<float(float)> mCurve; // monotone volume transfer func [0, 1] -> [0, 1]
};
} // namespace android::audio_utils
#endif // !ANDROID_AUDIO_UTILS_BALANCE_H