chromium/chromecast/media/base/slew_volume.cc

// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromecast/media/base/slew_volume.h"

#include <algorithm>
#include <cmath>
#include <cstring>

#include "base/check_op.h"
#include "media/base/vector_math.h"

namespace {

// The time to slew from current volume to target volume.
const int kMaxSlewTimeMs = 100;
const int kDefaultSampleRate = 44100;

}  // namespace

struct FMACTraits {
  static void ProcessBulkData(const float* src,
                              float volume,
                              int frames,
                              float* dest) {
    ::media::vector_math::FMAC(src, volume, frames, dest);
  }

  static void ProcessSingleDatum(const float* src, float volume, float* dest) {
    (*dest) += (*src) * volume;
  }

  static void ProcessZeroVolume(const float* src, int frames, float* dest) {}

  static void ProcessUnityVolume(const float* src, int frames, float* dest) {
    ProcessBulkData(src, 1.0, frames, dest);
  }
};

struct FMULTraits {
  static void ProcessBulkData(const float* src,
                              float volume,
                              int frames,
                              float* dest) {
    ::media::vector_math::FMUL(src, volume, frames, dest);
  }

  static void ProcessSingleDatum(const float* src, float volume, float* dest) {
    (*dest) = (*src) * volume;
  }

  static void ProcessZeroVolume(const float* src, int frames, float* dest) {
    std::memset(dest, 0, frames * sizeof(*dest));
  }

  static void ProcessUnityVolume(const float* src, int frames, float* dest) {
    if (src == dest) {
      return;
    }
    std::memcpy(dest, src, frames * sizeof(*dest));
  }
};

namespace chromecast {
namespace media {

SlewVolume::SlewVolume() : SlewVolume(kMaxSlewTimeMs) {}

SlewVolume::SlewVolume(int max_slew_time_ms)
    : SlewVolume(max_slew_time_ms, false) {}

SlewVolume::SlewVolume(int max_slew_time_ms, bool use_cosine_slew)
    : sample_rate_(kDefaultSampleRate),
      max_slew_time_ms_(max_slew_time_ms),
      max_slew_per_sample_(1000.0 / (max_slew_time_ms_ * sample_rate_)),
      use_cosine_slew_(use_cosine_slew) {}

void SlewVolume::SetSampleRate(int sample_rate) {
  CHECK_GT(sample_rate, 0);

  sample_rate_ = sample_rate;
  SetVolume(volume_scale_);
}

void SlewVolume::SetVolume(double volume_scale) {
  volume_scale_ = volume_scale;
  if (interrupted_) {
    current_volume_ = volume_scale_;
    last_starting_volume_ = current_volume_;
  }

  // Slew rate should be volume_to_slew / slew_time / sample_rate, but use a
  // minimum volume_to_slew of 0.1 to avoid very small slew per sample.
  double volume_diff =
      std::max(0.1, std::fabs(volume_scale_ - current_volume_));
  max_slew_per_sample_ =
      volume_diff * 1000.0 / (max_slew_time_ms_ * sample_rate_);

  if (use_cosine_slew_) {
    // Set initial state for cosine slew. Cosine fading always lasts
    // max_slew_time_ms_.
    slew_counter_ = max_slew_time_ms_ * 0.001 * sample_rate_;
    slew_angle_ = sin(M_PI / slew_counter_);
    slew_offset_ = (current_volume_ + volume_scale_) * 0.5;
    slew_cos_ = (current_volume_ - volume_scale_) * 0.5;
    slew_sin_ = 0.0;
  }
}

float SlewVolume::LastBufferMaxMultiplier() {
  return std::max(current_volume_, last_starting_volume_);
}

void SlewVolume::SetMaxSlewTimeMs(int max_slew_time_ms) {
  CHECK_GE(max_slew_time_ms, 0);

  max_slew_time_ms_ = max_slew_time_ms;
}

void SlewVolume::Interrupted() {
  interrupted_ = true;
  current_volume_ = volume_scale_;
}

void SlewVolume::ProcessFMAC(bool repeat_transition,
                             const float* src,
                             int frames,
                             int channels,
                             float* dest) {
  ProcessData<FMACTraits>(repeat_transition, src, frames, channels, dest);
}

void SlewVolume::ProcessFMUL(bool repeat_transition,
                             const float* src,
                             int frames,
                             int channels,
                             float* dest) {
  ProcessData<FMULTraits>(repeat_transition, src, frames, channels, dest);
}

template <typename Traits>
void SlewVolume::ProcessData(bool repeat_transition,
                             const float* src,
                             int frames,
                             int channels,
                             float* dest) {
  DCHECK(src);
  DCHECK(dest);
  // Ensure |src| and |dest| are 16-byte aligned.
  DCHECK_EQ(0u, reinterpret_cast<uintptr_t>(src) &
                    (::media::vector_math::kRequiredAlignment - 1));
  DCHECK_EQ(0u, reinterpret_cast<uintptr_t>(dest) &
                    (::media::vector_math::kRequiredAlignment - 1));

  if (!frames) {
    return;
  }

  interrupted_ = false;
  if (repeat_transition) {
    current_volume_ = last_starting_volume_;
  } else {
    last_starting_volume_ = current_volume_;
  }

  if (current_volume_ == volume_scale_) {
    if (current_volume_ == 0.0) {
      Traits::ProcessZeroVolume(src, frames * channels, dest);
      return;
    }
    if (current_volume_ == 1.0) {
      Traits::ProcessUnityVolume(src, frames * channels, dest);
      return;
    }
    Traits::ProcessBulkData(src, current_volume_, frames * channels, dest);
    return;
  }

  if (use_cosine_slew_) {
    int slew_frames = std::min(slew_counter_, frames);
    frames -= slew_frames;
    slew_counter_ -= slew_frames;
    for (; slew_frames > 0; --slew_frames) {
      slew_cos_ -= slew_sin_ * slew_angle_;
      slew_sin_ += slew_cos_ * slew_angle_;
      current_volume_ = std::clamp(slew_offset_ + slew_cos_, 0.0, 1.0);
      for (int i = 0; i < channels; ++i) {
        Traits::ProcessSingleDatum(src, current_volume_, dest);
        ++src;
        ++dest;
      }
    }
    if (!slew_counter_) {
      current_volume_ = volume_scale_;
    }
  } else if (current_volume_ < volume_scale_) {
    do {
      for (int i = 0; i < channels; ++i) {
        Traits::ProcessSingleDatum(src, current_volume_, dest);
        ++src;
        ++dest;
      }
      --frames;
      current_volume_ += max_slew_per_sample_;
    } while (current_volume_ < volume_scale_ && frames);
    current_volume_ = std::min(current_volume_, volume_scale_);
  } else {  // current_volume_ > volume_scale_
    do {
      for (int i = 0; i < channels; ++i) {
        Traits::ProcessSingleDatum(src, current_volume_, dest);
        ++src;
        ++dest;
      }
      --frames;
      current_volume_ -= max_slew_per_sample_;
    } while (current_volume_ > volume_scale_ && frames);
    current_volume_ = std::max(current_volume_, volume_scale_);
  }
  while (frames && (reinterpret_cast<uintptr_t>(src) &
                    (::media::vector_math::kRequiredAlignment - 1))) {
    for (int i = 0; i < channels; ++i) {
      Traits::ProcessSingleDatum(src, current_volume_, dest);
      ++src;
      ++dest;
    }
    --frames;
  }
  if (!frames) {
    return;
  }
  Traits::ProcessBulkData(src, current_volume_, frames * channels, dest);
}

}  // namespace media
}  // namespace chromecast