blob: 59f69acbd982420304de92d49d89e8e688ce53f5 [file] [log] [blame]
// Copyright 2014 The Chromium 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 "platform/audio/StereoPanner.h"
#include <algorithm>
#include "platform/audio/AudioBus.h"
#include "platform/audio/AudioUtilities.h"
#include "platform/wtf/MathExtras.h"
// Use a 50ms smoothing / de-zippering time-constant.
const float SmoothingTimeConstant = 0.050f;
namespace blink {
// Implement equal-power panning algorithm for mono or stereo input.
// See: http://webaudio.github.io/web-audio-api/#panning-algorithm
std::unique_ptr<StereoPanner> StereoPanner::Create(float sample_rate) {
return WTF::WrapUnique(new StereoPanner(sample_rate));
}
StereoPanner::StereoPanner(float sample_rate)
: is_first_render_(true), pan_(0.0) {
// Convert smoothing time (50ms) to a per-sample time value.
smoothing_constant_ = AudioUtilities::DiscreteTimeConstantForSampleRate(
SmoothingTimeConstant, sample_rate);
}
void StereoPanner::PanWithSampleAccurateValues(const AudioBus* input_bus,
AudioBus* output_bus,
const float* pan_values,
size_t frames_to_process) {
bool is_input_safe = input_bus &&
(input_bus->NumberOfChannels() == 1 ||
input_bus->NumberOfChannels() == 2) &&
frames_to_process <= input_bus->length();
DCHECK(is_input_safe);
if (!is_input_safe)
return;
unsigned number_of_input_channels = input_bus->NumberOfChannels();
bool is_output_safe = output_bus && output_bus->NumberOfChannels() == 2 &&
frames_to_process <= output_bus->length();
DCHECK(is_output_safe);
if (!is_output_safe)
return;
const float* source_l = input_bus->Channel(0)->Data();
const float* source_r =
number_of_input_channels > 1 ? input_bus->Channel(1)->Data() : source_l;
float* destination_l =
output_bus->ChannelByType(AudioBus::kChannelLeft)->MutableData();
float* destination_r =
output_bus->ChannelByType(AudioBus::kChannelRight)->MutableData();
if (!source_l || !source_r || !destination_l || !destination_r)
return;
double gain_l, gain_r, pan_radian;
int n = frames_to_process;
if (number_of_input_channels == 1) { // For mono source case.
while (n--) {
float input_l = *source_l++;
pan_ = clampTo(*pan_values++, -1.0, 1.0);
// Pan from left to right [-1; 1] will be normalized as [0; 1].
pan_radian = (pan_ * 0.5 + 0.5) * piOverTwoDouble;
gain_l = std::cos(pan_radian);
gain_r = std::sin(pan_radian);
*destination_l++ = static_cast<float>(input_l * gain_l);
*destination_r++ = static_cast<float>(input_l * gain_r);
}
} else { // For stereo source case.
while (n--) {
float input_l = *source_l++;
float input_r = *source_r++;
pan_ = clampTo(*pan_values++, -1.0, 1.0);
// Normalize [-1; 0] to [0; 1]. Do nothing when [0; 1].
pan_radian = (pan_ <= 0 ? pan_ + 1 : pan_) * piOverTwoDouble;
gain_l = std::cos(pan_radian);
gain_r = std::sin(pan_radian);
if (pan_ <= 0) {
*destination_l++ = static_cast<float>(input_l + input_r * gain_l);
*destination_r++ = static_cast<float>(input_r * gain_r);
} else {
*destination_l++ = static_cast<float>(input_l * gain_l);
*destination_r++ = static_cast<float>(input_r + input_l * gain_r);
}
}
}
}
void StereoPanner::PanToTargetValue(const AudioBus* input_bus,
AudioBus* output_bus,
float pan_value,
size_t frames_to_process) {
bool is_input_safe = input_bus &&
(input_bus->NumberOfChannels() == 1 ||
input_bus->NumberOfChannels() == 2) &&
frames_to_process <= input_bus->length();
DCHECK(is_input_safe);
if (!is_input_safe)
return;
unsigned number_of_input_channels = input_bus->NumberOfChannels();
bool is_output_safe = output_bus && output_bus->NumberOfChannels() == 2 &&
frames_to_process <= output_bus->length();
DCHECK(is_output_safe);
if (!is_output_safe)
return;
const float* source_l = input_bus->Channel(0)->Data();
const float* source_r =
number_of_input_channels > 1 ? input_bus->Channel(1)->Data() : source_l;
float* destination_l =
output_bus->ChannelByType(AudioBus::kChannelLeft)->MutableData();
float* destination_r =
output_bus->ChannelByType(AudioBus::kChannelRight)->MutableData();
if (!source_l || !source_r || !destination_l || !destination_r)
return;
float target_pan = clampTo(pan_value, -1.0, 1.0);
// Don't de-zipper on first render call.
if (is_first_render_) {
is_first_render_ = false;
pan_ = target_pan;
}
double gain_l, gain_r, pan_radian;
const double smoothing_constant = smoothing_constant_;
int n = frames_to_process;
if (number_of_input_channels == 1) { // For mono source case.
while (n--) {
float input_l = *source_l++;
pan_ += (target_pan - pan_) * smoothing_constant;
// Pan from left to right [-1; 1] will be normalized as [0; 1].
pan_radian = (pan_ * 0.5 + 0.5) * piOverTwoDouble;
gain_l = std::cos(pan_radian);
gain_r = std::sin(pan_radian);
*destination_l++ = static_cast<float>(input_l * gain_l);
*destination_r++ = static_cast<float>(input_l * gain_r);
}
} else { // For stereo source case.
while (n--) {
float input_l = *source_l++;
float input_r = *source_r++;
pan_ += (target_pan - pan_) * smoothing_constant;
// Normalize [-1; 0] to [0; 1] for the left pan position (<= 0), and
// do nothing when [0; 1].
pan_radian = (pan_ <= 0 ? pan_ + 1 : pan_) * piOverTwoDouble;
gain_l = std::cos(pan_radian);
gain_r = std::sin(pan_radian);
// The pan value should be checked every sample when de-zippering.
// See crbug.com/470559.
if (pan_ <= 0) {
// When [-1; 0], keep left channel intact and equal-power pan the
// right channel only.
*destination_l++ = static_cast<float>(input_l + input_r * gain_l);
*destination_r++ = static_cast<float>(input_r * gain_r);
} else {
// When [0; 1], keep right channel intact and equal-power pan the
// left channel only.
*destination_l++ = static_cast<float>(input_l * gain_l);
*destination_r++ = static_cast<float>(input_r + input_l * gain_r);
}
}
}
}
} // namespace blink