| // 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 "third_party/blink/renderer/platform/audio/stereo_panner.h" |
| |
| #include <algorithm> |
| #include <memory> |
| |
| #include "base/memory/ptr_util.h" |
| #include "third_party/blink/renderer/platform/audio/audio_bus.h" |
| #include "third_party/blink/renderer/platform/audio/audio_utilities.h" |
| #include "third_party/blink/renderer/platform/wtf/math_extras.h" |
| |
| namespace blink { |
| |
| // Implement equal-power panning algorithm for mono or stereo input. |
| // See: http://webaudio.github.io/web-audio-api/#panning-algorithm |
| |
| StereoPanner::StereoPanner(float sample_rate) {} |
| |
| void StereoPanner::PanWithSampleAccurateValues(const AudioBus* input_bus, |
| AudioBus* output_bus, |
| const float* pan_values, |
| uint32_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++; |
| double 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) * kPiOverTwoDouble; |
| 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++; |
| double 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) * kPiOverTwoDouble; |
| 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, |
| uint32_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); |
| |
| int n = frames_to_process; |
| |
| if (number_of_input_channels == 1) { // For mono source case. |
| // Pan from left to right [-1; 1] will be normalized as [0; 1]. |
| double pan_radian = (target_pan * 0.5 + 0.5) * kPiOverTwoDouble; |
| |
| double gain_l = std::cos(pan_radian); |
| double gain_r = std::sin(pan_radian); |
| |
| // TODO(rtoy): This can be vectorized using vector_math::Vsmul |
| while (n--) { |
| float input_l = *source_l++; |
| *destination_l++ = static_cast<float>(input_l * gain_l); |
| *destination_r++ = static_cast<float>(input_l * gain_r); |
| } |
| } else { // For stereo source case. |
| // Normalize [-1; 0] to [0; 1] for the left pan position (<= 0), and |
| // do nothing when [0; 1]. |
| double pan_radian = |
| (target_pan <= 0 ? target_pan + 1 : target_pan) * kPiOverTwoDouble; |
| |
| double gain_l = std::cos(pan_radian); |
| double gain_r = std::sin(pan_radian); |
| |
| // TODO(rtoy): Consider moving the if statement outside the loop |
| // since |target_pan| is constant inside the loop. |
| while (n--) { |
| float input_l = *source_l++; |
| float input_r = *source_r++; |
| if (target_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 |