blob: 9ea5ffd0b520d704471a108223ca6e9062991a4d [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "volume_curve.h"
#include <float.h>
#include <fuchsia/media/cpp/fidl.h>
#include <lib/syslog/cpp/macros.h>
#include <algorithm>
#include "src/media/audio/audio_core/mixer/gain.h"
#include "src/media/audio/audio_core/mixer/mixer_utils.h"
#include "src/media/audio/audio_core/process_config_loader.h"
namespace media::audio {
VolumeCurve VolumeCurve::DefaultForMinGain(float min_gain_db) {
FX_DCHECK(min_gain_db < Gain::kUnityGainDb);
FX_DCHECK(min_gain_db >= fuchsia::media::audio::MUTED_GAIN_DB);
std::vector<VolumeMapping> mappings = {
{fuchsia::media::audio::MIN_VOLUME, fuchsia::media::audio::MUTED_GAIN_DB}};
if (min_gain_db != fuchsia::media::audio::MUTED_GAIN_DB) {
mappings.push_back({FLT_EPSILON, min_gain_db});
}
mappings.push_back({fuchsia::media::audio::MAX_VOLUME, Gain::kUnityGainDb});
auto curve_result = VolumeCurve::FromMappings(std::move(mappings));
if (!curve_result.is_ok()) {
FX_LOGS(FATAL) << "Failed to build curve; error: " << curve_result.take_error();
}
return curve_result.take_value();
}
fit::result<VolumeCurve, VolumeCurve::Error> VolumeCurve::FromMappings(
std::vector<VolumeMapping> mappings) {
if (mappings.size() < 2) {
return fit::error(kLessThanTwoMappingsCannotMakeCurve);
}
if (mappings.front().volume != fuchsia::media::audio::MIN_VOLUME ||
mappings.back().volume != fuchsia::media::audio::MAX_VOLUME) {
return fit::error(kDomain0to1NotCovered);
}
if (mappings.back().gain_dbfs != Gain::kUnityGainDb) {
return fit::error(kRange0NotCovered);
}
for (size_t i = 1; i < mappings.size(); ++i) {
if (mappings[i - 1].volume >= mappings[i].volume) {
return fit::error(kNonIncreasingDomainIllegal);
}
if (mappings[i - 1].gain_dbfs >= mappings[i].gain_dbfs) {
return fit::error(kNonIncreasingRangeIllegal);
}
}
return fit::ok(VolumeCurve(std::move(mappings)));
}
VolumeCurve::VolumeCurve(std::vector<VolumeMapping> mappings) : mappings_(std::move(mappings)) {}
float VolumeCurve::VolumeToDb(float volume) const {
const float x = std::clamp<float>(volume, fuchsia::media::audio::MIN_VOLUME,
fuchsia::media::audio::MAX_VOLUME);
const auto bounds = Bounds(x, Attribute::kVolume);
FX_DCHECK(bounds.has_value())
<< "At construction, we ensure the volume domain includes [0.0, 1.0].";
const auto [lower_bound, upper_bound] = bounds.value();
const auto x0 = lower_bound.volume;
const auto a = lower_bound.gain_dbfs;
const auto x1 = upper_bound.volume;
const auto b = upper_bound.gain_dbfs;
FX_DCHECK(x1 != x0) << "At construction, we reject vertical segments.";
const auto alpha = (x - x0) / (x1 - x0);
return mixer::LinearInterpolateF(a, b, alpha);
}
float VolumeCurve::DbToVolume(float gain_dbfs) const {
const float x =
std::clamp<float>(gain_dbfs, fuchsia::media::audio::MUTED_GAIN_DB, Gain::kUnityGainDb);
const auto bounds = Bounds(x, Attribute::kGain);
if (!bounds.has_value()) {
// We verify that our volume curve tops off at Unity at construction time, so we won't be above
// that. If our gain is below our min gain for the volume curve, we'll clamp our volume to 0.
return 0.0;
}
const auto [lower_bound, upper_bound] = bounds.value();
const auto x0 = lower_bound.gain_dbfs;
const auto a = lower_bound.volume;
const auto x1 = upper_bound.gain_dbfs;
const auto b = upper_bound.volume;
FX_DCHECK(x1 != x0) << "At construction, we reject vertical segments.";
const auto alpha = (x - x0) / (x1 - x0);
return mixer::LinearInterpolateF(a, b, alpha);
}
std::optional<std::pair<VolumeCurve::VolumeMapping, VolumeCurve::VolumeMapping>>
VolumeCurve::Bounds(float x, VolumeCurve::Attribute attr) const {
const auto mappings_are_enclosing_bounds = [x, attr](VolumeMapping a, VolumeMapping b) {
if (attr == Attribute::kVolume) {
return a.volume <= x && b.volume >= x;
} else {
return a.gain_dbfs <= x && b.gain_dbfs >= x;
}
};
auto it = std::adjacent_find(mappings_.begin(), mappings_.end(), mappings_are_enclosing_bounds);
if (it == mappings_.end()) {
return std::nullopt;
}
const auto lower_bound = *it;
const auto upper_bound = *(++it);
return {{lower_bound, upper_bound}};
}
} // namespace media::audio