blob: f5c737afb699850f288b4513a02bd61ef774fe1d [file] [log] [blame]
// Copyright 2022 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.
#ifndef SRC_MEDIA_AUDIO_LIB_PROCESSING_GAIN_CONTROL_H_
#define SRC_MEDIA_AUDIO_LIB_PROCESSING_GAIN_CONTROL_H_
#include <lib/zx/time.h>
#include <functional>
#include <map>
#include <optional>
#include <variant>
#include <vector>
#include "src/media/audio/lib/processing/gain.h"
namespace media_audio {
enum class GainRampType {
kLinearScale, // Linear scale interpolation.
};
struct GainRamp {
zx::duration duration;
GainRampType type = GainRampType::kLinearScale;
};
// Class that controls audio gain. This essentially wraps the functionality of a FIDL GainControl.
//
// Gain can be controlled in two different ways:
//
// 1. by `ScheduleGain` and `ScheduleMute` functions:
// These functions can be used to schedule gain and mute changes at a specified reference time
// to be applied. When scheduling gain, an optional gain ramp parameter can be used, which
// would apply a ramp with specified duration, starting from the scheduled reference time, from
// the gain value at the reference time, to the specified target gain. Note that the starting
// gain value of the ramp is computed at the time of processing during the corresponding
// `Process` call, in order to make sure that all scheduled changes are taken into account at
// that reference time, regardless of the order of the schedule calls.
//
// 2. by `SetGain` and `SetMute` functions:
// These functions correspond to the "immediately" GainTimestamp option in FIDL GainControl
// API. They can be used to directly apply a change in gain or mute. Similar to scheduling
// gains, an optional gain ramp parameter can be used when setting a change in gain, which
// would start the specified ramp immediately in the next `Process` call.
//
// Gain changes are reported back to the caller in each `Process` call using a callback mechanism.
// Each `Process` call will report their initial state with a single callback at the start reference
// time of the call, followed by additional callbacks for each gain state change until the end
// reference time is reached. The following are guaranteed for each callback:
//
// * `ScheduleGain` and `ScheduleMute` will be reported in order of their reference times,
// regardless of which order they were called. For example, these calls with decreasing
// reference times below will be reported in the opposite order of their original call order:
//
// ```
// ScheduleGain(3, 3.0f);
// ScheduleGain(2, 2.0f);
// ScheduleMute(1, true);
// ```
//
// * Changes that are scheduled at the same reference time will be applied in their call order,
// where the changes will be combined into a single callback to minimize the number of reports.
// For example, calls below will be merged into a single change of `gain: 2.0f, is_muted: true`:
//
// ```
// ScheduleGain(5, 3.0f);
// ScheduleMute(5, false);
// ScheduleGain(5, -10.0f);
// ScheduleGain(5, 2.0f);
// ScheduleMute(5, true);
// ```
//
// * Similarly, all types of changes, including `SetGain` and `SetMute` calls, will be grouped
// together by reference time, and will only be reported if they result in an effective change.
// For example, setting the same gain value multiple times will trigger a callback only once.
//
// * Only a single gain ramp can be active at a time, i.e. any ongoing gain ramp at a time will be
// replaced by a call that is set to be applied anytime at or after the beginning of the ongoing
// ramp. This is not only true for the `ScheduleGain` and `ScheduleMute` calls, but also for the
// `SetGain` and `SetMute` calls.
//
// * Changes can be scheduled in the past, where the guarantees above will still be preserved.
// However, all the changes that were "late" to arrive will be merged and reported together in a
// single callback at the beginning of the next `Process` call.
//
// * `SetGain` and `SetMute` changes will typically be applied after `ScheduleGain` and
// `ScheduleMute` changes that are set to be applied at the same reference time. However, since
// `SetGain` and `SetMute` do not expose their reference time, we do *not* recommend mixing
// these two types of functions if the call order is of importance to the application.
//
// This class is not safe for concurrent use.
class GainControl {
public:
struct State {
float gain_db;
bool is_muted;
float linear_scale_slope_per_ns;
bool operator!=(const State& other) const {
return gain_db != other.gain_db || is_muted != other.is_muted ||
linear_scale_slope_per_ns != other.linear_scale_slope_per_ns;
}
};
using Callback = std::function<void(zx::time reference_time, const State& state)>;
// Processes the time range `[start_reference_time, end_reference_time)`, and triggers `callback`
// for each gain state in that range.
void Process(zx::time start_reference_time, zx::time end_reference_time,
const Callback& callback);
// Schedules gain at `reference_time` with an optional `ramp`.
void ScheduleGain(zx::time reference_time, float gain_db,
std::optional<GainRamp> ramp = std::nullopt);
// Schedules mute at `reference_time`.
void ScheduleMute(zx::time reference_time, bool is_muted);
// Sets gain *immediately* with an optional `ramp`.
void SetGain(float gain_db, std::optional<GainRamp> ramp = std::nullopt);
// Sets mute *immediately*.
void SetMute(bool is_muted);
private:
struct GainCommand {
float gain_db;
std::optional<GainRamp> ramp;
};
struct MuteCommand {
bool is_muted;
};
using Command = std::variant<GainCommand, MuteCommand>;
struct ActiveGainRamp {
zx::time end_time;
float end_gain_db;
float linear_scale_slope_per_ns; // Corresponds to `GainRampType::kLinearScale` ramp type.
};
// Advances `state_` to `reference_time` using the active gain ramp.
void AdvanceStateWithActiveGainRamp(zx::time reference_time);
// Completes the active gain ramp.
void CompleteActiveGainRamp();
// Processes `command` at `reference_time`.
void ProcessCommand(zx::time reference_time, const Command& command);
// Processes gain at `reference_time` with an optional `ramp`.
void ProcessGain(zx::time reference_time, float gain_db, const std::optional<GainRamp>& ramp);
// Commands to be applied *immediately* in the next `Process` call. Since each consequent call to
// `SetGain` or `SetMute` will override the previous call, we only need to store the last one.
std::optional<GainCommand> immediate_gain_command_;
std::optional<MuteCommand> immediate_mute_command_;
// Sorted map of scheduled commands by their reference times.
// TODO(fxbug.dev/87651): Make sure to prevent this from growing in an unbounded way.
std::multimap<zx::time, Command> scheduled_commands_;
std::optional<ActiveGainRamp> active_gain_ramp_;
zx::time last_advanced_time_ = zx::time(0);
zx::time last_processed_gain_command_time_ = zx::time(0);
zx::time last_processed_mute_command_time_ = zx::time(0);
State state_ = {/*gain_db=*/kUnityGainDb, /*is_muted=*/false, /*linear_scale_slope_per_ns=*/0.0f};
};
} // namespace media_audio
#endif // SRC_MEDIA_AUDIO_LIB_PROCESSING_GAIN_CONTROL_H_