2 * Copyright (C) 2019 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 #include <audio_utils/Balance.h>
19 namespace android::audio_utils {
21 // Implementation detail: parametric volume curve transfer function
23 constexpr float CURVE_PARAMETER = 2.f;
25 static inline float curve(float parameter, float inVolume) {
26 return exp(parameter * inVolume) - 1.f;
30 : mCurveNorm(1.f / curve(CURVE_PARAMETER, 1.f /* inVolume */))
32 setChannelMask(AUDIO_CHANNEL_OUT_STEREO);
35 void Balance::setChannelMask(audio_channel_mask_t channelMask)
37 channelMask &= ~ AUDIO_CHANNEL_HAPTIC_ALL;
38 if (!audio_is_output_channel(channelMask) // invalid mask
39 || mChannelMask == channelMask) { // no need to do anything
43 mChannelMask = channelMask;
44 mChannelCount = audio_channel_count_from_out_mask(channelMask);
46 // reset mVolumes (the next process() will recalculate if needed).
47 mVolumes.resize(mChannelCount);
48 std::fill(mVolumes.begin(), mVolumes.end(), 1.f);
51 // Implementation detail (may change):
52 // For implementation speed, we precompute the side (left, right, center),
53 // which is a fixed geometrical constant for a given channel mask.
54 // This assumes that the channel mask does not change frequently.
56 // For the channel mask spec, see system/media/audio/include/system/audio-base.h.
58 // The side is: 0 = left, 1 = right, 2 = center.
59 static constexpr int sideFromChannel[] = {
60 0, // AUDIO_CHANNEL_OUT_FRONT_LEFT = 0x1u,
61 1, // AUDIO_CHANNEL_OUT_FRONT_RIGHT = 0x2u,
62 2, // AUDIO_CHANNEL_OUT_FRONT_CENTER = 0x4u,
63 2, // AUDIO_CHANNEL_OUT_LOW_FREQUENCY = 0x8u,
64 0, // AUDIO_CHANNEL_OUT_BACK_LEFT = 0x10u,
65 1, // AUDIO_CHANNEL_OUT_BACK_RIGHT = 0x20u,
66 0, // AUDIO_CHANNEL_OUT_FRONT_LEFT_OF_CENTER = 0x40u,
67 1, // AUDIO_CHANNEL_OUT_FRONT_RIGHT_OF_CENTER = 0x80u,
68 2, // AUDIO_CHANNEL_OUT_BACK_CENTER = 0x100u,
69 0, // AUDIO_CHANNEL_OUT_SIDE_LEFT = 0x200u,
70 1, // AUDIO_CHANNEL_OUT_SIDE_RIGHT = 0x400u,
71 2, // AUDIO_CHANNEL_OUT_TOP_CENTER = 0x800u,
72 0, // AUDIO_CHANNEL_OUT_TOP_FRONT_LEFT = 0x1000u,
73 2, // AUDIO_CHANNEL_OUT_TOP_FRONT_CENTER = 0x2000u,
74 1, // AUDIO_CHANNEL_OUT_TOP_FRONT_RIGHT = 0x4000u,
75 0, // AUDIO_CHANNEL_OUT_TOP_BACK_LEFT = 0x8000u,
76 2, // AUDIO_CHANNEL_OUT_TOP_BACK_CENTER = 0x10000u,
77 1, // AUDIO_CHANNEL_OUT_TOP_BACK_RIGHT = 0x20000u,
78 0, // AUDIO_CHANNEL_OUT_TOP_SIDE_LEFT = 0x40000u,
79 1, // AUDIO_CHANNEL_OUT_TOP_SIDE_RIGHT = 0x80000u,
82 mSides.resize(mChannelCount);
83 for (unsigned i = 0, channel = channelMask; channel != 0; ++i) {
84 const int index = __builtin_ctz(channel);
85 if (index < sizeof(sideFromChannel) / sizeof(sideFromChannel[0])) {
86 mSides[i] = 2; // consider center
88 mSides[i] = sideFromChannel[index];
90 channel &= ~(1 << index);
94 void Balance::process(float *buffer, float balance, size_t frames)
97 for (size_t i = 0; i < frames; ++i) {
98 for (size_t j = 0; j < mChannelCount; ++j) {
99 *buffer++ *= mVolumes[j];
104 // Implementation detail (may change):
105 // This is not an energy preserving balance (e.g. using sin/cos cross fade or some such).
106 // Rather it preserves full gain on left and right when balance is 0.f,
107 // and decreases the right or left as one changes the balance.
108 void Balance::computeStereoBalance(float balance, float *left, float *right) const
111 *left = curve(CURVE_PARAMETER, 1.f - balance) * mCurveNorm;
113 } else if (balance < 0.f) {
115 *right = curve(CURVE_PARAMETER, 1.f + balance) * mCurveNorm;
122 // *left = balance > 0.f ? curve(CURVE_PARAMETER, 1.f - balance) * mCurveNorm : 1.f;
123 // *right = balance < 0.f ? curve(CURVE_PARAMETER, 1.f + balance) * mCurveNorm : 1.f;
126 std::string Balance::toString() const
128 std::stringstream ss;
129 ss << "balance " << mBalance << " channelCount " << mChannelCount << " volumes:";
130 for (float volume : mVolumes) {
136 void Balance::setBalance(float balance)
138 if (isnan(balance) || fabs(balance) > 1.f // balance out of range
139 || mBalance == balance || mChannelCount < 2) { // change not applicable
145 // handle the common cases
146 if (mChannelMask == AUDIO_CHANNEL_OUT_STEREO
147 || audio_channel_mask_get_representation(mChannelMask)
148 == AUDIO_CHANNEL_REPRESENTATION_INDEX) {
149 computeStereoBalance(balance, &mVolumes[0], &mVolumes[1]);
153 float balanceVolumes[3]; // left, right, center
154 computeStereoBalance(balance, &balanceVolumes[0], &balanceVolumes[1]);
155 balanceVolumes[2] = 1.f; // center TODO: consider center scaling.
157 for (size_t i = 0; i < mVolumes.size(); ++i) {
158 mVolumes[i] = balanceVolumes[mSides[i]];
162 } // namespace android::audio_utils