| // 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 |