diff options
Diffstat (limited to 'src/3rdparty/resonance-audio/resonance_audio')
267 files changed, 47090 insertions, 0 deletions
diff --git a/src/3rdparty/resonance-audio/resonance_audio/CMakeLists.txt b/src/3rdparty/resonance-audio/resonance_audio/CMakeLists.txt new file mode 100644 index 000000000..b52e62526 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/CMakeLists.txt @@ -0,0 +1,335 @@ +# Copyright 2018 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS-IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Build PFFFT. +set(PFFFT_DIR "${PROJECT_SOURCE_DIR}/third_party/pffft/" CACHE PATH "Path to pffft library") +set(PFFFT_INCLUDE_DIR ${PFFFT_DIR}) +set(PFFFT_SOURCE + ${PFFFT_DIR}/fftpack.c + ${PFFFT_DIR}/fftpack.h + ${PFFFT_DIR}/pffft.c + ${PFFFT_DIR}/pffft.h + ) +add_library(PffftObj OBJECT ${PFFFT_SOURCE}) +target_include_directories(PffftObj PRIVATE ${PFFFT_INCLUDE_DIR}/) + +if (UNIX) + set_target_properties(PffftObj PROPERTIES COMPILE_FLAGS -fPIC) +endif (UNIX) + +# Build SADIE HRTF database. +set(SADIE_HRTFS_DIR "${PROJECT_SOURCE_DIR}/third_party/SADIE_hrtf_database/generated/" CACHE PATH "Path to SADIE_hrtf_database library") +set(SADIE_HRTFS_INCLUDE_DIR ${SADIE_HRTFS_DIR}) +set(SADIE_HRTFS_SOURCE + ${SADIE_HRTFS_DIR}/hrtf_assets.cc + ${SADIE_HRTFS_DIR}/hrtf_assets.h + ) +add_library(SadieHrtfsObj OBJECT ${SADIE_HRTFS_SOURCE}) + +# Build Resonance Audio. +set(RA_SOURCES + ${PROJECT_SOURCE_DIR}/platforms/common/utils.cc + ${PROJECT_SOURCE_DIR}/platforms/common/utils.h + ${RA_SOURCE_DIR}/ambisonics/ambisonic_binaural_decoder.h + ${RA_SOURCE_DIR}/ambisonics/ambisonic_binaural_decoder.cc + ${RA_SOURCE_DIR}/ambisonics/ambisonic_codec.h + ${RA_SOURCE_DIR}/ambisonics/ambisonic_codec_impl.h + ${RA_SOURCE_DIR}/ambisonics/ambisonic_lookup_table.cc + ${RA_SOURCE_DIR}/ambisonics/ambisonic_lookup_table.h + ${RA_SOURCE_DIR}/ambisonics/ambisonic_spread_coefficients.h + ${RA_SOURCE_DIR}/ambisonics/associated_legendre_polynomials_generator.cc + ${RA_SOURCE_DIR}/ambisonics/associated_legendre_polynomials_generator.h + ${RA_SOURCE_DIR}/ambisonics/foa_rotator.cc + ${RA_SOURCE_DIR}/ambisonics/foa_rotator.h + ${RA_SOURCE_DIR}/ambisonics/hoa_rotator.cc + ${RA_SOURCE_DIR}/ambisonics/hoa_rotator.h + ${RA_SOURCE_DIR}/ambisonics/stereo_from_soundfield_converter.cc + ${RA_SOURCE_DIR}/ambisonics/stereo_from_soundfield_converter.h + ${RA_SOURCE_DIR}/ambisonics/utils.h + ${RA_SOURCE_DIR}/api/binaural_surround_renderer.cc + ${RA_SOURCE_DIR}/api/binaural_surround_renderer.h + ${RA_SOURCE_DIR}/api/resonance_audio_api.cc + ${RA_SOURCE_DIR}/api/resonance_audio_api.h + ${RA_SOURCE_DIR}/base/aligned_allocator.h + ${RA_SOURCE_DIR}/base/audio_buffer.cc + ${RA_SOURCE_DIR}/base/audio_buffer.h + ${RA_SOURCE_DIR}/base/channel_view.cc + ${RA_SOURCE_DIR}/base/channel_view.h + ${RA_SOURCE_DIR}/base/constants_and_types.h + ${RA_SOURCE_DIR}/base/integral_types.h + ${RA_SOURCE_DIR}/base/logging.h + ${RA_SOURCE_DIR}/base/misc_math.cc + ${RA_SOURCE_DIR}/base/misc_math.h + ${RA_SOURCE_DIR}/base/object_transform.h + ${RA_SOURCE_DIR}/base/simd_macros.h + ${RA_SOURCE_DIR}/base/simd_utils.cc + ${RA_SOURCE_DIR}/base/simd_utils.h + ${RA_SOURCE_DIR}/base/source_parameters.h + ${RA_SOURCE_DIR}/base/spherical_angle.cc + ${RA_SOURCE_DIR}/base/spherical_angle.h + ${RA_SOURCE_DIR}/base/unique_ptr_wrapper.h + ${RA_SOURCE_DIR}/config/global_config.h + ${RA_SOURCE_DIR}/config/source_config.cc + ${RA_SOURCE_DIR}/config/source_config.h + ${RA_SOURCE_DIR}/dsp/biquad_filter.cc + ${RA_SOURCE_DIR}/dsp/biquad_filter.h + ${RA_SOURCE_DIR}/dsp/channel_converter.cc + ${RA_SOURCE_DIR}/dsp/channel_converter.h + ${RA_SOURCE_DIR}/dsp/circular_buffer.cc + ${RA_SOURCE_DIR}/dsp/circular_buffer.h + ${RA_SOURCE_DIR}/dsp/delay_filter.cc + ${RA_SOURCE_DIR}/dsp/delay_filter.h + ${RA_SOURCE_DIR}/dsp/distance_attenuation.cc + ${RA_SOURCE_DIR}/dsp/distance_attenuation.h + ${RA_SOURCE_DIR}/dsp/fft_manager.cc + ${RA_SOURCE_DIR}/dsp/fft_manager.h + ${RA_SOURCE_DIR}/dsp/filter_coefficient_generators.cc + ${RA_SOURCE_DIR}/dsp/filter_coefficient_generators.h + ${RA_SOURCE_DIR}/dsp/fir_filter.cc + ${RA_SOURCE_DIR}/dsp/fir_filter.h + ${RA_SOURCE_DIR}/dsp/gain.cc + ${RA_SOURCE_DIR}/dsp/gain.h + ${RA_SOURCE_DIR}/dsp/gain_mixer.cc + ${RA_SOURCE_DIR}/dsp/gain_mixer.h + ${RA_SOURCE_DIR}/dsp/gain_processor.cc + ${RA_SOURCE_DIR}/dsp/gain_processor.h + ${RA_SOURCE_DIR}/dsp/mixer.cc + ${RA_SOURCE_DIR}/dsp/mixer.h + ${RA_SOURCE_DIR}/dsp/mono_pole_filter.cc + ${RA_SOURCE_DIR}/dsp/mono_pole_filter.h + ${RA_SOURCE_DIR}/dsp/multi_channel_iir.cc + ${RA_SOURCE_DIR}/dsp/multi_channel_iir.h + ${RA_SOURCE_DIR}/dsp/near_field_processor.cc + ${RA_SOURCE_DIR}/dsp/near_field_processor.h + ${RA_SOURCE_DIR}/dsp/occlusion_calculator.cc + ${RA_SOURCE_DIR}/dsp/occlusion_calculator.h + ${RA_SOURCE_DIR}/dsp/partitioned_fft_filter.cc + ${RA_SOURCE_DIR}/dsp/partitioned_fft_filter.h + ${RA_SOURCE_DIR}/dsp/reflection.h + ${RA_SOURCE_DIR}/dsp/reflections_processor.cc + ${RA_SOURCE_DIR}/dsp/reflections_processor.h + ${RA_SOURCE_DIR}/dsp/resampler.cc + ${RA_SOURCE_DIR}/dsp/resampler.h + ${RA_SOURCE_DIR}/dsp/reverb_onset_compensator.cc + ${RA_SOURCE_DIR}/dsp/reverb_onset_compensator.h + ${RA_SOURCE_DIR}/dsp/reverb_onset_update_processor.cc + ${RA_SOURCE_DIR}/dsp/reverb_onset_update_processor.h + ${RA_SOURCE_DIR}/dsp/sh_hrir_creator.cc + ${RA_SOURCE_DIR}/dsp/sh_hrir_creator.h + ${RA_SOURCE_DIR}/dsp/shoe_box_room.cc + ${RA_SOURCE_DIR}/dsp/shoe_box_room.h + ${RA_SOURCE_DIR}/dsp/spectral_reverb.cc + ${RA_SOURCE_DIR}/dsp/spectral_reverb.h + ${RA_SOURCE_DIR}/dsp/spectral_reverb_constants_and_tables.h + ${RA_SOURCE_DIR}/dsp/stereo_panner.cc + ${RA_SOURCE_DIR}/dsp/stereo_panner.h + ${RA_SOURCE_DIR}/dsp/utils.cc + ${RA_SOURCE_DIR}/dsp/utils.h + ${RA_SOURCE_DIR}/graph/ambisonic_binaural_decoder_node.cc + ${RA_SOURCE_DIR}/graph/ambisonic_binaural_decoder_node.h + ${RA_SOURCE_DIR}/graph/ambisonic_mixing_encoder_node.cc + ${RA_SOURCE_DIR}/graph/ambisonic_mixing_encoder_node.h + ${RA_SOURCE_DIR}/graph/binaural_surround_renderer_impl.cc + ${RA_SOURCE_DIR}/graph/binaural_surround_renderer_impl.h + ${RA_SOURCE_DIR}/graph/buffered_source_node.cc + ${RA_SOURCE_DIR}/graph/buffered_source_node.h + ${RA_SOURCE_DIR}/graph/foa_rotator_node.cc + ${RA_SOURCE_DIR}/graph/foa_rotator_node.h + ${RA_SOURCE_DIR}/graph/gain_mixer_node.cc + ${RA_SOURCE_DIR}/graph/gain_mixer_node.h + ${RA_SOURCE_DIR}/graph/gain_node.cc + ${RA_SOURCE_DIR}/graph/gain_node.h + ${RA_SOURCE_DIR}/graph/graph_manager.cc + ${RA_SOURCE_DIR}/graph/graph_manager.h + ${RA_SOURCE_DIR}/graph/graph_manager_config.h + ${RA_SOURCE_DIR}/graph/hoa_rotator_node.cc + ${RA_SOURCE_DIR}/graph/hoa_rotator_node.h + ${RA_SOURCE_DIR}/graph/mixer_node.cc + ${RA_SOURCE_DIR}/graph/mixer_node.h + ${RA_SOURCE_DIR}/graph/mono_from_soundfield_node.cc + ${RA_SOURCE_DIR}/graph/mono_from_soundfield_node.h + ${RA_SOURCE_DIR}/graph/near_field_effect_node.cc + ${RA_SOURCE_DIR}/graph/near_field_effect_node.h + ${RA_SOURCE_DIR}/graph/occlusion_node.cc + ${RA_SOURCE_DIR}/graph/occlusion_node.h + ${RA_SOURCE_DIR}/graph/reflections_node.cc + ${RA_SOURCE_DIR}/graph/reflections_node.h + ${RA_SOURCE_DIR}/graph/resonance_audio_api_impl.cc + ${RA_SOURCE_DIR}/graph/resonance_audio_api_impl.h + ${RA_SOURCE_DIR}/graph/reverb_node.cc + ${RA_SOURCE_DIR}/graph/reverb_node.h + ${RA_SOURCE_DIR}/graph/source_graph_config.h + ${RA_SOURCE_DIR}/graph/source_parameters_manager.cc + ${RA_SOURCE_DIR}/graph/source_parameters_manager.h + ${RA_SOURCE_DIR}/graph/stereo_mixing_panner_node.cc + ${RA_SOURCE_DIR}/graph/stereo_mixing_panner_node.h + ${RA_SOURCE_DIR}/graph/system_settings.h + ${RA_SOURCE_DIR}/node/node.h + ${RA_SOURCE_DIR}/node/processing_node.cc + ${RA_SOURCE_DIR}/node/processing_node.h + ${RA_SOURCE_DIR}/node/publisher_node.h + ${RA_SOURCE_DIR}/node/sink_node.cc + ${RA_SOURCE_DIR}/node/sink_node.h + ${RA_SOURCE_DIR}/node/source_node.cc + ${RA_SOURCE_DIR}/node/source_node.h + ${RA_SOURCE_DIR}/node/subscriber_node.h + ${PROJECT_SOURCE_DIR}/platforms/common/room_effects_utils.cc + ${PROJECT_SOURCE_DIR}/platforms/common/room_effects_utils.h + ${RA_SOURCE_DIR}/utils/buffer_crossfader.cc + ${RA_SOURCE_DIR}/utils/buffer_crossfader.h + ${RA_SOURCE_DIR}/utils/buffer_partitioner.cc + ${RA_SOURCE_DIR}/utils/buffer_partitioner.h + ${RA_SOURCE_DIR}/utils/buffer_unpartitioner.cc + ${RA_SOURCE_DIR}/utils/buffer_unpartitioner.h + ${RA_SOURCE_DIR}/utils/lockless_task_queue.cc + ${RA_SOURCE_DIR}/utils/lockless_task_queue.h + ${RA_SOURCE_DIR}/utils/planar_interleaved_conversion.cc + ${RA_SOURCE_DIR}/utils/planar_interleaved_conversion.h + ${RA_SOURCE_DIR}/utils/pseudoinverse.h + ${RA_SOURCE_DIR}/utils/sample_type_conversion.cc + ${RA_SOURCE_DIR}/utils/sample_type_conversion.h + ${RA_SOURCE_DIR}/utils/semi_lockless_fifo.h + ${RA_SOURCE_DIR}/utils/sum_and_difference_processor.cc + ${RA_SOURCE_DIR}/utils/sum_and_difference_processor.h + ${RA_SOURCE_DIR}/utils/threadsafe_fifo.h + ${RA_SOURCE_DIR}/utils/wav.cc + ${RA_SOURCE_DIR}/utils/wav.h + ${RA_SOURCE_DIR}/utils/wav_reader.cc + ${RA_SOURCE_DIR}/utils/wav_reader.h + ) + +add_library(ResonanceAudioObj OBJECT ${RA_SOURCES}) +target_include_directories(ResonanceAudioObj PRIVATE ${PROJECT_SOURCE_DIR}/resonance_audio/) +target_include_directories(ResonanceAudioObj PRIVATE ${EIGEN3_INCLUDE_DIR}/) +target_include_directories(ResonanceAudioObj PRIVATE ${PFFFT_INCLUDE_DIR}/) + + +if (BUILD_RESONANCE_AUDIO_API) + # Build shared library + add_library(ResonanceAudioShared SHARED $<TARGET_OBJECTS:ResonanceAudioObj> + $<TARGET_OBJECTS:SadieHrtfsObj> + $<TARGET_OBJECTS:PffftObj>) + + # Build static library + add_library(ResonanceAudioStatic STATIC $<TARGET_OBJECTS:ResonanceAudioObj> + $<TARGET_OBJECTS:SadieHrtfsObj> + $<TARGET_OBJECTS:PffftObj>) + + set(RA_INSTALL_DIR "${INSTALL_DIR}/resonance_audio/") + install(TARGETS ResonanceAudioStatic ResonanceAudioShared + LIBRARY DESTINATION ${RA_INSTALL_DIR}/lib + ARCHIVE DESTINATION ${RA_INSTALL_DIR}/lib + RUNTIME DESTINATION ${RA_INSTALL_DIR}/lib + PUBLIC_HEADER DESTINATION ${RA_INSTALL_DIR}/include) + + set(RA_PUBLIC_HEADERS + ${RA_SOURCE_DIR}/api/binaural_surround_renderer.h + ${RA_SOURCE_DIR}/api/resonance_audio_api.h + ) + install(FILES ${RA_PUBLIC_HEADERS} + DESTINATION ${RA_INSTALL_DIR}/include) +endif (BUILD_RESONANCE_AUDIO_API) + + +if (BUILD_RESONANCE_AUDIO_TESTS) + set(RA_TESTS + ${RA_SOURCE_DIR}/ambisonics/ambisonic_binaural_decoder_test.cc + ${RA_SOURCE_DIR}/ambisonics/ambisonic_codec_impl_test.cc + ${RA_SOURCE_DIR}/ambisonics/ambisonic_lookup_table_test.cc + ${RA_SOURCE_DIR}/ambisonics/associated_legendre_polynomials_generator_test.cc + ${RA_SOURCE_DIR}/ambisonics/foa_rotator_test.cc + ${RA_SOURCE_DIR}/ambisonics/hoa_rotator_test.cc + ${RA_SOURCE_DIR}/ambisonics/stereo_from_soundfield_converter_test.cc + ${RA_SOURCE_DIR}/ambisonics/utils_test.cc + ${RA_SOURCE_DIR}/base/aligned_allocator_test.cc + ${RA_SOURCE_DIR}/base/audio_buffer_test.cc + ${RA_SOURCE_DIR}/base/channel_view_test.cc + ${RA_SOURCE_DIR}/base/misc_math_test.cc + ${RA_SOURCE_DIR}/base/simd_utils_test.cc + ${RA_SOURCE_DIR}/base/spherical_angle_test.cc + ${RA_SOURCE_DIR}/dsp/biquad_filter_test.cc + ${RA_SOURCE_DIR}/dsp/channel_converter_test.cc + ${RA_SOURCE_DIR}/dsp/circular_buffer_test.cc + ${RA_SOURCE_DIR}/dsp/delay_filter_test.cc + ${RA_SOURCE_DIR}/dsp/distance_attenuation_test.cc + ${RA_SOURCE_DIR}/dsp/fft_manager_test.cc + ${RA_SOURCE_DIR}/dsp/filter_coefficient_generators_test.cc + ${RA_SOURCE_DIR}/dsp/fir_filter_test.cc + ${RA_SOURCE_DIR}/dsp/gain_mixer_test.cc + ${RA_SOURCE_DIR}/dsp/gain_processor_test.cc + ${RA_SOURCE_DIR}/dsp/gain_test.cc + ${RA_SOURCE_DIR}/dsp/mixer_test.cc + ${RA_SOURCE_DIR}/dsp/mono_pole_filter_test.cc + ${RA_SOURCE_DIR}/dsp/multi_channel_iir_test.cc + ${RA_SOURCE_DIR}/dsp/occlusion_calculator_test.cc + ${RA_SOURCE_DIR}/dsp/partitioned_fft_filter_test.cc + ${RA_SOURCE_DIR}/dsp/reflections_processor_test.cc + ${RA_SOURCE_DIR}/dsp/resampler_test.cc + ${RA_SOURCE_DIR}/dsp/shoe_box_room_test.cc + ${RA_SOURCE_DIR}/dsp/spectral_reverb_test.cc + ${RA_SOURCE_DIR}/dsp/stereo_panner_test.cc + ${RA_SOURCE_DIR}/dsp/utils_test.cc + ${RA_SOURCE_DIR}/graph/ambisonic_mixing_encoder_node_test.cc + ${RA_SOURCE_DIR}/graph/binaural_surround_renderer_impl_test.cc + ${RA_SOURCE_DIR}/graph/occlusion_node_test.cc + ${RA_SOURCE_DIR}/graph/gain_mixer_node_test.cc + ${RA_SOURCE_DIR}/graph/gain_node_test.cc + ${RA_SOURCE_DIR}/graph/mixer_node_test.cc + ${RA_SOURCE_DIR}/graph/source_parameters_manager_test.cc + ${RA_SOURCE_DIR}/node/audio_nodes_test.cc + ${RA_SOURCE_DIR}/node/node_test.cc + ${PROJECT_SOURCE_DIR}/platforms/common/room_effects_utils_test.cc + ${RA_SOURCE_DIR}/utils/buffer_crossfader_test.cc + ${RA_SOURCE_DIR}/utils/buffer_partitioner_test.cc + ${RA_SOURCE_DIR}/utils/buffer_unpartitioner_test.cc + ${RA_SOURCE_DIR}/utils/lockless_task_queue_test.cc + ${RA_SOURCE_DIR}/utils/planar_interleaved_conversion_test.cc + ${RA_SOURCE_DIR}/utils/pseudoinverse_test.cc + ${RA_SOURCE_DIR}/utils/sample_type_conversion_test.cc + ${RA_SOURCE_DIR}/utils/sum_and_difference_processor_test.cc + ${RA_SOURCE_DIR}/utils/test_util.cc + ${RA_SOURCE_DIR}/utils/test_util.h + ${RA_SOURCE_DIR}/utils/test_util_test.cc + ) + + # Unit Tests target. + add_executable(ResonanceAudioUnitTests ${RA_TESTS} + $<TARGET_OBJECTS:gtest> + $<TARGET_OBJECTS:ResonanceAudioObj> + $<TARGET_OBJECTS:SadieHrtfsObj> + $<TARGET_OBJECTS:PffftObj>) + + target_include_directories(ResonanceAudioUnitTests PRIVATE "${EIGEN3_INCLUDE_DIR}/") + target_include_directories(ResonanceAudioUnitTests PRIVATE "${PFFFT_INCLUDE_DIR}/") + target_include_directories(ResonanceAudioUnitTests PRIVATE "${PROJECT_SOURCE_DIR}/resonance_audio/") + target_include_directories(ResonanceAudioUnitTests PRIVATE "${GTEST_DIR}/googlemock/") + target_include_directories(ResonanceAudioUnitTests PRIVATE "${GTEST_DIR}/googlemock/include") + target_include_directories(ResonanceAudioUnitTests PRIVATE "${GTEST_DIR}/googletest/") + target_include_directories(ResonanceAudioUnitTests PRIVATE "${GTEST_DIR}/googletest/include") + + if (NOT WIN32) + find_package(Threads REQUIRED) + target_link_libraries(ResonanceAudioUnitTests pthread) + endif (NOT WIN32) + + add_test(NAME runResonanceAudioUnitTest COMMAND $<TARGET_FILE:ResonanceAudioUnitTests>) + + add_custom_command( + TARGET ResonanceAudioUnitTests + COMMENT "Run resonance audio tests" + POST_BUILD + COMMAND ResonanceAudioUnitTests + ) +endif (BUILD_RESONANCE_AUDIO_TESTS) diff --git a/src/3rdparty/resonance-audio/resonance_audio/ambisonics/ambisonic_binaural_decoder.cc b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/ambisonic_binaural_decoder.cc new file mode 100644 index 000000000..2a278389b --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/ambisonic_binaural_decoder.cc @@ -0,0 +1,80 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "ambisonics/ambisonic_binaural_decoder.h" + +#include "ambisonics/utils.h" +#include "base/constants_and_types.h" + + +namespace vraudio { + +AmbisonicBinauralDecoder::AmbisonicBinauralDecoder(const AudioBuffer& sh_hrirs, + size_t frames_per_buffer, + FftManager* fft_manager) + : fft_manager_(fft_manager), + freq_input_(kNumMonoChannels, NextPowTwo(frames_per_buffer) * 2), + filtered_input_(kNumMonoChannels, frames_per_buffer) { + CHECK(fft_manager_); + CHECK_NE(frames_per_buffer, 0U); + const size_t num_channels = sh_hrirs.num_channels(); + const size_t filter_size = sh_hrirs.num_frames(); + CHECK_NE(num_channels, 0U); + CHECK_NE(filter_size, 0U); + sh_hrir_filters_.reserve(num_channels); + for (size_t i = 0; i < num_channels; ++i) { + sh_hrir_filters_.emplace_back( + new PartitionedFftFilter(filter_size, frames_per_buffer, fft_manager_)); + sh_hrir_filters_[i]->SetTimeDomainKernel(sh_hrirs[i]); + } +} + +void AmbisonicBinauralDecoder::Process(const AudioBuffer& input, + AudioBuffer* output) { + + DCHECK(output); + DCHECK_EQ(kNumStereoChannels, output->num_channels()); + DCHECK_EQ(input.num_frames(), output->num_frames()); + DCHECK_EQ(input.num_channels(), sh_hrir_filters_.size()); + + output->Clear(); + + AudioBuffer::Channel* freq_input_channel = &freq_input_[0]; + AudioBuffer::Channel* filtered_input_channel = &filtered_input_[0]; + AudioBuffer::Channel* output_channel_0 = &(*output)[0]; + AudioBuffer::Channel* output_channel_1 = &(*output)[1]; + for (size_t channel = 0; channel < input.num_channels(); ++channel) { + const int degree = GetPeriphonicAmbisonicDegreeForChannel(channel); + fft_manager_->FreqFromTimeDomain(input[channel], freq_input_channel); + sh_hrir_filters_[channel]->Filter(*freq_input_channel); + sh_hrir_filters_[channel]->GetFilteredSignal(filtered_input_channel); + if (degree < 0) { + // Degree is negative: spherical harmonic is asymetric. + // So add contributions to the left channel and subtract from the right + // channel. + *output_channel_0 += *filtered_input_channel; + *output_channel_1 -= *filtered_input_channel; + + } else { + // Degree is zero or positive: spherical harmonic is symetric. + // So add contributions to both left and right channels. + *output_channel_0 += *filtered_input_channel; + *output_channel_1 += *filtered_input_channel; + } + } +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/ambisonics/ambisonic_binaural_decoder.h b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/ambisonic_binaural_decoder.h new file mode 100644 index 000000000..98a861234 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/ambisonic_binaural_decoder.h @@ -0,0 +1,71 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_AMBISONICS_AMBISONIC_BINAURAL_DECODER_H_ +#define RESONANCE_AUDIO_AMBISONICS_AMBISONIC_BINAURAL_DECODER_H_ + +#include <vector> + +#include "base/audio_buffer.h" +#include "dsp/fft_manager.h" +#include "dsp/partitioned_fft_filter.h" + +namespace vraudio { + +// Decodes an Ambisonic sound field, of an arbitrary order, to binaural stereo, +// by performing convolution in the spherical harmonics domain. The order (hence +// the number of channels) of the input must match the order (hence the number +// of channels) of the spherical harmonic-encoded Head Related Impulse Responses +// (HRIRs). Assumes that HRIRs are symmetric with respect to the sagittal plane. +class AmbisonicBinauralDecoder { + public: + // Constructs an |AmbisonicBinauralDecoder| from an |AudioBuffer| containing + // spherical harmonic representation of HRIRs. The order of spherical + // harmonic-encoded HRIRs (hence the number of channels) must match the order + // of the Ambisonic sound field input. + // + // @param sh_hrirs |AudioBuffer| containing time-domain spherical harmonic + // encoded symmetric HRIRs. + // @param frames_per_buffer Number of frames in each input/output buffer. + // @param fft_manager Pointer to a manager to perform FFT transformations. + AmbisonicBinauralDecoder(const AudioBuffer& sh_hrirs, + size_t frames_per_buffer, FftManager* fft_manager); + + // Processes an Ambisonic sound field input and outputs a binaurally decoded + // stereo buffer. + // + // @param input Input buffer to be processed. + // @param output Pointer to a stereo output buffer. + void Process(const AudioBuffer& input, AudioBuffer* output); + + private: + // Manager for all FFT related functionality (not owned). + FftManager* const fft_manager_; + + // Spherical Harmonic HRIR filter kernels. + std::vector<std::unique_ptr<PartitionedFftFilter>> sh_hrir_filters_; + + // Frequency domain representation of the input signal. + PartitionedFftFilter::FreqDomainBuffer freq_input_; + + // Temporary audio buffer to store the convolution output for asymmetric or + // symmetric spherical harmonic HRIR. + AudioBuffer filtered_input_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_AMBISONICS_AMBISONIC_BINAURAL_DECODER_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/ambisonics/ambisonic_binaural_decoder_test.cc b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/ambisonic_binaural_decoder_test.cc new file mode 100644 index 000000000..ca2b0dfca --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/ambisonic_binaural_decoder_test.cc @@ -0,0 +1,162 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "ambisonics/ambisonic_binaural_decoder.h" + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/audio_buffer.h" +#include "base/constants_and_types.h" +#include "dsp/fft_manager.h" + +namespace vraudio { + +namespace { + +// Generates sample audio data where the first sample is 0 and each consecutive +// sample is incremented by 0.001. Then it moves to the next channel and +// continues to increment the samples. +std::vector<std::vector<float>> GenerateAudioData(size_t num_channels, + size_t num_samples) { + std::vector<std::vector<float>> input_data(num_channels, + std::vector<float>(num_samples)); + float sample_value = 0.0f; + const float increment = 0.001f; + for (size_t channel = 0; channel < num_channels; ++channel) { + for (size_t sample = 0; sample < num_samples; ++sample) { + input_data[channel][sample] = sample_value; + sample_value += increment; + } + } + return input_data; +} + +// Number of frames in each audio input/output buffer. +const size_t kFramesPerBuffer = 18; + +} // namespace + +// Tests whether binaural docoding of Ambisonic input using short HRIR filters +// (shorter than the number of frames per buffer) gives correct results. +TEST(AmbisonicBinauralDecoderTest, ShortFilterTest) { + const std::vector<std::vector<float>> kInputData = + GenerateAudioData(kNumFirstOrderAmbisonicChannels, kFramesPerBuffer); + const std::vector<float> kExpectedOutputLeft = { + 0.0042840000f, 0.0087780003f, 0.013486000f, 0.018412000f, 0.023560001f, + 0.028934000f, 0.034538001f, 0.040376000f, 0.046452001f, 0.052770000f, + 0.059333999f, 0.066147998f, 0.073215999f, 0.080541998f, 0.088129997f, + 0.095983997f, 0.10410800f, 0.10638600f}; + const std::vector<float> kExpectedOutputRight = { + 0.0036720000f, 0.0074840002f, 0.011438000f, 0.015536000f, 0.019780001f, + 0.024172001f, 0.028713999f, 0.033408001f, 0.038256001f, 0.043260001f, + 0.048422001f, 0.053743999f, 0.059227999f, 0.064875998f, 0.070689999f, + 0.076672003f, 0.082823999f, 0.084252000f}; + const std::vector<std::vector<float>> kHrirDataShort = + GenerateAudioData(kNumFirstOrderAmbisonicChannels, kFramesPerBuffer - 1); + + AudioBuffer sh_hrirs(kHrirDataShort.size(), kHrirDataShort[0].size()); + sh_hrirs = kHrirDataShort; + + AudioBuffer input(kInputData.size(), kInputData[0].size()); + input = kInputData; + + AudioBuffer output(kNumStereoChannels, kInputData[0].size()); + output.Clear(); + + FftManager fft_manager(kFramesPerBuffer); + AmbisonicBinauralDecoder decoder(sh_hrirs, kFramesPerBuffer, &fft_manager); + decoder.Process(input, &output); + + for (size_t sample = 0; sample < kFramesPerBuffer; ++sample) { + EXPECT_NEAR(kExpectedOutputLeft[sample], output[0][sample], kEpsilonFloat); + EXPECT_NEAR(kExpectedOutputRight[sample], output[1][sample], kEpsilonFloat); + } +} + +// Tests whether binaural docoding of Ambisonic input using HRIR filters +// with the same size as frames per buffer gives correct results. +TEST(AmbisonicBinauralDecoderTest, SameSizeFilterTest) { + const std::vector<std::vector<float>> kInputData = + GenerateAudioData(kNumFirstOrderAmbisonicChannels, kFramesPerBuffer); + const std::vector<float> kExpectedOutputLeft = { + 0.0045360001f, 0.0092879999f, 0.014260001f, 0.019455999f, 0.024879999f, + 0.030536000f, 0.036428001f, 0.042560000f, 0.048935998f, 0.055560000f, + 0.062436000f, 0.069568001f, 0.076959997f, 0.084615998f, 0.092540003f, + 0.10073600f, 0.10920800f, 0.11796000f}; + const std::vector<float> kExpectedOutputRight = { + 0.0038880000f, 0.0079199998f, 0.012098000f, 0.016424000f, 0.020900000f, + 0.025528001f, 0.030309999f, 0.035248000f, 0.040344000f, 0.045600001f, + 0.051018000f, 0.056600001f, 0.062348001f, 0.068264000f, 0.074349999f, + 0.080608003f, 0.087040000f, 0.093648002f}; + const std::vector<std::vector<float>> kHrirDataSame = + GenerateAudioData(kNumFirstOrderAmbisonicChannels, kFramesPerBuffer); + + AudioBuffer sh_hrirs(kHrirDataSame.size(), kHrirDataSame[0].size()); + sh_hrirs = kHrirDataSame; + + AudioBuffer input(kInputData.size(), kInputData[0].size()); + input = kInputData; + + AudioBuffer output(kNumStereoChannels, kInputData[0].size()); + output.Clear(); + + FftManager fft_manager(kFramesPerBuffer); + AmbisonicBinauralDecoder decoder(sh_hrirs, kFramesPerBuffer, &fft_manager); + decoder.Process(input, &output); + + for (size_t sample = 0; sample < kFramesPerBuffer; ++sample) { + EXPECT_NEAR(kExpectedOutputLeft[sample], output[0][sample], kEpsilonFloat); + EXPECT_NEAR(kExpectedOutputRight[sample], output[1][sample], kEpsilonFloat); + } +} + +// Tests whether binaural docoding of Ambisonic input using long HRIR filters +// (longer than the number of frames per buffer) gives correct results. +TEST(AmbisonicBinauralDecoderTest, LongSizeFilterTest) { + const std::vector<std::vector<float>> kInputData = + GenerateAudioData(kNumFirstOrderAmbisonicChannels, kFramesPerBuffer); + const std::vector<float> kExpectedOutputLeft = { + 0.0047880001f, 0.0097980006f, 0.015034000f, 0.020500001f, 0.026200000f, + 0.032138001f, 0.038318001f, 0.044744000f, 0.051419999f, 0.058350001f, + 0.065537997f, 0.072987996f, 0.080704004f, 0.088689998f, 0.096950002f, + 0.10548800f, 0.11430800f, 0.12341400f}; + const std::vector<float> kExpectedOutputRight = { + 00.0041040001f, 0.0083560003f, 0.012758000f, 0.017312000f, 0.022020001f, + 0.026883999f, 0.031906001f, 0.037087999f, 0.042431999f, 0.047940001f, + 0.053613998f, 0.059455998f, 0.065467998f, 0.071652003f, 0.078010000f, + 0.084543996f, 0.091256000f, 0.098148003f}; + const std::vector<std::vector<float>> kHrirDataLong = + GenerateAudioData(kNumFirstOrderAmbisonicChannels, kFramesPerBuffer + 1); + + AudioBuffer sh_hrirs(kHrirDataLong.size(), kHrirDataLong[0].size()); + sh_hrirs = kHrirDataLong; + + AudioBuffer input(kInputData.size(), kInputData[0].size()); + input = kInputData; + + AudioBuffer output(kNumStereoChannels, kInputData[0].size()); + output.Clear(); + + FftManager fft_manager(kFramesPerBuffer); + AmbisonicBinauralDecoder decoder(sh_hrirs, kFramesPerBuffer, &fft_manager); + decoder.Process(input, &output); + + for (size_t sample = 0; sample < kFramesPerBuffer; ++sample) { + EXPECT_NEAR(kExpectedOutputLeft[sample], output[0][sample], kEpsilonFloat); + EXPECT_NEAR(kExpectedOutputRight[sample], output[1][sample], kEpsilonFloat); + } +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/ambisonics/ambisonic_codec.h b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/ambisonic_codec.h new file mode 100644 index 000000000..546a88470 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/ambisonic_codec.h @@ -0,0 +1,62 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_AMBISONICS_AMBISONIC_CODEC_H_ +#define RESONANCE_AUDIO_AMBISONICS_AMBISONIC_CODEC_H_ + +#include <vector> + +#include "base/audio_buffer.h" +#include "base/spherical_angle.h" + +namespace vraudio { +// An encoder/decoder for ambisonic sound fields. It supports variable ambisonic +// order, ACN channel sequencing and SN3D normalization. +class AmbisonicCodec { + public: + virtual ~AmbisonicCodec() {} + // Encodes the given buffer of decoded vectors (unencoded data). + // + // @param input |AudioBuffer| of decoded (unencoded) data. + // @param output |AudioBuffer| to store the encoded data. + virtual void EncodeBuffer(const AudioBuffer& input, AudioBuffer* output) = 0; + + // Decodes the given |AudioBuffer| of planar (non-interleaved) encoded data. + // + // @param input |AudioBuffer| of encoded data. + // @param output |AudioBuffer| to store the decoded data. + virtual void DecodeBuffer(const AudioBuffer& input, AudioBuffer* output) = 0; + + // Returns the maximum periphonic ambisonic order that this codec supports. + virtual int ambisonic_order() const = 0; + + // Returns the number of angles for this codec. + virtual size_t num_angles() const = 0; + + // Returns the maximum number of spherical harmonics computed by this codec. + virtual size_t num_spherical_harmonics() const = 0; + + // Returns the spherical angles currently used to define the encoder/decoder + // matrices. + virtual const std::vector<SphericalAngle>& angles() const = 0; + + // Sets the spherical angles used to build the encoder/decoder matrices. + virtual void set_angles(const std::vector<SphericalAngle>& angles) = 0; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_AMBISONICS_AMBISONIC_CODEC_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/ambisonics/ambisonic_codec_impl.h b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/ambisonic_codec_impl.h new file mode 100644 index 000000000..03a83140f --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/ambisonic_codec_impl.h @@ -0,0 +1,295 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_AMBISONICS_AMBISONIC_CODEC_IMPL_H_ +#define RESONANCE_AUDIO_AMBISONICS_AMBISONIC_CODEC_IMPL_H_ + +#include <cmath> +#include <vector> + +#include "Eigen/Dense" +#include "ambisonics/ambisonic_codec.h" +#include "ambisonics/associated_legendre_polynomials_generator.h" +#include "ambisonics/utils.h" +#include "base/audio_buffer.h" +#include "base/constants_and_types.h" +#include "base/logging.h" +#include "base/spherical_angle.h" +#include "utils/pseudoinverse.h" + +namespace vraudio { +// An encoder/decoder for ambisonic sound fields. It supports variable ambisonic +// order, ACN channel sequencing and SN3D normalization. +// +// @tparam NumAngles Used to fix the number of angles to be encoded/decoded at +// compile-time; use |Eigen::Dynamic| to indicate run-time variability. +// @tparam NumSphericalHarmonics Used to fix the number of spherical harmonic +// components at compile time; use |Eigen::Dynamic| to indicate run-time +// variability. +template <int NumAngles = Eigen::Dynamic, + int NumSphericalHarmonics = Eigen::Dynamic> +class AmbisonicCodecImpl : public AmbisonicCodec { + public: + // Spherical harmonics encoder matrix. + typedef Eigen::Matrix<float, NumSphericalHarmonics, NumAngles> EncoderMatrix; + // Spherical harmonics decoder matrix. + typedef Eigen::Matrix<float, NumAngles, NumSphericalHarmonics> DecoderMatrix; + // Spherical harmonics encoding of a frame or collection of frames (i.e., a + // vector of spherical harmonics). + typedef Eigen::Matrix<float, NumSphericalHarmonics, 1> EncodedVector; + // Decoded sequence of values for each angle / mono frame (i.e., a vector with + // a decoded mono frame for each angle). + typedef Eigen::Matrix<float, NumAngles, 1> DecodedVector; + + // Creates a codec with the given |ambisonic_order| and spherical |angles| to + // compute encoder/decoder matrices. + AmbisonicCodecImpl(int ambisonic_order, + const std::vector<SphericalAngle>& angles); + + // Implements |AmbisonicCodec|. + void EncodeBuffer(const AudioBuffer& input, AudioBuffer* output) override; + void DecodeBuffer(const AudioBuffer& input, AudioBuffer* output) override; + int ambisonic_order() const override; + size_t num_angles() const override; + size_t num_spherical_harmonics() const override; + const std::vector<SphericalAngle>& angles() const override; + void set_angles(const std::vector<SphericalAngle>& angles) override; + + // Encodes the given vector. + + void Encode(const Eigen::Ref<const Eigen::MatrixXf> decoded_vector, + Eigen::Ref<Eigen::MatrixXf> encoded_vector); + + // Decodes the given vector. + + void Decode(const Eigen::Ref<const Eigen::MatrixXf> encoded_vector, + Eigen::Ref<Eigen::MatrixXf> decoded_vector); + + // Gets the ambisonic sound field encoder matrix. + const Eigen::Ref<const Eigen::MatrixXf> GetEncoderMatrix(); + + // Gets the ambisonic sound field decoder matrix. + const Eigen::Ref<const Eigen::MatrixXf> GetDecoderMatrix(); + + // Necessary due to Eigen's alignment requirements on some platforms. + EIGEN_MAKE_ALIGNED_OPERATOR_NEW + + private: + // Returns the unnormalized spherical harmonic + // Y_degree^order(azimuth, elevation). + float UnnormalizedSphericalHarmonic(int degree, int order, + const SphericalAngle& angle) const; + + // The maximum-ordered ambisonic sound field handled by this codec. In the + // case of a periphonic codec, this is the order of the ambisonic sound field. + const int ambisonic_order_; + // Spherical angles used to compute spherical harmonics. For example, for a + // decoder, virtual loudspeaker positions; for an encoder, the position(s) of + // virtual sources relative to the listener. + std::vector<SphericalAngle> angles_; + // Current spherical harmonics encoder matrix if encoder_matrix_invalid_ is + // false. + EncoderMatrix encoder_matrix_; + // True if encoder_matrix_ needs to be recomputed. + bool encoder_matrix_invalid_; + // Current spherical harmonics decoder matrix if encoder_matrix_invalid_ is + // false. + DecoderMatrix decoder_matrix_; + // True if decoder_matrix_ needs to be recomputed. + bool decoder_matrix_invalid_; + // The associated Legendre polynomial generator for this codec. + AssociatedLegendrePolynomialsGenerator alp_generator_; + // Temporary storage for associated Legendre polynomials generated. + std::vector<float> associated_legendre_polynomials_temp_; +}; + +template <int NumAngles, int NumSphericalHarmonics> +AmbisonicCodecImpl<NumAngles, NumSphericalHarmonics>::AmbisonicCodecImpl( + int ambisonic_order, const std::vector<SphericalAngle>& angles) + : ambisonic_order_(ambisonic_order), + alp_generator_(ambisonic_order, false, false) { + DCHECK_GE(ambisonic_order_, 0); + set_angles(angles); +} + +template <int NumAngles, int NumSphericalHarmonics> +void AmbisonicCodecImpl<NumAngles, NumSphericalHarmonics>::Encode( + const Eigen::Ref<const Eigen::MatrixXf> decoded_vector, + Eigen::Ref<Eigen::MatrixXf> encoded_vector) { + encoded_vector.noalias() = GetEncoderMatrix() * decoded_vector; +} + +template <int NumAngles, int NumSphericalHarmonics> +void AmbisonicCodecImpl<NumAngles, NumSphericalHarmonics>::Decode( + const Eigen::Ref<const Eigen::MatrixXf> encoded_vector, + Eigen::Ref<Eigen::MatrixXf> decoded_vector) { + decoded_vector.noalias() = GetDecoderMatrix() * encoded_vector; +} + +template <int NumAngles, int NumSphericalHarmonics> +void AmbisonicCodecImpl<NumAngles, NumSphericalHarmonics>::EncodeBuffer( + const AudioBuffer& input, AudioBuffer* output) { + CHECK(output); + CHECK_EQ(input.num_channels(), num_angles()); + CHECK_EQ(output->num_channels(), num_spherical_harmonics()); + CHECK_EQ(input.num_frames(), output->num_frames()); + + Eigen::Map<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, + Eigen::RowMajor>, + Eigen::Aligned> + unencoded_buffer(&input[0][0], num_angles(), output->GetChannelStride()); + + Eigen::Map< + Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>, + Eigen::Aligned> + encoded_buffer(&(*output)[0][0], num_spherical_harmonics(), + input.GetChannelStride()); + + encoded_buffer.noalias() = GetEncoderMatrix() * unencoded_buffer; +} + +template <int NumAngles, int NumSphericalHarmonics> +void AmbisonicCodecImpl<NumAngles, NumSphericalHarmonics>::DecodeBuffer( + const AudioBuffer& input, AudioBuffer* output) { + CHECK(output); + CHECK_EQ(input.num_channels(), num_spherical_harmonics()); + CHECK_EQ(output->num_channels(), num_angles()); + CHECK_EQ(input.num_frames(), output->num_frames()); + + Eigen::Map<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, + Eigen::RowMajor>, + Eigen::Aligned> + encoded_buffer(&input[0][0], num_spherical_harmonics(), + input.GetChannelStride()); + + Eigen::Map< + Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>, + Eigen::Aligned> + decoded_buffer(&(*output)[0][0], num_angles(), + output->GetChannelStride()); + + decoded_buffer.noalias() = GetDecoderMatrix() * encoded_buffer; +} + +template <int NumAngles, int NumSphericalHarmonics> +const Eigen::Ref<const Eigen::MatrixXf> +AmbisonicCodecImpl<NumAngles, NumSphericalHarmonics>::GetEncoderMatrix() { + if (encoder_matrix_invalid_) { + encoder_matrix_ = EncoderMatrix( + GetNumPeriphonicComponents(ambisonic_order_), angles_.size()); + for (int col = 0; col < encoder_matrix_.cols(); col++) { + const SphericalAngle& angle = angles_[col]; + associated_legendre_polynomials_temp_ = + alp_generator_.Generate(std::sin(angle.elevation())); + // Compute the actual spherical harmonics using the generated polynomials. + for (int degree = 0; degree <= ambisonic_order_; degree++) { + for (int order = -degree; order <= degree; order++) { + const int row = AcnSequence(degree, order); + if (row == -1) { + // Skip this spherical harmonic. + continue; + } + encoder_matrix_(row, col) = + Sn3dNormalization(degree, order) * + UnnormalizedSphericalHarmonic(degree, order, angle); + } + } + } + encoder_matrix_invalid_ = false; + } + return encoder_matrix_; +} + +template <int NumAngles, int NumSphericalHarmonics> +const Eigen::Ref<const Eigen::MatrixXf> +AmbisonicCodecImpl<NumAngles, NumSphericalHarmonics>::GetDecoderMatrix() { + if (decoder_matrix_invalid_) { + decoder_matrix_ = Pseudoinverse<Eigen::MatrixXf>(GetEncoderMatrix()); + // Condition number of the encoding/decoding matrices. We use the fact that + // the decoding matrix is already a (pseudo)-inverse of the encoding matrix. + const float condition_number = + static_cast<float>(GetEncoderMatrix().norm() * decoder_matrix_.norm()); + const float num_rows = static_cast<float>(GetEncoderMatrix().rows()); + const float num_cols = static_cast<float>(GetEncoderMatrix().cols()); + if (condition_number > + 1.0f / (std::max(num_rows, num_cols) * kEpsilonFloat)) { + LOG(WARNING) << "Ambisonic decoding matrix is ill-conditioned. Results " + << "may be inaccurate."; + } + decoder_matrix_invalid_ = false; + } + return decoder_matrix_; +} + +template <int NumAngles, int NumSphericalHarmonics> +int AmbisonicCodecImpl<NumAngles, NumSphericalHarmonics>::ambisonic_order() + const { + return ambisonic_order_; +} + +template <int NumAngles, int NumSphericalHarmonics> +size_t AmbisonicCodecImpl<NumAngles, NumSphericalHarmonics>::num_angles() + const { + return angles_.size(); +} + +template <int NumAngles, int NumSphericalHarmonics> +size_t AmbisonicCodecImpl< + NumAngles, NumSphericalHarmonics>::num_spherical_harmonics() const { + // Return the worst-case scenario (the number of coefficients for a + // periphonic sound field). + return GetNumPeriphonicComponents(ambisonic_order_); +} + +template <int NumAngles, int NumSphericalHarmonics> +const std::vector<SphericalAngle>& +AmbisonicCodecImpl<NumAngles, NumSphericalHarmonics>::angles() const { + return angles_; +} + +template <int NumAngles, int NumSphericalHarmonics> +void AmbisonicCodecImpl<NumAngles, NumSphericalHarmonics>::set_angles( + const std::vector<SphericalAngle>& angles) { + CHECK_GT(angles.size(), 0); + + angles_ = angles; + encoder_matrix_invalid_ = decoder_matrix_invalid_ = true; +} + +template <int NumAngles, int NumSphericalHarmonics> +float AmbisonicCodecImpl<NumAngles, NumSphericalHarmonics>:: + UnnormalizedSphericalHarmonic(int degree, int order, + const SphericalAngle& angle) const { + const float last_term = + (order >= 0) ? std::cos(static_cast<float>(order) * angle.azimuth()) + : std::sin(static_cast<float>(-order) * angle.azimuth()); + return associated_legendre_polynomials_temp_[alp_generator_.GetIndex( + degree, std::abs(order))] * + last_term; +} + +// Codec for a single source. +template <int NumSphericalHarmonics = Eigen::Dynamic> +using MonoAmbisonicCodec = AmbisonicCodecImpl<1, NumSphericalHarmonics>; + +// Codec for a N-speaker first-order periphonic setup. +template <int NumAngles> +using FirstOrderPeriphonicAmbisonicCodec = + AmbisonicCodecImpl<NumAngles, GetNumPeriphonicComponentsStatic<1>::value>; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_AMBISONICS_AMBISONIC_CODEC_IMPL_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/ambisonics/ambisonic_codec_impl_test.cc b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/ambisonic_codec_impl_test.cc new file mode 100644 index 000000000..c7f0900be --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/ambisonic_codec_impl_test.cc @@ -0,0 +1,354 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "ambisonics/ambisonic_codec_impl.h" + +#include <cmath> +#include <memory> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/audio_buffer.h" +#include "base/constants_and_types.h" +#include "base/misc_math.h" + +namespace vraudio { + +namespace { + +const int kCubeNumVertices = 8; +const float kCubeVertices[kCubeNumVertices][3] = { + {-1.0f, 1.0f, -1.0f}, {1.0f, 1.0f, -1.0f}, {-1.0f, 1.0f, 1.0f}, + {1.0f, 1.0f, 1.0f}, {-1.0f, -1.0f, -1.0f}, {1.0f, -1.0f, -1.0f}, + {-1.0f, -1.0f, 1.0f}, {1.0f, -1.0f, 1.0f}}; + +// Generates a third-order B-format encoder matrix. +AmbisonicCodecImpl<>::EncoderMatrix GenerateThirdOrderBFormatEncoderMatrix( + const SphericalAngle& spherical_angle) { + const size_t kNumSphericalHarmonics = GetNumPeriphonicComponents(3); + const size_t kNumAngles = 1; + AmbisonicCodecImpl<>::EncoderMatrix encoder_matrix(kNumSphericalHarmonics, + kNumAngles); + const float azimuth_rad = spherical_angle.azimuth(); + const float elevation_rad = spherical_angle.elevation(); + encoder_matrix << 1.0f, std::cos(elevation_rad) * std::sin(azimuth_rad), + std::sin(elevation_rad), std::cos(elevation_rad) * std::cos(azimuth_rad), + kSqrtThree / 2.0f * std::cos(elevation_rad) * std::cos(elevation_rad) * + std::sin(2.0f * azimuth_rad), + kSqrtThree / 2.0f * std::sin(2.0f * elevation_rad) * + std::sin(azimuth_rad), + 0.5f * (3.0f * std::sin(elevation_rad) * std::sin(elevation_rad) - 1.0f), + kSqrtThree / 2.0f * std::sin(2.0f * elevation_rad) * + std::cos(azimuth_rad), + kSqrtThree / 2.0f * std::cos(elevation_rad) * std::cos(elevation_rad) * + std::cos(2.0f * azimuth_rad), + std::sqrt(5.0f / 8.0f) * IntegerPow(std::cos(elevation_rad), 3) * + std::sin(3.0f * azimuth_rad), + std::sqrt(15.0f) / 2.0f * std::sin(elevation_rad) * + std::cos(elevation_rad) * std::cos(elevation_rad) * + std::sin(2.0f * azimuth_rad), + std::sqrt(3.0f / 8.0f) * std::cos(elevation_rad) * + (5.0f * std::sin(elevation_rad) * std::sin(elevation_rad) - 1.0f) * + std::sin(azimuth_rad), + 0.5f * std::sin(elevation_rad) * + (5.0f * std::sin(elevation_rad) * std::sin(elevation_rad) - 3.0f), + std::sqrt(3.0f / 8.0f) * std::cos(elevation_rad) * + (5.0f * std::sin(elevation_rad) * std::sin(elevation_rad) - 1.0f) * + std::cos(azimuth_rad), + std::sqrt(15.0f) / 2.0f * std::sin(elevation_rad) * + std::cos(elevation_rad) * std::cos(elevation_rad) * + std::cos(2.0f * azimuth_rad), + std::sqrt(5.0f / 8.0f) * IntegerPow(std::cos(elevation_rad), 3) * + std::cos(3.0f * azimuth_rad); + return encoder_matrix; +} + +class AmbisonicCodecTest : public ::testing::Test { + protected: + AmbisonicCodecTest() { + for (int i = 0; i < kCubeNumVertices; i++) { + const float azimuth_rad = + atan2f(kCubeVertices[i][2], kCubeVertices[i][0]); + const float elevation_rad = + std::asin(kCubeVertices[i][1] / + std::sqrt(kCubeVertices[i][0] * kCubeVertices[i][0] + + kCubeVertices[i][1] * kCubeVertices[i][1] + + kCubeVertices[i][2] * kCubeVertices[i][2])); + + cube_angles_.push_back(SphericalAngle(azimuth_rad, elevation_rad)); + } + + codec_first_order_cube_ = std::unique_ptr<AmbisonicCodecImpl<>>( + new AmbisonicCodecImpl<>(1, cube_angles_)); + } + + std::unique_ptr<AmbisonicCodecImpl<>> codec_first_order_cube_; + std::vector<SphericalAngle> cube_angles_; +}; + +// Tests that encoder and decoder matrices are ~inverses of each other. +TEST_F(AmbisonicCodecTest, EncoderDecoderIdentity) { + const auto encoder_matrix = codec_first_order_cube_->GetEncoderMatrix(); + const auto decoder_matrix = codec_first_order_cube_->GetDecoderMatrix(); + + const auto should_be_identity = encoder_matrix * decoder_matrix; + EXPECT_TRUE(should_be_identity.isApprox( + decltype(should_be_identity)::Identity(should_be_identity.rows(), + should_be_identity.cols()), + kEpsilonFloat)) + << "Matrix should be within " << kEpsilonFloat + << " of an identity matrix: \n" + << should_be_identity; +} + +// Tests that Encode and Decode are ~inverse operations. +TEST_F(AmbisonicCodecTest, EncodeDecodeIsInverse) { + AmbisonicCodecImpl<>::DecodedVector unencoded_vector(8); + unencoded_vector << 1, 2, 3, 4, 5, 6, 7, 8; + AmbisonicCodecImpl<>::EncodedVector encoded_vector(4); + codec_first_order_cube_->Encode(unencoded_vector, encoded_vector); + AmbisonicCodecImpl<>::DecodedVector decoded_vector(8); + codec_first_order_cube_->Decode(encoded_vector, decoded_vector); + + EXPECT_TRUE(unencoded_vector.isApprox(decoded_vector, kEpsilonFloat)) + << "Decoded vector should be within " << kEpsilonFloat << " of: \n" + << unencoded_vector; +} + +// Tests that EncodeBuffer and Decode (over a buffer) are ~inverse operations. +TEST_F(AmbisonicCodecTest, EncodeBufferDecodeVectorsIsInverse) { + const int kNumElements = 256; + const int kNumSphericalHarmonics = GetNumPeriphonicComponentsStatic<1>::value; + + AudioBuffer unencoded_buffer(kCubeNumVertices, kNumElements); + // Produce a buffer of successive numbers between -1 and 1. + for (int element = 0; element < kNumElements; ++element) { + for (int angle = 0; angle < kCubeNumVertices; ++angle) { + unencoded_buffer[angle][element] = + static_cast<float>(element * kCubeNumVertices + angle) / + (0.5f * kNumElements * kCubeNumVertices) - + 1.0f; + } + } + + AudioBuffer encoded_buffer(kNumSphericalHarmonics, kNumElements); + codec_first_order_cube_->EncodeBuffer(unencoded_buffer, &encoded_buffer); + + // Verify the encoded buffer and decoded vectors are ~identical. + for (int element = 0; element < kNumElements; ++element) { + AmbisonicCodecImpl<>::EncodedVector encoded_vector(kNumSphericalHarmonics); + for (int harmonic = 0; harmonic < kNumSphericalHarmonics; ++harmonic) { + encoded_vector[harmonic] = encoded_buffer[harmonic][element]; + } + AmbisonicCodecImpl<>::DecodedVector decoded_vector(kCubeNumVertices); + codec_first_order_cube_->Decode(encoded_vector, decoded_vector); + for (int angle = 0; angle < kCubeNumVertices; ++angle) { + EXPECT_NEAR(unencoded_buffer[angle][element], decoded_vector[angle], + kEpsilonFloat); + } + } +} + +// Tests that Encode (over a buffer) and DecodeBuffer are ~inverse operations. +TEST_F(AmbisonicCodecTest, EncodeVectorsDecodeBufferIsInverse) { + const int kNumElements = 256; + const int kNumSphericalHarmonics = GetNumPeriphonicComponentsStatic<1>::value; + + AudioBuffer unencoded_buffer(kCubeNumVertices, kNumElements); + // Produce a buffer of successive numbers between -1 and 1. + for (int element = 0; element < kNumElements; ++element) { + for (int angle = 0; angle < kCubeNumVertices; ++angle) { + unencoded_buffer[angle][element] = + static_cast<float>(element * kCubeNumVertices + angle) / + (0.5f * kNumElements * kCubeNumVertices) - + 1.0f; + } + } + + AudioBuffer encoded_buffer(kNumSphericalHarmonics, kNumElements); + // Produce the encoded version of unencoded_buffer. + for (int element = 0; element < kNumElements; ++element) { + AmbisonicCodecImpl<>::DecodedVector unencoded_vector(kCubeNumVertices); + for (int angle = 0; angle < kCubeNumVertices; ++angle) { + unencoded_vector[angle] = unencoded_buffer[angle][element]; + } + AmbisonicCodecImpl<>::EncodedVector encoded_vector(kNumSphericalHarmonics); + codec_first_order_cube_->Encode(unencoded_vector, encoded_vector); + for (int harmonic = 0; harmonic < kNumSphericalHarmonics; ++harmonic) { + encoded_buffer[harmonic][element] = encoded_vector[harmonic]; + } + } + + AudioBuffer decoded_buffer(kCubeNumVertices, kNumElements); + codec_first_order_cube_->DecodeBuffer(encoded_buffer, &decoded_buffer); + // Verify the decoded buffer and unencoded buffer are ~identical. + for (int element = 0; element < kNumElements; ++element) { + for (int angle = 0; angle < kCubeNumVertices; ++angle) { + EXPECT_NEAR(unencoded_buffer[angle][element], + decoded_buffer[angle][element], kEpsilonFloat); + } + } +} + +// Tests that EncodeBuffer and DecodeBuffer are ~inverse operations. +TEST_F(AmbisonicCodecTest, EncodeBufferDecodeBufferIsInverse) { + const int kNumElements = 256; + const int kNumSphericalHarmonics = GetNumPeriphonicComponentsStatic<1>::value; + + AudioBuffer unencoded_buffer(kCubeNumVertices, kNumElements); + // Produce a buffer of successive numbers between -1 and 1. + for (int element = 0; element < kNumElements; ++element) { + for (int angle = 0; angle < kCubeNumVertices; ++angle) { + unencoded_buffer[angle][element] = + static_cast<float>(element * kCubeNumVertices + angle) / + (0.5f * kNumElements * kCubeNumVertices) - + 1.0f; + } + } + + + + AudioBuffer encoded_buffer(kNumSphericalHarmonics, kNumElements); + // Produce the encoded version of unencoded_buffer. + for (int element = 0; element < kNumElements; ++element) { + AmbisonicCodecImpl<>::DecodedVector unencoded_vector(kCubeNumVertices); + for (int angle = 0; angle < kCubeNumVertices; ++angle) { + unencoded_vector[angle] = unencoded_buffer[angle][element]; + } + AmbisonicCodecImpl<>::EncodedVector encoded_vector(kNumSphericalHarmonics); + codec_first_order_cube_->Encode(unencoded_vector, encoded_vector); + for (int harmonic = 0; harmonic < kNumSphericalHarmonics; ++harmonic) { + encoded_buffer[harmonic][element] = encoded_vector[harmonic]; + } + } + + AudioBuffer decoded_buffer(kCubeNumVertices, kNumElements); + codec_first_order_cube_->DecodeBuffer(encoded_buffer, &decoded_buffer); + // Verify the decoded buffer and unencoded buffer are ~identical. + for (int element = 0; element < kNumElements; ++element) { + for (int angle = 0; angle < kCubeNumVertices; ++angle) { + EXPECT_NEAR(unencoded_buffer[angle][element], + decoded_buffer[angle][element], kEpsilonFloat); + } + } +} + +// Tests that third-order encoding coefficients produced by Codec are correct. +TEST_F(AmbisonicCodecTest, ThirdOrderEncodingCoefficients) { + const int kNumSphericalHarmonics = GetNumPeriphonicComponentsStatic<3>::value; + + const float kAzimuthStart = -180.0f; + const float kAzimuthStop = 180.0f; + const float kAzimuthStep = 10.0f; + for (float azimuth = kAzimuthStart; azimuth <= kAzimuthStop; + azimuth += kAzimuthStep) { + const float kElevationStart = -90.0f; + const float kElevationStop = 90.0f; + const float kElevationStep = 10.0f; + for (float elevation = kElevationStart; elevation <= kElevationStop; + elevation += kElevationStep) { + const int kAmbisonicOrder = 3; + + const SphericalAngle kSphericalAngle = + SphericalAngle::FromDegrees(azimuth, elevation); + AmbisonicCodecImpl<> mono_codec(kAmbisonicOrder, {kSphericalAngle}); + + AmbisonicCodecImpl<>::DecodedVector kUnencodedVector(1); + kUnencodedVector << 0.12345f; + AmbisonicCodecImpl<>::EncodedVector codec_encoded_vector( + kNumSphericalHarmonics); + mono_codec.Encode(kUnencodedVector, codec_encoded_vector); + const AmbisonicCodecImpl<>::EncoderMatrix + expected_b_format_encoder_matrix = + GenerateThirdOrderBFormatEncoderMatrix(kSphericalAngle); + const AmbisonicCodecImpl<>::EncodedVector expected_encoded_vector = + static_cast<AmbisonicCodecImpl<>::EncodedVector>( + expected_b_format_encoder_matrix * kUnencodedVector); + ASSERT_TRUE( + codec_encoded_vector.isApprox(expected_encoded_vector, kEpsilonFloat)) + << "Codec encoded vector: \n" + << codec_encoded_vector << "\n should be within " << kEpsilonFloat + << " of expected: \n" + << expected_encoded_vector; + } + } +} + +// Tests that Sphere16 decoding matrix (ACN/SN3D) coefficients produced by +// Codec +// are correct. +TEST_F(AmbisonicCodecTest, Sphere16DecoderCoefficients) { + // Expected decoder coefficients for the Sphere16 setup (ACN/SN3D) obtained + // using //matlab/ambix/ambdecodematrix.m. + const std::vector<float> kExpectedDecoderCoefficients{ + 0.0625000000000001f, 0.0625000000000000f, 0.0625000000000000f, + 0.0625000000000000f, 0.0625000000000000f, 0.0625000000000000f, + 0.0625000000000000f, 0.0625000000000000f, 0.0625000000000000f, + 0.0625000000000000f, 0.0625000000000000f, 0.0625000000000000f, + 0.0625000000000000f, 0.0625000000000000f, 0.0625000000000000f, + 0.0625000000000000f, -1.94257951433956e-17f, 0.128971506904330f, + 0.182393254223798f, 0.128971506904330f, 1.73262494006158e-16f, + -0.128971506904330f, -0.182393254223798f, -0.128971506904330f, + -2.79581233842125e-16f, 0.116817928199481f, -1.55203460683255e-16f, + -0.116817928199482f, 0.0826027491940168f, 0.0826027491940169f, + -0.0826027491940164f, -0.0826027491940165f, -0.0440164745323702f, + 0.0440164745323702f, -0.0440164745323703f, 0.0440164745323702f, + -0.0440164745323703f, 0.0440164745323702f, -0.0440164745323702f, + 0.0440164745323703f, 0.146497161016369f, 0.146497161016369f, + 0.146497161016369f, 0.146497161016369f, -0.146497161016369f, + -0.146497161016369f, -0.146497161016369f, -0.146497161016369f, + 0.182393254223799f, 0.128971506904330f, 7.26758979552257e-17f, + -0.128971506904330f, -0.182393254223798f, -0.128971506904330f, + -5.02371557522155e-17f, 0.128971506904330f, 0.116817928199482f, + 6.83999538850465e-17f, -0.116817928199482f, -3.47728677633385e-17f, + 0.0826027491940167f, -0.0826027491940166f, -0.0826027491940166f, + 0.0826027491940166f}; + const int kAmbisonicOrder = 1; + const int kNumChannels = 4; + const int kNumVirtualSpeakers = 16; + std::vector<SphericalAngle> sphere16_angles{ + SphericalAngle::FromDegrees(0.0f, -13.6f), + SphericalAngle::FromDegrees(45.0f, 13.6f), + SphericalAngle::FromDegrees(90.0f, -13.6f), + SphericalAngle::FromDegrees(135.0f, 13.6f), + SphericalAngle::FromDegrees(180.0f, -13.6f), + SphericalAngle::FromDegrees(-135.0f, 13.6f), + SphericalAngle::FromDegrees(-90.0f, -13.6f), + SphericalAngle::FromDegrees(-45.0f, 13.6f), + SphericalAngle::FromDegrees(0.0f, 51.5f), + SphericalAngle::FromDegrees(90.0f, 51.5f), + SphericalAngle::FromDegrees(180.0f, 51.5f), + SphericalAngle::FromDegrees(-90.0f, 51.5f), + SphericalAngle::FromDegrees(45.0f, -51.5f), + SphericalAngle::FromDegrees(135.0f, -51.5f), + SphericalAngle::FromDegrees(-135.0f, -51.5f), + SphericalAngle::FromDegrees(-45.0f, -51.5f)}; + + std::unique_ptr<AmbisonicCodecImpl<>> codec_first_order_sphere16( + new AmbisonicCodecImpl<>(kAmbisonicOrder, sphere16_angles)); + const AmbisonicCodecImpl<>::DecoderMatrix decoder_matrix = + codec_first_order_sphere16->GetDecoderMatrix(); + // Check if the size of the decoding matrix is correct. + ASSERT_EQ(decoder_matrix.size(), kNumVirtualSpeakers * kNumChannels); + // Check each coefficient against MATLAB. + for (size_t i = 0; i < kExpectedDecoderCoefficients.size(); ++i) { + EXPECT_NEAR(kExpectedDecoderCoefficients[i], decoder_matrix(i), + kEpsilonFloat); + } +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/ambisonics/ambisonic_lookup_table.cc b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/ambisonic_lookup_table.cc new file mode 100644 index 000000000..2a2bc1b68 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/ambisonic_lookup_table.cc @@ -0,0 +1,231 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "ambisonics/ambisonic_lookup_table.h" + +#include "ambisonics/ambisonic_spread_coefficients.h" +#include "ambisonics/associated_legendre_polynomials_generator.h" +#include "ambisonics/utils.h" +#include "base/constants_and_types.h" +#include "base/misc_math.h" + + +namespace vraudio { + +namespace { + +// Number of azimuth angles to store in the pre-computed encoder lookup table +// (for 0 - 90 degrees using 1 degree increments). +const size_t kNumAzimuths = 91; + +// Number of elevation angles to store in the pre-computed encoder lookup table +// (for 0 - 90 degrees using 1 degree increments). +const size_t kNumElevations = 91; + +// Number of Cartesian axes for which to compute spherical harmonic symmetries. +const size_t kNumAxes = 3; + +// Minimum angular source spreads at different orders for which we start to +// apply spread gain correction coefficients. Array index corresponds to +// ambisonic order. For more information about sound source spread control, +// please refer to the Matlab code and the corresponding paper. +const int kMinSpreads[kMaxSupportedAmbisonicOrder + 1] = {361, 54, 40, 31}; + +// Spread coefficients are stored sequentially in a 1-d table. Therefore, to +// access coefficients for the required ambisonic order we need to apply offsets +// which are equal to the total number of coefficients for previous orders. For +// example, the total number of coefficient in each ambisonic order 'n' is +// equal to the number of unique orders multiplied by number of unique spreads, +// i.e.: (n + 1) * (360 - kMinSpreads[n] + 1). +const int kSpreadCoeffOffsets[kMaxSupportedAmbisonicOrder + 1] = {0, 1, 615, + 1578}; + +size_t GetEncoderTableIndex(size_t i, size_t j, size_t k, size_t width, + size_t depth) { + return i * width * depth + j * depth + k; +} + +size_t GetSymmetriesTableIndex(size_t i, size_t j, size_t width) { + return i * width + j; +} + +int GetSpreadTableIndex(int ambisonic_order, float source_spread_deg) { + // Number of unique ambisonic orders in the sound field. For example, in a + // first order sound field we have components of order 0 and 1. + const int num_unique_orders = ambisonic_order + 1; + return kSpreadCoeffOffsets[ambisonic_order] + + num_unique_orders * (static_cast<int>(source_spread_deg) - + kMinSpreads[ambisonic_order]); +} + +} // namespace + +AmbisonicLookupTable::AmbisonicLookupTable(int max_ambisonic_order) + : max_ambisonic_order_(max_ambisonic_order), + max_num_coeffs_in_table_( + GetNumPeriphonicComponents(max_ambisonic_order_) - 1), + encoder_table_(kNumAzimuths * kNumElevations * max_num_coeffs_in_table_), + symmetries_table_(kNumAxes * max_num_coeffs_in_table_) { + DCHECK_GE(max_ambisonic_order_, 0); + DCHECK_LE(max_ambisonic_order_, kMaxSupportedAmbisonicOrder); + ComputeEncoderTable(); + ComputeSymmetriesTable(); +} + +void AmbisonicLookupTable::GetEncodingCoeffs( + int ambisonic_order, const SphericalAngle& source_direction, + float source_spread_deg, std::vector<float>* encoding_coeffs) const { + + DCHECK_GE(ambisonic_order, 0); + DCHECK_LE(ambisonic_order, kMaxSupportedAmbisonicOrder); + DCHECK_GE(source_spread_deg, 0.0f); + DCHECK_LE(source_spread_deg, 360.0f); + DCHECK(encoding_coeffs); + // Raw coefficients are stored only for ambisonic orders 1 and up, since 0th + // order raw coefficient is always 1. + const size_t num_raw_coeffs = GetNumPeriphonicComponents(ambisonic_order) - 1; + // The actual number of returned Ambisonic coefficients is therefore + // |num_raw_coeffs + 1|. + DCHECK_EQ(encoding_coeffs->size(), num_raw_coeffs + 1); + DCHECK_GE(source_direction.azimuth(), -kPi); + DCHECK_LE(source_direction.azimuth(), kTwoPi); + DCHECK_GE(source_direction.elevation(), -kHalfPi); + DCHECK_LE(source_direction.elevation(), kHalfPi); + const int azimuth_deg = + source_direction.azimuth() < kPi + ? static_cast<int>(source_direction.azimuth() * kDegreesFromRadians) + : static_cast<int>(source_direction.azimuth() * kDegreesFromRadians) - + 360; + const int elevation_deg = + static_cast<int>(source_direction.elevation() * kDegreesFromRadians); + const size_t abs_azimuth_deg = static_cast<size_t>(std::abs(azimuth_deg)); + const size_t azimuth_idx = + abs_azimuth_deg > 90 ? 180 - abs_azimuth_deg : abs_azimuth_deg; + const size_t elevation_idx = static_cast<size_t>(std::abs(elevation_deg)); + (*encoding_coeffs)[0] = 1.0f; + for (size_t raw_coeff_idx = 0; raw_coeff_idx < num_raw_coeffs; + ++raw_coeff_idx) { + // Variable to hold information about spherical harmonic phase flip. 1 means + // no flip; -1 means 180 degrees flip. + float flip = 1.0f; + // The following three 'if' statements implement the logic to correct the + // phase of the current spherical harmonic, depending on which quadrant the + // sound source is located in. For more information, please see the Matlab + // code and the corresponding paper. + if (azimuth_deg < 0) { + flip = symmetries_table_[GetSymmetriesTableIndex( + 0, raw_coeff_idx, max_num_coeffs_in_table_)]; + } + if (elevation_deg < 0) { + flip *= symmetries_table_[GetSymmetriesTableIndex( + 1, raw_coeff_idx, max_num_coeffs_in_table_)]; + } + if (abs_azimuth_deg > 90) { + flip *= symmetries_table_[GetSymmetriesTableIndex( + 2, raw_coeff_idx, max_num_coeffs_in_table_)]; + } + const size_t encoder_table_idx = + GetEncoderTableIndex(azimuth_idx, elevation_idx, raw_coeff_idx, + kNumElevations, max_num_coeffs_in_table_); + (*encoding_coeffs)[raw_coeff_idx + 1] = + encoder_table_[encoder_table_idx] * flip; + } + + // If the spread is more than min. theoretical spread for the given + // |ambisonic_order|, multiply the encoding coefficients by the required + // spread control gains from the |kSpreadCoeffs| lookup table. + if (source_spread_deg >= kMinSpreads[ambisonic_order]) { + const int spread_table_idx = + GetSpreadTableIndex(ambisonic_order, source_spread_deg); + (*encoding_coeffs)[0] *= kSpreadCoeffs[spread_table_idx]; + for (size_t coeff = 1; coeff < encoding_coeffs->size(); ++coeff) { + const int current_coefficient_degree = + GetPeriphonicAmbisonicOrderForChannel(coeff); + (*encoding_coeffs)[coeff] *= + kSpreadCoeffs[spread_table_idx + current_coefficient_degree]; + } + } +} + +void AmbisonicLookupTable::ComputeEncoderTable() { + + // Associated Legendre polynomial generator. + AssociatedLegendrePolynomialsGenerator alp_generator( + max_ambisonic_order_, /*condon_shortley_phase=*/false, + /*compute_negative_order=*/false); + // Temporary storage for associated Legendre polynomials generated. + std::vector<float> temp_associated_legendre_polynomials; + for (size_t azimuth_idx = 0; azimuth_idx < kNumAzimuths; ++azimuth_idx) { + for (size_t elevation_idx = 0; elevation_idx < kNumElevations; + ++elevation_idx) { + const SphericalAngle angle( + static_cast<float>(azimuth_idx) * kRadiansFromDegrees, + static_cast<float>(elevation_idx) * kRadiansFromDegrees); + temp_associated_legendre_polynomials = + alp_generator.Generate(std::sin(angle.elevation())); + // First spherical harmonic is always equal 1 for all angles so we do not + // need to compute and store it. + for (int degree = 1; degree <= max_ambisonic_order_; ++degree) { + for (int order = -degree; order <= degree; ++order) { + const size_t alp_index = + alp_generator.GetIndex(degree, std::abs(order)); + const float alp_value = + temp_associated_legendre_polynomials[alp_index]; + const size_t raw_coeff_idx = AcnSequence(degree, order) - 1; + const size_t encoder_table_idx = + GetEncoderTableIndex(azimuth_idx, elevation_idx, raw_coeff_idx, + kNumElevations, max_num_coeffs_in_table_); + encoder_table_[encoder_table_idx] = + Sn3dNormalization(degree, order) * + UnnormalizedSphericalHarmonic(alp_value, order, angle.azimuth()); + } + } + } + } +} + +void AmbisonicLookupTable::ComputeSymmetriesTable() { + + for (int degree = 1; degree <= max_ambisonic_order_; ++degree) { + for (int order = -degree; order <= degree; ++order) { + const size_t raw_coeff_idx = AcnSequence(degree, order) - 1; + // Symmetry wrt the left-right axis (Y). + symmetries_table_[GetSymmetriesTableIndex(0, raw_coeff_idx, + max_num_coeffs_in_table_)] = + order < 0 ? -1.0f : 1.0f; + // Symmetry wrt the up-down axis (Z). + symmetries_table_[GetSymmetriesTableIndex(1, raw_coeff_idx, + max_num_coeffs_in_table_)] = + static_cast<float>(IntegerPow(-1, degree + order)); + // Symmetry wrt the front-back axis (X). + symmetries_table_[GetSymmetriesTableIndex(2, raw_coeff_idx, + max_num_coeffs_in_table_)] = + order < 0 ? static_cast<float>(-IntegerPow(-1, std::abs(order))) + : static_cast<float>(IntegerPow(-1, order)); + } + } +} + +float AmbisonicLookupTable::UnnormalizedSphericalHarmonic(float alp_value, + int order, + float azimuth_rad) { + const float horizontal_term = + (order >= 0) ? std::cos(static_cast<float>(order) * azimuth_rad) + : std::sin(static_cast<float>(-order) * azimuth_rad); + return alp_value * horizontal_term; +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/ambisonics/ambisonic_lookup_table.h b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/ambisonic_lookup_table.h new file mode 100644 index 000000000..096068d0a --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/ambisonic_lookup_table.h @@ -0,0 +1,92 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_AMBISONICS_AMBISONIC_LOOKUP_TABLE_H_ +#define RESONANCE_AUDIO_AMBISONICS_AMBISONIC_LOOKUP_TABLE_H_ + +#include <vector> + +#include "base/spherical_angle.h" + +namespace vraudio { + +// Represents a lookup table for encoding of Ambisonic periphonic sound fields. +// Supports arbitrary Ambisonic order and uses AmbiX convention (ACN channel +// sequencing, SN3D normalization). +class AmbisonicLookupTable { + public: + // Creates Ambisonic (AmbiX) encoder lookup table given the + // |max_ambisonic_order| used by the client. AmbiX convention uses ACN channel + // sequencing and SN3D normalization. + explicit AmbisonicLookupTable(int max_ambisonic_order); + + // Gets spherical harmonic encoding coefficients for a given order and + // writes them to |encoding_coeffs|. + // + // @param ambisonic_order Ambisonic order of the encoded sound source. + // @param source_direction Direction of a sound source in spherical + // coordinates. + // @param source_spread_deg Encoded sound source angular spread in degrees. + // @param encoding_coeffs Pointer to a vector of Ambisonic encoding + // coefficients. + void GetEncodingCoeffs(int ambisonic_order, + const SphericalAngle& source_direction, + float source_spread_deg, + std::vector<float>* encoding_coeffs) const; + + private: + // Computes a lookup table of encoding coefficients for one quadrant of the + // sphere. + void ComputeEncoderTable(); + + // Computes a table of spherical harmonics symmetry information for all + // cartesian axes. Value of 1 indicates that the current spherical harmonic is + // symmetric with respect to the current axis. Value of -1 indicates that the + // current spherical harmonic is anti-symmetric with respect to the current + // axis. + void ComputeSymmetriesTable(); + + // Returns the unnormalized spherical harmonic coefficient: + // Y_degree^order(azimuth, elevation). + // + // @param alp_value Associated Legendre polynomial for the given degree and + // order evaluated at sin elevation angle: P_degree^order(sin(elevation)). + // @param order Order of the Associated Legendre polynomial. + // @param azimuth_rad Azimuth angle in radians. + // @return Unnormalized spherical harmonic coefficient. + float UnnormalizedSphericalHarmonic(float alp_value, int order, + float azimuth_rad); + + // Ambisonic order. + const int max_ambisonic_order_; + + // Maximum number of coefficients to be stored in the lookup table equal to + // the number of Ambisonic channels for |max_ambisonic_order_| - 1. This is + // because we do not need to store the coefficient for the first spherical + // harmonic coefficient as it is always 1. + const size_t max_num_coeffs_in_table_; + + // Lookup table for storing Ambisonic encoding coefficients for one quadrant + // of the sphere. + std::vector<float> encoder_table_; + + // Lookup table for storing information about spherical harmonic symmetries. + std::vector<float> symmetries_table_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_AMBISONICS_AMBISONIC_LOOKUP_TABLE_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/ambisonics/ambisonic_lookup_table_test.cc b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/ambisonic_lookup_table_test.cc new file mode 100644 index 000000000..8f0c9c9a6 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/ambisonic_lookup_table_test.cc @@ -0,0 +1,240 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "ambisonics/ambisonic_lookup_table.h" + +#include <cmath> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "ambisonics/utils.h" +#include "base/constants_and_types.h" +#include "base/misc_math.h" + +namespace vraudio { + +namespace { + +const int kSourceAmbisonicOrder0 = 0; +const int kSourceAmbisonicOrder1 = 1; +const int kSourceAmbisonicOrder2 = 2; +const int kSourceAmbisonicOrder3 = 3; + +// Minimum angular source spread of 0 ensures that no gain correction +// coefficients should be applied to the Ambisonic encoding coefficients. +const float kMinSpreadDeg = 0.0f; + +} // namespace + +class AmbisonicLookupTableTest : public ::testing::TestWithParam<int> { + protected: + AmbisonicLookupTableTest() + : lookup_table_(kMaxSupportedAmbisonicOrder), + source_ambisonic_order_(GetParam()), + encoding_coeffs_(GetNumPeriphonicComponents(source_ambisonic_order_)) {} + + // Tests whether GetEncodingCoeffs() method returns correct coefficients. + void TestEncodingCoefficients() { + for (size_t i = 0; i < kSourceDirections.size(); ++i) { + const std::vector<float> kExpectedCoeffs = + GenerateExpectedCoeffs(kSourceDirections[i]); + lookup_table_.GetEncodingCoeffs(source_ambisonic_order_, + kSourceDirections[i], kMinSpreadDeg, + &encoding_coeffs_); + for (size_t j = 0; j < encoding_coeffs_.size(); ++j) { + EXPECT_NEAR(kExpectedCoeffs[j], encoding_coeffs_[j], kEpsilonFloat); + } + } + } + + // Tests whether changing the source spread results in correct gain changes in + // pressure and velocity ambisonic encoding coefficients. + void TestSpreadEnergy() { + // Choose an artbitrary source direction from |kSourceDirections|. + const SphericalAngle source_direction = kSourceDirections.back(); + lookup_table_.GetEncodingCoeffs(source_ambisonic_order_, source_direction, + kMinSpreadDeg, &encoding_coeffs_); + float current_pressure_energy = GetPressureEnergy(); + float current_velocity_energy = GetVelocityEnergy(); + for (int spread_deg = 1; spread_deg <= 360; ++spread_deg) { + lookup_table_.GetEncodingCoeffs(source_ambisonic_order_, source_direction, + static_cast<float>(spread_deg), + &encoding_coeffs_); + float new_pressure_energy = GetPressureEnergy(); + float new_velocity_energy = GetVelocityEnergy(); + EXPECT_TRUE(new_pressure_energy >= current_pressure_energy); + EXPECT_TRUE(new_velocity_energy <= current_velocity_energy); + current_pressure_energy = new_pressure_energy; + current_velocity_energy = new_velocity_energy; + } + } + + private: + // Generates expected ambisonic encoding coefficients for ambisonic orders 0 + // to 3, according to http://ambisonics.ch/standards/channels/index. + std::vector<float> GenerateExpectedCoeffs(const SphericalAngle& angle) { + const float a = angle.azimuth(); + const float e = angle.elevation(); + return std::vector<float>{ + 1.0f, + std::cos(e) * std::sin(a), + std::sin(e), + std::cos(e) * std::cos(a), + kSqrtThree / 2.0f * std::cos(e) * std::cos(e) * std::sin(2.0f * a), + kSqrtThree / 2.0f * std::sin(2.0f * e) * std::sin(a), + 0.5f * (3.0f * std::sin(e) * std::sin(e) - 1.0f), + kSqrtThree / 2.0f * std::sin(2.0f * e) * std::cos(a), + kSqrtThree / 2.0f * std::cos(e) * std::cos(e) * std::cos(2.0f * a), + std::sqrt(5.0f / 8.0f) * IntegerPow(std::cos(e), 3) * + std::sin(3.0f * a), + std::sqrt(15.0f) / 2.0f * std::sin(e) * std::cos(e) * std::cos(e) * + std::sin(2.0f * a), + std::sqrt(3.0f / 8.0f) * std::cos(e) * + (5.0f * std::sin(e) * std::sin(e) - 1.0f) * std::sin(a), + 0.5f * std::sin(e) * (5.0f * std::sin(e) * std::sin(e) - 3.0f), + std::sqrt(3.0f / 8.0f) * std::cos(e) * + (5.0f * std::sin(e) * std::sin(e) - 1.0f) * std::cos(a), + std::sqrt(15.0f) / 2.0f * std::sin(e) * std::cos(e) * std::cos(e) * + std::cos(2.0f * a), + std::sqrt(5.0f / 8.0f) * IntegerPow(std::cos(e), 3) * + std::cos(3.0f * a)}; + } + + // Computes energy of the pressure channel (Ambisonic channel 0). + float GetPressureEnergy() { + return encoding_coeffs_[0] * encoding_coeffs_[0]; + } + + // Computes total energy of all the velocity channels (Ambisonic channel 1 and + // above). + float GetVelocityEnergy() { + float velocity_energy = 0.0f; + for (size_t i = 1; i < encoding_coeffs_.size(); ++i) { + velocity_energy += encoding_coeffs_[i] * encoding_coeffs_[i]; + } + return velocity_energy; + } + + // Source angles corresponding to each Cartesian axis direction as well as + // some arbitrary directions (full degrees) in each quadrant of the sphere. + const std::vector<SphericalAngle> kSourceDirections = { + {SphericalAngle::FromDegrees(0.0f, 0.0f)} /* Front */, + {SphericalAngle::FromDegrees(90.0f, 0.0f)} /* Left */, + {SphericalAngle::FromDegrees(180.0f, 0.0f)} /* Back */, + {SphericalAngle::FromDegrees(-90.0f, 0.0f)} /* Right */, + {SphericalAngle::FromDegrees(0.0f, 90.0f)} /* Up */, + {SphericalAngle::FromDegrees(0.0f, -90.0f)} /* Down */, + {SphericalAngle::FromDegrees(10.0f, 20.0f)} /* Left-Top-Front */, + {SphericalAngle::FromDegrees(-30.0f, 40.0f)} /* Right-Top-Front */, + {SphericalAngle::FromDegrees(50.0f, -60.0f)} /* Left-Bottom-Front */, + {SphericalAngle::FromDegrees(290.0f, -80.0f)} /* Right-Bottom-Front */, + {SphericalAngle::FromDegrees(110.0f, 5.0f)} /* Left-Top-Back */, + {SphericalAngle::FromDegrees(-120.0f, 15.0f)} /* Right-Top-Back */, + {SphericalAngle::FromDegrees(130.0f, -25.0f)} /* Left-Bottom-Back */, + {SphericalAngle::FromDegrees(220.0f, -35.0f)} /* Right-Bottom-Back */, + }; + + AmbisonicLookupTable lookup_table_; + const int source_ambisonic_order_; + std::vector<float> encoding_coeffs_; +}; + +// Tests whether GetEncodingCoeffs() method returns correct coefficients. +TEST_P(AmbisonicLookupTableTest, GetEncodingCoeffsTest) { + TestEncodingCoefficients(); +} + +// Tests whether changing the source spread results in correct gain changes in +// pressure and velocity ambisonic encoding coefficients. For example, +// increasing of the source spread should result in overall monotonic increase +// of energy in the pressure channel and overall monotonic decrease in the +// velocity channels. +TEST_P(AmbisonicLookupTableTest, SpreadEnergyTest) { TestSpreadEnergy(); } + +INSTANTIATE_TEST_CASE_P(TestParameters, AmbisonicLookupTableTest, + testing::Values(kSourceAmbisonicOrder0, + kSourceAmbisonicOrder1, + kSourceAmbisonicOrder2, + kSourceAmbisonicOrder3)); + +class PreComputedCoeffsTest : public ::testing::Test { + protected: + // Tests whether the lookup table returns correct coefficients for sources + // with arbitrary ambisonic order, direction and spread. + void TestSpreadCoefficients() { + // Test spread coefficients for each config. + for (const auto& config : kConfigs) { + const int source_ambisonic_order = config.source_ambisonic_order; + const SphericalAngle& source_direction = config.source_direction; + const float source_spread_deg = config.source_spread_deg; + const std::vector<float>& expected_coeffs = config.expected_coefficients; + std::vector<float> encoding_coeffs( + GetNumPeriphonicComponents(source_ambisonic_order)); + AmbisonicLookupTable lookup_table(kMaxSupportedAmbisonicOrder); + lookup_table.GetEncodingCoeffs(source_ambisonic_order, source_direction, + source_spread_deg, &encoding_coeffs); + for (size_t i = 0; i < encoding_coeffs.size(); ++i) { + EXPECT_NEAR(expected_coeffs[i], encoding_coeffs[i], kEpsilonFloat); + } + } + } + + private: + struct TestConfig { + int source_ambisonic_order; + SphericalAngle source_direction; + float source_spread_deg; + std::vector<float> expected_coefficients; + }; + + const std::vector<TestConfig> kConfigs = { + // Zeroth order sound source. + {0 /* ambisonic order */, + SphericalAngle::FromDegrees(0.0f, 0.0f) /* source direction */, + 120.0f /* source spread */, + {1.0f} /* expected coefficients */}, + + // First order sound source. + {1 /* ambisonic order */, + SphericalAngle::FromDegrees(36.0f, 45.0f) /* source direction */, + 70.0f /* source spread */, + {1.20046f, 0.310569f, 0.528372f, 0.427462f} + /* expected coefficients */}, + + // Second order sound source. + {2 /* ambisonic order */, + SphericalAngle::FromDegrees(55.0f, -66.0f) /* source direction */, + 41.0f /* source spread */, + {1.038650f, 0.337027f, -0.924096f, 0.2359899f, 0.124062f, -0.485807f, + 0.6928289f, -0.340166f, -0.045155f} + /* expected coefficients */}, + + // Third order sound source. + {3 /* ambisonic order */, + SphericalAngle::FromDegrees(-13.0f, 90.0f) /* source direction */, + 32.0f /* source spread */, + {1.03237f, 0.0f, 1.02119f, 0.0f, 0.0f, 0.0f, 0.990433f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.898572f, 0.0f, 0.0f, 0.0f} + /* expected coefficients */}}; +}; + +// Tests whether the lookup table returns correct coefficients for sources with +// arbitrary ambisonic order, direction and spread. The expected coefficients +// have been pre-computed using Matlab. +TEST_F(PreComputedCoeffsTest, SpreadCoefficientsTest) { + TestSpreadCoefficients(); +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/ambisonics/ambisonic_spread_coefficients.h b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/ambisonic_spread_coefficients.h new file mode 100644 index 000000000..0c245b87f --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/ambisonic_spread_coefficients.h @@ -0,0 +1,610 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_AMBISONICS_AMBISONIC_SPREAD_COEFFICIENTS_H_ +#define RESONANCE_AUDIO_AMBISONICS_AMBISONIC_SPREAD_COEFFICIENTS_H_ + +namespace vraudio { + +// Lookup table with gain coefficients to control Ambisonic sound source spread +// at different orders, up to and including order 3. For information on how the +// spread control coefficients have been derived please refer to Matlab code as +// well as the corresponding paper. +static const float kSpreadCoeffs[5089] = { + 1.0f, 1.0019f, 0.998092f, 1.01441f, 0.985357f, + 1.02704f, 0.972176f, 1.03975f, 0.958555f, 1.05252f, + 0.944499f, 1.06532f, 0.930017f, 1.07813f, 0.915119f, + 1.09091f, 0.899819f, 1.10365f, 0.884131f, 1.11631f, + 0.868073f, 1.12886f, 0.851665f, 1.14128f, 0.834929f, + 1.15354f, 0.817888f, 1.16561f, 0.800569f, 1.17747f, + 0.782999f, 1.1891f, 0.76521f, 1.20046f, 0.747231f, + 1.21155f, 0.729095f, 1.22234f, 0.710837f, 1.23281f, + 0.692489f, 1.24296f, 0.674086f, 1.25276f, 0.655664f, + 1.26221f, 0.637256f, 1.27131f, 0.618896f, 1.28003f, + 0.600619f, 1.28839f, 0.582456f, 1.29637f, 0.564439f, + 1.30399f, 0.546597f, 1.31123f, 0.528959f, 1.31811f, + 0.511551f, 1.32463f, 0.494397f, 1.3308f, 0.47752f, + 1.33663f, 0.46094f, 1.34212f, 0.444677f, 1.34729f, + 0.428746f, 1.35214f, 0.413161f, 1.3567f, 0.397935f, + 1.36096f, 0.383079f, 1.36495f, 0.3686f, 1.36867f, + 0.354505f, 1.37215f, 0.340799f, 1.37538f, 0.327486f, + 1.37839f, 0.314566f, 1.38118f, 0.302039f, 1.38378f, + 0.289906f, 1.38618f, 0.278163f, 1.38841f, 0.266808f, + 1.39047f, 0.255835f, 1.39238f, 0.24524f, 1.39414f, + 0.235017f, 1.39576f, 0.225159f, 1.39726f, 0.21566f, + 1.39864f, 0.206511f, 1.39991f, 0.197704f, 1.40108f, + 0.189232f, 1.40215f, 0.181084f, 1.40314f, 0.173254f, + 1.40405f, 0.165731f, 1.40488f, 0.158507f, 1.40565f, + 0.151572f, 1.40635f, 0.144918f, 1.40699f, 0.138535f, + 1.40758f, 0.132414f, 1.40812f, 0.126546f, 1.40861f, + 0.120923f, 1.40906f, 0.115536f, 1.40947f, 0.110376f, + 1.40985f, 0.105436f, 1.4102f, 0.100705f, 1.41051f, + 0.0961781f, 1.4108f, 0.0918459f, 1.41107f, 0.087701f, + 1.41131f, 0.0837362f, 1.41153f, 0.0799443f, 1.41173f, + 0.0763185f, 1.41191f, 0.0728519f, 1.41208f, 0.069538f, + 1.41223f, 0.0663707f, 1.41237f, 0.0633438f, 1.41249f, + 0.0604515f, 1.41261f, 0.0576881f, 1.41271f, 0.0550481f, + 1.41281f, 0.0525264f, 1.4129f, 0.0501178f, 1.41298f, + 0.0478176f, 1.41305f, 0.045621f, 1.41312f, 0.0435236f, + 1.41318f, 0.0415211f, 1.41323f, 0.0396092f, 1.41328f, + 0.0377841f, 1.41333f, 0.0360419f, 1.41337f, 0.034379f, + 1.4134f, 0.0327918f, 1.41344f, 0.031277f, 1.41347f, + 0.0298315f, 1.4135f, 0.028452f, 1.41352f, 0.0271356f, + 1.41355f, 0.0258796f, 1.41357f, 0.0246811f, 1.41359f, + 0.0235377f, 1.41361f, 0.0224468f, 1.41362f, 0.0214061f, + 1.41364f, 0.0204132f, 1.41365f, 0.0194661f, 1.41366f, + 0.0185626f, 1.41367f, 0.0177008f, 1.41368f, 0.0168787f, + 1.41369f, 0.0160946f, 1.4137f, 0.0153468f, 1.41371f, + 0.0146335f, 1.41372f, 0.0139531f, 1.41372f, 0.0133043f, + 1.41373f, 0.0126855f, 1.41373f, 0.0120953f, 1.41374f, + 0.0115325f, 1.41374f, 0.0109958f, 1.41375f, 0.0104839f, + 1.41375f, 0.00999584f, 1.41375f, 0.00953039f, 1.41376f, + 0.00908654f, 1.41376f, 0.0086633f, 1.41376f, 0.00825972f, + 1.41376f, 0.00787488f, 1.41376f, 0.00750793f, 1.41377f, + 0.00715803f, 1.41377f, 0.0068244f, 1.41377f, 0.00650628f, + 1.41377f, 0.00620296f, 1.41377f, 0.00591376f, 1.41377f, + 0.00563801f, 1.41377f, 0.00537509f, 1.41378f, 0.00512441f, + 1.41378f, 0.0048854f, 1.41378f, 0.00465752f, 1.41378f, + 0.00444026f, 1.41378f, 0.00423311f, 1.41378f, 0.00403562f, + 1.41378f, 0.00384732f, 1.41378f, 0.0036678f, 1.41378f, + 0.00349665f, 1.41378f, 0.00333348f, 1.41378f, 0.00317791f, + 1.41378f, 0.00302959f, 1.41378f, 0.00288819f, 1.41378f, + 0.00275338f, 1.41378f, 0.00262486f, 1.41378f, 0.00250233f, + 1.41378f, 0.00238552f, 1.41378f, 0.00227415f, 1.41378f, + 0.00216798f, 1.41378f, 0.00206677f, 1.41378f, 0.00197027f, + 1.41378f, 0.00187828f, 1.41378f, 0.00179058f, 1.41378f, + 0.00170697f, 1.41378f, 0.00162727f, 1.41378f, 0.00155128f, + 1.41378f, 0.00147884f, 1.41378f, 0.00140979f, 1.41378f, + 0.00134395f, 1.41378f, 0.00128119f, 1.41378f, 0.00122136f, + 1.41378f, 0.00116432f, 1.41378f, 0.00110994f, 1.41378f, + 0.0010581f, 1.41378f, 0.00100869f, 1.41378f, 0.000961575f, + 1.41378f, 0.000916665f, 1.41378f, 0.000873851f, 1.41378f, + 0.000833036f, 1.41378f, 0.000794127f, 1.41378f, 0.000757035f, + 1.41378f, 0.000721675f, 1.41378f, 0.000687966f, 1.41378f, + 0.000655831f, 1.41378f, 0.000625197f, 1.41378f, 0.000595994f, + 1.41378f, 0.000568154f, 1.41378f, 0.000541615f, 1.41378f, + 0.000516315f, 1.41378f, 0.000492197f, 1.41378f, 0.000469205f, + 1.41378f, 0.000447287f, 1.41378f, 0.000426393f, 1.41378f, + 0.000406474f, 1.41378f, 0.000387486f, 1.41378f, 0.000369385f, + 1.41378f, 0.000352129f, 1.41378f, 0.00033568f, 1.41378f, + 0.000319998f, 1.41378f, 0.00030505f, 1.41378f, 0.000290799f, + 1.41378f, 0.000277214f, 1.41378f, 0.000264264f, 1.41378f, + 0.000251918f, 1.41378f, 0.00024015f, 1.41378f, 0.000228931f, + 1.41378f, 0.000218236f, 1.41378f, 0.000208041f, 1.41378f, + 0.000198322f, 1.41378f, 0.000189056f, 1.41378f, 0.000180224f, + 1.41378f, 0.000171805f, 1.41378f, 0.000163778f, 1.41378f, + 0.000156127f, 1.41378f, 0.000148833f, 1.41378f, 0.00014188f, + 1.41378f, 0.000135252f, 1.41378f, 0.000128933f, 1.41378f, + 0.000122909f, 1.41378f, 0.000117167f, 1.41378f, 0.000111693f, + 1.41378f, 0.000106475f, 1.41378f, 0.000101501f, 1.41378f, + 9.67588e-05f, 1.41378f, 9.22384e-05f, 1.41378f, 8.79291e-05f, + 1.41378f, 8.38211e-05f, 1.41378f, 7.99051e-05f, 1.41378f, + 7.6172e-05f, 1.41378f, 7.26133e-05f, 1.41378f, 6.92209e-05f, + 1.41378f, 6.5987e-05f, 1.41378f, 6.29041e-05f, 1.41378f, + 5.99653e-05f, 1.41378f, 5.71637e-05f, 1.41378f, 5.44931e-05f, + 1.41378f, 5.19472e-05f, 1.41378f, 4.95202e-05f, 1.41378f, + 4.72067e-05f, 1.41378f, 4.50012e-05f, 1.41378f, 4.28988e-05f, + 1.41378f, 4.08946e-05f, 1.41378f, 3.8984e-05f, 1.41378f, + 3.71627e-05f, 1.41378f, 3.54264e-05f, 1.41378f, 3.37713e-05f, + 1.41378f, 3.21935e-05f, 1.41378f, 3.06895e-05f, 1.41378f, + 2.92557e-05f, 1.41378f, 2.78888e-05f, 1.41378f, 2.65859e-05f, + 1.41378f, 2.53438e-05f, 1.41378f, 2.41597e-05f, 1.41378f, + 2.3031e-05f, 1.41378f, 2.1955e-05f, 1.41378f, 2.09292e-05f, + 1.41378f, 1.99514e-05f, 1.41378f, 1.90193e-05f, 1.41378f, + 1.81307e-05f, 1.41378f, 1.72837e-05f, 1.41378f, 1.64762e-05f, + 1.41378f, 1.57064e-05f, 1.41378f, 1.49726e-05f, 1.41378f, + 1.42731e-05f, 1.41378f, 1.36062e-05f, 1.41378f, 1.29706e-05f, + 1.41378f, 1.23646e-05f, 1.41378f, 1.17869e-05f, 1.41378f, + 1.12362e-05f, 1.41378f, 1.07113e-05f, 1.41378f, 1.02108e-05f, + 1.41378f, 9.73377e-06f, 1.41378f, 9.27901e-06f, 1.41378f, + 8.84549e-06f, 1.41378f, 8.43223e-06f, 1.41378f, 8.03827e-06f, + 1.41378f, 7.66273e-06f, 1.41378f, 7.30472e-06f, 1.41378f, + 6.96344e-06f, 1.41378f, 6.63811e-06f, 1.41378f, 6.32798e-06f, + 1.41378f, 6.03233e-06f, 1.41378f, 5.7505e-06f, 1.41378f, + 5.48184e-06f, 1.41378f, 5.22572e-06f, 1.41378f, 4.98158e-06f, + 1.41378f, 4.74884e-06f, 1.41378f, 4.52697e-06f, 1.41378f, + 4.31547e-06f, 1.41378f, 4.11385e-06f, 1.41378f, 3.92165e-06f, + 1.41378f, 3.73843e-06f, 1.41378f, 3.56377e-06f, 1.41378f, + 3.39727e-06f, 1.41378f, 3.23855e-06f, 1.41378f, 3.08724e-06f, + 1.41378f, 2.94301e-06f, 1.41378f, 2.80551e-06f, 1.41378f, + 2.67443e-06f, 1.41378f, 2.54948e-06f, 1.41378f, 2.43037e-06f, + 1.41378f, 2.31682e-06f, 1.41378f, 2.20858e-06f, 1.41378f, + 2.1054e-06f, 1.41378f, 2.00703e-06f, 1.41378f, 1.91326e-06f, + 1.41378f, 1.82388e-06f, 1.41378f, 1.73866e-06f, 1.41378f, + 1.65743e-06f, 1.41378f, 1.58e-06f, 1.41378f, 1.50618e-06f, + 1.41378f, 1.43581e-06f, 1.41378f, 1.36873e-06f, 1.41378f, + 1.30478e-06f, 1.41378f, 1.24382e-06f, 1.41378f, 1.18571e-06f, + 1.01483f, 1.00465f, 0.970383f, 1.03865f, 1.01155f, + 0.9215f, 1.06314f, 1.0179f, 0.869372f, 1.08819f, + 1.02356f, 0.813901f, 1.11364f, 1.02839f, 0.755004f, + 1.13935f, 1.03225f, 0.692625f, 1.16513f, 1.03497f, + 0.626735f, 1.19076f, 1.0364f, 0.557346f, 1.21602f, + 1.03638f, 0.484517f, 1.24065f, 1.03474f, 0.408362f, + 1.2644f, 1.03133f, 0.329059f, 1.28697f, 1.02601f, + 0.246853f, 1.3081f, 1.01866f, 0.162061f, 1.3275f, + 1.00918f, 0.0750718f, 1.34495f, 0.99753f, 0.0f, + 1.36175f, 0.984802f, 0.0f, 1.37869f, 0.971629f, + 0.0f, 1.39575f, 0.958015f, 0.0f, 1.4129f, + 0.943967f, 0.0f, 1.43008f, 0.929493f, 0.0f, + 1.44728f, 0.914603f, 0.0f, 1.46444f, 0.899312f, + 0.0f, 1.48154f, 0.883633f, 0.0f, 1.49853f, + 0.867584f, 0.0f, 1.51538f, 0.851185f, 0.0f, + 1.53206f, 0.834458f, 0.0f, 1.54851f, 0.817427f, + 0.0f, 1.56472f, 0.800117f, 0.0f, 1.58064f, + 0.782558f, 0.0f, 1.59624f, 0.764779f, 0.0f, + 1.6115f, 0.74681f, 0.0f, 1.62638f, 0.728685f, + 0.0f, 1.64086f, 0.710436f, 0.0f, 1.65492f, + 0.692098f, 0.0f, 1.66854f, 0.673706f, 0.0f, + 1.6817f, 0.655294f, 0.0f, 1.69439f, 0.636896f, + 0.0f, 1.7066f, 0.618548f, 0.0f, 1.71831f, + 0.600281f, 0.0f, 1.72953f, 0.582128f, 0.0f, + 1.74025f, 0.564121f, 0.0f, 1.75047f, 0.546289f, + 0.0f, 1.76019f, 0.528661f, 0.0f, 1.76943f, + 0.511262f, 0.0f, 1.77819f, 0.494118f, 0.0f, + 1.78647f, 0.477251f, 0.0f, 1.79429f, 0.46068f, + 0.0f, 1.80166f, 0.444426f, 0.0f, 1.8086f, + 0.428504f, 0.0f, 1.81511f, 0.412928f, 0.0f, + 1.82123f, 0.397711f, 0.0f, 1.82695f, 0.382863f, + 0.0f, 1.8323f, 0.368392f, 0.0f, 1.8373f, + 0.354306f, 0.0f, 1.84196f, 0.340607f, 0.0f, + 1.84631f, 0.327301f, 0.0f, 1.85035f, 0.314388f, + 0.0f, 1.8541f, 0.301869f, 0.0f, 1.85758f, + 0.289743f, 0.0f, 1.86081f, 0.278006f, 0.0f, + 1.8638f, 0.266657f, 0.0f, 1.86657f, 0.255691f, + 0.0f, 1.86912f, 0.245102f, 0.0f, 1.87149f, + 0.234885f, 0.0f, 1.87367f, 0.225033f, 0.0f, + 1.87568f, 0.215538f, 0.0f, 1.87753f, 0.206394f, + 0.0f, 1.87924f, 0.197593f, 0.0f, 1.8808f, + 0.189125f, 0.0f, 1.88225f, 0.180982f, 0.0f, + 1.88357f, 0.173156f, 0.0f, 1.88479f, 0.165638f, + 0.0f, 1.88591f, 0.158418f, 0.0f, 1.88694f, + 0.151487f, 0.0f, 1.88788f, 0.144836f, 0.0f, + 1.88874f, 0.138457f, 0.0f, 1.88953f, 0.132339f, + 0.0f, 1.89025f, 0.126475f, 0.0f, 1.89091f, + 0.120855f, 0.0f, 1.89152f, 0.115471f, 0.0f, + 1.89207f, 0.110314f, 0.0f, 1.89258f, 0.105376f, + 0.0f, 1.89305f, 0.100649f, 0.0f, 1.89347f, + 0.0961239f, 0.0f, 1.89386f, 0.0917941f, 0.0f, + 1.89421f, 0.0876516f, 0.0f, 1.89453f, 0.083689f, + 0.0f, 1.89483f, 0.0798993f, 0.0f, 1.8951f, + 0.0762755f, 0.0f, 1.89534f, 0.0728108f, 0.0f, + 1.89557f, 0.0694989f, 0.0f, 1.89577f, 0.0663333f, + 0.0f, 1.89596f, 0.0633081f, 0.0f, 1.89613f, + 0.0604174f, 0.0f, 1.89628f, 0.0576555f, 0.0f, + 1.89642f, 0.0550171f, 0.0f, 1.89655f, 0.0524968f, + 0.0f, 1.89667f, 0.0500896f, 0.0f, 1.89678f, + 0.0477907f, 0.0f, 1.89687f, 0.0455953f, 0.0f, + 1.89696f, 0.0434991f, 0.0f, 1.89704f, 0.0414977f, + 0.0f, 1.89712f, 0.0395869f, 0.0f, 1.89718f, + 0.0377628f, 0.0f, 1.89724f, 0.0360216f, 0.0f, + 1.8973f, 0.0343596f, 0.0f, 1.89735f, 0.0327733f, + 0.0f, 1.8974f, 0.0312594f, 0.0f, 1.89744f, + 0.0298146f, 0.0f, 1.89748f, 0.0284359f, 0.0f, + 1.89751f, 0.0271203f, 0.0f, 1.89754f, 0.025865f, + 0.0f, 1.89757f, 0.0246672f, 0.0f, 1.8976f, + 0.0235244f, 0.0f, 1.89762f, 0.0224342f, 0.0f, + 1.89764f, 0.021394f, 0.0f, 1.89766f, 0.0204017f, + 0.0f, 1.89768f, 0.0194551f, 0.0f, 1.8977f, + 0.0185521f, 0.0f, 1.89771f, 0.0176908f, 0.0f, + 1.89773f, 0.0168692f, 0.0f, 1.89774f, 0.0160856f, + 0.0f, 1.89775f, 0.0153381f, 0.0f, 1.89776f, + 0.0146252f, 0.0f, 1.89777f, 0.0139453f, 0.0f, + 1.89778f, 0.0132968f, 0.0f, 1.89778f, 0.0126783f, + 0.0f, 1.89779f, 0.0120885f, 0.0f, 1.8978f, + 0.011526f, 0.0f, 1.8978f, 0.0109896f, 0.0f, + 1.89781f, 0.010478f, 0.0f, 1.89781f, 0.00999021f, + 0.0f, 1.89782f, 0.00952502f, 0.0f, 1.89782f, + 0.00908142f, 0.0f, 1.89782f, 0.00865842f, 0.0f, + 1.89783f, 0.00825506f, 0.0f, 1.89783f, 0.00787044f, + 0.0f, 1.89783f, 0.00750369f, 0.0f, 1.89784f, + 0.00715399f, 0.0f, 1.89784f, 0.00682055f, 0.0f, + 1.89784f, 0.00650262f, 0.0f, 1.89784f, 0.00619947f, + 0.0f, 1.89784f, 0.00591042f, 0.0f, 1.89785f, + 0.00563483f, 0.0f, 1.89785f, 0.00537206f, 0.0f, + 1.89785f, 0.00512152f, 0.0f, 1.89785f, 0.00488265f, + 0.0f, 1.89785f, 0.0046549f, 0.0f, 1.89785f, + 0.00443776f, 0.0f, 1.89785f, 0.00423073f, 0.0f, + 1.89785f, 0.00403334f, 0.0f, 1.89785f, 0.00384516f, + 0.0f, 1.89785f, 0.00366574f, 0.0f, 1.89785f, + 0.00349468f, 0.0f, 1.89786f, 0.0033316f, 0.0f, + 1.89786f, 0.00317612f, 0.0f, 1.89786f, 0.00302788f, + 0.0f, 1.89786f, 0.00288656f, 0.0f, 1.89786f, + 0.00275183f, 0.0f, 1.89786f, 0.00262338f, 0.0f, + 1.89786f, 0.00250092f, 0.0f, 1.89786f, 0.00238417f, + 0.0f, 1.89786f, 0.00227287f, 0.0f, 1.89786f, + 0.00216676f, 0.0f, 1.89786f, 0.0020656f, 0.0f, + 1.89786f, 0.00196916f, 0.0f, 1.89786f, 0.00187722f, + 0.0f, 1.89786f, 0.00178957f, 0.0f, 1.89786f, + 0.00170601f, 0.0f, 1.89786f, 0.00162635f, 0.0f, + 1.89786f, 0.00155041f, 0.0f, 1.89786f, 0.00147801f, + 0.0f, 1.89786f, 0.00140899f, 0.0f, 1.89786f, + 0.00134319f, 0.0f, 1.89786f, 0.00128047f, 0.0f, + 1.89786f, 0.00122067f, 0.0f, 1.89786f, 0.00116366f, + 0.0f, 1.89786f, 0.00110932f, 0.0f, 1.89786f, + 0.00105751f, 0.0f, 1.89786f, 0.00100812f, 0.0f, + 1.89786f, 0.000961033f, 0.0f, 1.89786f, 0.000916148f, + 0.0f, 1.89786f, 0.000873358f, 0.0f, 1.89786f, + 0.000832566f, 0.0f, 1.89786f, 0.000793679f, 0.0f, + 1.89786f, 0.000756608f, 0.0f, 1.89786f, 0.000721268f, + 0.0f, 1.89786f, 0.000687578f, 0.0f, 1.89786f, + 0.000655462f, 0.0f, 1.89786f, 0.000624845f, 0.0f, + 1.89786f, 0.000595658f, 0.0f, 1.89786f, 0.000567834f, + 0.0f, 1.89786f, 0.00054131f, 0.0f, 1.89786f, + 0.000516024f, 0.0f, 1.89786f, 0.000491919f, 0.0f, + 1.89786f, 0.000468941f, 0.0f, 1.89786f, 0.000447035f, + 0.0f, 1.89786f, 0.000426152f, 0.0f, 1.89786f, + 0.000406245f, 0.0f, 1.89786f, 0.000387268f, 0.0f, + 1.89786f, 0.000369177f, 0.0f, 1.89786f, 0.000351931f, + 0.0f, 1.89786f, 0.000335491f, 0.0f, 1.89786f, + 0.000319818f, 0.0f, 1.89786f, 0.000304878f, 0.0f, + 1.89786f, 0.000290635f, 0.0f, 1.89786f, 0.000277058f, + 0.0f, 1.89786f, 0.000264115f, 0.0f, 1.89786f, + 0.000251776f, 0.0f, 1.89786f, 0.000240014f, 0.0f, + 1.89786f, 0.000228802f, 0.0f, 1.89786f, 0.000218113f, + 0.0f, 1.89786f, 0.000207923f, 0.0f, 1.89786f, + 0.00019821f, 0.0f, 1.89786f, 0.00018895f, 0.0f, + 1.89786f, 0.000180123f, 0.0f, 1.89786f, 0.000171708f, + 0.0f, 1.89786f, 0.000163686f, 0.0f, 1.89786f, + 0.000156039f, 0.0f, 1.89786f, 0.000148749f, 0.0f, + 1.89786f, 0.0001418f, 0.0f, 1.89786f, 0.000135175f, + 0.0f, 1.89786f, 0.00012886f, 0.0f, 1.89786f, + 0.00012284f, 0.0f, 1.89786f, 0.000117101f, 0.0f, + 1.89786f, 0.00011163f, 0.0f, 1.89786f, 0.000106415f, + 0.0f, 1.89786f, 0.000101444f, 0.0f, 1.89786f, + 9.67043e-05f, 0.0f, 1.89786f, 9.21864e-05f, 0.0f, + 1.89786f, 8.78795e-05f, 0.0f, 1.89786f, 8.37739e-05f, + 0.0f, 1.89786f, 7.98601e-05f, 0.0f, 1.89786f, + 7.61291e-05f, 0.0f, 1.89786f, 7.25724e-05f, 0.0f, + 1.89786f, 6.91819e-05f, 0.0f, 1.89786f, 6.59498e-05f, + 0.0f, 1.89786f, 6.28686e-05f, 0.0f, 1.89786f, + 5.99315e-05f, 0.0f, 1.89786f, 5.71315e-05f, 0.0f, + 1.89786f, 5.44624e-05f, 0.0f, 1.89786f, 5.19179e-05f, + 0.0f, 1.89786f, 4.94923e-05f, 0.0f, 1.89786f, + 4.71801e-05f, 0.0f, 1.89786f, 4.49758e-05f, 0.0f, + 1.89786f, 4.28746e-05f, 0.0f, 1.89786f, 4.08715e-05f, + 0.0f, 1.89786f, 3.8962e-05f, 0.0f, 1.89786f, + 3.71417e-05f, 0.0f, 1.89786f, 3.54065e-05f, 0.0f, + 1.89786f, 3.37523e-05f, 0.0f, 1.89786f, 3.21754e-05f, + 0.0f, 1.89786f, 3.06722e-05f, 0.0f, 1.89786f, + 2.92392e-05f, 0.0f, 1.89786f, 2.78731e-05f, 0.0f, + 1.89786f, 2.65709e-05f, 0.0f, 1.89786f, 2.53295e-05f, + 0.0f, 1.89786f, 2.41461e-05f, 0.0f, 1.89786f, + 2.3018e-05f, 0.0f, 1.89786f, 2.19426e-05f, 0.0f, + 1.89786f, 2.09175e-05f, 0.0f, 1.89786f, 1.99402e-05f, + 0.0f, 1.89786f, 1.90086e-05f, 0.0f, 1.89786f, + 1.81205e-05f, 0.0f, 1.89786f, 1.72739e-05f, 0.0f, + 1.89786f, 1.64669e-05f, 0.0f, 1.89786f, 1.56975e-05f, + 0.0f, 1.89786f, 1.49642e-05f, 0.0f, 1.89786f, + 1.4265e-05f, 0.0f, 1.89786f, 1.35986e-05f, 0.0f, + 1.89786f, 1.29632e-05f, 0.0f, 1.89786f, 1.23576e-05f, + 0.0f, 1.89786f, 1.17802e-05f, 0.0f, 1.89786f, + 1.12299e-05f, 0.0f, 1.89786f, 1.07052e-05f, 0.0f, + 1.89786f, 1.02051e-05f, 0.0f, 1.89786f, 9.72828e-06f, + 0.0f, 1.89786f, 9.27378e-06f, 0.0f, 1.89786f, + 8.84051e-06f, 0.0f, 1.89786f, 8.42748e-06f, 0.0f, + 1.89786f, 8.03374e-06f, 0.0f, 1.89786f, 7.65841e-06f, + 0.0f, 1.89786f, 7.3006e-06f, 0.0f, 1.89786f, + 6.95952e-06f, 0.0f, 1.89786f, 6.63437e-06f, 0.0f, + 1.89786f, 6.32441e-06f, 0.0f, 1.89786f, 6.02893e-06f, + 0.0f, 1.89786f, 5.74726e-06f, 0.0f, 1.89786f, + 5.47875e-06f, 0.0f, 1.89786f, 5.22278e-06f, 0.0f, + 1.89786f, 4.97877e-06f, 0.0f, 1.89786f, 4.74616e-06f, + 0.0f, 1.89786f, 4.52442e-06f, 0.0f, 1.89786f, + 4.31304e-06f, 0.0f, 1.89786f, 4.11153e-06f, 0.0f, + 1.89786f, 3.91944e-06f, 0.0f, 1.89786f, 3.73632e-06f, + 0.0f, 1.89786f, 3.56176e-06f, 0.0f, 1.89786f, + 3.39536e-06f, 0.0f, 1.89786f, 3.23672e-06f, 0.0f, + 1.89786f, 3.0855e-06f, 0.0f, 1.89786f, 2.94135e-06f, + 0.0f, 1.89786f, 2.80393e-06f, 0.0f, 1.89786f, + 2.67293e-06f, 0.0f, 1.89786f, 2.54805e-06f, 0.0f, + 1.89786f, 2.429e-06f, 0.0f, 1.89786f, 2.31552e-06f, + 0.0f, 1.89786f, 2.20734e-06f, 0.0f, 1.89786f, + 2.10421e-06f, 0.0f, 1.89786f, 2.0059e-06f, 0.0f, + 1.89786f, 1.91218e-06f, 0.0f, 1.89786f, 1.82285e-06f, + 0.0f, 1.89786f, 1.73768e-06f, 0.0f, 1.89786f, + 1.6565e-06f, 0.0f, 1.89786f, 1.57911e-06f, 0.0f, + 1.89786f, 1.50533e-06f, 0.0f, 1.89786f, 1.435e-06f, + 0.0f, 1.89786f, 1.36796e-06f, 0.0f, 1.89786f, + 1.30405e-06f, 0.0f, 1.89786f, 1.24312e-06f, 0.0f, + 1.89786f, 1.18504e-06f, 0.0f, 1.00324f, 1.00216f, + 0.999152f, 0.990038f, 1.03237f, 1.02119f, 0.990433f, + 0.898572f, 1.06269f, 1.04023f, 0.979161f, 0.799806f, + 1.094f, 1.05895f, 0.964976f, 0.693603f, 1.126f, + 1.07701f, 0.947526f, 0.57989f, 1.15835f, 1.09398f, + 0.926474f, 0.45869f, 1.19059f, 1.10944f, 0.901512f, + 0.330158f, 1.22223f, 1.12289f, 0.87237f, 0.194621f, + 1.25268f, 1.13384f, 0.838839f, 0.0526136f, 1.28199f, + 1.14236f, 0.801199f, 0.0f, 1.31207f, 1.15021f, + 0.760839f, 0.0f, 1.34301f, 1.15742f, 0.717799f, + 0.0f, 1.37465f, 1.16386f, 0.671999f, 0.0f, + 1.40681f, 1.16935f, 0.623371f, 0.0f, 1.43929f, + 1.17374f, 0.571868f, 0.0f, 1.47185f, 1.17684f, + 0.517465f, 0.0f, 1.50423f, 1.17846f, 0.460174f, + 0.0f, 1.53613f, 1.17844f, 0.400043f, 0.0f, + 1.56725f, 1.17657f, 0.337165f, 0.0f, 1.59725f, + 1.1727f, 0.271688f, 0.0f, 1.62577f, 1.16665f, + 0.203815f, 0.0f, 1.65245f, 1.15828f, 0.133806f, + 0.0f, 1.67697f, 1.14751f, 0.0619832f, 0.0f, + 1.69901f, 1.13426f, 0.0f, 0.0f, 1.72022f, + 1.11979f, 0.0f, 0.0f, 1.74163f, 1.10481f, + 0.0f, 0.0f, 1.76318f, 1.08933f, 0.0f, + 0.0f, 1.78484f, 1.07336f, 0.0f, 0.0f, + 1.80655f, 1.0569f, 0.0f, 0.0f, 1.82827f, + 1.03997f, 0.0f, 0.0f, 1.84995f, 1.02258f, + 0.0f, 0.0f, 1.87155f, 1.00475f, 0.0f, + 0.0f, 1.89302f, 0.986504f, 0.0f, 0.0f, + 1.91431f, 0.967857f, 0.0f, 0.0f, 1.93537f, + 0.948837f, 0.0f, 0.0f, 1.95615f, 0.929471f, + 0.0f, 0.0f, 1.97662f, 0.90979f, 0.0f, + 0.0f, 1.99674f, 0.889823f, 0.0f, 0.0f, + 2.01645f, 0.869607f, 0.0f, 0.0f, 2.03572f, + 0.849175f, 0.0f, 0.0f, 2.05452f, 0.828565f, + 0.0f, 0.0f, 2.07282f, 0.807816f, 0.0f, + 0.0f, 2.09058f, 0.786964f, 0.0f, 0.0f, + 2.10779f, 0.766051f, 0.0f, 0.0f, 2.12441f, + 0.745115f, 0.0f, 0.0f, 2.14044f, 0.724196f, + 0.0f, 0.0f, 2.15586f, 0.703332f, 0.0f, + 0.0f, 2.17065f, 0.682561f, 0.0f, 0.0f, + 2.18482f, 0.661921f, 0.0f, 0.0f, 2.19836f, + 0.641445f, 0.0f, 0.0f, 2.21128f, 0.621169f, + 0.0f, 0.0f, 2.22356f, 0.601125f, 0.0f, + 0.0f, 2.23523f, 0.581341f, 0.0f, 0.0f, + 2.24629f, 0.561847f, 0.0f, 0.0f, 2.25675f, + 0.542667f, 0.0f, 0.0f, 2.26663f, 0.523826f, + 0.0f, 0.0f, 2.27594f, 0.505344f, 0.0f, + 0.0f, 2.28471f, 0.487239f, 0.0f, 0.0f, + 2.29294f, 0.469528f, 0.0f, 0.0f, 2.30066f, + 0.452225f, 0.0f, 0.0f, 2.30789f, 0.435342f, + 0.0f, 0.0f, 2.31465f, 0.418888f, 0.0f, + 0.0f, 2.32097f, 0.40287f, 0.0f, 0.0f, + 2.32686f, 0.387294f, 0.0f, 0.0f, 2.33234f, + 0.372164f, 0.0f, 0.0f, 2.33745f, 0.357481f, + 0.0f, 0.0f, 2.34219f, 0.343246f, 0.0f, + 0.0f, 2.34659f, 0.329458f, 0.0f, 0.0f, + 2.35066f, 0.316113f, 0.0f, 0.0f, 2.35444f, + 0.303208f, 0.0f, 0.0f, 2.35794f, 0.290738f, + 0.0f, 0.0f, 2.36117f, 0.278698f, 0.0f, + 0.0f, 2.36415f, 0.26708f, 0.0f, 0.0f, + 2.36691f, 0.255878f, 0.0f, 0.0f, 2.36945f, + 0.245082f, 0.0f, 0.0f, 2.37179f, 0.234685f, + 0.0f, 0.0f, 2.37394f, 0.224677f, 0.0f, + 0.0f, 2.37592f, 0.215048f, 0.0f, 0.0f, + 2.37775f, 0.20579f, 0.0f, 0.0f, 2.37942f, + 0.196891f, 0.0f, 0.0f, 2.38096f, 0.188342f, + 0.0f, 0.0f, 2.38237f, 0.180132f, 0.0f, + 0.0f, 2.38367f, 0.172251f, 0.0f, 0.0f, + 2.38486f, 0.164689f, 0.0f, 0.0f, 2.38595f, + 0.157435f, 0.0f, 0.0f, 2.38694f, 0.150479f, + 0.0f, 0.0f, 2.38786f, 0.143811f, 0.0f, + 0.0f, 2.38869f, 0.137421f, 0.0f, 0.0f, + 2.38946f, 0.131299f, 0.0f, 0.0f, 2.39016f, + 0.125435f, 0.0f, 0.0f, 2.3908f, 0.11982f, + 0.0f, 0.0f, 2.39139f, 0.114445f, 0.0f, + 0.0f, 2.39192f, 0.1093f, 0.0f, 0.0f, + 2.39241f, 0.104376f, 0.0f, 0.0f, 2.39286f, + 0.099666f, 0.0f, 0.0f, 2.39327f, 0.0951603f, + 0.0f, 0.0f, 2.39364f, 0.0908511f, 0.0f, + 0.0f, 2.39398f, 0.0867305f, 0.0f, 0.0f, + 2.39429f, 0.082791f, 0.0f, 0.0f, 2.39457f, + 0.0790251f, 0.0f, 0.0f, 2.39483f, 0.0754256f, + 0.0f, 0.0f, 2.39506f, 0.0719858f, 0.0f, + 0.0f, 2.39528f, 0.0686988f, 0.0f, 0.0f, + 2.39547f, 0.0655584f, 0.0f, 0.0f, 2.39565f, + 0.0625583f, 0.0f, 0.0f, 2.39582f, 0.0596925f, + 0.0f, 0.0f, 2.39596f, 0.0569554f, 0.0f, + 0.0f, 2.3961f, 0.0543413f, 0.0f, 0.0f, + 2.39622f, 0.0518451f, 0.0f, 0.0f, 2.39633f, + 0.0494615f, 0.0f, 0.0f, 2.39644f, 0.0471857f, + 0.0f, 0.0f, 2.39653f, 0.0450131f, 0.0f, + 0.0f, 2.39661f, 0.0429389f, 0.0f, 0.0f, + 2.39669f, 0.0409591f, 0.0f, 0.0f, 2.39676f, + 0.0390693f, 0.0f, 0.0f, 2.39682f, 0.0372656f, + 0.0f, 0.0f, 2.39688f, 0.0355441f, 0.0f, + 0.0f, 2.39694f, 0.0339013f, 0.0f, 0.0f, + 2.39698f, 0.0323336f, 0.0f, 0.0f, 2.39703f, + 0.0308377f, 0.0f, 0.0f, 2.39707f, 0.0294103f, + 0.0f, 0.0f, 2.3971f, 0.0280484f, 0.0f, + 0.0f, 2.39714f, 0.0267489f, 0.0f, 0.0f, + 2.39717f, 0.0255092f, 0.0f, 0.0f, 2.39719f, + 0.0243265f, 0.0f, 0.0f, 2.39722f, 0.0231982f, + 0.0f, 0.0f, 2.39724f, 0.0221218f, 0.0f, + 0.0f, 2.39726f, 0.0210951f, 0.0f, 0.0f, + 2.39728f, 0.0201157f, 0.0f, 0.0f, 2.3973f, + 0.0191815f, 0.0f, 0.0f, 2.39731f, 0.0182904f, + 0.0f, 0.0f, 2.39733f, 0.0174405f, 0.0f, + 0.0f, 2.39734f, 0.0166299f, 0.0f, 0.0f, + 2.39735f, 0.0158567f, 0.0f, 0.0f, 2.39736f, + 0.0151194f, 0.0f, 0.0f, 2.39737f, 0.0144161f, + 0.0f, 0.0f, 2.39738f, 0.0137455f, 0.0f, + 0.0f, 2.39739f, 0.0131059f, 0.0f, 0.0f, + 2.3974f, 0.0124959f, 0.0f, 0.0f, 2.3974f, + 0.0119143f, 0.0f, 0.0f, 2.39741f, 0.0113596f, + 0.0f, 0.0f, 2.39741f, 0.0108306f, 0.0f, + 0.0f, 2.39742f, 0.0103262f, 0.0f, 0.0f, + 2.39742f, 0.00984523f, 0.0f, 0.0f, 2.39743f, + 0.00938658f, 0.0f, 0.0f, 2.39743f, 0.00894924f, + 0.0f, 0.0f, 2.39744f, 0.00853223f, 0.0f, + 0.0f, 2.39744f, 0.00813459f, 0.0f, 0.0f, + 2.39744f, 0.00775545f, 0.0f, 0.0f, 2.39744f, + 0.00739393f, 0.0f, 0.0f, 2.39745f, 0.00704923f, + 0.0f, 0.0f, 2.39745f, 0.00672056f, 0.0f, + 0.0f, 2.39745f, 0.00640719f, 0.0f, 0.0f, + 2.39745f, 0.00610841f, 0.0f, 0.0f, 2.39745f, + 0.00582353f, 0.0f, 0.0f, 2.39745f, 0.00555191f, + 0.0f, 0.0f, 2.39746f, 0.00529295f, 0.0f, + 0.0f, 2.39746f, 0.00504604f, 0.0f, 0.0f, + 2.39746f, 0.00481063f, 0.0f, 0.0f, 2.39746f, + 0.00458619f, 0.0f, 0.0f, 2.39746f, 0.00437221f, + 0.0f, 0.0f, 2.39746f, 0.0041682f, 0.0f, + 0.0f, 2.39746f, 0.0039737f, 0.0f, 0.0f, + 2.39746f, 0.00378826f, 0.0f, 0.0f, 2.39746f, + 0.00361147f, 0.0f, 0.0f, 2.39746f, 0.00344291f, + 0.0f, 0.0f, 2.39746f, 0.00328222f, 0.0f, + 0.0f, 2.39746f, 0.00312902f, 0.0f, 0.0f, + 2.39746f, 0.00298297f, 0.0f, 0.0f, 2.39747f, + 0.00284372f, 0.0f, 0.0f, 2.39747f, 0.00271097f, + 0.0f, 0.0f, 2.39747f, 0.00258441f, 0.0f, + 0.0f, 2.39747f, 0.00246376f, 0.0f, 0.0f, + 2.39747f, 0.00234873f, 0.0f, 0.0f, 2.39747f, + 0.00223908f, 0.0f, 0.0f, 2.39747f, 0.00213453f, + 0.0f, 0.0f, 2.39747f, 0.00203487f, 0.0f, + 0.0f, 2.39747f, 0.00193986f, 0.0f, 0.0f, + 2.39747f, 0.00184928f, 0.0f, 0.0f, 2.39747f, + 0.00176292f, 0.0f, 0.0f, 2.39747f, 0.0016806f, + 0.0f, 0.0f, 2.39747f, 0.00160212f, 0.0f, + 0.0f, 2.39747f, 0.00152731f, 0.0f, 0.0f, + 2.39747f, 0.00145598f, 0.0f, 0.0f, 2.39747f, + 0.00138799f, 0.0f, 0.0f, 2.39747f, 0.00132316f, + 0.0f, 0.0f, 2.39747f, 0.00126137f, 0.0f, + 0.0f, 2.39747f, 0.00120246f, 0.0f, 0.0f, + 2.39747f, 0.0011463f, 0.0f, 0.0f, 2.39747f, + 0.00109276f, 0.0f, 0.0f, 2.39747f, 0.00104172f, + 0.0f, 0.0f, 2.39747f, 0.000993069f, 0.0f, + 0.0f, 2.39747f, 0.000946686f, 0.0f, 0.0f, + 2.39747f, 0.000902469f, 0.0f, 0.0f, 2.39747f, + 0.000860316f, 0.0f, 0.0f, 2.39747f, 0.000820132f, + 0.0f, 0.0f, 2.39747f, 0.000781825f, 0.0f, + 0.0f, 2.39747f, 0.000745306f, 0.0f, 0.0f, + 2.39747f, 0.000710492f, 0.0f, 0.0f, 2.39747f, + 0.000677305f, 0.0f, 0.0f, 2.39747f, 0.000645667f, + 0.0f, 0.0f, 2.39747f, 0.000615507f, 0.0f, + 0.0f, 2.39747f, 0.000586756f, 0.0f, 0.0f, + 2.39747f, 0.000559347f, 0.0f, 0.0f, 2.39747f, + 0.000533218f, 0.0f, 0.0f, 2.39747f, 0.00050831f, + 0.0f, 0.0f, 2.39747f, 0.000484565f, 0.0f, + 0.0f, 2.39747f, 0.000461929f, 0.0f, 0.0f, + 2.39747f, 0.000440351f, 0.0f, 0.0f, 2.39747f, + 0.00041978f, 0.0f, 0.0f, 2.39747f, 0.00040017f, + 0.0f, 0.0f, 2.39747f, 0.000381476f, 0.0f, + 0.0f, 2.39747f, 0.000363656f, 0.0f, 0.0f, + 2.39747f, 0.000346667f, 0.0f, 0.0f, 2.39747f, + 0.000330473f, 0.0f, 0.0f, 2.39747f, 0.000315034f, + 0.0f, 0.0f, 2.39747f, 0.000300317f, 0.0f, + 0.0f, 2.39747f, 0.000286287f, 0.0f, 0.0f, + 2.39747f, 0.000272913f, 0.0f, 0.0f, 2.39747f, + 0.000260164f, 0.0f, 0.0f, 2.39747f, 0.00024801f, + 0.0f, 0.0f, 2.39747f, 0.000236423f, 0.0f, + 0.0f, 2.39747f, 0.000225378f, 0.0f, 0.0f, + 2.39747f, 0.000214849f, 0.0f, 0.0f, 2.39747f, + 0.000204812f, 0.0f, 0.0f, 2.39747f, 0.000195244f, + 0.0f, 0.0f, 2.39747f, 0.000186122f, 0.0f, + 0.0f, 2.39747f, 0.000177427f, 0.0f, 0.0f, + 2.39747f, 0.000169138f, 0.0f, 0.0f, 2.39747f, + 0.000161236f, 0.0f, 0.0f, 2.39747f, 0.000153704f, + 0.0f, 0.0f, 2.39747f, 0.000146523f, 0.0f, + 0.0f, 2.39747f, 0.000139678f, 0.0f, 0.0f, + 2.39747f, 0.000133152f, 0.0f, 0.0f, 2.39747f, + 0.000126932f, 0.0f, 0.0f, 2.39747f, 0.000121001f, + 0.0f, 0.0f, 2.39747f, 0.000115348f, 0.0f, + 0.0f, 2.39747f, 0.00010996f, 0.0f, 0.0f, + 2.39747f, 0.000104822f, 0.0f, 0.0f, 2.39747f, + 9.99252e-05f, 0.0f, 0.0f, 2.39747f, 9.52568e-05f, + 0.0f, 0.0f, 2.39747f, 9.08065e-05f, 0.0f, + 0.0f, 2.39747f, 8.65641e-05f, 0.0f, 0.0f, + 2.39747f, 8.25199e-05f, 0.0f, 0.0f, 2.39747f, + 7.86646e-05f, 0.0f, 0.0f, 2.39747f, 7.49895e-05f, + 0.0f, 0.0f, 2.39747f, 7.1486e-05f, 0.0f, + 0.0f, 2.39747f, 6.81463e-05f, 0.0f, 0.0f, + 2.39747f, 6.49625e-05f, 0.0f, 0.0f, 2.39747f, + 6.19275e-05f, 0.0f, 0.0f, 2.39747f, 5.90343e-05f, + 0.0f, 0.0f, 2.39747f, 5.62762e-05f, 0.0f, + 0.0f, 2.39747f, 5.3647e-05f, 0.0f, 0.0f, + 2.39747f, 5.11407e-05f, 0.0f, 0.0f, 2.39747f, + 4.87514e-05f, 0.0f, 0.0f, 2.39747f, 4.64738e-05f, + 0.0f, 0.0f, 2.39747f, 4.43025e-05f, 0.0f, + 0.0f, 2.39747f, 4.22327e-05f, 0.0f, 0.0f, + 2.39747f, 4.02596e-05f, 0.0f, 0.0f, 2.39747f, + 3.83787e-05f, 0.0f, 0.0f, 2.39747f, 3.65857e-05f, + 0.0f, 0.0f, 2.39747f, 3.48764e-05f, 0.0f, + 0.0f, 2.39747f, 3.3247e-05f, 0.0f, 0.0f, + 2.39747f, 3.16937e-05f, 0.0f, 0.0f, 2.39747f, + 3.0213e-05f, 0.0f, 0.0f, 2.39747f, 2.88014e-05f, + 0.0f, 0.0f, 2.39747f, 2.74558e-05f, 0.0f, + 0.0f, 2.39747f, 2.61731e-05f, 0.0f, 0.0f, + 2.39747f, 2.49503e-05f, 0.0f, 0.0f, 2.39747f, + 2.37846e-05f, 0.0f, 0.0f, 2.39747f, 2.26734e-05f, + 0.0f, 0.0f, 2.39747f, 2.16141e-05f, 0.0f, + 0.0f, 2.39747f, 2.06043e-05f, 0.0f, 0.0f, + 2.39747f, 1.96417e-05f, 0.0f, 0.0f, 2.39747f, + 1.8724e-05f, 0.0f, 0.0f, 2.39747f, 1.78492e-05f, + 0.0f, 0.0f, 2.39747f, 1.70153e-05f, 0.0f, + 0.0f, 2.39747f, 1.62203e-05f, 0.0f, 0.0f, + 2.39747f, 1.54625e-05f, 0.0f, 0.0f, 2.39747f, + 1.47401e-05f, 0.0f, 0.0f, 2.39747f, 1.40515e-05f, + 0.0f, 0.0f, 2.39747f, 1.3395e-05f, 0.0f, + 0.0f, 2.39747f, 1.27692e-05f, 0.0f, 0.0f, + 2.39747f, 1.21726e-05f, 0.0f, 0.0f, 2.39747f, + 1.16039e-05f, 0.0f, 0.0f, 2.39747f, 1.10617e-05f, + 0.0f, 0.0f, 2.39747f, 1.05449e-05f, 0.0f, + 0.0f, 2.39747f, 1.00523e-05f, 0.0f, 0.0f, + 2.39747f, 9.58263e-06f, 0.0f, 0.0f, 2.39747f, + 9.13493e-06f, 0.0f, 0.0f, 2.39747f, 8.70814e-06f, + 0.0f, 0.0f, 2.39747f, 8.3013e-06f, 0.0f, + 0.0f, 2.39747f, 7.91346e-06f, 0.0f, 0.0f, + 2.39747f, 7.54374e-06f, 0.0f, 0.0f, 2.39747f, + 7.1913e-06f, 0.0f, 0.0f, 2.39747f, 6.85532e-06f, + 0.0f, 0.0f, 2.39747f, 6.53504e-06f, 0.0f, + 0.0f, 2.39747f, 6.22972e-06f, 0.0f, 0.0f, + 2.39747f, 5.93867e-06f, 0.0f, 0.0f, 2.39747f, + 5.66121e-06f, 0.0f, 0.0f, 2.39747f, 5.39672e-06f, + 0.0f, 0.0f, 2.39747f, 5.14458e-06f, 0.0f, + 0.0f, 2.39747f, 4.90423e-06f, 0.0f, 0.0f, + 2.39747f, 4.6751e-06f, 0.0f, 0.0f, 2.39747f, + 4.45668e-06f, 0.0f, 0.0f, 2.39747f, 4.24846e-06f, + 0.0f, 0.0f, 2.39747f, 4.04997e-06f, 0.0f, + 0.0f, 2.39747f, 3.86076e-06f, 0.0f, 0.0f, + 2.39747f, 3.68038e-06f, 0.0f, 0.0f, 2.39747f, + 3.50843e-06f, 0.0f, 0.0f, 2.39747f, 3.34452e-06f, + 0.0f, 0.0f, 2.39747f, 3.18826e-06f, 0.0f, + 0.0f, 2.39747f, 3.03931e-06f, 0.0f, 0.0f, + 2.39747f, 2.89731e-06f, 0.0f, 0.0f, 2.39747f, + 2.76195e-06f, 0.0f, 0.0f, 2.39747f, 2.63291e-06f, + 0.0f, 0.0f, 2.39747f, 2.5099e-06f, 0.0f, + 0.0f, 2.39747f, 2.39263e-06f, 0.0f, 0.0f, + 2.39747f, 2.28085e-06f, 0.0f, 0.0f, 2.39747f, + 2.17429e-06f, 0.0f, 0.0f, 2.39747f, 2.0727e-06f, + 0.0f, 0.0f, 2.39747f, 1.97587e-06f, 0.0f, + 0.0f, 2.39747f, 1.88355e-06f, 0.0f, 0.0f, + 2.39747f, 1.79555e-06f, 0.0f, 0.0f, 2.39747f, + 1.71167e-06f, 0.0f, 0.0f, 2.39747f, 1.6317e-06f, + 0.0f, 0.0f, 2.39747f, 1.55546e-06f, 0.0f, + 0.0f, 2.39747f, 1.48279e-06f, 0.0f, 0.0f, + 2.39747f, 1.41352e-06f, 0.0f, 0.0f, 2.39747f, + 1.34748e-06f, 0.0f, 0.0f}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_AMBISONICS_AMBISONIC_SPREAD_COEFFICIENTS_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/ambisonics/associated_legendre_polynomials_generator.cc b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/associated_legendre_polynomials_generator.cc new file mode 100644 index 000000000..49b7abf89 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/associated_legendre_polynomials_generator.cc @@ -0,0 +1,147 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "ambisonics/associated_legendre_polynomials_generator.h" + +#include <cmath> + +#include "base/logging.h" +#include "base/misc_math.h" + +namespace vraudio { + +AssociatedLegendrePolynomialsGenerator::AssociatedLegendrePolynomialsGenerator( + int max_degree, bool condon_shortley_phase, bool compute_negative_order) + : max_degree_(max_degree), + condon_shortley_phase_(condon_shortley_phase), + compute_negative_order_(compute_negative_order) { + DCHECK_GE(max_degree_, 0); +} + +std::vector<float> AssociatedLegendrePolynomialsGenerator::Generate( + float x) const { + std::vector<float> values(GetNumValues()); + + // Bases for the recurrence relations. + values[GetIndex(0, 0)] = ComputeValue(0, 0, x, values); + if (max_degree_ >= 1) values[GetIndex(1, 0)] = ComputeValue(1, 0, x, values); + + // Using recurrence relations, we now compute the rest of the values needed. + // (degree, 0), based on (degree - 1, 0) and (degree - 2, 0): + for (int degree = 2; degree <= max_degree_; ++degree) { + const int order = 0; + values[GetIndex(degree, order)] = ComputeValue(degree, order, x, values); + } + // (degree, degree): + for (int degree = 1; degree <= max_degree_; ++degree) { + const int order = degree; + values[GetIndex(degree, order)] = ComputeValue(degree, order, x, values); + } + // (degree, degree - 1): + for (int degree = 2; degree <= max_degree_; ++degree) { + const int order = degree - 1; + values[GetIndex(degree, order)] = ComputeValue(degree, order, x, values); + } + // The remaining positive orders, based on (degree - 1, order) and + // (degree - 2, order): + for (int degree = 3; degree <= max_degree_; ++degree) { + for (int order = 1; order <= degree - 2; ++order) { + values[GetIndex(degree, order)] = ComputeValue(degree, order, x, values); + } + } + // (degree, -order): + if (compute_negative_order_) { + for (int degree = 1; degree <= max_degree_; ++degree) { + for (int order = 1; order <= degree; ++order) { + values[GetIndex(degree, -order)] = + ComputeValue(degree, -order, x, values); + } + } + } + if (!condon_shortley_phase_) { + for (int degree = 1; degree <= max_degree_; ++degree) { + const int start_order = compute_negative_order_ ? -degree : 0; + for (int order = start_order; order <= degree; ++order) { + // Undo the Condon-Shortley phase. + values[GetIndex(degree, order)] *= + static_cast<float>(std::pow(-1, order)); + } + } + } + return values; +} + +size_t AssociatedLegendrePolynomialsGenerator::GetNumValues() const { + if (compute_negative_order_) + return (max_degree_ + 1) * (max_degree_ + 1); + else + return ((max_degree_ + 1) * (max_degree_ + 2)) / 2; +} + +size_t AssociatedLegendrePolynomialsGenerator::GetIndex(int degree, + int order) const { + CheckIndexValidity(degree, order); + size_t result; + if (compute_negative_order_) { + result = static_cast<size_t>(degree * (degree + 1) + order); + } else { + result = static_cast<size_t>((degree * (degree + 1)) / 2 + order); + } + DCHECK_GE(result, 0U); + DCHECK_LT(result, GetNumValues()); + return result; +} + +float AssociatedLegendrePolynomialsGenerator::ComputeValue( + int degree, int order, float x, const std::vector<float>& values) const { + CheckIndexValidity(degree, order); + if (degree == 0 && order == 0) { + return 1; + } else if (degree == 1 && order == 0) { + return x; + } else if (degree == order) { + return std::pow(-1.0f, static_cast<float>(degree)) * + DoubleFactorial(2 * degree - 1) * + std::pow((1.0f - x * x), 0.5f * static_cast<float>(degree)); + } else if (order == degree - 1) { + return x * static_cast<float>(2 * degree - 1) * + values[GetIndex(degree - 1, degree - 1)]; + } else if (order < 0) { + return std::pow(-1.0f, static_cast<float>(order)) * + Factorial(degree + order) / Factorial(degree - order) * + values[GetIndex(degree, -order)]; + } else { + return (static_cast<float>(2 * degree - 1) * x * + values[GetIndex(degree - 1, order)] - + static_cast<float>(degree - 1 + order) * + values[GetIndex(degree - 2, order)]) / + static_cast<float>(degree - order); + } +} + +void AssociatedLegendrePolynomialsGenerator::CheckIndexValidity( + int degree, int order) const { + DCHECK_GE(degree, 0); + DCHECK_LE(degree, max_degree_); + if (compute_negative_order_) { + DCHECK_LE(-degree, order); + } else { + DCHECK_GE(order, 0); + } + DCHECK_LE(order, degree); +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/ambisonics/associated_legendre_polynomials_generator.h b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/associated_legendre_polynomials_generator.h new file mode 100644 index 000000000..84eb9d2f6 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/associated_legendre_polynomials_generator.h @@ -0,0 +1,89 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_AMBISONICS_ASSOCIATED_LEGENDRE_POLYNOMIALS_GENERATOR_H_ +#define RESONANCE_AUDIO_AMBISONICS_ASSOCIATED_LEGENDRE_POLYNOMIALS_GENERATOR_H_ + +#include <stddef.h> +#include <vector> + +namespace vraudio { + +// Generates associated Legendre polynomials. +class AssociatedLegendrePolynomialsGenerator { + public: + // Constructs a generator for associated Legendre polynomials (ALP). + // + // @param max_degree The maximum ALP degree supported by this generator. + // @param condon_shortley_phase Whether the Condon-Shortley phase, (-1)^order, + // should be included in the polynomials generated. + // @param compute_negative_order Whether this generator should compute + // negative-ordered polynomials. + AssociatedLegendrePolynomialsGenerator(int max_degree, + bool condon_shortley_phase, + bool compute_negative_order); + + // Generates the associated Legendre polynomials for the given |x|, returning + // the computed sequence. + // + // @param x The abscissa (the polynomials' variable). + // @return Output vector of computed values. + std::vector<float> Generate(float x) const; + + // Gets the number of associated Legendre polynomials this generator produces. + // + // @return The number of associated Legendre polynomials this generator + // produces. + size_t GetNumValues() const; + + // Gets the index into the output vector for the given |degree| and |order|. + // + // @param degree The polynomial's degree. + // @param order The polynomial's order. + // @return The index into the vector of computed values corresponding to the + // specified ALP. + size_t GetIndex(int degree, int order) const; + + private: + // Computes the ALP for (degree, order) the given |x| using recurrence + // relations. It is assumed that the ALPs necessary for each computation are + // already computed and stored in |values|. + // + // @param degree The degree of the polynomial being computed. + // @param degree The order of the polynomial being computed. + // @param values The previously computed values. + // @return The computed polynomial. + inline float ComputeValue(int degree, int order, float x, + const std::vector<float>& values) const; + + // Checks the validity of the given index. + // + // @param degree The polynomial's degree. + // @param order The polynomial's order. + inline void CheckIndexValidity(int degree, int order) const; + + // The maximum polynomial degree that can be computed; must be >= 0. + int max_degree_ = 0; + // Whether the Condon-Shortley phase, (-1)^order, should be included in the + // polynomials generated. + bool condon_shortley_phase_ = false; + // Whether this generator should compute negative-ordered polynomials. + bool compute_negative_order_ = false; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_AMBISONICS_ASSOCIATED_LEGENDRE_POLYNOMIALS_GENERATOR_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/ambisonics/associated_legendre_polynomials_generator_test.cc b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/associated_legendre_polynomials_generator_test.cc new file mode 100644 index 000000000..b9b6cdec4 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/associated_legendre_polynomials_generator_test.cc @@ -0,0 +1,167 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "ambisonics/associated_legendre_polynomials_generator.h" + +#include <cmath> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/constants_and_types.h" +#include "base/misc_math.h" + +namespace vraudio { + +namespace { + +// Tolerated test error margin for single-precision floating points. +const float kEpsilon = 1e-5f; + +// Generates associated Legendre polynomials up to and including the 4th degree, +// with the Condon-Shortley phase and negative orders included. +std::vector<float> GenerateExpectedValuesFourthDegree(float x) { + // From http://en.wikipedia.org/wiki/Associated_Legendre_polynomials + // Comments are (degree, order). + const std::vector<float> expected_values = { + 1.0f, // (0, 0) + 0.5f * std::sqrt(1.0f - x * x), // (1, -1) + x, // (1, 0) + -std::sqrt(1.0f - x * x), // (1, 1) + 1.0f / 8.0f * (1.0f - x * x), // (2, -2) + 0.5f * x * std::sqrt(1.0f - x * x), // (2, -1) + 0.5f * (3.0f * x * x - 1.0f), // (2, 0) + -3.0f * x * std::sqrt(1.0f - x * x), // (2, 1) + 3.0f * (1.0f - x * x), // (2, 2) + 15.0f / 720.0f * std::pow(1.0f - x * x, 3.0f / 2.0f), // (3, -3) + 15.0f / 120.0f * x * (1.0f - x * x), // (3, -2) + 3.0f / 24.0f * (5.0f * x * x - 1.0f) * + std::sqrt(1.0f - x * x), // (3, -1) + 0.5f * (5.0f * IntegerPow(x, 3) - 3.0f * x), // (3, 0) + -3.0f / 2.0f * (5.0f * x * x - 1.0f) * std::sqrt(1.0f - x * x), // (3, 1) + 15.0f * x * (1.0f - x * x), // (3, 2) + -15.0f * std::pow(1.0f - x * x, 3.0f / 2.0f), // (3, 3) + 105.0f / 40320.0f * IntegerPow(1.0f - x * x, 2), // (4, -4) + 105.0f / 5040.0f * x * std::pow(1.0f - x * x, 3.0f / 2.0f), // (4, -3) + 15.0f / 720.0f * (7.0f * x * x - 1.0f) * (1.0f - x * x), // (4, -2) + 5.0f / 40.0f * (7.0f * IntegerPow(x, 3) - 3.0f * x) * + std::sqrt(1.0f - x * x), // (4, -1) + 1.0f / 8.0f * + (35.0f * IntegerPow(x, 4) - 30.0f * x * x + 3.0f), // (4, 0) + -5.0f / 2.0f * (7.0f * IntegerPow(x, 3) - 3.0f * x) * + std::sqrt(1.0f - x * x), // (4, 1) + 15.0f / 2.0f * (7.0f * x * x - 1.0f) * (1.0f - x * x), // (4, 2) + -105.0f * x * std::pow(1.0f - x * x, 3.0f / 2.0f), // (4, 3) + 105.0f * IntegerPow(1.0f - x * x, 2) // (4, 4) + }; + + return expected_values; +} + +// Tests that the values given by GetIndex are successive indices (n, n+1, n+2, +// and so on). +TEST(AssociatedLegendrePolynomialsGeneratorTest, GetIndex_SuccessiveIndices) { + const int kMaxDegree = 5; + const AssociatedLegendrePolynomialsGenerator alp_generator(kMaxDegree, false, + true); + int last_index = -1; + for (int degree = 0; degree <= kMaxDegree; ++degree) { + for (int order = -degree; order <= degree; ++order) { + int index = static_cast<int>(alp_generator.GetIndex(degree, order)); + EXPECT_EQ(last_index + 1, index); + last_index = index; + } + } +} + +// Tests that the zeroth-degree, zeroth-order ALP is always 1. +TEST(AssociatedLegendrePolynomialsGeneratorTest, Generate_ZerothElementIsOne) { + const int kMaxDegree = 10; + for (int max_degree = 0; max_degree <= kMaxDegree; ++max_degree) { + for (int condon_shortley_phase = 0; condon_shortley_phase <= 1; + ++condon_shortley_phase) { + for (int compute_negative_order = 0; compute_negative_order <= 1; + ++compute_negative_order) { + AssociatedLegendrePolynomialsGenerator alp_generator( + max_degree, condon_shortley_phase != 0, + compute_negative_order != 0); + + const float kVariableStep = 0.2f; + for (float x = -1.0f; x <= 1.0f; x += kVariableStep) { + const std::vector<float> values = alp_generator.Generate(x); + + EXPECT_NEAR(values[0], 1.0f, kEpsilon); + } + } + } + } +} + +// Tests that the polynomials generated are correct until the 4th degree. +TEST(AssociatedLegendrePolynomialsGeneratorTest, Generate_CorrectFourthDegree) { + const int kMaxDegree = 4; + const bool kCondonShortleyPhase = true; + const bool kComputeNegativeOrder = true; + const AssociatedLegendrePolynomialsGenerator alp_generator( + kMaxDegree, kCondonShortleyPhase, kComputeNegativeOrder); + + const float kVariableStep = 0.05f; + for (float x = -1.0f; x <= 1.0f; x += kVariableStep) { + const std::vector<float> generated_values = alp_generator.Generate(x); + const std::vector<float> expected_values = + GenerateExpectedValuesFourthDegree(x); + ASSERT_EQ(expected_values.size(), generated_values.size()); + for (size_t i = 0; i < expected_values.size(); ++i) { + EXPECT_NEAR(generated_values[i], expected_values[i], kEpsilon) + << " at index " << i; + } + } +} + +// Tests that the Condon-Shortley phase is correctly applied. +TEST(AssociatedLegendrePolynomialsGeneratorTest, Generate_CondonShortleyPhase) { + const int kMaxDegree = 10; + const float kValue = 0.12345f; + for (int max_degree = 0; max_degree <= kMaxDegree; ++max_degree) { + for (int compute_negative_order = 0; compute_negative_order <= 1; + ++compute_negative_order) { + const AssociatedLegendrePolynomialsGenerator alp_generator_without_phase( + max_degree, false, compute_negative_order != 0); + const std::vector<float> values_without_phase = + alp_generator_without_phase.Generate(kValue); + + const AssociatedLegendrePolynomialsGenerator alp_generator_with_phase( + max_degree, true, compute_negative_order != 0); + const std::vector<float> values_with_phase = + alp_generator_with_phase.Generate(kValue); + + ASSERT_EQ(values_with_phase.size(), values_without_phase.size()); + for (int degree = 0; degree <= max_degree; ++degree) { + const int start_order = compute_negative_order ? -degree : 0; + for (int order = start_order; order <= degree; ++order) { + const size_t index = + alp_generator_without_phase.GetIndex(degree, order); + const float expected = values_without_phase[index] * + std::pow(-1.0f, static_cast<float>(order)); + EXPECT_NEAR(values_with_phase[index], expected, kEpsilon) + << " at degree " << degree << " and order " << order; + } + } + } + } +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/ambisonics/foa_rotator.cc b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/foa_rotator.cc new file mode 100644 index 000000000..f1ac68118 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/foa_rotator.cc @@ -0,0 +1,117 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#if defined(_WIN32) +#define _SCL_SECURE_NO_WARNINGS +#endif + +#include "ambisonics/foa_rotator.h" + +#include <algorithm> + +#include "base/constants_and_types.h" +#include "base/misc_math.h" + +namespace vraudio { + +bool FoaRotator::Process(const WorldRotation& target_rotation, + const AudioBuffer& input, AudioBuffer* output) { + + DCHECK(output); + DCHECK_EQ(input.num_channels(), kNumFirstOrderAmbisonicChannels); + DCHECK_EQ(input.num_channels(), output->num_channels()); + DCHECK_EQ(input.num_frames(), output->num_frames()); + + static const WorldRotation kIdentityRotation; + + if (current_rotation_.AngularDifferenceRad(kIdentityRotation) < + kRotationQuantizationRad && + target_rotation.AngularDifferenceRad(kIdentityRotation) < + kRotationQuantizationRad) { + return false; + } + + if (current_rotation_.AngularDifferenceRad(target_rotation) < + kRotationQuantizationRad) { + // Rotate the whole input buffer frame by frame. + Rotate(current_rotation_, 0, input.num_frames(), input, output); + return true; + } + + // In order to perform a smooth rotation, we divide the buffer into + // chunks of size |kSlerpFrameInterval|. + // + + const size_t kSlerpFrameInterval = 32; + + WorldRotation slerped_rotation; + // Rotate the input buffer at every slerp update interval. Truncate the + // final chunk if the input buffer is not an integer multiple of the + // chunk size. + for (size_t i = 0; i < input.num_frames(); i += kSlerpFrameInterval) { + const size_t duration = + std::min(input.num_frames() - i, kSlerpFrameInterval); + const float interpolation_factor = static_cast<float>(i + duration) / + static_cast<float>(input.num_frames()); + slerped_rotation = + current_rotation_.slerp(interpolation_factor, target_rotation); + // Rotate the input buffer frame by frame within the current chunk. + Rotate(slerped_rotation, i, duration, input, output); + } + + current_rotation_ = target_rotation; + + return true; +} + +void FoaRotator::Rotate(const WorldRotation& target_rotation, + size_t start_location, size_t duration, + const AudioBuffer& input, AudioBuffer* output) { + + const AudioBuffer::Channel& input_channel_audio_space_w = input[0]; + const AudioBuffer::Channel& input_channel_audio_space_y = input[1]; + const AudioBuffer::Channel& input_channel_audio_space_z = input[2]; + const AudioBuffer::Channel& input_channel_audio_space_x = input[3]; + AudioBuffer::Channel* output_channel_audio_space_w = &(*output)[0]; + AudioBuffer::Channel* output_channel_audio_space_y = &(*output)[1]; + AudioBuffer::Channel* output_channel_audio_space_z = &(*output)[2]; + AudioBuffer::Channel* output_channel_audio_space_x = &(*output)[3]; + + for (size_t frame = start_location; frame < start_location + duration; + ++frame) { + // Convert the current audio frame into world space position. + temp_audio_position_(0) = input_channel_audio_space_x[frame]; + temp_audio_position_(1) = input_channel_audio_space_y[frame]; + temp_audio_position_(2) = input_channel_audio_space_z[frame]; + ConvertWorldFromAudioPosition(temp_audio_position_, &temp_world_position_); + // Apply rotation to |world_position| and return to audio space. + temp_rotated_world_position_ = target_rotation * temp_world_position_; + + ConvertAudioFromWorldPosition(temp_rotated_world_position_, + &temp_rotated_audio_position_); + (*output_channel_audio_space_x)[frame] = + temp_rotated_audio_position_(0); // X + (*output_channel_audio_space_y)[frame] = + temp_rotated_audio_position_(1); // Y + (*output_channel_audio_space_z)[frame] = + temp_rotated_audio_position_(2); // Z + } + // Copy W channel. + std::copy_n(&input_channel_audio_space_w[start_location], duration, + &(*output_channel_audio_space_w)[start_location]); +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/ambisonics/foa_rotator.h b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/foa_rotator.h new file mode 100644 index 000000000..a91b975d4 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/foa_rotator.h @@ -0,0 +1,61 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_AMBISONICS_FOA_ROTATOR_H_ +#define RESONANCE_AUDIO_AMBISONICS_FOA_ROTATOR_H_ + +#include "base/audio_buffer.h" +#include "base/spherical_angle.h" + +namespace vraudio { + +// Rotator for first order ambisonic soundfields. It supports ACN channel +// ordering and SN3D normalization (AmbiX). +class FoaRotator { + public: + // @param target_rotation Target rotation to be applied to the input buffer. + // @param input First order soundfield input buffer to be rotated. + // @param output Pointer to output buffer. + // @return True if rotation has been applied. + bool Process(const WorldRotation& target_rotation, const AudioBuffer& input, + AudioBuffer* output); + + private: + // Method which rotates a specified chunk of data in the AudioBuffer. + // + // @param target_rotation Target rotation to be applied to the soundfield. + // @param start_location Sample index in the soundfield where the rotation + // should begin. + // @param duration Number of samples in soundfield to be rotated. + // @param input First order soundfield input buffer to be rotated. + // @param output Pointer to output buffer. + void Rotate(const WorldRotation& target_rotation, size_t start_location, + size_t duration, const AudioBuffer& input, AudioBuffer* output); + + // Current rotation which is used in the interpolation process in order to + // perform a smooth rotation. Initialized with an identity matrix. + WorldRotation current_rotation_; + + // Preallocation of temporary variables used during rotation. + AudioPosition temp_audio_position_; + WorldPosition temp_world_position_; + AudioPosition temp_rotated_audio_position_; + WorldPosition temp_rotated_world_position_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_AMBISONICS_FOA_ROTATOR_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/ambisonics/foa_rotator_test.cc b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/foa_rotator_test.cc new file mode 100644 index 000000000..e2f957fa2 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/foa_rotator_test.cc @@ -0,0 +1,201 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "ambisonics/foa_rotator.h" + +#include <algorithm> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "ambisonics/ambisonic_codec_impl.h" +#include "base/constants_and_types.h" +#include "utils/planar_interleaved_conversion.h" + +namespace vraudio { + +namespace { + +using testing::tuple; +using testing::Values; + +const int kAmbisonicOrder = 1; + +// Rotation angle used in the test. +const float kAngleDegrees = 90.0f; + +// Initial, arbitrary direction of the encoded soundfield source. +const SphericalAngle kInitialSourceAngle = + SphericalAngle::FromDegrees(22.0f, 33.0f); + +// Expected direction of the sound source rotated by |kAngleDegrees| against the +// right facing axis (x in the world coordinate system). +const SphericalAngle kXrotatedSourceAngle = + SphericalAngle::FromDegrees(150.021778639249f, 51.0415207997462f); + +// Expected direction of the sound source rotated by |kAngleDegrees| against the +// upward facing axis (y in the world coordinate system). +const SphericalAngle kYrotatedSourceAngle = + SphericalAngle::FromDegrees(112.0f, 33.0f); + +// Expected direction of the sound source rotated by |kAngleDegrees| against the +// back facing axis (z in the world coordinate system). +const SphericalAngle kZrotatedSourceAngle = + SphericalAngle::FromDegrees(35.0077312770459f, -18.3108067341351f); + +// Rotation interpolation interval in terms of frames (used by the FoaRotator). +const size_t kSlerpFrameInterval = 32; + +class FoaRotatorTest : public ::testing::Test { + protected: + FoaRotatorTest() {} + + // Virtual methods from ::testing::Test + ~FoaRotatorTest() override {} + + void SetUp() override { + // Initialize the first order soundfield rotator. + foa_rotator_ = std::unique_ptr<FoaRotator>(new FoaRotator); + } + + void TearDown() override {} + + // Test method which creates a soundfield buffer, rotates it by + // |rotation_angle| against |rotation_axis|, and compares the output to the + // reference soundfield buffer (where the source is spatialized at the + // |expected_source_angle|). + void CompareRotatedAndReferenceSoundfields( + const std::vector<float>& input_data, float rotation_angle, + const WorldPosition& rotation_axis, + const SphericalAngle& expected_source_angle) { + AudioBuffer input_buffer(1, input_data.size()); + FillAudioBuffer(input_data, 1, &input_buffer); + // Initialize a first order mono codec with the |kInitialSourceAngle|. This + // will be used to obtain the rotated soundfield. + std::unique_ptr<MonoAmbisonicCodec<>> rotated_source_mono_codec( + new MonoAmbisonicCodec<>(kAmbisonicOrder, {kInitialSourceAngle})); + // Initialize a first order mono codec with the expected source angle. This + // will be used as a reference for the rotated soundfield. + std::unique_ptr<MonoAmbisonicCodec<>> reference_source_mono_codec( + new MonoAmbisonicCodec<>(kAmbisonicOrder, {expected_source_angle})); + // Generate soundfield buffers representing sound sources at required + // angles. + AudioBuffer encoded_rotated_buffer(kNumFirstOrderAmbisonicChannels, + input_data.size()); + AudioBuffer encoded_reference_buffer(kNumFirstOrderAmbisonicChannels, + input_data.size()); + rotated_source_mono_codec->EncodeBuffer(input_buffer, + &encoded_rotated_buffer); + reference_source_mono_codec->EncodeBuffer(input_buffer, + &encoded_reference_buffer); + // Rotate the test soundfield by |rotation_angle| degrees wrt the given + // |rotation_axis|. + const WorldRotation rotation = WorldRotation( + AngleAxisf(rotation_angle * kRadiansFromDegrees, rotation_axis)); + const bool result = foa_rotator_->Process(rotation, encoded_rotated_buffer, + &encoded_rotated_buffer); + EXPECT_TRUE(result); + // Check if the sound source in the reference and rotated buffers are in the + // same direction. + // If the buffer size is more than |kSlerpFrameInterval|, due to + // interpolation, we expect that the last |kSlerpFrameInterval| frames have + // reached the target rotation. + // If the buffer size is less than |kSlerpFrameInterval|, because no + // interpolation is applied, the rotated soundfield should result from + // frame 0. + for (size_t channel = 0; channel < encoded_rotated_buffer.num_channels(); + ++channel) { + const int num_frames = + static_cast<int>(encoded_rotated_buffer[channel].size()); + const int interval = static_cast<int>(kSlerpFrameInterval); + const int frames_to_compare = + (num_frames % interval) ? (num_frames % interval) : interval; + const int start_frame = std::max(0, num_frames - frames_to_compare); + ASSERT_LT(start_frame, num_frames); + for (int frame = start_frame; frame < num_frames; ++frame) { + EXPECT_NEAR(encoded_rotated_buffer[channel][frame], + encoded_reference_buffer[channel][frame], kEpsilonFloat); + } + } + } + + // First Order Ambisonic rotator instance. + std::unique_ptr<FoaRotator> foa_rotator_; +}; + +// Tests that no rotation is aplied if |kRotationQuantizationRad| is not +// exceeded. +TEST_F(FoaRotatorTest, RotationThresholdTest) { + const size_t kFramesPerBuffer = 16; + const std::vector<float> kInputVector( + kFramesPerBuffer * kNumFirstOrderAmbisonicChannels, 1.0f); + AudioBuffer input_buffer(kNumFirstOrderAmbisonicChannels, kFramesPerBuffer); + FillAudioBuffer(kInputVector, kNumFirstOrderAmbisonicChannels, &input_buffer); + AudioBuffer output_buffer(kNumFirstOrderAmbisonicChannels, kFramesPerBuffer); + const WorldRotation kSmallRotation = + WorldRotation(1.0f, 0.001f, 0.001f, 0.001f); + const WorldRotation kLargeRotation = WorldRotation(1.0f, 0.1f, 0.1f, 0.1f); + EXPECT_FALSE( + foa_rotator_->Process(kSmallRotation, input_buffer, &output_buffer)); + EXPECT_TRUE( + foa_rotator_->Process(kLargeRotation, input_buffer, &output_buffer)); +} + +typedef tuple<WorldPosition, SphericalAngle> TestParams; +class FoaAxesRotationTest : public FoaRotatorTest, + public ::testing::WithParamInterface<TestParams> {}; + +// Tests first order soundfield rotation against the x, y and z axis using long +// input buffers (>> slerp interval). +TEST_P(FoaAxesRotationTest, CompareWithExpectedAngleLongBuffer) { + const WorldPosition& rotation_axis = ::testing::get<0>(GetParam()); + const SphericalAngle& expected_angle = ::testing::get<1>(GetParam()); + const size_t kLongFramesPerBuffer = 512; + const std::vector<float> kLongInputData(kLongFramesPerBuffer, 1.0f); + CompareRotatedAndReferenceSoundfields(kLongInputData, kAngleDegrees, + rotation_axis, expected_angle); +} + +// Tests first order soundfield rotation against the x, y and z axes using short +// input buffers (< slerp interval). +TEST_P(FoaAxesRotationTest, CompareWithExpectedAngleShortBuffer) { + const WorldPosition& rotation_axis = ::testing::get<0>(GetParam()); + const SphericalAngle& expected_angle = ::testing::get<1>(GetParam()); + const size_t kShortFramesPerBuffer = kSlerpFrameInterval / 2; + const std::vector<float> kShortInputData(kShortFramesPerBuffer, 1.0f); + CompareRotatedAndReferenceSoundfields(kShortInputData, kAngleDegrees, + rotation_axis, expected_angle); +} + +// Tests first order soundfield rotation against the x, y and z axes using +// buffer sizes that are (> slerp interval) and not integer multiples of +// the slerp interval. +TEST_P(FoaAxesRotationTest, CompareWithExpectedAngleOddBufferSizes) { + const WorldPosition& rotation_axis = ::testing::get<0>(GetParam()); + const SphericalAngle& expected_angle = ::testing::get<1>(GetParam()); + const size_t frames_per_buffer = kSlerpFrameInterval + 3U; + const std::vector<float> kShortInputData(frames_per_buffer, 1.0f); + CompareRotatedAndReferenceSoundfields(kShortInputData, kAngleDegrees, + rotation_axis, expected_angle); +} + +INSTANTIATE_TEST_CASE_P( + TestParameters, FoaAxesRotationTest, + Values(TestParams({1.0f, 0.0f, 0.0f}, kXrotatedSourceAngle), + TestParams({0.0f, 1.0f, 0.0f}, kYrotatedSourceAngle), + TestParams({0.0f, 0.0f, 1.0f}, kZrotatedSourceAngle))); + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/ambisonics/hoa_rotator.cc b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/hoa_rotator.cc new file mode 100644 index 000000000..a76575e22 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/hoa_rotator.cc @@ -0,0 +1,308 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "ambisonics/hoa_rotator.h" + +#include <algorithm> +#include <cmath> + +#include "ambisonics/utils.h" +#include "base/constants_and_types.h" +#include "base/logging.h" +#include "base/misc_math.h" + +namespace vraudio { + +namespace { + +// Below are the helper methods to compute SH rotation using recursion. The code +// is branched / modified from: + +// maths described in the following papers: +// +// [1] R. Green, "Spherical Harmonic Lighting: The Gritty Details", GDC 2003, +// http://www.research.scea.com/gdc2003/spherical-harmonic-lighting.pdf +// [2] J. Ivanic and K. Ruedenberg, "Rotation Matrices for Real Spherical +// Harmonics. Direct Determination by Recursion", J. Phys. Chem., vol. 100, +// no. 15, pp. 6342-6347, 1996. +// http://pubs.acs.org/doi/pdf/10.1021/jp953350u +// [2b] Corrections to initial publication: +// http://pubs.acs.org/doi/pdf/10.1021/jp9833350 + +// Kronecker Delta function. +inline float KroneckerDelta(int i, int j) { return (i == j) ? 1.0f : 0.0f; } + +// [2] uses an odd convention of referring to the rows and columns using +// centered indices, so the middle row and column are (0, 0) and the upper +// left would have negative coordinates. +// +// This is a convenience function to allow us to access an Eigen::MatrixXf +// in the same manner, assuming r is a (2l+1)x(2l+1) matrix. +float GetCenteredElement(const Eigen::MatrixXf& r, int i, int j) { + // The shift to go from [-l, l] to [0, 2l] is (rows - 1) / 2 = l, + // (since the matrix is assumed to be square, rows == cols). + const int offset = (static_cast<int>(r.rows()) - 1) / 2; + return r(i + offset, j + offset); +} + +// Helper function defined in [2] that is used by the functions U, V, W. +// This should not be called on its own, as U, V, and W (and their coefficients) +// select the appropriate matrix elements to access arguments |a| and |b|. +float P(int i, int a, int b, int l, const std::vector<Eigen::MatrixXf>& r) { + if (b == l) { + return GetCenteredElement(r[1], i, 1) * + GetCenteredElement(r[l - 1], a, l - 1) - + GetCenteredElement(r[1], i, -1) * + GetCenteredElement(r[l - 1], a, -l + 1); + } else if (b == -l) { + return GetCenteredElement(r[1], i, 1) * + GetCenteredElement(r[l - 1], a, -l + 1) + + GetCenteredElement(r[1], i, -1) * + GetCenteredElement(r[l - 1], a, l - 1); + } else { + return GetCenteredElement(r[1], i, 0) * GetCenteredElement(r[l - 1], a, b); + } +} + +// The functions U, V, and W should only be called if the correspondingly +// named coefficient u, v, w from the function ComputeUVWCoeff() is non-zero. +// When the coefficient is 0, these would attempt to access matrix elements that +// are out of bounds. The vector of rotations, |r|, must have the |l - 1| +// previously completed band rotations. These functions are valid for |l >= 2|. + +float U(int m, int n, int l, const std::vector<Eigen::MatrixXf>& r) { + // Although [1, 2] split U into three cases for m == 0, m < 0, m > 0 + // the actual values are the same for all three cases. + return P(0, m, n, l, r); +} + +float V(int m, int n, int l, const std::vector<Eigen::MatrixXf>& r) { + if (m == 0) { + return P(1, 1, n, l, r) + P(-1, -1, n, l, r); + } else if (m > 0) { + const float d = KroneckerDelta(m, 1); + return P(1, m - 1, n, l, r) * std::sqrt(1 + d) - + P(-1, -m + 1, n, l, r) * (1 - d); + } else { + // Note there is apparent errata in [1,2,2b] dealing with this particular + // case. [2b] writes it should be P*(1-d)+P*(1-d)^0.5 + // [1] writes it as P*(1+d)+P*(1-d)^0.5, but going through the math by hand, + // you must have it as P*(1-d)+P*(1+d)^0.5 to form a 2^.5 term, which + // parallels the case where m > 0. + const float d = KroneckerDelta(m, -1); + return P(1, m + 1, n, l, r) * (1 - d) + + P(-1, -m - 1, n, l, r) * std::sqrt(1 + d); + } +} + +float W(int m, int n, int l, const std::vector<Eigen::MatrixXf>& r) { + if (m == 0) { + // Whenever this happens, w is also 0 so W can be anything. + return 0.0f; + } else if (m > 0) { + return P(1, m + 1, n, l, r) + P(-1, -m - 1, n, l, r); + } else { + return P(1, m - 1, n, l, r) - P(-1, -m + 1, n, l, r); + } +} + +// Calculates the coefficients applied to the U, V, and W functions. Because +// their equations share many common terms they are computed simultaneously. +void ComputeUVWCoeff(int m, int n, int l, float* u, float* v, float* w) { + const float d = KroneckerDelta(m, 0); + const float denom = (abs(n) == l ? static_cast<float>(2 * l * (2 * l - 1)) + : static_cast<float>((l + n) * (l - n))); + const float one_over_denom = 1.0f / denom; + + *u = std::sqrt(static_cast<float>((l + m) * (l - m)) * one_over_denom); + *v = 0.5f * + std::sqrt((1.0f + d) * static_cast<float>(l + abs(m) - 1) * + (static_cast<float>(l + abs(m))) * one_over_denom) * + (1.0f - 2.0f * d); + *w = -0.5f * + std::sqrt(static_cast<float>(l - abs(m) - 1) * + (static_cast<float>(l - abs(m))) * one_over_denom) * + (1.0f - d); +} + +// Calculates the (2l+1)x(2l+1) rotation matrix for the band l. +// This uses the matrices computed for band 1 and band l-1 to compute the +// matrix for band l. |rotations| must contain the previously computed l-1 +// rotation matrices. +// +// This implementation comes from p. 5 (6346), Table 1 and 2 in [2] taking +// into account the corrections from [2b]. +void ComputeBandRotation(int l, std::vector<Eigen::MatrixXf>* rotations) { + // The lth band rotation matrix has rows and columns equal to the number of + // coefficients within that band (-l <= m <= l implies 2l + 1 coefficients). + Eigen::MatrixXf rotation(2 * l + 1, 2 * l + 1); + for (int m = -l; m <= l; ++m) { + for (int n = -l; n <= l; ++n) { + float u, v, w; + ComputeUVWCoeff(m, n, l, &u, &v, &w); + + // The functions U, V, W are only safe to call if the coefficients + // u, v, w are not zero. + if (std::abs(u) > 0.0f) u *= U(m, n, l, *rotations); + if (std::abs(v) > 0.0f) v *= V(m, n, l, *rotations); + if (std::abs(w) > 0.0f) w *= W(m, n, l, *rotations); + + rotation(m + l, n + l) = (u + v + w); + } + } + (*rotations)[l] = rotation; +} + +} // namespace + +HoaRotator::HoaRotator(int ambisonic_order) + : ambisonic_order_(ambisonic_order), + rotation_matrices_(ambisonic_order_ + 1), + rotation_matrix_( + static_cast<int>(GetNumPeriphonicComponents(ambisonic_order)), + static_cast<int>(GetNumPeriphonicComponents(ambisonic_order))) { + DCHECK_GE(ambisonic_order_, 2); + + // Initialize rotation sub-matrices. + // Order 0 matrix (first band) is simply the 1x1 identity. + Eigen::MatrixXf r(1, 1); + r(0, 0) = 1.0f; + rotation_matrices_[0] = r; + // All the other ambisonic orders (bands) are set to identity matrices of + // corresponding sizes. + for (int l = 1; l <= ambisonic_order_; ++l) { + const size_t submatrix_size = GetNumNthOrderPeriphonicComponents(l); + r.resize(submatrix_size, submatrix_size); + rotation_matrices_[l] = r.setIdentity(); + } + // Initialize the final rotation matrix to an identity matrix. + rotation_matrix_.setIdentity(); +} + +bool HoaRotator::Process(const WorldRotation& target_rotation, + const AudioBuffer& input, AudioBuffer* output) { + + DCHECK(output); + DCHECK_EQ(input.num_channels(), GetNumPeriphonicComponents(ambisonic_order_)); + DCHECK_EQ(input.num_channels(), output->num_channels()); + DCHECK_EQ(input.num_frames(), output->num_frames()); + + static const WorldRotation kIdentityRotation; + + if (current_rotation_.AngularDifferenceRad(kIdentityRotation) < + kRotationQuantizationRad && + target_rotation.AngularDifferenceRad(kIdentityRotation) < + kRotationQuantizationRad) { + return false; + } + + const size_t channel_stride = input.GetChannelStride(); + + typedef Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> + RowMajorMatrixf; + + const Eigen::Map<const RowMajorMatrixf, Eigen::Aligned, Eigen::OuterStride<>> + input_matrix(input[0].begin(), static_cast<int>(input.num_channels()), + static_cast<int>(input.num_frames()), + Eigen::OuterStride<>(static_cast<int>(channel_stride))); + + Eigen::Map<RowMajorMatrixf, Eigen::Aligned, Eigen::OuterStride<>> + output_matrix((*output)[0].begin(), + static_cast<int>(input.num_channels()), + static_cast<int>(input.num_frames()), + Eigen::OuterStride<>(static_cast<int>(channel_stride))); + + if (current_rotation_.AngularDifferenceRad(target_rotation) < + kRotationQuantizationRad) { + output_matrix = rotation_matrix_ * input_matrix; + return true; + } + + // In order to perform a smooth rotation, we divide the buffer into + // chunks of size |kSlerpFrameInterval|. + // + + const size_t kSlerpFrameInterval = 32; + + WorldRotation slerped_rotation; + // Rotate the input buffer at every slerp update interval. Truncate the + // final chunk if the input buffer is not an integer multiple of the + // chunk size. + for (size_t i = 0; i < input.num_frames(); i += kSlerpFrameInterval) { + const size_t duration = + std::min(input.num_frames() - i, kSlerpFrameInterval); + const float interpolation_factor = static_cast<float>(i + duration) / + static_cast<float>(input.num_frames()); + UpdateRotationMatrix( + current_rotation_.slerp(interpolation_factor, target_rotation)); + output_matrix.block(0 /* first channel */, i, output->num_channels(), + duration) = + rotation_matrix_ * input_matrix.block(0 /* first channel */, i, + input.num_channels(), duration); + } + current_rotation_ = target_rotation; + + return true; +} + +void HoaRotator::UpdateRotationMatrix(const WorldRotation& rotation) { + + + // There is no need to update 0th order 1-element sub-matrix. + // First order sub-matrix can be updated directly from the WorldRotation + // quaternion. However, we must account for the flipped left-right and + // front-back axis in the World coordinates. + AudioRotation rotation_audio_space; + ConvertAudioFromWorldRotation(rotation, &rotation_audio_space); + rotation_matrices_[1] = rotation_audio_space.toRotationMatrix(); + rotation_matrix_.block(1, 1, 3, 3) = rotation_matrices_[1]; + + // Sub-matrices for the remaining orders are updated recursively using the + // equations provided in [2, 2b]. An example final rotation matrix composed of + // sub-matrices of orders 0 to 3 has the following structure: + // + // X | 0 0 0 | 0 0 0 0 0 | 0 0 0 0 0 0 0 + // ------------------------------------- + // 0 | X X X | 0 0 0 0 0 | 0 0 0 0 0 0 0 + // 0 | X X X | 0 0 0 0 0 | 0 0 0 0 0 0 0 + // 0 | X X X | 0 0 0 0 0 | 0 0 0 0 0 0 0 + // ------------------------------------- + // 0 | 0 0 0 | X X X X X | 0 0 0 0 0 0 0 + // 0 | 0 0 0 | X X X X X | 0 0 0 0 0 0 0 + // 0 | 0 0 0 | X X X X X | 0 0 0 0 0 0 0 + // 0 | 0 0 0 | X X X X X | 0 0 0 0 0 0 0 + // 0 | 0 0 0 | X X X X X | 0 0 0 0 0 0 0 + // ------------------------------------- + // 0 | 0 0 0 | 0 0 0 0 0 | X X X X X X X + // 0 | 0 0 0 | 0 0 0 0 0 | X X X X X X X + // 0 | 0 0 0 | 0 0 0 0 0 | X X X X X X X + // 0 | 0 0 0 | 0 0 0 0 0 | X X X X X X X + // 0 | 0 0 0 | 0 0 0 0 0 | X X X X X X X + // 0 | 0 0 0 | 0 0 0 0 0 | X X X X X X X + // 0 | 0 0 0 | 0 0 0 0 0 | X X X X X X X + // + for (int current_order = 2; current_order <= ambisonic_order_; + ++current_order) { + ComputeBandRotation(current_order, &rotation_matrices_); + const int index = current_order * current_order; + const int size = + static_cast<int>(GetNumNthOrderPeriphonicComponents(current_order)); + rotation_matrix_.block(index, index, size, size) = + rotation_matrices_[current_order]; + } +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/ambisonics/hoa_rotator.h b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/hoa_rotator.h new file mode 100644 index 000000000..956811df4 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/hoa_rotator.h @@ -0,0 +1,69 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_AMBISONICS_HOA_ROTATOR_H_ +#define RESONANCE_AUDIO_AMBISONICS_HOA_ROTATOR_H_ + +#include <vector> + +#include "Eigen/Dense" +#include "base/audio_buffer.h" +#include "base/misc_math.h" + +namespace vraudio { + +// Rotator for higher order ambisonic sound fields. It supports ACN channel +// ordering and SN3D normalization (AmbiX). +class HoaRotator { + public: + // Constructs a sound field rotator of an arbitrary ambisonic order. + // + // @param ambisonic_order Order of ambisonic sound field. + explicit HoaRotator(int ambisonic_order); + + // Performs a smooth inplace rotation of a sound field buffer from + // |current_rotation_| to |target_rotation|. + // + // @param target_rotation Target rotation to be applied to the input buffer. + // @param input Higher order sound field input buffer to be rotated. + // @param output Pointer to output buffer. + // @return True if rotation has been applied. + bool Process(const WorldRotation& target_rotation, const AudioBuffer& input, + AudioBuffer* output); + + private: + // Updates the rotation matrix with using supplied WorldRotation. + // + // @param rotation World rotation. + void UpdateRotationMatrix(const WorldRotation& rotation); + + // Order of the ambisonic sound field handled by the rotator. + const int ambisonic_order_; + + // Current rotation which is used in the interpolation process in order to + // compute new rotation matrix. Initialized with an identity rotation. + WorldRotation current_rotation_; + + // Spherical harmonics rotation sub-matrices for each order. + std::vector<Eigen::MatrixXf> rotation_matrices_; + + // Final spherical harmonics rotation matrix. + Eigen::MatrixXf rotation_matrix_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_AMBISONICS_HOA_ROTATOR_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/ambisonics/hoa_rotator_test.cc b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/hoa_rotator_test.cc new file mode 100644 index 000000000..6472412c9 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/hoa_rotator_test.cc @@ -0,0 +1,201 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "ambisonics/hoa_rotator.h" + +#include <algorithm> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "ambisonics/ambisonic_codec_impl.h" +#include "base/constants_and_types.h" +#include "utils/planar_interleaved_conversion.h" + +namespace vraudio { + +namespace { + +using testing::tuple; +using testing::Values; + +const int kAmbisonicOrder = 3; + +// Rotation angle used in the test. +const float kAngleDegrees = 90.0f; + +// Initial, arbitrary direction of the encoded soundfield source. +const SphericalAngle kInitialSourceAngle = + SphericalAngle::FromDegrees(22.0f, 33.0f); + +// Expected direction of the sound source rotated by |kAngleDegrees| against the +// right facing axis (x in the world coordinate system). +const SphericalAngle kXrotatedSourceAngle = + SphericalAngle::FromDegrees(150.021778639249f, 51.0415207997462f); + +// Expected direction of the sound source rotated by |kAngleDegrees| against the +// upward facing axis (y in the world coordinate system). +const SphericalAngle kYrotatedSourceAngle = + SphericalAngle::FromDegrees(112.0f, 33.0f); + +// Expected direction of the sound source rotated by |kAngleDegrees| against the +// back facing axis (z in the world coordinate system). +const SphericalAngle kZrotatedSourceAngle = + SphericalAngle::FromDegrees(35.0077312770459f, -18.3108067341351f); + +// Rotation interpolation interval in terms of frames (used by the HoaRotator). +const size_t kSlerpFrameInterval = 32; + +class HoaRotatorTest : public ::testing::Test { + protected: + HoaRotatorTest() {} + + // Virtual methods from ::testing::Test + ~HoaRotatorTest() override {} + + void SetUp() override { + // Initialize the third order soundfield rotator. + hoa_rotator_ = std::unique_ptr<HoaRotator>(new HoaRotator(kAmbisonicOrder)); + } + + void TearDown() override {} + + // Test method which creates a HOA soundfield buffer, rotates it by + // |rotation_angle| against |rotation_axis|, and compares the output to the + // reference soundfield buffer (where the source is already spatialized at the + // |expected_source_angle|). + void CompareRotatedAndReferenceSoundfields( + const std::vector<float>& input_data, float rotation_angle, + const WorldPosition& rotation_axis, + const SphericalAngle& expected_source_angle) { + AudioBuffer input_buffer(1, input_data.size()); + FillAudioBuffer(input_data, 1, &input_buffer); + // Initialize a third order mono codec with the |kInitialSourceAngle|. This + // will be used to obtain the rotated soundfield. + std::unique_ptr<MonoAmbisonicCodec<>> rotated_source_mono_codec( + new MonoAmbisonicCodec<>(kAmbisonicOrder, {kInitialSourceAngle})); + // Initialize a third order mono codec with the expected source angle. This + // will be used as a reference for the rotated soundfield. + std::unique_ptr<MonoAmbisonicCodec<>> reference_source_mono_codec( + new MonoAmbisonicCodec<>(kAmbisonicOrder, {expected_source_angle})); + // Generate soundfield buffers representing sound sources at required + // angles. + AudioBuffer encoded_rotated_buffer(kNumThirdOrderAmbisonicChannels, + input_data.size()); + AudioBuffer encoded_reference_buffer(kNumThirdOrderAmbisonicChannels, + input_data.size()); + rotated_source_mono_codec->EncodeBuffer(input_buffer, + &encoded_rotated_buffer); + reference_source_mono_codec->EncodeBuffer(input_buffer, + &encoded_reference_buffer); + // Rotate the test soundfield by |rotation_angle| degrees wrt the given + // |rotation_axis|. + const WorldRotation rotation = WorldRotation( + AngleAxisf(rotation_angle * kRadiansFromDegrees, rotation_axis)); + const bool result = hoa_rotator_->Process(rotation, encoded_rotated_buffer, + &encoded_rotated_buffer); + EXPECT_TRUE(result); + // Check if the sound source in the reference and rotated buffers are in the + // same direction. + // If the buffer size is more than |kSlerpFrameInterval|, due to + // interpolation, we expect that the last |kSlerpFrameInterval| frames have + // undergone the full rotation. + // If the buffer size is less than |kSlerpFrameInterval|, because no + // interpolation is applied, the rotated soundfield should result from + // frame 0. + for (size_t channel = 0; channel < encoded_rotated_buffer.num_channels(); + ++channel) { + const int num_frames = + static_cast<int>(encoded_rotated_buffer[channel].size()); + const int interval = static_cast<int>(kSlerpFrameInterval); + const int frames_to_compare = + (num_frames % interval) ? (num_frames % interval) : interval; + const int start_frame = std::max(0, num_frames - frames_to_compare); + ASSERT_LT(start_frame, num_frames); + for (int frame = start_frame; frame < num_frames; ++frame) { + EXPECT_NEAR(encoded_rotated_buffer[channel][frame], + encoded_reference_buffer[channel][frame], kEpsilonFloat); + } + } + } + + // Higher Order Ambisonic rotator instance. + std::unique_ptr<HoaRotator> hoa_rotator_; +}; + +// Tests that no rotation is aplied if |kRotationQuantizationRad| is not +// exceeded. +TEST_F(HoaRotatorTest, RotationThresholdTest) { + const size_t kFramesPerBuffer = 16; + const std::vector<float> kInputVector( + kFramesPerBuffer * kNumThirdOrderAmbisonicChannels, 1.0f); + AudioBuffer input_buffer(kNumThirdOrderAmbisonicChannels, kFramesPerBuffer); + FillAudioBuffer(kInputVector, kNumThirdOrderAmbisonicChannels, &input_buffer); + AudioBuffer output_buffer(kNumThirdOrderAmbisonicChannels, kFramesPerBuffer); + const WorldRotation kSmallRotation = + WorldRotation(1.0f, 0.001f, 0.001f, 0.001f); + const WorldRotation kLargeRotation = WorldRotation(1.0f, 0.1f, 0.1f, 0.1f); + EXPECT_FALSE( + hoa_rotator_->Process(kSmallRotation, input_buffer, &output_buffer)); + EXPECT_TRUE( + hoa_rotator_->Process(kLargeRotation, input_buffer, &output_buffer)); +} + +typedef tuple<WorldPosition, SphericalAngle> TestParams; +class HoaAxesRotationTest : public HoaRotatorTest, + public ::testing::WithParamInterface<TestParams> {}; + +// Tests third order soundfield rotation against the x, y and z axis using long +// input buffers (>> slerp interval). +TEST_P(HoaAxesRotationTest, CompareWithExpectedAngleLongBuffer) { + const WorldPosition& rotation_axis = ::testing::get<0>(GetParam()); + const SphericalAngle& expected_angle = ::testing::get<1>(GetParam()); + const size_t kLongFramesPerBuffer = 512; + const std::vector<float> kLongInputData(kLongFramesPerBuffer, 1.0f); + CompareRotatedAndReferenceSoundfields(kLongInputData, kAngleDegrees, + rotation_axis, expected_angle); +} + +// Tests third order soundfield rotation against the x, y and z axes using short +// input buffers (< slerp interval). +TEST_P(HoaAxesRotationTest, CompareWithExpectedAngleShortBuffer) { + const WorldPosition& rotation_axis = ::testing::get<0>(GetParam()); + const SphericalAngle& expected_angle = ::testing::get<1>(GetParam()); + const size_t kShortFramesPerBuffer = kSlerpFrameInterval / 2; + const std::vector<float> kShortInputData(kShortFramesPerBuffer, 1.0f); + CompareRotatedAndReferenceSoundfields(kShortInputData, kAngleDegrees, + rotation_axis, expected_angle); +} + +// Tests third order soundfield rotation against the x, y and z axes using +// buffer sizes that are (> slerp interval) and not integer multiples of +// the slerp interval. +TEST_P(HoaAxesRotationTest, CompareWithExpectedAngleOddBufferSizes) { + const WorldPosition& rotation_axis = ::testing::get<0>(GetParam()); + const SphericalAngle& expected_angle = ::testing::get<1>(GetParam()); + const size_t frames_per_buffer = kSlerpFrameInterval + 3U; + const std::vector<float> kShortInputData(frames_per_buffer, 1.0f); + CompareRotatedAndReferenceSoundfields(kShortInputData, kAngleDegrees, + rotation_axis, expected_angle); +} + +INSTANTIATE_TEST_CASE_P( + TestParameters, HoaAxesRotationTest, + Values(TestParams({1.0f, 0.0f, 0.0f}, kXrotatedSourceAngle), + TestParams({0.0f, 1.0f, 0.0f}, kYrotatedSourceAngle), + TestParams({0.0f, 0.0f, 1.0f}, kZrotatedSourceAngle))); + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/ambisonics/stereo_from_soundfield_converter.cc b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/stereo_from_soundfield_converter.cc new file mode 100644 index 000000000..94462a445 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/stereo_from_soundfield_converter.cc @@ -0,0 +1,52 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "ambisonics/stereo_from_soundfield_converter.h" + +#include "base/constants_and_types.h" +#include "dsp/gain.h" + +namespace vraudio { + +namespace { + +const float kMidSideChannelGain = 0.5f; + +} // namespace + +void StereoFromSoundfield(const AudioBuffer& soundfield_input, + AudioBuffer* stereo_output) { + DCHECK(stereo_output); + DCHECK_EQ(kNumStereoChannels, stereo_output->num_channels()); + DCHECK_EQ(soundfield_input.num_frames(), stereo_output->num_frames()); + DCHECK_GE(soundfield_input.num_channels(), kNumFirstOrderAmbisonicChannels); + const AudioBuffer::Channel& channel_audio_space_w = soundfield_input[0]; + const AudioBuffer::Channel& channel_audio_space_y = soundfield_input[1]; + AudioBuffer::Channel* left_channel_output = &(*stereo_output)[0]; + AudioBuffer::Channel* right_channel_output = &(*stereo_output)[1]; + // Left = 0.5 * (Mid + Side). + *left_channel_output = channel_audio_space_w; + *left_channel_output += channel_audio_space_y; + ConstantGain(0 /* no offset */, kMidSideChannelGain, *left_channel_output, + left_channel_output, false /* accumulate_output */); + // Right = 0.5 * (Mid - Side). + *right_channel_output = channel_audio_space_w; + *right_channel_output -= channel_audio_space_y; + ConstantGain(0 /* no offset */, kMidSideChannelGain, *right_channel_output, + right_channel_output, false /* accumulate_output */); +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/ambisonics/stereo_from_soundfield_converter.h b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/stereo_from_soundfield_converter.h new file mode 100644 index 000000000..44d2144e7 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/stereo_from_soundfield_converter.h @@ -0,0 +1,35 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_AMBISONICS_STEREO_FROM_SOUNDFIELD_CONVERTER_H_ +#define RESONANCE_AUDIO_AMBISONICS_STEREO_FROM_SOUNDFIELD_CONVERTER_H_ + +#include "base/audio_buffer.h" + +namespace vraudio { + +// Performs Ambisonic to stereo decode by performing Mid-Side (M-S) matrixing. +// Soundfield format assumed is Ambix (ACN/SN3D). Only first order channels +// are used for the stereo decode. +// +// @param soundfield_input Soundfield of arbitrary order. +// @param stereo_output Pointer to stereo output buffer. +void StereoFromSoundfield(const AudioBuffer& soundfield_input, + AudioBuffer* stereo_output); + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_AMBISONICS_STEREO_FROM_SOUNDFIELD_CONVERTER_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/ambisonics/stereo_from_soundfield_converter_test.cc b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/stereo_from_soundfield_converter_test.cc new file mode 100644 index 000000000..aa1f5c70a --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/stereo_from_soundfield_converter_test.cc @@ -0,0 +1,103 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "ambisonics/stereo_from_soundfield_converter.h" + +#include <iterator> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/audio_buffer.h" +#include "base/constants_and_types.h" +#include "utils/planar_interleaved_conversion.h" + +namespace vraudio { + +namespace { + +// Number of frames per buffer. +const size_t kFramesPerBuffer = 2; + +// First order ambisonic signal in the AmbiX format (W, Y, Z, X), sound source +// in the front. +const float kFirstOrderSourceFront[] = {1.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 0.0f, 0.0f, 1.0f}; + +// First order ambisonic signal in the AmbiX format (W, Y, Z, X), sound source +// to the left. +const float kFirstOrderSourceLeft[] = {1.0f, 1.0f, 0.0f, 0.0f, + 1.0f, 1.0f, 0.0f, 0.0f}; + +// Tests whether the conversion to stereo from soundfield results in equal +// signal in both L/R output channels when there is a single source in front of +// the soundfield. +TEST(StereoFromSoundfieldConverterTest, StereoFromSoundfieldFrontTest) { + // Initialize the soundfield input buffer. + const std::vector<float> soundfield_data(std::begin(kFirstOrderSourceFront), + std::end(kFirstOrderSourceFront)); + AudioBuffer soundfield_buffer( + kNumFirstOrderAmbisonicChannels, + soundfield_data.size() / kNumFirstOrderAmbisonicChannels); + FillAudioBuffer(soundfield_data, kNumFirstOrderAmbisonicChannels, + &soundfield_buffer); + + // Output buffer is stereo. + AudioBuffer output(kNumStereoChannels, + soundfield_data.size() / kNumFirstOrderAmbisonicChannels); + + StereoFromSoundfield(soundfield_buffer, &output); + + // Test for near equality. + ASSERT_EQ(kNumStereoChannels, output.num_channels()); + const AudioBuffer::Channel& output_channel_left = output[0]; + const AudioBuffer::Channel& output_channel_right = output[1]; + for (size_t frame = 0; frame < kFramesPerBuffer; ++frame) { + EXPECT_NEAR(output_channel_left[frame], output_channel_right[frame], + kEpsilonFloat); + } +} + +// Tests whether the conversion to stereo from soundfield, when the sound source +// in the soundfield is to the left, results in a signal only in the L output +// channel. +TEST(StereoFromSoundfieldConverterTest, StereoFromSoundfieldLeftTest) { + // Initialize the soundfield input buffer. + const std::vector<float> soundfield_data(std::begin(kFirstOrderSourceLeft), + std::end(kFirstOrderSourceLeft)); + AudioBuffer soundfield_buffer( + kNumFirstOrderAmbisonicChannels, + soundfield_data.size() / kNumFirstOrderAmbisonicChannels); + FillAudioBuffer(soundfield_data, kNumFirstOrderAmbisonicChannels, + &soundfield_buffer); + + // Output buffer is stereo. + AudioBuffer output(kNumStereoChannels, + soundfield_data.size() / kNumFirstOrderAmbisonicChannels); + + StereoFromSoundfield(soundfield_buffer, &output); + + // Test for near equality. + ASSERT_EQ(kNumStereoChannels, output.num_channels()); + const AudioBuffer::Channel& output_channel_left = output[0]; + const AudioBuffer::Channel& output_channel_right = output[1]; + for (size_t frame = 0; frame < kFramesPerBuffer; ++frame) { + EXPECT_NEAR(output_channel_left[frame], 1.0f, kEpsilonFloat); + EXPECT_NEAR(output_channel_right[frame], 0.0f, kEpsilonFloat); + } +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/ambisonics/utils.h b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/utils.h new file mode 100644 index 000000000..8c71944d6 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/utils.h @@ -0,0 +1,120 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_AMBISONICS_UTILS_H_ +#define RESONANCE_AUDIO_AMBISONICS_UTILS_H_ + +#include <cmath> + +#include "base/constants_and_types.h" +#include "base/logging.h" +#include "base/misc_math.h" + +namespace vraudio { + +// Returns ACN channel sequence from a degree and order of a spherical harmonic. +inline int AcnSequence(int degree, int order) { + DCHECK_GE(degree, 0); + DCHECK_LE(-degree, order); + DCHECK_LE(order, degree); + + return degree * degree + degree + order; +} + +// Returns normalization factor for Schmidt semi-normalized spherical harmonics +// used in AmbiX. +inline float Sn3dNormalization(int degree, int order) { + DCHECK_GE(degree, 0); + DCHECK_LE(-degree, order); + DCHECK_LE(order, degree); + return std::sqrt((2.0f - ((order == 0) ? 1.0f : 0.0f)) * + Factorial(degree - std::abs(order)) / + Factorial(degree + std::abs(order))); +} + +// Returns the number of spherical harmonics for a periphonic ambisonic sound +// field of |ambisonic_order| at compile-time. +// We have to use template metaprogramming because MSVC12 doesn't support +// constexpr. +template <size_t AmbisonicOrder> +struct GetNumPeriphonicComponentsStatic { + enum { value = (AmbisonicOrder + 1) * (AmbisonicOrder + 1) }; +}; + +// Returns the number of spherical harmonics for a periphonic ambisonic sound +// field of |ambisonic_order|. +inline size_t GetNumPeriphonicComponents(int ambisonic_order) { + return static_cast<size_t>((ambisonic_order + 1) * (ambisonic_order + 1)); +} + +// Returns the number of periphonic spherical harmonics (SHs) for a particular +// Ambisonic order. E.g. number of 1st, 2nd or 3rd degree SHs in a 3rd order +// sound field. +inline size_t GetNumNthOrderPeriphonicComponents(int ambisonic_order) { + if (ambisonic_order == 0) return 1; + return static_cast<size_t>(GetNumPeriphonicComponents(ambisonic_order) - + GetNumPeriphonicComponents(ambisonic_order - 1)); +} + +// Calculates the ambisonic order of a periphonic sound field with the given +// number of spherical harmonics. +inline int GetPeriphonicAmbisonicOrder(size_t num_components) { + DCHECK_GT(num_components, 0); + const int ambisonic_order = static_cast<int>(std::sqrt(num_components)) - 1; + // Detect when num_components is not square. + DCHECK_EQ((ambisonic_order + 1) * (ambisonic_order + 1), + static_cast<int>(num_components)); + return ambisonic_order; +} + +// Calculates the order of the current spherical harmonic channel as the integer +// part of a square root of the channel number. Please note, that in Ambisonics +// the terms 'order' (usually denoted as 'n') and 'degree' (usually denoted as +// 'm') are used in the opposite meaning as in more traditional maths or physics +// conventions: +// [1] C. Nachbar, F. Zotter, E. Deleflie, A. Sontacchi, "AMBIX - A SUGGESTED +// AMBISONICS FORMAT", Proc. of the 2nd Ambisonics Symposium, June 2-3 2011, +// Lexington, KY, https://goo.gl/jzt4Yy. +inline int GetPeriphonicAmbisonicOrderForChannel(size_t channel) { + return static_cast<int>(sqrtf(static_cast<float>(channel))); +} + +// Calculates the degree of the current spherical harmonic channel. Please note, +// that in Ambisonics the terms 'order' (usually denoted as 'n') and 'degree' +// (usually denoted as 'm') are used in the opposite meaning as in more +// traditional maths or physics conventions: +// [1] C. Nachbar, F. Zotter, E. Deleflie, A. Sontacchi, "AMBIX - A SUGGESTED +// AMBISONICS FORMAT", Proc. of the 2nd Ambisonics Symposium, June 2-3 2011, +// Lexington, KY, https://goo.gl/jzt4Yy. +inline int GetPeriphonicAmbisonicDegreeForChannel(size_t channel) { + const int order = GetPeriphonicAmbisonicOrderForChannel(channel); + return static_cast<int>(channel) - order * (order + 1); +} + +// Returns whether the given |num_channels| corresponds to a valid ambisonic +// order configuration. +inline bool IsValidAmbisonicOrder(size_t num_channels) { + if (num_channels == 0) { + return false; + } + // Number of channels must be a square number for valid ambisonic orders. + const size_t sqrt_num_channels = static_cast<size_t>(std::sqrt(num_channels)); + return num_channels == sqrt_num_channels * sqrt_num_channels; +} + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_AMBISONICS_UTILS_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/ambisonics/utils_test.cc b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/utils_test.cc new file mode 100644 index 000000000..51dfa13d5 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/ambisonics/utils_test.cc @@ -0,0 +1,65 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "ambisonics/utils.h" + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/constants_and_types.h" + +namespace vraudio { + +namespace { + +// Ambisonic Channel Numbers (ACNs) used in the tests. +const size_t kAcnChannels[] = {0, 1, 4, 8, 16, 32}; + +// Number of different ACNs to be tested. +const size_t kNumAcnChannels = sizeof(kAcnChannels) / sizeof(kAcnChannels[0]); + +// Tests that the Ambisonic order for a given channel returned by the +// GetPeriphonicAmbisonicOrderForChannel() method is correct. +TEST(UtilsTest, GetPeriphonicAmbisonicOrderForChannelTest) { + const int kExpectedOrders[] = {0, 1, 2, 2, 4, 5}; + for (size_t channel = 0; channel < kNumAcnChannels; ++channel) { + EXPECT_EQ(kExpectedOrders[channel], + GetPeriphonicAmbisonicOrderForChannel(kAcnChannels[channel])); + } +} + +// Tests that the Ambisonic degree for a given channel returned by the +// GetPeriphonicAmbisonicDegreeForChannel() method is correct. +TEST(UtilsTest, GetPeriphonicAmbisonicDegreeForChannelTest) { + const int kExpectedDegrees[] = {0, -1, -2, 2, -4, 2}; + for (size_t channel = 0; channel < kNumAcnChannels; ++channel) { + EXPECT_EQ(kExpectedDegrees[channel], + GetPeriphonicAmbisonicDegreeForChannel(kAcnChannels[channel])); + } +} + +// Tests that the ambisonic order validation returns the expected results for +// arbitrary number of channels. +TEST(UtilsTest, IsValidAmbisonicOrderTest) { + for (size_t valid_channel : {1, 4, 9, 16, 25, 36}) { + EXPECT_TRUE(IsValidAmbisonicOrder(valid_channel)); + } + for (size_t invalid_channel : {2, 3, 5, 8, 50, 99}) { + EXPECT_FALSE(IsValidAmbisonicOrder(invalid_channel)); + } +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/api/binaural_surround_renderer.cc b/src/3rdparty/resonance-audio/resonance_audio/api/binaural_surround_renderer.cc new file mode 100644 index 000000000..96ed399ae --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/api/binaural_surround_renderer.cc @@ -0,0 +1,35 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "api/binaural_surround_renderer.h" + +#include "base/logging.h" +#include "graph/binaural_surround_renderer_impl.h" + +namespace vraudio { + +BinauralSurroundRenderer* BinauralSurroundRenderer::Create( + size_t frames_per_buffer, int sample_rate_hz, + SurroundFormat surround_format) { + std::unique_ptr<BinauralSurroundRendererImpl> renderer( + new BinauralSurroundRendererImpl(frames_per_buffer, sample_rate_hz)); + if (!renderer->Init(surround_format)) { + return nullptr; + } + return renderer.release(); +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/api/binaural_surround_renderer.h b/src/3rdparty/resonance-audio/resonance_audio/api/binaural_surround_renderer.h new file mode 100644 index 000000000..31e8e3b6e --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/api/binaural_surround_renderer.h @@ -0,0 +1,255 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_API_BINAURAL_SURROUND_RENDERER_H_ +#define RESONANCE_AUDIO_API_BINAURAL_SURROUND_RENDERER_H_ + +#include <stddef.h> +#include <stdint.h> + +// Avoid dependency to base/integral_types.h +typedef int16_t int16; + +namespace vraudio { + +// Renders virtual surround sound as well as ambisonic soundfields to binaural +// stereo. +class BinauralSurroundRenderer { + public: + + enum SurroundFormat { + // Enables to initialize a yet undefined rendering mode. + kInvalid = 0, + + // Binaurally renders a single virtual speaker at 0 degrees in front. + kSurroundMono = 1, + + // Binaurally renders virtual stereo speakers at -30 degrees and +30 + // degrees. + kSurroundStereo = 2, + + // Binaurally renders 5.1 surround sound according to the ITU-R BS.775-3 + // speaker configuration recommendation: + // - Left (L) at 30 degrees. + // - Right (R) at -30 degrees. + // - Center (C) at 0 degrees. + // - Low frequency effects (LFE) at front center at 0 degrees. + // - Left surround (LS) at 110 degrees. + // - Right surround (RS) at -110 degrees. + // + // The 5.1 channel input layout must matches AAC: L, R, C, LFE, LS, RS. + // Note that this differs from the Vorbis/Opus 5.1 channel layout, which + // is: L, C, R, LS, RS, LFE. + kSurroundFiveDotOne = 3, + + // Binaurally renders 7.1 surround sound according to the ITU-R BS.775-3 + // speaker configuration recommendation: + // - Left (FL) at 30 degrees. + // - Right (FR) at -30 degrees. + // - Center (C) at 0 degrees. + // - Low frequency effects (LFE) at front center at 0 degrees. + // - Left surround 1 (LS1) at 90 degrees. + // - Right surround 1 (RS1) at -90 degrees. + // - Left surround 2 (LS2) at 150 degrees. + // - Right surround 2 (LS2) at -150 degrees. + // + // The 7.1 channel input layout must matches AAC: L, R, C, LFE, LS1, RS1, + // LS2, RS2. + // Note that this differs from the Vorbis/Opus 7.1 channel layout, which + // is: L, C, R, LS1, RS1, LS2, RS2, LFE. + kSurroundSevenDotOne = 10, + + // Binaurally renders first-order ambisonics + // (AmbiX format: 4 channels, ACN channel ordering, SN3D normalization). + kFirstOrderAmbisonics = 4, + + // Binaurally renders second-order ambisonics. + // (AmbiX format: 9 channels, ACN channel ordering, SN3D normalization). + kSecondOrderAmbisonics = 5, + + // Binaurally renders third-order ambisonics. + // (AmbiX format: 16 channels, ACN channel ordering, SN3D normalization). + kThirdOrderAmbisonics = 6, + + // Binaurally renders first-order ambisonics with a non-diegetic-stereo + // track. The first 4 channels contain ambisonic AmbiX format. + // (AmbiX format: 4 channels, ACN channel ordering, SN3D normalization). + // Channel 5 to 6 contain non-diegetic-stereo. + kFirstOrderAmbisonicsWithNonDiegeticStereo = 7, + + // Binaurally renders second-order ambisonics with a non-diegetic-stereo + // track. The first 9 channels contain ambisonic AmbiX format. + // (AmbiX format: 9 channels, ACN channel ordering, SN3D normalization). + // Channel 10 to 11 contain non-diegetic-stereo. + kSecondOrderAmbisonicsWithNonDiegeticStereo = 8, + + // Binaurally renders third-order ambisonics with a non-diegetic-stereo + // track. The first 16 channels contain ambisonic AmbiX format. + // (AmbiX format: 16 channels, ACN channel ordering, SN3D normalization). + // Channel 17 to 18 contain non-diegetic-stereo. + kThirdOrderAmbisonicsWithNonDiegeticStereo = 9, + + // Note: Next available value is: 11 + }; + + + virtual ~BinauralSurroundRenderer() {} + + // Factory method to create a |BinauralSurroundRenderer| instance. Caller must + // take ownership of returned instance and destroy it via operator delete. + // + // @param frames_per_buffer Number of frames in output buffer. + // @param sample_rate_hz Sample rate of audio buffers. + // @param surround_format Input surround sound format. + // @param return |BinauralSurroundRenderer| instance, nullptr if creation + // fails. + static BinauralSurroundRenderer* Create(size_t frames_per_buffer, + int sample_rate_hz, + SurroundFormat surround_format); + + // Enables the stereo speaker mode. When activated, it disables HRTF-based + // filtering and switches to computationally cheaper stereo-panning. This + // helps to avoid HRTF-based coloring effects when stereo speakers are used + // and reduces computational complexity when headphone-based HRTF filtering is + // not needed. By default the stereo speaker mode is disabled. + // + // @param enabled Flag to enable stereo speaker mode. + virtual void SetStereoSpeakerMode(bool enabled) = 0; + + // Returns the number of frames the input buffer is currently able to consume. + // + // @return Number of available frames in input buffer. + virtual size_t GetNumAvailableFramesInInputBuffer() const = 0; + + // Adds interleaved int16 audio data to the renderer. If enough data has been + // provided for an output buffer to be generated then it will be immediately + // available via |Get[Interleaved|Planar]StereoOutputBuffer|. The input data + // is copied into an internal buffer which allows the caller to re-use the + // input buffer immediately. The available space in the internal buffer can be + // obtained via |GetAvailableInputSizeSamples|. + // + // @param input_buffer_ptr Pointer to interleaved input data. + // @param num_channels Number of channels in input buffer. + // @param num_frames Number of frames in input buffer. + // @return The number of consumed frames. + virtual size_t AddInterleavedInput(const int16* input_buffer_ptr, + size_t num_channels, + size_t num_frames) = 0; + + // Adds interleaved floating point audio data to the renderer. If enough data + // has been provided for an output buffer to be generated then it will be + // immediately available via |Get[Interleaved|Planar]StereoOutputBuffer|. The + // input data is copied into an internal buffer which allows the caller to + // re-use the input buffer immediately. The available space in the internal + // buffer can be obtained via |GetAvailableInputSizeSamples|. + // + // @param input_buffer_ptr Pointer to interleaved input data. + // @param num_channels Number of channels in input buffer. + // @param num_frames Number of frames in input buffer. + // @return The number of consumed frames. + virtual size_t AddInterleavedInput(const float* input_buffer_ptr, + size_t num_channels, + size_t num_frames) = 0; + + // Adds planar int16 audio data to the renderer. If enough data has + // been provided for an output buffer to be generated then it will be + // immediately available via |Get[Interleaved|Planar]StereoOutputBuffer|. The + // input data is copied into an internal buffer which allows the caller to + // re-use the input buffer immediately. The available space in the internal + // buffer can be obtained via |GetAvailableInputSizeSamples|. + // + // @param input_buffer_ptrs Array of pointers to planar channel data. + // @param num_channels Number of channels in input buffer. + // @param num_frames Number of frames in input buffer. + // @return The number of consumed frames. + virtual size_t AddPlanarInput(const int16* const* input_buffer_ptrs, + size_t num_channels, size_t num_frames) = 0; + + // Adds planar floating point audio data to the renderer. If enough data has + // been provided for an output buffer to be generated then it will be + // immediately available via |Get[Interleaved|Planar]StereoOutputBuffer|. The + // input data is copied into an internal buffer which allows the caller to + // re-use the input buffer immediately. The available space in the internal + // buffer can be obtained via |GetAvailableInputSizeSamples|. + // + // @param input_buffer_ptrs Array of pointers to planar channel data. + // @param num_channels Number of channels in input buffer. + // @param num_frames Number of frames in input buffer. + // @return The number of consumed frames. + virtual size_t AddPlanarInput(const float* const* input_buffer_ptrs, + size_t num_channels, size_t num_frames) = 0; + + // Returns the number of samples available in the output buffer. + // + // @return Number of available samples in output buffer. + virtual size_t GetAvailableFramesInStereoOutputBuffer() const = 0; + + // Gets a processed output buffer in interleaved int16 format. + // + // @param output_buffer_ptr Pointer to allocated interleaved output buffer. + // @param num_frames Size of output buffer in frames. + // @return The number of consumed frames. + virtual size_t GetInterleavedStereoOutput(int16* output_buffer_ptr, + size_t num_frames) = 0; + + // Gets a processed output buffer in interleaved float format. + // + // @param output_buffer_ptr Pointer to allocated interleaved output buffer. + // @param num_frames Size of output buffer in frames. + // @return The number of consumed frames. + virtual size_t GetInterleavedStereoOutput(float* output_buffer_ptr, + size_t num_frames) = 0; + + // Gets a processed output buffer in planar int16 point format. + // + // @param output_buffer_ptrs Array of pointers to planar channel data. + // @param num_frames Number of frames in output buffer. + // @return The number of consumed frames. + virtual size_t GetPlanarStereoOutput(int16** output_buffer_ptrs, + size_t num_frames) = 0; + + // Gets a processed output buffer in planar floating point format. + // + // @param output_buffer_ptrs Array of pointers to planar channel data. + // @param num_frames Number of frames in output buffer. + // @return The number of consumed frames. + virtual size_t GetPlanarStereoOutput(float** output_buffer_ptrs, + size_t num_frames) = 0; + + // Removes all buffered input and processed output buffers from the buffer + // queues. + virtual void Clear() = 0; + + // Triggers the processing of data that has been input but not yet processed. + // Note after calling this method, all processed output must be consumed via + // |Get[Interleaved|Planar]StereoOutputBuffer| before adding new input + // buffers. + // + // @return Whether any data was processed. + virtual bool TriggerProcessing() = 0; + + // Updates the head rotation. + // + // @param w W component of quaternion. + // @param x X component of quaternion. + // @param y Y component of quaternion. + // @param z Z component of quaternion. + virtual void SetHeadRotation(float w, float x, float y, float z) = 0; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_API_BINAURAL_SURROUND_RENDERER_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/api/resonance_audio_api.cc b/src/3rdparty/resonance-audio/resonance_audio/api/resonance_audio_api.cc new file mode 100644 index 000000000..d7b9ccfdd --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/api/resonance_audio_api.cc @@ -0,0 +1,30 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "api/resonance_audio_api.h" + +#include "base/logging.h" +#include "graph/resonance_audio_api_impl.h" + +namespace vraudio { + +extern "C" EXPORT_API ResonanceAudioApi* CreateResonanceAudioApi( + size_t num_channels, size_t frames_per_buffer, int sample_rate_hz) { + return new ResonanceAudioApiImpl(num_channels, frames_per_buffer, + sample_rate_hz); +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/api/resonance_audio_api.h b/src/3rdparty/resonance-audio/resonance_audio/api/resonance_audio_api.h new file mode 100644 index 000000000..f734dbee9 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/api/resonance_audio_api.h @@ -0,0 +1,416 @@ + +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_API_RESONANCE_AUDIO_API_H_ +#define RESONANCE_AUDIO_API_RESONANCE_AUDIO_API_H_ + +// EXPORT_API can be used to define the dllimport storage-class attribute. +#if !defined(EXPORT_API) +#define EXPORT_API +#endif + +#include <cstddef> // size_t declaration. +#include <cstdint> // int16_t declaration. + + +typedef int16_t int16; + +namespace vraudio { + +// Rendering modes define CPU load / rendering quality balances. +// Note that this struct is C-compatible by design to be used across external +// C/C++ and C# implementations. +enum RenderingMode { + // Stereo panning, i.e., this disables HRTF-based rendering. + kStereoPanning = 0, + // HRTF-based rendering using First Order Ambisonics, over a virtual array of + // 8 loudspeakers arranged in a cube configuration around the listener's head. + kBinauralLowQuality, + // HRTF-based rendering using Second Order Ambisonics, over a virtual array of + // 12 loudspeakers arranged in a dodecahedral configuration (using faces of + // the dodecahedron). + kBinauralMediumQuality, + // HRTF-based rendering using Third Order Ambisonics, over a virtual array of + // 26 loudspeakers arranged in a Lebedev grid: https://goo.gl/DX1wh3. + kBinauralHighQuality, + // Room effects only rendering. This disables HRTF-based rendering and direct + // (dry) output of a sound object. Note that this rendering mode should *not* + // be used for general-purpose sound object spatialization, as it will only + // render the corresponding room effects of given sound objects without the + // direct spatialization. + kRoomEffectsOnly, +}; + +// Distance rolloff models used for distance attenuation. +// Note that this enum is C-compatible by design to be used across external +// C/C++ and C# implementations. +enum DistanceRolloffModel { + // Logarithmic distance rolloff model. + kLogarithmic = 0, + // Linear distance rolloff model. + kLinear, + // Distance attenuation value will be explicitly set by the user. + kNone, +}; + +// Early reflection properties of an acoustic environment. +// Note that this struct is C-compatible by design to be used across external +// C/C++ and C# implementations. +struct ReflectionProperties { + // Default constructor initializing all data members to 0. + ReflectionProperties() + : room_position{0.0f, 0.0f, 0.0f}, + room_rotation{0.0f, 0.0f, 0.0f, 1.0f}, + room_dimensions{0.0f, 0.0f, 0.0f}, + cutoff_frequency(0.0f), + coefficients{0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, + gain(0.0f) {} + + // Center position of the shoebox room in world space. + float room_position[3]; + + // Rotation (quaternion) of the shoebox room in world space. + float room_rotation[4]; + + // Size of the shoebox shoebox room in world space. + float room_dimensions[3]; + + // Frequency threshold for low pass filtering (-3dB cuttoff). + float cutoff_frequency; + + // Reflection coefficients that are stored in world space as follows: + // [0] (-)ive x-axis wall (left) + // [1] (+)ive x-axis wall (right) + // [2] (-)ive y-axis wall (bottom) + // [3] (+)ive y-axis wall (top) + // [4] (-)ive z-axis wall (front) + // [5] (+)ive z-axis wall (back) + float coefficients[6]; + + // Uniform reflections gain which is applied to all reflections. + float gain; +}; + +// Late reverberation properties of an acoustic environment. +// Note that this struct is C-compatible by design to be used across external +// C/C++ and C# implementations. +struct ReverbProperties { + // Default constructor initializing all data members to 0. + ReverbProperties() + : rt60_values{0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, + gain(0.0f) {} + + // RT60's of the reverberation tail at different octave band centre + // frequencies in seconds. + float rt60_values[9]; + + // Reverb gain. + float gain; +}; + +class ResonanceAudioApi; + +// Factory method to create a |ResonanceAudioApi| instance. Caller must +// take ownership of returned instance and destroy it via operator delete. +// +// @param num_channels Number of channels of audio output. +// @param frames_per_buffer Number of frames per buffer. +// @param sample_rate_hz System sample rate. +extern "C" EXPORT_API ResonanceAudioApi* CreateResonanceAudioApi( + size_t num_channels, size_t frames_per_buffer, int sample_rate_hz); + +// The ResonanceAudioApi library renders high-quality spatial audio. It provides +// methods to binaurally render virtual sound sources with simulated room +// acoustics. In addition, it supports decoding and binaural rendering of +// ambisonic soundfields. Its implementation is single-threaded, thread-safe +// and non-blocking to be able to process raw PCM audio buffers directly on the +// audio thread while receiving parameter updates from the main/render thread. +class ResonanceAudioApi { + public: + // Sound object / ambisonic source identifier. + typedef int SourceId; + + // Invalid source id that can be used to initialize handler variables during + // class construction. + static const SourceId kInvalidSourceId = -1; + + virtual ~ResonanceAudioApi() {} + + // Renders and outputs an interleaved output buffer in float format. + // + // @param num_frames Size of output buffer in frames. + // @param num_channels Number of channels in output buffer. + // @param buffer_ptr Raw float pointer to audio buffer. + // @return True if a valid output was successfully rendered, false otherwise. + virtual bool FillInterleavedOutputBuffer(size_t num_channels, + size_t num_frames, + float* buffer_ptr) = 0; + + // Renders and outputs an interleaved output buffer in int16 format. + // + // @param num_channels Number of channels in output buffer. + // @param num_frames Size of output buffer in frames. + // @param buffer_ptr Raw int16 pointer to audio buffer. + // @return True if a valid output was successfully rendered, false otherwise. + virtual bool FillInterleavedOutputBuffer(size_t num_channels, + size_t num_frames, + int16* buffer_ptr) = 0; + + // Renders and outputs a planar output buffer in float format. + // + // @param num_frames Size of output buffer in frames. + // @param num_channels Number of channels in output buffer. + // @param buffer_ptr Pointer to array of raw float pointers to each channel of + // the audio buffer. + // @return True if a valid output was successfully rendered, false otherwise. + virtual bool FillPlanarOutputBuffer(size_t num_channels, size_t num_frames, + float* const* buffer_ptr) = 0; + + // Renders and outputs a planar output buffer in int16 format. + // + // @param num_channels Number of channels in output buffer. + // @param num_frames Size of output buffer in frames. + // @param buffer_ptr Pointer to array of raw int16 pointers to each channel of + // the audio buffer. + // @return True if a valid output was successfully rendered, false otherwise. + virtual bool FillPlanarOutputBuffer(size_t num_channels, size_t num_frames, + int16* const* buffer_ptr) = 0; + + // Sets listener's head position. + // + // @param x X coordinate of head position in world space. + // @param y Y coordinate of head position in world space. + // @param z Z coordinate of head position in world space. + virtual void SetHeadPosition(float x, float y, float z) = 0; + + // Sets listener's head rotation. + // + // @param x X component of quaternion. + // @param y Y component of quaternion. + // @param z Z component of quaternion. + // @param w W component of quaternion. + virtual void SetHeadRotation(float x, float y, float z, float w) = 0; + + // Sets the master volume of the main audio output. + // + // @param volume Master volume (linear) in amplitude in range [0, 1] for + // attenuation, range [1, inf) for gain boost. + virtual void SetMasterVolume(float volume) = 0; + + // Enables the stereo speaker mode. When activated, it disables HRTF-based + // filtering and switches to computationally cheaper stereo-panning. This + // helps to avoid HRTF-based coloring effects when stereo speakers are used + // and reduces computational complexity when headphone-based HRTF filtering is + // not needed. By default the stereo speaker mode is disabled. Note that + // stereo speaker mode overrides the |enable_hrtf| flag in + // |CreateSoundObjectSource|. + // + // @param enabled Flag to enable stereo speaker mode. + virtual void SetStereoSpeakerMode(bool enabled) = 0; + + // Creates an ambisonic source instance. + // + // @param num_channels Number of input channels. + // @return Id of new ambisonic source. + virtual SourceId CreateAmbisonicSource(size_t num_channels) = 0; + + // Creates a stereo non-spatialized source instance, which directly plays back + // mono or stereo audio. + // + // @param num_channels Number of input channels. + // @return Id of new non-spatialized source. + virtual SourceId CreateStereoSource(size_t num_channels) = 0; + + // Creates a sound object source instance. + // + // @param rendering_mode Rendering mode which governs quality and performance. + // @return Id of new sound object source. + virtual SourceId CreateSoundObjectSource(RenderingMode rendering_mode) = 0; + + // Destroys source instance. + // + // @param source_id Id of source to be destroyed. + virtual void DestroySource(SourceId id) = 0; + + // Sets the next audio buffer in interleaved float format to a sound source. + // + // @param source_id Id of sound source. + // @param audio_buffer_ptr Pointer to interleaved float audio buffer. + // @param num_channels Number of channels in interleaved audio buffer. + // @param num_frames Number of frames per channel in interleaved audio buffer. + virtual void SetInterleavedBuffer(SourceId source_id, + const float* audio_buffer_ptr, + size_t num_channels, size_t num_frames) = 0; + + // Sets the next audio buffer in interleaved int16 format to a sound source. + // + // @param source_id Id of sound source. + // @param audio_buffer_ptr Pointer to interleaved int16 audio buffer. + // @param num_channels Number of channels in interleaved audio buffer. + // @param num_frames Number of frames per channel in interleaved audio buffer. + virtual void SetInterleavedBuffer(SourceId source_id, + const int16* audio_buffer_ptr, + size_t num_channels, size_t num_frames) = 0; + + // Sets the next audio buffer in planar float format to a sound source. + // + // @param source_id Id of sound source. + // @param audio_buffer_ptr Pointer to array of pointers referring to planar + // audio buffers for each channel. + // @param num_channels Number of planar input audio buffers. + // @param num_frames Number of frames per channel. + virtual void SetPlanarBuffer(SourceId source_id, + const float* const* audio_buffer_ptr, + size_t num_channels, size_t num_frames) = 0; + + // Sets the next audio buffer in planar int16 format to a sound source. + // + // @param source_id Id of sound source. + // @param audio_buffer_ptr Pointer to array of pointers referring to planar + // audio buffers for each channel. + // @param num_channels Number of planar input audio buffers. + // @param num_frames Number of frames per channel. + virtual void SetPlanarBuffer(SourceId source_id, + const int16* const* audio_buffer_ptr, + size_t num_channels, size_t num_frames) = 0; + + // Sets the given source's distance attenuation value explicitly. The distance + // rolloff model of the source must be set to |DistanceRolloffModel::kNone| + // for the set value to take effect. + // + // @param source_id Id of source. + // @param distance_attenuation Distance attenuation value. + virtual void SetSourceDistanceAttenuation(SourceId source_id, + float distance_attenuation) = 0; + + // Sets the given source's distance attenuation method with minimum and + // maximum distances. Maximum distance must be greater than the minimum + // distance for the method to be set. + // + // @param source_id Id of source. + // @param rolloff Linear or logarithmic distance rolloff models. + // @param min_distance Minimum distance to apply distance attenuation method. + // @param max_distance Maximum distance to apply distance attenuation method. + virtual void SetSourceDistanceModel(SourceId source_id, + DistanceRolloffModel rolloff, + float min_distance, + float max_distance) = 0; + + // Sets the given source's position. Note that, the given position for an + // ambisonic source is only used to determine the corresponding room effects + // to be applied. + // + // @param source_id Id of source. + // @param x X coordinate of source position in world space. + // @param y Y coordinate of source position in world space. + // @param z Z coordinate of source position in world space. + virtual void SetSourcePosition(SourceId source_id, float x, float y, + float z) = 0; + + // Sets the room effects contribution for the given source. + // + // @param source_id Id of source. + // @param room_effects_gain Linear room effects volume in amplitude in range + // [0, 1] for attenuation, range [1, inf) for gain boost. + virtual void SetSourceRoomEffectsGain(SourceId source_id, + float room_effects_gain) = 0; + + // Sets the given source's rotation. + // + // @param source_id Id of source. + // @param x X component of quaternion. + // @param y Y component of quaternion. + // @param z Z component of quaternion. + // @param w W component of quaternion. + virtual void SetSourceRotation(SourceId source_id, float x, float y, float z, + float w) = 0; + + // Sets the given source's volume. + // + // @param source_id Id of source. + // @param volume Linear source volume in amplitude in range [0, 1] for + // attenuation, range [1, inf) for gain boost. + virtual void SetSourceVolume(SourceId source_id, float volume) = 0; + + // Sets the given sound object source's directivity. + // + // @param sound_object_source_id Id of sound object source. + // @param alpha Weighting balance between figure of eight pattern and circular + // pattern for source emission in range [0, 1]. A value of 0.5 results in + // a cardioid pattern. + // @param order Order applied to computed directivity. Higher values will + // result in narrower and sharper directivity patterns. Range [1, inf). + virtual void SetSoundObjectDirectivity(SourceId sound_object_source_id, + float alpha, float order) = 0; + + // Sets the listener's directivity with respect to the given sound object. + // This method could be used to simulate an angular rolloff in terms of the + // listener's orientation, given the polar pickup pattern with |alpha| and + // |order|. + // + // @param sound_object_source_id Id of sound object source. + // @param alpha Weighting balance between figure of eight pattern and circular + // pattern for listener's pickup in range [0, 1]. A value of 0.5 results + // in a cardioid pattern. + // @param order Order applied to computed pickup pattern. Higher values will + // result in narrower and sharper pickup patterns. Range [1, inf). + virtual void SetSoundObjectListenerDirectivity( + SourceId sound_object_source_id, float alpha, float order) = 0; + + // Sets the gain (linear) of the near field effect. + // + // @param sound_object_source_id Id of sound object source. + // @param gain Gain of the near field effect. Range [0, 9] (corresponding to + // approx. (-Inf, +20dB]). + virtual void SetSoundObjectNearFieldEffectGain( + SourceId sound_object_source_id, float gain) = 0; + + // Sets the given sound object source's occlusion intensity. + // + // @param sound_object_source_id Id of sound object source. + // @param intensity Number of occlusions occurred for the object. The value + // can be set to fractional for partial occlusions. Range [0, inf). + virtual void SetSoundObjectOcclusionIntensity(SourceId sound_object_source_id, + float intensity) = 0; + + // Sets the given sound object source's spread. + // + // @param sound_object_source_id Id of sound object source. + // @param spread_deg Spread in degrees. + virtual void SetSoundObjectSpread(SourceId sound_object_source_id, + float spread_deg) = 0; + + // Turns on/off the reflections and reverberation. + virtual void EnableRoomEffects(bool enable) = 0; + + // Sets the early reflection properties of the environment. + // + // @param reflection_properties Reflection properties. + virtual void SetReflectionProperties( + const ReflectionProperties& reflection_properties) = 0; + + // Sets the late reverberation properties of the environment. + // + // @param reverb_properties Reverb properties. + virtual void SetReverbProperties( + const ReverbProperties& reverb_properties) = 0; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_API_RESONANCE_AUDIO_API_H_ + diff --git a/src/3rdparty/resonance-audio/resonance_audio/base/aligned_allocator.h b/src/3rdparty/resonance-audio/resonance_audio/base/aligned_allocator.h new file mode 100644 index 000000000..628ccaa02 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/base/aligned_allocator.h @@ -0,0 +1,117 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_BASE_ALIGNED_ALLOCATOR_H_ +#define RESONANCE_AUDIO_BASE_ALIGNED_ALLOCATOR_H_ + +#include <stdlib.h> + +#include <algorithm> +#include <cstddef> +#include <memory> +#include <type_traits> + +#include "base/simd_utils.h" + + +namespace vraudio { + +// Performs static assert checks on the types size and alignment parameters. +template <size_t TypeSize, size_t Alignment> +void StaticAlignmentCheck() { + const bool alignment_is_power_of_two = + !(Alignment == 0) && !(Alignment & (Alignment - 1)); + static_assert(alignment_is_power_of_two, "Alignment must be power of two"); + + const bool type_size_is_power_of_two = !(TypeSize & (TypeSize - 1)); + static_assert(type_size_is_power_of_two, "Type size must be power of two"); +} + +// Returns a pointer to aligned memory. +template <typename Type, typename SizeType, typename PointerType> +PointerType AllignedMalloc(SizeType size, SizeType alignment) { + const SizeType data_size = size * sizeof(Type); + const SizeType offset = alignment - 1 + sizeof(PointerType); + void* mem_block_begin = malloc(data_size + offset); + if (mem_block_begin == nullptr) { + return nullptr; + } + // Find memory aligned address. + void** mem_block_aligned = reinterpret_cast<void**>( + ((reinterpret_cast<SizeType>(mem_block_begin) + offset) & + (~(alignment - 1)))); + // Save pointer to original block right before the aligned block. + mem_block_aligned[-1] = mem_block_begin; + return reinterpret_cast<PointerType>(mem_block_aligned); +} + +// Frees memory that has been aligned with |AllignedMalloc|. +template <typename PointerType> +void AllignedFree(PointerType mem_block_aligned) { + free(*(reinterpret_cast<void**>(mem_block_aligned) - 1)); +} + +// Class that allocates aligned memory. It is derived from std::allocator class +// to be used with STL containers. +// +// @tparam Type Datatype of container to allocate. +// @tparam Alignment Size of memory alignment. +template <typename Type, size_t Alignment> +class AlignedAllocator : public std::allocator<Type> { + public: + using Pointer = typename std::allocator_traits<std::allocator<Type>>::pointer; + using ConstPointer = typename std::allocator_traits<std::allocator<Type>>::const_pointer; + using SizeType = typename std::allocator_traits<std::allocator<Type>>::size_type; + + AlignedAllocator() { StaticAlignmentCheck<sizeof(Type), Alignment>(); } + + // Allocates memory for |size| elements and returns a pointer that is aligned + // to a multiple to |Alignment|. + // + // @param size Number of elements to allocate. + // @return Returns memory aligned pointer. + Pointer allocate(SizeType size) { return allocate(size, nullptr); } + + // Allocates memory for |size| elements and returns a pointer that is aligned + // to a multiple to |Alignment|. + // + // @param size Number of elements to allocate. + // @return Returns memory aligned pointer. + Pointer allocate(SizeType size, ConstPointer /* hint */) { + + return AllignedMalloc<Type, SizeType, Pointer>(size, Alignment); + } + + void deallocate(Pointer mem_block_aligned, size_t size) { + AllignedFree<Pointer>(mem_block_aligned); + } + + // Copy constructor to support rebind operation (to make MSVC happy). + template <typename U> + explicit AlignedAllocator<Type, Alignment>( + const AlignedAllocator<U, Alignment>& other) {} + + // Rebind is used to allocate container internal variables of type |U| + // (which don't need to be aligned). + template <typename U> + struct rebind { + typedef AlignedAllocator<U, Alignment> other; + }; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_BASE_ALIGNED_ALLOCATOR_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/base/aligned_allocator_test.cc b/src/3rdparty/resonance-audio/resonance_audio/base/aligned_allocator_test.cc new file mode 100644 index 000000000..c38b8a2f5 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/base/aligned_allocator_test.cc @@ -0,0 +1,53 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "base/aligned_allocator.h" + +#include <cstddef> +#include <vector> + +#include "base/integral_types.h" +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/simd_utils.h" + +using vraudio::AlignedAllocator; + +namespace { + +// Helper method to test memory alignment. +template <size_t Alignment> +void TestAlignedAllocator() { + static const size_t kRuns = 1000; + for (size_t run = 0; run < kRuns; ++run) { + std::vector<float, AlignedAllocator<float, Alignment> > aligned_vector(1); + const bool is_aligned = + ((reinterpret_cast<size_t>(&aligned_vector[0]) & (Alignment - 1)) == 0); + EXPECT_TRUE(is_aligned); + } +} + +} // namespace + +// Allocates multiple std::vectors using the AlignedAllocator and tests if the +// allocated memory is aligned. +TEST(AlignedAlocatorTest, TestAlignment) { + TestAlignedAllocator<2>(); + TestAlignedAllocator<4>(); + TestAlignedAllocator<16>(); + TestAlignedAllocator<32>(); + TestAlignedAllocator<64>(); +} + diff --git a/src/3rdparty/resonance-audio/resonance_audio/base/audio_buffer.cc b/src/3rdparty/resonance-audio/resonance_audio/base/audio_buffer.cc new file mode 100644 index 000000000..c2ad61510 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/base/audio_buffer.cc @@ -0,0 +1,74 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "base/audio_buffer.h" + +namespace vraudio { + +AudioBuffer::AudioBuffer() : num_frames_(0), source_id_(kInvalidSourceId) {} + +AudioBuffer::AudioBuffer(size_t num_channels, size_t num_frames) + : num_frames_(num_frames), source_id_(kInvalidSourceId) { + + InitChannelViews(num_channels); +} + +// Copy assignment from AudioBuffer. +AudioBuffer& AudioBuffer::operator=(const AudioBuffer& other) { + if (this != &other) { + num_frames_ = other.num_frames_; + source_id_ = other.source_id_; + InitChannelViews(other.num_channels()); + for (size_t i = 0; i < num_channels(); ++i) { + channel_views_[i] = other.channel_views_[i]; + } + } + return *this; +} + +AudioBuffer::AudioBuffer(AudioBuffer&& other) { + num_frames_ = other.num_frames_; + other.num_frames_ = 0; + data_ = std::move(other.data_); + data_size_ = other.data_size_; + other.data_size_ = 0; + channel_views_ = std::move(other.channel_views_); + source_id_ = other.source_id_; + other.source_id_ = kInvalidSourceId; +} + +void AudioBuffer::InitChannelViews(size_t num_channels) { + + + const size_t num_frames_to_next_channel = FindNextAlignedArrayIndex( + num_frames_, sizeof(float), kMemoryAlignmentBytes); + + data_size_ = num_channels * num_frames_to_next_channel; + data_.resize(data_size_); + + channel_views_.clear(); + channel_views_.reserve(num_channels); + + float* itr = data_.data(); + + for (size_t i = 0; i < num_channels; ++i) { + ChannelView new_channel_view(itr, num_frames_); + channel_views_.push_back(new_channel_view); + itr += num_frames_to_next_channel; + } +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/base/audio_buffer.h b/src/3rdparty/resonance-audio/resonance_audio/base/audio_buffer.h new file mode 100644 index 000000000..c63fd905c --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/base/audio_buffer.h @@ -0,0 +1,222 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_BASE_AUDIO_BUFFER_H_ +#define RESONANCE_AUDIO_BASE_AUDIO_BUFFER_H_ + +#include <algorithm> +#include <memory> + +#include "base/integral_types.h" +#include "base/aligned_allocator.h" +#include "base/channel_view.h" +#include "base/constants_and_types.h" +#include "base/logging.h" +#include "base/simd_utils.h" + + +namespace vraudio { + +// Audio buffer that manages multi-channel audio data in a planar data format. +// All channels are sequentially stored within a single consecutive chunk of +// memory. To access individual channel data, the array subscript operator can +// be used to obtain a |AudioBuffer::Channel|. Note that the user must guarantee +// that the AudioBuffer instance lives as long as its channel data is accessed +// via |AudioBuffer::Channel|s. Note that allocated buffers may *not* be +// initialized to zero. +// +// Examples: +// +// // Range-based for-loop over all channels and all samples. +// AudioBuffer audio_buffer(...) +// for (AudioBuffer::Channel& channel : audio_buffer) { +// for (float& sample : channel) { +// sample *= gain; +// } +// } +// +// // Range-based for-loop over all channels and array subscripts-based for-loop +// // to access samples. +// AudioBuffer audio_buffer(...) +// for (AudioBuffer::Channel& channel : audio_buffer) { +// for (size_t i = 0; i < channel.num_frames(); ++i) { +// channel[i] *= gain; +// } +// } +// +// // Array subscript-based for-loops over all channels samples. +// // AudioBuffer audio_buffer(...) +// for (size_t c=0; c < audio_buffer.num_channels(); ++c) { +// // First obtain a reference to AudioBuffer::Channel. +// AudioBuffer::Channel& channel = audio_buffer[c]; +// for (size_t i = 0; i < channel.num_frames(); ++i) { +// channel[i] *= gain; +// } +// } +// +// Note do *NOT* use double array subscripts to iterate over multiple samples +// since it performs a channel iterator lookup for every sample: +// for (size_t c=0; c < audio_buffer.num_channels(); ++c) { +// for (size_t i = 0; i < channel.size(); ++i) { +// audio_buffer[c][i] *= gain; // *BAD* +// } +// } +// +class AudioBuffer { + public: + // View on separate audio channel. + typedef ChannelView Channel; + + // Allocator class to allocate aligned floats. + typedef AlignedAllocator<float, kMemoryAlignmentBytes> FloatAllocator; + + // Allocator class to allocate aligned int16s. + typedef AlignedAllocator<int16, kMemoryAlignmentBytes> Int16Allocator; + + // AlignedFloatBuffer for storing audio data. + typedef std::vector<float, FloatAllocator> AlignedFloatVector; + + // AlignedInt16Buffer for storing audio data. + typedef std::vector<int16, Int16Allocator> AlignedInt16Vector; + + // Default constructor initializes an empty |AudioBuffer|. + AudioBuffer(); + + // Constructor. + // + // @param num_channels Number of channels. + // @param num_frames Number of frames. + AudioBuffer(size_t num_channels, size_t num_frames); + + // Move constructor. + AudioBuffer(AudioBuffer&& other); + + // Copy constructor is explicitly deleted to prevent accidental copies. + // Use copy assignment operator instead. + AudioBuffer(const AudioBuffer& other) = delete; + + // Copy assignment from AudioBuffer. + AudioBuffer& operator=(const AudioBuffer& other); + + // Returns the number of audio channels. + size_t num_channels() const { return channel_views_.size(); } + + // Returns the number of frames per buffer. + size_t num_frames() const { return num_frames_; } + + // Returns this buffer's source id. + SourceId source_id() const { return source_id_; } + + // Returns a reference to the selected ChannelView. + Channel& operator[](size_t channel) { + DCHECK_LT(channel, channel_views_.size()); + return channel_views_[channel]; + } + + // Returns a const reference to the selected ChannelView. + const Channel& operator[](size_t channel) const { + DCHECK_LT(channel, channel_views_.size()); + return channel_views_[channel]; + } + + // Copy assignment from std::vector<std::vector<float>>. + AudioBuffer& operator=(const std::vector<std::vector<float>>& other) { + DCHECK_EQ(other.size(), channel_views_.size()); + for (size_t channel = 0; channel < channel_views_.size(); ++channel) { + channel_views_[channel] = other[channel]; + } + return *this; + } + + // += operator + AudioBuffer& operator+=(const AudioBuffer& other) { + DCHECK_EQ(other.num_channels(), num_channels()); + DCHECK_EQ(other.num_frames(), num_frames()); + for (size_t i = 0; i < channel_views_.size(); ++i) + channel_views_[i] += other[i]; + + return *this; + } + + // -= operator + AudioBuffer& operator-=(const AudioBuffer& other) { + DCHECK_EQ(other.num_channels(), num_channels()); + DCHECK_EQ(other.num_frames(), num_frames()); + for (size_t i = 0; i < channel_views_.size(); ++i) + channel_views_[i] -= other[i]; + + return *this; + } + + // Returns an iterator to the ChannelView of the first channel. + std::vector<Channel>::iterator begin() { return channel_views_.begin(); } + + // Returns an iterator to the end of the ChannelView vector. + std::vector<Channel>::iterator end() { return channel_views_.end(); } + + // Returns a const_iterator to the ChannelView of the first channel. + std::vector<Channel>::const_iterator begin() const { + return channel_views_.begin(); + } + + // Returns an const_iterator to the end of the ChannelView vector. + std::vector<Channel>::const_iterator end() const { + return channel_views_.end(); + } + + // Fills all channels with zeros and reenables |Channel|s. + void Clear() { + for (Channel& channel : channel_views_) { + channel.SetEnabled(true); + channel.Clear(); + } + } + + // Returns the number of allocated frames per |Channel|. Note this may + // differ from the actual size of the |Channel| to ensure alignment of all + // |Channel|s. + size_t GetChannelStride() const { + return FindNextAlignedArrayIndex(num_frames_, sizeof(float), + kMemoryAlignmentBytes); + } + + // Sets the source id of which the buffer belongs to. + void set_source_id(SourceId source_id) { source_id_ = source_id; } + + private: + // Allocates memory and initializes vector of |ChannelView|s. + void InitChannelViews(size_t num_channels); + + // Number of frames per buffer. + size_t num_frames_; + + // Audio buffer that sequentially stores multiple audio channels in a planar + // format. + AlignedFloatVector data_; + + // Size of audio buffer. + size_t data_size_; + + // Vector of |AudioBuffer::Channel|s. + std::vector<Channel> channel_views_; + + // Id of a source that this buffer belongs to. + SourceId source_id_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_BASE_AUDIO_BUFFER_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/base/audio_buffer_test.cc b/src/3rdparty/resonance-audio/resonance_audio/base/audio_buffer_test.cc new file mode 100644 index 000000000..2c8f3e20b --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/base/audio_buffer_test.cc @@ -0,0 +1,144 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "base/audio_buffer.h" + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/constants_and_types.h" + +namespace vraudio { + +namespace { + +// Tests default constructor. +TEST(AudioBuffer, AudioBufferDefaultConstructor) { + AudioBuffer audio_buffer; + EXPECT_EQ(audio_buffer.num_channels(), 0U); + EXPECT_EQ(audio_buffer.num_frames(), 0U); +} + +// Tests initialization of |AudioBuffer|. +TEST(AudioBuffer, AudioBufferInitializationTest) { + static const size_t kNumChannels = 2; + static const size_t kFramesPerBuffer = 16; + AudioBuffer audio_buffer(kNumChannels, kFramesPerBuffer); + + EXPECT_EQ(audio_buffer.num_channels(), kNumChannels); + EXPECT_EQ(audio_buffer.num_frames(), kFramesPerBuffer); + EXPECT_EQ(static_cast<size_t>(audio_buffer.end() - audio_buffer.begin()), + kNumChannels); + + // Test range-based for-loop. + size_t channel_idx = 0; + for (const AudioBuffer::Channel& channel : audio_buffer) { + EXPECT_EQ(channel.begin(), audio_buffer[channel_idx].begin()); + EXPECT_EQ(channel.end(), audio_buffer[channel_idx].end()); + ++channel_idx; + } +} + +// Tests assignment operator from std::vector<std::vector<float>>. +TEST(AudioBuffer, AudioBufferAssignmentOperator) { + const std::vector<std::vector<float>> kTestVector = { + {0.0f, 1.0f, 2.0f}, {3.0f, 4.0f, 5.0f}, {6.0f, 7.0f, 8.0f}}; + + AudioBuffer audio_buffer(kTestVector.size(), kTestVector[0].size()); + audio_buffer = kTestVector; + + for (size_t channel = 0; channel < kTestVector.size(); ++channel) { + for (size_t frame = 0; frame < kTestVector[0].size(); ++frame) { + EXPECT_EQ(audio_buffer[channel][frame], kTestVector[channel][frame]); + } + } +} + +// Tests move constructor. +TEST(AudioBuffer, AudioBufferMoveConstructor) { + const std::vector<std::vector<float>> kTestVector = { + {0.0f, 1.0f, 2.0f}, {3.0f, 4.0f, 5.0f}, {6.0f, 7.0f, 8.0f}}; + + AudioBuffer audio_buffer(kTestVector.size(), kTestVector[0].size()); + audio_buffer = kTestVector; + const size_t num_channels = audio_buffer.num_channels(); + const size_t num_frames = audio_buffer.num_frames(); + + AudioBuffer moved_audio_buffer(std::move(audio_buffer)); + EXPECT_EQ(audio_buffer.num_channels(), 0U); + EXPECT_EQ(audio_buffer.num_frames(), 0U); + EXPECT_EQ(moved_audio_buffer.num_channels(), num_channels); + EXPECT_EQ(moved_audio_buffer.num_frames(), num_frames); + + for (size_t channel = 0; channel < kTestVector.size(); ++channel) { + for (size_t frame = 0; frame < kTestVector[0].size(); ++frame) { + EXPECT_EQ(moved_audio_buffer[channel][frame], + kTestVector[channel][frame]); + } + } +} + +// Tests memory alignment of each channel buffer. The address if the first +// element of each channel should be memory aligned. +TEST(AudioBuffer, TestBufferAlignment) { + static const size_t kNumRuns = 100; + static const size_t kNumChannels = 16; + + for (size_t run = 0; run < kNumRuns; ++run) { + const size_t frames_per_buffer = run + 1; + AudioBuffer audio_buffer(kNumChannels, frames_per_buffer); + for (size_t channel = 0; channel < kNumChannels; ++channel) { + const AudioBuffer::Channel& channel_view = audio_buffer[channel]; + const bool is_aligned = + ((reinterpret_cast<size_t>(&(*channel_view.begin())) & + (kMemoryAlignmentBytes - 1)) == 0); + EXPECT_TRUE(is_aligned); + } + } +} + +// Tests Clear method. +TEST(AudioBuffer, AudioBufferClear) { + const std::vector<std::vector<float>> kTestVector = { + {0.0f, 1.0f, 2.0f}, {3.0f, 4.0f, 5.0f}, {6.0f, 7.0f, 8.0f}}; + + AudioBuffer audio_buffer(kTestVector.size(), kTestVector[0].size()); + audio_buffer = kTestVector; + + audio_buffer.Clear(); + + for (size_t channel = 0; channel < kTestVector.size(); ++channel) { + for (size_t frame = 0; frame < kTestVector[0].size(); ++frame) { + EXPECT_EQ(0.0f, audio_buffer[channel][frame]); + } + } +} + +// Tests GetChannelStride method. +TEST(AudioBuffer, GetChannelStride) { + const size_t num_frames_per_alignment = kMemoryAlignmentBytes / sizeof(float); + for (size_t num_frames = 1; num_frames < num_frames_per_alignment * 5; + ++num_frames) { + AudioBuffer buffer(1, num_frames); + // Fast way to ceil(frame/num_frames_per_alignment). + const size_t expected_num_alignment_blocks = + (num_frames + num_frames_per_alignment - 1) / num_frames_per_alignment; + EXPECT_EQ(expected_num_alignment_blocks * num_frames_per_alignment, + buffer.GetChannelStride()); + } +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/base/channel_view.cc b/src/3rdparty/resonance-audio/resonance_audio/base/channel_view.cc new file mode 100644 index 000000000..e3d6fe14a --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/base/channel_view.cc @@ -0,0 +1,50 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "base/channel_view.h" + +#include "base/simd_utils.h" + +namespace vraudio { + +ChannelView& ChannelView::operator+=(const ChannelView& other) { + DCHECK_EQ(other.size(), size_); + DCHECK(enabled_); + float* this_sample = begin(); + const float* other_sample = other.begin(); + AddPointwise(size_, other_sample, this_sample, this_sample); + return *this; +} + +ChannelView& ChannelView::operator-=(const ChannelView& other) { + DCHECK_EQ(other.size(), size_); + DCHECK(enabled_); + float* this_sample = begin(); + const float* other_sample = other.begin(); + SubtractPointwise(size_, other_sample, this_sample, this_sample); + return *this; +} + +ChannelView& ChannelView::operator*=(const ChannelView& other) { + DCHECK_EQ(other.size(), size_); + DCHECK(enabled_); + float* this_sample = begin(); + const float* other_sample = other.begin(); + MultiplyPointwise(size_, other_sample, this_sample, this_sample); + return *this; +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/base/channel_view.h b/src/3rdparty/resonance-audio/resonance_audio/base/channel_view.h new file mode 100644 index 000000000..557773a99 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/base/channel_view.h @@ -0,0 +1,138 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_BASE_CHANNEL_VIEW_H_ +#define RESONANCE_AUDIO_BASE_CHANNEL_VIEW_H_ + +#include <algorithm> +#include <cstring> +#include <vector> + +#include "base/logging.h" + +namespace vraudio { + +// Provides an interface to a single audio channel in |AudioBuffer|. Note that a +// |ChannelView| instance does not own the data it is initialized with. +class ChannelView { + public: + // Array subscript operator returning a reference. + float& operator[](size_t index) { + DCHECK(enabled_); + DCHECK_LT(index, size_); + return *(begin() + index); + } + + // Const array subscript operator returning a const reference. + const float& operator[](size_t index) const { + DCHECK(enabled_); + DCHECK_LT(index, size_); + return *(begin() + index); + } + + // Returns the size of the channel in samples. + size_t size() const { return size_; } + + // Returns a float pointer to the begin of the channel data. + float* begin() { + DCHECK(enabled_); + return begin_itr_; + } + + // Returns a float pointer to the end of the channel data. + float* end() { + DCHECK(enabled_); + return begin_itr_ + size_; + } + + // Returns a const float pointer to the begin of the channel data. + const float* begin() const { + DCHECK(enabled_); + return begin_itr_; + } + + // Returns a const float pointer to the end of the channel data. + const float* end() const { + DCHECK(enabled_); + return begin_itr_ + size_; + } + + // Copy assignment from float vector. + ChannelView& operator=(const std::vector<float>& other) { + DCHECK(enabled_); + DCHECK_EQ(other.size(), size_); + memcpy(begin(), other.data(), sizeof(float) * size_); + return *this; + } + + // Copy assignment from ChannelView. + ChannelView& operator=(const ChannelView& other) { + if (this != &other) { + DCHECK(enabled_); + DCHECK_EQ(other.size(), size_); + memcpy(begin(), other.begin(), sizeof(float) * size_); + } + return *this; + } + + // Adds a |ChannelView| to this |ChannelView|. + ChannelView& operator+=(const ChannelView& other); + + // Subtracts a |ChannelView| from this |ChannelView|. + ChannelView& operator-=(const ChannelView& other); + + // Pointwise multiplies a |ChannelView| with this |Channelview|. + ChannelView& operator*=(const ChannelView& other); + + // Fills channel buffer with zeros. + void Clear() { + DCHECK(enabled_); + memset(begin(), 0, sizeof(float) * size_); + } + + // Allows for disabling the channel to prevent access to the channel data and + // channel iterators. It is used in the |Mixer| class to prevent the copies of + // silence |ChannelView|s. Note that |ChannelView| are enabled by default. + // + // @param enabled True to enable the channel. + void SetEnabled(bool enabled) { enabled_ = enabled; } + + // Returns true if |ChannelView| is enabled. + // + // @return State of |enabled_| flag. + bool IsEnabled() const { return enabled_; } + + private: + friend class AudioBuffer; + + // Constructor is initialized with a float pointer to the first sample and the + // size of chunk of planar channel data. + ChannelView(float* begin_itr, size_t size) + : begin_itr_(begin_itr), size_(size), enabled_(true) {} + + // Iterator of first and last element in channel. + float* const begin_itr_; + + // Channel size. + const size_t size_; + + // Flag indicating if the channel is enabled. + bool enabled_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_BASE_CHANNEL_VIEW_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/base/channel_view_test.cc b/src/3rdparty/resonance-audio/resonance_audio/base/channel_view_test.cc new file mode 100644 index 000000000..24b04dfd9 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/base/channel_view_test.cc @@ -0,0 +1,157 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "base/channel_view.h" + +#include <vector> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/audio_buffer.h" + +namespace vraudio { + +namespace { + +const float kTestData[] = {0.0f, 1.0f, 2.0f}; +const size_t kTestDataSize = sizeof(kTestData) / sizeof(float); + +typedef std::vector<float> Buffer; + +// Tests initialization of |ChannelView| class. +TEST(ChannelView, InitializationTest) { + AudioBuffer test_buffer(1, kTestDataSize); + ChannelView& test_buffer_view = test_buffer[0]; + for (size_t i = 0; i < kTestDataSize; ++i) { + test_buffer_view[i] = kTestData[i]; + } + + EXPECT_EQ(test_buffer.num_frames(), test_buffer_view.size()); + EXPECT_EQ(&test_buffer[0][0], test_buffer_view.begin()); + EXPECT_EQ(&test_buffer[0][0] + test_buffer.num_frames(), + test_buffer_view.end()); +} + +// Tests iterators and array subscript of |ChannelView|. +TEST(ChannelView, IteratorAndArraySubscriptTest) { + AudioBuffer test_buffer(1, kTestDataSize); + ChannelView& test_buffer_view = test_buffer[0]; + for (size_t i = 0; i < kTestDataSize; ++i) { + test_buffer_view[i] = kTestData[i]; + } + + for (size_t i = 0; i < test_buffer_view.size(); i++) { + EXPECT_EQ(test_buffer[0][i], test_buffer_view[i]); + EXPECT_EQ(kTestData[i], test_buffer_view[i]); + } + + // Test range-based for-loops. + for (float& sample : test_buffer_view) { + sample *= 2.0f; + } + size_t idx = 0; + for (const float& sample : test_buffer_view) { + EXPECT_EQ(kTestData[idx] * 2.0f, sample); + ++idx; + } +} + +// Tests copy-assignment operators. +TEST(ChannelView, CopyAssignmentTest) { + AudioBuffer test_buffer(1, kTestDataSize); + ChannelView& test_buffer_view = test_buffer[0]; + for (size_t i = 0; i < kTestDataSize; ++i) { + test_buffer_view[i] = kTestData[i]; + } + + AudioBuffer target_buffer(1, kTestDataSize); + ChannelView& target_vector_view = target_buffer[0]; + for (size_t i = 0; i < kTestDataSize; ++i) { + target_vector_view[i] = -1.0f; + } + + // Copy assignment from ChannelView. + target_vector_view = test_buffer_view; + + for (size_t i = 0; i < test_buffer_view.size(); i++) { + EXPECT_EQ(test_buffer_view[i], target_vector_view[i]); + } + + AudioBuffer target2_buffer(1, kTestDataSize); + ChannelView& target2_vector_view = target2_buffer[0]; + for (size_t i = 0; i < kTestDataSize; ++i) { + target2_vector_view[i] = -1.0f; + } + + // Copy assignment from AudioBuffer channel. + target2_vector_view = test_buffer[0]; + + for (size_t i = 0; i < test_buffer_view.size(); i++) { + EXPECT_EQ(test_buffer[0][i], target2_vector_view[i]); + } +} + +// Tests addition-assignment operator. +TEST(ChannelView, AdditionOperatorTest) { + // Here an AudioBuffer is used to ensure that the data is aligned. + AudioBuffer test_buffer(1, kTestDataSize); + ChannelView& test_buffer_view = test_buffer[0]; + for (size_t i = 0; i < kTestDataSize; ++i) { + test_buffer_view[i] = kTestData[i]; + } + + // Execute |ChannelView|s addition operator. + test_buffer_view += test_buffer_view; + + for (size_t i = 0; i < test_buffer_view.size(); i++) { + EXPECT_EQ(kTestData[i] * 2.0f, test_buffer_view[i]); + } +} + +// Tests subtraction-assignment operator. +TEST(ChannelView, SubtractionOperatorTest) { + // Here an AudioBuffer is used to ensure that the data is aligned. + AudioBuffer test_buffer(1, kTestDataSize); + ChannelView& test_buffer_view = test_buffer[0]; + for (size_t i = 0; i < kTestDataSize; ++i) { + test_buffer_view[i] = kTestData[i]; + } + + // Execute |ChannelView|s subtraction operator. + test_buffer_view -= test_buffer_view; + + for (size_t i = 0; i < test_buffer_view.size(); i++) { + EXPECT_EQ(0.0f, test_buffer_view[i]); + } +} + +// Tests Clear method. +TEST(ChannelView, ClearTest) { + AudioBuffer test_buffer(1, kTestDataSize); + ChannelView& test_buffer_view = test_buffer[0]; + for (size_t i = 0; i < kTestDataSize; ++i) { + test_buffer_view[i] = kTestData[i]; + } + + test_buffer_view.Clear(); + + for (const float& sample : test_buffer_view) { + EXPECT_EQ(0.0f, sample); + } +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/base/constants_and_types.h b/src/3rdparty/resonance-audio/resonance_audio/base/constants_and_types.h new file mode 100644 index 000000000..591c958dd --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/base/constants_and_types.h @@ -0,0 +1,176 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_BASE_CONSTANTS_AND_TYPES_H_ +#define RESONANCE_AUDIO_BASE_CONSTANTS_AND_TYPES_H_ + +#include <cmath> +#include <string> // for size_t + +namespace vraudio { + +// Sound object / ambisonic source identifier. + +typedef int SourceId; + +// Invalid source id that can be used to initialize handler variables during +// class construction. +static const SourceId kInvalidSourceId = -1; + + +// Defines memory alignment of audio buffers. Note that not only the first +// element of the |data_| buffer is memory aligned but also the address of the +// first elements of the |ChannelView|s. +const size_t kMemoryAlignmentBytes = 64; + +// Maximum Ambisonic order currently supported in vr audio, equivalent to High +// Quality sound object rendering mode. This number is limited by a) number of +// HRIR data points used in the binaural renderer; b) size of the lookup table +// controlling the angular spread of a sound source in the Ambisonic Lookup +// Table class. +static const int kMaxSupportedAmbisonicOrder = 3; + +// Maximum allowed size of internal buffers. +const size_t kMaxSupportedNumFrames = 16384; + +// Number of mono channels. +static const size_t kNumMonoChannels = 1; + +// Number of stereo channels. +static const size_t kNumStereoChannels = 2; + +// Number of surround 5.1 channels. +static const size_t kNumSurroundFiveDotOneChannels = 6; + +// Number of surround 7.1 channels. +static const size_t kNumSurroundSevenDotOneChannels = 8; + +// Number of first-order ambisonic channels. +static const size_t kNumFirstOrderAmbisonicChannels = 4; + +// Number of second-order ambisonic channels. +static const size_t kNumSecondOrderAmbisonicChannels = 9; + +// Number of third-order ambisonic channels. +static const size_t kNumThirdOrderAmbisonicChannels = 16; + +// Number of first-order ambisonic with non-diegetic stereo channels. +static const size_t kNumFirstOrderAmbisonicWithNonDiegeticStereoChannels = 6; + +// Number of second-order ambisonic with non-diegetic stereo channels. +static const size_t kNumSecondOrderAmbisonicWithNonDiegeticStereoChannels = 11; + +// Number of third-order ambisonic with non-diegetic stereo channels. +static const size_t kNumThirdOrderAmbisonicWithNonDiegeticStereoChannels = 18; + +// Negative 60dB in amplitude. +static const float kNegative60dbInAmplitude = 0.001f; + +// Tolerated error margins for floating points. +static const double kEpsilonDouble = 1e-6; +static const float kEpsilonFloat = 1e-6f; + +// Inverse square root of two (equivalent to -3dB audio signal attenuation). +static const float kInverseSqrtTwo = 1.0f / std::sqrt(2.0f); + +// Square roots. +static const float kSqrtTwo = std::sqrt(2.0f); +static const float kSqrtThree = std::sqrt(3.0f); + +// Pi in radians. +static const float kPi = static_cast<float>(M_PI); +// Half pi in radians. +static const float kHalfPi = static_cast<float>(M_PI / 2.0); +// Two pi in radians. +static const float kTwoPi = static_cast<float>(2.0 * M_PI); + +// Defines conversion factor from degrees to radians. +static const float kRadiansFromDegrees = static_cast<float>(M_PI / 180.0); + +// Defines conversion factor from radians to degrees. +static const float kDegreesFromRadians = static_cast<float>(180.0 / M_PI); + +// The negated natural logarithm of 1000. +static const float kNegativeLog1000 = -std::log(1000.0f); + +// The lowest octave band for computing room effects. +static const float kLowestOctaveBandHz = 31.25f; + +// Number of octave bands in which room effects are computed. +static const size_t kNumReverbOctaveBands = 9; + +// Centers of possible frequency bands up 8 kHz. +// ------------------------------------ +// Band no. Low Center High [Frequencies in Hz] +// ------------------------------------ +// 0 22 31.25 44.2 +// 1 44.2 62.5 88.4 +// 2 88.4 125 176.8 +// 3 176.8 250 353.6 +// 4 353.6 500 707.1 +// 5 707.1 1000 1414.2 +// 6 1414.2 2000 2828.4 +// 7 2828.4 4000 5656.9 +// 8 5656.9 8000 11313.7 +//-------------------------------------- +const float kOctaveBandCentres[kNumReverbOctaveBands] = { + 31.25f, 62.5f, 125.0f, 250.0f, 500.0f, 1000.0f, 2000.0f, 4000.0f, 8000.0f}; + +// Number of surfaces in a shoe-box room. +static const size_t kNumRoomSurfaces = 6; + +// Speed of sound in air at 20 degrees Celsius in meters per second. +// http://www.sengpielaudio.com/calculator-speedsound.htm +static const float kSpeedOfSound = 343.0f; + +// Locations of the stereo virtual loudspeakers in degrees. +static const float kStereoLeftDegrees = 90.0f; +static const float kStereoRightDegrees = -90.0f; + +// Conversion factor from seconds to milliseconds. +static const float kMillisecondsFromSeconds = 1000.0f; + +// Conversion factor from milliseconds to seconds. +static const float kSecondsFromMilliseconds = 0.001f; + +// Conversion factor from seconds to milliseconds. +static const double kMicrosecondsFromSeconds = 1e6; + +// Conversion factor from milliseconds to seconds. +static const double kSecondsFromMicroseconds = 1e-6; + +// The distance threshold where the near field effect should fade in. +static const float kNearFieldThreshold = 1.0f; + +// Minimum allowed distance of a near field sound source used to cap the allowed +// energy boost. +static const float kMinNearFieldDistance = 0.1f; + +// Maximum gain applied by Near Field Effect to the mono source signal. +static const float kMaxNearFieldEffectGain = 9.0f; + +// Number of samples across which the gain value should be interpolated for +// a unit gain change of 1.0f. + +static const size_t kUnitRampLength = 2048; + +// Rotation quantization which applies in ambisonic soundfield rotators. + +static const float kRotationQuantizationRad = 1.0f * kRadiansFromDegrees; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_BASE_CONSTANTS_AND_TYPES_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/base/integral_types.h b/src/3rdparty/resonance-audio/resonance_audio/base/integral_types.h new file mode 100644 index 000000000..7a4d9216e --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/base/integral_types.h @@ -0,0 +1,121 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Copyright 2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Basic integer type definitions for various platforms + +#ifndef BASE_INTEGRAL_TYPES_H_ +#define BASE_INTEGRAL_TYPES_H_ + +// Standard typedefs +typedef signed char schar; +typedef signed char int8; +typedef short int16; +typedef int int32; +#ifdef _MSC_VER +typedef __int64 int64; +#else +typedef long long int64; +#endif /* _MSC_VER */ + +// NOTE: unsigned types are DANGEROUS in loops and other arithmetical +// places. Use the signed types unless your variable represents a bit +// pattern (eg a hash value) or you really need the extra bit. Do NOT +// use 'unsigned' to express "this value should always be positive"; +// use assertions for this. + +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef unsigned int uint32; +#ifdef _MSC_VER +typedef unsigned __int64 uint64; +#else +typedef unsigned long long uint64; +#endif /* _MSC_VER */ + +// A type to represent a Unicode code-point value. As of Unicode 4.0, +// such values require up to 21 bits. +// (For type-checking on pointers, make this explicitly signed, +// and it should always be the signed version of whatever int32 is.) +typedef signed int char32; + +// A type to represent a natural machine word (for e.g. efficiently +// scanning through memory for checksums or index searching). Don't use +// this for storing normal integers. Ideally this would be just +// unsigned int, but our 64-bit architectures use the LP64 model +// (http://en.wikipedia.org/wiki/64-bit_computing#64-bit_data_models), hence +// their ints are only 32 bits. We want to use the same fundamental +// type on all archs if possible to preserve *printf() compatability. +typedef unsigned long uword_t; + +// long long macros to be used because gcc and vc++ use different suffixes, +// and different size specifiers in format strings +#undef GG_LONGLONG +#undef GG_ULONGLONG +#undef GG_LL_FORMAT + +#ifdef _MSC_VER /* if Visual C++ */ + +// VC++ long long suffixes +#define GG_LONGLONG(x) x##I64 +#define GG_ULONGLONG(x) x##UI64 + +// Length modifier in printf format string for int64's (e.g. within %d) +#define GG_LL_FORMAT "I64" // As in printf("%I64d", ...) +#define GG_LL_FORMAT_W L"I64" + +#else /* not Visual C++ */ + +#define GG_LONGLONG(x) x##LL +#define GG_ULONGLONG(x) x##ULL +#define GG_LL_FORMAT "ll" // As in "%lld". Note that "q" is poor form also. +#define GG_LL_FORMAT_W L"ll" + +#endif // _MSC_VER + + +static const uint8 kuint8max = (( uint8) 0xFF); +static const uint16 kuint16max = ((uint16) 0xFFFF); +static const uint32 kuint32max = ((uint32) 0xFFFFFFFF); +static const uint64 kuint64max = ((uint64) GG_LONGLONG(0xFFFFFFFFFFFFFFFF)); +static const int8 kint8min = (( int8) ~0x7F); +static const int8 kint8max = (( int8) 0x7F); +static const int16 kint16min = (( int16) ~0x7FFF); +static const int16 kint16max = (( int16) 0x7FFF); +static const int32 kint32min = (( int32) ~0x7FFFFFFF); +static const int32 kint32max = (( int32) 0x7FFFFFFF); +static const int64 kint64min = (( int64) GG_LONGLONG(~0x7FFFFFFFFFFFFFFF)); +static const int64 kint64max = (( int64) GG_LONGLONG(0x7FFFFFFFFFFFFFFF)); + + +typedef uint64 Fprint; +static const Fprint kIllegalFprint = 0; +static const Fprint kMaxFprint = GG_ULONGLONG(0xFFFFFFFFFFFFFFFF); + +#endif // BASE_INTEGRAL_TYPES_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/base/logging.h b/src/3rdparty/resonance-audio/resonance_audio/base/logging.h new file mode 100644 index 000000000..63dd00431 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/base/logging.h @@ -0,0 +1,108 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_PLATFORM_LOGGING_H_ +#define RESONANCE_AUDIO_PLATFORM_LOGGING_H_ + +#include <cstdlib> + +#include <cassert> +#include <iostream> +#include <sstream> + +#undef DCHECK +#undef DCHECK_EQ +#undef DCHECK_NE +#undef DCHECK_LE +#undef DCHECK_LT +#undef DCHECK_GE +#undef DCHECK_GT +#undef CHECK +#undef CHECK_EQ +#undef CHECK_NE +#undef CHECK_LE +#undef CHECK_LT +#undef CHECK_GE +#undef CHECK_GT +#undef CHECK_NOTNULL +#undef LOG + +// This class is used to disable logging, while still allowing for log messages +// to contain '<<' expressions. +class NullLogger { + public: + std::ostream& GetStream() { + static std::ostream kNullStream(nullptr); + return kNullStream; + } +}; + +// If statement prevents unused variable warnings. +#define DCHECK(expr) \ + if (false && (expr)) \ + ; \ + else \ + NullLogger().GetStream() +#define DCHECK_OP(val1, val2, op) DCHECK((val1)op(val2)) + +#define DCHECK_EQ(val1, val2) DCHECK_OP((val1), (val2), ==) +#define DCHECK_NE(val1, val2) DCHECK_OP((val1), (val2), !=) +#define DCHECK_LE(val1, val2) DCHECK_OP((val1), (val2), <=) +#define DCHECK_LT(val1, val2) DCHECK_OP((val1), (val2), <) +#define DCHECK_GE(val1, val2) DCHECK_OP((val1), (val2), >=) +#define DCHECK_GT(val1, val2) DCHECK_OP((val1), (val2), >) + +// This class is used to log to std::cerr. +class FatalLogger { + public: + FatalLogger(const char* file, int line) { + error_string_ << file << ":" << line << ": "; + } + ~FatalLogger() { + const std::string error_string = error_string_.str(); + std::cerr << error_string << std::endl; + abort(); + } + std::ostream& GetStream() { return error_string_; } + + private: + std::ostringstream error_string_; +}; + +#define CHECK(condition) \ + !(condition) ? FatalLogger(__FILE__, __LINE__).GetStream() \ + : NullLogger().GetStream() + +#define CHECK_OP(val1, val2, op) CHECK((val1)op(val2)) + +#define CHECK_EQ(val1, val2) CHECK_OP((val1), (val2), ==) +#define CHECK_NE(val1, val2) CHECK_OP((val1), (val2), !=) +#define CHECK_LE(val1, val2) CHECK_OP((val1), (val2), <=) +#define CHECK_LT(val1, val2) CHECK_OP((val1), (val2), <) +#define CHECK_GE(val1, val2) CHECK_OP((val1), (val2), >=) +#define CHECK_GT(val1, val2) CHECK_OP((val1), (val2), >) + +// Helper for CHECK_NOTNULL(), using C++11 perfect forwarding. +template <typename T> +T CheckNotNull(T&& t) { + assert(t != nullptr); + return std::forward<T>(t); +} +#define CHECK_NOTNULL(val) CheckNotNull(val) + +#define LOG(severity) NullLogger().GetStream() + +#endif // RESONANCE_AUDIO_PLATFORM_LOGGING_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/base/misc_math.cc b/src/3rdparty/resonance-audio/resonance_audio/base/misc_math.cc new file mode 100644 index 000000000..4de96ba41 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/base/misc_math.cc @@ -0,0 +1,94 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "base/misc_math.h" + +namespace vraudio { + +WorldPosition::WorldPosition() { setZero(); } + +WorldRotation::WorldRotation() { setIdentity(); } + +bool LinearLeastSquareFitting(const std::vector<float>& x_array, + const std::vector<float>& y_array, float* slope, + float* intercept, float* r_squared) { + // The array sizes must agree. + if (x_array.size() != y_array.size()) { + return false; + } + + // At least two points are needed to fit a line. + if (x_array.size() < 2) { + return false; + } + + float x_sum = 0.0f; + float y_sum = 0.0f; + float x_square_sum = 0.0f; + float xy_sum = 0.0f; + + for (size_t i = 0; i < x_array.size(); ++i) { + const float x = x_array[i]; + const float y = y_array[i]; + x_sum += x; + y_sum += y; + x_square_sum += x * x; + xy_sum += x * y; + } + + const float n_inverse = 1.0f / static_cast<float>(x_array.size()); + const float x_mean = x_sum * n_inverse; + const float y_mean = y_sum * n_inverse; + const float x_square_mean = x_square_sum * n_inverse; + const float xy_mean = xy_sum * n_inverse; + const float x_mean_square = x_mean * x_mean; + + // Prevent division by zero, which means a vertical line and the slope is + // infinite. + if (x_square_mean == x_mean_square) { + return false; + } + + *slope = (xy_mean - x_mean * y_mean) / (x_square_mean - x_mean_square); + *intercept = y_mean - *slope * x_mean; + + // Compute the coefficient of determination. + float total_sum_of_squares = 0.0f; + float residual_sum_of_squares = 0.0f; + for (size_t i = 0; i < x_array.size(); ++i) { + const float y_i = y_array[i]; + total_sum_of_squares += (y_i - y_mean) * (y_i - y_mean); + const float y_fit = *slope * x_array[i] + *intercept; + residual_sum_of_squares += (y_fit - y_i) * (y_fit - y_i); + } + + if (total_sum_of_squares == 0.0f) { + if (residual_sum_of_squares == 0.0f) { + // A special case where all y's are equal, where the |r_squared| should + // be 1.0, and the line is a perfectly horizontal line. + *r_squared = 1.0f; + return true; + } else { + // Division by zero. + return false; + } + } + + *r_squared = 1.0f - residual_sum_of_squares / total_sum_of_squares; + return true; +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/base/misc_math.h b/src/3rdparty/resonance-audio/resonance_audio/base/misc_math.h new file mode 100644 index 000000000..5993eb55d --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/base/misc_math.h @@ -0,0 +1,385 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_BASE_MISC_MATH_H_ +#define RESONANCE_AUDIO_BASE_MISC_MATH_H_ + +#ifndef _USE_MATH_DEFINES +#define _USE_MATH_DEFINES // Enable MSVC math constants (e.g., M_PI). +#endif // _USE_MATH_DEFINES + +#include <algorithm> +#include <cmath> +#include <cstdint> +#include <limits> +#include <utility> +#include <vector> + +#include "base/integral_types.h" +#include "Eigen/Dense" +#include "base/constants_and_types.h" +#include "base/logging.h" + +namespace vraudio { + +class WorldPosition : public Eigen::Matrix<float, 3, 1, Eigen::DontAlign> { + public: + // Inherits all constructors with 1-or-more arguments. Necessary because + // MSVC12 doesn't support inheriting constructors. + template <typename Arg1, typename... Args> + WorldPosition(const Arg1& arg1, Args&&... args) + : Matrix(arg1, std::forward<Args>(args)...) {} + + // Constructs a zero vector. + WorldPosition(); + + // Returns True if other |WorldPosition| differs by at least |kEpsilonFloat|. + bool operator!=(const WorldPosition& other) const { + return std::abs(this->x() - other.x()) > kEpsilonFloat || + std::abs(this->y() - other.y()) > kEpsilonFloat || + std::abs(this->z() - other.z()) > kEpsilonFloat; + } +}; + +class WorldRotation : public Eigen::Quaternion<float, Eigen::DontAlign> { + public: + // Inherits all constructors with 1-or-more arguments. Necessary because + // MSVC12 doesn't support inheriting constructors. + template <typename Arg1, typename... Args> + WorldRotation(const Arg1& arg1, Args&&... args) + : Quaternion(arg1, std::forward<Args>(args)...) {} + + // Constructs an identity rotation. + WorldRotation(); + + // Returns the shortest arc between two |WorldRotation|s in radians. + float AngularDifferenceRad(const WorldRotation& other) const { + const Quaternion difference = this->inverse() * other; + return static_cast<float>(Eigen::AngleAxisf(difference).angle()); + } +}; + +typedef Eigen::AngleAxis<float> AngleAxisf; + +typedef WorldPosition AudioPosition; + +typedef WorldRotation AudioRotation; + +// Converts |world_position| into an equivalent audio space position. +// The world space follows the typical CG coordinate system convention: +// Positive x points right, positive y points up, negative z points forward. +// The audio space follows the ambiX coordinate system convention that is +// commonly accepted in literature [http://goo.gl/XdYNm9]: +// Positive x points forward, negative y points right, positive z points up. +// Positions in both world space and audio space are in meters. +// +// @param world_position 3D position in world space. +// @param audio_position Output 3D position in audio space. +inline void ConvertAudioFromWorldPosition(const WorldPosition& world_position, + AudioPosition* audio_position) { + DCHECK(audio_position); + (*audio_position)(0) = -world_position[2]; + (*audio_position)(1) = -world_position[0]; + (*audio_position)(2) = world_position[1]; +} + +// Converts |audio_position| into an equivalent world space position. +// The world space follows the typical CG coordinate system convention: +// Positive x points right, positive y points up, negative z points forward. +// The audio space follows the ambiX coordinate system convention that is +// commonly accepted in literature [http://goo.gl/XdYNm9]: +// Positive x points forward, negative y points right, positive z points up. +// Positions in both world space and audio space are in meters. +// +// @param audio_position 3D position in audio space. +// @param world_position Output 3D position in world space. +inline void ConvertWorldFromAudioPosition(const AudioPosition& audio_position, + AudioPosition* world_position) { + DCHECK(world_position); + (*world_position)(0) = -audio_position[1]; + (*world_position)(1) = audio_position[2]; + (*world_position)(2) = -audio_position[0]; +} + +// Converts |world_rotation| into an equivalent audio space rotation. +// The world space follows the typical CG coordinate system convention: +// Positive x points right, positive y points up, negative z points forward. +// The audio space follows the ambiX coordinate system convention that is +// commonly accepted in literature [http://goo.gl/XdYNm9]: +// Positive x points forward, negative y points right, positive z points up. +// Positions in both world space and audio space are in meters. +// +// @param world_rotation 3D rotation in world space. +// @param audio_rotation Output 3D rotation in audio space. +inline void ConvertAudioFromWorldRotation(const WorldRotation& world_rotation, + AudioRotation* audio_rotation) { + DCHECK(audio_rotation); + audio_rotation->w() = world_rotation.w(); + audio_rotation->x() = -world_rotation.x(); + audio_rotation->y() = world_rotation.y(); + audio_rotation->z() = -world_rotation.z(); +} + +// Returns the relative direction vector |from_position| and |to_position| by +// rotating the relative position vector with respect to |from_rotation|. +// +// @param from_position Origin position of the direction. +// @param from_rotation Origin orientation of the direction. +// @param to_position Target position of the direction. +// @param relative_direction Relative direction vector (not normalized). +inline void GetRelativeDirection(const WorldPosition& from_position, + const WorldRotation& from_rotation, + const WorldPosition& to_position, + WorldPosition* relative_direction) { + DCHECK(relative_direction); + *relative_direction = + from_rotation.conjugate() * (to_position - from_position); +} + +// Returns the closest relative position in an axis-aligned bounding box to the +// given |relative_position|. +// +// @param position Input position relative to the center of the bounding box. +// @param aabb_dimensions Bounding box dimensions. +// @return aabb bounded position. +inline void GetClosestPositionInAabb(const WorldPosition& relative_position, + const WorldPosition& aabb_dimensions, + WorldPosition* closest_position) { + DCHECK(closest_position); + const WorldPosition aabb_offset = 0.5f * aabb_dimensions; + (*closest_position)[0] = + std::min(std::max(relative_position[0], -aabb_offset[0]), aabb_offset[0]); + (*closest_position)[1] = + std::min(std::max(relative_position[1], -aabb_offset[1]), aabb_offset[1]); + (*closest_position)[2] = + std::min(std::max(relative_position[2], -aabb_offset[2]), aabb_offset[2]); +} + +// Returns true if given world |position| is in given axis-aligned bounding box. +// +// @param position Position to be tested. +// @param aabb_center Bounding box center. +// @param aabb_dimensions Bounding box dimensions. +// @return True if |position| is within bounding box, false otherwise. +inline bool IsPositionInAabb(const WorldPosition& position, + const WorldPosition& aabb_center, + const WorldPosition& aabb_dimensions) { + return std::abs(position[0] - aabb_center[0]) <= 0.5f * aabb_dimensions[0] && + std::abs(position[1] - aabb_center[1]) <= 0.5f * aabb_dimensions[1] && + std::abs(position[2] - aabb_center[2]) <= 0.5f * aabb_dimensions[2]; +} + +// Returns true if an integer overflow occurred during the calculation of +// x = a * b. +// +// @param a First multiplicand. +// @param b Second multiplicand. +// @param x Product. +// @return True if integer overflow occurred, false otherwise. +template <typename T> +inline bool DoesIntegerMultiplicationOverflow(T a, T b, T x) { + // Detects an integer overflow occurs by inverting the multiplication and + // testing for x / a != b. + return a == 0 ? false : (x / a != b); +} + +// Returns true if an integer overflow occurred during the calculation of +// a + b. +// +// @param a First summand. +// @param b Second summand. +// @return True if integer overflow occurred, false otherwise. +template <typename T> +inline bool DoesIntegerAdditionOverflow(T a, T b) { + T x = a + b; + return x < b; +} + +// Safely converts an int to a size_t. +// +// @param i Integer input. +// @param x Size_t output. +// @return True if integer overflow occurred, false otherwise. +inline bool DoesIntSafelyConvertToSizeT(int i, size_t* x) { + if (i < 0) { + return false; + } + *x = static_cast<size_t>(i); + return true; +} + +// Safely converts a size_t to an int. +// +// @param i Size_t input. +// @param x Integer output. +// @return True if integer overflow occurred, false otherwise. +inline bool DoesSizeTSafelyConvertToInt(size_t i, int* x) { + if (i > static_cast<size_t>(std::numeric_limits<int>::max())) { + return false; + } + *x = static_cast<int>(i); + return true; +} + +// Finds the greatest common divisor between two integer values using the +// Euclidean algorithm. Always returns a positive integer. +// +// @param a First of the two integer values. +// @param b second of the two integer values. +// @return The greatest common divisor of the two integer values. +inline int FindGcd(int a, int b) { + a = std::abs(a); + b = std::abs(b); + int temp_value = 0; + while (b != 0) { + temp_value = b; + b = a % b; + a = temp_value; + } + return a; +} + +// Finds the next power of two from an integer. This method works with values +// representable by unsigned 32 bit integers. +// +// @param input Integer value. +// @return The next power of two from |input|. +inline size_t NextPowTwo(size_t input) { + // Ensure the value fits in a uint32_t. + DCHECK_LT(static_cast<uint64_t>(input), + static_cast<uint64_t>(std::numeric_limits<uint32_t>::max())); + uint32_t number = static_cast<uint32_t>(--input); + number |= number >> 1; // Take care of 2 bit numbers. + number |= number >> 2; // Take care of 4 bit numbers. + number |= number >> 4; // Take care of 8 bit numbers. + number |= number >> 8; // Take care of 16 bit numbers. + number |= number >> 16; // Take care of 32 bit numbers. + number++; + return static_cast<size_t>(number); +} + +// Returns the factorial (!) of x. If x < 0, it returns 0. +inline float Factorial(int x) { + if (x < 0) return 0.0f; + float result = 1.0f; + for (; x > 0; --x) result *= static_cast<float>(x); + return result; +} + +// Returns the double factorial (!!) of x. +// For odd x: 1 * 3 * 5 * ... * (x - 2) * x +// For even x: 2 * 4 * 6 * ... * (x - 2) * x +// If x < 0, it returns 0. +inline float DoubleFactorial(int x) { + if (x < 0) return 0.0f; + float result = 1.0f; + for (; x > 0; x -= 2) result *= static_cast<float>(x); + return result; +} + +// This is a *safe* alternative to std::equal function as a workaround in order +// to avoid MSVC compiler warning C4996 for unchecked iterators (see +// https://msdn.microsoft.com/en-us/library/aa985965.aspx). +// Also note that, an STL equivalent of this function was introduced in C++14 to +// be replaced with this implementation (see version (5) in +// http://en.cppreference.com/w/cpp/algorithm/equal). +template <typename Iterator> +inline bool EqualSafe(const Iterator& lhs_begin, const Iterator& lhs_end, + const Iterator& rhs_begin, const Iterator& rhs_end) { + auto lhs_itr = lhs_begin; + auto rhs_itr = rhs_begin; + while (lhs_itr != lhs_end && rhs_itr != rhs_end) { + if (*lhs_itr != *rhs_itr) { + return false; + } + ++lhs_itr; + ++rhs_itr; + } + return lhs_itr == lhs_end && rhs_itr == rhs_end; +} + +// Fast reciprocal of square-root. See: https://goo.gl/fqvstz for details. +// +// @param input The number to be inverse rooted. +// @return An approximation of the reciprocal square root of |input|. +inline float FastReciprocalSqrt(float input) { + const float kThreeHalfs = 1.5f; + const uint32_t kMagicNumber = 0x5f3759df; + + // Approximate a logarithm by aliasing to an integer. + uint32_t integer; + memcpy(&integer, &input, sizeof(float)); + integer = kMagicNumber - (integer >> 1); + float approximation; + memcpy(&approximation, &integer, sizeof(float)); + const float half_input = input * 0.5f; + // One iteration of Newton's method. + return approximation * + (kThreeHalfs - (half_input * approximation * approximation)); +} + +// Finds the best-fitting line to a given set of 2D points by minimizing the +// sum of the squares of the vertical (along y-axis) offsets. The slope and +// intercept of the fitted line are recorded, as well as the coefficient of +// determination, which gives the quality of the fitting. +// See http://mathworld.wolfram.com/LeastSquaresFitting.html for how to compute +// these values. +// +// @param x_array Array of the x coordinates of the points. +// @param y_array Array of the y coordinates of the points. +// @param slope Output slope of the fitted line. +// @param intercept Output slope of the fitted line. +// @param r_squared Coefficient of determination. +// @return False if the fitting fails. +bool LinearLeastSquareFitting(const std::vector<float>& x_array, + const std::vector<float>& y_array, float* slope, + float* intercept, float* r_squared); + +// Computes |base|^|exp|, where |exp| is a *non-negative* integer, with the +// squared exponentiation (a.k.a double-and-add) method. +// When T is a floating point type, this has the same semantics as pow(), but +// is much faster. +// T can also be any integral type, in which case computations will be +// performed in the value domain of this integral type, and overflow semantics +// will be those of T. +// You can also use any type for which operator*= is defined. +// See : + +// This method is reproduced here so vraudio classes don't need to depend on +// //util/math/mathutil.h +// +// @tparam base Input to the exponent function. Any type for which *= is +// defined. +// @param exp Integer exponent, must be greater than or equal to zero. +// @return |base|^|exp|. +template <typename T> +static inline T IntegerPow(T base, int exp) { + DCHECK_GE(exp, 0); + T result = static_cast<T>(1); + while (true) { + if (exp & 1) { + result *= base; + } + exp >>= 1; + if (!exp) break; + base *= base; + } + return result; +} + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_BASE_MISC_MATH_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/base/misc_math_test.cc b/src/3rdparty/resonance-audio/resonance_audio/base/misc_math_test.cc new file mode 100644 index 000000000..e75c100c6 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/base/misc_math_test.cc @@ -0,0 +1,373 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "base/misc_math.h" + +#include "third_party/googletest/googletest/include/gtest/gtest.h" + +#include "base/constants_and_types.h" + +namespace vraudio { + +namespace { + +TEST(MiscMath, WorldPositionEqualityTest) { + const WorldPosition kOriginalWorldPosition(0.33f, 0.44f, 0.55f); + const WorldPosition kSameWorldPosition = kOriginalWorldPosition; + EXPECT_FALSE(kOriginalWorldPosition != kSameWorldPosition); +} + +TEST(MiscMath, WorldPositionInequalityTest) { + const WorldPosition kOriginalWorldPosition(0.11f, 0.22f, 0.33f); + const std::vector<WorldPosition> kDifferentWorldPositions{ + {-0.22f, 0.22f, 0.33f}, {0.11f, -0.33f, 0.33f}, {0.11f, 0.22f, -0.22f}, + {0.11f, 0.33f, -0.44f}, {0.22f, 0.22f, -0.44f}, {0.22f, 0.33f, -0.55f}}; + + for (auto& position : kDifferentWorldPositions) { + EXPECT_TRUE(kOriginalWorldPosition != position); + } +} + +TEST(MiscMath, ConvertAudioFromWorldPositionTest) { + static const WorldPosition kWorldPosition(0.5f, -1.2f, 10.f); + static const AudioPosition kExpectedAudioPosition( + -kWorldPosition[2], -kWorldPosition[0], kWorldPosition[1]); + AudioPosition test_position; + ConvertAudioFromWorldPosition(kWorldPosition, &test_position); + + EXPECT_TRUE(kExpectedAudioPosition.isApprox(test_position, kEpsilonFloat)); +} + +TEST(MiscMath, ConvertWorldFromAudioPositionTest) { + static const AudioPosition kAudioPosition(1.0f, 2.0f, -0.2f); + static const WorldPosition kExpectedWorldPosition( + -kAudioPosition[1], kAudioPosition[2], -kAudioPosition[0]); + + WorldPosition test_position; + ConvertWorldFromAudioPosition(kAudioPosition, &test_position); + + EXPECT_TRUE(kExpectedWorldPosition.isApprox(test_position, kEpsilonFloat)); +} + +TEST(MiscMath, ConvertAudioFromWorldRotationTest) { + static const WorldRotation kWorldRotation(1.0f, 0.5f, -1.2f, 10.f); + static const AudioRotation kExpectedAudioRotation( + kWorldRotation.w(), -kWorldRotation.x(), kWorldRotation.y(), + -kWorldRotation.z()); + AudioRotation test_rotation; + ConvertAudioFromWorldRotation(kWorldRotation, &test_rotation); + + EXPECT_TRUE(kExpectedAudioRotation.isApprox(test_rotation, kEpsilonFloat)); +} + +TEST(MiscMath, GetRelativeDirectionTest) { + static const WorldPosition kFromPosition(0.0f, 0.0f, 0.0f); + static const WorldPosition kFromRotationAxis(0.0f, 0.0f, 1.0f); + static const float kFromRotationAngle = static_cast<float>(M_PI / 2.0); + WorldRotation kFromRotation = + WorldRotation(AngleAxisf(kFromRotationAngle, kFromRotationAxis)); + + static const WorldPosition kToPosition(1.0f, 2.0f, 3.0f); + + static const WorldPosition kExpectedRelativeDirection(2.0f, -1.0f, 3.0f); + WorldPosition test_relative_direction; + GetRelativeDirection(kFromPosition, kFromRotation, kToPosition, + &test_relative_direction); + + EXPECT_TRUE(kExpectedRelativeDirection.isApprox(test_relative_direction, + kEpsilonFloat)); +} + +TEST(MiscMath, GetClosestPositionInAabbInsideTest) { + static const WorldPosition kRelativeSourcePosition(0.0f, -0.2f, 0.0f); + static const WorldPosition kRoomDimensions(1.0f, 1.0f, 1.0f); + static const WorldPosition kExpectedAabbPosition = kRelativeSourcePosition; + WorldPosition test_position; + GetClosestPositionInAabb(kRelativeSourcePosition, kRoomDimensions, + &test_position); + + EXPECT_TRUE(kExpectedAabbPosition.isApprox(test_position, kEpsilonFloat)); +} + +TEST(MiscMath, GetClosestPositionInAabbOutsideTest) { + static const WorldPosition kRelativeSourcePosition(0.2f, 0.7f, -0.5f); + static const WorldPosition kRoomDimensions(1.0f, 1.0f, 1.0f); + static const WorldPosition kExpectedAabbPosition(0.2f, 0.5f, -0.5f); + WorldPosition test_position; + GetClosestPositionInAabb(kRelativeSourcePosition, kRoomDimensions, + &test_position); + + EXPECT_TRUE(kExpectedAabbPosition.isApprox(test_position, kEpsilonFloat)); +} + +TEST(MiscMath, IsPositionInAabbInsideTest) { + static const WorldPosition kSourcePosition(0.5f, 0.3f, 0.2f); + static const WorldPosition kRoomPosition(0.5f, 0.5f, 0.5f); + static const WorldPosition kRoomDimensions(1.0f, 1.0f, 1.0f); + + EXPECT_TRUE( + IsPositionInAabb(kSourcePosition, kRoomPosition, kRoomDimensions)); +} + +TEST(MiscMath, IsPositionInAabbOutsideTest) { + static const WorldPosition kSourcePosition(0.7f, 1.2f, 0.0f); + static const WorldPosition kRoomPosition(0.5f, 0.5f, 0.5f); + static const WorldPosition kRoomDimensions(1.0f, 1.0f, 1.0f); + + EXPECT_FALSE( + IsPositionInAabb(kSourcePosition, kRoomPosition, kRoomDimensions)); +} + +TEST(MiscMath, IntegerMultiplicationOverflowDetection) { + static const size_t kMaxValue = std::numeric_limits<size_t>::max(); + static const size_t kHalfMaxValue = kMaxValue / 2; + + // 2 * 3 == 6 should not lead to an integer overflow. + EXPECT_FALSE(DoesIntegerMultiplicationOverflow<size_t>(2, 3, 6)); + + EXPECT_FALSE(DoesIntegerMultiplicationOverflow<size_t>(kHalfMaxValue, 2, + kHalfMaxValue * 2)); + EXPECT_TRUE( + DoesIntegerMultiplicationOverflow<size_t>(kMaxValue, 2, kMaxValue << 1)); + EXPECT_FALSE( + DoesIntegerMultiplicationOverflow<size_t>(0, kMaxValue, 0 * kMaxValue)); + EXPECT_FALSE( + DoesIntegerMultiplicationOverflow<size_t>(kMaxValue, 0, kMaxValue * 0)); +} + +TEST(MiscMath, DoesIntegerAdditionOverflow) { + static const size_t kMaxValue = std::numeric_limits<size_t>::max(); + static const size_t kHalfMaxValue = kMaxValue / 2; + + EXPECT_FALSE( + DoesIntegerAdditionOverflow<size_t>(kHalfMaxValue, kHalfMaxValue)); + EXPECT_TRUE(DoesIntegerAdditionOverflow<size_t>(kMaxValue, kHalfMaxValue)); + EXPECT_TRUE(DoesIntegerAdditionOverflow<size_t>(1, kMaxValue)); + EXPECT_FALSE(DoesIntegerAdditionOverflow<size_t>(kMaxValue, 0)); + EXPECT_FALSE(DoesIntegerAdditionOverflow<size_t>(0, kMaxValue)); +} + +TEST(MiscMath, DoesIntSafelyConvertToSizeT) { + static const int kMaxIntValue = std::numeric_limits<int>::max(); + size_t test_size_t; + EXPECT_TRUE(DoesIntSafelyConvertToSizeT(kMaxIntValue, &test_size_t)); + EXPECT_EQ(static_cast<size_t>(kMaxIntValue), test_size_t); + EXPECT_TRUE(DoesIntSafelyConvertToSizeT(0, &test_size_t)); + EXPECT_EQ(0U, test_size_t); + EXPECT_FALSE(DoesIntSafelyConvertToSizeT(-1, &test_size_t)); +} + +TEST(MiscMath, DoesSizeTSafelyConvertToInt) { + static const size_t kMaxIntValue = std::numeric_limits<size_t>::max(); + int test_int; + + EXPECT_FALSE(DoesSizeTSafelyConvertToInt(kMaxIntValue, &test_int)); + EXPECT_TRUE( + DoesSizeTSafelyConvertToInt(std::numeric_limits<int>::max(), &test_int)); + EXPECT_EQ(std::numeric_limits<int>::max(), test_int); + EXPECT_TRUE(DoesSizeTSafelyConvertToInt(0, &test_int)); + EXPECT_EQ(0, test_int); +} + +TEST(MiscMath, GreatestCommonDivisorTest) { + const std::vector<int> a_values = {2, 10, 3, 5, 48000, 7, -2, 2, -3}; + const std::vector<int> b_values = {8, 4, 1, 10, 24000, 13, 6, -6, -9}; + const std::vector<int> expected = {2, 2, 1, 5, 24000, 1, 2, 2, 3}; + + for (size_t i = 0; i < expected.size(); ++i) { + EXPECT_EQ(expected[i], FindGcd(a_values[i], b_values[i])); + } +} + +TEST(MiscMath, NextPowTwoTest) { + const std::vector<size_t> inputs = {2, 10, 3, 5, 48000, 7, 23, 32}; + const std::vector<size_t> expected = {2, 16, 4, 8, 65536, 8, 32, 32}; + + for (size_t i = 0; i < inputs.size(); ++i) { + EXPECT_EQ(expected[i], NextPowTwo(inputs[i])); + } +} + +TEST(MiscMath, EqualSafeEqualArraysTest) { + const float kOriginalArray[3] = {0.11f, 0.22f, 0.33f}; + const float kSameArray[3] = {0.11f, 0.22f, 0.33f}; + + EXPECT_TRUE(EqualSafe(std::begin(kOriginalArray), std::end(kOriginalArray), + std::begin(kSameArray), std::end(kSameArray))); +} + +TEST(MiscMath, EqualSafeUnequalArraysTest) { + const std::vector<float> kOriginalArray{0.11f, 0.22f, 0.33f}; + const std::vector<std::vector<float>> kDifferentArrays{ + {-0.22f, 0.22f, 0.33f}, {0.11f, -0.33f, 0.33f}, {0.11f, 0.22f, -0.22f}, + {0.11f, 0.33f, -0.44f}, {0.22f, 0.22f, -0.44f}, {0.22f, 0.33f, -0.55f}}; + + for (auto& array : kDifferentArrays) { + EXPECT_FALSE(EqualSafe(std::begin(kOriginalArray), std::end(kOriginalArray), + std::begin(array), std::end(array))); + } +} + +TEST(MiscMath, FastReciprocalSqrtTest) { + const std::vector<float> kNumbers{130.0f, 13.0f, 1.3f, + 0.13f, 0.013f, 0.0013f}; + const float kSqrtEpsilon = 2e-3f; + for (auto& number : kNumbers) { + const float actual = std::sqrt(number); + const float approximate = 1.0f / FastReciprocalSqrt(number); + EXPECT_LT(std::abs(actual - approximate) / actual, kSqrtEpsilon); + } +} + +TEST(MiscMath, LinearFittingArrayDifferentSizesFails) { + const std::vector<float> x_array{1.0f, 2.0f}; + const std::vector<float> y_array{3.0f, 4.0f, 5.0f}; + float slope = 0.0f; + float intercept = 0.0f; + float r_squared = 0.0f; + EXPECT_FALSE(LinearLeastSquareFitting(x_array, y_array, &slope, &intercept, + &r_squared)); +} + +TEST(MiscMath, LinearFittingFewerThanTwoPointsFails) { + const std::vector<float> x_array{1.0f}; + const std::vector<float> y_array{2.0f}; + float slope = 0.0f; + float intercept = 0.0f; + float r_squared = 0.0f; + EXPECT_FALSE(LinearLeastSquareFitting(x_array, y_array, &slope, &intercept, + &r_squared)); +} + +TEST(MiscMath, LinearFittingVerticalLineFails) { + // All points line up on the y-axis. + const std::vector<float> x_array{0.0f, 0.0f, 0.0f}; + const std::vector<float> y_array{1.0f, 2.0f, 3.0f}; + float slope = 0.0f; + float intercept = 0.0f; + float r_squared = 0.0f; + EXPECT_FALSE(LinearLeastSquareFitting(x_array, y_array, &slope, &intercept, + &r_squared)); +} + +TEST(MiscMath, LinearFittingHorizontalLine) { + // All points line up on the x-axis. + const std::vector<float> x_array{1.0f, 2.0f, 3.0f}; + const std::vector<float> y_array{0.0f, 0.0f, 0.0f}; + float slope = 0.0f; + float intercept = 0.0f; + float r_squared = 0.0f; + EXPECT_TRUE(LinearLeastSquareFitting(x_array, y_array, &slope, &intercept, + &r_squared)); + EXPECT_FLOAT_EQ(slope, 0.0f); + EXPECT_FLOAT_EQ(intercept, 0.0f); + EXPECT_FLOAT_EQ(r_squared, 1.0f); +} + +TEST(MiscMath, LinearFittingSlopedLine) { + // All points line up on the line y = 2.0 x + 1.0. + const std::vector<float> x_array{1.0f, 2.0f, 3.0f, 4.0f}; + const std::vector<float> y_array{3.0f, 5.0f, 7.0f, 9.0f}; + float slope = 0.0f; + float intercept = 0.0f; + float r_squared = 0.0f; + EXPECT_TRUE(LinearLeastSquareFitting(x_array, y_array, &slope, &intercept, + &r_squared)); + EXPECT_FLOAT_EQ(slope, 2.0f); + EXPECT_FLOAT_EQ(intercept, 1.0f); + EXPECT_FLOAT_EQ(r_squared, 1.0f); +} + +TEST(MiscMath, LinearFittingSlopedLineWithError) { + // All points lie close to the line y = 2.0 x + 1.0 with some offsets. + const std::vector<float> x_array{1.002f, 2.001f, 2.998f, 4.003f}; + const std::vector<float> y_array{3.001f, 4.998f, 7.005f, 8.996f}; + float slope = 0.0f; + float intercept = 0.0f; + float r_squared = 0.0f; + EXPECT_TRUE(LinearLeastSquareFitting(x_array, y_array, &slope, &intercept, + &r_squared)); + + // Expect that the fitting is close to the line with some error. + const float error_tolerance = 1e-3f; + EXPECT_NEAR(slope, 2.0f, error_tolerance); + EXPECT_NEAR(intercept, 1.0f, error_tolerance); + EXPECT_NEAR(r_squared, 1.0f, error_tolerance); +} + +TEST(MiscMath, LinearFittingUncorrelatedPoints) { + // All points evenly distributed on a circle y^2 + x^2 = 1.0, which gives + // the worst coefficient of determination (almost zero). + const size_t num_points = 20; + std::vector<float> x_array(num_points, 0.0f); + std::vector<float> y_array(num_points, 0.0f); + for (size_t i = 0; i < num_points; ++i) { + const float theta = + kTwoPi * static_cast<float>(i) / static_cast<float>(num_points); + x_array[i] = std::cos(theta); + y_array[i] = std::sin(theta); + } + + float slope = 0.0f; + float intercept = 0.0f; + float r_squared = 0.0f; + EXPECT_TRUE(LinearLeastSquareFitting(x_array, y_array, &slope, &intercept, + &r_squared)); + EXPECT_FLOAT_EQ(r_squared, 0.0f); +} + +TEST(MiscMath, WorldRotation) { + // Test rotation around single quaternion axis. + const float kAngularRandomOffsetRad = 0.5f; + const float kAngularDifferenceRad = 0.3f; + Eigen::AngleAxisf rotation_a(kAngularRandomOffsetRad, + Eigen::Vector3f::UnitY()); + Eigen::AngleAxisf rotation_b(kAngularRandomOffsetRad + kAngularDifferenceRad, + Eigen::Vector3f::UnitY()); + EXPECT_FLOAT_EQ(WorldRotation(rotation_a).AngularDifferenceRad(rotation_b), + kAngularDifferenceRad); + + // Test rotation between axis. + Eigen::AngleAxisf rotation_c(0.0f, Eigen::Vector3f::UnitY()); + Eigen::AngleAxisf rotation_d(kPi, Eigen::Vector3f::UnitZ()); + EXPECT_FLOAT_EQ(WorldRotation(rotation_c).AngularDifferenceRad(rotation_d), + kPi); +} + +TEST(MiscMath, IntegerPow) { + const float kFloatValue = 1.5f; + const float kNegativeFloatValue = -3.3f; + const size_t kSizeTValue = 11U; + const int kIntValue = 5; + const int kNegativeIntValue = -13; + + for (int exponent = 0; exponent < 5; ++exponent) { + EXPECT_FLOAT_EQ(IntegerPow(kFloatValue, exponent), + std::pow(kFloatValue, static_cast<float>(exponent))); + EXPECT_FLOAT_EQ( + IntegerPow(kNegativeFloatValue, exponent), + std::pow(kNegativeFloatValue, static_cast<float>(exponent))); + EXPECT_EQ(IntegerPow(kSizeTValue, exponent), + std::pow(kSizeTValue, exponent)); + EXPECT_EQ(IntegerPow(kIntValue, exponent), std::pow(kIntValue, exponent)); + EXPECT_EQ(IntegerPow(kNegativeIntValue, exponent), + std::pow(kNegativeIntValue, exponent)); + } +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/base/object_transform.h b/src/3rdparty/resonance-audio/resonance_audio/base/object_transform.h new file mode 100644 index 000000000..1d43e72a0 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/base/object_transform.h @@ -0,0 +1,31 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_BASE_OBJECT_TRANSFORM_H_ +#define RESONANCE_AUDIO_BASE_OBJECT_TRANSFORM_H_ + +#include "base/misc_math.h" + +namespace vraudio { + +struct ObjectTransform { + WorldPosition position; + WorldRotation rotation; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_BASE_OBJECT_TRANSFORM_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/base/simd_macros.h b/src/3rdparty/resonance-audio/resonance_audio/base/simd_macros.h new file mode 100644 index 000000000..11ae40bf4 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/base/simd_macros.h @@ -0,0 +1,65 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_BASE_SIMD_MACROS_H_ +#define RESONANCE_AUDIO_BASE_SIMD_MACROS_H_ + +#if !defined(DISABLE_SIMD) && (defined(__x86_64__) || defined(_M_X64) || \ + defined(i386) || defined(_M_IX86)) +// SSE1 is enabled. +#include <xmmintrin.h> +typedef __m128 SimdVector; +#define SIMD_SSE +#define SIMD_LENGTH 4 +#define SIMD_MULTIPLY(a, b) _mm_mul_ps(a, b) +#define SIMD_ADD(a, b) _mm_add_ps(a, b) +#define SIMD_SUB(a, b) _mm_sub_ps(a, b) +#define SIMD_MULTIPLY_ADD(a, b, c) _mm_add_ps(_mm_mul_ps(a, b), c) +#define SIMD_SQRT(a) _mm_rcp_ps(_mm_rsqrt_ps(a)) +#define SIMD_RECIPROCAL_SQRT(a) _mm_rsqrt_ps(a) +#define SIMD_LOAD_ONE_FLOAT(p) _mm_set1_ps(p) +#elif !defined(DISABLE_SIMD) && \ + (((defined(__arm__) || defined(__TARGET_ARCH_ARM) || defined(_M_ARM)) && defined(__ARM_NEON__)) || \ + defined(_M_ARM64) || defined(__aarch64__) || defined(__ARM64__)) +// ARM NEON is enabled. +#include <arm_neon.h> +typedef float32x4_t SimdVector; +#define SIMD_NEON +#define SIMD_LENGTH 4 +#define SIMD_MULTIPLY(a, b) vmulq_f32(a, b) +#define SIMD_ADD(a, b) vaddq_f32(a, b) +#define SIMD_SUB(a, b) vsubq_f32(a, b) +#define SIMD_MULTIPLY_ADD(a, b, c) vmlaq_f32(c, a, b) +#define SIMD_SQRT(a) vrecpeq_f32(vrsqrteq_f32(a)) +#define SIMD_RECIPROCAL_SQRT(a) vrsqrteq_f32(a) +#define SIMD_LOAD_ONE_FLOAT(p) vld1q_dup_f32(&(p)) +#else +// No SIMD optimizations enabled. +#include "base/misc_math.h" +typedef float SimdVector; +#define SIMD_DISABLED +#define SIMD_LENGTH 1 +#define SIMD_MULTIPLY(a, b) ((a) * (b)) +#define SIMD_ADD(a, b) ((a) + (b)) +#define SIMD_SUB(a, b) ((a) - (b)) +#define SIMD_MULTIPLY_ADD(a, b, c) ((a) * (b) + (c)) +#define SIMD_SQRT(a) (1.0f / FastReciprocalSqrt(a)) +#define SIMD_RECIPROCAL_SQRT(a) FastReciprocalSqrt(a) +#define SIMD_LOAD_ONE_FLOAT(p) (p) +#warning "Not using SIMD optimizations!" +#endif + +#endif // RESONANCE_AUDIO_BASE_SIMD_MACROS_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/base/simd_utils.cc b/src/3rdparty/resonance-audio/resonance_audio/base/simd_utils.cc new file mode 100644 index 000000000..309ca7ba8 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/base/simd_utils.cc @@ -0,0 +1,1299 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Prevent Visual Studio from complaining about std::copy_n. +#if defined(_WIN32) +#define _SCL_SECURE_NO_WARNINGS +#endif + +#include "base/simd_utils.h" + +#include <algorithm> +#include <limits> + +#include "base/constants_and_types.h" +#include "base/logging.h" +#include "base/misc_math.h" +#include "base/simd_macros.h" + + +namespace vraudio { + +namespace { + +#ifdef SIMD_NEON +// Deinterleaving operates on 8 int16s at a time. +const size_t kSixteenBitSimdLength = SIMD_LENGTH * 2; +#endif // SIMD_NEON + +// Float format of max and min values storable in an int16_t, for clamping. +const float kInt16Max = static_cast<float>(0x7FFF); +const float kInt16Min = static_cast<float>(-0x7FFF); + +// Conversion factors between float and int16_t (both directions). +const float kFloatFromInt16 = 1.0f / kInt16Max; +const float kInt16FromFloat = kInt16Max; + +// Expected SIMD alignment in bytes. +const size_t kSimdSizeBytes = 16; + +inline size_t GetNumChunks(size_t length) { return length / SIMD_LENGTH; } + +inline size_t GetLeftoverSamples(size_t length) { return length % SIMD_LENGTH; } + +template <typename T> +inline bool IsAlignedTemplated(const T* pointer) { + return reinterpret_cast<uintptr_t>(pointer) % kSimdSizeBytes == 0; +} + +#ifdef SIMD_DISABLED +// Calculates the approximate complex magnude of z = real + i * imaginary. +inline void ComplexMagnitude(float real, float imaginary, float* output) { + *output = real * real + imaginary * imaginary; + // The value of |output| is not being recalculated, simply modified. + *output = 1.0f / FastReciprocalSqrt(*output); +} +#endif // defined(SIMD_DISABLED) + +} // namespace + +bool IsAligned(const float* pointer) { + return IsAlignedTemplated<float>(pointer); +} + +bool IsAligned(const int16_t* pointer) { + return IsAlignedTemplated<int16_t>(pointer); +} + +size_t FindNextAlignedArrayIndex(size_t length, size_t type_size_bytes, + size_t memory_alignment_bytes) { + const size_t byte_length = type_size_bytes * length; + const size_t unaligned_bytes = byte_length % memory_alignment_bytes; + const size_t bytes_to_next_aligned = + (unaligned_bytes == 0) ? 0 : memory_alignment_bytes - unaligned_bytes; + return (byte_length + bytes_to_next_aligned) / type_size_bytes; +} + +void AddPointwise(size_t length, const float* input_a, const float* input_b, + float* output) { + DCHECK(input_a); + DCHECK(input_b); + DCHECK(output); + + const SimdVector* input_a_vector = + reinterpret_cast<const SimdVector*>(input_a); + const SimdVector* input_b_vector = + reinterpret_cast<const SimdVector*>(input_b); + SimdVector* output_vector = reinterpret_cast<SimdVector*>(output); +#ifdef SIMD_SSE + const size_t num_chunks = GetNumChunks(length); + const bool inputs_aligned = IsAligned(input_a) && IsAligned(input_b); + const bool output_aligned = IsAligned(output); + if (inputs_aligned && output_aligned) { + for (size_t i = 0; i < num_chunks; ++i) { + output_vector[i] = SIMD_ADD(input_a_vector[i], input_b_vector[i]); + } + } else if (inputs_aligned) { + for (size_t i = 0; i < num_chunks; ++i) { + const SimdVector output_temp = + SIMD_ADD(input_a_vector[i], input_b_vector[i]); + _mm_storeu_ps(&output[i * SIMD_LENGTH], output_temp); + } + } else if (output_aligned) { + for (size_t i = 0; i < num_chunks; ++i) { + const SimdVector input_a_temp = _mm_load_ps(&input_a[i * SIMD_LENGTH]); + const SimdVector input_b_temp = _mm_load_ps(&input_b[i * SIMD_LENGTH]); + output_vector[i] = SIMD_ADD(input_a_temp, input_b_temp); + } + } else { + for (size_t i = 0; i < num_chunks; ++i) { + const SimdVector input_a_temp = _mm_load_ps(&input_a[i * SIMD_LENGTH]); + const SimdVector input_b_temp = _mm_load_ps(&input_b[i * SIMD_LENGTH]); + const SimdVector output_temp = SIMD_ADD(input_a_temp, input_b_temp); + _mm_storeu_ps(&output[i * SIMD_LENGTH], output_temp); + } + } +#else + for (size_t i = 0; i < GetNumChunks(length); ++i) { + output_vector[i] = SIMD_ADD(input_a_vector[i], input_b_vector[i]); + } +#endif // SIMD_SSE + + // Add samples at the end that were missed by the SIMD chunking. + const size_t leftover_samples = GetLeftoverSamples(length); + DCHECK_GE(length, leftover_samples); + for (size_t i = length - leftover_samples; i < length; ++i) { + output[i] = input_a[i] + input_b[i]; + } +} + +void SubtractPointwise(size_t length, const float* input_a, + const float* input_b, float* output) { + DCHECK(input_a); + DCHECK(input_b); + DCHECK(output); + + const SimdVector* input_a_vector = + reinterpret_cast<const SimdVector*>(input_a); + const SimdVector* input_b_vector = + reinterpret_cast<const SimdVector*>(input_b); + SimdVector* output_vector = reinterpret_cast<SimdVector*>(output); + +#ifdef SIMD_SSE + const size_t num_chunks = GetNumChunks(length); + const bool inputs_aligned = IsAligned(input_a) && IsAligned(input_b); + const bool output_aligned = IsAligned(output); + if (inputs_aligned && output_aligned) { + for (size_t i = 0; i < num_chunks; ++i) { + output_vector[i] = SIMD_SUB(input_b_vector[i], input_a_vector[i]); + } + } else if (inputs_aligned) { + for (size_t i = 0; i < num_chunks; ++i) { + const SimdVector output_temp = + SIMD_SUB(input_b_vector[i], input_a_vector[i]); + _mm_storeu_ps(&output[i * SIMD_LENGTH], output_temp); + } + } else if (output_aligned) { + for (size_t i = 0; i < num_chunks; ++i) { + const SimdVector input_a_temp = _mm_load_ps(&input_a[i * SIMD_LENGTH]); + const SimdVector input_b_temp = _mm_load_ps(&input_b[i * SIMD_LENGTH]); + output_vector[i] = SIMD_SUB(input_b_temp, input_a_temp); + } + } else { + for (size_t i = 0; i < num_chunks; ++i) { + const SimdVector input_a_temp = _mm_load_ps(&input_a[i * SIMD_LENGTH]); + const SimdVector input_b_temp = _mm_load_ps(&input_b[i * SIMD_LENGTH]); + const SimdVector output_temp = SIMD_SUB(input_b_temp, input_a_temp); + _mm_storeu_ps(&output[i * SIMD_LENGTH], output_temp); + } + } +#else + for (size_t i = 0; i < GetNumChunks(length); ++i) { + output_vector[i] = SIMD_SUB(input_b_vector[i], input_a_vector[i]); + } +#endif // SIMD_SSE + + // Subtract samples at the end that were missed by the SIMD chunking. + const size_t leftover_samples = GetLeftoverSamples(length); + DCHECK_GE(length, leftover_samples); + for (size_t i = length - leftover_samples; i < length; ++i) { + output[i] = input_b[i] - input_a[i]; + } +} + +void MultiplyPointwise(size_t length, const float* input_a, + const float* input_b, float* output) { + DCHECK(input_a); + DCHECK(input_b); + DCHECK(output); + + const SimdVector* input_a_vector = + reinterpret_cast<const SimdVector*>(input_a); + const SimdVector* input_b_vector = + reinterpret_cast<const SimdVector*>(input_b); + SimdVector* output_vector = reinterpret_cast<SimdVector*>(output); + +#ifdef SIMD_SSE + const size_t num_chunks = GetNumChunks(length); + const bool inputs_aligned = IsAligned(input_a) && IsAligned(input_b); + const bool output_aligned = IsAligned(output); + if (inputs_aligned && output_aligned) { + for (size_t i = 0; i < num_chunks; ++i) { + output_vector[i] = SIMD_MULTIPLY(input_a_vector[i], input_b_vector[i]); + } + } else if (inputs_aligned) { + for (size_t i = 0; i < num_chunks; ++i) { + const SimdVector output_temp = + SIMD_MULTIPLY(input_a_vector[i], input_b_vector[i]); + _mm_storeu_ps(&output[i * SIMD_LENGTH], output_temp); + } + } else if (output_aligned) { + for (size_t i = 0; i < num_chunks; ++i) { + const SimdVector input_a_temp = _mm_loadu_ps(&input_a[i * SIMD_LENGTH]); + const SimdVector input_b_temp = _mm_loadu_ps(&input_b[i * SIMD_LENGTH]); + output_vector[i] = SIMD_MULTIPLY(input_a_temp, input_b_temp); + } + } else { + for (size_t i = 0; i < num_chunks; ++i) { + const SimdVector input_a_temp = _mm_loadu_ps(&input_a[i * SIMD_LENGTH]); + const SimdVector input_b_temp = _mm_loadu_ps(&input_b[i * SIMD_LENGTH]); + const SimdVector output_temp = SIMD_MULTIPLY(input_a_temp, input_b_temp); + _mm_storeu_ps(&output[i * SIMD_LENGTH], output_temp); + } + } +#else + for (size_t i = 0; i < GetNumChunks(length); ++i) { + output_vector[i] = SIMD_MULTIPLY(input_a_vector[i], input_b_vector[i]); + } +#endif // SIMD_SSE + + // Multiply samples at the end that were missed by the SIMD chunking. + const size_t leftover_samples = GetLeftoverSamples(length); + DCHECK_GE(length, leftover_samples); + for (size_t i = length - leftover_samples; i < length; ++i) { + output[i] = input_a[i] * input_b[i]; + } +} + +void MultiplyAndAccumulatePointwise(size_t length, const float* input_a, + const float* input_b, float* accumulator) { + DCHECK(input_a); + DCHECK(input_b); + DCHECK(accumulator); + + const SimdVector* input_a_vector = + reinterpret_cast<const SimdVector*>(input_a); + const SimdVector* input_b_vector = + reinterpret_cast<const SimdVector*>(input_b); + SimdVector* accumulator_vector = reinterpret_cast<SimdVector*>(accumulator); + +#ifdef SIMD_SSE + const size_t num_chunks = GetNumChunks(length); + const bool inputs_aligned = IsAligned(input_a) && IsAligned(input_b); + const bool accumulator_aligned = IsAligned(accumulator); + if (inputs_aligned && accumulator_aligned) { + for (size_t i = 0; i < num_chunks; ++i) { + accumulator_vector[i] = SIMD_MULTIPLY_ADD( + input_a_vector[i], input_b_vector[i], accumulator_vector[i]); + } + } else if (inputs_aligned) { + for (size_t i = 0; i < num_chunks; ++i) { + SimdVector accumulator_temp = _mm_loadu_ps(&accumulator[i * SIMD_LENGTH]); + accumulator_temp = SIMD_MULTIPLY_ADD(input_a_vector[i], input_b_vector[i], + accumulator_temp); + _mm_storeu_ps(&accumulator[i * SIMD_LENGTH], accumulator_temp); + } + } else if (accumulator_aligned) { + for (size_t i = 0; i < num_chunks; ++i) { + const SimdVector input_a_temp = _mm_loadu_ps(&input_a[i * SIMD_LENGTH]); + const SimdVector input_b_temp = _mm_loadu_ps(&input_b[i * SIMD_LENGTH]); + accumulator_vector[i] = + SIMD_MULTIPLY_ADD(input_a_temp, input_b_temp, accumulator_vector[i]); + } + } else { + for (size_t i = 0; i < num_chunks; ++i) { + const SimdVector input_a_temp = _mm_loadu_ps(&input_a[i * SIMD_LENGTH]); + const SimdVector input_b_temp = _mm_loadu_ps(&input_b[i * SIMD_LENGTH]); + SimdVector accumulator_temp = _mm_loadu_ps(&accumulator[i * SIMD_LENGTH]); + accumulator_temp = + SIMD_MULTIPLY_ADD(input_a_temp, input_b_temp, accumulator_temp); + _mm_storeu_ps(&accumulator[i * SIMD_LENGTH], accumulator_temp); + } + } +#else + for (size_t i = 0; i < GetNumChunks(length); ++i) { + accumulator_vector[i] = SIMD_MULTIPLY_ADD( + input_a_vector[i], input_b_vector[i], accumulator_vector[i]); + } +#endif // SIMD_SSE + + // Apply gain and accumulate to samples at the end that were missed by the + // SIMD chunking. + const size_t leftover_samples = GetLeftoverSamples(length); + DCHECK_GE(length, leftover_samples); + for (size_t i = length - leftover_samples; i < length; ++i) { + accumulator[i] += input_a[i] * input_b[i]; + } +} + +void ScalarMultiply(size_t length, float gain, const float* input, + float* output) { + DCHECK(input); + DCHECK(output); + + const SimdVector* input_vector = reinterpret_cast<const SimdVector*>(input); + SimdVector* output_vector = reinterpret_cast<SimdVector*>(output); + + const SimdVector gain_vector = SIMD_LOAD_ONE_FLOAT(gain); +#ifdef SIMD_SSE + const size_t num_chunks = GetNumChunks(length); + const bool input_aligned = IsAligned(input); + const bool output_aligned = IsAligned(output); + if (input_aligned && output_aligned) { + for (size_t i = 0; i < num_chunks; ++i) { + output_vector[i] = SIMD_MULTIPLY(gain_vector, input_vector[i]); + } + } else if (input_aligned) { + for (size_t i = 0; i < num_chunks; ++i) { + const SimdVector output_temp = + SIMD_MULTIPLY(gain_vector, input_vector[i]); + _mm_storeu_ps(&output[i * SIMD_LENGTH], output_temp); + } + } else if (output_aligned) { + for (size_t i = 0; i < num_chunks; ++i) { + const SimdVector input_temp = _mm_loadu_ps(&input[i * SIMD_LENGTH]); + output_vector[i] = SIMD_MULTIPLY(gain_vector, input_temp); + } + } else { + for (size_t i = 0; i < num_chunks; ++i) { + const SimdVector input_temp = _mm_loadu_ps(&input[i * SIMD_LENGTH]); + const SimdVector output_temp = SIMD_MULTIPLY(gain_vector, input_temp); + _mm_storeu_ps(&output[i * SIMD_LENGTH], output_temp); + } + } +#else + for (size_t i = 0; i < GetNumChunks(length); ++i) { + output_vector[i] = SIMD_MULTIPLY(gain_vector, input_vector[i]); + } +#endif // SIMD_SSE + + // Apply gain to samples at the end that were missed by the SIMD chunking. + const size_t leftover_samples = GetLeftoverSamples(length); + DCHECK_GE(length, leftover_samples); + for (size_t i = length - leftover_samples; i < length; ++i) { + output[i] = input[i] * gain; + } +} + +void ScalarMultiplyAndAccumulate(size_t length, float gain, const float* input, + float* accumulator) { + DCHECK(input); + DCHECK(accumulator); + + const SimdVector* input_vector = reinterpret_cast<const SimdVector*>(input); + SimdVector* accumulator_vector = reinterpret_cast<SimdVector*>(accumulator); + + const SimdVector gain_vector = SIMD_LOAD_ONE_FLOAT(gain); +#ifdef SIMD_SSE + const size_t num_chunks = GetNumChunks(length); + const bool input_aligned = IsAligned(input); + const bool accumulator_aligned = IsAligned(accumulator); + if (input_aligned && accumulator_aligned) { + for (size_t i = 0; i < num_chunks; ++i) { + accumulator_vector[i] = SIMD_MULTIPLY_ADD(gain_vector, input_vector[i], + accumulator_vector[i]); + } + } else if (input_aligned) { + for (size_t i = 0; i < num_chunks; ++i) { + SimdVector accumulator_temp = _mm_loadu_ps(&accumulator[i * SIMD_LENGTH]); + accumulator_temp = + SIMD_MULTIPLY_ADD(gain_vector, input_vector[i], accumulator_temp); + _mm_storeu_ps(&accumulator[i * SIMD_LENGTH], accumulator_temp); + } + } else if (accumulator_aligned) { + for (size_t i = 0; i < num_chunks; ++i) { + const SimdVector input_temp = _mm_loadu_ps(&input[i * SIMD_LENGTH]); + accumulator_vector[i] = + SIMD_MULTIPLY_ADD(gain_vector, input_temp, accumulator_vector[i]); + } + } else { + for (size_t i = 0; i < num_chunks; ++i) { + const SimdVector input_temp = _mm_loadu_ps(&input[i * SIMD_LENGTH]); + SimdVector accumulator_temp = _mm_loadu_ps(&accumulator[i * SIMD_LENGTH]); + accumulator_temp = + SIMD_MULTIPLY_ADD(gain_vector, input_temp, accumulator_temp); + _mm_storeu_ps(&accumulator[i * SIMD_LENGTH], accumulator_temp); + } + } +#else + for (size_t i = 0; i < GetNumChunks(length); ++i) { + accumulator_vector[i] = + SIMD_MULTIPLY_ADD(gain_vector, input_vector[i], accumulator_vector[i]); + } +#endif // SIMD_SSE + + // Apply gain and accumulate to samples at the end that were missed by the + // SIMD chunking. + const size_t leftover_samples = GetLeftoverSamples(length); + DCHECK_GE(length, leftover_samples); + for (size_t i = length - leftover_samples; i < length; ++i) { + accumulator[i] += input[i] * gain; + } +} + +void ReciprocalSqrt(size_t length, const float* input, float* output) { + DCHECK(input); + DCHECK(output); + +#if !defined(SIMD_DISABLED) + const SimdVector* input_vector = reinterpret_cast<const SimdVector*>(input); + SimdVector* output_vector = reinterpret_cast<SimdVector*>(output); +#endif // !defined(SIMD_DISABLED) + +#ifdef SIMD_SSE + const size_t num_chunks = GetNumChunks(length); + const bool input_aligned = IsAligned(input); + const bool output_aligned = IsAligned(output); + if (input_aligned && output_aligned) { + for (size_t i = 0; i < num_chunks; ++i) { + output_vector[i] = SIMD_RECIPROCAL_SQRT(input_vector[i]); + } + } else if (input_aligned) { + for (size_t i = 0; i < num_chunks; ++i) { + const SimdVector output_temp = SIMD_RECIPROCAL_SQRT(input_vector[i]); + _mm_storeu_ps(&output[i * SIMD_LENGTH], output_temp); + } + } else if (output_aligned) { + for (size_t i = 0; i < num_chunks; ++i) { + const SimdVector input_temp = _mm_loadu_ps(&input[i * SIMD_LENGTH]); + output_vector[i] = SIMD_RECIPROCAL_SQRT(input_temp); + } + } else { + for (size_t i = 0; i < num_chunks; ++i) { + const SimdVector input_temp = _mm_loadu_ps(&input[i * SIMD_LENGTH]); + const SimdVector output_temp = SIMD_RECIPROCAL_SQRT(input_temp); + _mm_storeu_ps(&output[i * SIMD_LENGTH], output_temp); + } + } +#elif defined SIMD_NEON + for (size_t i = 0; i < GetNumChunks(length); ++i) { + output_vector[i] = SIMD_RECIPROCAL_SQRT(input_vector[i]); + } +#endif // SIMD_SSE + + // Apply to samples at the end that were missed by the SIMD chunking. + const size_t leftover_samples = GetLeftoverSamples(length); + DCHECK_GE(length, leftover_samples); + for (size_t i = length - leftover_samples; i < length; ++i) { + output[i] = FastReciprocalSqrt(input[i]); + } +} + +void Sqrt(size_t length, const float* input, float* output) { + DCHECK(input); + DCHECK(output); + +#if !defined(SIMD_DISABLED) + const SimdVector* input_vector = reinterpret_cast<const SimdVector*>(input); + SimdVector* output_vector = reinterpret_cast<SimdVector*>(output); +#endif // !defined(SIMD_DISABLED) + +#ifdef SIMD_SSE + const size_t num_chunks = GetNumChunks(length); + const bool input_aligned = IsAligned(input); + const bool output_aligned = IsAligned(output); + if (input_aligned && output_aligned) { + for (size_t i = 0; i < num_chunks; ++i) { + output_vector[i] = SIMD_SQRT(input_vector[i]); + } + } else if (input_aligned) { + for (size_t i = 0; i < num_chunks; ++i) { + const SimdVector output_temp = SIMD_SQRT(input_vector[i]); + _mm_storeu_ps(&output[i * SIMD_LENGTH], output_temp); + } + } else if (output_aligned) { + for (size_t i = 0; i < num_chunks; ++i) { + const SimdVector input_temp = _mm_loadu_ps(&input[i * SIMD_LENGTH]); + output_vector[i] = SIMD_SQRT(input_temp); + } + } else { + for (size_t i = 0; i < num_chunks; ++i) { + const SimdVector input_temp = _mm_loadu_ps(&input[i * SIMD_LENGTH]); + const SimdVector output_temp = SIMD_SQRT(input_temp); + _mm_storeu_ps(&output[i * SIMD_LENGTH], output_temp); + } + } +#elif defined SIMD_NEON + for (size_t i = 0; i < GetNumChunks(length); ++i) { + // This should be faster than using a sqrt method : https://goo.gl/XRKwFp + output_vector[i] = SIMD_SQRT(input_vector[i]); + } +#endif // SIMD_SSE + + // Apply to samples at the end that were missed by the SIMD chunking. + const size_t leftover_samples = GetLeftoverSamples(length); + DCHECK_GE(length, leftover_samples); + for (size_t i = length - leftover_samples; i < length; ++i) { + output[i] = 1.0f / FastReciprocalSqrt(input[i]); + } +} + +void ApproxComplexMagnitude(size_t length, const float* input, float* output) { + DCHECK(input); + DCHECK(output); + +#if !defined(SIMD_DISABLED) + const SimdVector* input_vector = reinterpret_cast<const SimdVector*>(input); + SimdVector* output_vector = reinterpret_cast<SimdVector*>(output); + const size_t num_chunks = GetNumChunks(length); + const bool input_aligned = IsAligned(input); + const bool output_aligned = IsAligned(output); +#endif // !defined(SIMD_DISABLED) + +#ifdef SIMD_SSE + if (input_aligned && output_aligned) { + for (size_t out_index = 0; out_index < num_chunks; ++out_index) { + const size_t first_index = out_index * 2; + const size_t second_index = first_index + 1; + const SimdVector squared_1 = + SIMD_MULTIPLY(input_vector[first_index], input_vector[first_index]); + const SimdVector squared_2 = + SIMD_MULTIPLY(input_vector[second_index], input_vector[second_index]); + const SimdVector unshuffled_1 = + _mm_shuffle_ps(squared_1, squared_2, _MM_SHUFFLE(2, 0, 2, 0)); + const SimdVector unshuffled_2 = + _mm_shuffle_ps(squared_1, squared_2, _MM_SHUFFLE(3, 1, 3, 1)); + output_vector[out_index] = SIMD_ADD(unshuffled_1, unshuffled_2); + output_vector[out_index] = SIMD_SQRT(output_vector[out_index]); + } + } else if (input_aligned) { + for (size_t out_index = 0; out_index < num_chunks; ++out_index) { + const size_t first_index = out_index * 2; + const size_t second_index = first_index + 1; + const SimdVector squared_1 = + SIMD_MULTIPLY(input_vector[first_index], input_vector[first_index]); + const SimdVector squared_2 = + SIMD_MULTIPLY(input_vector[second_index], input_vector[second_index]); + const SimdVector unshuffled_1 = + _mm_shuffle_ps(squared_1, squared_2, _MM_SHUFFLE(2, 0, 2, 0)); + const SimdVector unshuffled_2 = + _mm_shuffle_ps(squared_1, squared_2, _MM_SHUFFLE(3, 1, 3, 1)); + SimdVector output_temp = SIMD_ADD(unshuffled_1, unshuffled_2); + output_vector[out_index] = SIMD_SQRT(output_temp); + _mm_storeu_ps(&output[out_index * SIMD_LENGTH], output_temp); + } + } else if (output_aligned) { + for (size_t out_index = 0; out_index < num_chunks; ++out_index) { + const size_t first_index = out_index * 2; + const size_t second_index = first_index + 1; + const SimdVector first_temp = + _mm_loadu_ps(&input[first_index * SIMD_LENGTH]); + const SimdVector second_temp = + _mm_loadu_ps(&input[second_index * SIMD_LENGTH]); + const SimdVector squared_1 = SIMD_MULTIPLY(first_temp, first_temp); + const SimdVector squared_2 = SIMD_MULTIPLY(second_temp, second_temp); + const SimdVector unshuffled_1 = + _mm_shuffle_ps(squared_1, squared_2, _MM_SHUFFLE(2, 0, 2, 0)); + const SimdVector unshuffled_2 = + _mm_shuffle_ps(squared_1, squared_2, _MM_SHUFFLE(3, 1, 3, 1)); + output_vector[out_index] = SIMD_ADD(unshuffled_1, unshuffled_2); + output_vector[out_index] = SIMD_SQRT(output_vector[out_index]); + } + } else { + for (size_t out_index = 0; out_index < num_chunks; ++out_index) { + const size_t first_index = out_index * 2; + const size_t second_index = first_index + 1; + const SimdVector first_temp = + _mm_loadu_ps(&input[first_index * SIMD_LENGTH]); + const SimdVector second_temp = + _mm_loadu_ps(&input[second_index * SIMD_LENGTH]); + const SimdVector squared_1 = SIMD_MULTIPLY(first_temp, first_temp); + const SimdVector squared_2 = SIMD_MULTIPLY(second_temp, second_temp); + const SimdVector unshuffled_1 = + _mm_shuffle_ps(squared_1, squared_2, _MM_SHUFFLE(2, 0, 2, 0)); + const SimdVector unshuffled_2 = + _mm_shuffle_ps(squared_1, squared_2, _MM_SHUFFLE(3, 1, 3, 1)); + SimdVector output_temp = SIMD_ADD(unshuffled_1, unshuffled_2); + output_temp = SIMD_SQRT(output_temp); + _mm_storeu_ps(&output[out_index * SIMD_LENGTH], output_temp); + } + } +#elif defined SIMD_NEON + if (input_aligned && output_aligned) { + for (size_t out_index = 0; out_index < num_chunks; ++out_index) { + const size_t first_index = out_index * 2; + const size_t second_index = first_index + 1; + const SimdVector squared_1 = + SIMD_MULTIPLY(input_vector[first_index], input_vector[first_index]); + const SimdVector squared_2 = + SIMD_MULTIPLY(input_vector[second_index], input_vector[second_index]); + const float32x4x2_t unshuffled = vuzpq_f32(squared_1, squared_2); + output_vector[out_index] = SIMD_ADD(unshuffled.val[0], unshuffled.val[1]); + output_vector[out_index] = SIMD_SQRT(output_vector[out_index]); + } + } else if (input_aligned) { + for (size_t out_index = 0; out_index < num_chunks; ++out_index) { + const size_t first_index = out_index * 2; + const size_t second_index = first_index + 1; + const SimdVector squared_1 = + SIMD_MULTIPLY(input_vector[first_index], input_vector[first_index]); + const SimdVector squared_2 = + SIMD_MULTIPLY(input_vector[second_index], input_vector[second_index]); + const float32x4x2_t unshuffled = vuzpq_f32(squared_1, squared_2); + SimdVector output_temp = SIMD_ADD(unshuffled.val[0], unshuffled.val[1]); + output_temp = SIMD_SQRT(output_temp); + vst1q_f32(&output[out_index * SIMD_LENGTH], output_temp); + } + } else if (output_aligned) { + for (size_t out_index = 0; out_index < num_chunks; ++out_index) { + const size_t first_index = out_index * 2; + const size_t second_index = first_index + 1; + const SimdVector first_temp = + vld1q_f32(&input[first_index * SIMD_LENGTH]); + const SimdVector second_temp = + vld1q_f32(&input[second_index * SIMD_LENGTH]); + const SimdVector squared_1 = SIMD_MULTIPLY(first_temp, first_temp); + const SimdVector squared_2 = SIMD_MULTIPLY(second_temp, second_temp); + const float32x4x2_t unshuffled = vuzpq_f32(squared_1, squared_2); + output_vector[out_index] = SIMD_ADD(unshuffled.val[0], unshuffled.val[1]); + output_vector[out_index] = SIMD_SQRT(output_vector[out_index]); + } + } else { + for (size_t out_index = 0; out_index < num_chunks; ++out_index) { + const size_t first_index = out_index * 2; + const size_t second_index = first_index + 1; + const SimdVector first_temp = + vld1q_f32(&input[first_index * SIMD_LENGTH]); + const SimdVector second_temp = + vld1q_f32(&input[second_index * SIMD_LENGTH]); + const SimdVector squared_1 = SIMD_MULTIPLY(first_temp, first_temp); + const SimdVector squared_2 = SIMD_MULTIPLY(second_temp, second_temp); + const float32x4x2_t unshuffled = vuzpq_f32(squared_1, squared_2); + SimdVector output_temp = SIMD_ADD(unshuffled.val[0], unshuffled.val[1]); + output_temp = SIMD_SQRT(output_temp); + vst1q_f32(&output[out_index * SIMD_LENGTH], output_temp); + } + } +#endif // SIMD_SSE + + // Apply to samples at the end that were missed by the SIMD chunking. + const size_t leftover_samples = GetLeftoverSamples(length); + DCHECK_GE(length, leftover_samples); + for (size_t i = length - leftover_samples; i < length; ++i) { + const size_t real_index = i * 2; + const size_t imag_index = real_index + 1; + const float squared_sum = (input[real_index] * input[real_index]) + + (input[imag_index] * input[imag_index]); + output[i] = 1.0f / FastReciprocalSqrt(squared_sum); + } +} + +void ComplexInterleavedFormatFromMagnitudeAndSinCosPhase( + size_t length, const float* magnitude, const float* cos_phase, + const float* sin_phase, float* complex_interleaved_format_output) { + size_t leftover_samples = 0; +#ifdef SIMD_NEON + if (IsAligned(complex_interleaved_format_output) && IsAligned(cos_phase) && + IsAligned(sin_phase) && IsAligned(magnitude)) { + const SimdVector* cos_vec = reinterpret_cast<const SimdVector*>(cos_phase); + const SimdVector* sin_vec = reinterpret_cast<const SimdVector*>(sin_phase); + const SimdVector* magnitude_vec = + reinterpret_cast<const SimdVector*>(magnitude); + + const size_t num_chunks = GetNumChunks(length); + float32x4x2_t interleaved_pair; + + SimdVector* interleaved_vec = + reinterpret_cast<SimdVector*>(complex_interleaved_format_output); + for (size_t i = 0, j = 0; j < num_chunks; ++i, j += 2) { + interleaved_pair = vzipq_f32(cos_vec[i], sin_vec[i]); + interleaved_vec[j] = + SIMD_MULTIPLY(interleaved_pair.val[0], magnitude_vec[i]); + interleaved_vec[j + 1] = + SIMD_MULTIPLY(interleaved_pair.val[1], magnitude_vec[i]); + } + + leftover_samples = GetLeftoverSamples(length); + } +#endif // SIMD_NEON + DCHECK_EQ(leftover_samples % 2U, 0U); + for (size_t i = leftover_samples, j = leftover_samples / 2; i < length; + i += 2, ++j) { + const size_t imaginary_offset = i + 1; + complex_interleaved_format_output[i] = magnitude[j] * cos_phase[j]; + complex_interleaved_format_output[imaginary_offset] = + magnitude[j] * sin_phase[j]; + } +} + +void StereoFromMonoSimd(size_t length, const float* mono, float* left, + float* right) { + ScalarMultiply(length, kInverseSqrtTwo, mono, left); + std::copy_n(left, length, right); +} + +void MonoFromStereoSimd(size_t length, const float* left, const float* right, + float* mono) { + DCHECK(left); + DCHECK(right); + DCHECK(mono); + + const SimdVector* left_vector = reinterpret_cast<const SimdVector*>(left); + const SimdVector* right_vector = reinterpret_cast<const SimdVector*>(right); + SimdVector* mono_vector = reinterpret_cast<SimdVector*>(mono); + + const SimdVector inv_root_two_vec = SIMD_LOAD_ONE_FLOAT(kInverseSqrtTwo); +#ifdef SIMD_SSE + const size_t num_chunks = GetNumChunks(length); + const bool inputs_aligned = IsAligned(left) && IsAligned(right); + const bool mono_aligned = IsAligned(mono); + if (inputs_aligned && mono_aligned) { + for (size_t i = 0; i < num_chunks; ++i) { + mono_vector[i] = SIMD_MULTIPLY(inv_root_two_vec, + SIMD_ADD(left_vector[i], right_vector[i])); + } + } else if (inputs_aligned) { + for (size_t i = 0; i < num_chunks; ++i) { + const SimdVector mono_temp = SIMD_MULTIPLY( + inv_root_two_vec, SIMD_ADD(left_vector[i], right_vector[i])); + _mm_storeu_ps(&mono[i * SIMD_LENGTH], mono_temp); + } + } else if (mono_aligned) { + for (size_t i = 0; i < num_chunks; ++i) { + const SimdVector left_temp = _mm_loadu_ps(&left[i * SIMD_LENGTH]); + const SimdVector right_temp = _mm_loadu_ps(&right[i * SIMD_LENGTH]); + mono_vector[i] = + SIMD_MULTIPLY(inv_root_two_vec, SIMD_ADD(left_temp, right_temp)); + } + } else { + for (size_t i = 0; i < num_chunks; ++i) { + const SimdVector left_temp = _mm_loadu_ps(&left[i * SIMD_LENGTH]); + const SimdVector right_temp = _mm_loadu_ps(&right[i * SIMD_LENGTH]); + const SimdVector mono_temp = + SIMD_MULTIPLY(inv_root_two_vec, SIMD_ADD(left_temp, right_temp)); + _mm_storeu_ps(&mono[i * SIMD_LENGTH], mono_temp); + } + } +#else + for (size_t i = 0; i < GetNumChunks(length); ++i) { + mono_vector[i] = SIMD_MULTIPLY(inv_root_two_vec, + SIMD_ADD(left_vector[i], right_vector[i])); + } +#endif // SIMD_SSE + const size_t leftover_samples = GetLeftoverSamples(length); + // Downmix samples at the end that were missed by the SIMD chunking. + DCHECK_GE(length, leftover_samples); + for (size_t i = length - leftover_samples; i < length; ++i) { + mono[i] = kInverseSqrtTwo * (left[i] + right[i]); + } +} + +#ifdef SIMD_NEON + +void Int16FromFloat(size_t length, const float* input, int16_t* output) { + DCHECK(input); + DCHECK(output); + + // if (input_aligned || output_aligned) { + const SimdVector* input_vector = reinterpret_cast<const SimdVector*>(input); + int16x4_t* output_vector = reinterpret_cast<int16x4_t*>(output); + + // A temporary 32 bit integer vector is needed as we only have intrinsics to + // convert from 32 bit floats to 32 bit ints. Then truncate to 16 bit ints. + int32x4_t temporary_wide_vector; + SimdVector temporary_float_vector; + + const SimdVector scaling_vector = SIMD_LOAD_ONE_FLOAT(kInt16FromFloat); + + for (size_t i = 0; i < GetNumChunks(length); ++i) { + temporary_float_vector = SIMD_MULTIPLY(scaling_vector, input_vector[i]); + temporary_wide_vector = vcvtq_s32_f32(temporary_float_vector); + output_vector[i] = vqmovn_s32(temporary_wide_vector); + } + + // The remainder. + const size_t leftover_samples = GetLeftoverSamples(length); + DCHECK_GE(length, leftover_samples); + float temp_float; + for (size_t i = length - leftover_samples; i < length; ++i) { + temp_float = input[i] * kInt16FromFloat; + temp_float = std::min(kInt16Max, std::max(kInt16Min, temp_float)); + output[i] = static_cast<int16_t>(temp_float); + } +} + +void FloatFromInt16(size_t length, const int16_t* input, float* output) { + DCHECK(input); + DCHECK(output); + + size_t leftover_samples = length; + const bool input_aligned = IsAligned(input); + const bool output_aligned = IsAligned(output); + if (input_aligned || output_aligned) { + const int16x4_t* input_vector = reinterpret_cast<const int16x4_t*>(input); + SimdVector* output_vector = reinterpret_cast<SimdVector*>(output); + + int16x4_t temporary_narrow_vector; + SimdVector temporary_float_vector; + int32x4_t temporary_wide_vector; + const SimdVector scaling_vector = SIMD_LOAD_ONE_FLOAT(kFloatFromInt16); + + if (input_aligned && output_aligned) { + for (size_t i = 0; i < GetNumChunks(length); ++i) { + temporary_wide_vector = vmovl_s16(input_vector[i]); + output_vector[i] = vcvtq_f32_s32(temporary_wide_vector); + output_vector[i] = SIMD_MULTIPLY(scaling_vector, output_vector[i]); + } + } else if (input_aligned) { + for (size_t i = 0; i < GetNumChunks(length); ++i) { + temporary_wide_vector = vmovl_s16(input_vector[i]); + temporary_float_vector = vcvtq_f32_s32(temporary_wide_vector); + temporary_float_vector = + SIMD_MULTIPLY(scaling_vector, temporary_float_vector); + vst1q_f32(&output[i * SIMD_LENGTH], temporary_float_vector); + } + } else { + for (size_t i = 0; i < GetNumChunks(length); ++i) { + temporary_narrow_vector = vld1_s16(&input[i * SIMD_LENGTH]); + temporary_wide_vector = vmovl_s16(temporary_narrow_vector); + output_vector[i] = vcvtq_f32_s32(temporary_wide_vector); + output_vector[i] = SIMD_MULTIPLY(scaling_vector, output_vector[i]); + } + } + leftover_samples = GetLeftoverSamples(length); + } + + // The remainder. + for (size_t i = length - leftover_samples; i < length; ++i) { + output[i] = static_cast<float>(input[i]) * kFloatFromInt16; + } +} + +#elif (defined SIMD_SSE && !defined(_MSC_VER)) + +void Int16FromFloat(size_t length, const float* input, int16_t* output) { + DCHECK(input); + DCHECK(output); + + size_t leftover_samples = length; + const bool input_aligned = IsAligned(input); + const bool output_aligned = IsAligned(output); + if (output_aligned) { + const SimdVector* input_vector = reinterpret_cast<const SimdVector*>(input); + __m64* output_vector = reinterpret_cast<__m64*>(output); + + const SimdVector scaling_vector = SIMD_LOAD_ONE_FLOAT(kInt16FromFloat); + const SimdVector min_vector = SIMD_LOAD_ONE_FLOAT(kInt16Min); + const SimdVector max_vector = SIMD_LOAD_ONE_FLOAT(kInt16Max); + + SimdVector temporary_float_vector; + + if (input_aligned) { + for (size_t i = 0; i < GetNumChunks(length); ++i) { + temporary_float_vector = SIMD_MULTIPLY(scaling_vector, input_vector[i]); + temporary_float_vector = _mm_max_ps(temporary_float_vector, min_vector); + temporary_float_vector = _mm_min_ps(temporary_float_vector, max_vector); + output_vector[i] = _mm_cvtps_pi16(temporary_float_vector); + } + } else { + for (size_t i = 0; i < GetNumChunks(length); ++i) { + temporary_float_vector = _mm_loadu_ps(&input[i * SIMD_LENGTH]); + temporary_float_vector = + SIMD_MULTIPLY(scaling_vector, temporary_float_vector); + temporary_float_vector = _mm_max_ps(temporary_float_vector, min_vector); + temporary_float_vector = _mm_min_ps(temporary_float_vector, max_vector); + output_vector[i] = _mm_cvtps_pi16(temporary_float_vector); + } + } + // There is no easy way to simply store the 16 bit ints so we dont have an + // |input_aligned| only case. + leftover_samples = GetLeftoverSamples(length); + } + + // The remainder. + float temp_float; + for (size_t i = length - GetLeftoverSamples(length); i < length; ++i) { + temp_float = input[i] * kInt16FromFloat; + temp_float = std::min(kInt16Max, std::max(kInt16Min, temp_float)); + output[i] = static_cast<int16_t>(temp_float); + } +} + +void FloatFromInt16(size_t length, const int16_t* input, float* output) { + DCHECK(input); + DCHECK(output); + + size_t leftover_samples = length; + const bool input_aligned = IsAligned(input); + const bool output_aligned = IsAligned(output); + if (input_aligned) { + SimdVector* output_vector = reinterpret_cast<SimdVector*>(output); + const __m64* input_vector = reinterpret_cast<const __m64*>(input); + + const SimdVector scaling_vector = SIMD_LOAD_ONE_FLOAT(kFloatFromInt16); + + if (output_aligned) { + for (size_t i = 0; i < GetNumChunks(length); ++i) { + output_vector[i] = _mm_cvtpi16_ps(input_vector[i]); + output_vector[i] = SIMD_MULTIPLY(scaling_vector, output_vector[i]); + } + } else { + SimdVector temporary_float_vector; + for (size_t i = 0; i < GetNumChunks(length); ++i) { + temporary_float_vector = _mm_cvtpi16_ps(input_vector[i]); + temporary_float_vector = + SIMD_MULTIPLY(scaling_vector, temporary_float_vector); + _mm_storeu_ps(&output[i * SIMD_LENGTH], temporary_float_vector); + } + } + // There is no easy way to simply load the 16 bit ints so we dont have an + // |output_aligned| only case. + leftover_samples = GetLeftoverSamples(length); + } + + // The remainder. + for (size_t i = length - leftover_samples; i < length; ++i) { + output[i] = static_cast<float>(input[i]) * kFloatFromInt16; + } +} + +#else // SIMD disabled or Windows build. + +void Int16FromFloat(size_t length, const float* input, int16_t* output) { + DCHECK(input); + DCHECK(output); + + float temp_float; + for (size_t i = 0; i < length; ++i) { + temp_float = input[i] * kInt16FromFloat; + temp_float = std::min(kInt16Max, std::max(kInt16Min, temp_float)); + output[i] = static_cast<int16_t>(temp_float); + } +} + +void FloatFromInt16(size_t length, const int16_t* input, float* output) { + DCHECK(input); + DCHECK(output); + + for (size_t i = 0; i < length; ++i) { + output[i] = static_cast<float>(input[i]) * kFloatFromInt16; + } +} + +#endif // SIMD_NEON + +void InterleaveStereo(size_t length, const int16_t* channel_0, + const int16_t* channel_1, int16_t* interleaved_buffer) { + DCHECK(interleaved_buffer); + DCHECK(channel_0); + DCHECK(channel_1); + + size_t leftover_samples = length; +#ifdef SIMD_NEON + if (IsAligned(interleaved_buffer) && IsAligned(channel_0) && + IsAligned(channel_1)) { + const int16x8_t* channel_0_vec = + reinterpret_cast<const int16x8_t*>(channel_0); + const int16x8_t* channel_1_vec = + reinterpret_cast<const int16x8_t*>(channel_1); + + const size_t num_chunks = length / kSixteenBitSimdLength; + int16x8x2_t interleaved_pair; + + int16x8_t* interleaved_vec = + reinterpret_cast<int16x8_t*>(interleaved_buffer); + for (size_t i = 0, j = 0; i < num_chunks; ++i, j += 2) { + interleaved_pair = vzipq_s16(channel_0_vec[i], channel_1_vec[i]); + interleaved_vec[j] = interleaved_pair.val[0]; + interleaved_vec[j + 1] = interleaved_pair.val[1]; + } + + leftover_samples = length % kSixteenBitSimdLength; + } +#endif // SIMD_NEON + for (size_t i = length - leftover_samples; i < length; ++i) { + const size_t interleaved_index = kNumStereoChannels * i; + interleaved_buffer[interleaved_index] = channel_0[i]; + interleaved_buffer[interleaved_index + 1] = channel_1[i]; + } +} + +void InterleaveStereo(size_t length, const float* channel_0, + const float* channel_1, float* interleaved_buffer) { + DCHECK(interleaved_buffer); + DCHECK(channel_0); + DCHECK(channel_1); + + size_t leftover_samples = length; +#ifdef SIMD_NEON + if (IsAligned(interleaved_buffer) && IsAligned(channel_0) && + IsAligned(channel_1)) { + const SimdVector* channel_0_vec = + reinterpret_cast<const SimdVector*>(channel_0); + const SimdVector* channel_1_vec = + reinterpret_cast<const SimdVector*>(channel_1); + + const size_t num_chunks = GetNumChunks(length); + float32x4x2_t interleaved_pair; + + SimdVector* interleaved_vec = + reinterpret_cast<SimdVector*>(interleaved_buffer); + for (size_t i = 0, j = 0; i < num_chunks; ++i, j += 2) { + interleaved_pair = vzipq_f32(channel_0_vec[i], channel_1_vec[i]); + interleaved_vec[j] = interleaved_pair.val[0]; + interleaved_vec[j + 1] = interleaved_pair.val[1]; + } + + leftover_samples = GetLeftoverSamples(length); + } +#endif // SIMD_NEON + for (size_t i = length - leftover_samples; i < length; ++i) { + const size_t interleaved_index = kNumStereoChannels * i; + interleaved_buffer[interleaved_index] = channel_0[i]; + interleaved_buffer[interleaved_index + 1] = channel_1[i]; + } +} + +void InterleaveStereo(size_t length, const float* channel_0, + const float* channel_1, int16_t* interleaved_buffer) { + DCHECK(interleaved_buffer); + DCHECK(channel_0); + DCHECK(channel_1); + + size_t leftover_samples = length; +#ifdef SIMD_NEON + if (IsAligned(interleaved_buffer) && IsAligned(channel_0) && + IsAligned(channel_1)) { + const SimdVector* channel_0_vec = + reinterpret_cast<const SimdVector*>(channel_0); + const SimdVector* channel_1_vec = + reinterpret_cast<const SimdVector*>(channel_1); + + const size_t num_chunks = GetNumChunks(length); + float32x4x2_t interleaved_pair; + int32x4_t temporary_wide_vector; + + const SimdVector scaling_vector = SIMD_LOAD_ONE_FLOAT(kInt16FromFloat); + const SimdVector min_vector = SIMD_LOAD_ONE_FLOAT(kInt16Min); + const SimdVector max_vector = SIMD_LOAD_ONE_FLOAT(kInt16Max); + + int16x4_t* interleaved_vec = + reinterpret_cast<int16x4_t*>(interleaved_buffer); + for (size_t i = 0; i < num_chunks; ++i) { + const size_t interleaved_index = kNumStereoChannels * i; + interleaved_pair = vzipq_f32(channel_0_vec[i], channel_1_vec[i]); + interleaved_pair.val[0] = + SIMD_MULTIPLY(scaling_vector, interleaved_pair.val[0]); + interleaved_pair.val[0] = vmaxq_f32(interleaved_pair.val[0], min_vector); + interleaved_pair.val[0] = vminq_f32(interleaved_pair.val[0], max_vector); + temporary_wide_vector = vcvtq_s32_f32(interleaved_pair.val[0]); + interleaved_vec[interleaved_index] = vqmovn_s32(temporary_wide_vector); + interleaved_pair.val[1] = + SIMD_MULTIPLY(scaling_vector, interleaved_pair.val[1]); + interleaved_pair.val[1] = vmaxq_f32(interleaved_pair.val[1], min_vector); + interleaved_pair.val[1] = vminq_f32(interleaved_pair.val[1], max_vector); + temporary_wide_vector = vcvtq_s32_f32(interleaved_pair.val[1]); + interleaved_vec[interleaved_index + 1] = + vqmovn_s32(temporary_wide_vector); + } + + leftover_samples = GetLeftoverSamples(length); + } +#endif // SIMD_NEON + for (size_t i = length - leftover_samples; i < length; ++i) { + const size_t interleaved_index = kNumStereoChannels * i; + interleaved_buffer[interleaved_index] = static_cast<int16_t>(std::max( + kInt16Min, std::min(kInt16Max, kInt16FromFloat * channel_0[i]))); + interleaved_buffer[interleaved_index + 1] = static_cast<int16_t>(std::max( + kInt16Min, std::min(kInt16Max, kInt16FromFloat * channel_1[i]))); + } +} + +void DeinterleaveStereo(size_t length, const int16_t* interleaved_buffer, + int16_t* channel_0, int16_t* channel_1) { + DCHECK(interleaved_buffer); + DCHECK(channel_0); + DCHECK(channel_1); + + size_t leftover_samples = length; +#ifdef SIMD_NEON + if (IsAligned(interleaved_buffer) && IsAligned(channel_0) && + IsAligned(channel_1)) { + const size_t num_chunks = length / kSixteenBitSimdLength; + leftover_samples = length % kSixteenBitSimdLength; + int16x8_t* channel_0_vec = reinterpret_cast<int16x8_t*>(channel_0); + int16x8_t* channel_1_vec = reinterpret_cast<int16x8_t*>(channel_1); + int16x8x2_t deinterleaved_pair; + const int16x8_t* interleaved_vec = + reinterpret_cast<const int16x8_t*>(interleaved_buffer); + for (size_t chunk = 0; chunk < num_chunks; ++chunk) { + const size_t interleaved_index = chunk * kNumStereoChannels; + deinterleaved_pair = vuzpq_s16(interleaved_vec[interleaved_index], + interleaved_vec[interleaved_index + 1]); + channel_0_vec[chunk] = deinterleaved_pair.val[0]; + channel_1_vec[chunk] = deinterleaved_pair.val[1]; + } + } +#endif // SIMD_NEON + for (size_t i = length - leftover_samples; i < length; ++i) { + const size_t interleaved_index = kNumStereoChannels * i; + channel_0[i] = interleaved_buffer[interleaved_index]; + channel_1[i] = interleaved_buffer[interleaved_index + 1]; + } +} + +void DeinterleaveStereo(size_t length, const float* interleaved_buffer, + float* channel_0, float* channel_1) { + DCHECK(interleaved_buffer); + DCHECK(channel_0); + DCHECK(channel_1); + + size_t leftover_samples = length; +#ifdef SIMD_NEON + if (IsAligned(interleaved_buffer) && IsAligned(channel_0) && + IsAligned(channel_1)) { + const size_t num_chunks = GetNumChunks(length); + leftover_samples = GetLeftoverSamples(length); + SimdVector* channel_0_vec = reinterpret_cast<SimdVector*>(channel_0); + SimdVector* channel_1_vec = reinterpret_cast<SimdVector*>(channel_1); + float32x4x2_t deinterleaved_pair; + + const SimdVector* interleaved_vec = + reinterpret_cast<const SimdVector*>(interleaved_buffer); + for (size_t chunk = 0; chunk < num_chunks; ++chunk) { + const size_t interleaved_index = chunk * kNumStereoChannels; + deinterleaved_pair = vuzpq_f32(interleaved_vec[interleaved_index], + interleaved_vec[interleaved_index + 1]); + channel_0_vec[chunk] = deinterleaved_pair.val[0]; + channel_1_vec[chunk] = deinterleaved_pair.val[1]; + } + } +#endif // SIMD_NEON + for (size_t i = length - leftover_samples; i < length; ++i) { + const size_t interleaved_index = kNumStereoChannels * i; + channel_0[i] = interleaved_buffer[interleaved_index]; + channel_1[i] = interleaved_buffer[interleaved_index + 1]; + } +} + +void DeinterleaveStereo(size_t length, const int16_t* interleaved_buffer, + float* channel_0, float* channel_1) { + DCHECK(interleaved_buffer); + DCHECK(channel_0); + DCHECK(channel_1); + + size_t leftover_samples = length; +#ifdef SIMD_NEON + if (IsAligned(interleaved_buffer) && IsAligned(channel_0) && + IsAligned(channel_1)) { + const size_t num_chunks = GetNumChunks(length); + leftover_samples = GetLeftoverSamples(length); + SimdVector* channel_0_vec = reinterpret_cast<SimdVector*>(channel_0); + SimdVector* channel_1_vec = reinterpret_cast<SimdVector*>(channel_1); + int16x4x2_t deinterleaved_pair; + int32x4_t temporary_wide; + const SimdVector scaling_vector = SIMD_LOAD_ONE_FLOAT(kFloatFromInt16); + + const int16x4_t* interleaved_vec = + reinterpret_cast<const int16x4_t*>(interleaved_buffer); + for (size_t chunk = 0; chunk < num_chunks; ++chunk) { + const size_t interleaved_index = chunk * kNumStereoChannels; + deinterleaved_pair = vuzp_s16(interleaved_vec[interleaved_index], + interleaved_vec[interleaved_index + 1]); + temporary_wide = vmovl_s16(deinterleaved_pair.val[0]); + channel_0_vec[chunk] = vcvtq_f32_s32(temporary_wide); + channel_0_vec[chunk] = + SIMD_MULTIPLY(scaling_vector, channel_0_vec[chunk]); + temporary_wide = vmovl_s16(deinterleaved_pair.val[1]); + channel_1_vec[chunk] = vcvtq_f32_s32(temporary_wide); + channel_1_vec[chunk] = + SIMD_MULTIPLY(scaling_vector, channel_1_vec[chunk]); + } + } +#endif // SIMD_NEON + for (size_t i = length - leftover_samples; i < length; ++i) { + const size_t interleaved_index = kNumStereoChannels * i; + channel_0[i] = static_cast<float>(interleaved_buffer[interleaved_index]) * + kFloatFromInt16; + channel_1[i] = + static_cast<float>(interleaved_buffer[interleaved_index + 1]) * + kFloatFromInt16; + } +} + +void InterleaveQuad(size_t length, const int16_t* channel_0, + const int16_t* channel_1, const int16_t* channel_2, + const int16_t* channel_3, int16_t* workspace, + int16_t* interleaved_buffer) { +#ifdef SIMD_NEON + DCHECK(IsAligned(workspace)); + const size_t double_length = length * 2; + int16_t* workspace_half_point = + workspace + FindNextAlignedArrayIndex(double_length, sizeof(int16_t), + kMemoryAlignmentBytes); + InterleaveStereo(length, channel_0, channel_2, workspace); + InterleaveStereo(length, channel_1, channel_3, workspace_half_point); + InterleaveStereo(double_length, workspace, workspace_half_point, + interleaved_buffer); +#else + for (size_t i = 0; i < length; ++i) { + const size_t interleaved_index = kNumFirstOrderAmbisonicChannels * i; + interleaved_buffer[interleaved_index] = channel_0[i]; + interleaved_buffer[interleaved_index + 1] = channel_1[i]; + interleaved_buffer[interleaved_index + 2] = channel_2[i]; + interleaved_buffer[interleaved_index + 3] = channel_3[i]; + } +#endif // SIMD_NEON +} + +void InterleaveQuad(size_t length, const float* channel_0, + const float* channel_1, const float* channel_2, + const float* channel_3, float* workspace, + float* interleaved_buffer) { +#ifdef SIMD_NEON + DCHECK(IsAligned(workspace)); + const size_t double_length = length * 2; + float* workspace_half_point = + workspace + FindNextAlignedArrayIndex(double_length, sizeof(float), + kMemoryAlignmentBytes); + DCHECK(IsAligned(workspace_half_point)); + InterleaveStereo(length, channel_0, channel_2, workspace); + InterleaveStereo(length, channel_1, channel_3, workspace_half_point); + InterleaveStereo(double_length, workspace, workspace_half_point, + interleaved_buffer); +#else + for (size_t i = 0; i < length; ++i) { + const size_t interleaved_index = kNumFirstOrderAmbisonicChannels * i; + interleaved_buffer[interleaved_index] = channel_0[i]; + interleaved_buffer[interleaved_index + 1] = channel_1[i]; + interleaved_buffer[interleaved_index + 2] = channel_2[i]; + interleaved_buffer[interleaved_index + 3] = channel_3[i]; + } +#endif // SIMD_NEON +} + +void DeinterleaveQuad(size_t length, const int16_t* interleaved_buffer, + int16_t* workspace, int16_t* channel_0, + int16_t* channel_1, int16_t* channel_2, + int16_t* channel_3) { +#ifdef SIMD_NEON + DCHECK(IsAligned(workspace)); + const size_t double_length = length * 2; + int16_t* workspace_half_point = + workspace + FindNextAlignedArrayIndex(double_length, sizeof(int16_t), + kMemoryAlignmentBytes); + DCHECK(IsAligned(workspace_half_point)); + DeinterleaveStereo(double_length, interleaved_buffer, workspace, + workspace_half_point); + DeinterleaveStereo(length, workspace, channel_0, channel_2); + DeinterleaveStereo(length, workspace_half_point, channel_1, channel_3); +#else + for (size_t i = 0; i < length; ++i) { + const size_t interleaved_index = kNumFirstOrderAmbisonicChannels * i; + channel_0[i] = interleaved_buffer[interleaved_index]; + channel_1[i] = interleaved_buffer[interleaved_index + 1]; + channel_2[i] = interleaved_buffer[interleaved_index + 2]; + channel_3[i] = interleaved_buffer[interleaved_index + 3]; + } +#endif // SIMD_NEON +} + +void DeinterleaveQuad(size_t length, const float* interleaved_buffer, + float* workspace, float* channel_0, float* channel_1, + float* channel_2, float* channel_3) { +#ifdef SIMD_NEON + DCHECK(IsAligned(workspace)); + const size_t double_length = length * 2; + float* workspace_half_point = + workspace + FindNextAlignedArrayIndex(double_length, sizeof(float), + kMemoryAlignmentBytes); + DCHECK(IsAligned(workspace_half_point)); + DeinterleaveStereo(double_length, interleaved_buffer, workspace, + workspace_half_point); + DeinterleaveStereo(length, workspace, channel_0, channel_2); + DeinterleaveStereo(length, workspace_half_point, channel_1, channel_3); +#else + for (size_t i = 0; i < length; ++i) { + const size_t interleaved_index = kNumFirstOrderAmbisonicChannels * i; + channel_0[i] = interleaved_buffer[interleaved_index]; + channel_1[i] = interleaved_buffer[interleaved_index + 1]; + channel_2[i] = interleaved_buffer[interleaved_index + 2]; + channel_3[i] = interleaved_buffer[interleaved_index + 3]; + } +#endif // SIMD_NEON +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/base/simd_utils.h b/src/3rdparty/resonance-audio/resonance_audio/base/simd_utils.h new file mode 100644 index 000000000..64fb9c6d1 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/base/simd_utils.h @@ -0,0 +1,296 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_BASE_SIMD_UTILS_H_ +#define RESONANCE_AUDIO_BASE_SIMD_UTILS_H_ + +#include <cstddef> +#include <cstdint> + +namespace vraudio { + +// Checks if the pointer provided is correctly aligned for SIMD. +// +// @param pointer Pointer to check. +// @return True if the pointer is correctly aligned. +bool IsAligned(const float* pointer); +bool IsAligned(const int16_t* pointer); + +// Rounds a number of frames up to the next aligned memory address +// based on |memory_alignment_bytes|. This allows for aligning offset pointers +// into a single chunk of allocated memory. +// +// @param length Number of samples before the desired offset pointer. +// @param type_size_bytes Size of the type of each entry in the array. +// @param memory_alignment_bytes Number of bytes to which an address is aligned. +// @return Number of samples into the memory chunk to ensure aligned memory. +size_t FindNextAlignedArrayIndex(size_t length, size_t type_size_bytes, + size_t memory_alignment_bytes); + +// Adds a float array |input_a| to another float array |input_b| and stores the +// result in |output|. +// +// @param length Number of floats. +// @param input_a Pointer to the first float in input_a array. +// @param input_b Pointer to the first float in input_b array. +// @param output Pointer to the first float in output array. +void AddPointwise(size_t length, const float* input_a, const float* input_b, + float* output); + +// Subtracts a float array |input|, pointwise from another float array |output|. +// +// @param length Number of floats. +// @param input Pointer to the first float in input_a array. +// @param output Pointer to the first float in input_b array. +// @param output Pointer to the first float in output array. +void SubtractPointwise(size_t length, const float* input_a, + const float* input_b, float* output); + +// Pointwise multiplies a float array |input_a| with another float array +// |input_b| and stores the result in |output|. +// +// @param length Number of floats. +// @param input Pointer to the first float in input_a array. +// @param input Pointer to the first float in input_b array. +// @param output Pointer to the first float in output array. +void MultiplyPointwise(size_t length, const float* input_a, + const float* input_b, float* output); + +// Pointwise multiplies a float array |input_a| with another float array +// |input_b| and adds the result onto |accumulator|. +// +// @param length Number of floats. +// @param input_a Pointer to the first float in input_a array. +// @param input_b Pointer to the first float in input_b array. +// @param accumulator Pointer to the first float in accumulator array. +void MultiplyAndAccumulatePointwise(size_t length, const float* input_a, + const float* input_b, float* accumulator); + +// Multiplies a float array |input| by a scalar |gain| over |length| samples. +// +// @param length Number of floats. +// @param gain Scalar value with which to multiply the input. +// @param input Pointer to the first float in input array. +// @param output Pointer to the first float in output array. +void ScalarMultiply(size_t length, float gain, const float* input, + float* output); + +// Multiplies a float array |input| by a scalar |gain| over |length| samples and +// adds the result onto |accumulator|. +// +// @param length Number of floats. +// @param gain Scalar value with which to multiply the input. +// @param input Pointer to the first float in input array. +// @param output Pointer to the first float in accumulator array. +void ScalarMultiplyAndAccumulate(size_t length, float gain, const float* input, + float* accumulator); + +// Calculates an approximmate reciprocal square root. +// +// @param length Number of floats. +// @param input Pointer to the first float in input array. +// @param output Pointer to the first float in output array. +void ReciprocalSqrt(size_t length, const float* input, float* output); + +// Calculates an approximate square root. +// +// @param length Number of floats. +// @param input Pointer to the first float in input array. +// @param output Pointer to the first float in output array. +void Sqrt(size_t length, const float* input, float* output); + +// Calculates the approximate magnitudes of interleaved complex numbers. +// +// @param length Number of complex numbers in the input array, +// (i.e. half its length). +// @param input Pointer to the first float in input array. Length: 2 * |length|. +// @param output Pointer to the first float in output array, Length: |length|. +void ApproxComplexMagnitude(size_t length, const float* input, float* output); + +// Calculates the complex values in interleaved format (real, imaginary), from a +// vector of magnitudes and of sines and cosines of phase. +// +// @param length Number of total entries (real & imaginary) in the input array. +// @param magnitude Pointer to the first float in the magnitude array, Length: +// |length| / 2 +// @param cos_phase Pointer to the first float in the cosine phase array, +// Length: |length| / 2 +// @param sin_phase Pointer to the first float in the sine phase array, Length: +// |length| / 2 +// @param complex_interleaved_format_output Pointer to the first float in the +// output array. Length: |length|. +void ComplexInterleavedFormatFromMagnitudeAndSinCosPhase( + size_t length, const float* magnitude, const float* cos_phase, + const float* sin_phase, float* complex_interleaved_format_output); + +// Generates an identical left and right pair of stereo channels from a mono +// input channel, where each channel is the mono channel times 1/sqrt(2). +// +// @param length Number of floats. +// @param mono Pointer to the first float in an input mono array. +// @param left Pointer to the first float in the left output array. +// @param right Pointer to the first float in the right output array. +void StereoFromMonoSimd(size_t length, const float* mono, float* left, + float* right); + +// Generates a mono downmix from a pair of stereo channels, where the output is +// equal to the sum of the two inputs times 1/sqrt(2). +// +// @param length Number of floats. +// @param left Pointer to the first float in the left input array. +// @param right Pointer to the first float in the right input array. +// @param mono Pointer to the first float in an output mono array. +void MonoFromStereoSimd(size_t length, const float* left, const float* right, + float* mono); + +// Converts an array of 32 bit float input to clamped 16 bit int output. +// +// @param length Number of floats in the input array and int16_ts in the output. +// @param input Float array. +// @param output Int array. +void Int16FromFloat(size_t length, const float* input, int16_t* output); + +// Converts an array of 16 bit int input to 32 bit float output. +// +// @param length Number of int16_ts in the input array and floats in the output. +// @param input Int array. +// @param output Float array. +void FloatFromInt16(size_t length, const int16_t* input, float* output); + +// Interleaves a pair of mono buffers of int_16 data into a stereo buffer. +// +// @param length Number of frames per mono channel. The interleaved buffer must +// be twice this size. +// @param channel_0 Input buffer of mono data for the first channel. +// @param channel_1 Input buffer of mono data for the second channel. +// @param interleaved_buffer Output buffer of stereo interleaved data. +void InterleaveStereo(size_t length, const int16_t* channel_0, + const int16_t* channel_1, int16_t* interleaved_buffer); + +// Interleaves a pair of mono buffers of float data into a stereo buffer. +// +// @param length Number of frames per mono channel. The interleaved buffer must +// be twice this size. +// @param channel_0 Input buffer of mono data for the first channel. +// @param channel_1 Input buffer of mono data for the second channel. +// @param interleaved_buffer Output buffer of stereo interleaved data. +void InterleaveStereo(size_t length, const float* channel_0, + const float* channel_1, float* interleaved_buffer); + +// Interleaves a pair of mono buffers of float data into a stereo buffer of +// int16_t data. +// +// @param length Number of frames per mono channel. The interleaved buffer must +// be twice this size. +// @param channel_0 Input buffer of mono data for the first channel (float). +// @param channel_1 Input buffer of mono data for the second channel (float). +// @param interleaved_buffer Output buffer of stereo interleaved data (int16_t). +void InterleaveStereo(size_t length, const float* channel_0, + const float* channel_1, int16_t* interleaved_buffer); + +// Deinterleaves a stereo buffer of int16_t data into a pair of mono buffers. +// +// @param length Number of frames per mono channel. The interleaved buffer must +// be twice this size. +// @param interleaved_buffer Input buffer of stereo interleaved data. +// @param channel_0 Output buffer of mono data for the first channel. +// @param channel_1 Output buffer of mono data for the second channel. +void DeinterleaveStereo(size_t length, const int16_t* interleaved_buffer, + int16_t* channel_0, int16_t* channel_1); + +// Deinterleaves a stereo buffer of float data into a pair of mono buffers. +// +// @param length Number of frames per mono channel. The interleaved buffer must +// be twice this size. +// @param interleaved_buffer Input buffer of stereo interleaved data. +// @param channel_0 Output buffer of mono data for the first channel. +// @param channel_1 Output buffer of mono data for the second channel. +void DeinterleaveStereo(size_t length, const float* interleaved_buffer, + float* channel_0, float* channel_1); + +// Deinterleaves a stereo buffer of int16_t data into a pair of mono float +// buffers, performing the int16 to floating point conversion. +// +// @param length Number of frames per mono channel. The interleaved buffer must +// be twice this size. +// @param interleaved_buffer Input buffer of stereo interleaved data (int16_t). +// @param channel_0 Output buffer of mono data for the first channel (float). +// @param channel_1 Output buffer of mono data for the second channel (float). +void DeinterleaveStereo(size_t length, const int16_t* interleaved_buffer, + float* channel_0, float* channel_1); + +// Interleaves four mono buffers of int16_t data into a quad buffer. +// +// @param length Number of frames per mono channel. The interleaved buffer must +// be four times this size and the workspace must be five times this size. +// @param channel_0 Input buffer of mono data for the first channel. +// @param channel_1 Input buffer of mono data for the second channel. +// @param channel_2 Input buffer of mono data for the third channel. +// @param channel_3 Input buffer of mono data for the fourth channel. +// @param workspace Aligned buffer of 5 * |length| samples in length. +// @param interleaved_buffer Output buffer of quad interleaved data. +void InterleaveQuad(size_t length, const int16_t* channel_0, + const int16_t* channel_1, const int16_t* channel_2, + const int16_t* channel_3, int16_t* workspace, + int16_t* interleaved_buffer); + +// Interleaves four mono buffers of float data into a quad buffer. +// +// @param length Number of frames per mono channel. The interleaved buffer must +// be four times this size and the workspace must be five times this size. +// @param channel_0 Input buffer of mono data for the first channel. +// @param channel_1 Input buffer of mono data for the second channel. +// @param channel_2 Input buffer of mono data for the third channel. +// @param channel_3 Input buffer of mono data for the fourth channel. +// @param workspace Aligned buffer of 5 * |length| samples in length. +// @param interleaved_buffer Output buffer of quad interleaved data. +void InterleaveQuad(size_t length, const float* channel_0, + const float* channel_1, const float* channel_2, + const float* channel_3, float* workspace, + float* interleaved_buffer); + +// Deinterleaves a quad buffer of int16_t data into four mono buffers. +// +// @param length Number of frames per mono channel. The interleaved buffer must +// be four times this size and the workspace must be five times this size. +// @param interleaved_buffer Input buffer of quad interleaved data. +// @param workspace Aligned buffer of 5 * |length| samples in length. +// @param channel_0 Output buffer of mono data for the first channel. +// @param channel_1 Output buffer of mono data for the second channel. +// @param channel_2 Output buffer of mono data for the third channel. +// @param channel_3 Output buffer of mono data for the fourth channel. +void DeinterleaveQuad(size_t length, const int16_t* interleaved_buffer, + int16_t* workspace, int16_t* channel_0, + int16_t* channel_1, int16_t* channel_2, + int16_t* channel_3); + +// Deinterleaves a quad buffer of float data into four mono buffers. +// +// @param length Number of frames per mono channel. The interleaved buffer must +// be four times this size and the workspace must be five times this size. +// @param interleaved_buffer Input buffer of quad interleaved data. +// @param workspace Aligned buffer of 5 * |length| samples in length. +// @param channel_0 Output buffer of mono data for the first channel. +// @param channel_1 Output buffer of mono data for the second channel. +// @param channel_2 Output buffer of mono data for the third channel. +// @param channel_3 Output buffer of mono data for the fourth channel. +void DeinterleaveQuad(size_t length, const float* interleaved_buffer, + float* workspace, float* channel_0, float* channel_1, + float* channel_2, float* channel_3); + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_BASE_SIMD_UTILS_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/base/simd_utils_test.cc b/src/3rdparty/resonance-audio/resonance_audio/base/simd_utils_test.cc new file mode 100644 index 000000000..7fb8e83b5 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/base/simd_utils_test.cc @@ -0,0 +1,711 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "base/simd_utils.h" + +#include <algorithm> +#include <cmath> +#include <cstdint> +#include <vector> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/audio_buffer.h" +#include "base/constants_and_types.h" + +namespace vraudio { + +namespace { + +// Input lengths (purposefully chosen not to be a multiple of SIMD_LENGTH). +const size_t kInputSize = 7; +const size_t kNumTestChannels = 3; +const size_t kNumQuadChannels = 4; + +// Length of deinterleaved and interleaved buffers. +const size_t kHalfSize = 21; +const size_t kFullSize = kHalfSize * 2; +const size_t kQuadSize = kHalfSize * 4; +const size_t kPentSize = kHalfSize * 5; + +// The int16 values for the deinterleaving test. +const int16_t kOne = 0x0001; +const int16_t kTwo = 0x0002; +const int16_t kThree = 0x0003; +const int16_t kFour = 0x0004; +const int16_t kMax = 0x7FFF; +const int16_t kMin = -0x7FFF; + +// Epsilon for conversion from int16_t back to float. +const float kFloatEpsilon = 1e-4f; +const int16_t kIntEpsilon = 1; + +// Intereleaved data. +const int16_t kInterleavedInput[kFullSize] = { + kOne, kTwo, kOne, kTwo, kOne, kTwo, kOne, kTwo, kOne, kTwo, kOne, + kTwo, kOne, kTwo, kOne, kTwo, kOne, kTwo, kOne, kTwo, kOne, kTwo, + kOne, kTwo, kOne, kTwo, kOne, kTwo, kOne, kTwo, kOne, kTwo, kOne, + kTwo, kOne, kTwo, kOne, kTwo, kOne, kTwo, kOne, kTwo}; + +// Corresponding values for float and 16 bit int. +const float kFloatInput[kInputSize] = {0.5f, -0.5f, 1.0f, -1.0f, + 1.0f, -1.0f, 0.0f}; +const int16_t kIntInput[kInputSize] = {0x4000, -0x4000, 0x7FFF, -0x7FFF, + 0x7FFF, -0x7FFF, 0}; + +TEST(SimdUtilsTest, IsAlignedTest) { + AudioBuffer aligned_audio_buffer(kNumMonoChannels, kInputSize); + const float* aligned_ptr = aligned_audio_buffer[0].begin(); + const float* unaligned_ptr = aligned_ptr + 1; + EXPECT_TRUE(IsAligned(aligned_ptr)); + EXPECT_FALSE(IsAligned(unaligned_ptr)); +} + +TEST(SimdUtilsTest, AddPointwiseTest) { + const float kResult = 3.0f; + AudioBuffer aligned_audio_buffer(kNumTestChannels, kInputSize); + aligned_audio_buffer.Clear(); + for (size_t i = 0; i < kInputSize; ++i) { + aligned_audio_buffer[0][i] = 1.0f; + aligned_audio_buffer[1][i] = 2.0f; + } + AddPointwise(kInputSize, &aligned_audio_buffer[0][0], + &aligned_audio_buffer[1][0], &aligned_audio_buffer[2][0]); + for (size_t i = 0; i < kInputSize; ++i) { + EXPECT_FLOAT_EQ(aligned_audio_buffer[2][i], kResult); + } +} + +TEST(SimdUtilsTest, AddPointwiseInPlaceTest) { + AudioBuffer aligned_audio_buffer(kNumStereoChannels, kInputSize); + aligned_audio_buffer.Clear(); + for (size_t i = 0; i < kInputSize; ++i) { + aligned_audio_buffer[0][i] = static_cast<float>(i); + } + const size_t kRuns = 2; + for (size_t i = 0; i < kRuns; ++i) { + AddPointwise(kInputSize, &aligned_audio_buffer[0][0], + &aligned_audio_buffer[1][0], &aligned_audio_buffer[1][0]); + } + for (size_t i = 0; i < kInputSize; ++i) { + EXPECT_FLOAT_EQ(aligned_audio_buffer[1][i], static_cast<float>(i * kRuns)); + } +} + +TEST(SimdUtilsTest, SubtractPointwiseTest) { + AudioBuffer aligned_audio_buffer(kNumStereoChannels, kInputSize); + aligned_audio_buffer.Clear(); + for (size_t i = 0; i < kInputSize; ++i) { + aligned_audio_buffer[0][i] = static_cast<float>(i); + aligned_audio_buffer[1][i] = static_cast<float>(2 * i); + } + SubtractPointwise(kInputSize, &aligned_audio_buffer[0][0], + &aligned_audio_buffer[1][0], &aligned_audio_buffer[1][0]); + for (size_t i = 0; i < kInputSize; ++i) { + EXPECT_FLOAT_EQ(aligned_audio_buffer[1][i], aligned_audio_buffer[0][i]); + } +} + +TEST(SimdUtilsTest, MultiplyPointwiseTest) { + AudioBuffer aligned_audio_buffer(kNumStereoChannels, kInputSize); + aligned_audio_buffer.Clear(); + for (size_t i = 0; i < kInputSize; ++i) { + aligned_audio_buffer[0][i] = static_cast<float>(i); + aligned_audio_buffer[1][i] = static_cast<float>(i); + } + MultiplyPointwise(kInputSize, &aligned_audio_buffer[0][0], + &aligned_audio_buffer[1][0], &aligned_audio_buffer[1][0]); + for (size_t i = 0; i < kInputSize; ++i) { + EXPECT_FLOAT_EQ(aligned_audio_buffer[1][i], + aligned_audio_buffer[0][i] * aligned_audio_buffer[0][i]); + } +} + +TEST(SimdUtilsTest, MultiplyAndAccumulatePointwiseTest) { + const float kInitialOutput = 1.0f; + AudioBuffer aligned_input_buffer(kNumStereoChannels, kInputSize); + aligned_input_buffer.Clear(); + AudioBuffer aligned_output_buffer(kNumMonoChannels, kInputSize); + aligned_output_buffer.Clear(); + for (size_t i = 0; i < kInputSize; ++i) { + aligned_input_buffer[0][i] = static_cast<float>(i); + aligned_input_buffer[1][i] = static_cast<float>(i); + aligned_output_buffer[0][i] = kInitialOutput; + } + MultiplyAndAccumulatePointwise(kInputSize, &aligned_input_buffer[0][0], + &aligned_input_buffer[1][0], + &aligned_output_buffer[0][0]); + for (size_t i = 0; i < kInputSize; ++i) { + EXPECT_FLOAT_EQ(aligned_output_buffer[0][i], + kInitialOutput + aligned_input_buffer[0][i] * + aligned_input_buffer[1][i]); + } +} + +TEST(SimdUtilsTest, ScalarMultiplyTest) { + AudioBuffer aligned_audio_buffer(kNumStereoChannels, kInputSize); + aligned_audio_buffer.Clear(); + for (size_t i = 0; i < kInputSize; ++i) { + aligned_audio_buffer[0][i] = 1.0f; + aligned_audio_buffer[1][i] = 0.0f; + } + const float gain = 0.5f; + ScalarMultiply(kInputSize, gain, &aligned_audio_buffer[0][0], + &aligned_audio_buffer[1][0]); + for (size_t i = 0; i < kInputSize; ++i) { + EXPECT_FLOAT_EQ(aligned_audio_buffer[1][i], + aligned_audio_buffer[0][i] * gain); + } +} + +TEST(SimdUtilsTest, ScalarMultiplyAndAccumuateTest) { + const float kResult = 2.0f; + AudioBuffer aligned_audio_buffer(kNumStereoChannels, kInputSize); + aligned_audio_buffer.Clear(); + for (size_t i = 0; i < kInputSize; ++i) { + aligned_audio_buffer[0][i] = 0.5f; + aligned_audio_buffer[1][i] = 1.0f; + } + const float gain = 2.0f; + ScalarMultiplyAndAccumulate(kInputSize, gain, &aligned_audio_buffer[0][0], + &aligned_audio_buffer[1][0]); + for (size_t i = 0; i < kInputSize; ++i) { + EXPECT_FLOAT_EQ(aligned_audio_buffer[1][i], kResult); + } +} + +TEST(SimdUtilsTest, SqrtTest) { + const std::vector<float> kNumbers{130.0f, 13.0f, 1.3f, + 0.13f, 0.013f, 0.0013f}; + AudioBuffer numbers(kNumMonoChannels, kNumbers.size()); + AudioBuffer approximate(kNumMonoChannels, kNumbers.size()); + numbers[0] = kNumbers; + const float kSqrtEpsilon = 2e-3f; + + Sqrt(kNumbers.size(), numbers[0].begin(), approximate[0].begin()); + + for (size_t i = 0; i < kNumbers.size(); ++i) { + const float actual = std::sqrt(kNumbers[i]); + EXPECT_LT(std::abs(actual - approximate[0][i]) / actual, kSqrtEpsilon); + } +} + +TEST(SimdUtilsTest, ReciprocalSqrtTest) { + const std::vector<float> kNumbers{130.0f, 13.0f, 1.3f, + 0.13f, 0.013f, 0.0013f}; + AudioBuffer numbers(kNumMonoChannels, kNumbers.size()); + AudioBuffer sqrt(kNumMonoChannels, kNumbers.size()); + AudioBuffer recip_sqrt(kNumMonoChannels, kNumbers.size()); + + Sqrt(kNumbers.size(), numbers[0].begin(), sqrt[0].begin()); + ReciprocalSqrt(kNumbers.size(), numbers[0].begin(), recip_sqrt[0].begin()); + + for (size_t i = 0; i < kNumbers.size(); ++i) { + EXPECT_FLOAT_EQ(1.0f / recip_sqrt[0][i], sqrt[0][i]); + } +} + +// Tests that the correct complex magnitudes are calculated for a range of +// complex numbers with both positive and negative imaginary part. +TEST(SimdUtilsTest, ApproxComplexMagnitudeTest) { + const size_t kFramesPerBuffer = 17; + // Check that we are correct to within 0.5% of each value. + const float kErrEpsilon = 5e-3f; + AudioBuffer complex_buffer(kNumMonoChannels, 2 * kFramesPerBuffer); + for (size_t i = 0; i < kFramesPerBuffer; ++i) { + const size_t j = 2 * i; + complex_buffer[0][j] = static_cast<float>(i); + complex_buffer[0][j + 1] = ((i % 2) ? -1.0f : 1.0f) * static_cast<float>(i); + } + AudioBuffer magnitude_buffer(kNumMonoChannels, kFramesPerBuffer); + + ApproxComplexMagnitude(kFramesPerBuffer, complex_buffer[0].begin(), + magnitude_buffer[0].begin()); + + for (size_t sample = 0; sample < kFramesPerBuffer; ++sample) { + const float expected = static_cast<float>(sample) * kSqrtTwo; + // Check its correct to within 0.5%. + EXPECT_NEAR(magnitude_buffer[0][sample], expected, kErrEpsilon * expected); + } +} + +// Tests that the ComplexInterleavedFormatFromMagnitudeAndSinCosPhase() method +// correctly recovers the frequency response from magnitude and phase. +TEST(SimdUtilsTest, ComplexInterleavedFormatFromMagnitudeAndSinCosPhaseTest) { + // The folowing vectors contain the inverse sines and cosines of the numbers + // 0 to 0.75 in steps of 0.05 (calculated in MATLAB). + const size_t kLength = 16; + AudioBuffer cos_vec(kNumMonoChannels, kLength); + cos_vec[0] = {1.5708f, 1.5208f, 1.4706f, 1.4202f, 1.3694f, 1.3181f, + 1.2661f, 1.2132f, 1.1593f, 1.1040f, 1.0472f, 0.9884f, + 0.9273f, 0.8632f, 0.7954f, 0.7227f}; + AudioBuffer sin_vec(kNumMonoChannels, kLength); + sin_vec[0] = {0.0000f, 0.0500f, 0.1002f, 0.1506f, 0.2014f, 0.2527f, + 0.3047f, 0.3576f, 0.4115f, 0.4668f, 0.5236f, 0.5824f, + 0.6435f, 0.7076f, 0.7754f, 0.8481f}; + const float kMagnitude = 10.0f; + AudioBuffer magnitude(kNumMonoChannels, kLength); + std::fill(magnitude[0].begin(), magnitude[0].end(), kMagnitude); + const size_t output_size = 2 * sin_vec.num_frames(); + AudioBuffer output(kNumMonoChannels, output_size); + output.Clear(); + + ComplexInterleavedFormatFromMagnitudeAndSinCosPhase( + output_size, &magnitude[0][0], &cos_vec[0][0], &sin_vec[0][0], + &output[0][0]); + + for (size_t i = 0, j = 0; i < output_size; i += 2, ++j) { + EXPECT_FLOAT_EQ(output[0][i], kMagnitude * cos_vec[0][j]); + EXPECT_FLOAT_EQ(output[0][i + 1], kMagnitude * sin_vec[0][j]); + } +} + +TEST(SimdUtilsTest, StereoMonoTest) { + const float kResult = 2.0f / std::sqrt(2.0f); + AudioBuffer aligned_audio_buffer(kNumTestChannels, kInputSize); + aligned_audio_buffer.Clear(); + for (size_t i = 0; i < kInputSize; ++i) { + aligned_audio_buffer[0][i] = 1.0f; + aligned_audio_buffer[1][i] = 1.0f; + } + MonoFromStereoSimd(kInputSize, &aligned_audio_buffer[0][0], + &aligned_audio_buffer[1][0], &aligned_audio_buffer[2][0]); + for (size_t i = 0; i < kInputSize; ++i) { + EXPECT_FLOAT_EQ(aligned_audio_buffer[2][i], kResult); + } + // Perform inverse operation. + StereoFromMonoSimd(kInputSize, &aligned_audio_buffer[2][0], + &aligned_audio_buffer[0][0], &aligned_audio_buffer[1][0]); + for (size_t i = 0; i < kInputSize; ++i) { + EXPECT_FLOAT_EQ(aligned_audio_buffer[0][i], 1.0f); + EXPECT_FLOAT_EQ(aligned_audio_buffer[1][i], 1.0f); + } +} + +TEST(SimdUtilsTest, InterleaveAlignedInt16Test) { + AudioBuffer::AlignedInt16Vector interleaved(kFullSize); + AudioBuffer::AlignedInt16Vector channel_0(kHalfSize); + AudioBuffer::AlignedInt16Vector channel_1(kHalfSize); + + // Fill the aligned input buffer. + for (size_t i = 0; i < kHalfSize; ++i) { + channel_0[i] = kOne; + channel_1[i] = kTwo; + } + + InterleaveStereo(kHalfSize, channel_0.data(), channel_1.data(), + interleaved.data()); + + for (size_t i = 0; i < kFullSize; ++i) { + const int16_t value = (i % 2 == 0) ? kOne : kTwo; + EXPECT_EQ(interleaved[i], value); + } +} + +TEST(SimdUtilsTest, InterleaveUnalignedInt16Test) { + AudioBuffer::AlignedInt16Vector interleaved(kFullSize); + AudioBuffer::AlignedInt16Vector channel_0(kHalfSize); + AudioBuffer::AlignedInt16Vector channel_1(kHalfSize); + + // Fill the aligned input buffer. + for (size_t i = 0; i < kHalfSize; ++i) { + channel_0[i] = kOne; + channel_1[i] = kTwo; + } + + InterleaveStereo(kHalfSize, channel_0.data(), channel_1.data(), + interleaved.data()); + + for (size_t i = 0; i < kFullSize; ++i) { + const int16_t value = (i % 2 == 0) ? kOne : kTwo; + EXPECT_EQ(interleaved[i], value); + } +} + +TEST(SimdUtilsTest, DeinterleaveAlignedInt16Test) { + AudioBuffer::AlignedInt16Vector interleaved(kFullSize); + AudioBuffer::AlignedInt16Vector channel_0(kHalfSize); + AudioBuffer::AlignedInt16Vector channel_1(kHalfSize); + + // Fill the aligned input buffer. + for (size_t i = 0; i < kFullSize; ++i) { + interleaved[i] = kInterleavedInput[i]; + } + + // Clear the output buffers. + for (size_t i = 0; i < kHalfSize; ++i) { + channel_0[i] = static_cast<int16_t>(0); + channel_1[i] = static_cast<int16_t>(0); + } + + // Test the case where input is aligned. + DeinterleaveStereo(kHalfSize, interleaved.data(), channel_0.data(), + channel_1.data()); + for (size_t i = 0; i < kHalfSize; ++i) { + EXPECT_EQ(channel_0[i], kOne); + EXPECT_EQ(channel_1[i], kTwo); + } +} + +TEST(SimdUtilsTest, DeinterleaveUnalignedInt16Test) { + AudioBuffer::AlignedInt16Vector channel_0(kHalfSize); + AudioBuffer::AlignedInt16Vector channel_1(kHalfSize); + + // Clear the output buffers. + for (size_t i = 0; i < kHalfSize; ++i) { + channel_0[i] = static_cast<int16_t>(0); + channel_1[i] = static_cast<int16_t>(0); + } + + // Test the case where input is unaligned. + DeinterleaveStereo(kHalfSize, kInterleavedInput, channel_0.data(), + channel_1.data()); + for (size_t i = 0; i < kHalfSize; ++i) { + EXPECT_EQ(channel_0[i], kOne); + EXPECT_EQ(channel_1[i], kTwo); + } +} + +TEST(SimdUtilsTest, DeinterleaveAlignedInt16ConvertToFloatTest) { + AudioBuffer::AlignedInt16Vector interleaved(kFullSize); + AudioBuffer planar(kNumStereoChannels, kHalfSize); + AudioBuffer::Channel& channel_0 = planar[0]; + AudioBuffer::Channel& channel_1 = planar[1]; + + // Fill the aligned input buffer. + for (size_t i = 0; i < kFullSize; ++i) { + interleaved[i] = i % 2 ? kMin : kMax; + } + + // Clear the output buffers. + planar.Clear(); + + // Test the case where input is aligned. + DeinterleaveStereo(kHalfSize, interleaved.data(), channel_0.begin(), + channel_1.begin()); + for (size_t i = 0; i < kHalfSize; ++i) { + EXPECT_NEAR(channel_0[i], 1.0f, kEpsilonFloat); + EXPECT_NEAR(channel_1[i], -1.0f, kEpsilonFloat); + } +} + +TEST(SimdUtilsTest, DeinterleaveUnalignedInt16ConvertToFloatTest) { + int16_t interleaved[kFullSize]; + AudioBuffer planar(kNumStereoChannels, kHalfSize); + AudioBuffer::Channel& channel_0 = planar[0]; + AudioBuffer::Channel& channel_1 = planar[1]; + + // Fill the unaligned input buffer. + for (size_t i = 0; i < kFullSize; ++i) { + interleaved[i] = i % 2 ? kMin : kMax; + } + + // Clear the output buffers. + planar.Clear(); + + // Test the case where input is unaligned. + DeinterleaveStereo(kHalfSize, interleaved, channel_0.begin(), + channel_1.begin()); + + for (size_t i = 0; i < kHalfSize; ++i) { + EXPECT_NEAR(channel_0[i], 1.0f, kEpsilonFloat); + EXPECT_NEAR(channel_1[i], -1.0f, kEpsilonFloat); + } +} + +TEST(SimdUtilsTest, InterleaveAlignedFloatTest) { + AudioBuffer interleaved(kNumMonoChannels, kFullSize); + AudioBuffer planar(kNumStereoChannels, kHalfSize); + AudioBuffer::Channel& channel_0 = planar[0]; + AudioBuffer::Channel& channel_1 = planar[1]; + + // Fill the aligned input buffer. + for (size_t i = 0; i < kHalfSize; ++i) { + channel_0[i] = 1.0f; + channel_1[i] = 2.0f; + } + + InterleaveStereo(kHalfSize, channel_0.begin(), channel_1.begin(), + interleaved[0].begin()); + + for (size_t i = 0; i < kFullSize; ++i) { + const float value = (i % 2 == 0) ? 1.0f : 2.0f; + EXPECT_FLOAT_EQ(interleaved[0][i], value); + } +} + +TEST(SimdUtilsTest, InterleaveUnalignedFloatTest) { + float interleaved[kFullSize]; + AudioBuffer planar(kNumStereoChannels, kHalfSize); + AudioBuffer::Channel& channel_0 = planar[0]; + AudioBuffer::Channel& channel_1 = planar[1]; + + // Fill the aligned input buffer. + for (size_t i = 0; i < kHalfSize; ++i) { + channel_0[i] = 1.0f; + channel_1[i] = 2.0f; + } + + InterleaveStereo(kHalfSize, channel_0.begin(), channel_1.begin(), + interleaved); + + for (size_t i = 0; i < kFullSize; ++i) { + const float value = (i % 2 == 0) ? 1.0f : 2.0f; + EXPECT_FLOAT_EQ(interleaved[i], value); + } +} + +TEST(SimdUtilsTest, InterleaveAlignedFloatConvertToInt16Test) { + AudioBuffer::AlignedInt16Vector interleaved(kFullSize); + AudioBuffer planar(kNumStereoChannels, kHalfSize); + AudioBuffer::Channel& channel_0 = planar[0]; + AudioBuffer::Channel& channel_1 = planar[1]; + + // Fill the aligned input buffer. + for (size_t i = 0; i < kHalfSize; ++i) { + channel_0[i] = 1.0f; + channel_1[i] = -1.0f; + } + + InterleaveStereo(kHalfSize, channel_0.begin(), channel_1.begin(), + interleaved.data()); + + for (size_t i = 0; i < kFullSize; ++i) { + const int16_t value = i % 2 ? kMin : kMax; + EXPECT_NEAR(interleaved[i], value, kIntEpsilon); + } +} + +TEST(SimdUtilsTest, InterleaveUnalignedFloatConvertToInt16Test) { + int16_t interleaved[kFullSize]; + AudioBuffer planar(kNumStereoChannels, kHalfSize); + AudioBuffer::Channel& channel_0 = planar[0]; + AudioBuffer::Channel& channel_1 = planar[1]; + + // Fill the aligned input buffer. + for (size_t i = 0; i < kHalfSize; ++i) { + channel_0[i] = 1.0f; + channel_1[i] = -1.0f; + } + + InterleaveStereo(kHalfSize, channel_0.begin(), channel_1.begin(), + interleaved); + + for (size_t i = 0; i < kFullSize; ++i) { + const int16_t value = i % 2 ? kMin : kMax; + EXPECT_NEAR(interleaved[i], value, kIntEpsilon); + } +} + +TEST(SimdUtilsTest, DeinterleaveAlignedFloatTest) { + AudioBuffer interleaved(kNumMonoChannels, kFullSize); + AudioBuffer planar(kNumStereoChannels, kHalfSize); + AudioBuffer::Channel& channel_0 = planar[0]; + AudioBuffer::Channel& channel_1 = planar[1]; + + // Fill the aligned input buffer. + for (size_t i = 0; i < kFullSize; ++i) { + interleaved[0][i] = (i % 2 == 0) ? 1.0f : 2.0f; + } + + // Clear the output buffers. + planar.Clear(); + + // Test the case where input is aligned. + DeinterleaveStereo(kHalfSize, interleaved[0].begin(), channel_0.begin(), + channel_1.begin()); + for (size_t i = 0; i < kHalfSize; ++i) { + EXPECT_FLOAT_EQ(channel_0[i], 1.0f); + EXPECT_FLOAT_EQ(channel_1[i], 2.0f); + } +} + +TEST(SimdUtilsTest, DeinterleaveUnalignedFloatTest) { + float interleaved[kFullSize]; + AudioBuffer planar(kNumStereoChannels, kHalfSize); + AudioBuffer::Channel& channel_0 = planar[0]; + AudioBuffer::Channel& channel_1 = planar[1]; + + // Fill the aligned input buffer. + for (size_t i = 0; i < kFullSize; ++i) { + interleaved[i] = (i % 2 == 0) ? 1.0f : 2.0f; + } + + // Clear the output buffers. + planar.Clear(); + + // Test the case where input is unaligned. + DeinterleaveStereo(kHalfSize, interleaved, channel_0.begin(), + channel_1.begin()); + for (size_t i = 0; i < kHalfSize; ++i) { + EXPECT_FLOAT_EQ(channel_0[i], 1.0f); + EXPECT_FLOAT_EQ(channel_1[i], 2.0f); + } +} + +TEST(SimdUtilsTest, InterleaveQuadInt16Test) { + AudioBuffer::AlignedInt16Vector interleaved(kQuadSize); + AudioBuffer::AlignedInt16Vector workspace(kPentSize); + AudioBuffer::AlignedInt16Vector channel_0(kHalfSize); + AudioBuffer::AlignedInt16Vector channel_1(kHalfSize); + AudioBuffer::AlignedInt16Vector channel_2(kHalfSize); + AudioBuffer::AlignedInt16Vector channel_3(kHalfSize); + + // Fill the aligned input buffer. + for (size_t i = 0; i < kHalfSize; ++i) { + channel_0[i] = kOne; + channel_1[i] = kTwo; + channel_2[i] = kThree; + channel_3[i] = kFour; + } + + InterleaveQuad(kHalfSize, channel_0.data(), channel_1.data(), + channel_2.data(), channel_3.data(), workspace.data(), + interleaved.data()); + + for (size_t i = 0; i < kQuadSize; ++i) { + const int16_t value = static_cast<int16_t>(1 + (i % kNumQuadChannels)); + EXPECT_FLOAT_EQ(interleaved[i], value); + } +} + +TEST(SimdUtilsTest, DeinterleaveQuadInt16Test) { + AudioBuffer::AlignedInt16Vector interleaved(kQuadSize); + AudioBuffer::AlignedInt16Vector workspace(kPentSize); + AudioBuffer::AlignedInt16Vector channel_0(kHalfSize); + AudioBuffer::AlignedInt16Vector channel_1(kHalfSize); + AudioBuffer::AlignedInt16Vector channel_2(kHalfSize); + AudioBuffer::AlignedInt16Vector channel_3(kHalfSize); + + // Fill the aligned input buffer. + for (size_t i = 0; i < kQuadSize; ++i) { + interleaved[i] = static_cast<int16_t>(1 + (i % kNumQuadChannels)); + } + + // Clear the output buffers. + for (size_t i = 0; i < kHalfSize; ++i) { + channel_0[i] = static_cast<int16_t>(0); + channel_1[i] = static_cast<int16_t>(0); + channel_2[i] = static_cast<int16_t>(0); + channel_3[i] = static_cast<int16_t>(0); + } + + // Test the case where input is aligned. + DeinterleaveQuad(kHalfSize, interleaved.data(), workspace.data(), + channel_0.data(), channel_1.data(), channel_2.data(), + channel_3.data()); + + for (size_t i = 0; i < kHalfSize; ++i) { + EXPECT_EQ(channel_0[i], kOne); + EXPECT_EQ(channel_1[i], kTwo); + EXPECT_EQ(channel_2[i], kThree); + EXPECT_EQ(channel_3[i], kFour); + } +} + +TEST(SimdUtilsTest, InterleaveQuadFloatTest) { + AudioBuffer interleaved(kNumMonoChannels, kQuadSize); + AudioBuffer workspace(kNumMonoChannels, kPentSize); + AudioBuffer planar(kNumQuadChannels, kHalfSize); + AudioBuffer::Channel& channel_0 = planar[0]; + AudioBuffer::Channel& channel_1 = planar[1]; + AudioBuffer::Channel& channel_2 = planar[2]; + AudioBuffer::Channel& channel_3 = planar[3]; + + // Fill the aligned input buffer. + for (size_t i = 0; i < kHalfSize; ++i) { + channel_0[i] = 1.0f; + channel_1[i] = 2.0f; + channel_2[i] = 3.0f; + channel_3[i] = 4.0f; + } + + InterleaveQuad(kHalfSize, channel_0.begin(), channel_1.begin(), + channel_2.begin(), channel_3.begin(), workspace[0].begin(), + interleaved[0].begin()); + + for (size_t i = 0; i < kQuadSize; ++i) { + const float value = static_cast<float>(1 + (i % kNumQuadChannels)); + EXPECT_FLOAT_EQ(interleaved[0][i], value); + } +} + +TEST(SimdUtilsTest, DeinterleaveQuadFloatTest) { + AudioBuffer interleaved(kNumMonoChannels, kQuadSize); + AudioBuffer workspace(kNumMonoChannels, kPentSize); + AudioBuffer planar(kNumQuadChannels, kHalfSize); + AudioBuffer::Channel& channel_0 = planar[0]; + AudioBuffer::Channel& channel_1 = planar[1]; + AudioBuffer::Channel& channel_2 = planar[2]; + AudioBuffer::Channel& channel_3 = planar[3]; + + // Fill the aligned input buffer. + for (size_t i = 0; i < kQuadSize; ++i) { + interleaved[0][i] = static_cast<float>(1 + (i % kNumQuadChannels)); + } + + // Clear the output buffers. + planar.Clear(); + + // Test the case where input is aligned. + DeinterleaveQuad(kHalfSize, interleaved[0].begin(), workspace[0].begin(), + channel_0.begin(), channel_1.begin(), channel_2.begin(), + channel_3.begin()); + + for (size_t i = 0; i < kHalfSize; ++i) { + EXPECT_FLOAT_EQ(channel_0[i], 1.0f); + EXPECT_FLOAT_EQ(channel_1[i], 2.0f); + EXPECT_FLOAT_EQ(channel_2[i], 3.0f); + EXPECT_FLOAT_EQ(channel_3[i], 4.0f); + } +} + +TEST(SimdUtilsTest, Int16FromFloatTest) { + AudioBuffer float_buffer(kNumMonoChannels, kInputSize); + float_buffer.Clear(); + + AudioBuffer::AlignedInt16Vector int_buffer(kInputSize); + + for (size_t i = 0; i < kInputSize; ++i) { + float_buffer[0][i] = kFloatInput[i]; + } + + Int16FromFloat(kInputSize, &(float_buffer[0][0]), int_buffer.data()); + + for (size_t i = 0; i < kInputSize; ++i) { + EXPECT_NEAR(int_buffer[i], kIntInput[i], kIntEpsilon); + } +} + +TEST(SimdUtilsTest, FloatFromInt16Test) { + AudioBuffer float_buffer(kNumMonoChannels, kInputSize); + float_buffer.Clear(); + + AudioBuffer::AlignedInt16Vector int_buffer(kInputSize); + + for (size_t i = 0; i < kInputSize; ++i) { + int_buffer[i] = static_cast<int16_t>(kIntInput[i]); + } + + FloatFromInt16(kInputSize, int_buffer.data(), &(float_buffer[0][0])); + + for (size_t i = 0; i < kInputSize; i += 2) { + EXPECT_NEAR(float_buffer[0][i], kFloatInput[i], kFloatEpsilon); + } +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/base/source_parameters.h b/src/3rdparty/resonance-audio/resonance_audio/base/source_parameters.h new file mode 100644 index 000000000..bf24a424e --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/base/source_parameters.h @@ -0,0 +1,101 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_BASE_SOURCE_PARAMETERS_H_ +#define RESONANCE_AUDIO_BASE_SOURCE_PARAMETERS_H_ + +#include "api/resonance_audio_api.h" + +#include "base/constants_and_types.h" +#include "base/object_transform.h" + +namespace vraudio { + +// Gain attenuation types for audio sources. +enum AttenuationType { + kInput = 0, + kDirect, + kReflections, + kReverb, + kNumAttenuationTypes +}; + +// Parameters describing an audio source. +struct SourceParameters { + // Object transform associated with this buffer. + ObjectTransform object_transform; + + // Angular spread in degrees. Range [0, 360]. + float spread_deg = 0.0f; + + // Source gain factor. + float gain = 1.0f; + + // Source gain attenuation factors to be calculated per each buffer. + float attenuations[kNumAttenuationTypes]; + + // Distance attenuation. Value 1 represents no attenuation should be applied, + // value 0 will fully attenuate the volume. Range [0, 1]. + float distance_attenuation = 1.0f; + + // Distance attenuation rolloff model to use. + DistanceRolloffModel distance_rolloff_model = + DistanceRolloffModel::kLogarithmic; + + // Minimum distance at which to apply distance attenuation. + float minimum_distance = 0.0f; + + // Maximum distance at which to apply distance attenuation. + float maximum_distance = 500.0f; + + // Alpha weighting of source's directivity pattern. This sets the balance + // between the dipole and omnidirectional directivity patterns which combine + // to produce the single directivity output value. Range [0, 1], where 0 is + // fully omnidirectional and 1 is fully dipole. + float directivity_alpha = 0.0f; + + // Source directivity order. Increasing this value increases the directivity + // towards the front of the source. Range [1, inf). + float directivity_order = 1.0f; + + // Alpha weighting of listener's directivity pattern. This sets the balance + // between the dipole and omnidirectional pickup patterns which combine to + // produce the single output value. Range [0, 1], where 0 is fully + // omnidirectional and 1 is fully dipole. + float listener_directivity_alpha = 0.0f; + + // Listener directivity order. Increasing this value increases the directivity + // towards the front of the listener. Range [1, inf). + float listener_directivity_order = 1.0f; + + // Occlusion intensity. Value 0 represents no occlusion, values greater than 1 + // represent multiple occlusions. The intensity of each occlusion is scaled + // in range [0, 1]. + float occlusion_intensity = 0.0f; + + // Near field effect gain. Range [0, 9]. + float near_field_gain = 0.0f; + + // Source gain factor for the room effects. + float room_effects_gain = 1.0f; + + // Whether the source uses binaural rendering or stereo panning. + bool enable_hrtf = true; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_BASE_SOURCE_PARAMETERS_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/base/spherical_angle.cc b/src/3rdparty/resonance-audio/resonance_audio/base/spherical_angle.cc new file mode 100644 index 000000000..4b028979d --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/base/spherical_angle.cc @@ -0,0 +1,78 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "base/spherical_angle.h" + +#include <cmath> + +#include "base/constants_and_types.h" + +namespace vraudio { + +SphericalAngle::SphericalAngle(float azimuth, float elevation) + : azimuth_(azimuth), elevation_(elevation) {} + +SphericalAngle::SphericalAngle() : SphericalAngle(0.0f, 0.0f) {} + +SphericalAngle::SphericalAngle(const SphericalAngle& other) + : azimuth_(other.azimuth_), elevation_(other.elevation_) {} + +SphericalAngle& SphericalAngle::operator=(const SphericalAngle other) { + if (&other == this) { + return *this; + } + this->azimuth_ = other.azimuth_; + this->elevation_ = other.elevation_; + return *this; +} + +SphericalAngle SphericalAngle::FromWorldPosition( + const WorldPosition& world_position) { + return SphericalAngle( + std::atan2(-world_position[0], -world_position[2]), + std::atan2(world_position[1], + std::sqrt(world_position[0] * world_position[0] + + world_position[2] * world_position[2]))); +} + +SphericalAngle SphericalAngle::FromDegrees(float azimuth_degrees, + float elevation_degrees) { + return SphericalAngle(azimuth_degrees * kRadiansFromDegrees, + elevation_degrees * kRadiansFromDegrees); +} + +SphericalAngle SphericalAngle::FlipAzimuth() const { + return SphericalAngle(-azimuth_, elevation_); +} + +WorldPosition SphericalAngle::GetWorldPositionOnUnitSphere() const { + return WorldPosition(-std::cos(elevation_) * std::sin(azimuth_), + std::sin(elevation_), + -std::cos(elevation_) * std::cos(azimuth_)); +} + +SphericalAngle SphericalAngle::Rotate(const WorldRotation& rotation) const { + const WorldPosition original_world_position = GetWorldPositionOnUnitSphere(); + const WorldPosition rotated_world_position = + rotation * original_world_position; + return FromWorldPosition(rotated_world_position); +} + +bool SphericalAngle::operator==(const SphericalAngle& other) const { + return (azimuth_ == other.azimuth_) && (elevation_ == other.elevation_); +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/base/spherical_angle.h b/src/3rdparty/resonance-audio/resonance_audio/base/spherical_angle.h new file mode 100644 index 000000000..4559f19d1 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/base/spherical_angle.h @@ -0,0 +1,87 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_BASE_SPHERICAL_ANGLE_H_ +#define RESONANCE_AUDIO_BASE_SPHERICAL_ANGLE_H_ + +#include "base/misc_math.h" + +namespace vraudio { + +// Represents angular position on a sphere in terms of azimuth and elevation. +class SphericalAngle { + public: + // Constructs a spherical angle with the given azimuth and elevation. + SphericalAngle(float azimuth, float elevation); + + // Constructs a default spherical angle (azimuth = 0, elevation = 0). + SphericalAngle(); + + // Constructs a spherical angle from a given one. + SphericalAngle(const SphericalAngle& other); + + SphericalAngle& operator=(const SphericalAngle other); + + // Returns a spherical angle representation of given |world_position| (World + // Space). + // + // @param world_position 3D position in world space. + // @return Spherical angle that represents the |world_position|. + static SphericalAngle FromWorldPosition(const WorldPosition& world_position); + + // Returns a spherical angle from azimuth and elevation in degrees. + static SphericalAngle FromDegrees(float azimuth_degrees, + float elevation_degrees); + + // Returns another spherical angle with the same elevation but the azimuth + // sign flipped. + // + // @return Horizontally flipped version of the spherical angle. + SphericalAngle FlipAzimuth() const; + + // Returns the |WorldPosition| coordinates (World Space) on the unit sphere + // corresponding to this spherical angle. The transformation is + // defined as such: + // x = -cos(elevation) * sin(azimuth) + // y = sin(elevation) + // z = -cos(elevation) * cos(azimuth) + // + // @return 3D position in world space. + WorldPosition GetWorldPositionOnUnitSphere() const; + + // Returns the rotated version of the spherical angle using given + // |WorldRotation|. + // + // @param rotation Rotation to be applied to the spherical angle. + // @return Rotated version of the spherical angle. + SphericalAngle Rotate(const WorldRotation& rotation) const; + + void set_azimuth(float azimuth) { azimuth_ = azimuth; } + void set_elevation(float elevation) { elevation_ = elevation; } + + float azimuth() const { return azimuth_; } + float elevation() const { return elevation_; } + + bool operator==(const SphericalAngle& other) const; + + private: + float azimuth_; + float elevation_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_BASE_SPHERICAL_ANGLE_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/base/spherical_angle_test.cc b/src/3rdparty/resonance-audio/resonance_audio/base/spherical_angle_test.cc new file mode 100644 index 000000000..49ee16d5d --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/base/spherical_angle_test.cc @@ -0,0 +1,122 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "base/spherical_angle.h" + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/constants_and_types.h" + +namespace vraudio { + +namespace { + +// Spherical angle to be used in the rotation tests. +const float kAzimuth = 0.0f; +const float kElevation = 0.0f; +const SphericalAngle kSphericalAngle(0.0f, 0.0f); + +// Arbitrary rotation angle to be used in the rotation tests. +const float kRotationAngle = 10.0f * kRadiansFromDegrees; + +// Tests that the GetWorldPositionOnUnitSphere() and FromWorldPosition() +// functions act as perfect inverses of one another for angles defined on the +// unit sphere (in this case the vraudio cube speaker layout). +TEST(SphericalAngleTest, CartesianToSphericalAndBackTest) { + // Azimuth and elevation angles of the cubic spherical loudspeaker array. + const std::vector<SphericalAngle> kCubeAngles = { + SphericalAngle::FromDegrees(45.0f, 35.26f), + SphericalAngle::FromDegrees(-45.0f, 35.26f), + SphericalAngle::FromDegrees(-135.0f, 35.26f), + SphericalAngle::FromDegrees(135.0f, 35.26f), + SphericalAngle::FromDegrees(45.0f, -35.26f), + SphericalAngle::FromDegrees(-45.0f, -35.26f), + SphericalAngle::FromDegrees(-135.0f, -35.26f), + SphericalAngle::FromDegrees(135.0f, -35.26f)}; + + for (size_t i = 0; i < kCubeAngles.size(); ++i) { + const WorldPosition position = + kCubeAngles[i].GetWorldPositionOnUnitSphere(); + const SphericalAngle angle = SphericalAngle::FromWorldPosition(position); + EXPECT_EQ(kCubeAngles[i].azimuth(), angle.azimuth()); + EXPECT_EQ(kCubeAngles[i].elevation(), angle.elevation()); + } +} + +// Tests the horizontal angle flip across the median plane. +TEST(SphericalAngleTest, FlipTest) { + const std::vector<SphericalAngle> kTestAngles = { + SphericalAngle::FromDegrees(45.0f, 35.26f), + SphericalAngle::FromDegrees(-15.0f, -10.0f)}; + + for (size_t i = 0; i < kTestAngles.size(); ++i) { + SphericalAngle flipped_spherical_angle = kTestAngles[i].FlipAzimuth(); + + // Check if the flipped spherical anglee is correct. + EXPECT_NEAR(kTestAngles[i].azimuth(), -flipped_spherical_angle.azimuth(), + kEpsilonFloat); + EXPECT_NEAR((kTestAngles[i].elevation()), + flipped_spherical_angle.elevation(), kEpsilonFloat); + } +} + +// Tests that the Rotate() function correctly rotates the spherical angle +// against the x axis (right facing). +TEST(SphericalAngleTest, RotateXTest) { + const WorldPosition kAxis = {1.0f, 0.0f, 0.0f}; + const WorldRotation kRotation(AngleAxisf(kRotationAngle, kAxis)); + // Rotate against the x axis (right facing). + + const SphericalAngle kXrotatedSphericalAngle = + kSphericalAngle.Rotate(kRotation); + + // Check if the rotated spherical angle is correct. + EXPECT_NEAR(kAzimuth, kXrotatedSphericalAngle.azimuth(), kEpsilonFloat); + EXPECT_NEAR((kElevation + kRotationAngle), + kXrotatedSphericalAngle.elevation(), kEpsilonFloat); +} + +// Tests that the Rotate() function correctly rotates the spherical angle +// against the y axis (upward facing). +TEST(SphericalAngleTest, RotateYTest) { + const WorldPosition kAxis(0.0f, 1.0f, 0.0f); + const WorldRotation kRotation(AngleAxisf(kRotationAngle, kAxis)); + // Rotate against the y axis (upward facing). + const SphericalAngle kYrotatedSphericalAngle = + kSphericalAngle.Rotate(kRotation); + + // Check if the rotated spherical angle is correct. + EXPECT_NEAR((kAzimuth + kRotationAngle), kYrotatedSphericalAngle.azimuth(), + kEpsilonFloat); + EXPECT_NEAR(kElevation, kYrotatedSphericalAngle.elevation(), kEpsilonFloat); +} + +// Tests that the Rotate() function correctly rotates the spherical angle +// against the Z axis (forward facing). +TEST(SphericalAngleTest, RotateZTest) { + const WorldPosition kAxis = {0.0f, 0.0f, 1.0f}; + const WorldRotation kRotation(AngleAxisf(kRotationAngle, kAxis)); + // Rotate against the z axis (forward facing). + const SphericalAngle kZrotatedSphericalAngle = + kSphericalAngle.Rotate(kRotation); + + // Check if the rotated spherical angle is correct. + EXPECT_NEAR(kAzimuth, kZrotatedSphericalAngle.azimuth(), kEpsilonFloat); + EXPECT_NEAR(kElevation, kZrotatedSphericalAngle.elevation(), kEpsilonFloat); +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/base/unique_ptr_wrapper.h b/src/3rdparty/resonance-audio/resonance_audio/base/unique_ptr_wrapper.h new file mode 100644 index 000000000..b8e848135 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/base/unique_ptr_wrapper.h @@ -0,0 +1,33 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_BASE_UNIQUE_PTR_WRAPPER_H_ +#define RESONANCE_AUDIO_BASE_UNIQUE_PTR_WRAPPER_H_ + +#include <memory> + +// Wrapper around std::unique_ptr to enable the binding of unique_ptr buffers to +// std:function and/or lambda function. +template <typename T> +struct UniquePtrWrapper { + UniquePtrWrapper(const UniquePtrWrapper& other) : ptr(std::move(other.ptr)) {} + UniquePtrWrapper(UniquePtrWrapper&& other) : ptr(std::move(other.ptr)) {} + explicit UniquePtrWrapper(std::unique_ptr<T> buffer) + : ptr(std::move(buffer)) {} + mutable std::unique_ptr<T> ptr; +}; + +#endif // RESONANCE_AUDIO_BASE_UNIQUE_PTR_WRAPPER_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/config/global_config.h b/src/3rdparty/resonance-audio/resonance_audio/config/global_config.h new file mode 100644 index 000000000..4af629e89 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/config/global_config.h @@ -0,0 +1,37 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_CONFIG_GLOBAL_CONFIG_H_ +#define RESONANCE_AUDIO_CONFIG_GLOBAL_CONFIG_H_ + +#include "graph/graph_manager_config.h" + +namespace vraudio { + +inline GraphManagerConfig GlobalConfig() { + GraphManagerConfig config; + config.configuration_name = "Global Config"; + + config.max_ambisonic_order = 3; + config.sh_hrir_filenames = {{1, "WAV/Subject_002/SH/sh_hrir_order_1.wav"}, + {2, "WAV/Subject_002/SH/sh_hrir_order_2.wav"}, + {3, "WAV/Subject_002/SH/sh_hrir_order_3.wav"}}; + return config; +} + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_CONFIG_GLOBAL_CONFIG_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/config/source_config.cc b/src/3rdparty/resonance-audio/resonance_audio/config/source_config.cc new file mode 100644 index 000000000..a8e8c75f7 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/config/source_config.cc @@ -0,0 +1,76 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "config/source_config.h" + +namespace vraudio { + +SourceGraphConfig StereoPanningConfig() { + SourceGraphConfig config; + config.configuration_name = "Stereo Panning"; + + config.ambisonic_order = 1; + config.enable_hrtf = false; + config.enable_direct_rendering = true; + + return config; +} + +SourceGraphConfig BinauralLowQualityConfig() { + SourceGraphConfig config; + config.configuration_name = "Binaural Low Quality"; + + config.ambisonic_order = 1; + config.enable_hrtf = true; + config.enable_direct_rendering = true; + + return config; +} + +SourceGraphConfig BinauralMediumQualityConfig() { + SourceGraphConfig config; + config.configuration_name = "Binaural Medium Quality"; + + config.ambisonic_order = 2; + config.enable_hrtf = true; + config.enable_direct_rendering = true; + + return config; +} + +SourceGraphConfig BinauralHighQualityConfig() { + SourceGraphConfig config; + config.configuration_name = "Binaural High Quality"; + + config.ambisonic_order = 3; + config.enable_hrtf = true; + config.enable_direct_rendering = true; + + return config; +} + +SourceGraphConfig RoomEffectsOnlyConfig() { + SourceGraphConfig config; + config.configuration_name = "Room Effects Only"; + + config.ambisonic_order = 1; + config.enable_hrtf = false; + config.enable_direct_rendering = false; + + return config; +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/config/source_config.h b/src/3rdparty/resonance-audio/resonance_audio/config/source_config.h new file mode 100644 index 000000000..21b71fc0b --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/config/source_config.h @@ -0,0 +1,32 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_CONFIG_SOURCE_CONFIG_H_ +#define RESONANCE_AUDIO_CONFIG_SOURCE_CONFIG_H_ + +#include "graph/source_graph_config.h" + +namespace vraudio { + +SourceGraphConfig StereoPanningConfig(); +SourceGraphConfig BinauralLowQualityConfig(); +SourceGraphConfig BinauralMediumQualityConfig(); +SourceGraphConfig BinauralHighQualityConfig(); +SourceGraphConfig RoomEffectsOnlyConfig(); + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_CONFIG_SOURCE_CONFIG_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/biquad_filter.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/biquad_filter.cc new file mode 100644 index 000000000..ca489cce3 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/biquad_filter.cc @@ -0,0 +1,149 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/biquad_filter.h" + +#include "base/constants_and_types.h" + +namespace vraudio { + +namespace { + +// This value has been chosen empirically after a series of experiments +// performed by kellyi@ and alperg@. It was found that crossfading over 256 +// samples yielded no audible glitching artefacts and an acceptable amount of +// delay. + +const size_t kIdealSamplesToIterate = 256U; + +} // namespace + +BiquadFilter::BiquadFilter(const BiquadCoefficients& coefficients, + size_t frames_per_buffer) + : biquad_delay_line_({{0.0f, 0.0f}}), + interpolate_flag_(false), + interpolate_counter_(0), + old_delay_line_({{0.0f, 0.0f}}), + samples_to_iterate_over_(frames_per_buffer > kIdealSamplesToIterate + ? kIdealSamplesToIterate + : frames_per_buffer), + fade_scale_(1.0f / static_cast<float>(samples_to_iterate_over_)), + old_coefficients_() { + DCHECK_GT(frames_per_buffer, 0U); + CHECK_GT(coefficients_.a[0], kEpsilonFloat); + SetCoefficients(coefficients); +} + +void BiquadFilter::SetCoefficients(const BiquadCoefficients& coefficients) { + coefficients_ = coefficients; + // Normalize the coefficients for use in the |FilterSample()| function. + coefficients_.a[1] /= coefficients_.a[0]; + coefficients_.a[2] /= coefficients_.a[0]; + coefficients_.b[0] /= coefficients_.a[0]; + coefficients_.b[1] /= coefficients_.a[0]; + coefficients_.b[2] /= coefficients_.a[0]; +} + +void BiquadFilter::Filter(const AudioBuffer::Channel& input_channel, + AudioBuffer::Channel* output_channel) { + DCHECK(output_channel); + DCHECK_EQ(input_channel.size(), output_channel->size()); + + if (interpolate_flag_) { + for (size_t frame = 0; frame < input_channel.size(); ++frame) { + // Biquad coefficients are updated here. + UpdateInterpolate(); + (*output_channel)[frame] = InterpolateFilterSample(input_channel[frame]); + } + } else { + for (size_t frame = 0; frame < input_channel.size(); ++frame) { + (*output_channel)[frame] = FilterSample( + input_channel[frame], &biquad_delay_line_, coefficients_); + } + } +} + +void BiquadFilter::InterpolateToCoefficients( + const BiquadCoefficients& coefficients) { + interpolate_flag_ = true; + // Reset the counter so we perform the update over samples_to_iterate_over_ + // samples. + interpolate_counter_ = 0; + // Store the old coefficients, update the new ones and transfer data between + // delay lines. + old_coefficients_ = coefficients_; + coefficients_ = coefficients; + old_delay_line_ = biquad_delay_line_; +} + +void BiquadFilter::Clear() { + biquad_delay_line_ = {{0.0f, 0.0f}}; + interpolate_flag_ = false; + interpolate_counter_ = 0; + old_delay_line_ = {{0.0f, 0.0f}}; +} + +float BiquadFilter::FilterSample(float input, std::array<float, 2>* delay_line, + const BiquadCoefficients& coefficients) { + // Using A Direct Form II Implementation Difference equation: + // Source: Digital Signal Processing Principles Algorithms and Applications + // Fourth Edition. John G. Prolakis and Dimitris G. Manolakis - Chapter 9 + // w[n] = x[n] - (a1/a0)*w[n-1] - (a2/a0)*w[n-2] + // y(n) = (b0/a0)*w[n] + (b1/a0)*w[n-1] + (b2/a0)*w[n-2] + // where x[n] is input, w[n] is storage and y[n] is output. + // The division by a0 has been performed in Biquad::SetCoefficients. + + // define a temporary storage value w. + const float w = input - (*delay_line)[0] * coefficients.a[1] - + (*delay_line)[1] * coefficients.a[2]; + + // Do second half of the calculation to generate output. + const float output = w * coefficients.b[0] + + (*delay_line)[0] * coefficients.b[1] + + (*delay_line)[1] * coefficients.b[2]; + + // Update delay line. + (*delay_line)[1] = (*delay_line)[0]; + (*delay_line)[0] = w; + + return output; +} + +void BiquadFilter::UpdateInterpolate() { + if (++interpolate_counter_ > samples_to_iterate_over_) { + interpolate_flag_ = false; + } +} + +float BiquadFilter::InterpolateFilterSample(float input) { + const float new_filter_output = + FilterSample(input, &biquad_delay_line_, coefficients_); + + if (!interpolate_flag_) { + return new_filter_output; + } else { + // Process the "old" filter values. + const float old_filter_output = + FilterSample(input, &old_delay_line_, old_coefficients_); + // A linear crossfade between old_filter_output and new_filter_output, + // stepsize is fade_scale_. + const float weight = fade_scale_ * static_cast<float>(interpolate_counter_); + const float sample_diff = new_filter_output - old_filter_output; + return weight * sample_diff + old_filter_output; + } +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/biquad_filter.h b/src/3rdparty/resonance-audio/resonance_audio/dsp/biquad_filter.h new file mode 100644 index 000000000..2e05c3276 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/biquad_filter.h @@ -0,0 +1,137 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_DSP_BIQUAD_FILTER_H_ +#define RESONANCE_AUDIO_DSP_BIQUAD_FILTER_H_ + +#include <array> + +#include "base/audio_buffer.h" +#include "base/logging.h" + +namespace vraudio { + +// Set of transfer function coefficients. +struct BiquadCoefficients { + // Constructor takes as its arguments 6 floats representing the transfer + // function coefficients of a biquad filter. + BiquadCoefficients(float a0, float a1, float a2, float b0, float b1, float b2) + : a({{a0, a1, a2}}), b({{b0, b1, b2}}) {} + + // Default constructor that sets a0 and b0 == 1 so that if the coefficients + // are used, the filter is stable and has no effect on input. i.e. input + // appears to just pass through. + BiquadCoefficients() : BiquadCoefficients(1.0, 0.0, 0.0, 1.0, 0.0, 0.0) {} + + // The denominator quadratic coefficients. + std::array<float, 3> a; + // The numerator quadratic coefficients. + std::array<float, 3> b; +}; + +// Class representing a biquad filter as its transfer function coefficients. +// This class also performs filtering. +class BiquadFilter { + public: + // Constructs a BiquadFilter using a BiquadCoefficients struct. + // + // @param coefficients A BiquadCoefficients to set the internal + // |coefficients_|. + // @param frames_per_buffer The number of frames in each data buffer to be + // processed. This is used to set the number of samples to iterate over + // during a change of filter state. + BiquadFilter(const BiquadCoefficients& coefficients, + size_t frames_per_buffer); + + // Filter method for use with AudioBuffer::Channel. + // + // @param input_channel An AudioBuffer::Channel of input. + // @param output_channel A pointer to an AudioBuffer::Channel for output. + void Filter(const AudioBuffer::Channel& input_channel, + AudioBuffer::Channel* output_channel); + + // Sets the fiter's coefficents to those passed, with all + // coefficients bar a0 scaled by 1/a0. + // + // @param coefficients A set of BiquadCoefficients. + void SetCoefficients(const BiquadCoefficients& coefficients); + + // Sets the target coefficients to be interpolated. + // + // @param coefficents The BiquadCoefficients we wish to interpolate to over + // samples_to_iterate_over_ samples of the next input buffer. + void InterpolateToCoefficients(const BiquadCoefficients& coefficients); + + // Clears the internal state of the filter, except for coefficients. + void Clear(); + + private: + friend class BiquadFilterInterpolateTest; + + // Filters a single sample of input. + // + // @param input A single input sample from a Planar or Interleaved buffer. + // @param delay_line The delay_line to use (important for interpolation). + // @param coefficients The biquad coeffients for use in filtering. + // @return An output value to be placed in a Planar or Interleaved buffer. + float FilterSample(float input, std::array<float, 2>* delay_line, + const BiquadCoefficients& coefficients); + + // If InterpolateToState() has been called to assign new filter coefficients, + // this function will be called samples_to_iterate_over_ times within the next + // call of the Filter() function to slowly transition to the new coefficients + // which were passed to InterpolateToState() previously. + void UpdateInterpolate(); + + // Filters a single sample of input while transitioning between coefficients. + // Performs a linear crossfade over samples_to_iterate_over_ samples. + // + // @param input A single input sample from a Planar or Interleaved buffer. + // @return Output value to be placed in an audio buffer. + float InterpolateFilterSample(float input); + + // Stores the memory state of the biquad + std::array<float, 2> biquad_delay_line_; + + // Flag that denotes whether or not we are transitioning to another set of + // coefficients via interpolation. + bool interpolate_flag_; + + // Counter that denotes how far we are into transitioning to another set of + // coefficients via interpolation. + size_t interpolate_counter_; + + // Stores the memory state of the biquad. + std::array<float, 2> old_delay_line_; + + // Number of samples over which to apply the filter coefficients. + size_t samples_to_iterate_over_; + + // Value used to crossfade between filter outputs. + float fade_scale_; + + // A set of coefficient which are updated as we interpolate between filter + // coefficients. + BiquadCoefficients old_coefficients_; + + // Represents and maintains the state of the biquad filter in terms of its + // transfer function coefficients. + BiquadCoefficients coefficients_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_DSP_BIQUAD_FILTER_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/biquad_filter_test.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/biquad_filter_test.cc new file mode 100644 index 000000000..c8ac6de78 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/biquad_filter_test.cc @@ -0,0 +1,303 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/biquad_filter.h" + +#include <algorithm> +#include <cmath> +#include <numeric> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/constants_and_types.h" + +namespace vraudio { + +namespace { + +const size_t kTestInputDataSize = 16; + +const float kTestFilterCoefficients[] = {1.0f, 0.0f, 0.0, 1.0f, 0.1f, 0.1f}; + +const size_t kIdealSamplesToIterate = 256; + +// For use with std::transform in the StabilityTest. +float operator_abs(float f) { return std::abs(f); } + +} // namespace + +// Tests that the filtering of a mono signal produces the correct output. +TEST(BiquadFilterTest, FilterTest) { + const size_t kNumIterations = 1; + + const BiquadCoefficients kCoefficients( + kTestFilterCoefficients[0], kTestFilterCoefficients[1], + kTestFilterCoefficients[2], kTestFilterCoefficients[3], + kTestFilterCoefficients[4], kTestFilterCoefficients[5]); + + AudioBuffer input(kNumMonoChannels, kTestInputDataSize); + AudioBuffer output(kNumMonoChannels, kTestInputDataSize); + // Set the input AudioBuffer to a Kronecker delta. + input.Clear(); + input[0][0] = 1.0f; + + // This vector will accumulate the output from the filter over time (Only + // works with mono channel). + std::vector<float> output_accumulator; + output_accumulator.reserve(kNumIterations * kTestInputDataSize); + + // Create a biquad filter initialized with transfer function coefficients. + BiquadFilter biquad(kCoefficients, kTestInputDataSize); + + // Perform filtering. + for (size_t i = 0; i < kNumIterations; ++i) { + biquad.Filter(input[0], &output[0]); + output_accumulator.insert(output_accumulator.end(), output[0].begin(), + output[0].end()); + } + + // Since the denominator of the biquad coefficients is [1 0 0] we can expect + // the impulse response to be equal to the numerator. + for (size_t i = 0; i < output_accumulator.size(); ++i) { + if (i < 3U) { + EXPECT_NEAR(kTestFilterCoefficients[3 + i], output_accumulator[i], + kEpsilonFloat); + } else { + EXPECT_EQ(0.0f, output_accumulator[i]); + } + } +} + +// Tests that the filtering of a mono signal produces the correct output. +TEST(BiquadFilterTest, InplaceFilterTest) { + const size_t kNumIterations = 1; + + const BiquadCoefficients kCoefficients( + kTestFilterCoefficients[0], kTestFilterCoefficients[1], + kTestFilterCoefficients[2], kTestFilterCoefficients[3], + kTestFilterCoefficients[4], kTestFilterCoefficients[5]); + + AudioBuffer input(kNumMonoChannels, kTestInputDataSize); + // Set the input AudioBuffer to a Kronecker delta. + input.Clear(); + input[0][0] = 1.0f; + + // This vector will accumulate the output from the filter over time (Only + // works with mono channel). + std::vector<float> output_accumulator; + output_accumulator.reserve(kTestInputDataSize); + + // Create a biquad filter initialized with transfer function coefficients. + BiquadFilter biquad(kCoefficients, kTestInputDataSize); + + // Perform inplace filtering of |input| vector. + for (size_t i = 0; i < kNumIterations; ++i) { + biquad.Filter(input[0], &input[0]); + output_accumulator.insert(output_accumulator.end(), input[0].begin(), + input[0].end()); + } + + // Since the denominator of the biquad coefficients is [1 0 0] we can expect + // the impulse response to be equal to the numerator. + for (size_t i = 0; i < output_accumulator.size(); ++i) { + if (i < 3) { + EXPECT_NEAR(kTestFilterCoefficients[3 + i], output_accumulator[i], + kEpsilonFloat); + } else { + EXPECT_EQ(0.0f, output_accumulator[i]); + } + } +} + +// Tests whether the interpolation stops after kIdealSamplesToIterate samples, +// given a long enough buffer. +TEST(BiquadFilterTest, InterpolationStopsTest) { + const size_t kFramesPerBuffer = 512; + + std::vector<std::vector<float>> planar_input_data( + kNumMonoChannels, std::vector<float>(kFramesPerBuffer)); + planar_input_data[0][0] = 1.0f; + planar_input_data[0][256] = 1.0f; + + AudioBuffer input_planar(kNumMonoChannels, kFramesPerBuffer); + AudioBuffer output_planar(kNumMonoChannels, kFramesPerBuffer); + input_planar = planar_input_data; + + // Instantiate Biquad for planar data. The impulse response of the default is: + // 1, 0, 0, ...... + BiquadFilter biquad(BiquadCoefficients(), kFramesPerBuffer); + + // Coefficients we wish to interpolate to. The impulse response of this filter + // is: 1, 0, 1, ...... + const BiquadCoefficients kNextCoefficients = {1.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 1.0f}; + + biquad.InterpolateToCoefficients(kNextCoefficients); + biquad.Filter(input_planar[0], &output_planar[0]); + + // Based on the known impulse responses we can see that if elements 256, 257 + // and 258 have the values 1, 0, 1, then only the new filter coefficients are + // contributing and we have stopped crossfading. Note: The value of 256 here + // comes from kIdealSamplesToIterate defined in biquad_filter.cc + EXPECT_EQ(1.0f, output_planar[0][kIdealSamplesToIterate]); + EXPECT_EQ(0.0f, output_planar[0][kIdealSamplesToIterate + 1]); + EXPECT_EQ(1.0f, output_planar[0][kIdealSamplesToIterate + 2]); +} + +// Tests whether the |BiquadFilter| remains stable when its z-domain poles lie +// very close to the unit circle. +TEST(BiquadFilterTest, StabilityTest) { + static const size_t kBufferSize = 1024; + const size_t kIterations = 200; + // The following filter was designed in MATLAB with a very slow decay rate. + // (There are both poles and zeros with magnitude 0.999). Even 200'000 samples + // after the initial impulse the response will have only died away, in an + // oscillating manner, to ~1/700 of its peak value. + static const BiquadCoefficients kHighQCoefficients( + 1.0f, -0.907804951302441f, 0.999869108872718f, 15279.8745150008f, 0.0f, + -15279.8745150008f); + BiquadFilter biquad(kHighQCoefficients, kBufferSize); + + AudioBuffer input(kNumMonoChannels, kBufferSize); + AudioBuffer output(kNumMonoChannels, kBufferSize); + // Set the input AudioBuffer to a Kronecker delta. + input.Clear(); + input[0][0] = 1.0f; + + // This vector will accumulate the output from the filter over time (Only + // works with mono channel). + std::vector<float> output_accumulator; + output_accumulator.reserve(kBufferSize * kIterations); + + // Filter once with the Kronecker delta. + biquad.Filter(input[0], &output[0]); + output_accumulator.insert(output_accumulator.end(), output[0].begin(), + output[0].end()); + + // Perform filtering with all zero input. + input[0][0] = 0.0f; + for (size_t i = 1; i < kIterations; ++i) { + biquad.Filter(input[0], &output[0]); + output_accumulator.insert(output_accumulator.end(), output[0].begin(), + output[0].end()); + } + + // Test that the signal is decaying over time (i.e. filter is stable). + for (size_t i = 0; i < output_accumulator.size() - kBufferSize; + i += kBufferSize) { + std::vector<float> absolute_first_half(kBufferSize / 2); + std::transform(output_accumulator.begin() + i, + output_accumulator.begin() + i + kBufferSize / 2, + absolute_first_half.begin(), operator_abs); + std::vector<float> absolute_second_half(kBufferSize / 2); + std::transform(output_accumulator.begin() + 1 + i + kBufferSize / 2, + output_accumulator.begin() + i + kBufferSize, + absolute_second_half.begin(), operator_abs); + const float sum_first_half = std::accumulate( + absolute_first_half.begin(), absolute_first_half.end(), 0.0f); + const float sum_second_half = std::accumulate( + absolute_second_half.begin(), absolute_second_half.end(), 0.0f); + EXPECT_LT(sum_second_half, sum_first_half); + } +} + +class BiquadFilterInterpolateTest : public ::testing::Test { + protected: + BiquadFilterInterpolateTest() {} + // Virtual methods from ::testing::Test + ~BiquadFilterInterpolateTest() override {} + void SetUp() override {} + void TearDown() override {} + + // Returns true if |filter|'s internal coefficients are equal to those of + // |expected_coefficients| after scaling by a0. + bool TestInternalCoefficients( + BiquadFilter* filter, const BiquadCoefficients& expected_coefficients) { + bool return_value = true; + return_value &= + expected_coefficients.a[0] - filter->coefficients_.a[0] < kEpsilonFloat; + + // From this point we must take account of the scaling by 1/a0. + const float a0 = filter->coefficients_.a[0]; + return_value &= + expected_coefficients.b[0] - filter->coefficients_.b[0] * a0 < + kEpsilonFloat; + for (int i = 1; i < 3; ++i) { + return_value &= + expected_coefficients.b[i] - filter->coefficients_.b[i] * a0 < + kEpsilonFloat; + return_value &= + expected_coefficients.a[i] - filter->coefficients_.a[i] * a0 < + kEpsilonFloat; + } + return return_value; + } +}; + +// Tests whether updating the filter's coefficients with the +// InterpolateToCoefficients reaches the correct coefficients after filtering a +// block of data and whether the samples_to_iterate_over_ value is set +// correctly. +TEST_F(BiquadFilterInterpolateTest, InterpolatedUpdateTest) { + const size_t kFramesPerBuffer = 8; + + const std::vector<std::vector<float>> kPlanarInputData = { + {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f}}; + + const std::vector<float> kExpectedOutputAfterChange = { + 8.0f, 10.0f, 4.0f, 6.0f, 8.0f, 10.0f, 12.0f, 14.0f}; + + // Default constructor sets a = {1.0, 0.0, 0.0} and b = {1.0, 0.0, 0.0}. + const BiquadCoefficients kCoefficients; + + // Create input and output AudioBuffers. + AudioBuffer input_planar(kNumMonoChannels, kFramesPerBuffer); + AudioBuffer output_planar(kNumMonoChannels, kFramesPerBuffer); + input_planar = kPlanarInputData; + + // Instantiate Biquad for planar data. + BiquadFilter biquad(kCoefficients, kFramesPerBuffer); + + // Perform filtering on kNumMonoChannels interleaved. + biquad.Filter(input_planar[0], &output_planar[0]); + + for (size_t i = 0; i < kFramesPerBuffer; ++i) { + EXPECT_NEAR(output_planar[0][i], kPlanarInputData[0][i], kEpsilonFloat); + } + + // Coefficients we wish to interpolate to. + static const BiquadCoefficients kNextCoefficients = {1.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 1.0f}; + + biquad.InterpolateToCoefficients(kNextCoefficients); + + // Filter once to transition and once to flush out the state. + biquad.Filter(input_planar[0], &output_planar[0]); + biquad.Filter(input_planar[0], &output_planar[0]); + + // Now the output should be just from the new state. + biquad.Filter(input_planar[0], &output_planar[0]); + + // Now check that we transitioned properly, i.e. the output is as expected. + for (size_t i = 0; i < kFramesPerBuffer; ++i) { + EXPECT_NEAR(output_planar[0][i], kExpectedOutputAfterChange[i], + kEpsilonFloat); + } + + // Now check that we have the new coefficients. + EXPECT_TRUE(TestInternalCoefficients(&biquad, kNextCoefficients)); +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/channel_converter.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/channel_converter.cc new file mode 100644 index 000000000..375001bac --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/channel_converter.cc @@ -0,0 +1,45 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/channel_converter.h" + +#include <cmath> + +#include "base/constants_and_types.h" +#include "base/logging.h" +#include "base/simd_utils.h" + +namespace vraudio { + +void ConvertStereoFromMono(const AudioBuffer& input, AudioBuffer* output) { + DCHECK(output); + DCHECK_EQ(input.num_channels(), kNumMonoChannels); + DCHECK_EQ(output->num_channels(), kNumStereoChannels); + DCHECK_EQ(input.num_frames(), output->num_frames()); + StereoFromMonoSimd(input.num_frames(), &input[0][0], &(*output)[0][0], + &(*output)[1][0]); +} + +void ConvertMonoFromStereo(const AudioBuffer& input, AudioBuffer* output) { + DCHECK(output); + DCHECK_EQ(input.num_channels(), kNumStereoChannels); + DCHECK_EQ(output->num_channels(), kNumMonoChannels); + DCHECK_EQ(input.num_frames(), output->num_frames()); + MonoFromStereoSimd(input.num_frames(), &input[0][0], &input[1][0], + &(*output)[0][0]); +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/channel_converter.h b/src/3rdparty/resonance-audio/resonance_audio/dsp/channel_converter.h new file mode 100644 index 000000000..26d825ae4 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/channel_converter.h @@ -0,0 +1,40 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_DSP_CHANNEL_CONVERTER_H_ +#define RESONANCE_AUDIO_DSP_CHANNEL_CONVERTER_H_ + +#include "base/audio_buffer.h" + +namespace vraudio { + +// Converts a mono |input| buffer to a stereo |output| buffer by preserving the +// signal energy. +// +// @param input Mono input buffer. +// @param output Pointer to a stereo output buffer. +void ConvertStereoFromMono(const AudioBuffer& input, AudioBuffer* output); + +// Converts a stereo |input| buffer to a mono |output| buffer by preserving the +// signal energy. +// +// @param input Stereo input buffer. +// @param output Pointer to a mono output buffer. +void ConvertMonoFromStereo(const AudioBuffer& input, AudioBuffer* output); + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_DSP_CHANNEL_CONVERTER_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/channel_converter_test.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/channel_converter_test.cc new file mode 100644 index 000000000..3d6928ad5 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/channel_converter_test.cc @@ -0,0 +1,80 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/channel_converter.h" + +#include <vector> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/constants_and_types.h" + +namespace vraudio { + +namespace { + +// Number of frames for test buffers. +const size_t kNumFrames = 5; + +// Tests that the correct stereo output buffer is obtained from the converter +// given an arbitrary mono input buffer. +TEST(ChannelConverterTest, ConvertStereoFromMonoTest) { + const std::vector<std::vector<float>> kMonoInput = { + {1.0f, 2.0f, 3.0f, 4.0f, 5.0f}}; + const std::vector<std::vector<float>> kExpectedStereoOutput = { + {0.707107f, 1.414214f, 2.121320f, 2.828427f, 3.535534f}, + {0.707107f, 1.414214f, 2.121320f, 2.828427f, 3.535534f}}; + + // Initialize the test buffers. + AudioBuffer mono_input(kNumMonoChannels, kNumFrames); + mono_input = kMonoInput; + AudioBuffer stereo_output(kNumStereoChannels, kNumFrames); + // Process the input buffer. + ConvertStereoFromMono(mono_input, &stereo_output); + // Compare the output buffer against the expected output. + for (size_t channel = 0; channel < kNumStereoChannels; ++channel) { + for (size_t frame = 0; frame < kNumFrames; ++frame) { + EXPECT_NEAR(stereo_output[channel][frame], + kExpectedStereoOutput[channel][frame], kEpsilonFloat); + } + } +} + +// Tests that the correct mono output buffer is obtained from the converter +// given an arbitrary stereo input buffer. +TEST(ChannelConverterTest, ConvertMonoFromStereoTest) { + const std::vector<std::vector<float>> kStereoInput = { + {0.707107f, 1.414214f, 2.121320f, 2.828427f, 3.535534f}, + {0.707107f, 1.414214f, 2.121320f, 2.828427f, 3.535534f}}; + + const std::vector<std::vector<float>> kExpectedMonoOutput = { + {1.0f, 2.0f, 3.0f, 4.0f, 5.0f}}; + + // Initialize the test buffers. + AudioBuffer stereo_input(kNumStereoChannels, kNumFrames); + stereo_input = kStereoInput; + AudioBuffer mono_output(kNumMonoChannels, kNumFrames); + // Process the input buffer. + ConvertMonoFromStereo(stereo_input, &mono_output); + // Compare the output buffer against the expected output. + for (size_t frame = 0; frame < kNumFrames; ++frame) { + EXPECT_NEAR(mono_output[0][frame], kExpectedMonoOutput[0][frame], + kEpsilonFloat); + } +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/circular_buffer.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/circular_buffer.cc new file mode 100644 index 000000000..5ef2be2c9 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/circular_buffer.cc @@ -0,0 +1,120 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/circular_buffer.h" + +#include <algorithm> + +#include "base/constants_and_types.h" + +namespace vraudio { + +CircularBuffer::CircularBuffer(size_t buffer_length, size_t num_input_frames, + size_t num_output_frames) + : num_input_frames_(num_input_frames), + num_output_frames_(num_output_frames), + buffer_(kNumMonoChannels, buffer_length), + write_cursor_(0), + read_cursor_(0), + num_valid_frames_(0) { + CHECK_GE(buffer_length, num_input_frames + num_output_frames); +} + +bool CircularBuffer::InsertBuffer(const AudioBuffer::Channel& input) { + DCHECK_EQ(input.size(), num_input_frames_); + + if (num_valid_frames_ + num_input_frames_ > buffer_.num_frames()) { + return false; + } + // Remaining space after the write cursor. + const size_t forward_write_space = read_cursor_ <= write_cursor_ + ? buffer_.num_frames() - write_cursor_ + : read_cursor_ - write_cursor_; + + // Copy the input into the buffer. + AudioBuffer::Channel* buffer_channel = &buffer_[0]; + if (forward_write_space >= num_input_frames_) { + DCHECK_LE(buffer_channel->begin() + write_cursor_ + num_input_frames_, + buffer_channel->end()); + std::copy(input.begin(), input.end(), + buffer_channel->begin() + write_cursor_); + } else { + DCHECK_LE(buffer_channel->begin() + write_cursor_ + forward_write_space, + buffer_channel->end()); + DCHECK_LT(input.begin() + forward_write_space, input.end()); + std::copy(input.begin(), input.begin() + forward_write_space, + buffer_channel->begin() + write_cursor_); + DCHECK_LE(buffer_channel->begin() + forward_write_space, + buffer_channel->end()); + std::copy(input.begin() + forward_write_space, input.end(), + buffer_channel->begin()); + } + + write_cursor_ = (write_cursor_ + num_input_frames_) % buffer_.num_frames(); + num_valid_frames_ += num_input_frames_; + return true; +} + +bool CircularBuffer::RetrieveBuffer(AudioBuffer::Channel* output) { + return RetrieveBufferWithOffset(/*offset=*/0, output); +} + +bool CircularBuffer::RetrieveBufferWithOffset(size_t offset, + AudioBuffer::Channel* output) { + DCHECK_LE(output->begin() + num_output_frames_ + offset, output->end()); + + if (num_valid_frames_ < num_output_frames_) { + return false; + } + // Remaining space after the read cursor. + const size_t forward_read_space = read_cursor_ < write_cursor_ + ? write_cursor_ - read_cursor_ + : buffer_.num_frames() - read_cursor_; + + // Copy the buffer values into the output. + AudioBuffer::Channel* buffer_channel = &buffer_[0]; + if (forward_read_space >= num_output_frames_) { + DCHECK_LE(buffer_channel->begin() + read_cursor_ + num_output_frames_, + buffer_channel->end()); + std::copy(buffer_channel->begin() + read_cursor_, + buffer_channel->begin() + read_cursor_ + num_output_frames_, + output->begin() + offset); + } else { + DCHECK_LE(buffer_channel->begin() + read_cursor_ + forward_read_space, + buffer_channel->end()); + DCHECK_LE(output->begin() + forward_read_space + offset, output->end()); + std::copy(buffer_channel->begin() + read_cursor_, + buffer_channel->begin() + read_cursor_ + forward_read_space, + output->begin() + offset); + DCHECK_GE(buffer_channel->begin() + num_output_frames_ - forward_read_space, + buffer_channel->begin()); + DCHECK_LE(output->begin() + offset + num_output_frames_, output->end()); + std::copy(buffer_channel->begin(), + buffer_channel->begin() + num_output_frames_ - forward_read_space, + output->begin() + offset + forward_read_space); + } + read_cursor_ = (read_cursor_ + num_output_frames_) % buffer_.num_frames(); + num_valid_frames_ -= num_output_frames_; + return true; +} + +void CircularBuffer::Clear() { + read_cursor_ = 0; + write_cursor_ = 0; + num_valid_frames_ = 0; +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/circular_buffer.h b/src/3rdparty/resonance-audio/resonance_audio/dsp/circular_buffer.h new file mode 100644 index 000000000..e21f5817d --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/circular_buffer.h @@ -0,0 +1,96 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_DSP_CIRCULAR_BUFFER_H_ +#define RESONANCE_AUDIO_DSP_CIRCULAR_BUFFER_H_ + +#include "base/audio_buffer.h" + +namespace vraudio { +// Class that implements a simple mono circular buffer, accepting input and +// output of different length. +class CircularBuffer { + public: + // Constructs a circular buffer. + // + // @param buffer_length The length of the Circular buffer. This value must be + // at least |num_input_frames| + |num_output_frames| so that we can always + // either add or remove data. + // @param num_input_frames Length of the input buffers in frames. + // @param num_output_frames Length of the output buffers in frames. + CircularBuffer(size_t buffer_length, size_t num_input_frames, + size_t num_output_frames); + + // Inserts a buffer of mono input into the |CircularBuffer| if space permits. + // + // @param input A channel of input data |num_input_frames| in length. + // @return True if there was space in the buffer and the input was + // successfully inserted, false otherwise. + bool InsertBuffer(const AudioBuffer::Channel& input); + + // Retrieves a buffer of output from the |CircularBuffer| if it contains + // sufficient data. + // + // @param output A channel to hold |num_output_frames| of output data. The + // channel may be greater in length than |num_output_frames|, in this + // case only the first |num_output_frames| will be overwritten. + // @return True if there was sufficient data in the buffer and the output was + // successfully retrieved. + bool RetrieveBuffer(AudioBuffer::Channel* output); + + // Retrieves a buffer of output from the |CircularBuffer| to an offset + // location in an output channel, provided it contains sufficient data. + // + // @param offset Number of samples of offset into the |output| channel. + // @param output A channel to hold |num_output_frames| of output data. The + // channel may be greater in length than |num_output_frames| + |offset|, + // in this case only the first |num_output_frames| after |offset| will be + // overwritten. + // @return True if there was sufficient data in the buffer and the output was + // successfully retrieved. + bool RetrieveBufferWithOffset(size_t offset, AudioBuffer::Channel* output); + + // Returns the number of samples of data currently in the |CircularBuffer|. + // + // @return The number of samples of data currently in the buffer. + size_t GetOccupancy() const { return num_valid_frames_; } + + // Resets the |CircularBuffer|. + void Clear(); + + private: + // Number of input frames to be inserted into the buffer. + const size_t num_input_frames_; + + // Number of output frames to be retrieved from the buffer. + const size_t num_output_frames_; + + // Mono audio buffer to hold the data. + AudioBuffer buffer_; + + // Position at which we are writing into the buffer. + size_t write_cursor_; + + // position at which we are reading from the buffer. + size_t read_cursor_; + + // Number of frames of data currently stored within the buffer. + size_t num_valid_frames_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_DSP_CIRCULAR_BUFFER_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/circular_buffer_test.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/circular_buffer_test.cc new file mode 100644 index 000000000..5b53246e6 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/circular_buffer_test.cc @@ -0,0 +1,230 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/circular_buffer.h" + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/constants_and_types.h" + +namespace vraudio { + +namespace { + +// Tests that when the output from the circular buffer is of a smaller length +// than the input, the |CircularBuffer|correctly accepts and rejects inserts +// and retrievals. +TEST(CircularBufferTest, RemainingReadSpaceTest) { + const size_t kInputSize = 5; + const size_t kOutputSize = 4; + const size_t kBufferSize = 9; + CircularBuffer circular_buffer(kBufferSize, kInputSize, kOutputSize); + AudioBuffer input_a(kNumMonoChannels, kInputSize); + input_a[0] = {0.0f, 1.0f, 2.0f, 3.0f, 4.0f}; + AudioBuffer output(kNumMonoChannels, kOutputSize); + output.Clear(); + + EXPECT_TRUE(circular_buffer.InsertBuffer(input_a[0])); + // There should not be enough space to write another buffer. + EXPECT_FALSE(circular_buffer.InsertBuffer(input_a[0])); + + EXPECT_TRUE(circular_buffer.RetrieveBuffer(&output[0])); + // There should not be enough data to read from the buffer. + EXPECT_FALSE(circular_buffer.RetrieveBuffer(&output[0])); + + // The output buffer should contain the first |kOutputSize| entries from the + // input buffer. + for (size_t i = 0; i < kOutputSize; ++i) { + EXPECT_FLOAT_EQ(output[0][i], input_a[0][i]); + } + + AudioBuffer input_b(kNumMonoChannels, kInputSize); + input_b[0] = {5.0f, 6.0f, 7.0f, 8.0f, 9.0f}; + + EXPECT_TRUE(circular_buffer.InsertBuffer(input_b[0])); + // There should not be enough space to write another buffer. + EXPECT_FALSE(circular_buffer.InsertBuffer(input_b[0])); + + EXPECT_TRUE(circular_buffer.RetrieveBuffer(&output[0])); + // There should not be enough data to read from the buffer. + EXPECT_FALSE(circular_buffer.RetrieveBuffer(&output[0])); + + // The output buffer should contain the final entry from the first input + // buffer and then |kOutputSize| - 1 entries from the second. + EXPECT_FLOAT_EQ(output[0][0], input_a[0][kInputSize - 1]); + for (size_t i = 1; i < kOutputSize; ++i) { + EXPECT_FLOAT_EQ(output[0][i], input_b[0][i - 1]); + } +} + +// Tests that when the output from the circular buffer is of a greater length +// than the input, the |CircularBuffer|correctly accepts and rejects inserts +// and retrievals. +TEST(CircularBufferTest, RemainingWriteSpaceTest) { + const size_t kInputSize = 4; + const size_t kOutputSize = 5; + const size_t kBufferSize = 10; + CircularBuffer circular_buffer(kBufferSize, kInputSize, kOutputSize); + AudioBuffer input_a(kNumMonoChannels, kInputSize); + input_a[0] = {0.0f, 1.0f, 2.0f, 3.0f}; + AudioBuffer input_b(kNumMonoChannels, kInputSize); + input_b[0] = {4.0f, 5.0f, 6.0f, 7.0f}; + AudioBuffer input_c(kNumMonoChannels, kInputSize); + input_c[0] = {8.0f, 9.0f, 10.0f, 11.0f}; + AudioBuffer input_d(kNumMonoChannels, kInputSize); + input_d[0] = {12.0f, 13.0f, 14.0f, 15.0f}; + AudioBuffer input_e(kNumMonoChannels, kInputSize); + input_e[0] = {16.0f, 17.0f, 18.0f, 19.0f}; + AudioBuffer output(kNumMonoChannels, kOutputSize); + output.Clear(); + + EXPECT_TRUE(circular_buffer.InsertBuffer(input_a[0])); + EXPECT_TRUE(circular_buffer.InsertBuffer(input_b[0])); + // There should not be enough space to write another buffer. + EXPECT_FALSE(circular_buffer.InsertBuffer(input_c[0])); + + EXPECT_TRUE(circular_buffer.RetrieveBuffer(&output[0])); + // There should not be enough data to read from the buffer. + EXPECT_FALSE(circular_buffer.RetrieveBuffer(&output[0])); + + float value = 0.0f; + for (size_t i = 0; i < kOutputSize; ++i) { + EXPECT_FLOAT_EQ(output[0][i], value); + value += 1.0f; + } + + // Add another 4 samples of input in. + EXPECT_TRUE(circular_buffer.InsertBuffer(input_c[0])); + // There should not be enough space to write another buffer. + EXPECT_FALSE(circular_buffer.InsertBuffer(input_d[0])); + + EXPECT_TRUE(circular_buffer.RetrieveBuffer(&output[0])); + // There should not be enough data to read from the buffer. + EXPECT_FALSE(circular_buffer.RetrieveBuffer(&output[0])); + + for (size_t i = 0; i < kOutputSize; ++i) { + EXPECT_FLOAT_EQ(output[0][i], value); + value += 1.0f; + } + + // Add another 8 samples of input in. + EXPECT_TRUE(circular_buffer.InsertBuffer(input_d[0])); + EXPECT_TRUE(circular_buffer.InsertBuffer(input_e[0])); + // There should not be enough space to write another buffer. + EXPECT_FALSE(circular_buffer.InsertBuffer(input_a[0])); + + // We should be able to get 10 samples of output. + EXPECT_TRUE(circular_buffer.RetrieveBuffer(&output[0])); + for (size_t i = 0; i < kOutputSize; ++i) { + EXPECT_FLOAT_EQ(output[0][i], value); + value += 1.0f; + } + EXPECT_TRUE(circular_buffer.RetrieveBuffer(&output[0])); + for (size_t i = 0; i < kOutputSize; ++i) { + EXPECT_FLOAT_EQ(output[0][i], value); + value += 1.0f; + } + + // There should not be enough data to read from the buffer. + EXPECT_FALSE(circular_buffer.RetrieveBuffer(&output[0])); + + // The buffer should now be completely empty. + EXPECT_EQ(circular_buffer.GetOccupancy(), 0U); +} + +// Tests tha a call to RetrieveBuffer will work when the output buffer is +// oversized, but that only the first kOutputSize samples are filled. +TEST(CircularBufferTest, LongerWriteSpaceTest) { + const size_t kInputSize = 5; + const size_t kOutputSize = 3; + const size_t kBufferSize = 10; + CircularBuffer circular_buffer(kBufferSize, kInputSize, kOutputSize); + AudioBuffer input_a(kNumMonoChannels, kInputSize); + input_a[0] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f}; + AudioBuffer output(kNumMonoChannels, kBufferSize); + output.Clear(); + + EXPECT_TRUE(circular_buffer.InsertBuffer(input_a[0])); + EXPECT_TRUE(circular_buffer.RetrieveBuffer(&output[0])); + + for (size_t i = 0; i < kBufferSize; ++i) { + if (i < kOutputSize) { + EXPECT_FLOAT_EQ(output[0][i], input_a[0][i]); + } else { + EXPECT_FLOAT_EQ(output[0][i], 0.0f); + } + } +} + +// Tests tha a call to RetrieveBufferOffset will work when the output buffer is +// oversized, but that the first kOutputSize samples after kOffset are filled. +TEST(CircularBufferTest, OffsetRerieveTest) { + const size_t kInputSize = 5; + const size_t kOutputSize = 3; + const size_t kBufferSize = 10; + const size_t kOffset = 2; + CircularBuffer circular_buffer(kBufferSize, kInputSize, kOutputSize); + AudioBuffer input_a(kNumMonoChannels, kInputSize); + input_a[0] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f}; + AudioBuffer output(kNumMonoChannels, kBufferSize); + output.Clear(); + + EXPECT_TRUE(circular_buffer.InsertBuffer(input_a[0])); + EXPECT_TRUE(circular_buffer.RetrieveBufferWithOffset(kOffset, &output[0])); + + for (size_t i = 0; i < kBufferSize; ++i) { + if (i < kOffset) { + EXPECT_FLOAT_EQ(output[0][i], 0.0f); + } else if (i < kOffset + kOutputSize) { + EXPECT_FLOAT_EQ(output[0][i], input_a[0][i - kOffset]); + } else { + EXPECT_FLOAT_EQ(output[0][i], 0.0f); + } + } +} + +// Tests that the circular buffer handles odd input buffer lengths in the +// manner that it will be passed in the spectral reverb +// (see: dsp/spectral_reverb.cc) +TEST(CircularBufferTest, Strange) { + const size_t kNumRuns = 100; + // An odd non pwer of two input buffer size. + const size_t kOddInputSize = 713; + // Output size is equal to the SpectralReverb's internal buffer size. + const size_t kOutputSize = 1024; + // SpectralReverb's internal forier transform length. + const size_t kFFTSize = 4096; + CircularBuffer input(kFFTSize + kOddInputSize, kOddInputSize, kOutputSize); + CircularBuffer output(kOutputSize + kOddInputSize, kOutputSize, + kOddInputSize); + AudioBuffer in(kNumMonoChannels, kOddInputSize); + AudioBuffer out(kNumMonoChannels, kOutputSize); + + // AudioBuffers will be input and output in the same manner as in + // dsp/spectral_reverb.cc. + EXPECT_TRUE(output.InsertBuffer(out[0])); + for (size_t i = 0; i < kNumRuns; ++i) { + EXPECT_TRUE(input.InsertBuffer(in[0])); + while (input.GetOccupancy() >= kOutputSize) { + EXPECT_TRUE(input.RetrieveBuffer(&out[0])); + EXPECT_TRUE(output.InsertBuffer(out[0])); + } + EXPECT_TRUE(output.RetrieveBuffer(&in[0])); + } +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/delay_filter.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/delay_filter.cc new file mode 100644 index 000000000..b448de58b --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/delay_filter.cc @@ -0,0 +1,180 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/delay_filter.h" + +#include <cmath> + +#include "base/constants_and_types.h" +#include "base/misc_math.h" + + +namespace vraudio { +// The following explains the behaviour of the delay line when a change to the +// delay length is made. Initially |delay| is 2, |frames_per_buffer_| +// is 4, and |delay_line_| is 8 in length. +// +// delay = 2 +// +// input0 [1, 2, 3, 4] output0 [0, 0, 1, 2] +// +// 0 1 2 3 4 5 6 7 +// delay line [1, 2, 3, 4, 0, 0, 0, 0] +// | | +// w r +// ---------------------------------------------------------- +// +// delay = 2 +// +// input1 [5, 6, 7, 8] output1 [3, 4, 5, 6] +// +// 0 1 2 3 4 5 6 7 +// delay line [1, 2, 3, 4, 5, 6, 7, 8] +// | | +// w r +// ---------------------------------------------------------- +// +// delay = 1 +// +// 0 1 2 3 4 5 6 7 +// delay line [1, 2, 3, 4, 5, 6, 7, 8] +// | | +// w r +// ---------------------------------------------------------- +// +// delay = 1 +// +// input2 [9, 10, 11, 12] output2 [8, 9, 10, 11] +// +// 0 1 2 3 4 5 6 7 +// delay line [9, 10, 11, 12, 5, 6, 7, 8] +// | | +// w r +// ---------------------------------------------------------- + +DelayFilter::DelayFilter(size_t max_delay_length, size_t frames_per_buffer) + : frames_per_buffer_(frames_per_buffer), + delay_line_(nullptr), + write_cursor_(0) { + DCHECK_GT(frames_per_buffer_, 0U); + SetMaximumDelay(max_delay_length); +} + +void DelayFilter::SetMaximumDelay(size_t max_delay_length) { + max_delay_length_ = max_delay_length; + const size_t new_buffer_size = frames_per_buffer_ + max_delay_length_; + + if (delay_line_ == nullptr) { + delay_line_.reset(new AudioBuffer(kNumMonoChannels, new_buffer_size)); + delay_line_->Clear(); + return; + } + AudioBuffer::Channel* delay_channel = &(*delay_line_)[0]; + // If |delay_line_| is not large enough, resize. + const size_t current_buffer_size = delay_line_->num_frames(); + if (max_delay_length_ > current_buffer_size - frames_per_buffer_) { + // Allocate |new_delay_line| and populate it with the current |delay_line_| + // data, so that replacing the buffer does not affect the already stored + // samples. + auto new_delay_line = std::unique_ptr<AudioBuffer>( + new AudioBuffer(kNumMonoChannels, new_buffer_size)); + new_delay_line->Clear(); + std::copy(delay_channel->begin() + write_cursor_, delay_channel->end(), + (*new_delay_line)[0].begin()); + if (write_cursor_ > 0) { + // Positive |write_cursor_| means that we still have remaining samples to + // be moved to |new_delay_line_| all of which reside left of the cursor. + std::copy( + delay_channel->begin(), delay_channel->begin() + write_cursor_, + (*new_delay_line)[0].begin() + current_buffer_size - write_cursor_); + write_cursor_ = current_buffer_size; + } + delay_line_ = std::move(new_delay_line); + } +} + +void DelayFilter::InsertData(const AudioBuffer::Channel& input) { + + DCHECK_EQ(input.size(), frames_per_buffer_); + + const size_t delay_buffer_size = delay_line_->num_frames(); + + // Record the remaining space in the |delay_line_| after the write cursor. + const size_t remaining_size_write = delay_buffer_size - write_cursor_; + AudioBuffer::Channel* delay_channel = &(*delay_line_)[0]; + + // Copy the input into the delay line. + if (remaining_size_write >= frames_per_buffer_) { + DCHECK_LE(delay_channel->begin() + write_cursor_ + frames_per_buffer_, + delay_channel->end()); + std::copy(input.begin(), input.end(), + delay_channel->begin() + write_cursor_); + } else { + DCHECK_LE(delay_channel->begin() + write_cursor_ + remaining_size_write, + delay_channel->end()); + DCHECK_LE(input.begin() + remaining_size_write, input.end()); + std::copy(input.begin(), input.begin() + remaining_size_write, + delay_channel->begin() + write_cursor_); + DCHECK_LE(delay_channel->begin() + remaining_size_write, + delay_channel->end()); + std::copy(input.begin() + remaining_size_write, input.end(), + delay_channel->begin()); + } + + write_cursor_ = (write_cursor_ + frames_per_buffer_) % delay_buffer_size; +} + +void DelayFilter::GetDelayedData(size_t delay_samples, + AudioBuffer::Channel* buffer) { + + DCHECK(buffer); + DCHECK_GE(delay_samples, 0U); + DCHECK_LE(delay_samples, max_delay_length_); + + const size_t delay_buffer_size = delay_line_->num_frames(); + // Position in the delay line to begin reading from. + DCHECK_GE(write_cursor_ + delay_buffer_size, + delay_samples + frames_per_buffer_); + const size_t read_cursor = + (write_cursor_ + delay_buffer_size - delay_samples - frames_per_buffer_) % + delay_buffer_size; + // Record the remaining space in the |delay_line_| after the read cursor. + const size_t remaining_size_read = delay_buffer_size - read_cursor; + AudioBuffer::Channel* delay_channel = &(*delay_line_)[0]; + + // Extract a portion of the delay line into the buffer. + if (remaining_size_read >= frames_per_buffer_) { + DCHECK_LE(buffer->begin() + frames_per_buffer_, buffer->end()); + DCHECK_LE(delay_channel->begin() + read_cursor + frames_per_buffer_, + delay_channel->end()); + std::copy(delay_channel->begin() + read_cursor, + delay_channel->begin() + read_cursor + frames_per_buffer_, + buffer->begin()); + } else { + DCHECK_LE(buffer->begin() + delay_channel->size() - read_cursor, + buffer->end()); + std::copy(delay_channel->begin() + read_cursor, delay_channel->end(), + buffer->begin()); + DCHECK_LE(buffer->begin() + frames_per_buffer_, buffer->end()); + DCHECK_LE(delay_channel->begin() + frames_per_buffer_ - remaining_size_read, + delay_channel->end()); + std::copy(delay_channel->begin(), + delay_channel->begin() + frames_per_buffer_ - remaining_size_read, + buffer->begin() + remaining_size_read); + } +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/delay_filter.h b/src/3rdparty/resonance-audio/resonance_audio/dsp/delay_filter.h new file mode 100644 index 000000000..be7c49774 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/delay_filter.h @@ -0,0 +1,85 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_DSP_DELAY_FILTER_H_ +#define RESONANCE_AUDIO_DSP_DELAY_FILTER_H_ + +#include <algorithm> +#include <memory> + +#include "base/audio_buffer.h" + +namespace vraudio { + +// Single channel delay line class. Delays input buffer data by a non-negative +// integer number of samples. +// +// This implementation is not thread safe. The ClearBuffer() and InsertData() +// functions should not be called by a seperate thread during GetDelayedData(). +class DelayFilter { + public: + // Constructs a DelayFilter. + // + // @param max_delay_length Maximum number of samples the input should be + // delayed by. + // @param frames_per_buffer Number of frames in each processed buffer. + DelayFilter(size_t max_delay_length, size_t frames_per_buffer); + + // Sets the maximum delay length. It will allocate more space in the + // |delay_line_| if the new |max_delay_length| is more than doubled. + // + // @param max_delay_length New maximum delay in samples. + void SetMaximumDelay(size_t max_delay_length); + + // Returns the current length of the |delay_line_|. + size_t GetDelayBufferLength() const { return delay_line_->num_frames(); } + + // Returns the current maximum delay applicable to input buffers. + size_t GetMaximumDelayLength() const { return max_delay_length_; } + + // Sets all of the |delay_line_| samples to zero. + void ClearBuffer() { delay_line_->Clear(); } + + // Copies an |AudioBuffer::Channel| of data to the delay line. + // + // @param input Input data. + void InsertData(const AudioBuffer::Channel& input); + + // Fills an |AudioBuffer::Channel| with data delayed by a specified amount + // less than or equal to the delay line's set |max_delay_length_|. + // + // @param delay_samples Requested delay to the data extraced from the delay + // line. Must be less than or equal to |max_delay_length_|. + // @param buffer Pointer to the output data, i.e., delayed input data. + void GetDelayedData(size_t delay_samples, AudioBuffer::Channel* buffer); + + private: + // Maximum length of the delay to be applied (in samples). + size_t max_delay_length_; + + // Number of frames in each AudioBuffer input/output. + size_t frames_per_buffer_; + + // The delay line holding all of the delayed samples. + std::unique_ptr<AudioBuffer> delay_line_; + + // Position in the delay line to begin writing to. + size_t write_cursor_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_DSP_DELAY_FILTER_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/delay_filter_test.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/delay_filter_test.cc new file mode 100644 index 000000000..12a7a8f60 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/delay_filter_test.cc @@ -0,0 +1,197 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/delay_filter.h" + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/constants_and_types.h" + +namespace vraudio { + +namespace { + +// Set of delay lengths to be used in the tests below. +const size_t kInitDelayLength = 4; +const size_t kSecondDelayLength = 5; +const size_t kThirdDelayLength = 2; +const size_t kFourthDelayLength = 11; +const size_t kZeroDelayLength = 0; + +// Frames for buffer for the input and output data buffers below. +const size_t kFramesPerBuffer = 6; + +// Frames per buffer and channel number for the input and output data. +const size_t kFramesPerBuffer2 = 10; + +// Function which passes an AudioBuffer through the delay line followed by all +// zero AudioBuffers to flush the data out. This function then tests that the +// input data has been delayed by the correct amount. +// +// @param delay A pointer to a DelayFilter which will be used in the test. +// @param delay_length An integer delay length to be used in the test. +void IntegerDelayTestHelper(DelayFilter* delay, size_t delay_length) { + std::vector<float> output_collect; + delay->SetMaximumDelay(delay_length); + delay->ClearBuffer(); + + // Initialize mono input buffer and fill with test data. + const std::vector<float> kData = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}; + AudioBuffer input(kNumMonoChannels, kFramesPerBuffer); + auto* input_channel = &input[0]; + *input_channel = kData; + + // Process input. + delay->InsertData(*input_channel); + delay->GetDelayedData(delay_length, input_channel); + output_collect.insert(output_collect.end(), input_channel->begin(), + input_channel->end()); + + // Keep passing zeros through till we flush all of the input data out. + while (delay_length + kFramesPerBuffer > output_collect.size()) { + // Set the |input| AudioBuffer to have all zero data. + input.Clear(); + // Process input. + delay->InsertData(*input_channel); + delay->GetDelayedData(delay_length, input_channel); + output_collect.insert(output_collect.end(), input_channel->begin(), + input_channel->end()); + } + + // Check that the first GetDelayedData() call yields |delay_length| zeros at + // the beginning of its output. + for (size_t i = 0; i < delay_length; ++i) { + EXPECT_EQ(output_collect[i], 0.0f); + } + // Check that the output is the same data as the input but delayed by + // delay_length samples. + for (size_t i = 0; i < kFramesPerBuffer; ++i) { + EXPECT_EQ(output_collect[i + delay_length], kData[i]); + } +} + +// Tests that the maximum_delay_length_ and delay_buffer_size_ are set to the +// correct values on construction and when SetDelay() is called. +TEST(DelayFilterTest, DelayCorrectTest) { + DelayFilter delay(kInitDelayLength, kFramesPerBuffer); + + EXPECT_EQ(delay.GetMaximumDelayLength(), kInitDelayLength); + EXPECT_EQ(delay.GetDelayBufferLength(), kInitDelayLength + kFramesPerBuffer); + + delay.SetMaximumDelay(kSecondDelayLength); + + EXPECT_EQ(delay.GetMaximumDelayLength(), kSecondDelayLength); + EXPECT_EQ(delay.GetDelayBufferLength(), + kSecondDelayLength + kFramesPerBuffer); + + // In this next case we reduce the maximum length and thus do not expect a + // reallocation. + delay.SetMaximumDelay(kThirdDelayLength); + + EXPECT_EQ(delay.GetMaximumDelayLength(), kThirdDelayLength); + EXPECT_EQ(delay.GetDelayBufferLength(), + kSecondDelayLength + kFramesPerBuffer); + + delay.SetMaximumDelay(kFourthDelayLength); + + EXPECT_EQ(delay.GetMaximumDelayLength(), kFourthDelayLength); + // Now we expect the buffer to have been reallocated. + EXPECT_EQ(delay.GetDelayBufferLength(), + kFourthDelayLength + kFramesPerBuffer); +} + +// Tests whether when setting a different delay on one DelayFilter, +// the output is correct in each case. +TEST(DelayFilterTest, DelayTest) { + DelayFilter delay(kInitDelayLength, kFramesPerBuffer); + + IntegerDelayTestHelper(&delay, kInitDelayLength); + + // Tests the case of an increasing delay. + IntegerDelayTestHelper(&delay, kSecondDelayLength); + + // Tests the case of a decreasing delay. + IntegerDelayTestHelper(&delay, kThirdDelayLength); + + // Tests the case of an increasing delay with allocation of more buffer space. + IntegerDelayTestHelper(&delay, kFourthDelayLength); +} + +// Tests that differently delayed buffers can be extracted from a single delay +// line. +TEST(DelayFilterTest, MultipleDelaysTest) { + DelayFilter delay(kFramesPerBuffer2, kFramesPerBuffer2); + AudioBuffer input(kNumMonoChannels, kFramesPerBuffer2); + for (size_t i = 0; i < kFramesPerBuffer2; ++i) { + input[0][i] = static_cast<float>(i + 1); + } + delay.InsertData(input[0]); + const size_t kDelayOne = 1; + const size_t kDelayTwo = 2; + AudioBuffer buffer_1(kNumMonoChannels, kFramesPerBuffer2); + AudioBuffer buffer_2(kNumMonoChannels, kFramesPerBuffer2); + + delay.GetDelayedData(kDelayOne, &buffer_1[0]); + for (size_t i = 0; i < kFramesPerBuffer2 - kDelayOne; ++i) { + EXPECT_NEAR(buffer_1[0][i + kDelayOne], input[0][i], kEpsilonFloat); + } + + delay.GetDelayedData(kDelayTwo, &buffer_2[0]); + for (size_t i = 0; i < kFramesPerBuffer2 - kDelayTwo; ++i) { + EXPECT_NEAR(buffer_2[0][i + kDelayTwo], input[0][i], kEpsilonFloat); + } +} + +// Tests whether a zero delay length is dealt with correctly, Along with a +// negative delay value (treated as zero delay. +TEST(DelayFilterTest, ZeroDelayTest) { + DelayFilter delay(kZeroDelayLength, kFramesPerBuffer); + IntegerDelayTestHelper(&delay, kZeroDelayLength); +} + +// Tests that output from a delay line that is initally large enough vs one that +// is resized is the same. +TEST(DelayFilterTest, InitialSizeVsResizeTest) { + const size_t kSmallMaxDelay = 2; + const size_t kLargeMaxDelay = 5; + const size_t kActualDelay = 4; + + DelayFilter delay_sufficient(kLargeMaxDelay, kFramesPerBuffer); + DelayFilter delay_insufficient(kSmallMaxDelay, kFramesPerBuffer); + + AudioBuffer input_buffer(kNumMonoChannels, kFramesPerBuffer); + for (size_t i = 0; i < kFramesPerBuffer; ++i) { + input_buffer[0][i] = static_cast<float>(i + 1); + } + + delay_sufficient.InsertData(input_buffer[0]); + delay_insufficient.InsertData(input_buffer[0]); + delay_insufficient.SetMaximumDelay(kLargeMaxDelay); + + AudioBuffer buffer_sufficient(kNumMonoChannels, kFramesPerBuffer); + AudioBuffer buffer_insufficient(kNumMonoChannels, kFramesPerBuffer); + + delay_sufficient.GetDelayedData(kActualDelay, &buffer_sufficient[0]); + delay_insufficient.GetDelayedData(kActualDelay, &buffer_insufficient[0]); + + for (size_t i = 0; i < kFramesPerBuffer; ++i) { + EXPECT_NEAR(buffer_sufficient[0][i], buffer_insufficient[0][i], + kEpsilonFloat); + } +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/distance_attenuation.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/distance_attenuation.cc new file mode 100644 index 000000000..816053a7c --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/distance_attenuation.cc @@ -0,0 +1,121 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/distance_attenuation.h" + +#include <algorithm> +#include <cmath> + +#include "base/constants_and_types.h" + +namespace vraudio { + +float ComputeLogarithmicDistanceAttenuation( + const WorldPosition& listener_position, + const WorldPosition& source_position, float min_distance, + float max_distance) { + const float distance = (listener_position - source_position).norm(); + if (distance > max_distance) { + return 0.0f; + } + // Logarithmic attenuation. + const float min_distance_allowed = + std::max(min_distance, kNearFieldThreshold); + if (distance > min_distance_allowed) { + const float attenuation_interval = max_distance - min_distance_allowed; + if (attenuation_interval > kEpsilonFloat) { + // Compute the distance attenuation value by the logarithmic curve + // "1 / (d + 1)" with an offset of |min_distance_allowed|. + const float relative_distance = distance - min_distance_allowed; + const float attenuation = 1.0f / (relative_distance + 1.0f); + // Shift the curve downwards by the attenuation value at |max_distance|, + // and scale the value by the inverse of it in order to keep the curve's + // peak value 1 at |min_distance_allowed|. + const float attenuation_max = 1.0f / (1.0f + attenuation_interval); + return (attenuation - attenuation_max) / (1.0f - attenuation_max); + } + } + return 1.0f; +} + +float ComputeLinearDistanceAttenuation(const WorldPosition& listener_position, + const WorldPosition& source_position, + float min_distance, float max_distance) { + const float distance = (listener_position - source_position).norm(); + if (distance > max_distance) { + return 0.0f; + } + // Linear attenuation. + const float min_distance_allowed = + std::max(min_distance, kNearFieldThreshold); + if (distance > min_distance_allowed) { + const float attenuation_interval = max_distance - min_distance_allowed; + if (attenuation_interval > kEpsilonFloat) { + return (max_distance - distance) / attenuation_interval; + } + } + return 1.0f; +} + +float ComputeNearFieldEffectGain(const WorldPosition& listener_position, + const WorldPosition& source_position) { + const float distance = (listener_position - source_position).norm(); + if (distance < kNearFieldThreshold) { + return (1.0f / std::max(distance, kMinNearFieldDistance)) - 1.0f; + } + return 0.0f; +} + +void UpdateAttenuationParameters(float master_gain, float reflections_gain, + float reverb_gain, + const WorldPosition& listener_position, + SourceParameters* parameters) { + // Compute distance attenuation. + const WorldPosition& source_position = parameters->object_transform.position; + const auto rolloff_model = parameters->distance_rolloff_model; + const float min_distance = parameters->minimum_distance; + const float max_distance = parameters->maximum_distance; + + float distance_attenuation = 0.0f; + switch (rolloff_model) { + case DistanceRolloffModel::kLogarithmic: + distance_attenuation = ComputeLogarithmicDistanceAttenuation( + listener_position, source_position, min_distance, max_distance); + break; + case DistanceRolloffModel::kLinear: + distance_attenuation = ComputeLinearDistanceAttenuation( + listener_position, source_position, min_distance, max_distance); + break; + case DistanceRolloffModel::kNone: + default: + // Distance attenuation is already set by the user. + distance_attenuation = parameters->distance_attenuation; + break; + } + // Update gain attenuations. + const float input_gain = master_gain * parameters->gain; + const float direct_attenuation = input_gain * distance_attenuation; + const float room_effects_attenuation = parameters->room_effects_gain; + + parameters->attenuations[AttenuationType::kInput] = input_gain; + parameters->attenuations[AttenuationType::kDirect] = direct_attenuation; + parameters->attenuations[AttenuationType::kReflections] = + room_effects_attenuation * direct_attenuation * reflections_gain; + parameters->attenuations[AttenuationType::kReverb] = + room_effects_attenuation * input_gain * reverb_gain; +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/distance_attenuation.h b/src/3rdparty/resonance-audio/resonance_audio/dsp/distance_attenuation.h new file mode 100644 index 000000000..2b98ac63c --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/distance_attenuation.h @@ -0,0 +1,82 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_DSP_DISTANCE_ATTENUATION_H_ +#define RESONANCE_AUDIO_DSP_DISTANCE_ATTENUATION_H_ + +#include "base/misc_math.h" +#include "base/source_parameters.h" + +namespace vraudio { + +// Returns the distance attenuation for |source_position| with respect to +// |listener_position|. The amplitude will decrease by approximately 6 dB every +// time the distance is doubled, i.e., the sound pressure "p" (amplitude) +// approximately falls inversely proportional to the distance "1/r". +// +// @param listener_position World position of the listener. +// @param source_position World position of the source. +// @param min_distance The minimum distance at which distance attenuation is +// applied. +// @param max_distance The maximum distance at which the direct sound has a gain +// of 0.0. +// @return Attenuation (gain) value in range [0.0f, 1.0f]. +float ComputeLogarithmicDistanceAttenuation( + const WorldPosition& listener_position, + const WorldPosition& source_position, float min_distance, + float max_distance); + +// Returns the distance attenuation for |source_position| with respect to +// |listener_position|. The amplitude will decrease linearly between +// |min_distance| and |max_distance| from 1.0 to 0.0. +// +// @param listener_position World position of the listener. +// @param source_position World position of the source. +// @param min_distance The minimum distance at which distance attenuation is +// applied. +// @param max_distance The maximum distance at which the direct sound has a gain +// of 0.0. +// @return Attenuation (gain) value in range [0.0f, 1.0f]. +float ComputeLinearDistanceAttenuation(const WorldPosition& listener_position, + const WorldPosition& source_position, + float min_distance, float max_distance); + +// Calculates the gain to be applied to the near field compensating stereo mix. +// This function will return 0.0f for all sources further away than one meter +// and will return a value between 0.0 and 9.0 for sources as they approach +// the listener's head location. +// +// @param listener_position World position of the listener. +// @param source_position World position of the source. +// @return Gain value in range [0.0f, 9.0f]. +float ComputeNearFieldEffectGain(const WorldPosition& listener_position, + const WorldPosition& source_position); + +// Calculates and updates gain attenuations of the given source |parameters|. +// +// @param master_gain Global gain adjustment in amplitude. +// @param reflections_gain Reflections gain in amplitude. +// @param reverb_gain Reverb gain in amplitude. +// @param listener_position World position of the listener. +// @param parameters Source parameters to apply the gain attenuations into. +void UpdateAttenuationParameters(float master_gain, float reflections_gain, + float reverb_gain, + const WorldPosition& listener_position, + SourceParameters* parameters); + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_DSP_DISTANCE_ATTENUATION_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/distance_attenuation_test.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/distance_attenuation_test.cc new file mode 100644 index 000000000..6bccfcabf --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/distance_attenuation_test.cc @@ -0,0 +1,132 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/distance_attenuation.h" + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/constants_and_types.h" +#include "base/misc_math.h" + +namespace vraudio { + +namespace { + +// Tests the logarithmic distance attenuation method against the pre-computed +// results. +TEST(DistanceAttenuationTest, ComputeLogarithmicDistanceAttenuationTest) { + const WorldPosition kListenerPosition(0.0f, 3.0f, 0.0f); + const WorldPosition kSourcePosition(0.0f, 0.0f, -4.0f); + const float kMinDistance_low = 1.0f; + const float kMinDistance_high = 10.0f; + const float kMaxDistance_low = 3.0f; + const float kMaxDistance_high = 500.0f; + const float kExpectedAttenuation_a = 0.1983967f; + const float kExpectedAttenuation_b = 0.0f; + const float kExpectedAttenuation_c = 1.0f; + + float attenuation = ComputeLogarithmicDistanceAttenuation( + kListenerPosition, kSourcePosition, kMinDistance_low, kMaxDistance_high); + EXPECT_NEAR(kExpectedAttenuation_a, attenuation, kEpsilonFloat); + + // Test for the case where the source is beyond the maximum distance. + attenuation = ComputeLogarithmicDistanceAttenuation( + kListenerPosition, kSourcePosition, kMinDistance_low, kMaxDistance_low); + EXPECT_NEAR(kExpectedAttenuation_b, attenuation, kEpsilonFloat); + + // Test for the case where the source is within the minimum distance. + attenuation = ComputeLogarithmicDistanceAttenuation( + kListenerPosition, kSourcePosition, kMinDistance_high, kMaxDistance_high); + EXPECT_NEAR(kExpectedAttenuation_c, attenuation, kEpsilonFloat); +} + +// Tests the linear distance attenuation method against the pre-computed +// results. +TEST(DistanceAttenuationTest, ComputeLinearDistanceAttenuationTest) { + const WorldPosition kListenerPosition(0.0f, 3.0f, 0.0f); + const WorldPosition kSourcePosition(0.0f, 0.0f, -4.0f); + const float kMinDistance_low = 1.0f; + const float kMinDistance_high = 10.0f; + const float kMaxDistance_low = 3.0f; + const float kMaxDistance_high = 8.0f; + const float kExpectedAttenuation_a = 0.4285714f; + const float kExpectedAttenuation_b = 0.0f; + const float kExpectedAttenuation_c = 1.0f; + + float attenuation = ComputeLinearDistanceAttenuation( + kListenerPosition, kSourcePosition, kMinDistance_low, kMaxDistance_high); + EXPECT_NEAR(kExpectedAttenuation_a, attenuation, kEpsilonFloat); + + // Test for the case where the source is beyond the maximum distance. + attenuation = ComputeLinearDistanceAttenuation( + kListenerPosition, kSourcePosition, kMinDistance_low, kMaxDistance_low); + EXPECT_NEAR(kExpectedAttenuation_b, attenuation, kEpsilonFloat); + + // Test for the case where the source is within the minimum distance. + attenuation = ComputeLinearDistanceAttenuation( + kListenerPosition, kSourcePosition, kMinDistance_high, kMaxDistance_high); + EXPECT_NEAR(kExpectedAttenuation_c, attenuation, kEpsilonFloat); +} + +// Tests the gain attenuations update method against the pre-computed results. +TEST(DistanceAttenuationTest, UpdateAttenuationParametersTest) { + const float kMasterGain = 0.5f; + const float kReflectionsGain = 0.5f; + const float kReverbGain = 2.0f; + const WorldPosition kListenerPosition(0.0f, 0.0f, 0.0f); + const WorldPosition kSourcePosition(0.0f, 0.0f, 1.5f); + const float kDistanceAttenuation = 0.2f; + const float kRoomEffectsGain = 0.25f; + + // Initialize new |SourceParameters| with an explicit distance attenuation + // value to avoid extra complexity. + SourceParameters parameters; + parameters.distance_rolloff_model = DistanceRolloffModel::kNone; + parameters.distance_attenuation = kDistanceAttenuation; + parameters.object_transform.position = kSourcePosition; + parameters.room_effects_gain = kRoomEffectsGain; + // Update the gain attenuation parameters. + UpdateAttenuationParameters(kMasterGain, kReflectionsGain, kReverbGain, + kListenerPosition, ¶meters); + // Check the attenuation parameters against the pre-computed values. + const size_t num_attenuations = + static_cast<size_t>(AttenuationType::kNumAttenuationTypes); + const float kExpectedAttenuations[num_attenuations] = {0.5f, 0.1f, 0.0125f, + 0.25f}; + for (size_t i = 0; i < num_attenuations; ++i) { + EXPECT_NEAR(kExpectedAttenuations[i], parameters.attenuations[i], + kEpsilonFloat) + << "Attenuation " << i; + } +} + +// Tests the near field effects gain computation method against the pre-computed +// results. +TEST(NearFieldEffectTest, ComputeNearFieldEffectTest) { + const WorldPosition kListenerPosition(0.0f, 3.0f, 0.0f); + const WorldPosition kSourcePosition_a(0.0f, 0.0f, -4.0f); + const WorldPosition kSourcePosition_b(0.0f, 2.5f, 0.0f); + const float kExpectedGain_a = 0.0f; + const float kExpectedGain_b = 1.0f; + + float gain = ComputeNearFieldEffectGain(kListenerPosition, kSourcePosition_a); + EXPECT_NEAR(kExpectedGain_a, gain, kEpsilonFloat); + gain = ComputeNearFieldEffectGain(kListenerPosition, kSourcePosition_b); + EXPECT_NEAR(kExpectedGain_b, gain, kEpsilonFloat); +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/fft_manager.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/fft_manager.cc new file mode 100644 index 000000000..5be470b17 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/fft_manager.cc @@ -0,0 +1,209 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/fft_manager.h" + +#include <algorithm> + +#include "base/constants_and_types.h" +#include "base/logging.h" +#include "base/misc_math.h" +#include "base/simd_utils.h" + + +// Prevent Visual Studio from complaining about std::copy_n. +#if defined(_WIN32) +#define _SCL_SECURE_NO_WARNINGS +#endif + +namespace vraudio { + +namespace { + +// for |fft_size_|s less than 2^14, the stack is used, on the reccomendation of +// the author of the pffft library. +const size_t kPffftMaxStackSize = 16384; + +} // namespace + +// The pffft implementation requires a minimum fft size of 32 samples. +const size_t FftManager::kMinFftSize = 32; + +FftManager::FftManager(size_t frames_per_buffer) + : fft_size_(std::max(NextPowTwo(frames_per_buffer) * 2, kMinFftSize)), + frames_per_buffer_(frames_per_buffer), + inverse_fft_scale_(1.0f / static_cast<float>(fft_size_)), + temp_zeropad_buffer_(kNumMonoChannels, fft_size_), + temp_freq_buffer_(kNumMonoChannels, fft_size_) { + DCHECK_GT(frames_per_buffer, 0U); + DCHECK_GE(fft_size_, kMinFftSize); + DCHECK(!(fft_size_ & (fft_size_ - 1))); // Ensure its a power of two. + // Suggested pffft initialization. + if (fft_size_ > kPffftMaxStackSize) { + // Allocate memory for work space factors etc, Size reccomended by pffft. + const size_t num_bytes = 2 * fft_size_ * sizeof(float); + pffft_workspace_ = + reinterpret_cast<float*>(pffft_aligned_malloc(num_bytes)); + } + + fft_ = pffft_new_setup(static_cast<int>(fft_size_), PFFFT_REAL); + + temp_zeropad_buffer_.Clear(); +} + +FftManager::~FftManager() { + pffft_destroy_setup(fft_); + if (pffft_workspace_ != nullptr) { + pffft_aligned_free(pffft_workspace_); + } +} + +void FftManager::FreqFromTimeDomain(const AudioBuffer::Channel& time_channel, + AudioBuffer::Channel* freq_channel) { + + + DCHECK(freq_channel); + DCHECK_EQ(freq_channel->size(), fft_size_); + DCHECK_LE(time_channel.size(), fft_size_); + + // Perform forward FFT transform. + if (time_channel.size() == fft_size_) { + pffft_transform(fft_, time_channel.begin(), freq_channel->begin(), + pffft_workspace_, PFFFT_FORWARD); + } else { + std::copy_n(time_channel.begin(), frames_per_buffer_, + temp_zeropad_buffer_[0].begin()); + pffft_transform(fft_, temp_zeropad_buffer_[0].begin(), + freq_channel->begin(), pffft_workspace_, PFFFT_FORWARD); + } +} + +void FftManager::TimeFromFreqDomain(const AudioBuffer::Channel& freq_channel, + AudioBuffer::Channel* time_channel) { + + + DCHECK(time_channel); + DCHECK_EQ(freq_channel.size(), fft_size_); + + // Perform reverse FFT transform. + const size_t time_channel_size = time_channel->size(); + if (time_channel_size == fft_size_) { + pffft_transform(fft_, freq_channel.begin(), time_channel->begin(), + pffft_workspace_, PFFFT_BACKWARD); + } else { + DCHECK_EQ(time_channel_size, frames_per_buffer_); + auto& temp_channel = temp_freq_buffer_[0]; + pffft_transform(fft_, freq_channel.begin(), temp_channel.begin(), + pffft_workspace_, PFFFT_BACKWARD); + std::copy_n(temp_channel.begin(), frames_per_buffer_, + time_channel->begin()); + } +} + +void FftManager::ApplyReverseFftScaling(AudioBuffer::Channel* time_channel) { + DCHECK(time_channel->size() == frames_per_buffer_ || + time_channel->size() == fft_size_); + // Normalization must be performed here as we normally do this as part of the + // convolution. + ScalarMultiply(time_channel->size(), inverse_fft_scale_, + time_channel->begin(), time_channel->begin()); +} + +void FftManager::GetCanonicalFormatFreqBuffer(const AudioBuffer::Channel& input, + AudioBuffer::Channel* output) { + DCHECK_EQ(input.size(), fft_size_); + DCHECK_EQ(output->size(), fft_size_); + pffft_zreorder(fft_, input.begin(), output->begin(), PFFFT_FORWARD); +} + +void FftManager::GetPffftFormatFreqBuffer(const AudioBuffer::Channel& input, + AudioBuffer::Channel* output) { + DCHECK_EQ(input.size(), fft_size_); + DCHECK_EQ(output->size(), fft_size_); + pffft_zreorder(fft_, input.begin(), output->begin(), PFFFT_BACKWARD); +} + +void FftManager::MagnitudeFromCanonicalFreqBuffer( + const AudioBuffer::Channel& freq_channel, + AudioBuffer::Channel* magnitude_channel) { + DCHECK(magnitude_channel); + DCHECK_EQ(freq_channel.size(), fft_size_); + DCHECK_EQ(magnitude_channel->size(), frames_per_buffer_ + 1); + + (*magnitude_channel)[0] = std::abs(freq_channel[0]); + ApproxComplexMagnitude(frames_per_buffer_ - 1, freq_channel.begin() + 2, + magnitude_channel->begin() + 1); + (*magnitude_channel)[frames_per_buffer_] = std::abs(freq_channel[1]); +} + +void FftManager::CanonicalFreqBufferFromMagnitudeAndPhase( + const AudioBuffer::Channel& magnitude_channel, + const AudioBuffer::Channel& phase_channel, + AudioBuffer::Channel* canonical_freq_channel) { + DCHECK(canonical_freq_channel); + DCHECK_EQ(magnitude_channel.size(), frames_per_buffer_ + 1); + DCHECK_EQ(phase_channel.size(), frames_per_buffer_ + 1); + DCHECK_EQ(canonical_freq_channel->size(), fft_size_); + + (*canonical_freq_channel)[0] = magnitude_channel[0]; + (*canonical_freq_channel)[1] = -magnitude_channel[frames_per_buffer_]; + for (size_t i = 1, j = 2; i < frames_per_buffer_; ++i, j += 2) { + (*canonical_freq_channel)[j] = + magnitude_channel[i] * std::cos(phase_channel[i]); + (*canonical_freq_channel)[j + 1] = + magnitude_channel[i] * std::sin(phase_channel[i]); + } +} + +void FftManager::CanonicalFreqBufferFromMagnitudeAndSinCosPhase( + size_t phase_offset, const AudioBuffer::Channel& magnitude_channel, + const AudioBuffer::Channel& sin_phase_channel, + const AudioBuffer::Channel& cos_phase_channel, + AudioBuffer::Channel* canonical_freq_channel) { + static const size_t kSimdLength = 4; + DCHECK(canonical_freq_channel); + DCHECK_EQ(magnitude_channel.size(), frames_per_buffer_ + 1); + DCHECK_GE(sin_phase_channel.size() + phase_offset, frames_per_buffer_ + 1); + DCHECK_GE(cos_phase_channel.size() + phase_offset, frames_per_buffer_ + 1); + DCHECK_EQ(canonical_freq_channel->size(), fft_size_); + + (*canonical_freq_channel)[0] = magnitude_channel[0]; + (*canonical_freq_channel)[1] = -magnitude_channel[frames_per_buffer_]; + // Continue on till we can gaurantee alignment in our audio buffer. + for (size_t i = 1, j = 2; i <= kSimdLength; ++i, j += 2) { + (*canonical_freq_channel)[j] = + magnitude_channel[i] * cos_phase_channel[i + phase_offset]; + (*canonical_freq_channel)[j + 1] = + magnitude_channel[i] * sin_phase_channel[i + phase_offset]; + } + ComplexInterleavedFormatFromMagnitudeAndSinCosPhase( + 2 * (frames_per_buffer_ - kSimdLength), &magnitude_channel[kSimdLength], + &cos_phase_channel[kSimdLength + phase_offset], + &sin_phase_channel[kSimdLength + phase_offset], + &(*canonical_freq_channel)[2 * kSimdLength]); +} + +void FftManager::FreqDomainConvolution(const AudioBuffer::Channel& input_a, + const AudioBuffer::Channel& input_b, + AudioBuffer::Channel* scaled_output) { + DCHECK_EQ(input_a.size(), fft_size_); + DCHECK_EQ(input_b.size(), fft_size_); + DCHECK_EQ(scaled_output->size(), fft_size_); + pffft_zconvolve_accumulate(fft_, input_a.begin(), input_b.begin(), + scaled_output->begin(), inverse_fft_scale_); +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/fft_manager.h b/src/3rdparty/resonance-audio/resonance_audio/dsp/fft_manager.h new file mode 100644 index 000000000..d7869a3d7 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/fft_manager.h @@ -0,0 +1,182 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_DSP_FFT_MANAGER_H_ +#define RESONANCE_AUDIO_DSP_FFT_MANAGER_H_ + +#include "pffft.h" +#include "base/audio_buffer.h" + +namespace vraudio { + +// This class wraps the pffft library and enables reall FFT transformations to +// be performed on aligned float buffers of data. The class also manages all +// necessary data buffers and zero padding. This class is not thread safe. +class FftManager { + public: + // Minimum required FFT size. + static const size_t kMinFftSize; + + // Constructs a FftManager insatnce. One instance of this class can be shared. + // This class is not thread safe. + // + // @param frames_per_buffer System's number of frames per buffer. + explicit FftManager(size_t frames_per_buffer); + + // Destroys a FftManager instance freeing associated aligned memory. + ~FftManager(); + + // Transforms a single channel of time domain input data into a frequency + // domain representation. + // + // @param time_channel Time domain input. If the length is less than + // |fft_size_|, the input is zeropadded. The max length is |fft_size_|. + // @param freq_channel Frequency domain output, |fft_size| samples long. + void FreqFromTimeDomain(const AudioBuffer::Channel& time_channel, + AudioBuffer::Channel* freq_channel); + + // Transforms a single channel of frequency domain input data into a time + // domain representation. Note: The input must be in pffft format see: + // goo.gl/LYbgX7. This method can output to a buffer of either + // |frames_per_buffer_| or |fft_size_| in length. This feature ensures an + // additional copy is not needed where this method is to be used with an + // overlap add. + // + // @param freq_channel Frequency domain input, |fft_size| samples long. + // @param time_channel Time domain output, |frames_per_buffer_| samples long + // OR |fft_size_| samples long. + void TimeFromFreqDomain(const AudioBuffer::Channel& freq_channel, + AudioBuffer::Channel* time_channel); + + // Applies a 1/|fft_size_| scaling to time domain output. NOTE this need not + // be applied where a convolution is taking place as the scaling will be + // included therein. + // + // @param time_channel Time domain data to be scaled. + void ApplyReverseFftScaling(AudioBuffer::Channel* time_channel); + + // Transforms a pffft frequency domain format buffer into canonical format + // with alternating real and imaginary values with increasing frequency. The + // first two entries of |output| are the real part of the DC and Nyquist + // frequencies (imaginary part is zero). The alternating real and imaginary + // parts start from the third entry in |output|. For more info on the pffft + // format see: goo.gl/LYbgX7 + // + // @param input Frequency domain input channel, |fft_size| samples long. + // @param output Frequency domain output channel,|fft_size| samples long. + void GetCanonicalFormatFreqBuffer(const AudioBuffer::Channel& input, + AudioBuffer::Channel* output); + + // Transforms a canonical frequency domain format buffer into pffft format. + // For more info on the pffft format see: goo.gl/LYbgX7 + // + // @param input Frequency domain input channel, |fft_size| samples long. + // @param output Frequency domain output channel, |fft_size| samples long. + void GetPffftFormatFreqBuffer(const AudioBuffer::Channel& input, + AudioBuffer::Channel* output); + + // Genarates a buffer containing the single sided magnitude spectrum of a + // frequency domain buffer. The input must be in Canonical format. The output + // will have DC frequency as it's first entry and the Nyquist as it's last. + // + // @param freq_channel Canonical format frequency domain buffer, + // |fft_size_| samples long. + // @param magnitude_channel Magnitude of the |freq_channel|. + // |frames_per_buffer_| + 1 samples long. + void MagnitudeFromCanonicalFreqBuffer( + const AudioBuffer::Channel& freq_channel, + AudioBuffer::Channel* magnitude_channel); + + // Combines single sided magnitude and phase spectra into a canonical format + // frequency domain buffer. The inputs must have DC frequency as their first + // entry and the Nyquist as their last. + // + // @param magnitude_channel Magnitude of the |frequency_buffer|. + // |frames_per_buffer_| + 1 samples long. + // @param phase_channel Phase of the |frequency_buffer|. + // |frames_per_buffer_| + 1 samples long. + // @param canonical_freq_channel Canonical format frequency domain buffer, + // |fft_size_| samples long. + void CanonicalFreqBufferFromMagnitudeAndPhase( + const AudioBuffer::Channel& magnitude_channel, + const AudioBuffer::Channel& phase_channel, + AudioBuffer::Channel* canonical_freq_channel); + + // Combines single sided magnitude spectrum and the cosine and sine of a phase + // spectrum into a canonical format frequency domain buffer. The inputs must + // have DC frequency as their first entry and the Nyquist as their last. + // The phase spectra channels can be offset by |phase_offset|. This feature + // is specifically for use as an optimization in the |SpectralReverb|. + // + // @param phase_offset An offset into the channels of the phase buffer. + // @param magnitude_channel Magnitude of the |frequency_buffer|. + // |frames_per_buffer_| + 1 samples long. + // @param sin_phase_channel Sine of the phase of the |frequency_buffer|. + // |frames_per_buffer_| + 1 samples long. + // @param cos_phase_channel Cosine of the phase of the |frequency_buffer|. + // |frames_per_buffer_| + 1 samples long. + // @param canonical_freq_channel Canonical format frequency domain buffer, + // |fft_size_| samples long. + void CanonicalFreqBufferFromMagnitudeAndSinCosPhase( + size_t phase_offset, const AudioBuffer::Channel& magnitude_channel, + const AudioBuffer::Channel& sin_phase_channel, + const AudioBuffer::Channel& cos_phase_channel, + AudioBuffer::Channel* canonical_freq_channel); + + // Performs a pointwise complex multiplication of two frequency domain buffers + // and applies tha inverse scaling factor of 1/|fft_size_|. This operation is + // equivalent to a time domain circular convolution. + // + // @param input_a Frequency domain input channel, |fft_size| samples long. + // @param input_b Frequency domain input channel, |fft_size| samples long. + // @param scaled_output Frequency domain output channel, |fft_size| samples + // long. + void FreqDomainConvolution(const AudioBuffer::Channel& input_a, + const AudioBuffer::Channel& input_b, + AudioBuffer::Channel* scaled_output); + + // Returns the number of points in the FFT. + size_t GetFftSize() const { return fft_size_; } + + private: + // FFT size in samples. + const size_t fft_size_; + + // Number of frames in each buffer of input data. + const size_t frames_per_buffer_; + + // Inverse scale to be applied to buffers transformed from frequency to time + // domain. + const float inverse_fft_scale_; + + // Temporary time domain buffer to store zeropadded input. + AudioBuffer temp_zeropad_buffer_; + + // Temporary freq domain buffer to store. + AudioBuffer temp_freq_buffer_; + + // pffft states. + PFFFT_Setup* fft_; + + // Workspace for pffft. This pointer should be set to null for |fft_size_| + // less than 2^14. In which case the stack is used. This is the recommendation + // by the author of the pffft library. + float* pffft_workspace_ = nullptr; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_DSP_FFT_MANAGER_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/fft_manager_test.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/fft_manager_test.cc new file mode 100644 index 000000000..e96ed231c --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/fft_manager_test.cc @@ -0,0 +1,260 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/fft_manager.h" + +#include <cstdlib> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/audio_buffer.h" +#include "base/constants_and_types.h" +#include "base/logging.h" +#include "base/misc_math.h" + +namespace vraudio { + +namespace { + +const float kInverseFftEpsilon = 2e-5f; + +// This tests that the |FreqFromTimeDomain| and |TimeFromFreqDomain| +// functions are the inverse of one another for a number of fft sizes and signal +// types. +TEST(FftManagerTest, FftIfftTest) { + // Generate a test signal. + const size_t kNumSignalTypes = 3; + const size_t kNumBufferLengths = 10; + const size_t kBufferLengths[kNumBufferLengths] = {31, 32, 63, 64, 127, + 128, 255, 256, 511, 512}; + + for (size_t length_idx = 0; length_idx < kNumBufferLengths; length_idx++) { + for (size_t type = 0; type < kNumSignalTypes; ++type) { + AudioBuffer time_signal(kNumMonoChannels, kBufferLengths[length_idx]); + for (size_t i = 0; i < kBufferLengths[length_idx]; ++i) { + switch (type) { + case 0: + time_signal[0][i] = static_cast<float>(i) / + static_cast<float>(kBufferLengths[length_idx]); + break; + case 1: + time_signal[0][i] = std::cos(static_cast<float>(i)); + break; + case 2: + time_signal[0][i] = static_cast<float>(i % 2) * -0.5f; + break; + } + } + AudioBuffer freq_signal(kNumMonoChannels, + NextPowTwo(kBufferLengths[length_idx]) * 2); + AudioBuffer output(kNumMonoChannels, kBufferLengths[length_idx]); + output.Clear(); + + FftManager fft_manager(kBufferLengths[length_idx]); + + fft_manager.FreqFromTimeDomain(time_signal[0], &freq_signal[0]); + fft_manager.TimeFromFreqDomain(freq_signal[0], &output[0]); + fft_manager.ApplyReverseFftScaling(&output[0]); + + for (size_t i = 0; i < kBufferLengths[length_idx]; ++i) { + EXPECT_NEAR(output[0][i], time_signal[0][i], kEpsilonFloat); + } + } + } +} + +// Tests that the result from an inverse FFT is the same whether it is written +// into a buffer of |frames_per_buffer_| or |fft_size_| in length. +TEST(FftManagerTest, ReverseFftOutputSizeTest) { + const size_t kFramesPerBuffer = 32; + AudioBuffer freq_buffer(kNumMonoChannels, 2 * kFramesPerBuffer); + freq_buffer.Clear(); + AudioBuffer time_buffer_short(kNumMonoChannels, kFramesPerBuffer); + time_buffer_short.Clear(); + AudioBuffer time_buffer_long(kNumMonoChannels, 2 * kFramesPerBuffer); + time_buffer_long.Clear(); + AudioBuffer input_buffer(kNumMonoChannels, kFramesPerBuffer); + + std::srand(0); + for (auto& sample : input_buffer[0]) { + sample = static_cast<float>(std::rand()) / static_cast<float>(RAND_MAX); + } + + FftManager fft_manager(kFramesPerBuffer); + fft_manager.FreqFromTimeDomain(input_buffer[0], &freq_buffer[0]); + fft_manager.TimeFromFreqDomain(freq_buffer[0], &time_buffer_short[0]); + fft_manager.ApplyReverseFftScaling(&time_buffer_short[0]); + fft_manager.TimeFromFreqDomain(freq_buffer[0], &time_buffer_long[0]); + fft_manager.ApplyReverseFftScaling(&time_buffer_long[0]); + + for (size_t i = 0; i < kFramesPerBuffer; ++i) { + EXPECT_NEAR(time_buffer_short[0][i], time_buffer_long[0][i], kEpsilonFloat); + EXPECT_NEAR(time_buffer_long[0][i + kFramesPerBuffer], 0.0f, + kInverseFftEpsilon); + } +} + +// Tests that a frequency domain buffer can be transformed into a canonical +// format and back. +TEST(FftManagerTest, PffftFormatToCanonicalFormatTest) { + const size_t kFramesPerBuffer = 32; + AudioBuffer time_buffer(kNumMonoChannels, kFramesPerBuffer); + time_buffer.Clear(); + time_buffer[0][0] = 1.0f; + time_buffer[0][1] = 1.0f; + AudioBuffer freq_buffer(kNumMonoChannels, 2 * kFramesPerBuffer); + freq_buffer.Clear(); + AudioBuffer reordered_buffer(kNumMonoChannels, 2 * kFramesPerBuffer); + reordered_buffer.Clear(); + AudioBuffer final_buffer(kNumMonoChannels, 2 * kFramesPerBuffer); + reordered_buffer.Clear(); + + FftManager fft_manager(kFramesPerBuffer); + fft_manager.FreqFromTimeDomain(time_buffer[0], &freq_buffer[0]); + + fft_manager.GetCanonicalFormatFreqBuffer(freq_buffer[0], + &reordered_buffer[0]); + fft_manager.GetPffftFormatFreqBuffer(reordered_buffer[0], &final_buffer[0]); + + for (size_t i = 0; i < kFramesPerBuffer * 2; ++i) { + EXPECT_NEAR(final_buffer[0][i], freq_buffer[0][i], kEpsilonFloat); + } +} + +// Tests that for a scaled kronecker delta, the magnitude response will be flat +// and equal to the absolute magnitude of the kronecker. +TEST(FftManagerTest, MagnitudeTest) { + const size_t kFramesPerBuffer = 32; + const size_t kMagnitudeLength = kFramesPerBuffer + 1; + FftManager fft_manager(kFramesPerBuffer); + AudioBuffer time_buffer(kNumMonoChannels, kFramesPerBuffer); + time_buffer.Clear(); + AudioBuffer freq_buffer(kNumMonoChannels, 2 * kFramesPerBuffer); + AudioBuffer reordered_buffer(kNumMonoChannels, 2 * kFramesPerBuffer); + AudioBuffer magnitude_buffer(kNumMonoChannels, kMagnitudeLength); + const std::vector<float> magnitudes = {1.0f, 2.0f, 3.0f, -1.0f, -2.0f, -3.0f}; + + for (auto& magnitude : magnitudes) { + time_buffer[0][0] = magnitude; + fft_manager.FreqFromTimeDomain(time_buffer[0], &freq_buffer[0]); + fft_manager.GetCanonicalFormatFreqBuffer(freq_buffer[0], + &reordered_buffer[0]); + fft_manager.MagnitudeFromCanonicalFreqBuffer(reordered_buffer[0], + &magnitude_buffer[0]); + for (size_t sample = 0; sample < kMagnitudeLength; ++sample) { + // Check its correct to within 0.5%. + const float kErrEpsilon = 5e-3f; + const float expected = std::abs(magnitude); + EXPECT_NEAR(magnitude_buffer[0][sample], expected, + kErrEpsilon * expected); + } + } +} + +// Tests that conversion from Canonical frequency domain data to phase and +// magnitude spectra and back results in an output equal to the input. +TEST(FftManagerTest, FreqFromMagnitudePhase) { + const size_t kFramesPerBuffer = 16; + const size_t kMagnitudePhaseLength = kFramesPerBuffer + 1; + FftManager fft_manager(kFramesPerBuffer); + AudioBuffer time_buffer(kNumMonoChannels, kFramesPerBuffer); + time_buffer.Clear(); + time_buffer[0][0] = 0.5f; + time_buffer[0][1] = 1.0f; + AudioBuffer freq_buffer(kNumMonoChannels, 2 * kFramesPerBuffer); + AudioBuffer reordered_buffer(kNumMonoChannels, 2 * kFramesPerBuffer); + AudioBuffer phase_buffer(kNumMonoChannels, kMagnitudePhaseLength); + AudioBuffer magnitude_buffer(kNumMonoChannels, kMagnitudePhaseLength); + fft_manager.FreqFromTimeDomain(time_buffer[0], &freq_buffer[0]); + fft_manager.GetCanonicalFormatFreqBuffer(freq_buffer[0], + &reordered_buffer[0]); + fft_manager.MagnitudeFromCanonicalFreqBuffer(reordered_buffer[0], + &magnitude_buffer[0]); + + // Calculate the phase. + phase_buffer[0][0] = 0.0f; + for (size_t i = 1, j = 2; i < kFramesPerBuffer; ++i, j += 2) { + phase_buffer[0][i] = std::atan2(reordered_buffer[0][j + 1] /*imag*/, + reordered_buffer[0][j] /*real*/); + } + phase_buffer[0][kFramesPerBuffer] = kPi; + + fft_manager.CanonicalFreqBufferFromMagnitudeAndPhase( + magnitude_buffer[0], phase_buffer[0], &freq_buffer[0]); + + for (size_t sample = 0; sample < kFramesPerBuffer * 2; ++sample) { + // Check its correct to within 0.5%. + const float kErrEpsilon = 5e-3f; + EXPECT_NEAR(freq_buffer[0][sample], reordered_buffer[0][sample], + kErrEpsilon * std::abs(reordered_buffer[0][sample])); + } +} + +// Tests that conversion from phase and magnitude spectra and back results in +// an output equal to that from sine and cosine phase, using SIMD on arm. +TEST(FftManagerTest, FMagnitudePhaseAndSineCosinePhase) { + const size_t kFramesPerBuffer = 16; + const size_t kMagnitudePhaseLength = kFramesPerBuffer + 1; + FftManager fft_manager(kFramesPerBuffer); + AudioBuffer freq_buffer_one(kNumMonoChannels, 2 * kFramesPerBuffer); + AudioBuffer freq_buffer_two(kNumMonoChannels, 2 * kFramesPerBuffer); + AudioBuffer phase_buffer(kNumMonoChannels, kMagnitudePhaseLength); + AudioBuffer sin_phase_buffer(kNumMonoChannels, kMagnitudePhaseLength); + AudioBuffer cos_phase_buffer(kNumMonoChannels, kMagnitudePhaseLength); + AudioBuffer magnitude_buffer(kNumMonoChannels, kMagnitudePhaseLength); + + std::fill(magnitude_buffer[0].begin(), magnitude_buffer[0].end(), 2.0f); + phase_buffer[0] = std::vector<float>( + {0.4720f, 1.6100f, -1.9831f, 0.7569f, 0.2799f, -1.1481f, -0.3807f, + 0.3008f, 3.1416f, 2.4314f, -1.1851f, 2.6645f, 0.6369f, -0.0554f, 0.6275f, + -0.1799f, 1.2345f}); + for (size_t i = 0; i < phase_buffer.num_frames(); ++i) { + sin_phase_buffer[0][i] = std::sin(phase_buffer[0][i]); + cos_phase_buffer[0][i] = std::cos(phase_buffer[0][i]); + } + + fft_manager.CanonicalFreqBufferFromMagnitudeAndPhase( + magnitude_buffer[0], phase_buffer[0], &freq_buffer_one[0]); + fft_manager.CanonicalFreqBufferFromMagnitudeAndSinCosPhase( + 0, /* phase_offset */ + magnitude_buffer[0], sin_phase_buffer[0], cos_phase_buffer[0], + &freq_buffer_two[0]); + + for (size_t i = 0; i < kFramesPerBuffer * 2; ++i) { + EXPECT_NEAR(freq_buffer_one[0][i], freq_buffer_two[0][i], kEpsilonFloat); + } +} + +// Tests that the correct scaling factor is applied consistently across a time +// domain buffer. +TEST(FftManagerTest, ReverseScalingTest) { + const size_t kFramesPerBuffer = 128; + const float kExpectedScale = 1.0f / static_cast<float>(2 * kFramesPerBuffer); + + AudioBuffer buffer(kNumMonoChannels, kFramesPerBuffer); + for (auto& sample : buffer[0]) { + sample = 1.0f; + } + FftManager fft_manager(kFramesPerBuffer); + + fft_manager.ApplyReverseFftScaling(&buffer[0]); + for (auto& sample : buffer[0]) { + EXPECT_NEAR(sample, kExpectedScale, kEpsilonFloat); + } +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/filter_coefficient_generators.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/filter_coefficient_generators.cc new file mode 100644 index 000000000..11d19694d --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/filter_coefficient_generators.cc @@ -0,0 +1,165 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/filter_coefficient_generators.h" + +#include <algorithm> +#include <cmath> + +#include "base/constants_and_types.h" + +namespace vraudio { + +namespace { + +// Hard coded 4th order fit calculated from: + +const int kPolynomialOrder = 4; +const float kPolynomialCoefficients[] = { + 2.52730826376129e-06f, 0.00018737263228963f, 0.00618822707412605f, + 0.113947188715447f, 0.999048314740445f}; + +// Threshold frequency for human hearing 20Hz. +const float kTwentyHz = 20.0f; + +// ln(2)/2 for use in bandpass filter. +const float kLn2over2 = 0.346573590279973f; + +} // namespace + +BiquadCoefficients ComputeBandPassBiquadCoefficients(int sample_rate, + float center_frequency, + int bandwidth) { + DCHECK_GT(sample_rate, 0); + DCHECK_GE(center_frequency, 0.0f); + DCHECK_GT(bandwidth, 0); + // Check if an invalid |center_frequency|, greater than or equal to the + // Nyquist rate is passed as input. + CHECK_LT(center_frequency, 0.5f * static_cast<float>(sample_rate)); + + // Frequency of interest (in radians). + const float w0 = kTwoPi * center_frequency / static_cast<float>(sample_rate); + // Intermediate storage. + const float cos_w0 = std::cos(w0); + const float sin_w0 = std::sin(w0); + const float alpha = + sin_w0 * sinhf(kLn2over2 * static_cast<float>(bandwidth) * w0 / sin_w0); + + // The BiquadFilterState constructor is passed (a0, a1, a2, b0, b1, b2). + return BiquadCoefficients(1.0f + alpha, -2.0f * cos_w0, 1.0f - alpha, alpha, + 0.0f, -alpha); +} + +void ComputeDualBandBiquadCoefficients( + int sample_rate, float crossover_frequency, + BiquadCoefficients* low_pass_coefficients, + BiquadCoefficients* high_pass_coefficients) { + DCHECK_GT(sample_rate, 0); + DCHECK_GE(crossover_frequency, 0.0f); + DCHECK_LE(crossover_frequency, static_cast<float>(sample_rate) / 2.0f); + DCHECK(low_pass_coefficients); + DCHECK(high_pass_coefficients); + + const float k = std::tan(static_cast<float>(M_PI) * crossover_frequency / + static_cast<float>(sample_rate)); + const float k_squared = k * k; + const float denominator = k_squared + 2.0f * k + 1.0f; + + // |denominator| must not be near 0. Since |k| is always guaranteed to be in + // the range 0 < k < pi/2, the |denominator| should always be >=1. This is a + // sanity check only. + DCHECK_GT(denominator, kEpsilonDouble); + + // Computes numerator coefficients of the low-pass |low_pass_coefficients| + // bi-quad. + low_pass_coefficients->a[0] = 1.0f; + low_pass_coefficients->a[1] = 2.0f * (k_squared - 1.0f) / denominator; + low_pass_coefficients->a[2] = (k_squared - 2.0f * k + 1.0f) / denominator; + + // Numerator coefficients of the high-pass |high_pass_coefficients| bi-quad + // are the same. + BiquadCoefficients high_pass; + std::copy(low_pass_coefficients->a.begin(), low_pass_coefficients->a.end(), + high_pass_coefficients->a.begin()); + + // Computes denominator coefficients of the low-pass |low_pass_coefficients| + // bi-quad. + low_pass_coefficients->b[0] = k_squared / denominator; + low_pass_coefficients->b[1] = 2.0f * low_pass_coefficients->b[0]; + low_pass_coefficients->b[2] = low_pass_coefficients->b[0]; + + // Computes denominator coefficients of the high-pass |high_pass_coefficients| + // bi-quad. + high_pass_coefficients->b[0] = 1.0f / denominator; + high_pass_coefficients->b[1] = -2.0f * high_pass_coefficients->b[0]; + high_pass_coefficients->b[2] = high_pass_coefficients->b[0]; +} + +BiquadCoefficients ComputeLowPassBiquadCoefficients( + int sample_rate, float specification_frequency, float attenuation) { + DCHECK_GT(sample_rate, 0); + DCHECK_GE(specification_frequency, 0.0f); + DCHECK_LE(specification_frequency, static_cast<float>(sample_rate) / 2.0f); + DCHECK_LT(attenuation, 0.0f); + + // Frequency of interest (in radians). + const float w0 = + kTwoPi * specification_frequency / static_cast<float>(sample_rate); + + // Q is the Q-factor. For more information see "Digital Signal Processing" - + // J. G. Prolakis, D. G. Manolakis - Published by Pearson. + float Q = 0.0f; + + // Variable to handle the growth in power as one extra mult per iteration + // across the polynomial coefficients. + float attenuation_to_a_power = 1.0f; + + // Add in each term in reverse order. + for (int order = kPolynomialOrder; order >= 0; --order) { + Q += kPolynomialCoefficients[order] * attenuation_to_a_power; + attenuation_to_a_power *= attenuation; + } + + // Intermediate storage of commonly used values. + const float alpha = std::sin(w0) / (2.0f * Q); + const float cos_w0 = std::cos(w0); + + // Filter coefficients. + float a0 = 1.0f + alpha; + float a1 = -2.0f * cos_w0; + float a2 = 1.0f - alpha; + // Coefficients b0 and b2 will have the same value in this case. + float b0_b2 = (1.0f - cos_w0) / 2.0f; + float b1 = 1.0f - cos_w0; + + return BiquadCoefficients(a0, a1, a2, b0_b2, b1, b0_b2); +} + +float ComputeLowPassMonoPoleCoefficient(float cuttoff_frequency, + int sample_rate) { + float coefficient = 0.0f; + // Check that the cuttoff_frequency provided is not too low (below human + // threshold, also danger of filter instability). + if (cuttoff_frequency > kTwentyHz) { + const float inverse_time_constant = kTwoPi * cuttoff_frequency; + const float sample_rate_float = static_cast<float>(sample_rate); + coefficient = + sample_rate_float / (inverse_time_constant + sample_rate_float); + } + return coefficient; +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/filter_coefficient_generators.h b/src/3rdparty/resonance-audio/resonance_audio/dsp/filter_coefficient_generators.h new file mode 100644 index 000000000..4c7659a7a --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/filter_coefficient_generators.h @@ -0,0 +1,177 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_DSP_FILTER_COEFFICIENT_GENERATORS_H_ +#define RESONANCE_AUDIO_DSP_FILTER_COEFFICIENT_GENERATORS_H_ + +#include "dsp/biquad_filter.h" + +// Functions for the generation of filter coefficients for various common tasks. +// Currently supports the following filter types: +// Low pass first-order filter. +// Low pass biquad filter. +// Band pass biquad filter. +// Dual band, matched phase biquad shelf filters. +namespace vraudio { + +// Computes corresponding biquad coefficients for band pass filter with respect +// to the centre frequency and the bandwidth between -3 dB frequencies. +// +// b0 + b1*z^-1 + b2*z^-2 +// H(z) = ------------------------ +// a0 + a1*z^-1 + a2*z^-2 +// +// where: +// w0 = 2*pi*center_frequency/sample_rate +// +// alpha = sin(w0)*sinh( ln(2)/2 * bandwidth * w0/sin(w0) ) +// +// b0 = alpha +// b1 = 0 +// b2 = -alpha +// a0 = 1 + alpha +// a1 = -2*cos(w0) +// a2 = 1 - alpha +// +// @param sample_rate Sampling rate in Hz. +// @param centre_frequency Centre frequency of passband. +// @param bandwidth Bandwidth in octaves between -3 dB frequencies. +// @return Output structure of band-pass BiquadCoefficients. +BiquadCoefficients ComputeBandPassBiquadCoefficients(int sample_rate, + float centre_frequency, + int bandwidth); + +// Computes two sets of transfer function coefficients to be used with a +// pair of generic bi-quad filters. The coefficients are used to implement a +// phase-matched low-pass |low_pass_state| and high-pass |high_pass_state| +// filter pair with a cross-over frequency defined as |crossover_frequency|. +// +// Implementation of the matched bi-quad filter pair as described in: +// http://www.ai.sri.com/ajh/ambisonics/BLaH3.pdf +// +// b0 + b1*z^-1 + b2*z^-2 +// H(z) = ------------------------ +// a0 + a1*z^-1 + a2*z^-2 +// +// where: +// +// a0 = 1 +// +// 2(k^2 − 1) +// a1 = -------------- +// k^2 + 2k + 1 +// +// k^2 - 2k + 1 +// a2 = -------------- +// k^2 + 2k + 1 +// +// low-pass: high-pass: +// +// k^2 1 +// b0 = ------------- b0 = -------------- +// k^2 + 2k + 1 k^2 + 2k + 1 +// +// b1 = 2b0 b1 = -2b0 +// +// b2 = b0 b2 = b0 +// +// and +// +// pi * crossover_frequency +// k = tan -------------------------- +// sample_frequency +// +// @param sample_rate Sampling rate in [Hz] +// @param crossover_frequency Cross-over frequency in [Hz] +// @param low_pass_coefficients Output structure of low-pass bi-quad +// coefficients +// @param high_pass_coefficients Output structure of high-pass bi-quad +// coefficients. +void ComputeDualBandBiquadCoefficients( + int sample_rate, float crossover_frequency, + BiquadCoefficients* low_pass_coefficients, + BiquadCoefficients* high_pass_coefficients); + +// Computes biquad coefficients for low pass filter with respect to the +// specification frequency and the attenuation value at that frequency. +// +// b0 + b1*z^-1 + b2*z^-2 +// H(z) = ------------------------ +// a0 + a1*z^-1 + a2*z^-2 +// +// where: +// Q = 2.5273e-06*attenuation^4 + 0.00018737*attenuation^3 + +// 0.0061882*attenuation^2 + 0.11395*attenuation + 0.99905 +// +// w0 = 2*pi*specification_frequency/sample_rate; +// +// alpha = sin(w0)/(2*Q); +// +// a0 = 1 + alpha; +// a1 = -2*cos(w0); +// a2 = 1 - alpha; +// b0 = (1 - cos(w0))/2; +// b1 = 1 - cos(w0); +// b2 = (1 - cos(w0)/2; +// +// These coefficients were generated after a bilinerar transform of an +// analogue filter H(s) = 1 / (s^2 + s/Q + 1), from: +// www.analog.com/library/analogdialogue/archives/43-09/edch%208%20filter.pdf. +// +// Please note Q has been calculated by fitting a 4th order polynomial to the +// average of Q vs attenuation curves at different f0's from 10kHz to 19kHz. +// Please note that at differing frequencies these graphs had identical shape +// and differed only by an overall offset of at most 0.1 (attenuation) dB from +// the mean. This script can be found at + +// +// @param sample_rate Sampling rate in Hz. +// @param specification_frequency Frequency at which attenuation applies in Hz. +// @param attenuation Attenuation at specification_frequency in negative dB. +// @return low_pass Output structure of low-pass BiquadCoefficients. +BiquadCoefficients ComputeLowPassBiquadCoefficients( + int sample_rate, float specification_frequency, float attenuation); + +// Generates a coefficient for the |MonoPoleFilterClass| based on a 3dB +// bandwidth. +// +// The Laplace transfer function of a first order low pass system is: +// +// 1 +// ------------ where tau is the RC time constant of the system. +// 1 + tau * s +// +// For a discrete moving average filter with input x[n] and output y[n], +// the difference equation is: +// y[n] = a * y[n - 1] + (1 - a) * x[n] +// tau +// a = ---------- where T is the sample period. +// tau + T +// since the -3dB bandwith of a first order system can be +// 1 related to its time constant by +// f3 = -------------- +// 2 * pi * tau we can obtain 'a' from cuttoff_frequency and +// sample rate. +// +// @param cuttoff_frequency The -3dB frequency in Hz of the low pass lobe. +// @param sample_rate System sampling rate. +// @return A |MonoPoleFilterClass| coefficient for the specified bandwidth. +float ComputeLowPassMonoPoleCoefficient(float cuttoff_frequency, + int sample_rate); + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_DSP_FILTER_COEFFICIENT_GENERATORS_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/filter_coefficient_generators_test.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/filter_coefficient_generators_test.cc new file mode 100644 index 000000000..163b6a6d2 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/filter_coefficient_generators_test.cc @@ -0,0 +1,146 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/filter_coefficient_generators.h" + +#include "third_party/googletest/googletest/include/gtest/gtest.h" + +namespace vraudio { + +namespace { + +// Allowable error between filter coefficients. +const float kErrorf = 1e-5f; + +// Input into the ComputeLowPassBiquadCoefficients() function. +const int kSampleRate = 44100; +const float kSpecificationFrequency = 13000.0f; +const float kAttenuation = -15.0f; + +const float kMonoPoleCoefficient = 0.94863985f; + +// These filter coefficients were calculated in MATLAB with a specified Q value +// of 0.17775. The attenuation at the specification frequency was then +// calculated from the MATLAB freqz() calls. +const BiquadCoefficients kIdealCoefficients(3.70224817628938f, + 0.555382147099001f, + -1.70224817628938f, + 0.63884553677475f, 1.2776910735495f, + 0.63884553677475f); + +// These filter coefficients were calculated in MATLAB for a centre_frequency of +// 8kHz and a bandwidth of 1 octave. +const BiquadCoefficients kIdealBandpassCoefficients(1.37364799922092f, -1.0f, + 0.626352000779082f, + 0.373647999220918f, 0.0f, + -0.373647999220918f); + +const float kBandPassCenterFrequency = 8000.0f; +const int kBandPassOctaveBandwidth = 1; + +// Input into the ComputeDualBandBiquadCoefficients() function. +const int kSampleRate48 = 48000; +const float kCrossoverFrequency = 380.0f; + +// Pre-computed coefficients for the phase-matched filter pair (dual-band) with +// the cross-over frequency set at 380Hz and the sample rate 48kHz. +const float kLowPassA[] = {1.0f, -1.9029109f, 0.90526748f}; +const float kLowPassB[] = {0.00058914319f, 0.0011782864f, 0.00058914319f}; +const float kHighPassA[] = {1.0f, -1.9029109f, 0.90526748f}; +const float kHighPassB[] = {0.95204461f, -1.9040892f, 0.95204461f}; + +TEST(FilterCoefficientGeneratorsTest, ComputeMonoPoleLowpassCoefficientTest) { + float coefficient = + ComputeLowPassMonoPoleCoefficient(kCrossoverFrequency, kSampleRate); + EXPECT_NEAR(kMonoPoleCoefficient, coefficient, kErrorf); + + const float twenty_hertz = 20.0f; + coefficient = ComputeLowPassMonoPoleCoefficient(twenty_hertz, kSampleRate); + EXPECT_EQ(0.0f, coefficient); + // New test for the case where a frequency below 20Hz is passed. +} + +// Tests that the BiquadCoefficients generated by +// ComputeBandPassBiquadCoefficients() match those known to be correct as +// directly calculated from MATLAB. +TEST(FilterCoefficientGeneratorsTest, ComputeBandPassBiquadCoefficientsTest) { + // Perform computation. + BiquadCoefficients biquad_filter_coefficients = + ComputeBandPassBiquadCoefficients(kSampleRate48, kBandPassCenterFrequency, + kBandPassOctaveBandwidth); + + // Make sure they are all equal. + for (size_t i = 0; i < biquad_filter_coefficients.a.size(); ++i) { + EXPECT_NEAR(kIdealBandpassCoefficients.a[i], + biquad_filter_coefficients.a[i], kErrorf); + EXPECT_NEAR(kIdealBandpassCoefficients.b[i], + biquad_filter_coefficients.b[i], kErrorf); + } +} + +// Tests that the BiquadCoefficients generated by +// ComputeLowPassBiquadCoefficients() match those known to be correct as +// directly calculated from MATLAB. This also tests the validity of the inherent +// 4th order best fit between attenuation at a specified frequency and Q factor. +TEST(FilterCoefficientGeneratorsTest, ComputeLowPassBiquadCoefficientsTest) { + // Perform computation. + BiquadCoefficients biquad_filter_coefficients = + ComputeLowPassBiquadCoefficients(kSampleRate, kSpecificationFrequency, + kAttenuation); + + // Make sure they are all equal. + for (size_t i = 0; i < biquad_filter_coefficients.a.size(); ++i) { + EXPECT_NEAR(kIdealCoefficients.a[i], biquad_filter_coefficients.a[i], + kErrorf); + EXPECT_NEAR(kIdealCoefficients.b[i], biquad_filter_coefficients.b[i], + kErrorf); + } +} + +// Tests that the BiquadCoefficients generated by +// ComputeDualBandBiquadCoefficients() match those known to be correct as +// directly calculated from MATLAB. These coefficients are as described in: +// http://www.ai.sri.com/ajh/ambisonics/BLaH3.pdf. +TEST(DualBandBiquadCoefficientsTest, ComputeDualBandBiquadCoefficientsTest) { + // Generate default bi-quad coefficients and constructs low- and high-pass + // filter coefficients. + const BiquadCoefficients kDefaultCoefficients; + BiquadCoefficients low_pass_coefficients(kDefaultCoefficients); + BiquadCoefficients high_pass_coefficients(kDefaultCoefficients); + + // Perform computation. + ComputeDualBandBiquadCoefficients(kSampleRate48, kCrossoverFrequency, + &low_pass_coefficients, + &high_pass_coefficients); + + // Check if arrays with a and b coefficients for both filters are of the + // same size. + ASSERT_EQ(low_pass_coefficients.a.size(), low_pass_coefficients.b.size()); + ASSERT_EQ(high_pass_coefficients.a.size(), low_pass_coefficients.a.size()); + ASSERT_EQ(high_pass_coefficients.b.size(), low_pass_coefficients.b.size()); + + // Make sure they are all equal. + for (size_t i = 0; i < low_pass_coefficients.a.size(); ++i) { + EXPECT_NEAR(low_pass_coefficients.a[i], kLowPassA[i], kErrorf); + EXPECT_NEAR(low_pass_coefficients.b[i], kLowPassB[i], kErrorf); + EXPECT_NEAR(high_pass_coefficients.a[i], kHighPassA[i], kErrorf); + EXPECT_NEAR(high_pass_coefficients.b[i], kHighPassB[i], kErrorf); + } +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/fir_filter.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/fir_filter.cc new file mode 100644 index 000000000..e77ab71ce --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/fir_filter.cc @@ -0,0 +1,148 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Prevent Visual Studio from complaining about std::copy_n. +#if defined(_WIN32) +#define _SCL_SECURE_NO_WARNINGS +#endif + +#include "dsp/fir_filter.h" + +#include "base/constants_and_types.h" +#include "base/logging.h" +#include "base/simd_macros.h" + +namespace vraudio { + +FirFilter::FirFilter(const AudioBuffer::Channel& filter_coefficients, + size_t frames_per_buffer) { + DCHECK_GE(filter_coefficients.size(), 1U); + // Create the kernel buffer in repeated entry representation from standard + // form FIR representation. + const size_t coefficients_length = filter_coefficients.size(); + filter_length_ = coefficients_length + + ((coefficients_length % SIMD_LENGTH == 0) + ? 0 + : SIMD_LENGTH - (coefficients_length % SIMD_LENGTH)); + num_filter_chunks_ = filter_length_ / SIMD_LENGTH; + DCHECK_EQ(filter_length_ % SIMD_LENGTH, 0); + coefficients_ = AudioBuffer(kNumMonoChannels, filter_length_ * SIMD_LENGTH); + AudioBuffer::Channel* coefficients = &(coefficients_[0]); + // Store the coefficients so that each individual coefficient is repeated + // SIMD_LENGTH times. + for (size_t coeff = 0; coeff < coefficients_length; ++coeff) { + auto coefficient_iter = coefficients->begin() + (coeff * SIMD_LENGTH); + std::fill(coefficient_iter, coefficient_iter + SIMD_LENGTH, + filter_coefficients[coeff]); + } + std::fill(coefficients->begin() + (coefficients_length * SIMD_LENGTH), + coefficients->end(), 0.0f); + // Allocate an aligned buffer with |filter_length_| extra samples to + // store previous input samples. + state_ = AudioBuffer(kNumMonoChannels, frames_per_buffer + filter_length_); + state_.Clear(); +} + +void FirFilter::Process(const AudioBuffer::Channel& input, + AudioBuffer::Channel* output) { + DCHECK(output); + DCHECK_EQ(input.size(), state_.num_frames() - filter_length_); + // In this case we know that SIMD_LENGTH == 1, therefore we don't need to take + // account of it. + const size_t input_length = input.size(); + std::copy_n(state_[0].end() - filter_length_, filter_length_, + state_[0].begin()); + std::copy_n(input.begin(), input_length, state_[0].begin() + filter_length_); +#if defined(SIMD_DISABLED) + AudioBuffer::Channel* coefficients = &(coefficients_[0]); + AudioBuffer::Channel* data = &(state_[0]); + for (size_t frame = 0; frame < input_length; ++frame) { + for (size_t coeff = 0; coeff < filter_length_; ++coeff) { + (*output)[frame] += + (*coefficients)[coeff] * (*data)[filter_length_ - coeff]; + } + } +#else + const SimdVector* cptr_input = + reinterpret_cast<const SimdVector*>(&(state_[0][0])); + const SimdVector* cptr_filter = + reinterpret_cast<const SimdVector*>(&(coefficients_[0][0])); + SimdVector* ptr_output = reinterpret_cast<SimdVector*>(&((*output)[0])); + + const size_t num_input_chunks = input_length / SIMD_LENGTH; + DCHECK_EQ(input_length % SIMD_LENGTH, 0); + + // A pair of |SimdVector|s one for holding the input for each operation, the + // other to store data that needed to be copied as it straddled a four float + // boundary. + SimdVector input_use; + SimdVector input_split; + for (size_t input_position = num_filter_chunks_; + input_position < num_input_chunks + num_filter_chunks_; + ++input_position) { + // Replace these with the indexed pointers to save on copies. + SimdVector* output_now = &ptr_output[input_position - num_filter_chunks_]; + DCHECK_GE(input_position, num_filter_chunks_); + + for (size_t chunk = 0; chunk < num_filter_chunks_; ++chunk) { + const size_t filter_index_offset = chunk * SIMD_LENGTH; + const SimdVector* input_a = &cptr_input[input_position - (chunk + 1)]; + const SimdVector* input_b = &cptr_input[input_position - chunk]; +#if defined(SIMD_SSE) + // Select four floats from input_a and input_b, based on the mask. Here we + // take the latter two entries from input_b followed by the first two + // entries of input_a. + input_split = _mm_shuffle_ps(*input_a, *input_b, _MM_SHUFFLE(1, 0, 3, 2)); + + // All from input_b. + *output_now = SIMD_MULTIPLY_ADD( + *input_b, cptr_filter[filter_index_offset], *output_now); + // One from input_a and the rest (three) from input_b. + input_use = + _mm_shuffle_ps(input_split, *input_b, _MM_SHUFFLE(2, 1, 2, 1)); + *output_now = SIMD_MULTIPLY_ADD( + input_use, cptr_filter[1 + filter_index_offset], *output_now); + // Two from input_a and two from input_b. + *output_now = SIMD_MULTIPLY_ADD( + input_split, cptr_filter[2 + filter_index_offset], *output_now); + // Three from input_a and one from input_b. + input_use = + _mm_shuffle_ps(*input_a, input_split, _MM_SHUFFLE(2, 1, 2, 1)); + *output_now = SIMD_MULTIPLY_ADD( + input_use, cptr_filter[3 + filter_index_offset], *output_now); +#elif defined(SIMD_NEON) + // All from input_b. + *output_now = SIMD_MULTIPLY_ADD( + *input_b, cptr_filter[filter_index_offset], *output_now); + // One from input_a and the rest (three) from input_b. + input_use = vextq_f32(*input_a, *input_b, 3); + *output_now = SIMD_MULTIPLY_ADD( + input_use, cptr_filter[1 + filter_index_offset], *output_now); + // Two from input_a and two from input_b. + input_use = vextq_f32(*input_a, *input_b, 2); + *output_now = SIMD_MULTIPLY_ADD( + input_use, cptr_filter[2 + filter_index_offset], *output_now); + // Three from input_a and one from input_b. + input_use = vextq_f32(*input_a, *input_b, 1); + *output_now = SIMD_MULTIPLY_ADD( + input_use, cptr_filter[3 + filter_index_offset], *output_now); +#endif // SIMD_SSE/SIMD_NEON + } + } +#endif // SIMD_DISABLED +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/fir_filter.h b/src/3rdparty/resonance-audio/resonance_audio/dsp/fir_filter.h new file mode 100644 index 000000000..46c1edde5 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/fir_filter.h @@ -0,0 +1,62 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_DSP_FIR_FILTER_H_ +#define RESONANCE_AUDIO_DSP_FIR_FILTER_H_ + +#include "base/audio_buffer.h" + +namespace vraudio { + +class FirFilter { + public: + // Constructs a |FirFilter| from a mono |AudioBuffer| of filter coefficients. + // + // @param filter_coefficients FIR filter coefficients. + // @param frames_per_buffer Number of frames of data in each audio buffer. + FirFilter(const AudioBuffer::Channel& filter_coefficients, + size_t frames_per_buffer); + + // Filters an array of input with a finite impulse response (FIR) filter in + // the time domain. All pointers and lengths passed must be SIMD compatible. + // + // @param input Pointer to an aray of input. + // @param output Pointer to a cleared block of memory of the input length. + void Process(const AudioBuffer::Channel& input, AudioBuffer::Channel* output); + + // Returns the length of the filter kernel after zeropadding. + // + // @return Length of the FIR filter in frames. + size_t filter_length() const { return filter_length_; } + + private: + // Length of the filter kernel in frames after zeropadding. + size_t filter_length_; + + // Number of filter chunks equivalent to the filter length after zeropadding + // divided by the SIMD_LENGTH. + size_t num_filter_chunks_; + + // Coefficients of the filter stored in a repeated format. + AudioBuffer coefficients_; + + // Aligned buffer of previous and current input. + AudioBuffer state_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_DSP_FIR_FILTER_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/fir_filter_test.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/fir_filter_test.cc new file mode 100644 index 000000000..e64a44f15 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/fir_filter_test.cc @@ -0,0 +1,104 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/fir_filter.h" + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/audio_buffer.h" +#include "base/constants_and_types.h" +#include "base/simd_macros.h" + +namespace vraudio { + +namespace { + +const size_t kFirFramesPerBuffer = 32; +const size_t kFirKernelLength = 8; + +// Tests that the filter is resized to a length compatable with the current +// SIMD_LENGTH whether it is one greater than or one less than a multiple of +// SIMD_LENGTH to begin with. +TEST(FirFilterTest, GetFilterLengthTest) { + const size_t twice_simd_length = (SIMD_LENGTH * 2); + // Divisible by the length of a SIMD vector. + AudioBuffer kernel_buffer1(kNumMonoChannels, twice_simd_length); + FirFilter filter1(kernel_buffer1[0], twice_simd_length); + EXPECT_EQ(twice_simd_length, filter1.filter_length()); + + // Shorter filter. + AudioBuffer kernel_buffer2(kNumMonoChannels, twice_simd_length - 1); + FirFilter filter2(kernel_buffer2[0], twice_simd_length - 1); + EXPECT_EQ(twice_simd_length, filter2.filter_length()); + + // Longer filter. + AudioBuffer kernel_buffer3(kNumMonoChannels, twice_simd_length + 1); + FirFilter filter3(kernel_buffer3[0], twice_simd_length + 1); + EXPECT_EQ(twice_simd_length + SIMD_LENGTH, filter3.filter_length()); +} + +// Tests that the output from a convolution between a double kronecker input and +// a alternating 1, 0 buffer of input yields the correct output buffer. +TEST(FirFilterTest, ProcessWithDoubleKronecker) { + AudioBuffer input_buffer(kNumMonoChannels, kFirFramesPerBuffer); + input_buffer.Clear(); + AudioBuffer output_buffer(1, kFirFramesPerBuffer); + output_buffer.Clear(); + // First create a vector containing the FIR filter coefficients in standard + // format. + AudioBuffer kernel_buffer(kNumMonoChannels, kFirKernelLength); + kernel_buffer.Clear(); + kernel_buffer[0][0] = 1.0f; + kernel_buffer[0][kFirKernelLength / 2] = 1.0f; + // Now we can create the kernel buffer in its repeated entry representation + // from this standard form FIR representation. + FirFilter filter(kernel_buffer[0], kFirFramesPerBuffer); + // Next populate the input buffer. + for (size_t i = 0; i < kFirFramesPerBuffer; i += 2) { + input_buffer[0][i] = 1.0f; + } + + filter.Process(input_buffer[0], &(output_buffer[0])); + + for (size_t i = 0; i < kFirFramesPerBuffer; ++i) { + if (i % 2 != 0) { + EXPECT_NEAR(0.0f, output_buffer[0][i], kEpsilonFloat); + } else if (i <= 3) { + EXPECT_NEAR(1.0f, output_buffer[0][i], kEpsilonFloat); + } else { + EXPECT_NEAR(2.0f, output_buffer[0][i], kEpsilonFloat); + } + } + + // Run again with a cleared buffer to flush out the filter state. + input_buffer.Clear(); + output_buffer.Clear(); + + filter.Process(input_buffer[0], &(output_buffer[0])); + + for (size_t i = 0; i < kFirFramesPerBuffer; ++i) { + if (i % 2 != 0) { + EXPECT_NEAR(0.0f, output_buffer[0][i], kEpsilonFloat); + } else if (i <= 3) { + EXPECT_NEAR(1.0f, output_buffer[0][i], kEpsilonFloat); + } else { + EXPECT_NEAR(0.0f, output_buffer[0][i], kEpsilonFloat); + } + } +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/gain.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/gain.cc new file mode 100644 index 000000000..54e3fc216 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/gain.cc @@ -0,0 +1,102 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/gain.h" + +#include "base/constants_and_types.h" +#include "base/simd_macros.h" +#include "base/simd_utils.h" + +namespace vraudio { + +float LinearGainRamp(size_t ramp_length, float start_gain, float end_gain, + const AudioBuffer::Channel& input_samples, + AudioBuffer::Channel* output_samples, + bool accumulate_output) { + DCHECK(output_samples); + DCHECK_EQ(input_samples.size(), output_samples->size()); + DCHECK_GT(ramp_length, 0U); + + const size_t process_length = std::min(ramp_length, input_samples.size()); + const float gain_increment_per_sample = + (end_gain - start_gain) / static_cast<float>(ramp_length); + + float current_gain = start_gain; + if (accumulate_output) { + for (size_t frame = 0; frame < process_length; ++frame) { + (*output_samples)[frame] += current_gain * input_samples[frame]; + current_gain += gain_increment_per_sample; + } + } else { + for (size_t frame = 0; frame < process_length; ++frame) { + (*output_samples)[frame] = current_gain * input_samples[frame]; + current_gain += gain_increment_per_sample; + } + } + + return current_gain; +} + +void ConstantGain(size_t offset_index, float gain, + const AudioBuffer::Channel& input_samples, + AudioBuffer::Channel* output_samples, + bool accumulate_output) { + DCHECK(output_samples); + const size_t input_size = input_samples.size(); + DCHECK_EQ(input_size, output_samples->size()); + DCHECK_LT(offset_index, input_size); + + // Apply gain to samples at the beginning, prior to SIMD_LENGTH alignment. + const size_t unaligned_samples = SIMD_LENGTH - (offset_index % SIMD_LENGTH); + const size_t offset_index_simd = + std::min(input_size, offset_index + unaligned_samples); + if (accumulate_output) { + for (size_t i = offset_index; i < offset_index_simd; ++i) { + (*output_samples)[i] += input_samples[i] * gain; + } + } else { + for (size_t i = offset_index; i < offset_index_simd; ++i) { + (*output_samples)[i] = input_samples[i] * gain; + } + } + + if (offset_index_simd == input_size) { + // Return if there are no remaining operations to carry out. + return; + } + + const size_t aligned_length = input_size - offset_index_simd; + const float* aligned_input = &(input_samples[offset_index_simd]); + float* aligned_output = &(*output_samples)[offset_index_simd]; + + // Apply gain via SIMD operations. + if (accumulate_output) { + ScalarMultiplyAndAccumulate(aligned_length, gain, aligned_input, + aligned_output); + } else { + ScalarMultiply(aligned_length, gain, aligned_input, aligned_output); + } +} + +bool IsGainNearZero(float gain) { + return std::abs(gain) < kNegative60dbInAmplitude; +} + +bool IsGainNearUnity(float gain) { + return std::abs(1.0f - gain) < kNegative60dbInAmplitude; +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/gain.h b/src/3rdparty/resonance-audio/resonance_audio/dsp/gain.h new file mode 100644 index 000000000..500af8c22 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/gain.h @@ -0,0 +1,67 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_DSP_GAIN_H_ +#define RESONANCE_AUDIO_DSP_GAIN_H_ + +#include <cstddef> +#include "base/audio_buffer.h" + +namespace vraudio { + +// Implements a linearly-interpolated application of gain to a buffer channel. +// +// @param ramp_length Length of interpolation ramp in samples. Must be > 0. +// @param start_gain Starting gain value for ramp. +// @param end_gain Finishing gain value for ramp. +// @param input_samples Channel buffer to which interpolated gain is applied. +// @param output_samples Channel buffer to contain scaled output. +// @param accumulate_output True if the processed input should be mixed into the +// output. Otherwise, the output will be replaced by the processed input. +// @return Next gain value to be applied to the buffer channel. +float LinearGainRamp(size_t ramp_length, float start_gain, float end_gain, + const AudioBuffer::Channel& input_samples, + AudioBuffer::Channel* output_samples, + bool accumulate_output); + +// Applies a gain value to a vector of buffer samples starting at some offset. +// +// @param offset_index Starting index for gain application in buffer. +// @param gain Gain value applied to samples. +// @param input_samples Channel buffer to which gain is applied. +// @param output_samples Channel buffer to contain scaled output. +// @param accumulate_output True if the processed input should be mixed into the +// output. Otherwise, the output will be replaced by the processed input. +void ConstantGain(size_t offset_index, float gain, + const AudioBuffer::Channel& input_samples, + AudioBuffer::Channel* output_samples, bool accumulate_output); + +// Checks if the gain factor is close enough to zero (less than -60 decibels). +// +// @param gain Gain value to be tested. +// @return true if the current gain factors are near zero, false otherwise. +bool IsGainNearZero(float gain); + +// Checks if the gain state is close enough to Unity (less than -60 decibels +// below or above). +// +// @param gain Gain value to be tested. +// @return true if the current gain factors are near unity, false otherwise. +bool IsGainNearUnity(float gain); + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_DSP_GAIN_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/gain_mixer.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/gain_mixer.cc new file mode 100644 index 000000000..06417be11 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/gain_mixer.cc @@ -0,0 +1,115 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/gain_mixer.h" + +#include <cmath> + +#include "base/logging.h" + +namespace vraudio { + +GainMixer::GainMixer(size_t num_channels, size_t frames_per_buffer) + : num_channels_(num_channels), + output_(num_channels_, frames_per_buffer), + is_empty_(false) { + DCHECK_NE(num_channels_, 0U); + Reset(); +} + +void GainMixer::AddInput(const AudioBuffer& input, + const std::vector<float>& gains) { + DCHECK_EQ(gains.size(), num_channels_); + DCHECK_EQ(input.num_channels(), num_channels_); + DCHECK_EQ(input.num_frames(), output_.num_frames()); + + auto* gain_processors = GetOrCreateProcessors(input.source_id()); + // Accumulate the input buffers into the output buffer. + for (size_t i = 0; i < num_channels_; ++i) { + if (input[i].IsEnabled()) { + (*gain_processors)[i].ApplyGain(gains[i], input[i], &output_[i], + true /* accumulate_output */); + } else { + // Make sure the gain processor is initialized. + (*gain_processors)[i].Reset(gains[i]); + } + } + is_empty_ = false; +} + +void GainMixer::AddInputChannel(const AudioBuffer::Channel& input, + SourceId source_id, + const std::vector<float>& gains) { + DCHECK_EQ(gains.size(), num_channels_); + DCHECK_EQ(input.size(), output_.num_frames()); + + auto* gain_processors = GetOrCreateProcessors(source_id); + // Accumulate the input buffers into the output buffer. + for (size_t i = 0; i < num_channels_; ++i) { + if (input.IsEnabled()) { + (*gain_processors)[i].ApplyGain(gains[i], input, &output_[i], + true /* accumulate_output */); + } else { + // Make sure the gain processor is initialized. + (*gain_processors)[i].Reset(gains[i]); + } + } + is_empty_ = false; +} + +const AudioBuffer* GainMixer::GetOutput() const { + if (is_empty_) { + return nullptr; + } + return &output_; +} + +void GainMixer::Reset() { + if (!is_empty_) { + // Delete the processors for sources which no longer exist. + for (auto it = source_gain_processors_.begin(); + it != source_gain_processors_.end(); + /* no increment */) { + if (it->second.processors_active) { + it->second.processors_active = false; + ++it; + } else { + source_gain_processors_.erase(it++); + } + } + // Reset the output buffer. + output_.Clear(); + } + is_empty_ = true; +} + +GainMixer::GainProcessors::GainProcessors(size_t num_channels) + : processors_active(true), processors(num_channels) {} + +std::vector<GainProcessor>* GainMixer::GetOrCreateProcessors( + SourceId source_id) { + // Attempt to find a |ScaleAndAccumulateProcessor| for the given |source_id|, + // if none can be found add one. In either case mark that the processor has + // been used so that it is not later deleted. + if (source_gain_processors_.find(source_id) == + source_gain_processors_.end()) { + source_gain_processors_.insert({source_id, GainProcessors(num_channels_)}); + } + source_gain_processors_.at(source_id).processors_active = true; + return &(source_gain_processors_.at(source_id).processors); +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/gain_mixer.h b/src/3rdparty/resonance-audio/resonance_audio/dsp/gain_mixer.h new file mode 100644 index 000000000..18c785c26 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/gain_mixer.h @@ -0,0 +1,96 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_DSP_GAIN_MIXER_H_ +#define RESONANCE_AUDIO_DSP_GAIN_MIXER_H_ + +#include <memory> +#include <unordered_map> +#include <vector> + +#include "base/audio_buffer.h" +#include "base/constants_and_types.h" +#include "dsp/gain_processor.h" + +namespace vraudio { + +class GainMixer { + public: + GainMixer(size_t num_channels, size_t frames_per_buffer); + + // Adds a separately scaled version of each channel of the input buffer to the + // output buffer's corresponding channels. + // + // @param input Input buffer to be added. + // @param gains Gains to be applied to the buffers channels. Must be equal in + // length to the number of channels in |input|. + void AddInput(const AudioBuffer& input, const std::vector<float>& gains); + + // Adds a single input channel to each of the output buffer's channels, with + // a separate gain applied to the input channel per output channel. + // + // @param input Input channel to be added. + // @param source_id Identifier corresponding to the input. + // @param gains Gains to be applied to the buffers channels. Must be equal in + // length to the number of channels in the output buffer. + void AddInputChannel(const AudioBuffer::Channel& input, SourceId source_id, + const std::vector<float>& gains); + + // Returns a pointer to the accumulator. + // + // @return Pointer to the processed (mixed) output buffer, or nullptr if no + // input has been added to the accumulator. + const AudioBuffer* GetOutput() const; + + // Resets the state of the accumulator. + void Reset(); + + private: + // Comprises one |GainProcessor| per channel of a source and a boolean to + // denote whether that source is active. + struct GainProcessors { + explicit GainProcessors(size_t num_channels); + + // Bool to signify if a given source is still passing data to a processor. + bool processors_active; + + // Scale and accumulation processors, one per channel for each source. + std::vector<GainProcessor> processors; + }; + + // Returns the |GainProcessor|s associated with a |source_id| (or creates + // one if needed) and sets the corresponding |processors_active| flag to true. + // + // @param source_id Identifier for a given input. + // @return The corresponding |ScalingAccumulators|. + std::vector<GainProcessor>* GetOrCreateProcessors(SourceId source_id); + + // Number of channels. + const size_t num_channels_; + + // Output buffer (accumulator). + AudioBuffer output_; + + // Denotes whether the accumulator has processed any inputs or not. + bool is_empty_; + + // Scale and accumulation processors, one per channel for each source. + std::unordered_map<SourceId, GainProcessors> source_gain_processors_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_DSP_GAIN_MIXER_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/gain_mixer_test.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/gain_mixer_test.cc new file mode 100644 index 000000000..70a45a692 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/gain_mixer_test.cc @@ -0,0 +1,188 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/gain_mixer.h" + +#include <iterator> +#include <vector> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/constants_and_types.h" +#include "utils/planar_interleaved_conversion.h" + +namespace vraudio { + +namespace { + +const int kId1 = 0; +const int kId2 = 1; + +// Number of frames per output buffer. +const size_t kFramesPerBuffer = 3; + +// Sample mono input data, gains, and the corresponding output data. +const float kGain1 = 0.5f; +const float kGain2 = 2.0f; +const float kInput1[kFramesPerBuffer] = {0.2f, 0.4f, 0.6f}; +const float kInput2[kFramesPerBuffer] = {0.5f, 1.0f, 1.5f}; +const float kOutput[kFramesPerBuffer] = {1.1f, 2.2f, 3.3f}; + +// Tests that the gain mixer returns null output if no input has been added. +TEST(MixerTest, EmptyInputTest) { + GainMixer gain_mixer(kNumMonoChannels, kFramesPerBuffer); + + const AudioBuffer* output = gain_mixer.GetOutput(); + EXPECT_TRUE(output == nullptr); +} + +// Mono processing test with two inputs. Tests the mixed output buffer against +// the manually computed output data. +TEST(GainMixerTest, ProcessTest) { + GainMixer gain_mixer(kNumMonoChannels, kFramesPerBuffer); + + // Initialize input buffers. + AudioBuffer input1(kNumMonoChannels, kFramesPerBuffer); + input1.set_source_id(kId1); + FillAudioBuffer(std::begin(kInput1), kFramesPerBuffer, kNumMonoChannels, + &input1); + AudioBuffer input2(kNumMonoChannels, kFramesPerBuffer); + input2.set_source_id(kId2); + FillAudioBuffer(std::begin(kInput2), kFramesPerBuffer, kNumMonoChannels, + &input2); + // Initialize gain vectors. + const std::vector<float> kGainVector1(1, kGain1); + const std::vector<float> kGainVector2(1, kGain2); + + // Add the input buffers to the |GainMixer| (process multiple times so the + // gains have reached steady state. + const AudioBuffer* output = nullptr; + for (size_t iterations = 0; iterations < kUnitRampLength; ++iterations) { + gain_mixer.Reset(); + gain_mixer.AddInput(input1, kGainVector1); + gain_mixer.AddInput(input2, kGainVector2); + // Get the processed output (should contain the pre-computed output data). + output = gain_mixer.GetOutput(); + } + + EXPECT_FALSE(output == nullptr); + EXPECT_EQ(output->num_channels(), kNumMonoChannels); + EXPECT_EQ(output->num_frames(), kFramesPerBuffer); + for (size_t i = 0; i < kFramesPerBuffer; ++i) { + EXPECT_NEAR((*output)[0][i], kOutput[i], kEpsilonFloat); + } +} + +// Reset test for the |GainMixer| with single input at a time. First, adds an +// input buffer, resets the |GainMixer|. Then, adds another input buffer and +// tests the final output buffer against the data of the last added input buffer +// to verify if the system has been successfully reset. +TEST(GainMixerTest, ResetTest) { + GainMixer gain_mixer(kNumMonoChannels, kFramesPerBuffer); + + // Initialize input buffers. + AudioBuffer input1(kNumMonoChannels, kFramesPerBuffer); + input1.set_source_id(kId1); + FillAudioBuffer(std::begin(kInput1), kFramesPerBuffer, kNumMonoChannels, + &input1); + AudioBuffer input2(kNumMonoChannels, kFramesPerBuffer); + input2.set_source_id(kId2); + FillAudioBuffer(std::begin(kInput2), kFramesPerBuffer, kNumMonoChannels, + &input2); + // Initialize gain vectors. + const std::vector<float> kGainVector1(1, kGain1); + const std::vector<float> kGainVector2(1, kGain2); + + // Add the first input buffer to the |GainMixer|. + gain_mixer.AddInput(input1, kGainVector1); + // Reset the accumulator (and release the buffer). + gain_mixer.Reset(); + // Add the second input buffers to the |GainMixer|. + gain_mixer.AddInput(input2, kGainVector2); + + // Get the output (should only contain the second input). + const AudioBuffer* output = gain_mixer.GetOutput(); + + EXPECT_FALSE(output == nullptr); + EXPECT_EQ(output->num_channels(), kNumMonoChannels); + EXPECT_EQ(output->num_frames(), kFramesPerBuffer); + for (size_t i = 0; i < kFramesPerBuffer; ++i) { + EXPECT_NEAR((*output)[0][i], kInput2[i] * kGain2, kEpsilonFloat); + } +} + +// Tests that gains are correctly applied for multiple channels. +TEST(GainMixerTest, MultiChannelTest) { + const std::vector<float> kGains = {0.1f, -0.2f}; + const std::vector<float> kInputChannel = {10.0f, 10.0f, 10.0f}; + GainMixer gain_mixer(kGains.size(), kFramesPerBuffer); + + // Initialize input buffer. + AudioBuffer input(kGains.size(), kInputChannel.size()); + for (size_t i = 0; i < kGains.size(); ++i) { + input[i] = kInputChannel; + } + + const AudioBuffer* output = nullptr; + for (size_t iterations = 0; iterations < kUnitRampLength; ++iterations) { + gain_mixer.Reset(); + gain_mixer.AddInput(input, kGains); + output = gain_mixer.GetOutput(); + } + + EXPECT_FALSE(output == nullptr); + EXPECT_EQ(output->num_channels(), kGains.size()); + EXPECT_EQ(output->num_frames(), kFramesPerBuffer); + for (size_t channel = 0; channel < kGains.size(); ++channel) { + for (size_t i = 0; i < kInputChannel.size(); ++i) { + EXPECT_NEAR((*output)[channel][i], kInputChannel[i] * kGains[channel], + kEpsilonFloat); + } + } +} + +// Tests that gains are correctly applied for a mono input buffer across a +// multichannel output buffer. +TEST(GainMixerTest, MonoChannelInputTest) { + const std::vector<float> kGains = {2.0f, -3.0f}; + const std::vector<float> kInputChannel = {10.0f, 10.0f, 10.0f}; + GainMixer gain_mixer(kGains.size(), kInputChannel.size()); + + // Create a mono input buffer. + AudioBuffer input(kNumMonoChannels, kInputChannel.size()); + AudioBuffer::Channel* input_channel = &input[0]; + *input_channel = kInputChannel; + + const AudioBuffer* output = nullptr; + for (size_t iterations = 0; iterations < kUnitRampLength; ++iterations) { + gain_mixer.Reset(); + gain_mixer.AddInputChannel(*input_channel, kId1, kGains); + output = gain_mixer.GetOutput(); + } + + EXPECT_FALSE(output == nullptr); + EXPECT_EQ(output->num_channels(), kGains.size()); + EXPECT_EQ(output->num_frames(), kFramesPerBuffer); + for (size_t channel = 0; channel < kGains.size(); ++channel) { + for (size_t i = 0; i < kInputChannel.size(); ++i) { + EXPECT_NEAR((*output)[channel][i], kInputChannel[i] * kGains[channel], + kEpsilonFloat); + } + } +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/gain_processor.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/gain_processor.cc new file mode 100644 index 000000000..f5eaea22d --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/gain_processor.cc @@ -0,0 +1,94 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/gain_processor.h" + +#include <algorithm> +#include <cmath> + +#include "base/constants_and_types.h" + +#include "dsp/gain.h" + +namespace vraudio { + +// Delegate default constructor. +GainProcessor::GainProcessor() : current_gain_(0.0f), is_initialized_(false) {} + +GainProcessor::GainProcessor(float initial_gain) + : current_gain_(initial_gain), is_initialized_(true) {} + +void GainProcessor::ApplyGain(float target_gain, + const AudioBuffer::Channel& input, + AudioBuffer::Channel* output, + bool accumulate_output) { + + DCHECK(output); + + if (!is_initialized_) { + Reset(target_gain); + } + + // Check buffer length. + const size_t input_length = input.size(); + DCHECK_GT(input_length, 0U); + DCHECK_EQ(input_length, output->size()); + + // Index for where to stop interpolating. + size_t ramp_length = + static_cast<size_t>(std::abs(target_gain - current_gain_) * + static_cast<float>(kUnitRampLength)); + + // Check if there is a new gain value. + if (ramp_length > 0) { + // Apply gain ramp to buffer. + current_gain_ = LinearGainRamp(ramp_length, current_gain_, target_gain, + input, output, accumulate_output); + } else { + // No ramping needed. + current_gain_ = target_gain; + } + + // Apply constant gain to the rest of the buffer. + if (ramp_length < input_length) { + if (IsGainNearZero(current_gain_)) { + // Skip processing if the gain is zero. + if (!accumulate_output) { + // Directly fill the remaining output with zeros. + std::fill(output->begin() + ramp_length, output->end(), 0.0f); + } + return; + } else if (IsGainNearUnity(current_gain_) && !accumulate_output) { + // Skip processing if the gain is unity. + if (&input != output) { + // Directly copy the remaining input samples into output. + std::copy(input.begin() + ramp_length, input.end(), + output->begin() + ramp_length); + } + return; + } + ConstantGain(ramp_length, current_gain_, input, output, accumulate_output); + } +} + +float GainProcessor::GetGain() const { return current_gain_; } + +void GainProcessor::Reset(float gain) { + current_gain_ = gain; + is_initialized_ = true; +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/gain_processor.h b/src/3rdparty/resonance-audio/resonance_audio/dsp/gain_processor.h new file mode 100644 index 000000000..4baa276ea --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/gain_processor.h @@ -0,0 +1,74 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_DSP_GAIN_PROCESSOR_H_ +#define RESONANCE_AUDIO_DSP_GAIN_PROCESSOR_H_ + +#include <cmath> +#include <cstddef> +#include <vector> + +#include "base/audio_buffer.h" + +namespace vraudio { + +// Processor class which applies a gain to a vector of samples from an audio +// buffer. A short linear ramp is applied to the gain to reduce audible +// artifacts in the output. +class GainProcessor { + public: + // Default constructor keeps the gain state uninitialized. The first call to + // |ApplyGain| sets the internal gain state. + GainProcessor(); + + // Constructs |GainProcessor| with some initial gain value. + // + // @param initial_gain Gain value used as starting point for first processing + // period's gain ramping. + explicit GainProcessor(float initial_gain); + + // Applies gain supplied to the input samples. + // + // @param target_gain Target gain value. + // @param input Samples to which gain will be applied. + // @param output Samples to which gain has been applied. + // @param accumulate_output True if the processed input should be mixed into + // the output. Otherwise, the output will be replaced by the processed + // input. + void ApplyGain(float target_gain, const AudioBuffer::Channel& input, + AudioBuffer::Channel* output, bool accumulate_output); + + // Returns the |current_gain_| value. + // + // @return Current gain applied by the |GainProcessor|. + float GetGain() const; + + // Resets the gain processor to a new gain factor. + // + // @param gain Gain value. + void Reset(float gain); + + private: + // Latest gain value to be applied to buffer values. + float current_gain_; + + // Flag to indiciate if an initial gain has been assigned. + bool is_initialized_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_DSP_GAIN_PROCESSOR_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/gain_processor_test.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/gain_processor_test.cc new file mode 100644 index 000000000..e1077655a --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/gain_processor_test.cc @@ -0,0 +1,181 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/gain_processor.h" + +#include <algorithm> +#include <cmath> +#include <vector> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/audio_buffer.h" +#include "base/constants_and_types.h" + +// A set of simple tests to confirm expected behavior when a linear gain ramp is +// applied to a vector of samples. +namespace vraudio { + +namespace { + +// Initial gain value. +const float kInitialGain = 0.1f; + +// Target gain value. +const float kTargetGain = 0.6f; + +// Test input data length. +const size_t kInputLength = 10; + +// Test value of each sample of the output buffer prior to processing. +const float kInitialOutputValue = 2.0f; + +class GainProcessorTest : public ::testing::TestWithParam<bool> {}; + +// This test checks that gain value is applied to the input buffer in a linear +// ramp when the buffer length is less than the ramp length. +TEST_P(GainProcessorTest, ApplyGainOverBufferLengthTest) { + // Expected output data for ramped gain application. + const float kExpectedOutput[kInputLength] = { + 0.1f, 0.1004883f, 0.1009766f, 0.1014648f, 0.1019531f, + 0.1024414f, 0.1029297f, 0.1034180f, 0.1039063f, 0.1043945f}; + const bool accumulate_output = GetParam(); + // Initialize input buffer. + AudioBuffer input(kNumMonoChannels, kInputLength); + std::fill(input[0].begin(), input[0].end(), 1.0f); + // Initialize output buffer. + AudioBuffer output(kNumMonoChannels, kInputLength); + std::fill(output[0].begin(), output[0].end(), kInitialOutputValue); + + // Initialize gain processor with a gain value. + GainProcessor gain_processor(kInitialGain); + // Process buffer samples with the test gain value. + gain_processor.ApplyGain(kTargetGain, input[0], &output[0], + accumulate_output); + + // Check that gain values have been applied correctly to the buffer. + for (size_t i = 0; i < kInputLength; ++i) { + const float expected_value = accumulate_output + ? kInitialOutputValue + kExpectedOutput[i] + : kExpectedOutput[i]; + EXPECT_NEAR(expected_value, output[0][i], kEpsilonFloat); + } +} + +// This test checks that gain value is applied to the buffer samples in a linear +// ramp. The test also confirms that the gain ramping halts at the correct index +// for buffer lengths greater than the ramp length. +TEST_P(GainProcessorTest, ApplyGainLongerThanRampTest) { + const bool accumulate_output = GetParam(); + // Initialize input buffer. + AudioBuffer input(kNumMonoChannels, kUnitRampLength); + std::fill(input[0].begin(), input[0].end(), 1.0f); + // Initialize output buffer. + AudioBuffer output(kNumMonoChannels, kUnitRampLength); + std::fill(output[0].begin(), output[0].end(), kInitialOutputValue); + + // Initialize gain processor with a gain value. + GainProcessor gain_processor(kInitialGain); + // Process an input buffer with the test gain value. + gain_processor.ApplyGain(kTargetGain, input[0], &output[0], + accumulate_output); + + // Generate expected gain ramp. The output should consist of the linearly + // interpolated gain values over the ramp length, and the final (constant) + // gain for the rest of the buffer. + std::vector<float> expected_output(kUnitRampLength); + const size_t ramp_length = + static_cast<size_t>(std::abs(kTargetGain - kInitialGain) * + static_cast<float>(kUnitRampLength)); + const float increment = + (kTargetGain - kInitialGain) / static_cast<float>(ramp_length); + float expected_value = + accumulate_output ? kInitialOutputValue + kInitialGain : kInitialGain; + for (size_t i = 0; i < kUnitRampLength; ++i) { + expected_output[i] = expected_value; + if (i < ramp_length) { + expected_value += increment; + } + } + // Check that gain values have been applied correctly to the buffer. + for (size_t i = 0; i < kUnitRampLength; ++i) { + // Check that ramp was applied. + EXPECT_NEAR(expected_output[i], output[0][i], kEpsilonFloat); + } +} + +// This test checks that gain values are reset to initial gain after a call to +// the |Reset()| method. +TEST_P(GainProcessorTest, ResetGainProcessorTest) { + const bool accumulate_output = GetParam(); + // Initialize input buffer. + AudioBuffer input(kNumMonoChannels, kUnitRampLength); + std::fill(input[0].begin(), input[0].end(), 1.0f); + // Initialize output buffer. + AudioBuffer output(kNumMonoChannels, kUnitRampLength); + std::fill(output[0].begin(), output[0].end(), 0.0f); + + // Initialize gain processor with a gain of 0.0f. + GainProcessor gain_processor(0.0f); + // Reset gain to |kInitialGain|. + gain_processor.Reset(kInitialGain); + // Apply newly-reset gains (no ramp expected). + gain_processor.ApplyGain(kInitialGain, input[0], &output[0], + accumulate_output); + + // Check that uniform gain has been applied correctly to the buffer. + for (size_t i = 0; i < kInputLength; ++i) { + EXPECT_NEAR(kInitialGain, output[0][i], kEpsilonFloat); + } +} + +// Checks that the initial gain is assigned during the first call of |ApplyGain| +// in case the |GainProcessor| instance is constructed via the default +// constructor. +TEST_P(GainProcessorTest, DefaultConstructorTest) { + const bool accumulate_output = GetParam(); + // Initialize input buffer. + AudioBuffer input(kNumMonoChannels, kUnitRampLength); + std::fill(input[0].begin(), input[0].end(), kInitialGain); + // Initialize output buffer. + AudioBuffer output(kNumMonoChannels, kUnitRampLength); + std::fill(output[0].begin(), output[0].end(), 0.0f); + + // Declare gain processor without specifiying a gain value. + GainProcessor gain_processor; + // Apply some new gain value. + gain_processor.ApplyGain(kTargetGain, input[0], &output[0], + accumulate_output); + + // Check that uniform gain has been applied correctly to the buffer. + for (size_t i = 0; i < kInputLength; ++i) { + EXPECT_NEAR(kInitialGain * kTargetGain, output[0][i], kEpsilonFloat); + } +} + +// Tests the |GetGain| method. +TEST(GainProcessorTest, GetGainTest) { + // Test |GainProcessor| with arbitrary gain. + const float kGainValue = -1.5f; + GainProcessor gain_processor(kGainValue); + EXPECT_EQ(gain_processor.GetGain(), kGainValue); +} + +INSTANTIATE_TEST_CASE_P(AccumulateOutput, GainProcessorTest, + ::testing::Values(false, true)); + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/gain_test.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/gain_test.cc new file mode 100644 index 000000000..01e4471f4 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/gain_test.cc @@ -0,0 +1,132 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/gain.h" + +#include <algorithm> +#include <vector> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/audio_buffer.h" +#include "base/constants_and_types.h" +#include "dsp/utils.h" + +namespace vraudio { + +namespace { + +// Tolerated error margin. +const float kEpsilon = 1e-4f; + +class GainTest : public ::testing::TestWithParam<bool> {}; + +// This test checks that the gain applied using ConstantGain is correct. +TEST_P(GainTest, ConstantGainTest) { + const size_t kMaxInputLength = 32; + const float kGain = 1; + const bool accumulate_output = GetParam(); + + for (size_t length = 1; length < kMaxInputLength; ++length) { + AudioBuffer input_samples(kNumMonoChannels, length); + GenerateUniformNoise( + /*min=*/-1.0f, /*min=*/1.0f, static_cast<unsigned>(length), + &input_samples[0]); + for (size_t offset_index = 0; offset_index < length; ++offset_index) { + // Initialize input buffer. + AudioBuffer input(kNumMonoChannels, length); + input[0] = input_samples[0]; + // Initialize output buffer with the same values. + AudioBuffer output(kNumMonoChannels, length); + output[0] = input_samples[0]; + + // Apply constant gain. + ConstantGain(offset_index, kGain, input[0], &output[0], + accumulate_output); + + // Compute expected values. + AudioBuffer expected_samples_buffer(kNumMonoChannels, length); + expected_samples_buffer[0] = input_samples[0]; + for (size_t i = offset_index; i < length; ++i) { + const float processed_input = kGain * input[0][i]; + if (accumulate_output) { + expected_samples_buffer[0][i] += processed_input; + } else { + expected_samples_buffer[0][i] = processed_input; + } + } + // Check that the output buffer has the expected values per each sample. + for (size_t i = 0; i < length; ++i) { + EXPECT_NEAR(expected_samples_buffer[0][i], output[0][i], kEpsilon) + << " at index=" << i << " with input_length=" << length + << " and offset_index=" << offset_index; + } + } + } +} + +// Test that checks that the gain ramp applied is correct. +TEST_P(GainTest, LinearGainRampTest) { + const float kInitialOutputValue = 2.0f; + const float kStartGain = 0.0f; + const float kEndGain = 1.0f; + const size_t kNumSamples = 10; + const float kPerSampleIncrease = + (kEndGain - kStartGain) / static_cast<float>(kNumSamples); + const bool accumulate_output = GetParam(); + + // Create an input buffer with unity samples. + AudioBuffer input(kNumMonoChannels, kNumSamples); + std::fill(input[0].begin(), input[0].end(), 1.0f); + // Create an output buffer with all samples the same. + AudioBuffer output(kNumMonoChannels, kNumSamples); + std::fill(output[0].begin(), output[0].end(), kInitialOutputValue); + + // Apply linear gain ramp. + LinearGainRamp(kNumSamples, kStartGain, kEndGain, input[0], &(output[0]), + accumulate_output); + + // Check that the output buffer has the expected values per each sample. + float expected_value = accumulate_output ? kInitialOutputValue : 0.0f; + for (size_t i = 0; i < kNumSamples; ++i) { + EXPECT_NEAR(expected_value, output[0][i], kEpsilon); + expected_value += kPerSampleIncrease; + } +} + +TEST(GainTest, GainUtilsTest) { + const float kGainUnity = 1.0f; + const float kGainZero = 0.0f; + const float kGainOther = -1.7f; + + // Test the cases where each should return true. + EXPECT_TRUE(IsGainNearZero(kGainZero)); + EXPECT_TRUE(IsGainNearUnity(kGainUnity)); + + // Test the cases where gain value is non zero, positive and negative. + EXPECT_FALSE(IsGainNearZero(kGainOther)); + EXPECT_FALSE(IsGainNearZero(kGainUnity)); + + // Test the case where gain value is not unity, with alternate value and zero. + EXPECT_FALSE(IsGainNearUnity(kGainOther)); + EXPECT_FALSE(IsGainNearUnity(kGainZero)); +} + +INSTANTIATE_TEST_CASE_P(AccumulateOutput, GainTest, + ::testing::Values(false, true)); + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/mixer.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/mixer.cc new file mode 100644 index 000000000..1b7460428 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/mixer.cc @@ -0,0 +1,56 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/mixer.h" + +#include "base/logging.h" + +namespace vraudio { + +Mixer::Mixer(size_t target_num_channels, size_t frames_per_buffer) + : output_(target_num_channels, frames_per_buffer), is_empty_(false) { + Reset(); +} + +void Mixer::AddInput(const AudioBuffer& input) { + DCHECK_EQ(input.num_frames(), output_.num_frames()); + + // Accumulate the input buffers into the output buffer. + const size_t num_channels = + std::min(input.num_channels(), output_.num_channels()); + for (size_t n = 0; n < num_channels; ++n) { + if (input[n].IsEnabled()) { + output_[n] += input[n]; + } + } + is_empty_ = false; +} + +const AudioBuffer* Mixer::GetOutput() const { + if (is_empty_) { + return nullptr; + } + return &output_; +} + +void Mixer::Reset() { + if (!is_empty_) { + output_.Clear(); + } + is_empty_ = true; +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/mixer.h b/src/3rdparty/resonance-audio/resonance_audio/dsp/mixer.h new file mode 100644 index 000000000..b89144799 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/mixer.h @@ -0,0 +1,62 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_DSP_MIXER_H_ +#define RESONANCE_AUDIO_DSP_MIXER_H_ + +#include <memory> + +#include "base/audio_buffer.h" + +namespace vraudio { + +// Accepts multiple input buffers and outputs a downmix to a single output +// buffer. All input buffers must have the same number of frames per buffer, the +// output will have the target number of channels regardless of the input number +// of channels. +class Mixer { + public: + // Constructor. + // + // @param target_num_channels Target number of channels in accumulator buffer. + // @param frames_per_buffer Number of frames in accumulator buffer. + Mixer(size_t target_num_channels, size_t frames_per_buffer); + + // Adds an input buffer to the mixer, updates the output buffer accordingly. + // + // @param input Input buffer to be added. + void AddInput(const AudioBuffer& input); + + // Returns a pointer to the accumulator. + // + // @return Pointer to the processed (mixed) output buffer, or nullptr if no + // input has been added to the accumulator. + const AudioBuffer* GetOutput() const; + + // Resets the state of the accumulator. + void Reset(); + + private: + // Output buffer (accumulator). + AudioBuffer output_; + + // Denotes whether the accumulator has processed any inputs or not. + bool is_empty_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_DSP_MIXER_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/mixer_test.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/mixer_test.cc new file mode 100644 index 000000000..0cfdf8675 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/mixer_test.cc @@ -0,0 +1,163 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/mixer.h" + +#include <iterator> +#include <vector> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/constants_and_types.h" +#include "utils/planar_interleaved_conversion.h" + +namespace vraudio { + +namespace { + +// Number of frames per output buffer. +const size_t kFramesPerBuffer = 3; + +// Sample two-channel interleaved input data. +const float kStereoInput1[kNumStereoChannels * kFramesPerBuffer] = { + 0.5f, 0.0f, -0.25f, 0.0f, 0.25f, 0.0f}; +const float kStereoInput2[kNumStereoChannels * kFramesPerBuffer] = { + 0.5f, 1.0f, 0.5f, -1.0f, 0.5f, 1.0f}; + +// Tests that the mixer returns null output if no input has been added. +TEST(MixerTest, EmptyInputTest) { + Mixer mixer(kNumStereoChannels, kFramesPerBuffer); + + const AudioBuffer* output = mixer.GetOutput(); + EXPECT_TRUE(output == nullptr); +} + +// This is a simple two-channel process test with two inputs. Tests the +// mixed output buffer against the manually computed output data. +TEST(MixerTest, ProcessUniformInputChannelsTest) { + Mixer mixer(kNumStereoChannels, kFramesPerBuffer); + + // Initialize the input buffers. + AudioBuffer input1(kNumStereoChannels, kFramesPerBuffer); + FillAudioBuffer(std::begin(kStereoInput1), kFramesPerBuffer, + kNumStereoChannels, &input1); + AudioBuffer input2(kNumStereoChannels, kFramesPerBuffer); + FillAudioBuffer(std::begin(kStereoInput2), kFramesPerBuffer, + kNumStereoChannels, &input2); + + // Add the input buffers to the mixer. + mixer.AddInput(input1); + mixer.AddInput(input2); + // Get the processed output. + const AudioBuffer* output = mixer.GetOutput(); + + // Test that the output channels was accumulated correctly. + EXPECT_FALSE(output == nullptr); + EXPECT_EQ(output->num_channels(), kNumStereoChannels); + EXPECT_EQ(output->num_frames(), kFramesPerBuffer); + for (size_t n = 0; n < kNumStereoChannels; ++n) { + for (size_t i = 0; i < kFramesPerBuffer; ++i) { + EXPECT_NEAR((*output)[n][i], input1[n][i] + input2[n][i], kEpsilonFloat); + } + } +} + +// This is a non-uniform process test with two inputs with arbitrary number of +// channels. Tests the mixed output buffer with different target number of +// channels against the manually computed output data. +TEST(MixerTest, ProcessVaryingInputChannelsTest) { + // Initialize the input buffers. + AudioBuffer mono_input(kNumMonoChannels, kFramesPerBuffer); + FillAudioBuffer(std::begin(kStereoInput1), kFramesPerBuffer, kNumMonoChannels, + &mono_input); + AudioBuffer stereo_input(kNumStereoChannels, kFramesPerBuffer); + FillAudioBuffer(std::begin(kStereoInput2), kFramesPerBuffer, + kNumStereoChannels, &stereo_input); + + // Initialize a mono mixer. + Mixer mono_mixer(kNumMonoChannels, kFramesPerBuffer); + // Add the input buffers to the mixer. + mono_mixer.AddInput(mono_input); + mono_mixer.AddInput(stereo_input); + // Get the processed output. + const AudioBuffer* mono_output = mono_mixer.GetOutput(); + // Test that the mono output channel was accumulated correctly. + EXPECT_FALSE(mono_output == nullptr); + EXPECT_EQ(mono_output->num_channels(), kNumMonoChannels); + EXPECT_EQ(mono_output->num_frames(), kFramesPerBuffer); + for (size_t i = 0; i < kFramesPerBuffer; ++i) { + EXPECT_NEAR((*mono_output)[0][i], mono_input[0][i] + stereo_input[0][i], + kEpsilonFloat); + } + + // Initialize a stereo mixer. + Mixer stereo_mixer(kNumStereoChannels, kFramesPerBuffer); + // Add the input buffers to the mixer. + stereo_mixer.AddInput(mono_input); + stereo_mixer.AddInput(stereo_input); + // Get the processed output. + const AudioBuffer* stereo_output = stereo_mixer.GetOutput(); + // Test that the stereo output channels were accumulated correctly. + EXPECT_FALSE(stereo_output == nullptr); + EXPECT_EQ(stereo_output->num_channels(), kNumStereoChannels); + EXPECT_EQ(stereo_output->num_frames(), kFramesPerBuffer); + for (size_t n = 0; n < kNumStereoChannels; ++n) { + for (size_t i = 0; i < kFramesPerBuffer; ++i) { + // The second channel should only contain the samples from |stereo_input|. + EXPECT_NEAR( + (*stereo_output)[n][i], + (n == 0) ? mono_input[n][i] + stereo_input[n][i] : stereo_input[n][i], + kEpsilonFloat); + } + } +} + +// This is a two-channel reset test for the mixer with single input at a time. +// First, adds an input buffer, resets the mixer. Then, adds another input +// buffer and tests the final output buffer against the data of the last added +// input buffer to verify if the system has been successfully reset. +TEST(MixerTest, ResetTest) { + Mixer mixer(kNumStereoChannels, kFramesPerBuffer); + + // Initialize the input buffers. + AudioBuffer input1(kNumStereoChannels, kFramesPerBuffer); + FillAudioBuffer(std::begin(kStereoInput1), kFramesPerBuffer, + kNumStereoChannels, &input1); + AudioBuffer input2(kNumStereoChannels, kFramesPerBuffer); + FillAudioBuffer(std::begin(kStereoInput2), kFramesPerBuffer, + kNumStereoChannels, &input2); + + // Add the first input buffer to the mixer. + mixer.AddInput(input1); + // Reset the accumulator. + mixer.Reset(); + // Add the second input buffers to the mixer. + mixer.AddInput(input2); + // Get the processed output. + const AudioBuffer* output = mixer.GetOutput(); + // Test that the output channels contains only the samples from |input2|. + EXPECT_FALSE(output == nullptr); + EXPECT_EQ(output->num_channels(), kNumStereoChannels); + EXPECT_EQ(output->num_frames(), kFramesPerBuffer); + for (size_t n = 0; n < kNumStereoChannels; ++n) { + for (size_t i = 0; i < kFramesPerBuffer; ++i) { + EXPECT_NEAR((*output)[n][i], input2[n][i], kEpsilonFloat); + } + } +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/mono_pole_filter.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/mono_pole_filter.cc new file mode 100644 index 000000000..18c4625d5 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/mono_pole_filter.cc @@ -0,0 +1,59 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/mono_pole_filter.h" + +#include <algorithm> + +#include "base/constants_and_types.h" + + +namespace vraudio { + +MonoPoleFilter::MonoPoleFilter(float coefficient) : previous_output_(0.0f) { + SetCoefficient(coefficient); +} + +void MonoPoleFilter::SetCoefficient(float coefficient) { + coefficient_ = std::max(std::min(coefficient, 1.0f), 0.0f); +} + +bool MonoPoleFilter::Filter(const AudioBuffer::Channel& input, + AudioBuffer::Channel* output) { + + DCHECK(output); + const size_t num_frames = input.size(); + DCHECK_EQ(num_frames, output->size()); + + // Do not perform processing if the coefficient is zero to avoid wasteful + // "all pass" cases. + if (coefficient_ < kEpsilonFloat) { + previous_output_ = input[num_frames - 1]; + return false; + } + + // The difference equation implemented here is as follows: + // y[n] = a * (y[n-1] - x[n]) + x[n] + // where y[n] is the output and x[n] is the input vector. + for (size_t frame = 0; frame < num_frames; ++frame) { + (*output)[frame] = + coefficient_ * (previous_output_ - input[frame]) + input[frame]; + previous_output_ = (*output)[frame]; + } + return true; +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/mono_pole_filter.h b/src/3rdparty/resonance-audio/resonance_audio/dsp/mono_pole_filter.h new file mode 100644 index 000000000..4ec3f75dd --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/mono_pole_filter.h @@ -0,0 +1,57 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_DSP_MONO_POLE_FILTER_H_ +#define RESONANCE_AUDIO_DSP_MONO_POLE_FILTER_H_ + +#include "base/audio_buffer.h" + +namespace vraudio { + +// Class representing a mono pole filter. This class also performs filtering. +class MonoPoleFilter { + public: + // Constructs a |MonoPoleFilter| given a single coefficient. + // + // @param coefficient A single coefficient between 0.0f and 1.0f. + explicit MonoPoleFilter(float coefficient); + + // Filter method for use with AudioBuffer::Channel. + // + // @param input |AudioBuffer::Channel| of input to be processed. + // @param output Pointer to output |AudioBuffer::Channel|. + // @return Returns false if the filter has an allpass configuration. This + // helps to avoid copies whenever the output is expected to be identical + // to the input. + bool Filter(const AudioBuffer::Channel& input, AudioBuffer::Channel* output); + + // Sets the filter's coefficent. + // + // @param coefficient A mono pole filter coefficient. + void SetCoefficient(float coefficient); + + private: + // The previous frame computed by the filter. + float previous_output_; + + // Represents and maintains the state of the filter in terms of its + // transfer function coefficient. + float coefficient_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_DSP_MONO_POLE_FILTER_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/mono_pole_filter_test.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/mono_pole_filter_test.cc new file mode 100644 index 000000000..c93b4fab7 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/mono_pole_filter_test.cc @@ -0,0 +1,59 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/mono_pole_filter.h" + +#include <cmath> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/constants_and_types.h" +#include "base/misc_math.h" + +namespace vraudio { + +namespace { + +const float kCoefficient = 0.5f; +const size_t kFramesPerBuffer = 10; + +// Tests that the filter correctly implements the difference equation. +TEST(MonoPoleFilterTest, ImpulseResponseTest) { + MonoPoleFilter filter(kCoefficient); + AudioBuffer buffer(1U, kFramesPerBuffer); + buffer.Clear(); + buffer[0][0] = 1.0f; + EXPECT_TRUE(filter.Filter(buffer[0], &buffer[0])); + for (size_t i = 0; i < kFramesPerBuffer; ++i) { + // The impulse response of this filter (for coefficient a), should be: + // d[n] = (1 - a) * a^n + const float expected = + (1.0f - kCoefficient) * IntegerPow(kCoefficient, static_cast<int>(i)); + EXPECT_NEAR(buffer[0][i], expected, kEpsilonFloat); + } +} + +// Tests that no processing is performed when the filter is allpass. +TEST(MonoPoleFilterTest, AllPassNoProcessingTest) { + MonoPoleFilter filter(0.0f); + AudioBuffer buffer(1U, kFramesPerBuffer); + buffer.Clear(); + buffer[0][0] = 1.0f; + EXPECT_FALSE(filter.Filter(buffer[0], &buffer[0])); +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/multi_channel_iir.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/multi_channel_iir.cc new file mode 100644 index 000000000..dc3bbcb8f --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/multi_channel_iir.cc @@ -0,0 +1,152 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/multi_channel_iir.h" + +#include <algorithm> +#include <limits> + +#include "base/constants_and_types.h" +#include "base/logging.h" +#include "base/simd_macros.h" + + +namespace vraudio { + +std::unique_ptr<MultiChannelIir> MultiChannelIir::Create( + size_t num_channels, size_t frames_per_buffer, + const std::vector<std::vector<float>>& numerators, + const std::vector<std::vector<float>>& denominators) { + CHECK_EQ(denominators.size(), numerators.size()); + CHECK_EQ(denominators.size(), num_channels); + CHECK_EQ(num_channels % SIMD_LENGTH, 0U); + CHECK_GT(num_channels, 0U); + for (size_t channel = 0; channel < num_channels; ++channel) { + CHECK_GT(denominators[channel].size(), 0U); + CHECK_GT(std::abs(denominators[channel][0]), + std::numeric_limits<float>::epsilon()); + CHECK_EQ(denominators[channel].size(), numerators[channel].size()); + } + + std::unique_ptr<MultiChannelIir> multi_channel_iir(new MultiChannelIir( + num_channels, frames_per_buffer, numerators[0].size())); + CHECK(multi_channel_iir); + + for (size_t channel = 0; channel < num_channels; ++channel) { + size_t interleaved_index = channel; + for (size_t i = 0; i < numerators[channel].size(); ++i) { + // Make the denominator polynomials monic, (divide all coefficients by the + // the first denominator coefficients). Furthermore negate all + // coefficients beyond the first (see equation in Process method). + // Copy the coefficients into the |numerator_| and |denominator_| buffers. + multi_channel_iir->numerator_[0][interleaved_index] = + numerators[channel][i] / denominators[channel][0]; + multi_channel_iir->denominator_[0][interleaved_index] = + denominators[channel][i] / + ((i > 0) ? -denominators[channel][0] : denominators[channel][0]); + interleaved_index += num_channels; + } + } + + multi_channel_iir->delay_line_.Clear(); + return multi_channel_iir; +} + +void MultiChannelIir::Process(AudioBuffer::Channel* interleaved_buffer) { + + DCHECK(interleaved_buffer); + DCHECK_EQ(interleaved_buffer->size(), num_channels_ * frames_per_buffer_); + const SimdVector* simd_numerator = + reinterpret_cast<SimdVector*>(&(numerator_[0][0])); + const SimdVector* simd_denominator = + reinterpret_cast<SimdVector*>(&(denominator_[0][0])); + SimdVector* simd_delay_line = + reinterpret_cast<SimdVector*>(&(delay_line_[0][0])); + SimdVector* simd_buffer = + reinterpret_cast<SimdVector*>(&((*interleaved_buffer)[0])); + + const size_t num_channel_chunks = num_channels_ / SIMD_LENGTH; + const size_t num_buffer_chunks = interleaved_buffer->size() / SIMD_LENGTH; + const size_t delay_length_in_chunks = num_channel_chunks * num_coefficients_; + + for (size_t current_frame = 0, individual_frame = 0; + current_frame < num_buffer_chunks; + current_frame += num_channel_chunks, individual_frame += num_channels_) { + DCHECK_LT(individual_frame, interleaved_buffer->size()); + // Copy the current sample into the delay line at the very start. + // {x[n], w[n-1], w[n-2], . . .} where each x, w represents all channels. + std::copy_n(interleaved_buffer->begin() + individual_frame, num_channels_, + delay_line_[0].begin() + (delay_line_front_ * SIMD_LENGTH)); + // Using A Direct Form II implementation difference equation: + // Source: Digital Signal Processing Principles Algorithms & Applications + // Fourth Edition. John G. Prolakis and Dimitris G. Manolakis - Chap 9 + // w[n] = x[n] - (a1/a0)*w[n-1] - (a2/a0)*w[n-2] - . . . + // y(n) = (b0/a0)*w[n] + (b1/a0)*w[n-1] + (b2/a0)*w[n-2] + . . . + // where x[n] is input, w[n] is storage and y[n] is output. + // The division by a0 has been performed in the constructor along with the + // negation of the denominator coefficients beyond the first. Note also + // that each term here refers to a set of channels. + for (size_t channel_chunk = 0; channel_chunk < num_channel_chunks; + ++channel_chunk) { + // First zero out the relevant section of the buffer before accumulation. + // Zero constant used for loading zeros into a neon simd array, as the + // |vld1q_dup_f32| neon intrinsic requires an lvalue parameter. + const float kZerof = 0.0f; + simd_buffer[current_frame + channel_chunk] = SIMD_LOAD_ONE_FLOAT(kZerof); + for (size_t coeff_offset = num_channel_chunks; + coeff_offset < delay_length_in_chunks; + coeff_offset += num_channel_chunks) { + // Denominator part. + const size_t multiplication_index = channel_chunk + coeff_offset; + const size_t delay_multiplication_index = + (multiplication_index + delay_line_front_) % delay_length_in_chunks; + const size_t delay_write_index = channel_chunk + delay_line_front_; + simd_delay_line[delay_write_index] = + SIMD_MULTIPLY_ADD(simd_denominator[multiplication_index], + simd_delay_line[delay_multiplication_index], + simd_delay_line[delay_write_index]); + } + for (size_t coeff_offset = 0; coeff_offset < delay_length_in_chunks; + coeff_offset += num_channel_chunks) { + // Numerator part. + const size_t multiplication_index = channel_chunk + coeff_offset; + const size_t write_index = current_frame + channel_chunk; + const size_t delay_multiplication_index = + (multiplication_index + delay_line_front_) % delay_length_in_chunks; + simd_buffer[write_index] = + SIMD_MULTIPLY_ADD(simd_numerator[multiplication_index], + simd_delay_line[delay_multiplication_index], + simd_buffer[write_index]); + } + } + // Update the index to the wrapped around 'front' of the delay line. + delay_line_front_ = ((static_cast<int>(delay_line_front_) - + num_channel_chunks + delay_length_in_chunks) % + delay_length_in_chunks); + } +} + +MultiChannelIir::MultiChannelIir(size_t num_channels, size_t frames_per_buffer, + size_t num_coefficients) + : num_channels_(num_channels), + frames_per_buffer_(frames_per_buffer), + num_coefficients_(num_coefficients), + delay_line_front_(0), + numerator_(kNumMonoChannels, num_coefficients_ * num_channels_), + denominator_(kNumMonoChannels, num_coefficients_ * num_channels_), + delay_line_(kNumMonoChannels, num_coefficients_ * num_channels_) {} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/multi_channel_iir.h b/src/3rdparty/resonance-audio/resonance_audio/dsp/multi_channel_iir.h new file mode 100644 index 000000000..fd7efe72e --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/multi_channel_iir.h @@ -0,0 +1,88 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_DSP_MULTI_CHANNEL_IIR_H_ +#define RESONANCE_AUDIO_DSP_MULTI_CHANNEL_IIR_H_ + +#include <memory> +#include <vector> + +#include "base/audio_buffer.h" + +namespace vraudio { + +// Class that performs IIR filtering on interleaved data. This class can be used +// to implement any order of IIR filter on multichannel data where the number of +// channels is a multiple of the SIMD vector length. +class MultiChannelIir { + public: + // Returns a |MultiChannelIir| given valid parameters. + // + // @param num_channels Number of channels in each input buffer. The number of + // channels must be divisible by |SIMD_LENGTH|. + // @param frames_per_buffer Number of frames in each input buffer. + // @param numerators Numerator coefficients, one set per channel. + // @param denominators Denominator coefficients, should be equal in length to + // the |numerator| vector, one set per channel. + // @return A |MultiChannelIir| instance. + static std::unique_ptr<MultiChannelIir> Create( + size_t num_channels, size_t frames_per_buffer, + const std::vector<std::vector<float>>& numerators, + const std::vector<std::vector<float>>& denominators); + + // Processes an interleaved buffer of input data with the given IIR filter. + // + // @param interleaved_buffer A single channel of data containing input in + // interleaved format, this will contain output data in interleaved format + // on return. + void Process(AudioBuffer::Channel* interleaved_buffer); + + private: + // Constructs a |MultiChannelIir|. + // + // @param num_channels Number of channels in each input buffer. The number of + // channels must be divisible by |SIMD_LENGTH|. + // @param frames_per_buffer Number of frames in each input buffer. + // @param num_coefficients Number of coefficients in the numerator, which + // equals the number in the denominator. + MultiChannelIir(size_t num_channels, size_t frames_per_buffer, + size_t num_coefficients); + + // Number of channels in each input buffer. + const size_t num_channels_; + + // Number of frames in each input buffer. + const size_t frames_per_buffer_; + + // Number of coefficients in the numerator and denominator polynomials. + const size_t num_coefficients_; + + // Current front of the delay line which is circularly indexed. + size_t delay_line_front_; + + // Stores numerator coefficients in repeated fashion. + AudioBuffer numerator_; + + // Stores denominator coefficients in repeated fashion. + AudioBuffer denominator_; + + // Holds previous data computed from the numerator section. + AudioBuffer delay_line_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_DSP_MULTI_CHANNEL_IIR_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/multi_channel_iir_test.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/multi_channel_iir_test.cc new file mode 100644 index 000000000..3c291196e --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/multi_channel_iir_test.cc @@ -0,0 +1,117 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/multi_channel_iir.h" + +#include <memory> +#include <vector> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/constants_and_types.h" +#include "base/logging.h" + +namespace vraudio { + +namespace { + +const size_t kFramesPerBuffer = 4; + +// Fills a channel buffer with each of the values passed |num_channels| times. +void FillChannels(const std::vector<float>& values, size_t num_channels, + AudioBuffer::Channel* buffer) { + DCHECK_EQ(values.size() * num_channels, buffer->size()); + size_t offset = 0; + for (auto value : values) { + std::fill_n(buffer->begin() + offset, num_channels, value); + offset += num_channels; + } +} + +// Compares the contents of the |input| to the corresponding section of the +// |expected| vector. +void CompareMultiChannelOutput(const std::vector<std::vector<float>>& expected, + size_t offset, size_t num_channels, + const AudioBuffer::Channel& input) { + DCHECK_GE(expected[0].size(), kFramesPerBuffer + offset); + for (size_t channel = 0; channel < num_channels; ++channel) { + for (size_t frame = 0; frame < kFramesPerBuffer; ++frame) { + EXPECT_NEAR(expected[channel][frame + offset], + input[frame * num_channels + channel], kEpsilonFloat); + } + } +} + +// Tests that the |MultiChannelIir| can filter a single channel of input +// simultaneously with four different biquad filters. +TEST(MultiChannelIirTest, MultipleFilterSetProcessTest) { + const std::vector<std::vector<float>> numerators({{1.0f, 3.0f, 5.0f}, + {2.0f, 2.0f, 4.0f}, + {1.0f, 2.0f, 2.0f}, + {2.0f, 4.0f, 2.0f}}); + const std::vector<std::vector<float>> denominators({{1.0f, 1.0f, 0.0f}, + {2.0f, 2.0f, 0.0f}, + {1.0f, 1.0f, 0.0f}, + {2.0f, 2.0f, 0.0f}}); + + const std::vector<float> initial_input({1.0f, 4.0f, 6.0f, 0.0f}); + const std::vector<float> zero_input({0.0f, 0.0f, 0.0f, 0.0f}); + // These values have been determined through the MATLAB commands: + // filter([1 3 5], [1 1 0], [1 4 6 0, 0 0 0 0, 0 0 0 0]) + // filter([2 2 4], [2 2 0], [1 4 6 0, 0 0 0 0, 0 0 0 0]) + // filter([1 2 2], [1 1 0], [1 4 6 0, 0 0 0 0, 0 0 0 0]) + // filter([2 4 2], [2 2 0], [1 4 6 0, 0 0 0 0, 0 0 0 0]) + const std::vector<std::vector<float>> kExpectedOutputs = { + {1.0f, 6.0f, 17.0f, 21.0f, 9.0f, -9.0f, 9.0f, -9.0f, 9.0f, -9.0f, 9.0f, + -9.0f}, + {1.0f, 4.0f, 8.0f, 6.0f, 6.0f, -6.0f, 6.0f, -6.0f, 6.0f, -6.0f, 6.0f, + -6.0f}, + {1.0f, 5.0f, 11.0f, 9.0f, 3.0f, -3.0f, 3.0f, -3.0f, 3.0f, -3.0f, 3.0f, + -3.0f}, + {1.0f, 5.0f, 10.0f, 6.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, + 0.0f}}; + + const size_t num_channels = numerators.size(); + + AudioBuffer interleaved_buffer(kNumMonoChannels, + num_channels * kFramesPerBuffer); + std::unique_ptr<MultiChannelIir> filter = MultiChannelIir::Create( + num_channels, kFramesPerBuffer, numerators, denominators); + + // Filter the initial buffer [1 4 6 0], and compare the output to the first + // kFramesPerBuffer entries in |kExpectedOutputs|. + FillChannels(initial_input, num_channels, &interleaved_buffer[0]); + filter->Process(&(interleaved_buffer[0])); + CompareMultiChannelOutput(kExpectedOutputs, /*offset*/ 0, num_channels, + interleaved_buffer[0]); + + // Filter zeros [0 0 0 0], and compare the output to the next kFramesPerBuffer + // entries in |kExpectedOutputs|. + FillChannels(zero_input, num_channels, &interleaved_buffer[0]); + filter->Process(&(interleaved_buffer[0])); + CompareMultiChannelOutput(kExpectedOutputs, kFramesPerBuffer, num_channels, + interleaved_buffer[0]); + + // Filter zeros [0 0 0 0], and compare the output to the next kFramesPerBuffer + // entries in |kExpectedOutputs|. + FillChannels(zero_input, num_channels, &interleaved_buffer[0]); + filter->Process(&(interleaved_buffer[0])); + CompareMultiChannelOutput(kExpectedOutputs, 2 * kFramesPerBuffer, + num_channels, interleaved_buffer[0]); +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/near_field_processor.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/near_field_processor.cc new file mode 100644 index 000000000..90afcc472 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/near_field_processor.cc @@ -0,0 +1,97 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/near_field_processor.h" + +#include "base/constants_and_types.h" +#include "dsp/filter_coefficient_generators.h" +#include "dsp/gain.h" + +namespace vraudio { + +namespace { + +// Cross-over frequency of the band-splitting filter. +const float kCrossOverFrequencyHz = 1000.0f; + +// +6dB bass boost factor converted to linear scale. +const float kBassBoost = 2.0f; + +// Average group delay of the HRTF filters in seconds. Please see +// [internal ref] +const float kMeanHrtfGroupDelaySeconds = 0.00066667f; + +// Average group delay of the shelf-filter in samples. +const size_t kMeanShelfFilterGroupDelaySamples = 1; + +} // namespace + +NearFieldProcessor::NearFieldProcessor(int sample_rate, + size_t frames_per_buffer) + : frames_per_buffer_(frames_per_buffer), + delay_compensation_(static_cast<size_t>(kMeanHrtfGroupDelaySeconds * + static_cast<float>(sample_rate)) - + kMeanShelfFilterGroupDelaySamples), + lo_pass_filter_(BiquadCoefficients(), frames_per_buffer_), + hi_pass_filter_(BiquadCoefficients(), frames_per_buffer_), + low_passed_buffer_(kNumMonoChannels, frames_per_buffer_), + delay_filter_(delay_compensation_, frames_per_buffer_) { + DCHECK_GT(sample_rate, 0); + DCHECK_GT(frames_per_buffer, 0); + DCHECK_LT(kCrossOverFrequencyHz, 0.5f * static_cast<float>(sample_rate)); + + // Generate biquad coefficients and construct low- and high-pass filter + // states. + BiquadCoefficients lo_pass_coefficients; + BiquadCoefficients hi_pass_coefficients; + ComputeDualBandBiquadCoefficients(sample_rate, kCrossOverFrequencyHz, + &lo_pass_coefficients, + &hi_pass_coefficients); + + // Create two biquad filters initialized with the above filter coefficients. + lo_pass_filter_.SetCoefficients(lo_pass_coefficients); + hi_pass_filter_.SetCoefficients(hi_pass_coefficients); +} + +void NearFieldProcessor::Process(const AudioBuffer::Channel& input, + AudioBuffer::Channel* output, + bool enable_hrtf) { + + DCHECK(output); + DCHECK_EQ(input.size(), frames_per_buffer_); + DCHECK_EQ(output->size(), frames_per_buffer_); + + // Low-pass filter the input and put it in the temporary low-passed buffer. + auto* low_passed_channel = &low_passed_buffer_[0]; + lo_pass_filter_.Filter(input, low_passed_channel); + + // High-pass filter the input and put it in the output channel (unmodified). + hi_pass_filter_.Filter(input, output); + // Iterate through all the samples in the |low_passed_buffer_| and apply + // the bass boost. Then, combine with the high-passed part in order to form + // the shelf-filtered output. Note: phase flip of the low-passed signal is + // required to form the correct filtered output. + ConstantGain(/*offset_index=*/0, -kBassBoost, *low_passed_channel, output, + /*accumulate_output=*/true); + + if (enable_hrtf) { + // Delay the output to compensate for the average HRTF group delay. + delay_filter_.InsertData(*output); + delay_filter_.GetDelayedData(delay_compensation_, output); + } +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/near_field_processor.h b/src/3rdparty/resonance-audio/resonance_audio/dsp/near_field_processor.h new file mode 100644 index 000000000..86a3093d5 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/near_field_processor.h @@ -0,0 +1,75 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_DSP_NEAR_FIELD_PROCESSOR_H_ +#define RESONANCE_AUDIO_DSP_NEAR_FIELD_PROCESSOR_H_ + +#include "base/audio_buffer.h" +#include "dsp/biquad_filter.h" +#include "dsp/delay_filter.h" + +namespace vraudio { + +// Class which applies an approximate near field effect to a source mono input. +// The effect consists of a +6dB bass boost (shelf-filter) as well as phase +// correction (HRTF group delay compensation) when the signal is to be combined +// with the binaural output. +// +// For more information: [internal ref] +class NearFieldProcessor { + public: + // Constructor of the |NearFieldProcessor|, uses the following parameters: + // + // @param sample_rate Sampling rate in [Hz]. + // @param frames_per_buffer Number of frames per buffer in the input/output + // signal. + NearFieldProcessor(int sample_rate, size_t frames_per_buffer); + + // Returns currently used delay compensation in samples. + size_t GetDelayCompensation() const { return delay_compensation_; } + + // Applies approximate near field effect to the source mono input signal. + // + // @param input Mono input channel. + // @param output Pointer to mono output channel. + // @param enable_hrtf Whether to enable delay compensation for HRTF filtering. + void Process(const AudioBuffer::Channel& input, AudioBuffer::Channel* output, + bool enable_hrtf); + + private: + // Number of frames per buffer. + const size_t frames_per_buffer_; + + // Delay compensation computed as average group delay of the HRTF filter + // minus average group delay of the shelf-filter. Should be disabled when + // using with stereo-panned sound sources. + const size_t delay_compensation_; + + // Biquad filters that apply frequency splitting of the input mono signal. + BiquadFilter lo_pass_filter_; + BiquadFilter hi_pass_filter_; + + // Buffer for the low-passed signal. We do not modify the high-passed signal + // so we can write it directly to the output channel. + AudioBuffer low_passed_buffer_; + + // Delay filter used to delay the incoming input mono buffer. + DelayFilter delay_filter_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_DSP_NEAR_FIELD_PROCESSOR_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/occlusion_calculator.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/occlusion_calculator.cc new file mode 100644 index 000000000..9c8d92c1b --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/occlusion_calculator.cc @@ -0,0 +1,52 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/occlusion_calculator.h" + +#include <cmath> + +#include "base/logging.h" +#include "base/misc_math.h" + +namespace vraudio { + +float CalculateDirectivity(float alpha, float order, + const SphericalAngle& spherical_angle) { + // Clamp alpha weighting. + const float alpha_clamped = std::min(std::max(alpha, 0.0f), 1.0f); + + // Check for zero-valued alpha (omnidirectional). + if (alpha_clamped < std::numeric_limits<float>::epsilon()) { + return 1.0f; + } else { + const float gain = (1.0f - alpha_clamped) + + alpha_clamped * (std::cos(spherical_angle.azimuth()) * + std::cos(spherical_angle.elevation())); + + return std::pow(std::abs(gain), std::max(order, 1.0f)); + } +} + +float CalculateOcclusionFilterCoefficient(float directivity, + float occlusion_intensity) { + DCHECK_GE(occlusion_intensity, 0.0f); + + const float occlusion_factor = + 1.0f / IntegerPow(occlusion_intensity + 1.0f, 4); + return std::max(0.0f, 1.0f - directivity * occlusion_factor); +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/occlusion_calculator.h b/src/3rdparty/resonance-audio/resonance_audio/dsp/occlusion_calculator.h new file mode 100644 index 000000000..924da9068 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/occlusion_calculator.h @@ -0,0 +1,54 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_DSP_OCCLUSION_CALCULATOR_H_ +#define RESONANCE_AUDIO_DSP_OCCLUSION_CALCULATOR_H_ + +#include "base/spherical_angle.h" + +namespace vraudio { + +// Calculates directivity gain value for supplied source and listener +// parameters. +// +// @param alpha Balance between dipole pattern and omnidirectional pattern for +// source emission. By varying this value, differing directivity patterns +// can be formed. Value in range [0, 1]. 2D visualization for several +// values: http://goo.gl/GhKvoc. +// @param order Order of directivity function. Higher values will result in +// increased directivity. Value in range [1, +inf]. 2D visualization for +// several orders: +// http://goo.gl/sNrm1a. +// @param spherical_angle Spherical angle of the listener relative to the +// audio source which is being shaped. +// @return Gain value in range [0, 1]. +float CalculateDirectivity(float alpha, float order, + const SphericalAngle& spherical_angle); + +// This function calculates a |MonoPoleFilter| coefficient based upon the +// directivity and occlusion values. The coefficient calculation was designed +// via empirical methods. +// +// @param directivity Gain value calculated based upon the directivity. +// @param occlusion_intensity Gain value calculated based upon the degree of +// occlusion. +// @return Filter coefficient for a mono pole low pass filter. +float CalculateOcclusionFilterCoefficient(float directivity, + float occlusion_intensity); + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_DSP_OCCLUSION_CALCULATOR_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/occlusion_calculator_test.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/occlusion_calculator_test.cc new file mode 100644 index 000000000..4de2d07ad --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/occlusion_calculator_test.cc @@ -0,0 +1,139 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/occlusion_calculator.h" + +#include <algorithm> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/constants_and_types.h" +#include "base/misc_math.h" + +namespace vraudio { + +namespace { + +// Spherical coordinates of listener relative to source. +const float kListenerAheadRads[2] = {0.0f, 0.0f}; +const float kListenerBesideRads[2] = {0.0f, static_cast<float>(M_PI_2)}; +const float kListenerAboveAndAheadRads[2] = {static_cast<float>(M_PI_4), 0.0f}; + +// Test case data structure. +struct DirectivityTestParams { + // Directivity function's alpha weighting. + const float alpha; + // Directivity function's order. + const float order; + // Spherical angle of the listener relative to the source. + const float* relative_spherical_angle; + // Expected value produced by the directivity function for the given input + // parameters. + const float expected_directivity; +}; + +// This test runs through a series of parametrized test cases and checks that +// the directivity value calculated according to those parameters is correct. +class DirectivityCalculatorParametrizedTest + : public testing::Test, + public testing::WithParamInterface<DirectivityTestParams> {}; + +// Parametrized test which verifies that the directivity value calculated from +// the test parameters matches the expected value supplied. +TEST_P(DirectivityCalculatorParametrizedTest, CalculateDirectivityTest) { + // Test parameters. + const DirectivityTestParams test_params = GetParam(); + + // Construct test angle. + const float elevation_rad = test_params.relative_spherical_angle[0]; + const float azimuth_rad = test_params.relative_spherical_angle[1]; + SphericalAngle test_angle(elevation_rad, azimuth_rad); + + // Calculate directivity. + const float directivity = + CalculateDirectivity(test_params.alpha, test_params.order, test_angle); + + // Check calculated directivity. + EXPECT_NEAR(test_params.expected_directivity, directivity, kEpsilonFloat); +} + +// Test parameters, according to struct |DirectivityParams|. +DirectivityTestParams test_cases[] = { + // Omnidirectional. + {0.0f, 1.0f, &(kListenerAheadRads[0]), 1.0f}, + {0.0f, 1.0f, &(kListenerBesideRads[0]), 1.0f}, + {0.0f, 1.0f, &(kListenerAboveAndAheadRads[0]), 1.0f}, + {0.0f, 2.0f, &(kListenerAboveAndAheadRads[0]), 1.0f}, + {0.0f, 0.5f, &(kListenerAboveAndAheadRads[0]), 1.0f}, + // Hypocardioid. + {0.25f, 1.0f, &(kListenerAheadRads[0]), 1.0f}, + {0.25f, 1.0f, &(kListenerBesideRads[0]), 0.75f}, + {0.25f, 1.0f, &(kListenerAboveAndAheadRads[0]), 0.926777f}, + {0.25f, 2.0f, &(kListenerAboveAndAheadRads[0]), 0.858915f}, + {0.25f, 0.5f, &(kListenerAboveAndAheadRads[0]), 0.926777f}, + // Cardioid. + {0.5f, 1.0f, &(kListenerAheadRads[0]), 1.0f}, + {0.5f, 1.0f, &(kListenerBesideRads[0]), 0.5f}, + {0.5f, 1.0f, &(kListenerAboveAndAheadRads[0]), 0.853553f}, + {0.5f, 2.0f, &(kListenerAboveAndAheadRads[0]), 0.728553f}, + // Hypercardioid. + {0.75f, 1.0f, &(kListenerAheadRads[0]), 1.0f}, + {0.75f, 1.0f, &(kListenerBesideRads[0]), 0.25f}, + {0.75f, 1.0f, &(kListenerAboveAndAheadRads[0]), 0.780330f}, + {0.75f, 2.0f, &(kListenerAboveAndAheadRads[0]), 0.608915f}, + {0.75f, 0.5f, &(kListenerAboveAndAheadRads[0]), 0.780330f}, + // Dipole. + {1.0f, 1.0f, &(kListenerAheadRads[0]), 1.0f}, + {1.0f, 1.0f, &(kListenerBesideRads[0]), 0.0f}, + {1.0f, 1.0f, &(kListenerAboveAndAheadRads[0]), 0.707107f}, + {1.0f, 2.0f, &(kListenerAboveAndAheadRads[0]), 0.5f}, + {1.0f, 0.5f, &(kListenerAboveAndAheadRads[0]), 0.707107f}}; + +INSTANTIATE_TEST_CASE_P(Instance, DirectivityCalculatorParametrizedTest, + testing::ValuesIn(test_cases)); + +TEST(DirectivityTest, CalculateOcclusionFilterCoefficientTest) { + // When there is no occlusion (occlusion == 0) expect the filter coefficient + // to be 1 - directivity. + const std::vector<float> directivities = {0.0f, 0.3f, 0.5f, 0.7f, 0.9f, 1.5f}; + for (size_t i = 0; i < directivities.size() - 1; ++i) { + const float coefficient = + CalculateOcclusionFilterCoefficient(directivities[i], 0.0f); + EXPECT_EQ(1.0f - directivities[i], coefficient); + } + // Ensure the minimum value returned is 0. + const float coefficient = + CalculateOcclusionFilterCoefficient(directivities.back(), 0.0f); + EXPECT_EQ(0.0f, coefficient); +} + +TEST(OcclusionTest, CalculateOcclusionFilterCoefficientTest) { + // When there is no effect on directivity, expect the filter coefficient + // to be 1 / (x + 1)^4 where x is the occlusion intensity. + const std::vector<float> occlusions = {0.01f, 0.1f, 1.0f, 10.0f, 100.0f}; + std::vector<float> expected_coefficients = occlusions; + // Calculate 1 / (x + 1)^4 where x is the occlusion intensity. + std::for_each(expected_coefficients.begin(), expected_coefficients.end(), + [](float& n) { n = 1.0f - 1.0f / IntegerPow(n + 1.0f, 4); }); + for (size_t i = 0; i < expected_coefficients.size(); ++i) { + const float coefficient = + CalculateOcclusionFilterCoefficient(1.0f, occlusions[i]); + EXPECT_EQ(expected_coefficients[i], coefficient); + } +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/partitioned_fft_filter.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/partitioned_fft_filter.cc new file mode 100644 index 000000000..4a2f071f8 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/partitioned_fft_filter.cc @@ -0,0 +1,267 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Prevent Visual Studio from complaining about std::copy_n. +#if defined(_WIN32) +#define _SCL_SECURE_NO_WARNINGS +#endif + +#include "dsp/partitioned_fft_filter.h" + +#include <algorithm> + +#include "pffft.h" +#include "base/constants_and_types.h" +#include "base/logging.h" +#include "base/misc_math.h" +#include "base/simd_utils.h" + +#include "dsp/utils.h" + +namespace vraudio { + +PartitionedFftFilter::PartitionedFftFilter(size_t filter_size, + size_t frames_per_buffer, + FftManager* fft_manager) + : PartitionedFftFilter(filter_size, frames_per_buffer, filter_size, + fft_manager) {} + +PartitionedFftFilter::PartitionedFftFilter(size_t filter_size, + size_t frames_per_buffer, + size_t max_filter_size, + FftManager* fft_manager) + : fft_manager_(fft_manager), + fft_size_(fft_manager_->GetFftSize()), + chunk_size_(fft_size_ / 2), + frames_per_buffer_(frames_per_buffer), + max_filter_size_( + CeilToMultipleOfFramesPerBuffer(max_filter_size, frames_per_buffer_)), + max_num_partitions_(max_filter_size_ / frames_per_buffer_), + filter_size_( + CeilToMultipleOfFramesPerBuffer(filter_size, frames_per_buffer_)), + num_partitions_(filter_size_ / frames_per_buffer_), + kernel_freq_domain_buffer_(max_num_partitions_, fft_size_), + buffer_selector_(0), + curr_front_buffer_(0), + freq_domain_buffer_(max_num_partitions_, fft_size_), + filtered_time_domain_buffers_(kNumStereoChannels, fft_size_), + freq_domain_accumulator_(kNumMonoChannels, fft_size_), + temp_zeropad_buffer_(kNumMonoChannels, chunk_size_), + temp_kernel_chunk_buffer_(kNumMonoChannels, frames_per_buffer_) { + // Ensure that |frames_per_buffer_| is less than or equal to the final + // partition |chunk_size_|. + CHECK(fft_manager_); + CHECK_LE(frames_per_buffer_, chunk_size_); + CHECK_GE(filter_size_, filter_size); + CHECK_GE(max_filter_size_, max_filter_size); + // Ensure that |filter_size_| does not exceed |max_filter_size_|. + CHECK_LE(filter_size, max_filter_size_); + // Make sure all partitions have the same size. + CHECK_EQ(num_partitions_ * frames_per_buffer_, filter_size_); + CHECK_EQ(max_num_partitions_ * frames_per_buffer_, max_filter_size_); + + Clear(); +} + +void PartitionedFftFilter::Clear() { + // Reset valid part of the filter |FreqDomainBuffer|s to zero. + for (size_t i = 0; i < num_partitions_; ++i) { + kernel_freq_domain_buffer_[i].Clear(); + freq_domain_buffer_[i].Clear(); + } + // Reset filter state to zero. + filtered_time_domain_buffers_.Clear(); +} + +void PartitionedFftFilter::ResetFreqDomainBuffers(size_t new_filter_size) { + + // Update the filter size. + DCHECK_GT(new_filter_size, 0U); + filter_size_ = + CeilToMultipleOfFramesPerBuffer(new_filter_size, frames_per_buffer_); + DCHECK_LE(filter_size_, max_filter_size_); + + const size_t old_num_partitions = num_partitions_; + num_partitions_ = filter_size_ / frames_per_buffer_; + const size_t min_num_partitions = + std::min(old_num_partitions, num_partitions_); + + if (curr_front_buffer_ > 0) { + + FreqDomainBuffer temp_freq_domain_buffer(min_num_partitions, fft_size_); + // Copy in |min_num_partitions| to |temp_freq_domain_buffer|, starting with + // the partition at |curr_front_buffer_| to be moved back to the beginning + // of |freq_domain_buffer| . + for (size_t i = 0; i < min_num_partitions; ++i) { + temp_freq_domain_buffer[i] = + freq_domain_buffer_[(curr_front_buffer_ + i) % old_num_partitions]; + } + // Replace the partitions. + for (size_t i = 0; i < min_num_partitions; ++i) { + freq_domain_buffer_[i] = temp_freq_domain_buffer[i]; + } + curr_front_buffer_ = 0; + } + // Clear out the remaining partitions in case the filter size grew. + for (size_t i = old_num_partitions; i < num_partitions_; ++i) { + freq_domain_buffer_[i].Clear(); + } +} + +void PartitionedFftFilter::ReplacePartition( + size_t partition_index, const AudioBuffer::Channel& kernel_chunk) { + DCHECK_GE(partition_index, 0U); + DCHECK_LT(partition_index, num_partitions_); + DCHECK_EQ(kernel_chunk.size(), frames_per_buffer_); + + fft_manager_->FreqFromTimeDomain( + kernel_chunk, &kernel_freq_domain_buffer_[partition_index]); +} + +void PartitionedFftFilter::SetFilterLength(size_t new_filter_size) { + DCHECK_GT(new_filter_size, 0U); + new_filter_size = + CeilToMultipleOfFramesPerBuffer(new_filter_size, frames_per_buffer_); + DCHECK_LE(new_filter_size, max_filter_size_); + + const size_t new_num_partitions = new_filter_size / frames_per_buffer_; + DCHECK_LE(new_num_partitions, max_num_partitions_); + + // Clear out the remaining partitions in case the filter size grew. + for (size_t i = num_partitions_; i < new_num_partitions; ++i) { + kernel_freq_domain_buffer_[i].Clear(); + } + // Call |ResetFreqDomainBuffers| to make sure that the input buffers are also + // correctly resized. + ResetFreqDomainBuffers(new_filter_size); +} + +void PartitionedFftFilter::SetTimeDomainKernel( + const AudioBuffer::Channel& kernel) { + + + // Precomputes a set of floor(|filter_size_|/(|fft_size|/2)) frequency domain + // kernels, one for each partition of the |kernel|. This allows to reduce + // computational complexity if a fixed set of filter kernels is needed. The + // size of the |kernel| can be arbitrarily long but must not be less than + // |fft_size_|/2. Filter lengths which are multiples of |fft_size_|/2 are most + // efficient. + + // Calculate the new number of partitions as filter length may have changed. + // This number is set to 1 if the filter length is smaller than + // |frames_per_buffer_|. + const size_t new_num_partitions = + CeilToMultipleOfFramesPerBuffer(kernel.size(), frames_per_buffer_) / + frames_per_buffer_; + + auto& padded_channel = temp_kernel_chunk_buffer_[0]; + // Break up time domain filter into chunks and FFT each of these separately. + for (size_t partition = 0; partition < new_num_partitions; ++partition) { + DCHECK_LE(partition * frames_per_buffer_, kernel.size()); + const float* chunk_begin_itr = + kernel.begin() + partition * frames_per_buffer_; + const size_t num_frames_to_copy = + std::min<size_t>(frames_per_buffer_, kernel.end() - chunk_begin_itr); + + std::copy_n(chunk_begin_itr, num_frames_to_copy, padded_channel.begin()); + // This fill only occurs on the very last partition. + std::fill(padded_channel.begin() + num_frames_to_copy, padded_channel.end(), + 0.0f); + fft_manager_->FreqFromTimeDomain(padded_channel, + &kernel_freq_domain_buffer_[partition]); + } + + if (new_num_partitions != num_partitions_) { + const size_t new_filter_size = new_num_partitions * frames_per_buffer_; + ResetFreqDomainBuffers(new_filter_size); + } +} + +void PartitionedFftFilter::SetFreqDomainKernel(const FreqDomainBuffer& kernel) { + DCHECK_LE(kernel.num_channels(), max_num_partitions_); + DCHECK_EQ(kernel.num_frames(), fft_size_); + + const size_t new_num_partitions = kernel.num_channels(); + for (size_t i = 0; i < new_num_partitions; ++i) { + kernel_freq_domain_buffer_[i] = kernel[i]; + } + if (new_num_partitions != num_partitions_) { + const size_t new_filter_size = new_num_partitions * frames_per_buffer_; + ResetFreqDomainBuffers(new_filter_size); + } +} + +void PartitionedFftFilter::Filter(const FreqDomainBuffer::Channel& input) { + + + DCHECK_EQ(input.size(), fft_size_); + std::copy_n(input.begin(), fft_size_, + freq_domain_buffer_[curr_front_buffer_].begin()); + buffer_selector_ = !buffer_selector_; + freq_domain_accumulator_.Clear(); + auto* accumulator_channel = &freq_domain_accumulator_[0]; + + for (size_t i = 0; i < num_partitions_; ++i) { + // Complex vector product in frequency domain with filter kernel. + const size_t modulo_index = (curr_front_buffer_ + i) % num_partitions_; + + // Perform inverse scaling along with accumulation of last fft buffer. + fft_manager_->FreqDomainConvolution(freq_domain_buffer_[modulo_index], + kernel_freq_domain_buffer_[i], + accumulator_channel); + } + // Our modulo based index. + curr_front_buffer_ = + (curr_front_buffer_ + num_partitions_ - 1) % num_partitions_; + // Perform inverse FFT transform of |freq_domain_buffer_| and store the + // result back in |filtered_time_domain_buffers_|. + fft_manager_->TimeFromFreqDomain( + *accumulator_channel, &filtered_time_domain_buffers_[buffer_selector_]); +} + +void PartitionedFftFilter::GetFilteredSignal(AudioBuffer::Channel* output) { + + DCHECK(output); + DCHECK_EQ(output->size(), frames_per_buffer_); + + const size_t curr_buffer = buffer_selector_; + const size_t prev_buffer = !buffer_selector_; + + // Overlap add. + if (frames_per_buffer_ == chunk_size_) { + AddPointwise(chunk_size_, &(filtered_time_domain_buffers_[curr_buffer][0]), + &filtered_time_domain_buffers_[prev_buffer][chunk_size_], + &((*output)[0])); + } else { + // If we have a non power of two |frames_per_buffer| we will have to perform + // the overlap add and then a copy, as buffer lengths are not a multiple of + // |chunk_size_|. NOTE: Indexing into |filtered_time_domain_buffers_| for a + // non power of two |frames_per_buffer_| means that the |input_b| parameter + // of |AddPointwiseOutOfPlace| may not be aligned in memory and thus we will + // not be able to use SIMD for the overlap add operation. + const auto& first_channel = filtered_time_domain_buffers_[curr_buffer]; + const auto& second_channel = filtered_time_domain_buffers_[prev_buffer]; + auto& output_channel = temp_zeropad_buffer_[0]; + for (size_t i = 0; i < frames_per_buffer_; ++i) { + output_channel[i] = + first_channel[i] + second_channel[i + frames_per_buffer_]; + } + std::copy_n(temp_zeropad_buffer_[0].begin(), frames_per_buffer_, + output->begin()); + } +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/partitioned_fft_filter.h b/src/3rdparty/resonance-audio/resonance_audio/dsp/partitioned_fft_filter.h new file mode 100644 index 000000000..f8399922a --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/partitioned_fft_filter.h @@ -0,0 +1,167 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_DSP_PARTITIONED_FFT_FILTER_H_ +#define RESONANCE_AUDIO_DSP_PARTITIONED_FFT_FILTER_H_ + +#include <vector> + +#include "base/audio_buffer.h" +#include "base/misc_math.h" +#include "dsp/fft_manager.h" + +struct PFFFT_Setup; + +namespace vraudio { + +// Class performing a FFT-based overlap and add FIR convolution. +// Given an FFT size N and a filter size M > N/2; the filter is broken in to +// floor(M/(N/2)) partitions in the time domain. This results in a set of +// frequency domain "filters" of length (N/2)+1. +class PartitionedFftFilter { + public: + // Typedef declares the data type for storing frequency domain buffers. Each + // channel stores the kernel for a partition. + typedef AudioBuffer FreqDomainBuffer; + + // Constructor preallocates memory based on the |filter_size|. This can be + // used for simplicity if the filter size will be constant after creation. + // + // @param filter_size Length of the time domain filter in samples. This will + // be increased such that it becomes a multiple of |chunk_size_|. + // @param frames_per_buffer Number of points in each time domain input buffer. + // @param fft_manager Pointer to a manager to perform FFT transformations. + PartitionedFftFilter(size_t filter_size, size_t frames_per_buffer, + FftManager* fft_manager); + + // Constructor preallocates memory based on the |max_filter_size|. The + // |fft_size_| will be twice |frames_per_buffer| if this is a power of two and + // twice the next larger power of two if it is not. + // + // @param filter_size Length of the time domain filter in samples. This will + // be increased such that it becomes a multiple of |chunk_size_|. + // @param frames_per_buffer Number of points in each time domain input buffer. + // @param max_filter_size Maximum length that |filter_size| can get. + // @param fft_manager Pointer to a manager for all fft related functionality. + PartitionedFftFilter(size_t filter_size, size_t frames_per_buffer, + size_t max_filter_size, FftManager* fft_manager); + + // Initializes the FIR filter from a time domain kernel. + // + // @parem kernel Time domain filter to be used for processing. + void SetTimeDomainKernel(const AudioBuffer::Channel& kernel); + + // Initializes the FIR filter from a precomputed frequency domain kernel. + // + // @param kernel Frequency domain filter to be used for processing. + void SetFreqDomainKernel(const FreqDomainBuffer& kernel); + + // Replaces a partition indicated by the |partition_index| with + // |kernel_chunk|'s frequency domain equivalent. + // + // @param partition_index Location (partition) of the time domain filter we + // wish to replace. + // @param kernel_chunk |fft_size_|/2 length chunk of a filter used to + // replace the |partition_index|th partition. + void ReplacePartition(size_t partition_index, + const AudioBuffer::Channel& kernel_chunk); + + // Alters the filter length by adding or removing partitions in the frequency + // domain. If |new_filter_size| is not a multiple of |chunk_size_| (i.e. + // frames per buffer), then the time domain filter kernel will be zeropadded + // to a multiple of |chunk_size_|. + // + // @param new_filter_size New length of the time domain filter kernel. + void SetFilterLength(size_t new_filter_size); + + // Processes a block of frequency domain samples. The size of the input + // block must be |fft_size_|. + // + // @param Frequency domain input buffer. + void Filter(const FreqDomainBuffer::Channel& input); + + // Returns block of filtered signal output of size |fft_size_|/2. + // + // @param output Time domain block filtered with the given kernel. + void GetFilteredSignal(AudioBuffer::Channel* output); + + // Resets the filter state. + void Clear(); + + private: + friend class PartitionedFftFilterFrequencyBufferTest; + + // Adjusts the |num_partitions_| and the size of |freq_domain_buffers_| for + // use with a new time domain filter kernel greater in length than the + // previous kernel. + // + // @param new_kernel_size Length of the time domain filter kernel. + void ResetFreqDomainBuffers(size_t new_kernel_size); + + // Manager for all FFT related functionality (not owned). + FftManager* const fft_manager_; + + // Number of points in the |fft_manager_|s FFT. + const size_t fft_size_; + + // Size of each partition of the filter in time domain. + const size_t chunk_size_; + + // Number of frames in each buffer of input data. + const size_t frames_per_buffer_; + + // Maximum filter size in samples. + const size_t max_filter_size_; + + // Maximum partition count. + const size_t max_num_partitions_; + + // Filter size in samples. + size_t filter_size_; + + // Partition Count. + size_t num_partitions_; + + // Kernel buffer in frequency domain. + FreqDomainBuffer kernel_freq_domain_buffer_; + + // Buffer selector to switch between two filtered signal buffers. + size_t buffer_selector_; + + // The freq_domain_buffer we will write new incoming audio into. + size_t curr_front_buffer_; + + // Frequency domain buffer used to perform filtering. + FreqDomainBuffer freq_domain_buffer_; + + // Two buffers that are consecutively filled with filtered signal output. + AudioBuffer filtered_time_domain_buffers_; + + // Accumulator for the outputs from each convolution partition + FreqDomainBuffer freq_domain_accumulator_; + + // Temporary time domain buffer to store output when zero padding has been + // applied due to non power of two input buffer lengths. + AudioBuffer temp_zeropad_buffer_; + + // Temporary time domain buffer to hold time domain kernel chunks during + // conversion of a kernel from time to frequency domain. + AudioBuffer temp_kernel_chunk_buffer_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_DSP_PARTITIONED_FFT_FILTER_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/partitioned_fft_filter_test.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/partitioned_fft_filter_test.cc new file mode 100644 index 000000000..230ec2544 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/partitioned_fft_filter_test.cc @@ -0,0 +1,761 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Prevent Visual Studio from complaining about std::copy_n. +#if defined(_WIN32) +#define _SCL_SECURE_NO_WARNINGS +#endif + +#include "dsp/partitioned_fft_filter.h" + +#include <cmath> +#include <iostream> +#include <vector> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/audio_buffer.h" +#include "base/constants_and_types.h" +#include "base/logging.h" +#include "dsp/fft_manager.h" + +namespace vraudio { + +namespace { + +// Permitted error in the output relative to the expected output. This value is +// 1e-3 as expected output is rounded for readability. +const float kFftEpsilon = 1e-3f; + +// Length of input buffer for use in LongerShorterKernelTest. +const size_t kLength = 32; + +// Helper function for use in LongerShorterKernelTest. Passes a dirac of length +// kLength through a filter followed by |zeros_iteration| zero vectors of the +// same length. +void ProcessFilterWithImpulseSignal(PartitionedFftFilter* filter, + FftManager* fft_manager, + size_t zeros_iteration, + std::vector<float>* output_signal) { + AudioBuffer signal_buffer(kNumMonoChannels, kLength); + signal_buffer.Clear(); + signal_buffer[0][0] = 1.0f; + AudioBuffer output_buffer(kNumMonoChannels, kLength); + + // Begin filtering with the original (small) kernel. + PartitionedFftFilter::FreqDomainBuffer freq_domain_buffer(kNumMonoChannels, + kLength * 2); + fft_manager->FreqFromTimeDomain(signal_buffer[0], &freq_domain_buffer[0]); + filter->Filter(freq_domain_buffer[0]); + filter->GetFilteredSignal(&output_buffer[0]); + output_signal->insert(output_signal->end(), output_buffer[0].begin(), + output_buffer[0].end()); + // Filter zeros to flush out internal buffers. + std::fill(signal_buffer[0].begin(), signal_buffer[0].end(), 0.0f); + for (size_t i = 0; i < zeros_iteration; ++i) { + fft_manager->FreqFromTimeDomain(signal_buffer[0], &freq_domain_buffer[0]); + filter->Filter(freq_domain_buffer[0]); + filter->GetFilteredSignal(&output_buffer[0]); + output_signal->insert(output_signal->end(), output_buffer[0].begin(), + output_buffer[0].end()); + } +} + +// Tests that convolution will work correctly when the input buffer length is +// not a power of two. +TEST(PartitionedFftFilterTest, NonPow2Test) { + // Non power of two input buffer length. + const size_t kMinNonPowTwo = 13; + const size_t kMaxNonPowTwo = 41; + for (size_t buffer_length = kMinNonPowTwo; buffer_length <= kMaxNonPowTwo; + buffer_length += 2) { + for (size_t filter_length = kLength; filter_length <= 3 * kLength; + filter_length += kLength) { + // Use a filter length that is a power of two. + AudioBuffer kernel_buffer(kNumMonoChannels, filter_length); + // Place to collect all of the output. + std::vector<float> output_signal; + // First set the kernel to a linear ramp. + for (size_t i = 0; i < filter_length; ++i) { + kernel_buffer[0][i] = static_cast<float>(i) / 4.0f; + } + FftManager fft_manager(buffer_length); + PartitionedFftFilter filter(filter_length, buffer_length, &fft_manager); + filter.SetTimeDomainKernel(kernel_buffer[0]); + + // Kronecker delta signal. + AudioBuffer signal_buffer(kNumMonoChannels, buffer_length); + signal_buffer.Clear(); + signal_buffer[0][0] = 1.0f; + AudioBuffer output_buffer(kNumMonoChannels, buffer_length); + // Create a freq domain buffer which should be fft_size + // (i.e. NextPowTwo(buffer_length) * 2). + PartitionedFftFilter::FreqDomainBuffer freq_domain_buffer( + kNumMonoChannels, NextPowTwo(buffer_length) * 2); + freq_domain_buffer.Clear(); + fft_manager.FreqFromTimeDomain(signal_buffer[0], &freq_domain_buffer[0]); + // Perform convolution. + filter.Filter(freq_domain_buffer[0]); + filter.GetFilteredSignal(&output_buffer[0]); + output_signal.insert(output_signal.end(), output_buffer[0].begin(), + output_buffer[0].end()); + while (output_signal.size() < kernel_buffer.num_frames()) { + // Flush with zeros. + freq_domain_buffer.Clear(); + output_buffer.Clear(); + filter.Filter(freq_domain_buffer[0]); + filter.GetFilteredSignal(&output_buffer[0]); + output_signal.insert(output_signal.end(), output_buffer[0].begin(), + output_buffer[0].end()); + } + // Ensure the output is identical to the input buffer. (large epsilon + // needed due to large values in filter). + for (size_t i = 0; i < kernel_buffer.num_frames(); ++i) { + EXPECT_NEAR(kernel_buffer[0][i], output_signal[i], + kEpsilonFloat * 4.0f); + } + } + } +} + +// Tests that the outputs from the convolution are correct based on a +// precomputed vector from MATLAB. +TEST(PartitionedFftFilterTest, CorrectNonPowTwoOutputTest) { + const size_t kBufferSize = 15; + + // Create an arbirary vector of size 16 for filter and 15 for input signal. + const std::vector<float> kernel = {1.0f, 3.0f, 0.0f, 2.0f, 5.0f, 1.0f, + 3.0f, 2.0f, 0.0f, 4.0f, 1.0f, 3.0f, + 0.0f, 2.0f, 1.0f, 2.0f}; + const std::vector<float> signal = {2.0f, 3.0f, 3.0f, 4.0f, 0.0f, + 0.0f, 2.0f, 1.0f, 2.0f, 1.0f, + 3.0f, 2.0f, 4.0f, 0.0f, 2.0f}; + // Ideal output vector verified with MATLAB. + const std::vector<float> ideal_output = { + 2.0f, 9.0f, 12.0f, 17.0f, 28.0f, 23.0f, 34.0f, 43.0f, 24.0f, + 37.0f, 40.0f, 43.0f, 57.0f, 49.0f, 50.0f, 57.0f, 65.0f, 57.0f, + 60.0f, 61.0f, 47.0f, 74.0f, 59.0f, 55.0f, 48.0f, 62.0f, 51.0f, + 69.0f, 51.0f, 54.0f, 55.0f, 56.0f, 45.0f, 43.0f, 33.0f, 24.0f, + 40.0f, 16.0f, 31.0f, 11.0f, 22.0f, 8.0f, 12.0f, 2.0f, 4.0f}; + + AudioBuffer kernel_buffer(kNumMonoChannels, kernel.size()); + kernel_buffer[0] = kernel; + + AudioBuffer signal_buffer(kNumMonoChannels, signal.size()); + signal_buffer[0] = signal; + + AudioBuffer output_buffer(kNumMonoChannels, signal.size()); + + std::vector<float> output_signal; + output_signal.reserve(kernel.size() + signal.size() * 3); + + FftManager fft_manager(kBufferSize); + PartitionedFftFilter filter(kernel.size(), kBufferSize, &fft_manager); + filter.SetTimeDomainKernel(kernel_buffer[0]); + + PartitionedFftFilter::FreqDomainBuffer freq_domain_buffer( + 1, NextPowTwo(kBufferSize) * 2); + fft_manager.FreqFromTimeDomain(signal_buffer[0], &freq_domain_buffer[0]); + filter.Filter(freq_domain_buffer[0]); + filter.GetFilteredSignal(&output_buffer[0]); + output_signal.insert(output_signal.end(), output_buffer[0].begin(), + output_buffer[0].end()); + + // Filter again with the same input. + filter.Filter(freq_domain_buffer[0]); + filter.GetFilteredSignal(&output_buffer[0]); + output_signal.insert(output_signal.end(), output_buffer[0].begin(), + output_buffer[0].end()); + + // Filter zeros to flush out internal buffers. + signal_buffer.Clear(); + fft_manager.FreqFromTimeDomain(signal_buffer[0], &freq_domain_buffer[0]); + filter.Filter(freq_domain_buffer[0]); + filter.GetFilteredSignal(&output_buffer[0]); + output_signal.insert(output_signal.end(), output_buffer[0].begin(), + output_buffer[0].end()); + + for (size_t sample = 0; sample < ideal_output.size(); ++sample) { + EXPECT_NEAR(output_signal[sample], ideal_output[sample], kFftEpsilon); + } +} + +// Tests that we can switch to a time domain filter kernel of greater +// length than the original kernal provided at instantiation. It then tests that +// we can switch to a time domain filter kernel of lesser length than the +// original kernel. +TEST(PartitionedFftFilterTest, LongerShorterTimeDomainKernelTest) { + AudioBuffer small_kernel_buffer(kNumMonoChannels, kLength); + AudioBuffer big_kernel_buffer(kNumMonoChannels, kLength * 2); + + // Place to collect all of the output. + std::vector<float> total_output_signal; + + // First set the kernels to linear ramps of differing lengths. + for (size_t i = 0; i < kLength * 2; ++i) { + if (i < kLength) { + small_kernel_buffer[0][i] = static_cast<float>(i) / 4.0f; + } + big_kernel_buffer[0][i] = static_cast<float>(i) / 4.0f; + } + + FftManager fft_manager(kLength); + PartitionedFftFilter filter(small_kernel_buffer.num_frames(), kLength, + big_kernel_buffer.num_frames(), &fft_manager); + + filter.SetTimeDomainKernel(small_kernel_buffer[0]); + ProcessFilterWithImpulseSignal(&filter, &fft_manager, 2, + &total_output_signal); + + filter.SetTimeDomainKernel(big_kernel_buffer[0]); + ProcessFilterWithImpulseSignal(&filter, &fft_manager, 3, + &total_output_signal); + + filter.SetTimeDomainKernel(small_kernel_buffer[0]); + ProcessFilterWithImpulseSignal(&filter, &fft_manager, 2, + &total_output_signal); + + // Test to see if output from both kernels is present kLength * 2 zeros in + // between. + for (size_t i = 0; i < kLength; ++i) { + EXPECT_NEAR(static_cast<float>(i) / 4.0f, total_output_signal[i], + kFftEpsilon); + EXPECT_NEAR(0.0f, total_output_signal[i + kLength], kFftEpsilon); + EXPECT_NEAR(0.0f, total_output_signal[i + 2 * kLength], kFftEpsilon); + EXPECT_NEAR(static_cast<float>(i) / 4.0f, + total_output_signal[i + 3 * kLength], kFftEpsilon); + EXPECT_NEAR(static_cast<float>(i + kLength) / 4.0f, + total_output_signal[i + 4 * kLength], kFftEpsilon); + EXPECT_NEAR(0.0f, total_output_signal[i + 5 * kLength], kFftEpsilon); + EXPECT_NEAR(0.0f, total_output_signal[i + 6 * kLength], kFftEpsilon); + EXPECT_NEAR(static_cast<float>(i) / 4.0f, + total_output_signal[i + 7 * kLength], kFftEpsilon); + EXPECT_NEAR(0.0f, total_output_signal[i + 8 * kLength], kFftEpsilon); + EXPECT_NEAR(0.0f, total_output_signal[i + 9 * kLength], kFftEpsilon); + } +} + +// Tests that the outputs from the same convolution performed with different FFT +// sizes will all return equal results, including when the FFT size is equal to +// the kernel size. +TEST(PartitionedFftFilterTest, PartitionSizeInvarianceTest) { + const std::vector<size_t> kFftSizes = {32, 64, 128}; + + // Create an arbirary vector of size 128 for both filter and input signal. + const size_t max_fft_size = kFftSizes[kFftSizes.size() - 1]; + AudioBuffer kernel_buffer(kNumMonoChannels, max_fft_size); + AudioBuffer signal_buffer(kNumMonoChannels, max_fft_size); + for (size_t i = 0; i < max_fft_size; ++i) { + kernel_buffer[0][i] = static_cast<float>(static_cast<int>(i) % 13 - 7); + signal_buffer[0][i] = static_cast<float>(static_cast<int>(i) % 17 - 9); + } + + std::vector<std::vector<float>> output_signal( + kFftSizes.size(), std::vector<float>(max_fft_size * 2)); + + // Iterate over 3 fft sizes. + for (size_t fft_idx = 0; fft_idx < kFftSizes.size(); ++fft_idx) { + const size_t chunk_size = kFftSizes[fft_idx] / 2; + FftManager fft_manager(chunk_size); + PartitionedFftFilter filter(max_fft_size, chunk_size, &fft_manager); + filter.SetTimeDomainKernel(kernel_buffer[0]); + + AudioBuffer input_chunk(kNumMonoChannels, chunk_size); + AudioBuffer output_chunk(kNumMonoChannels, chunk_size); + PartitionedFftFilter::FreqDomainBuffer freq_domain_buffer( + 1, kFftSizes[fft_idx]); + + // Break the input signal into chunks of fft size / 2. + for (size_t chunk = 0; chunk < (max_fft_size / chunk_size); ++chunk) { + AudioBuffer signal_block(kNumMonoChannels, chunk_size); + std::copy_n(signal_buffer[0].begin() + (chunk * chunk_size), chunk_size, + signal_block[0].begin()); + + fft_manager.FreqFromTimeDomain(signal_block[0], &freq_domain_buffer[0]); + filter.Filter(freq_domain_buffer[0]); + filter.GetFilteredSignal(&output_chunk[0]); + output_signal[fft_idx].insert(output_signal[fft_idx].end(), + output_chunk[0].begin(), + output_chunk[0].end()); + } + } + // Now test the outputs are pretty much equal to one another (to the first). + for (size_t i = 1; i < kFftSizes.size(); ++i) { + for (size_t sample = 0; sample < output_signal[0].size(); ++sample) { + EXPECT_NEAR(output_signal[0][sample], output_signal[i][sample], + kFftEpsilon); + } + } +} + +// Tests that the outputs from the convolution are correct based on a +// precomputed vector from MATLAB. +TEST(PartitionedFftFilterTest, CorrectOutputTest) { + const size_t kBufferSize = 32; + + // Create an arbirary vector of size 32 for both filter and input signal. + const std::vector<float> kernel = { + 1.0f, 3.0f, 0.0f, 2.0f, 5.0f, 1.0f, 3.0f, 2.0f, 0.0f, 4.0f, 1.0f, + 3.0f, 0.0f, 2.0f, 1.0f, 2.0f, 2.0f, 1.0f, 0.0f, 3.0f, 5.0f, 2.0f, + 3.0f, 0.0f, 1.0f, 4.0f, 2.0f, 0.0f, 1.0f, 0.0f, 2.0f, 1.0f}; + const std::vector<float> signal = { + 2.0f, 1.0f, 3.0f, 3.0f, 2.0f, 4.0f, 2.0f, 1.0f, 3.0f, 4.0f, 5.0f, + 3.0f, 2.0f, 2.0f, 5.0f, 4.0f, 5.0f, 3.0f, 3.0f, 4.0f, 0.0f, 0.0f, + 2.0f, 1.0f, 2.0f, 1.0f, 3.0f, 2.0f, 4.0f, 0.0f, 2.0f, 1.0f}; + + AudioBuffer kernel_buffer(kNumMonoChannels, kernel.size()); + kernel_buffer[0] = kernel; + + AudioBuffer signal_buffer(kNumMonoChannels, signal.size()); + signal_buffer[0] = signal; + + AudioBuffer output_buffer(kNumMonoChannels, kernel.size()); + + std::vector<float> output_signal; + output_signal.reserve(kernel.size() + signal.size()); + + FftManager fft_manager(kBufferSize); + PartitionedFftFilter filter(kernel.size(), kBufferSize, &fft_manager); + filter.SetTimeDomainKernel(kernel_buffer[0]); + + PartitionedFftFilter::FreqDomainBuffer freq_domain_buffer(kNumMonoChannels, + kBufferSize * 2); + fft_manager.FreqFromTimeDomain(signal_buffer[0], &freq_domain_buffer[0]); + filter.Filter(freq_domain_buffer[0]); + filter.GetFilteredSignal(&output_buffer[0]); + output_signal.insert(output_signal.end(), output_buffer[0].begin(), + output_buffer[0].end()); + + // Filter zeros to flush out internal buffers. + signal_buffer.Clear(); + fft_manager.FreqFromTimeDomain(signal_buffer[0], &freq_domain_buffer[0]); + filter.Filter(freq_domain_buffer[0]); + filter.GetFilteredSignal(&output_buffer[0]); + output_signal.insert(output_signal.end(), output_buffer[0].begin(), + output_buffer[0].end()); + + // Ideal output vector verified with MATLAB. + const std::vector<float> kIdeal = { + 2.0f, 7.0f, 6.0f, 16.0f, 23.0f, 23.0f, 42.0f, 36.0f, 38.0f, + 62.0f, 51.0f, 66.0f, 67.0f, 72.0f, 88.0f, 90.0f, 90.0f, 95.0f, + 104.0f, 118.0f, 127.0f, 113.0f, 123.0f, 131.0f, 116.0f, 144.0f, 119.0f, + 126.0f, 138.0f, 138.0f, 153.0f, 142.0f, 130.0f, 125.0f, 137.0f, 142.0f, + 121.0f, 113.0f, 99.0f, 112.0f, 84.0f, 89.0f, 68.0f, 66.0f, 74.0f, + 54.0f, 54.0f, 59.0f, 53.0f, 42.0f, 42.0f, 26.0f, 32.0f, 28.0f, + 18.0f, 15.0f, 19.0f, 9.0f, 12.0f, 5.0f, 4.0f, 4.0f, 1.0f}; + for (size_t sample = 0; sample < kIdeal.size(); ++sample) { + EXPECT_NEAR(output_signal[sample], kIdeal[sample], kFftEpsilon); + } +} + +// Tests that the outputs from the convolution are equal to zero when the inputs +// are all zero. +TEST(PartitionedFftFilterTest, ZeroInputZeroOutputTest) { + const size_t kChunkSize = 16; + + // Create an arbirary vector of size 16 for filter. + const std::vector<float> kernel = {1.0f, 3.0f, 0.0f, 2.0f, 5.0f, 1.0f, + 3.0f, 2.0f, 0.0f, 4.0f, 1.0f, 3.0f, + 0.0f, 2.0f, 1.0f, 2.0f}; + + AudioBuffer kernel_buffer(kNumMonoChannels, kernel.size()); + kernel_buffer[0] = kernel; + + std::vector<float> output_signal; + output_signal.reserve(kernel.size() * 2); + + FftManager fft_manager(kChunkSize); + PartitionedFftFilter filter(kernel_buffer[0].size(), kChunkSize, + &fft_manager); + filter.SetTimeDomainKernel(kernel_buffer[0]); + + AudioBuffer output_chunk(kNumMonoChannels, kChunkSize); + + // Filter zeros to flush out internal buffers. + AudioBuffer zero_signal(kNumMonoChannels, kChunkSize); + zero_signal.Clear(); + + PartitionedFftFilter::FreqDomainBuffer freq_domain_buffer(kNumMonoChannels, + kChunkSize * 2); + for (size_t j = 0; j < 2 * kernel.size() / kChunkSize; ++j) { + fft_manager.FreqFromTimeDomain(zero_signal[0], &freq_domain_buffer[0]); + filter.Filter(freq_domain_buffer[0]); + filter.GetFilteredSignal(&output_chunk[0]); + output_signal.insert(output_signal.end(), output_chunk[0].begin(), + output_chunk[0].end()); + } + + // Check that all output samples are practically 0.0. + for (size_t sample = 0; sample < output_signal.size(); ++sample) { + EXPECT_EQ(output_signal[sample], 0.0f); + } +} + +// This test uses a simple shifted dirac impulse as kernel and checks the +// resulting signal delay. +TEST(PartitionedFftFilterTest, DiracImpulseTest) { + const size_t kFilterSize = 32; + const size_t kNumBlocks = 4; + const size_t kSignalSize = kFilterSize * kNumBlocks; + + // Generate a saw tooth signal. + AudioBuffer test_signal(kNumMonoChannels, kSignalSize); + for (size_t i = 0; i < kSignalSize; ++i) { + test_signal[0][i] = static_cast<float>(i % 5); + } + + FftManager fft_manager(kFilterSize); + PartitionedFftFilter fft_filter(kFilterSize, kFilterSize, &fft_manager); + + AudioBuffer kernel(kNumMonoChannels, kFilterSize); + // Construct dirac impulse response. This kernel should result in a delay of + // length "|kFilterSize| / 2". + kernel.Clear(); + kernel[0][kFilterSize / 2] = 1.0f; + fft_filter.SetTimeDomainKernel(kernel[0]); + + std::vector<float> filtered_signal; + filtered_signal.reserve(kSignalSize); + + AudioBuffer filtered_block(kNumMonoChannels, kFilterSize); + PartitionedFftFilter::FreqDomainBuffer freq_domain_buffer(kNumMonoChannels, + kFilterSize * 2); + + for (size_t b = 0; b < kNumBlocks; ++b) { + AudioBuffer signal_block(kNumMonoChannels, kFilterSize); + std::copy_n(test_signal[0].begin() + b * kFilterSize, kFilterSize, + signal_block[0].begin()); + + fft_manager.FreqFromTimeDomain(signal_block[0], &freq_domain_buffer[0]); + fft_filter.Filter(freq_domain_buffer[0]); + fft_filter.GetFilteredSignal(&filtered_block[0]); + filtered_signal.insert(filtered_signal.end(), filtered_block[0].begin(), + filtered_block[0].end()); + } + + for (size_t i = 0; i < kFilterSize / 2; ++i) { + // First "filter_size / 2" samples should be zero padded due to the applied + // delay. + EXPECT_NEAR(filtered_signal[i], 0.0f, 1e-5f); + } + for (size_t i = kFilterSize / 2; i < kSignalSize; ++i) { + // Test if signal is delayed by exactly |kFilterSize| / 2 samples. + EXPECT_NEAR(filtered_signal[i], test_signal[0][i - kFilterSize / 2], 1e-5f); + } +} + +} // namespace + +class PartitionedFftFilterFrequencyBufferTest : public ::testing::Test { + protected: + PartitionedFftFilterFrequencyBufferTest() {} + // Virtual methods from ::testing::Test + ~PartitionedFftFilterFrequencyBufferTest() override {} + void SetUp() override {} + void TearDown() override {} + + void SetFreqDomainBuffer(PartitionedFftFilter* filter, + FftManager* fft_manager) { + std::vector<std::vector<float>> values_vectors( + filter->num_partitions_, + std::vector<float>(fft_manager->GetFftSize(), 0.0f)); + for (size_t i = 0; i < filter->num_partitions_; ++i) { + values_vectors[i][0] = static_cast<float>(i + 1); + filter->freq_domain_buffer_[i] = values_vectors[i]; + } + } + + void SetFreqDomainKernel(PartitionedFftFilter* filter) { + // Put a Kronecker delta in the first partition, a kronecker delta times 2 + // in the second, a kronecker delta times 3 in the third, etc... + AudioBuffer kernel(kNumMonoChannels, filter->filter_size_); + for (size_t i = 0; i < filter->num_partitions_; ++i) { + for (size_t sample = 0; sample < filter->chunk_size_; ++sample) { + kernel[0][i * filter->chunk_size_ + sample] = static_cast<float>(i + 1); + } + } + filter->SetTimeDomainKernel(kernel[0]); + } + + void TestFrequencyBufferReset(size_t initial_size, size_t bigger_size, + size_t smaller_size, + PartitionedFftFilter* filter, + FftManager* fft_manager) { + const size_t initial_num_partitions = 2 * initial_size; + const size_t bigger_num_partitions = 2 * bigger_size; + const size_t smaller_num_partitions = 2 * smaller_size; + // Set the |freq_domain_buffer_| inside |filter| such that it has known + // values. + SetFreqDomainBuffer(filter, fft_manager); + AudioBuffer initial_freq_domain_buffer; + initial_freq_domain_buffer = filter->freq_domain_buffer_; + + // Set the current front buffer to something other than zero and less than + // the number of partitions. If |num_partitions_| is 1 it is set to 0. + filter->curr_front_buffer_ = filter->num_partitions_ / 2; + + // Reset the |freq_domain_buffers_| for a new bigger kernel size. + filter->ResetFreqDomainBuffers(bigger_size * fft_manager->GetFftSize()); + AudioBuffer bigger_freq_domain_buffer; + bigger_freq_domain_buffer = filter->freq_domain_buffer_; + // Verify that the input index has been reset. + EXPECT_EQ(filter->curr_front_buffer_, 0U); + + // Set the current front buffer to something other than zero and less than + // the number of partitions. If |num_partitions_| is 1 it is set to 0. + filter->curr_front_buffer_ = filter->num_partitions_ / 2; + + // Reset the |freq_domain_buffers_| for a new smaller kernel size. + filter->ResetFreqDomainBuffers(smaller_size * fft_manager->GetFftSize()); + AudioBuffer smaller_freq_domain_buffer; + smaller_freq_domain_buffer = filter->freq_domain_buffer_; + // Verify that the input index has been reset. + EXPECT_EQ(filter->curr_front_buffer_, 0U); + + // Expect the following to have happened: + // Initially there are 16 partitions with the first element in each channel + // of the |freq_domain_buffers_| in |filter| being 1, 2, ..., 16. + for (size_t i = 0; i < initial_num_partitions; ++i) { + EXPECT_EQ(initial_freq_domain_buffer[i][0], static_cast<float>(i + 1)); + } + + // |curr_front_buffer_| is set to 8. After a reset with a larger kernel we + // expect all of the buffers to have been copied into the new + // |freq_domain_buffers_| starting from |curr_front_buffer_| and wrapping + // round with the remaining channels set to zero. + for (size_t i = 0; i < initial_num_partitions; ++i) { + EXPECT_EQ(bigger_freq_domain_buffer[i][0], + initial_freq_domain_buffer[(initial_size + i) % + initial_num_partitions][0]); + } + for (size_t i = initial_num_partitions; i < bigger_num_partitions; ++i) { + EXPECT_EQ(bigger_freq_domain_buffer[i][0], 0.0f); + } + + // |curr_front_buffer_| is then set to 5. After a reset with a smaller + // kernel ammounting to 10 partitions we expect just the first 10 buffers to + // have been copied into the new |freq_domain_buffers_| starting from + // |curr_front_buffer_| and wrapping round. + for (size_t i = 0; i < smaller_num_partitions; ++i) { + EXPECT_EQ(smaller_freq_domain_buffer[i][0], + bigger_freq_domain_buffer[(bigger_size + i) % + bigger_num_partitions][0]); + } + } + + void TestLongerShorterFrequencyDomainKernel(size_t buffer_size) { + AudioBuffer small_kernel_buffer(kNumMonoChannels, buffer_size); + AudioBuffer big_kernel_buffer(kNumMonoChannels, 2 * buffer_size); + + // Place to collect all of the output. + std::vector<float> total_output_signal; + + // First set the kernels to linear ramps of differing lengths. + for (size_t i = 0; i < 2 * buffer_size; ++i) { + if (i < buffer_size) { + small_kernel_buffer[0][i] = static_cast<float>(i) / 4.0f; + } + big_kernel_buffer[0][i] = static_cast<float>(i) / 4.0f; + } + + FftManager fft_manager(buffer_size); + PartitionedFftFilter filter(buffer_size, buffer_size, 2 * buffer_size, + &fft_manager); + + // Generate the small and large frequency domain buffers. + filter.SetTimeDomainKernel(small_kernel_buffer[0]); + const size_t small_num_partitions = filter.num_partitions_; + PartitionedFftFilter::FreqDomainBuffer small_freq_domain_kernel = + PartitionedFftFilter::FreqDomainBuffer(small_num_partitions, + fft_manager.GetFftSize()); + for (size_t i = 0; i < small_num_partitions; ++i) { + small_freq_domain_kernel[i] = filter.kernel_freq_domain_buffer_[i]; + } + + filter.SetTimeDomainKernel(big_kernel_buffer[0]); + const size_t big_num_partitions = filter.num_partitions_; + PartitionedFftFilter::FreqDomainBuffer big_freq_domain_kernel = + PartitionedFftFilter::FreqDomainBuffer(big_num_partitions, + fft_manager.GetFftSize()); + for (size_t i = 0; i < big_num_partitions; ++i) { + big_freq_domain_kernel[i] = filter.kernel_freq_domain_buffer_[i]; + } + + filter.SetFreqDomainKernel(small_freq_domain_kernel); + ProcessFilterWithImpulseSignal(&filter, &fft_manager, 2, + &total_output_signal); + + filter.SetFreqDomainKernel(big_freq_domain_kernel); + ProcessFilterWithImpulseSignal(&filter, &fft_manager, 3, + &total_output_signal); + + filter.SetFreqDomainKernel(small_freq_domain_kernel); + ProcessFilterWithImpulseSignal(&filter, &fft_manager, 2, + &total_output_signal); + + // Test to see if output from both kernels is present |big_size| zeros in + // between. + for (size_t i = 0; i < buffer_size; ++i) { + EXPECT_NEAR(static_cast<float>(i) / 4.0f, total_output_signal[i], + kFftEpsilon); + EXPECT_NEAR(0.0f, total_output_signal[i + buffer_size], kFftEpsilon); + EXPECT_NEAR(0.0f, total_output_signal[i + 2 * buffer_size], kFftEpsilon); + EXPECT_NEAR(static_cast<float>(i) / 4.0f, + total_output_signal[i + 3 * buffer_size], kFftEpsilon); + EXPECT_NEAR(static_cast<float>(i + buffer_size) / 4.0f, + total_output_signal[i + 4 * buffer_size], kFftEpsilon); + EXPECT_NEAR(0.0f, total_output_signal[i + 5 * buffer_size], kFftEpsilon); + EXPECT_NEAR(0.0f, total_output_signal[i + 6 * buffer_size], kFftEpsilon); + EXPECT_NEAR(static_cast<float>(i) / 4.0f, + total_output_signal[i + 7 * buffer_size], kFftEpsilon); + EXPECT_NEAR(0.0f, total_output_signal[i + 8 * buffer_size], kFftEpsilon); + EXPECT_NEAR(0.0f, total_output_signal[i + 9 * buffer_size], kFftEpsilon); + } + } + + void TestPartitionReplacement(PartitionedFftFilter* filter, + FftManager* fft_manager) { + // Fill the first partition with a kronecker delta in the first partition, a + // kronecker delta times 2 in the second, a kronecker delta times 3 in the + // third, etc... + SetFreqDomainKernel(filter); + // Get a copy of the kernel. + AudioBuffer initial_kernel; + initial_kernel = filter->kernel_freq_domain_buffer_; + + // Create a freq domain kernel chunk with the same values as the final + // partition. + AudioBuffer kernel_chunk(kNumMonoChannels, filter->chunk_size_); + kernel_chunk.Clear(); + AudioBuffer::Channel& kernel_chunk_channel = kernel_chunk[0]; + for (size_t sample = 0; sample < filter->chunk_size_; ++sample) { + kernel_chunk_channel[sample] = + static_cast<float>(filter->num_partitions_); + } + // The partition we will replace. + const size_t kReplacePartitionIndex = 2; + // Replace a partition with a new time domain kernel chunk (all zeros). + filter->ReplacePartition(kReplacePartitionIndex, kernel_chunk_channel); + + // Get a copy of the kernel after replacing the 3rd freq domain partition. + AudioBuffer replaced_kernel; + replaced_kernel = filter->kernel_freq_domain_buffer_; + + for (size_t i = 0; i < filter->num_partitions_; ++i) { + for (size_t sample = 0; sample < fft_manager->GetFftSize(); ++sample) { + // Expect that all of the other partitions are unchanged. + if (i == kReplacePartitionIndex) { + // Check if the chosen partition has been altered. + EXPECT_EQ(initial_kernel[filter->num_partitions_ - 1][sample], + replaced_kernel[i][sample]); + } else { + // Check if all of the other partitions are unchanged. + EXPECT_EQ(initial_kernel[i][sample], replaced_kernel[i][sample]); + } + } + } + } + + void TestFilterLengthSetter(PartitionedFftFilter* filter, + FftManager* fft_manager) { + // Set the |freq_domain_buffer_| inside |filter| such that it has known + // values (In the time domain, all value 1, then 2 etc.. per partition). + SetFreqDomainKernel(filter); + + // Get a copy of the kernel and number of partitions before we set the + // filter length. + AudioBuffer initial_kernel; + initial_kernel = filter->kernel_freq_domain_buffer_; + const size_t initial_num_partitions = filter->num_partitions_; + + filter->SetFilterLength(filter->filter_size_ / 2); + + // Get a copy after we half the filter length. + AudioBuffer half_kernel; + half_kernel = filter->kernel_freq_domain_buffer_; + const size_t half_num_partitions = filter->num_partitions_; + EXPECT_EQ(initial_num_partitions, 2 * half_num_partitions); + + filter->SetFilterLength(filter->filter_size_ * 2); + + // Get a copy after we re-double the filter length. + AudioBuffer redouble_kernel; + redouble_kernel = filter->kernel_freq_domain_buffer_; + EXPECT_EQ(initial_num_partitions, filter->num_partitions_); + + for (size_t i = 0; i < half_num_partitions; ++i) { + for (size_t sample = 0; sample < fft_manager->GetFftSize(); ++sample) { + // Check that the first two partitions in all 3 cases are the same. + EXPECT_EQ(initial_kernel[i][sample], half_kernel[i][sample]); + EXPECT_EQ(initial_kernel[i][sample], redouble_kernel[i][sample]); + // Check that the final two partitions, after resizing the filters + // frequency domain buffers, contain only zeros. + EXPECT_EQ(redouble_kernel[i + half_num_partitions][sample], 0.0f); + } + } + } +}; + +// Tests whether the reordering of the channels of |freq_domain_buffers_| is as +// expected after calling |ResetFreqDomainBuffers| with both longer and shorter +// filters. +TEST_F(PartitionedFftFilterFrequencyBufferTest, FrequencyBufferResetTest) { + const size_t kBufferSize = 32; + const size_t kInitialFilterSizeFactor = 8; + const size_t kBiggerFilterSizeFactor = 10; + const size_t kSmallerFilterSizeFactor = 5; + // Initially there will be 16 partitions, then 20, then 10. + FftManager fft_manager(kBufferSize); + PartitionedFftFilter filter( + kInitialFilterSizeFactor * kBufferSize * 2, kBufferSize, + kBiggerFilterSizeFactor * kBufferSize * 2, &fft_manager); + TestFrequencyBufferReset(kInitialFilterSizeFactor, kBiggerFilterSizeFactor, + kSmallerFilterSizeFactor, &filter, &fft_manager); +} + +// Tests that we can switch to a frequency domain filter kernel with a greater +// number of partitions than the original kernel provided at instantiation. +// Ensures that we can switch to a frequency domain filter kernel with less +// partitions than the original kernel. +TEST_F(PartitionedFftFilterFrequencyBufferTest, + LongerShorterFrequencyDomainKernelTest) { + TestLongerShorterFrequencyDomainKernel(kLength); +} + +// Tests whether replacing an individual partition works correctly. +TEST_F(PartitionedFftFilterFrequencyBufferTest, ReplacePartitionTest) { + const size_t kBufferSize = 32; + const size_t kFilterSizeFactor = 5; + // A filter with 5 partitions. + FftManager fft_manager(kBufferSize); + PartitionedFftFilter filter(kFilterSizeFactor * kBufferSize * 2, kBufferSize, + &fft_manager); + TestPartitionReplacement(&filter, &fft_manager); +} + +// Tests whether setting the length of the filter kernel, and thus the number +// of partitions gives the expected results. +TEST_F(PartitionedFftFilterFrequencyBufferTest, SetFilterLengthTest) { + const size_t kBufferSize = 32; + const size_t kFilterSizeFactor = 5; + // A filter with 5 partitions. + FftManager fft_manager(kBufferSize); + PartitionedFftFilter filter(kFilterSizeFactor * kBufferSize * 2, kBufferSize, + kFilterSizeFactor * kBufferSize * 4, + &fft_manager); + TestFilterLengthSetter(&filter, &fft_manager); +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/reflection.h b/src/3rdparty/resonance-audio/resonance_audio/dsp/reflection.h new file mode 100644 index 000000000..4b134f6f2 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/reflection.h @@ -0,0 +1,34 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_DSP_REFLECTION_H_ +#define RESONANCE_AUDIO_DSP_REFLECTION_H_ + +namespace vraudio { + +// Describes a room reflection that contains information on its time of arrival +// and magnitude. +struct Reflection { + // Time of arrival of the reflection in seconds. + float delay_time_seconds = 0.0f; + + // Magnitude of the reflection. + float magnitude = 0.0f; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_DSP_REFLECTION_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/reflections_processor.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/reflections_processor.cc new file mode 100644 index 000000000..9a9174cdc --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/reflections_processor.cc @@ -0,0 +1,177 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/reflections_processor.h" + +#include <algorithm> + +#include "base/constants_and_types.h" +#include "base/logging.h" +#include "dsp/filter_coefficient_generators.h" +#include "dsp/gain.h" +#include "dsp/shoe_box_room.h" + +namespace vraudio { + +namespace { + +// Maximum allowed delay time for a reflection. Above 2s, the effective output +// level of a reflection will fall below -60dB and thus perceived dynamic +// changes should be negligible. +const size_t kMaxDelayTimeSeconds = 2; + +// Returns the maximum delay time in the given set of reflections. +float FindMaxReflectionDelayTime(const std::vector<Reflection>& reflections) { + float max_delay_time = 0.0f; + for (const auto& reflection : reflections) { + max_delay_time = std::max(max_delay_time, reflection.delay_time_seconds); + } + return max_delay_time; +} + +} // namespace + +ReflectionsProcessor::ReflectionsProcessor(int sample_rate, + size_t frames_per_buffer) + : sample_rate_(sample_rate), + frames_per_buffer_(frames_per_buffer), + max_delay_samples_(kMaxDelayTimeSeconds * sample_rate_), + low_pass_filter_(0.0f), + temp_mono_buffer_(kNumMonoChannels, frames_per_buffer_), + current_reflection_buffer_(kNumFirstOrderAmbisonicChannels, + frames_per_buffer), + target_reflection_buffer_(kNumFirstOrderAmbisonicChannels, + frames_per_buffer), + target_reflections_(kNumRoomSurfaces), + crossfade_(false), + crossfader_(frames_per_buffer_), + num_frames_to_process_on_empty_input_(0), + delays_(kNumRoomSurfaces), + delay_filter_(max_delay_samples_, frames_per_buffer), + delay_buffer_(kNumRoomSurfaces, frames_per_buffer), + gains_(kNumRoomSurfaces), + gain_processors_(kNumRoomSurfaces) { + DCHECK_GT(sample_rate_, 0); + DCHECK_GT(frames_per_buffer_, 0U); +} + +void ReflectionsProcessor::Update( + const ReflectionProperties& reflection_properties, + const WorldPosition& listener_position) { + + // Initialize the low-pass filter. + const float low_pass_coefficient = ComputeLowPassMonoPoleCoefficient( + reflection_properties.cutoff_frequency, sample_rate_); + low_pass_filter_.SetCoefficient(low_pass_coefficient); + // Update the target reflections. + WorldPosition relative_listener_position; + GetRelativeDirection( + WorldPosition(reflection_properties.room_position), + WorldRotation(reflection_properties.room_rotation).conjugate(), + listener_position, &relative_listener_position); + ComputeReflections(relative_listener_position, + WorldPosition(reflection_properties.room_dimensions), + reflection_properties.coefficients, &target_reflections_); + // Additional |frames_per_buffer_| to process needed to compensate the + // crossfade between the current and target reflections. + num_frames_to_process_on_empty_input_ = + frames_per_buffer_ + + static_cast<size_t>(FindMaxReflectionDelayTime(target_reflections_) * + static_cast<float>(sample_rate_)); + // Reflections have been updated so crossfade is required. + crossfade_ = true; +} + +void ReflectionsProcessor::Process(const AudioBuffer& input, + AudioBuffer* output) { + DCHECK_EQ(input.num_channels(), kNumMonoChannels); + DCHECK_EQ(input.num_frames(), frames_per_buffer_); + DCHECK(output); + DCHECK_GE(output->num_channels(), kNumFirstOrderAmbisonicChannels); + DCHECK_EQ(output->num_frames(), frames_per_buffer_); + // Prefilter mono input. + const AudioBuffer::Channel& input_channel = input[0]; + AudioBuffer::Channel* temp_channel = &temp_mono_buffer_[0]; + const bool filter_success = + low_pass_filter_.Filter(input_channel, temp_channel); + const AudioBuffer::Channel& low_pass_channel = + filter_success ? *temp_channel : input_channel; + delay_filter_.InsertData(low_pass_channel); + // Process reflections. + if (crossfade_) { + ApplyReflections(¤t_reflection_buffer_); + UpdateGainsAndDelays(); + ApplyReflections(&target_reflection_buffer_); + crossfader_.ApplyLinearCrossfade(target_reflection_buffer_, + current_reflection_buffer_, output); + crossfade_ = false; + } else { + ApplyReflections(output); + } +} + +void ReflectionsProcessor::UpdateGainsAndDelays() { + for (size_t i = 0; i < kNumRoomSurfaces; ++i) { + delays_[i] = + std::min(max_delay_samples_, + static_cast<size_t>(target_reflections_[i].delay_time_seconds * + static_cast<float>(sample_rate_))); + gains_[i] = target_reflections_[i].magnitude; + } +} + +void ReflectionsProcessor::ApplyReflections(AudioBuffer* output) { + DCHECK(output); + DCHECK_GE(output->num_channels(), kNumFirstOrderAmbisonicChannels); + (*output).Clear(); + for (size_t i = 0; i < kNumRoomSurfaces; ++i) { + auto* delay_channel = &delay_buffer_[i]; + delay_filter_.GetDelayedData(delays_[i], delay_channel); + const bool zero_gain = IsGainNearZero(gains_[i]) && + IsGainNearZero(gain_processors_[i].GetGain()); + if (!zero_gain) { + gain_processors_[i].ApplyGain(gains_[i], *delay_channel, delay_channel, + false /* accumulate_output */); + // Applies fast Ambisonic reflections encoding. + (*output)[0] += *delay_channel; + switch (i) { + case 0: /* left wall reflection */ + (*output)[1] += *delay_channel; + break; + case 1: /* right wall reflection */ + (*output)[1] -= *delay_channel; + break; + case 2: /* floor reflection */ + (*output)[2] -= *delay_channel; + break; + case 3: /* ceiling reflection */ + (*output)[2] += *delay_channel; + break; + case 4: /* front wall reflection */ + (*output)[3] += *delay_channel; + break; + case 5: /* back wall reflection */ + (*output)[3] -= *delay_channel; + break; + } + } else { + // Make sure the gain processor is initialized. + gain_processors_[i].Reset(0.0f); + } + } +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/reflections_processor.h b/src/3rdparty/resonance-audio/resonance_audio/dsp/reflections_processor.h new file mode 100644 index 000000000..1b9765b0f --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/reflections_processor.h @@ -0,0 +1,128 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_DSP_REFLECTIONS_PROCESSOR_H_ +#define RESONANCE_AUDIO_DSP_REFLECTIONS_PROCESSOR_H_ + +#include <utility> +#include <vector> + +#include "api/resonance_audio_api.h" +#include "base/audio_buffer.h" +#include "base/misc_math.h" +#include "dsp/delay_filter.h" +#include "dsp/gain_processor.h" +#include "dsp/mono_pole_filter.h" +#include "dsp/reflection.h" +#include "utils/buffer_crossfader.h" + +namespace vraudio { + +// Class that accepts single mono buffer as input and outputs a first order +// ambisonic buffer of the mix of all the rooms early reflections computed for +// the buffer. +// +// The input consists of a mono mix of all the sound objects in the system. The +// reflections are assumed to be aligned with the aabb axes and thus the first +// order ambisonic axes. +class ReflectionsProcessor { + public: + // Constructs a |ReflectionsProcessor|. + // + // @param sample_rate System sampling rate. + // @param frames_per_buffer System frames per buffer. + ReflectionsProcessor(int sample_rate, size_t frames_per_buffer); + + // Updates reflections according to the new |ReflectionProperties|. + // + // @param reflection_properties New reflection properties. + // @param listener_position New listener position. + void Update(const ReflectionProperties& reflection_properties, + const WorldPosition& listener_position); + + // Processes a mono |AudioBuffer| into an ambisonic |AudioBuffer|. + // + // @param input Mono input buffer. + // @param output Ambisonic output buffer. + void Process(const AudioBuffer& input, AudioBuffer* output); + + // Returns the number of frames required to keep processing on empty input + // signal. This value can be used to avoid any potential artifacts on the + // final output signal when the input signal stream is empty. + size_t num_frames_to_process_on_empty_input() const { + return num_frames_to_process_on_empty_input_; + } + + private: + // Updates |gains_| and |delays_| vectors of the |ReflectionsProcessor|. + void UpdateGainsAndDelays(); + + // Applies |target_reflections_| and fast-encodes them into first order + // ambisonics. + // + // @param output Ambisonic output buffer. + void ApplyReflections(AudioBuffer* output); + + // System sampling rate. + const int sample_rate_; + + // System number of frames per buffer. + const size_t frames_per_buffer_; + + // Maximum allowed delay time for a reflection (in samples). + const size_t max_delay_samples_; + + // Low pass filter to be applied to input signal. + MonoPoleFilter low_pass_filter_; + + // Audio buffer to store mono low pass filtered buffers during processing. + AudioBuffer temp_mono_buffer_; + + // Audio buffers to store FOA reflections buffers during crossfading. + AudioBuffer current_reflection_buffer_; + AudioBuffer target_reflection_buffer_; + + // Target reflections filled with new data when |Update| is called. + std::vector<Reflection> target_reflections_; + + // Indicates whether relfections have been updated and a crossfade is needed. + bool crossfade_; + + // Buffer crossfader to apply linear crossfade during reflections update. + BufferCrossfader crossfader_; + + // Number of frames needed to keep processing on empty input signal. + size_t num_frames_to_process_on_empty_input_; + + // Number of samples of delay to be applied for each reflection. + std::vector<size_t> delays_; + + // Delay filter to delay the incoming buffer. + DelayFilter delay_filter_; + + // Delay buffer used to store delayed reflections before scaling and encoding. + AudioBuffer delay_buffer_; + + // Gains to be applied for each reflection. + std::vector<float> gains_; + + // |GainProcessor|s to apply |gains_|. + std::vector<GainProcessor> gain_processors_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_DSP_REFLECTIONS_PROCESSOR_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/reflections_processor_test.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/reflections_processor_test.cc new file mode 100644 index 000000000..ab1688e51 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/reflections_processor_test.cc @@ -0,0 +1,142 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/reflections_processor.h" + +#include <algorithm> +#include <utility> +#include <vector> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "api/resonance_audio_api.h" +#include "base/constants_and_types.h" +#include "utils/test_util.h" + +namespace vraudio { + +namespace { + +const size_t kFramesPerBuffer = 512; +const int kSampleRate = 48000; +const float kRoomDimensions[3] = {2.0f, 2.0f, 2.0f}; +const size_t kExpectedDelaySamples = 279; + +} // namespace + +class ReflectionsProcessorTest : public ::testing::Test { + protected: + void SetUp() override { + const float kReflectionCoefficients[kNumRoomSurfaces] = {0.5f, 0.5f, 0.5f, + 0.5f, 0.5f, 0.5f}; + const WorldPosition kListenerPosition(0.0f, 0.0f, 0.0f); + std::copy(std::begin(kReflectionCoefficients), + std::end(kReflectionCoefficients), + std::begin(reflection_properties_.coefficients)); + std::copy(std::begin(kRoomDimensions), std::end(kRoomDimensions), + std::begin(reflection_properties_.room_dimensions)); + listener_position_ = kListenerPosition; + } + + ReflectionProperties reflection_properties_; + WorldPosition listener_position_; +}; // namespace vraudio + +// Tests that the processed output is delayed, filtered, and scaled as expected. +TEST_F(ReflectionsProcessorTest, ProcessTest) { + ReflectionsProcessor reflections_processor(kSampleRate, kFramesPerBuffer); + reflections_processor.Update(reflection_properties_, listener_position_); + + AudioBuffer input(kNumMonoChannels, kFramesPerBuffer); + AudioBuffer output(kNumFirstOrderAmbisonicChannels, kFramesPerBuffer); + // Process until we have transitioned to the specified Gain. + for (size_t i = 0; i < kUnitRampLength; i += kFramesPerBuffer) { + input.Clear(); + output.Clear(); + GenerateDiracImpulseFilter(0, &input[0]); + reflections_processor.Process(input, &output); + } + + // The reflections calculated should be as follows: + // delay = 280 samples, magnitude = 0.25, direction = {azim 90, elev 0}. + // delay = 280 samples, magnitude = 0.25, direction = {azim -90, elev 0}. + // delay = 280 samples, magnitude = 0.25, direction = {azim 0, elev -90}. + // delay = 280 samples, magnitude = 0.25, direction = {azim 0, elev 90}. + // delay = 280 samples, magnitude = 0.25, direction = {azim 0, elev 0}. + // delay = 280 samples, magnitude = 0.25, direction = {azim 180, elev 0}. + const float kExpectedMagnitude = 0.25f; + // We expect the following ambisonic encoding coefficients: + // {azim 90, elev 0} = {1 1 0 0}. + // {azim -90, elev 0} = {1 -1 0 0}. + // {azim 0, elev -90} = {1 0 -1 0}. + // {azim 0, elev 90} = {1 0 1 0}. + // {azim 0, elev 0} = {1 0 0 1}. + // {azim 180, elev 0} = {1 0 0 -1}. + const std::vector<std::vector<float>> kEncodingCoefficients = { + {1.0f, 1.0f, 0.0f, 0.0f}, {1.0f, -1.0f, 0.0f, 0.0f}, + {1.0f, 0.0f, -1.0f, 0.0f}, {1.0f, 0.0f, 1.0f, 0.0f}, + {1.0f, 0.0f, 0.0f, 1.0f}, {1.0f, 0.0f, 0.0f, -1.0f}}; + + for (size_t j = kExpectedDelaySamples; j < kFramesPerBuffer; ++j) { + for (size_t k = 0; k < kNumFirstOrderAmbisonicChannels; ++k) { + float coefficient_sum = 0.0f; + for (size_t i = 0; i < kEncodingCoefficients.size(); ++i) { + coefficient_sum += kEncodingCoefficients[i][k]; + } + const float expected_sample = input[0][j - kExpectedDelaySamples] * + kExpectedMagnitude * coefficient_sum; + EXPECT_NEAR(expected_sample, output[k][j], kEpsilonFloat); + } + } +} + +// Tests that when transitioning from a reflection with zero magnitude to one +// with non-zero magnitude, there will be a steady incremental increase in +// the "fade in". +TEST_F(ReflectionsProcessorTest, CrossFadeTest) { + ReflectionsProcessor reflections_processor(kSampleRate, kFramesPerBuffer); + + AudioBuffer input(kNumMonoChannels, kFramesPerBuffer); + AudioBuffer output(kNumFirstOrderAmbisonicChannels, kFramesPerBuffer); + + input.Clear(); + output.Clear(); + for (size_t i = 0; i < kFramesPerBuffer; ++i) { + input[0][i] = 1.0f; + } + + reflections_processor.Process(input, &output); + reflections_processor.Update(reflection_properties_, listener_position_); + reflections_processor.Process(input, &output); + + // All the reflections are expected to arrive at the listener at the same + // time. That is why their directional contributions are expected to cancel + // out. Only in first channel we will see a steady increase. + for (size_t channel = 0; channel < kNumFirstOrderAmbisonicChannels; + ++channel) { + if (channel < kNumMonoChannels) { + for (size_t frame = kExpectedDelaySamples; frame < kFramesPerBuffer; + ++frame) { + EXPECT_TRUE(output[channel][frame] > output[channel][frame - 1]); + } + } else { + for (size_t frame = 0; frame < kFramesPerBuffer; ++frame) { + EXPECT_NEAR(output[channel][frame], 0.0f, kEpsilonFloat); + } + } + } +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/resampler.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/resampler.cc new file mode 100644 index 000000000..e135afcdf --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/resampler.cc @@ -0,0 +1,335 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Prevent Visual Studio from complaining about std::copy_n. +#if defined(_WIN32) +#define _SCL_SECURE_NO_WARNINGS +#endif + +#include "dsp/resampler.h" + +#include <functional> +#include <numeric> + +#include "base/constants_and_types.h" +#include "base/logging.h" +#include "base/misc_math.h" +#include "base/simd_utils.h" + +#include "dsp/utils.h" + +namespace vraudio { + +namespace { + +// (kTransitionBandwidthRatio / 2) * (sample_rate / cutoff_frequency) +// = filter_length. +// The value below was chosen empirically as a tradeoff between execution time +// and filter rolloff wrt. cutoff frequency. +const size_t kTransitionBandwidthRatio = 13; + +// Maximum number of channels internally based upon the maximum supported +// ambisonic order. +const size_t kMaxNumChannels = + (kMaxSupportedAmbisonicOrder + 1) * (kMaxSupportedAmbisonicOrder + 1); + +} // namespace + +Resampler::Resampler() + : up_rate_(0), + down_rate_(0), + time_modulo_up_rate_(0), + last_processed_sample_(0), + num_channels_(0), + coeffs_per_phase_(0), + transposed_filter_coeffs_(kNumMonoChannels, kMaxSupportedNumFrames), + temporary_filter_coeffs_(kNumMonoChannels, kMaxSupportedNumFrames), + state_(kMaxNumChannels, kMaxSupportedNumFrames) { + state_.Clear(); +} + +void Resampler::Process(const AudioBuffer& input, AudioBuffer* output) { + + // See "Digital Signal Processing, 4th Edition, Prolakis and Manolakis, + // Pearson, Chapter 11 (specificaly Figures 11.5.10 and 11.5.13). + DCHECK_EQ(input.num_channels(), num_channels_); + const size_t input_length = input.num_frames(); + DCHECK_GE(output->num_frames(), GetNextOutputLength(input_length)); + DCHECK_LE(output->num_frames(), GetMaxOutputLength(input_length)); + DCHECK_EQ(output->num_channels(), num_channels_); + output->Clear(); + + if (up_rate_ == down_rate_) { + *output = input; + return; + } + + // |input_sample| is the last processed sample. + size_t input_sample = last_processed_sample_; + // |output_sample| is the output. + size_t output_sample = 0; + + const auto& filter_coefficients = transposed_filter_coeffs_[0]; + + while (input_sample < input_length) { + size_t filter_index = time_modulo_up_rate_ * coeffs_per_phase_; + size_t offset_input_index = input_sample - coeffs_per_phase_ + 1; + const int offset = -static_cast<int>(offset_input_index); + + if (offset > 0) { + // We will need to draw data from the |state_| buffer. + const int state_num_frames = static_cast<int>(coeffs_per_phase_ - 1); + int state_index = state_num_frames - offset; + while (state_index < state_num_frames) { + for (size_t channel = 0; channel < num_channels_; ++channel) { + (*output)[channel][output_sample] += + state_[channel][state_index] * filter_coefficients[filter_index]; + } + state_index++; + filter_index++; + } + // Move along by |offset| samples up as far as |input|. + offset_input_index += offset; + } + + // We now move back to where |input_sample| "points". + while (offset_input_index <= input_sample) { + for (size_t channel = 0; channel < num_channels_; ++channel) { + (*output)[channel][output_sample] += + input[channel][offset_input_index] * + filter_coefficients[filter_index]; + } + offset_input_index++; + filter_index++; + } + output_sample++; + + time_modulo_up_rate_ += down_rate_; + // Advance the input pointer. + input_sample += time_modulo_up_rate_ / up_rate_; + // Decide which phase of the polyphase filter to use next. + time_modulo_up_rate_ %= up_rate_; + } + DCHECK_GE(input_sample, input_length); + last_processed_sample_ = input_sample - input_length; + + // Take care of the |state_| buffer. + const int samples_left_in_input = + static_cast<int>(coeffs_per_phase_) - 1 - static_cast<int>(input_length); + if (samples_left_in_input > 0) { + for (size_t channel = 0; channel < num_channels_; ++channel) { + // Copy end of the |state_| buffer to the beginning. + auto& state_channel = state_[channel]; + DCHECK_GE(static_cast<int>(state_channel.size()), samples_left_in_input); + std::copy_n(state_channel.end() - samples_left_in_input, + samples_left_in_input, state_channel.begin()); + // Then copy input to the end of the buffer. + std::copy_n(input[channel].begin(), input_length, + state_channel.end() - input_length); + } + } else { + for (size_t channel = 0; channel < num_channels_; ++channel) { + // Copy the last of the |input| samples into the |state_| buffer. + DCHECK_GT(coeffs_per_phase_, 0U); + DCHECK_GT(input[channel].size(), coeffs_per_phase_ - 1); + std::copy_n(input[channel].end() - (coeffs_per_phase_ - 1), + coeffs_per_phase_ - 1, state_[channel].begin()); + } + } +} + +size_t Resampler::GetMaxOutputLength(size_t input_length) const { + if (up_rate_ == down_rate_) { + return input_length; + } + DCHECK_GT(down_rate_, 0U); + // The + 1 takes care of the case where: + // (time_modulo_up_rate_ + up_rate_ * last_processed_sample_) < + // ((input_length * up_rate_) % down_rate_) + // The output length will be equal to the return value or the return value -1. + return (input_length * up_rate_) / down_rate_ + 1; +} + +size_t Resampler::GetNextOutputLength(size_t input_length) const { + if (up_rate_ == down_rate_) { + return input_length; + } + const size_t max_length = GetMaxOutputLength(input_length); + if ((time_modulo_up_rate_ + up_rate_ * last_processed_sample_) >= + ((input_length * up_rate_) % down_rate_)) { + return max_length - 1; + } + return max_length; +} + +void Resampler::SetRateAndNumChannels(int source_frequency, + int destination_frequency, + size_t num_channels) { + + // Convert sampling rates to be relatively prime. + DCHECK_GT(source_frequency, 0); + DCHECK_GT(destination_frequency, 0); + DCHECK_GT(num_channels, 0U); + const int greatest_common_divisor = + FindGcd(destination_frequency, source_frequency); + const size_t destination = + static_cast<size_t>(destination_frequency / greatest_common_divisor); + const size_t source = + static_cast<size_t>(source_frequency / greatest_common_divisor); + + // Obtain the size of the |state_| before |coeffs_per_phase_| is updated in + // |GenerateInterpolatingFilter()|. + const size_t old_state_size = + coeffs_per_phase_ > 0 ? coeffs_per_phase_ - 1 : 0; + if ((destination != up_rate_) || (source != down_rate_)) { + up_rate_ = destination; + down_rate_ = source; + if (up_rate_ == down_rate_) { + return; + } + // Create transposed multirate filters from sincs. + GenerateInterpolatingFilter(source_frequency); + // Reset the time variable as it may be longer than the new filter length if + // we switched from upsampling to downsampling via a call to SetRate(). + time_modulo_up_rate_ = 0; + } + + // Update the |state_| buffer. + if (num_channels_ != num_channels) { + num_channels_ = num_channels; + InitializeStateBuffer(old_state_size); + } +} + +bool Resampler::AreSampleRatesSupported(int source, int destination) { + DCHECK_GT(source, 0); + DCHECK_GT(destination, 0); + // Determines whether sample rates are supported based upon whether our + // maximul filter lenhgth is big enough to hold the corresponding + // interpolation filter. + const int max_rate = + std::max(source, destination) / FindGcd(source, destination); + size_t filter_length = max_rate * kTransitionBandwidthRatio; + filter_length += filter_length % 2; + return filter_length <= kMaxSupportedNumFrames; +} + +void Resampler::ResetState() { + + time_modulo_up_rate_ = 0; + last_processed_sample_ = 0; + state_.Clear(); +} + +void Resampler::InitializeStateBuffer(size_t old_state_num_frames) { + // Update the |state_| buffer if it is null or if the number of coefficients + // per phase in the polyphase filter has changed. + if (up_rate_ == down_rate_ || num_channels_ == 0) { + return; + } + // If the |state_| buffer is to be kept. For example in the case of a change + // in either source or destination sampling rate, maintaining the old |state_| + // buffers contents allows a glitch free transition. + const size_t new_state_num_frames = + coeffs_per_phase_ > 0 ? coeffs_per_phase_ - 1 : 0; + if (old_state_num_frames != new_state_num_frames) { + const size_t min_size = + std::min(new_state_num_frames, old_state_num_frames); + const size_t max_size = + std::max(new_state_num_frames, old_state_num_frames); + for (size_t channel = 0; channel < num_channels_; ++channel) { + auto& state_channel = state_[channel]; + DCHECK_LT(state_channel.begin() + max_size, state_channel.end()); + std::fill(state_channel.begin() + min_size, + state_channel.begin() + max_size, 0.0f); + } + } +} + +void Resampler::GenerateInterpolatingFilter(int sample_rate) { + // See "Digital Signal Processing, 4th Edition, Prolakis and Manolakis, + // Pearson, Chapter 11 (specificaly Figures 11.5.10 and 11.5.13). + const size_t max_rate = std::max(up_rate_, down_rate_); + const float cutoff_frequency = + static_cast<float>(sample_rate) / static_cast<float>(2 * max_rate); + size_t filter_length = max_rate * kTransitionBandwidthRatio; + filter_length += filter_length % 2; + auto* filter_channel = &temporary_filter_coeffs_[0]; + filter_channel->Clear(); + GenerateSincFilter(cutoff_frequency, static_cast<float>(sample_rate), + filter_length, filter_channel); + + // Pad out the filter length so that it can be arranged in polyphase fashion. + const size_t transposed_length = + filter_length + max_rate - (filter_length % max_rate); + coeffs_per_phase_ = transposed_length / max_rate; + ArrangeFilterAsPolyphase(filter_length, *filter_channel); +} + +void Resampler::ArrangeFilterAsPolyphase(size_t filter_length, + const AudioBuffer::Channel& filter) { + // Coefficients are transposed and flipped. + // Suppose |up_rate_| is 3, and the input number of coefficients is 10, + // h[0], ..., h[9]. + // Then the |transposed_filter_coeffs_| buffer will look like this: + // h[9], h[6], h[3], h[0], flipped phase 0 coefs. + // 0, h[7], h[4], h[1], flipped phase 1 coefs (zero-padded). + // 0, h[8], h[5], h[2], flipped phase 2 coefs (zero-padded). + transposed_filter_coeffs_.Clear(); + auto& transposed_coefficients_channel = transposed_filter_coeffs_[0]; + for (size_t i = 0; i < up_rate_; ++i) { + for (size_t j = 0; j < coeffs_per_phase_; ++j) { + if (j * up_rate_ + i < filter_length) { + const size_t coeff_index = + (coeffs_per_phase_ - 1 - j) + i * coeffs_per_phase_; + transposed_coefficients_channel[coeff_index] = filter[j * up_rate_ + i]; + } + } + } +} + +void Resampler::GenerateSincFilter(float cutoff_frequency, float sample_rate, + size_t filter_length, + AudioBuffer::Channel* buffer) { + + DCHECK_GT(sample_rate, 0.0f); + const float angular_cutoff_frequency = + kTwoPi * cutoff_frequency / sample_rate; + + const size_t half_filter_length = filter_length / 2; + GenerateHannWindow(true /* Full Window */, filter_length, buffer); + auto* buffer_channel = &buffer[0]; + + for (size_t i = 0; i < filter_length; ++i) { + if (i == half_filter_length) { + (*buffer_channel)[half_filter_length] *= angular_cutoff_frequency; + } else { + const float denominator = + static_cast<float>(i) - (static_cast<float>(filter_length) / 2.0f); + DCHECK_GT(std::abs(denominator), kEpsilonFloat); + (*buffer_channel)[i] *= + std::sin(angular_cutoff_frequency * denominator) / denominator; + } + } + // Normalize. + const float normalizing_factor = + static_cast<float>(up_rate_) / + std::accumulate(buffer_channel->begin(), buffer_channel->end(), 0.0f); + ScalarMultiply(filter_length, normalizing_factor, buffer_channel->begin(), + buffer_channel->begin()); +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/resampler.h b/src/3rdparty/resonance-audio/resonance_audio/dsp/resampler.h new file mode 100644 index 000000000..c2c079a80 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/resampler.h @@ -0,0 +1,135 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_DSP_RESAMPLER_H_ +#define RESONANCE_AUDIO_DSP_RESAMPLER_H_ + +#include "base/audio_buffer.h" + +namespace vraudio { + +// Class that provides rational resampling of audio data. +class Resampler { + public: + Resampler(); + + // Resamples an |AudioBuffer| of input data sampled at |source_frequency| to + // |destination_frequency|. + // + // @param input Input data to be resampled. + // @param output Resampled output data. + void Process(const AudioBuffer& input, AudioBuffer* output); + + // Returns the maximum length which the output buffer will be, given the + // current source and destination frequencies and input length. The actual + // output length will either be this or one less. + // + // @param input_length Length of the input. + // @return Maximum length of the output. + size_t GetMaxOutputLength(size_t input_length) const; + + // Returns the next length which the output buffer will be, given the + // current source and destination frequencies and input length. + // + // @param input_length Length of the input. + // @return Next length of the output. + size_t GetNextOutputLength(size_t input_length) const; + + // Sets the source and destination sampling rate as well as the number of + // channels. Note this method only resets the filter state number of channel + // changes. + // + // @param source_frequency Sampling rate of input data. + // @param destination_frequency Desired output sampling rate. + // @param num_channels Number of channels to process. + void SetRateAndNumChannels(int source_frequency, int destination_frequency, + size_t num_channels); + + // Returns whether the sampling rates provided are supported by the resampler. + // + // @param source Source sampling rate. + // @param destination Destination sampling rate. + // @return True if the sampling rate pair are supported. + static bool AreSampleRatesSupported(int source, int destination); + + // Resets the inner state of the |Resampler| allowing its use repeatedly on + // different data streams. + void ResetState(); + + private: + friend class PolyphaseFilterTest; + // Initializes the |state_| buffer. Called when sampling rate is changed or + // the state is reset. + // + // @param size_t old_state_num_frames Number of frames in the |state_| buffer + // previous to the most recent call to |GenerateInterpolatingFilter|. + void InitializeStateBuffer(size_t old_state_num_frames); + + // Generates a windowed sinc to act as the interpolating/anti-aliasing filter. + // + // @param sample_rate The system sampling rate. + void GenerateInterpolatingFilter(int sample_rate); + + // Arranges the anti aliasing filter coefficients in polyphase filter format. + // + // @param filter_length Number of frames in |filter| containing filter + // coefficients. + // @param filter Vector of filter coefficients. + void ArrangeFilterAsPolyphase(size_t filter_length, + const AudioBuffer::Channel& filter); + + // Generates Hann windowed sinc function anti aliasing filters. + // + // @param cutoff_frequency Transition band (-3dB) frequency of the filter. + // @param sample_rate The system sampling rate. + // @param filter_length Number of frames in |buffer| containing filter + // coefficients. + // @param buffer |AudioBuffer::Channel| to contain the filter coefficients. + void GenerateSincFilter(float cutoff_frequency, float sample_rate, + size_t filter_length, AudioBuffer::Channel* buffer); + + // Rate of the interpolator section of the rational sampling rate converter. + size_t up_rate_; + + // Rate of the decimator section of the rational sampling rate convereter. + size_t down_rate_; + + // Time variable for the polyphase filter. + size_t time_modulo_up_rate_; + + // Marks the last processed sample of the input. + size_t last_processed_sample_; + + // Number of channels in the |AudioBuffer|s processed. + size_t num_channels_; + + // Number of filter coefficients in each phase of the polyphase filter. + size_t coeffs_per_phase_; + + // Filter coefficients stored in polyphase form. + AudioBuffer transposed_filter_coeffs_; + + // Filter coefficients in planar form, used for calculating the transposed + // filter. + AudioBuffer temporary_filter_coeffs_; + + // Buffer holding the samples of input required between input buffers. + AudioBuffer state_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_DSP_RESAMPLER_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/resampler_test.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/resampler_test.cc new file mode 100644 index 000000000..5b23de58f --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/resampler_test.cc @@ -0,0 +1,325 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/resampler.h" + +#include <numeric> +#include <utility> +#include <vector> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/constants_and_types.h" +#include "utils/test_util.h" + +namespace vraudio { + +namespace { + +const int kToneFrequency = 1000; + +const size_t kInputBufferLength = 441; + +const int kSourceDataSampleRate = 44100; +const int kUpDestinationDataSampleRate = 48000; +const int kDownDestinationDataSampleRate = 24000; + +TEST(ResamplerTest, UpSampleTest) { + // Create input buffer with 1000Hz sine wave at 44100Hz, 441 samples long. + // This should be ten periods of a sine wave. + AudioBuffer input(kNumMonoChannels, kInputBufferLength); + GenerateSineWave(kToneFrequency, kSourceDataSampleRate, &input[0]); + + Resampler resampler; + resampler.SetRateAndNumChannels( + kSourceDataSampleRate, kUpDestinationDataSampleRate, kNumMonoChannels); + AudioBuffer output(kNumMonoChannels, + resampler.GetNextOutputLength(kInputBufferLength)); + resampler.Process(input, &output); + + // Ensure the length of the output is as expected. + const size_t input_length_proportion = + kSourceDataSampleRate / kInputBufferLength; + const size_t expected_output_length = + kUpDestinationDataSampleRate / input_length_proportion; + EXPECT_EQ(expected_output_length, output.num_frames()); + + // Ensure the output also contains 10 periods of a sine wave, confirming it is + // still at 1000Hz given the new sampling rate. + AudioBuffer thresholded_output(kNumMonoChannels, output.num_frames()); + for (size_t i = 0; i < output.num_frames(); ++i) { + thresholded_output[0][i] = std::abs(output[0][i]) < 0.5f ? 0.0f : 1.0f; + } + size_t num_square_wave_corners = 1; + // We have now generated a square wave with twice the number of corners as + // there are zero crossings. + for (size_t i = 0; i < output.num_frames() - 1; ++i) { + if (thresholded_output[0][i] != thresholded_output[0][i + 1]) { + num_square_wave_corners++; + } + } + const size_t zerocross_count = num_square_wave_corners / 2; + + const size_t num_expected_zero_crossings = + 2 * kToneFrequency / (kSourceDataSampleRate / kInputBufferLength); + EXPECT_EQ(num_expected_zero_crossings, zerocross_count); +} + +TEST(ResamplerTest, DownSampleTest) { + // Create input buffer with 1000Hz sine wave at 44100Hz, 441 samples long. + // This should be ten periods of a sine wave. + AudioBuffer input(kNumMonoChannels, kInputBufferLength); + GenerateSineWave(kToneFrequency, kSourceDataSampleRate, &input[0]); + + Resampler resampler; + resampler.SetRateAndNumChannels( + kSourceDataSampleRate, kDownDestinationDataSampleRate, kNumMonoChannels); + AudioBuffer output(kNumMonoChannels, + resampler.GetNextOutputLength(kInputBufferLength)); + resampler.Process(input, &output); + + // Ensure the length of the output is as expected. + const size_t input_length_proportion = + kSourceDataSampleRate / kInputBufferLength; + const size_t expected_output_length = + kDownDestinationDataSampleRate / input_length_proportion; + EXPECT_EQ(expected_output_length, output.num_frames()); + + // Ensure the output also contains one period of a sine wave, confirming it is + // still at 100Hz given the new sampling rate. + AudioBuffer thresholded_output(kNumMonoChannels, output.num_frames()); + for (size_t i = 0; i < output.num_frames(); ++i) { + thresholded_output[0][i] = std::abs(output[0][i]) < 0.5f ? 0.0f : 1.0f; + } + int zero_cross_count = 1; + for (size_t i = 0; i < output.num_frames() - 1; ++i) { + if (thresholded_output[0][i] != thresholded_output[0][i + 1]) { + zero_cross_count++; + } + } + const int num_expected_zero_crossings = + 4 * kToneFrequency / (kSourceDataSampleRate / kInputBufferLength); + EXPECT_EQ(num_expected_zero_crossings, zero_cross_count); +} + +TEST(ResamplerTest, ResetStateTest) { + // Create input buffer with 1000Hz sine wave at 44100Hz, 441 samples long. + // This should be ten periods of a sine wave. + AudioBuffer input(kNumMonoChannels, kInputBufferLength); + GenerateSineWave(kToneFrequency, kSourceDataSampleRate, &input[0]); + + Resampler resampler; + resampler.SetRateAndNumChannels( + kSourceDataSampleRate, kUpDestinationDataSampleRate, kNumMonoChannels); + AudioBuffer output_1(kNumMonoChannels, + resampler.GetNextOutputLength(kInputBufferLength)); + resampler.Process(input, &output_1); + + resampler.ResetState(); + AudioBuffer output_2(kNumMonoChannels, + resampler.GetNextOutputLength(kInputBufferLength)); + resampler.Process(input, &output_2); + + // If the clearing of the resampler worked properly there should be no + // internal state between process calls and thus both outputs should be + // identical. + for (size_t sample = 0; sample < output_1.num_frames(); ++sample) { + EXPECT_NEAR(output_1[0][sample], output_2[0][sample], kEpsilonFloat); + } +} + +TEST(ResamplerTest, TwoChannelTest) { + AudioBuffer input(kNumStereoChannels, kInputBufferLength); + GenerateSineWave(kToneFrequency, kSourceDataSampleRate, &input[0]); + GenerateSineWave(2 * kToneFrequency, kSourceDataSampleRate, &input[1]); + + Resampler resampler; + resampler.SetRateAndNumChannels( + kSourceDataSampleRate, kUpDestinationDataSampleRate, kNumStereoChannels); + AudioBuffer output(kNumStereoChannels, + resampler.GetNextOutputLength(kInputBufferLength)); + resampler.Process(input, &output); + + AudioBuffer thresholded_output(kNumStereoChannels, output.num_frames()); + for (size_t i = 0; i < output.num_frames(); ++i) { + thresholded_output[0][i] = std::abs(output[0][i]) < 0.5f ? 0.0f : 1.0f; + thresholded_output[1][i] = std::abs(output[1][i]) < 0.5f ? 0.0f : 1.0f; + } + int channel_0_zero_cross_count = 1; + int channel_1_zero_cross_count = 1; + for (size_t i = 0; i < output.num_frames() - 1; ++i) { + if (thresholded_output[0][i] != thresholded_output[0][i + 1]) { + channel_0_zero_cross_count++; + } + if (thresholded_output[1][i] != thresholded_output[1][i + 1]) { + channel_1_zero_cross_count++; + } + } + const int channel_0_expected_zero_crossings = + 4 * kToneFrequency / (kSourceDataSampleRate / kInputBufferLength); + const int channel_1_expected_zero_crossings = + 8 * kToneFrequency / (kSourceDataSampleRate / kInputBufferLength); + EXPECT_EQ(channel_0_zero_cross_count, channel_0_expected_zero_crossings); + EXPECT_EQ(channel_1_zero_cross_count, channel_1_expected_zero_crossings); +} + +TEST(ResamplerTest, DiracImpulseUpsampleTest) { + const size_t kInputSignalSize = 128; + AudioBuffer input_signal(kNumMonoChannels, kInputSignalSize); + GenerateDiracImpulseFilter(kInputSignalSize / 2, &input_signal[0]); + + const int kSourceSamplingRate = 100; + const int kDestinationSamplingRate = 200; + + Resampler resampler; + resampler.SetRateAndNumChannels(kSourceSamplingRate, kDestinationSamplingRate, + kNumMonoChannels); + + AudioBuffer output_signal(kNumMonoChannels, + resampler.GetNextOutputLength(kInputSignalSize)); + const int resampling_factor = kDestinationSamplingRate / kSourceSamplingRate; + EXPECT_EQ(kInputSignalSize * resampling_factor, output_signal.num_frames()); + // Perform resampling. Dirac impulse position should shift according to + // "resampling_factor". + resampler.Process(input_signal, &output_signal); + EXPECT_EQ(kInputSignalSize * resampling_factor, output_signal.num_frames()); + + DelayCompare(input_signal[0], output_signal[0], kInputSignalSize / 2, + kEpsilonFloat); +} + +TEST(ResamplerTest, GetNextOutputLengthTest) { + const size_t input_length = 10; + Resampler resampler; + resampler.SetRateAndNumChannels(kSourceDataSampleRate, + kSourceDataSampleRate / 2, kNumMonoChannels); + // In this case the source rate is twice the destination rate, we can expect + // the output length to be half the input_length. + + resampler.SetRateAndNumChannels(kSourceDataSampleRate, + kSourceDataSampleRate * 2, kNumMonoChannels); + // In this case the source rate is half the destination rate, we can + // expect the output length to be twice the input_length. + EXPECT_EQ(input_length * 2, resampler.GetNextOutputLength(input_length)); +} + +TEST(Resampler, AreSampleRatesSupportedTest) { + const size_t kNumPairs = 6; + const int kSourceRates[kNumPairs] = {4000, 16000, 44100, 96000, 41999, 48000}; + const int kDestRates[kNumPairs] = {96000, 44100, 48000, 32000, 44100, 43210}; + const bool kExpectedResults[kNumPairs] = {true, true, true, + true, false, false}; + + // We generally support only rates with relatively high GCD. + for (size_t i = 0; i < kNumPairs; ++i) { + const bool result = + Resampler::AreSampleRatesSupported(kSourceRates[i], kDestRates[i]); + EXPECT_EQ(result, kExpectedResults[i]); + } +} + +class OutputLengthTest : public ::testing::TestWithParam<std::pair<int, int>> { +}; + +// Tests that the lengths returned by the |Process| method are always equal to +// the length returned by |GetMaxOutputLength|, or one sample less. +TEST_P(OutputLengthTest, OutputLengthTest) { + const size_t kInputSize = 512; + Resampler resampler; + const auto rates = GetParam(); + resampler.SetRateAndNumChannels(rates.first, rates.second, kNumMonoChannels); + AudioBuffer input(kNumMonoChannels, kInputSize); + const size_t max_output_length = resampler.GetMaxOutputLength(kInputSize); + AudioBuffer output(kNumMonoChannels, max_output_length); + + // Process 100 times and expect the lengts to be maximum length or one less. + const size_t kTimesToProcess = 100; + for (size_t i = 0; i < kTimesToProcess; ++i) { + const size_t next_output_length = resampler.GetNextOutputLength(kInputSize); + resampler.Process(input, &output); + EXPECT_LE(next_output_length, max_output_length); + EXPECT_LE(max_output_length - next_output_length, 1U); + } +} + +INSTANTIATE_TEST_CASE_P(RatePairs, OutputLengthTest, + ::testing::Values(std::make_pair(44100, 48000), + std::make_pair(48000, 44100))); + +} // namespace + +class PolyphaseFilterTest : public ::testing::Test { + protected: + PolyphaseFilterTest() {} + // Virtual methods from ::testing::Test + ~PolyphaseFilterTest() override {} + void SetUp() override {} + void TearDown() override {} + + std::unique_ptr<AudioBuffer> GetPolyphaseFilter( + const AudioBuffer::Channel& filter_coefficients, int coeffs_per_phase, + int up_rate) { + // Create a resampler with arbitrary source and destination sampling rates. + Resampler resampler; + resampler.SetRateAndNumChannels(kSourceDataSampleRate, + kSourceDataSampleRate, kNumMonoChannels); + resampler.up_rate_ = up_rate; + resampler.transposed_filter_coeffs_.Clear(); + resampler.coeffs_per_phase_ = coeffs_per_phase; + resampler.ArrangeFilterAsPolyphase(filter_coefficients.size(), + filter_coefficients); + std::unique_ptr<AudioBuffer> polyphase_filter(new AudioBuffer()); + *polyphase_filter = resampler.transposed_filter_coeffs_; + return polyphase_filter; + } +}; + +TEST_F(PolyphaseFilterTest, CorrectPolyphaseFilterTest) { + // Choose an uprate which is a factor of the filters length. + const int filter_length = 24; + const int uprate = 4; + const int phase_length = filter_length / uprate; + + // Create a vector of ascending numbers (1:24). + AudioBuffer ascending(kNumMonoChannels, filter_length); + ascending.Clear(); + std::iota(ascending[0].begin(), ascending[0].end(), 1.0f); + + std::unique_ptr<AudioBuffer> output = + GetPolyphaseFilter(ascending[0], phase_length, uprate); + + // In polyphase format, the filter coefficients can be thought of as a matrix + // with uprate columns. The last uprate coeffecients are in the first row and + // the first uprate coefficients are in the last row. This is stored in a + // vector, column by column. + // + // 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 + // becomes: + // 21 22 23 24 + // 17 18 19 20 + // 13 14 15 16 + // 9 10 11 12 + // 5 6 7 8 + // 1 2 3 4 + size_t index = 0; + for (int phase = uprate; phase > 0; --phase) { + for (int value = filter_length - phase + 1; value > uprate - phase; + value -= uprate) { + EXPECT_EQ(static_cast<float>(value), (*output)[0][index++]); + } + } +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/reverb_onset_compensator.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/reverb_onset_compensator.cc new file mode 100644 index 000000000..2996d3799 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/reverb_onset_compensator.cc @@ -0,0 +1,214 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/reverb_onset_compensator.h" + +#include <algorithm> +#include <cmath> +#include <iterator> + +#include "base/constants_and_types.h" +#include "dsp/spectral_reverb_constants_and_tables.h" +#include "dsp/utils.h" + +namespace vraudio { + +namespace { + +// Number of reverb updaters. Twelve were chosen as this represents one update +// per buffer length at 24kHz, a number we are very unlikely to exceed. +const size_t kNumReverbUpdaters = 12; + +} // namespace + +ReverbOnsetCompensator::ReverbOnsetCompensator(int sampling_rate, + size_t frames_per_buffer, + FftManager* fft_manager) + : fft_manager_(fft_manager), + sampling_rate_(sampling_rate), + frames_per_buffer_(frames_per_buffer), + base_curves_(kNumStereoChannels, kCorrectionCurveLength), + adder_curves_(kNumStereoChannels, kCorrectionCurveLength), + left_filter_(CeilToMultipleOfFramesPerBuffer(kCorrectionCurveLength, + frames_per_buffer_), + frames_per_buffer_, fft_manager_), + right_filter_(CeilToMultipleOfFramesPerBuffer(kCorrectionCurveLength, + frames_per_buffer_), + frames_per_buffer_, fft_manager_), + delay_filter_(CeilToMultipleOfFramesPerBuffer(kCorrectionCurveLength, + frames_per_buffer_), + frames_per_buffer_), + num_active_processors_(0), + temp_kernel_buffer_(kNumStereoChannels, frames_per_buffer_), + temp_freq_buffer_(kNumMonoChannels, fft_manager_->GetFftSize()) { + CHECK(fft_manager_); + DCHECK_GT(sampling_rate_, 0); + DCHECK_GT(frames_per_buffer_, 0U); + + temp_kernel_buffer_.Clear(); + temp_freq_buffer_.Clear(); + + GenerateNoiseVectors(); + GenerateCorrectionCurves(); + + // Insert reverb updaters. + for (size_t i = 0; i < kNumReverbUpdaters; ++i) { + update_processors_.emplace_front(new ReverbOnsetUpdateProcessor( + frames_per_buffer_, sampling_rate_, &base_curves_, &adder_curves_)); + } +} + +void ReverbOnsetCompensator::Process(const AudioBuffer& input, + AudioBuffer* output) { + DCHECK(output); + DCHECK_EQ(kNumMonoChannels, input.num_channels()); + DCHECK_EQ(frames_per_buffer_, input.num_frames()); + DCHECK_EQ(kNumStereoChannels, output->num_channels()); + DCHECK_EQ(frames_per_buffer_, output->num_frames()); + + delay_filter_.InsertData(input[0]); + delay_filter_.GetDelayedData(kCompensationOnsetLength, &(*output)[0]); + + // Process reverb updates. + AudioBuffer::Channel* kernel_channel_left = &temp_kernel_buffer_[0]; + AudioBuffer::Channel* kernel_channel_right = &temp_kernel_buffer_[1]; + + size_t processor_index = 0; + while (processor_index < num_active_processors_) { + auto current_processor = update_processors_.begin(); + std::advance(current_processor, processor_index); + const size_t partition_index = + (*current_processor)->GetCurrentPartitionIndex(); + if ((*current_processor) + ->Process(bandpassed_noise_left_, bandpassed_noise_right_, + kernel_channel_left, kernel_channel_right)) { + left_filter_.ReplacePartition(partition_index, *kernel_channel_left); + right_filter_.ReplacePartition(partition_index, *kernel_channel_right); + ++processor_index; + } else { + // Update of the |current_processor| is finished, move it to the end of + // the list and reduce the number of active processors. + update_processors_.splice(update_processors_.end(), update_processors_, + current_processor); + --num_active_processors_; + } + } + + // Filter the input (Using the output buffer due to the delay operation). + fft_manager_->FreqFromTimeDomain((*output)[0], &temp_freq_buffer_[0]); + + left_filter_.Filter(temp_freq_buffer_[0]); + right_filter_.Filter(temp_freq_buffer_[0]); + + left_filter_.GetFilteredSignal(&(*output)[0]); + right_filter_.GetFilteredSignal(&(*output)[1]); +} + +void ReverbOnsetCompensator::Update(const float* rt60_values, float gain) { + DCHECK(rt60_values); + // Reset a reverb update processor from the end of the list and place it at + // the front. If the list is full, rotate the list and reuse the oldest active + // processor. + std::list<std::unique_ptr<ReverbOnsetUpdateProcessor>>::iterator + new_processor; + if (num_active_processors_ < kNumReverbUpdaters) { + new_processor = update_processors_.end(); + std::advance(new_processor, -1); + } else { + new_processor = update_processors_.begin(); + } + + (*new_processor)->SetReverbTimes(rt60_values); + (*new_processor)->SetGain(gain); + + if (new_processor != update_processors_.begin()) { + auto list_item = update_processors_.begin(); + std::advance(list_item, num_active_processors_); + if (list_item != new_processor) { + update_processors_.splice(list_item, update_processors_, new_processor, + std::next(new_processor)); + } + ++num_active_processors_; + } else { + std::rotate(update_processors_.begin(), + std::next(update_processors_.begin()), + update_processors_.end()); + } +} + +void ReverbOnsetCompensator::GenerateCorrectionCurves() { + // Copy into the adder curves such that the memory is aligned. + std::copy(kLowCorrectionCurve, kLowCorrectionCurve + kCorrectionCurveLength, + adder_curves_[0].begin()); + std::copy(kHighCorrectionCurve, kHighCorrectionCurve + kCorrectionCurveLength, + adder_curves_[1].begin()); + + // Evaluate the polynomials to generate the base curves. Here the 'low' and + // 'high' names refer to the reverberation times. + AudioBuffer::Channel* low_channel = &base_curves_[0]; + AudioBuffer::Channel* high_channel = &base_curves_[1]; + for (size_t i = 0; i < kCorrectionCurveLength; ++i) { + // Scaled independent variable (Allowed better conditioning). + const float conditioning_scalar = + (static_cast<float>(i) - kCurveOffset) * kCurveScale; + (*low_channel)[i] = kLowReverberationCorrectionCurve[0]; + (*high_channel)[i] = kHighReverberationCorrectionCurve[0]; + float power = conditioning_scalar; + for (size_t k = 1; k < kCurvePolynomialLength; ++k) { + (*low_channel)[i] += power * kLowReverberationCorrectionCurve[k]; + (*high_channel)[i] += power * kHighReverberationCorrectionCurve[k]; + power *= conditioning_scalar; + } + (*low_channel)[i] = std::max((*low_channel)[i], 0.0f); + (*high_channel)[i] = std::max((*high_channel)[i], 0.0f); + } +} + +void ReverbOnsetCompensator::GenerateNoiseVectors() { + const size_t num_octave_bands = GetNumReverbOctaveBands(sampling_rate_); + const size_t noise_length = CeilToMultipleOfFramesPerBuffer( + kCorrectionCurveLength, frames_per_buffer_); + for (size_t band = 0; band < num_octave_bands; ++band) { + // Generate preset tail. + bandpassed_noise_left_.emplace_back(kNumMonoChannels, noise_length); + GenerateBandLimitedGaussianNoise(kOctaveBandCentres[band], sampling_rate_, + /*seed=*/1U, + &bandpassed_noise_left_[band]); + bandpassed_noise_right_.emplace_back(kNumMonoChannels, noise_length); + GenerateBandLimitedGaussianNoise(kOctaveBandCentres[band], sampling_rate_, + /*seed=*/2U, + &bandpassed_noise_right_[band]); + + auto min_max = std::minmax_element(bandpassed_noise_left_[band][0].begin(), + bandpassed_noise_left_[band][0].end()); + const float left_scale = + std::max(std::fabs(*min_max.first), std::fabs(*min_max.second)); + min_max = std::minmax_element(bandpassed_noise_right_[band][0].begin(), + bandpassed_noise_right_[band][0].end()); + const float right_scale = + std::max(std::fabs(*min_max.first), std::fabs(*min_max.second)); + + const float scale = std::max(left_scale, right_scale); + + ScalarMultiply(noise_length, scale, bandpassed_noise_left_[band][0].begin(), + bandpassed_noise_left_[band][0].begin()); + ScalarMultiply(noise_length, scale, + bandpassed_noise_right_[band][0].begin(), + bandpassed_noise_right_[band][0].begin()); + } +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/reverb_onset_compensator.h b/src/3rdparty/resonance-audio/resonance_audio/dsp/reverb_onset_compensator.h new file mode 100644 index 000000000..4bf76f8f2 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/reverb_onset_compensator.h @@ -0,0 +1,110 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_DSP_REVERB_ONSET_COMPENSATOR_H_ +#define RESONANCE_AUDIO_DSP_REVERB_ONSET_COMPENSATOR_H_ + +#include <list> +#include <vector> + +#include "base/audio_buffer.h" +#include "dsp/delay_filter.h" +#include "dsp/fft_manager.h" +#include "dsp/partitioned_fft_filter.h" +#include "dsp/reverb_onset_update_processor.h" + +namespace vraudio { + +// Implements a convolutional compensator for the spectral reverb onset curve. +class ReverbOnsetCompensator { + public: + // Constructs a |ReverbOnsetCompensator|. + // + // @param sampling_rate The sampling rate in Hz. + // @param frames_per_buffer The number of frames per buffer in the system. + // @param fft_manager Pointer to a FftManager to perform FFT transformations. + ReverbOnsetCompensator(int sampling_rate, size_t frames_per_buffer, + FftManager* fft_manager); + + // Resets the reverb with a new set of reverberation times. The new tail is + // generated by replacing the current tail buffer by buffer. + // + // @param rt60_values |kNumReverbOctaveBands| values denoting the + // reverberation decay time to -60dB in octave bands starting at + // |kLowestOctaveBand|. + // @param gain Gain to be applied across all frequencies. + void Update(const float* rt60_values, float gain); + + // Processes a mono |AudioBuffer| with a reverberant tail. + // + // @param input A mono |AudioBuffer| of input data. + // @param output Pointer to stereo output buffer. + void Process(const AudioBuffer& input, AudioBuffer* output); + + private: + // Generates the constituent curves which are combined to make up the + // correction curve envelopes. These envelopes ensure the initial part of the + // specral reverb's impulse response, which exhibits 'growing' behaviour, + // follows the desired exponential decay. + void GenerateCorrectionCurves(); + + // Generates a pair of |kNumOctaveBands| band, octave filtered, noise buffers. + void GenerateNoiseVectors(); + + // Manager for all FFT related functionality (not owned). + FftManager* const fft_manager_; + + // The system sampling rate. + const int sampling_rate_; + + // The system number of frames per buffer. + const size_t frames_per_buffer_; + + // Pre-generated band-passed noise to be used as a base for the reverb tail. + std::vector<AudioBuffer> bandpassed_noise_left_; + std::vector<AudioBuffer> bandpassed_noise_right_; + + // The constituent curves used to generate the onset compensation envelopes. + AudioBuffer base_curves_; + AudioBuffer adder_curves_; + + // Filter for processing the left reverberant channel. + PartitionedFftFilter left_filter_; + + // Filter for processing the right reverberant channel. + PartitionedFftFilter right_filter_; + + // Delay filter used to ensure the compensation curve starts at the same point + // as the spectral reverb. + DelayFilter delay_filter_; + + // Number of active update processors. + size_t num_active_processors_; + + // Active reverb update processors to replace the corresponding filter + // partitions of the reverb tail within each process call. + std::list<std::unique_ptr<ReverbOnsetUpdateProcessor>> update_processors_; + + // Temporary buffer used to process filter kernel partitions. + AudioBuffer temp_kernel_buffer_; + + // Temporary buffer to hold FFT frequency domain output. + PartitionedFftFilter::FreqDomainBuffer temp_freq_buffer_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_DSP_REVERB_ONSET_COMPENSATOR_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/reverb_onset_update_processor.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/reverb_onset_update_processor.cc new file mode 100644 index 000000000..3a5dbcf02 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/reverb_onset_update_processor.cc @@ -0,0 +1,184 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/reverb_onset_update_processor.h" + +#include <algorithm> + +#include "base/constants_and_types.h" +#include "base/simd_utils.h" +#include "dsp/spectral_reverb_constants_and_tables.h" +#include "dsp/utils.h" + +namespace vraudio { + +namespace { + +// Find the absolute difference between two size_t values. +inline size_t absdiff(size_t lhs, size_t rhs) { + return lhs > rhs ? lhs - rhs : rhs - lhs; +} + +} // namespace + +ReverbOnsetUpdateProcessor::ReverbOnsetUpdateProcessor( + size_t frames_per_buffer, int sampling_rate, AudioBuffer* base_curves, + AudioBuffer* adder_curves) + : sampling_rate_(sampling_rate), + tail_update_cursor_(0), + tail_length_(CeilToMultipleOfFramesPerBuffer(kCorrectionCurveLength, + frames_per_buffer)), + gain_(1.0f), + curve_indices_(GetNumReverbOctaveBands(sampling_rate_), kInvalidIndex), + pure_decay_coefficients_(curve_indices_.size(), 0.0f), + pure_decay_exponents_(curve_indices_.size(), 0.0f), + band_buffer_(kNumStereoChannels, frames_per_buffer), + envelope_buffer_(kNumMonoChannels, frames_per_buffer), + base_curves_(base_curves), + adder_curves_(adder_curves) {} + +void ReverbOnsetUpdateProcessor::SetReverbTimes(const float* rt60_values) { + DCHECK(rt60_values); + const size_t num_octave_bands = curve_indices_.size(); + const float sampling_rate_float = static_cast<float>(sampling_rate_); + tail_update_cursor_ = 0; + // Choose curves for each band. + for (size_t band = 0; band < num_octave_bands; ++band) { + curve_indices_[band] = + GetFeedbackIndexFromRt60(rt60_values[band], sampling_rate_float); + // Deal with the case where only the convolution is needed. + if (curve_indices_[band] == kInvalidIndex) { + const float min_reverb_time = + kMinReverbTimeForFeedback48kHz * + (sampling_rate_float / kDefaultSpectralReverbSampleRate); + const float effective_rt = + rt60_values[band] <= min_reverb_time ? rt60_values[band] : 0.0f; + pure_decay_exponents_[band] = + std::abs(effective_rt) > kEpsilonFloat + ? std::exp(kNegativeLog1000 / + (sampling_rate_float * effective_rt)) + : 0.0f; + pure_decay_coefficients_[band] = pure_decay_exponents_[band]; + } + } +} + +bool ReverbOnsetUpdateProcessor::Process( + const std::vector<AudioBuffer>& bandpassed_noise_left, + const std::vector<AudioBuffer>& bandpassed_noise_right, + AudioBuffer::Channel* kernel_channel_left, + AudioBuffer::Channel* kernel_channel_right) { + if (tail_update_cursor_ >= tail_length_) { + // Processing the reverb tail is finished. + tail_update_cursor_ = 0; + return false; + } + const size_t frames_per_buffer = band_buffer_.num_frames(); + DCHECK(kernel_channel_left); + DCHECK(kernel_channel_right); + DCHECK_EQ(bandpassed_noise_left.size(), curve_indices_.size()); + DCHECK_EQ(bandpassed_noise_right.size(), curve_indices_.size()); + DCHECK_EQ(bandpassed_noise_left[0].num_frames(), tail_length_); + DCHECK_EQ(bandpassed_noise_right[0].num_frames(), tail_length_); + DCHECK_GE(tail_length_, kCorrectionCurveLength); + DCHECK_EQ(kernel_channel_left->size(), frames_per_buffer); + DCHECK_EQ(kernel_channel_right->size(), frames_per_buffer); + + // Clear for accumulation per frequency band. + kernel_channel_left->Clear(); + kernel_channel_right->Clear(); + + AudioBuffer::Channel& band_channel_left = band_buffer_[0]; + AudioBuffer::Channel& band_channel_right = band_buffer_[1]; + // Define the number of samples we are still able to copy from the multiplier + // and adder curves. + const size_t copy_length = + frames_per_buffer + tail_update_cursor_ <= kCorrectionCurveLength + ? frames_per_buffer + : absdiff(kCorrectionCurveLength, tail_update_cursor_); + AudioBuffer::Channel* envelope_channel = &envelope_buffer_[0]; + // Compute the band buffer for each band response. + for (size_t band = 0; band < curve_indices_.size(); ++band) { + const AudioBuffer::Channel& noise_channel_left = + bandpassed_noise_left[band][0]; + const AudioBuffer::Channel& noise_channel_right = + bandpassed_noise_right[band][0]; + // Fill the band buffer with the next noise buffer and apply gain. + ScalarMultiply(frames_per_buffer, gain_, + noise_channel_left.begin() + tail_update_cursor_, + band_channel_left.begin()); + ScalarMultiply(frames_per_buffer, gain_, + noise_channel_right.begin() + tail_update_cursor_, + band_channel_right.begin()); + // Skip the band if we have an invalid index + const int curve_index = curve_indices_[band]; + if (curve_index != kInvalidIndex) { + // Apply the correct compensation curve to the buffer. + const float scale = kCurveCorrectionMultipliers[curve_index]; + AudioBuffer::Channel* adder_curve_channel; + if (tail_update_cursor_ < kCorrectionCurveLength) { + // Use either the high frequency or low frequency curve. + if (static_cast<size_t>(curve_index) >= kCurveChangeoverIndex) { + adder_curve_channel = &(*adder_curves_)[1]; + std::copy_n((*base_curves_)[1].begin() + tail_update_cursor_, + copy_length, envelope_channel->begin()); + } else { + adder_curve_channel = &(*adder_curves_)[0]; + std::copy_n((*base_curves_)[0].begin() + tail_update_cursor_, + copy_length, envelope_channel->begin()); + } + // Construct the correct envelope (chunk thereof). + ScalarMultiplyAndAccumulate( + copy_length, scale, + adder_curve_channel->begin() + tail_update_cursor_, + envelope_channel->begin()); + // Ensure the end part of the envelope does not contain spurious data. + std::fill(envelope_channel->begin() + copy_length, + envelope_channel->end(), 0.0f); + } else { + // If we have moved past the length of the correction curve, fill the + // envelope chunk with zeros. + envelope_channel->Clear(); + } + + // Apply that envelope to the given band and accumulate into the output. + MultiplyAndAccumulatePointwise( + frames_per_buffer, envelope_channel->begin(), + band_channel_left.begin(), kernel_channel_left->begin()); + MultiplyAndAccumulatePointwise( + frames_per_buffer, envelope_channel->begin(), + band_channel_right.begin(), kernel_channel_right->begin()); + } else { + // If the decay time is too short for the spectral reverb to make a + // contribution (0.15s @48kHz), the compensation filter will consist of + // the entire tail. + for (size_t frame = 0; frame < frames_per_buffer; ++frame) { + (*kernel_channel_left)[frame] += + pure_decay_coefficients_[band] * band_channel_left[frame]; + (*kernel_channel_right)[frame] += + pure_decay_coefficients_[band] * band_channel_right[frame]; + // Update the decay coefficient. + pure_decay_coefficients_[band] *= pure_decay_exponents_[band]; + } + } + } + // Update the cursor. + tail_update_cursor_ += frames_per_buffer; + + return true; +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/reverb_onset_update_processor.h b/src/3rdparty/resonance-audio/resonance_audio/dsp/reverb_onset_update_processor.h new file mode 100644 index 000000000..19fee4b42 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/reverb_onset_update_processor.h @@ -0,0 +1,113 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_DSP_REVERB_ONSET_UPDATE_PROCESSOR_H_ +#define RESONANCE_AUDIO_DSP_REVERB_ONSET_UPDATE_PROCESSOR_H_ + +#include <vector> + +#include "base/audio_buffer.h" + +namespace vraudio { + +// Updater for the |ReverbOnsetCompensator|. +class ReverbOnsetUpdateProcessor { + public: + // Constructs a update processor for the sprectral reverb onset compensator. + // + // @param frames_per_buffer_ System buffer length in frames. + // @param sampling_rate System sample rate. + // @param base_curves Constituent curve used for envelope generation. + // @param ader_curves Constituent curve used for envelope generation. + ReverbOnsetUpdateProcessor(size_t frames_per_buffer, int sampling_rate, + AudioBuffer* base_curves, + AudioBuffer* adder_curves); + + // Sets reverberation times in different frequency bands. + // + // @param rt60_values |kNumReverbOctaveBands| values denoting the + // reverberation decay time to -60dB in octave bands starting at + // |kLowestOctaveBand|. + void SetReverbTimes(const float* rt60_values); + + // Sets the gain applied to the overall compensation envelope. + // + // @param gain Gain applied to overall envelope. + void SetGain(float gain) { gain_ = gain; } + + // Processes the next tail update. + // + // @param bandpassed_noise_left Pre-computed bandpassed noise buffer. + // @param bandpassed_noise_right Pre-computed bandpassed noise buffer. + // @param kernel_channel_left Kernel channel to fill in the processed output. + // @param kernel_channel_right Kernel channel to fill in the processed output. + // @return True if the tail update continues. + bool Process(const std::vector<AudioBuffer>& bandpassed_noise_left, + const std::vector<AudioBuffer>& bandpassed_noise_right, + AudioBuffer::Channel* kernel_channel_left, + AudioBuffer::Channel* kernel_channel_right); + + // Returns the partition index of the current update state. + // + // @return Current partition index. + size_t GetCurrentPartitionIndex() const { + const size_t frames_per_buffer = band_buffer_.num_frames(); + DCHECK_NE(frames_per_buffer, 0U); + return tail_update_cursor_ / frames_per_buffer; + } + + // Disable copy and assignment operator. + ReverbOnsetUpdateProcessor(ReverbOnsetUpdateProcessor const&) = delete; + void operator=(ReverbOnsetUpdateProcessor const&) = delete; + + private: + // System sample rate. + int sampling_rate_; + + // Current frame position of the reverb tail to be updated. + size_t tail_update_cursor_; + + // Length of the new reverb tail to be replaced in frames. + size_t tail_length_; + + // Gain applied to the reverb compensation. + float gain_; + + // Indices of the multiplication factor to be used to create the onset + // compensation curve at each frequency band. + std::vector<int> curve_indices_; + + // Decay coefficients per each band of the reverb tail, used below 0.15s + // @48kHz. + std::vector<float> pure_decay_coefficients_; + + // Decay exponential per each band of the reverb tail, used below 0.15s + // @48kHz. + std::vector<float> pure_decay_exponents_; + + // Temporary buffers used to process the decayed noise per each band. + AudioBuffer band_buffer_; + AudioBuffer envelope_buffer_; + + // Pointers to audio buffers owned by the |ReverbOnsetCompensator| storing the + // constituent curves used to generate the onset compensation envelopes. + AudioBuffer* base_curves_; + AudioBuffer* adder_curves_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_DSP_REVERB_ONSET_UPDATE_PROCESSOR_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/sh_hrir_creator.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/sh_hrir_creator.cc new file mode 100644 index 000000000..11226a6fc --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/sh_hrir_creator.cc @@ -0,0 +1,72 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/sh_hrir_creator.h" + +#include "third_party/SADIE_hrtf_database/generated/hrtf_assets.h" +#include "ambisonics/utils.h" +#include "base/logging.h" +#include "dsp/resampler.h" +#include "utils/planar_interleaved_conversion.h" + +namespace vraudio { + +std::unique_ptr<AudioBuffer> CreateShHrirsFromWav(const Wav& wav, + int target_sample_rate_hz, + Resampler* resampler) { + DCHECK(resampler); + const size_t num_channels = wav.GetNumChannels(); + CHECK(IsValidAmbisonicOrder(num_channels)); + + const size_t sh_hrir_length = wav.interleaved_samples().size() / num_channels; + std::unique_ptr<AudioBuffer> sh_hrirs( + new AudioBuffer(num_channels, sh_hrir_length)); + FillAudioBuffer(wav.interleaved_samples(), num_channels, sh_hrirs.get()); + + const int wav_sample_rate_hz = wav.GetSampleRateHz(); + CHECK_GT(wav_sample_rate_hz, 0); + CHECK_GT(target_sample_rate_hz, 0); + if (wav_sample_rate_hz != target_sample_rate_hz) { + if (!Resampler::AreSampleRatesSupported(wav_sample_rate_hz, + target_sample_rate_hz)) { + LOG(FATAL) << "Unsupported sampling rates for loading HRIRs: " + << wav_sample_rate_hz << ", " << target_sample_rate_hz; + } + resampler->ResetState(); + // Resample the SH HRIRs if necessary. + resampler->SetRateAndNumChannels(wav_sample_rate_hz, target_sample_rate_hz, + num_channels); + std::unique_ptr<AudioBuffer> resampled_sh_hrirs(new AudioBuffer( + num_channels, resampler->GetNextOutputLength(sh_hrir_length))); + resampler->Process(*sh_hrirs, resampled_sh_hrirs.get()); + return resampled_sh_hrirs; + } + return sh_hrirs; +} + +std::unique_ptr<AudioBuffer> CreateShHrirsFromAssets( + const std::string& filename, int target_sample_rate_hz, + Resampler* resampler) { + // Read SH HRIR from asset store. + sadie::HrtfAssets hrtf_assets; + std::unique_ptr<std::string> sh_hrir_data = hrtf_assets.GetFile(filename); + CHECK_NOTNULL(sh_hrir_data.get()); + std::istringstream wav_data_stream(*sh_hrir_data); + std::unique_ptr<const Wav> wav = Wav::CreateOrNull(&wav_data_stream); + return CreateShHrirsFromWav(*wav, target_sample_rate_hz, resampler); +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/sh_hrir_creator.h b/src/3rdparty/resonance-audio/resonance_audio/dsp/sh_hrir_creator.h new file mode 100644 index 000000000..5a11926f5 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/sh_hrir_creator.h @@ -0,0 +1,54 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_DSP_SH_HRIR_CREATOR_H_ +#define RESONANCE_AUDIO_DSP_SH_HRIR_CREATOR_H_ + +#include "base/audio_buffer.h" +#include "dsp/resampler.h" +#include "utils/wav.h" + +namespace vraudio { + +// Creates a multichannel audio buffer of Spherical Harmonic-encoded Head +// Related Impulse Responses (SH HRIRs) from Wav SH HRIR assets. It also +// checks if the channel count of the SH HRIR file is correct and resamples the +// SH HRIRs if necessary to match the system (target) sampling rate. +// +// @param wav |Wav| instance that contains SH HRIRs. +// @param target_sample_rate_hz Target sampling rate in Hertz. +// @param resampler Pointer to a resampler used to convert HRIRs to the system +// rate, +// (This resampler's internal state will be reset on each function call). +// @return Unique pointer to |AudioBuffer| where the SH HRIRs will be written. +std::unique_ptr<AudioBuffer> CreateShHrirsFromWav(const Wav& wav, + int target_sample_rate_hz, + Resampler* resampler); + +// Creates a SH HRIR multichannel audio buffer from assets. +// +// @param filename Name of the Wav file that contains SH HRIRs. +// @param target_sample_rate_hz Target sampling rate in Hertz. +// @param resampler Pointer to a resampler used to convert HRIRs to the system +// rate. +// @return Unique pointer to |AudioBuffer| where the SH HRIRs will be written. +std::unique_ptr<AudioBuffer> CreateShHrirsFromAssets( + const std::string& filename, int target_sample_rate_hz, + Resampler* resampler); + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_DSP_SH_HRIR_CREATOR_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/shoe_box_room.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/shoe_box_room.cc new file mode 100644 index 000000000..769166248 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/shoe_box_room.cc @@ -0,0 +1,61 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/shoe_box_room.h" + +#include "base/constants_and_types.h" + +namespace vraudio { + +void ComputeReflections(const WorldPosition& relative_listener_position, + const WorldPosition& room_dimensions, + const float* reflection_coefficients, + std::vector<Reflection>* reflections) { + DCHECK(reflection_coefficients); + DCHECK(reflections); + DCHECK_EQ(reflections->size(), kNumRoomSurfaces); + const WorldPosition kOrigin(0.0f, 0.0f, 0.0f); + if (!IsPositionInAabb(relative_listener_position, kOrigin, room_dimensions)) { + // Listener is outside the room, skip computation. + std::fill(reflections->begin(), reflections->end(), Reflection()); + return; + } + // Calculate the distance of the listener to each wall. + // Since all the sources are 'attached' to the listener in the computation + // of reflections, the distance travelled is arbitrary. So, we add 1.0f to + // the computed distance in order to avoid delay time approaching 0 and the + // magnitude approaching +inf. + const WorldPosition offsets = 0.5f * room_dimensions; + const float distances_travelled[kNumRoomSurfaces] = { + offsets[0] + relative_listener_position[0] + 1.0f, + offsets[0] - relative_listener_position[0] + 1.0f, + offsets[1] + relative_listener_position[1] + 1.0f, + offsets[1] - relative_listener_position[1] + 1.0f, + offsets[2] + relative_listener_position[2] + 1.0f, + offsets[2] - relative_listener_position[2] + 1.0f}; + + for (size_t i = 0; i < kNumRoomSurfaces; ++i) { + // Convert distances to time delays in seconds. + (*reflections)[i].delay_time_seconds = + distances_travelled[i] / kSpeedOfSound; + // Division by distance is performed here as we don't want this applied more + // than once. + (*reflections)[i].magnitude = + reflection_coefficients[i] / distances_travelled[i]; + } +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/shoe_box_room.h b/src/3rdparty/resonance-audio/resonance_audio/dsp/shoe_box_room.h new file mode 100644 index 000000000..eab514922 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/shoe_box_room.h @@ -0,0 +1,44 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_DSP_SHOE_BOX_ROOM_H_ +#define RESONANCE_AUDIO_DSP_SHOE_BOX_ROOM_H_ + +#include <vector> + +#include "base/misc_math.h" +#include "dsp/reflection.h" + +namespace vraudio { + +// Computes a set of reflections from each surface of a shoe-box room model. +// Uses a simplified calculation method which assumes that all the sources are +// 'attached' to the listener. Also, assumes that the listener is inside the +// shoe-box room. +// +// @param relative_listener_position Relative listener position to the center of +// the room. +// @param room_dimensions Dimensions of the room. +// @param reflection_coefficients Reflection coefficients. +// @return List of computed reflections by the image source method. +void ComputeReflections(const WorldPosition& relative_listener_position, + const WorldPosition& room_dimensions, + const float* reflection_coefficients, + std::vector<Reflection>* reflections); + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_DSP_SHOE_BOX_ROOM_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/shoe_box_room_test.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/shoe_box_room_test.cc new file mode 100644 index 000000000..78393604f --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/shoe_box_room_test.cc @@ -0,0 +1,96 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/shoe_box_room.h" + +#include <algorithm> +#include <vector> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/constants_and_types.h" + +namespace vraudio { + +namespace { + +// Expected reflection delay times in seconds. +const float kExpectedDelays[kNumRoomSurfaces] = { + 0.011661f, 0.005830f, 0.016035f, 0.004373f, 0.020408f, 0.002915f}; + +// Expected reflection magnitudes. +const float kExpectedMagnitudes[kNumRoomSurfaces] = { + 0.25f, 0.5f, 0.181818f, 0.666666f, 0.142857f, 1.0f}; + +// Tests that a set of single reflections arrive in from the correct directions. +TEST(ShoeBoxRoomTest, SingleReflectionsTest) { + const WorldPosition kListenerPosition(1.0f, 2.0f, 3.0f); + const WorldPosition kRoomDimensions(4.0f, 5.0f, 6.0f); + + // Perform the simplified image source method with the wall at the given index + // having a reflection coefficient of 1 and all of the other walls having a + // reflection coefficient of 0. Thus we expect one reflection in the output + // and we should be able to predict its magnitude, delay and direction of + // arrival in terms of azimuth and elevation. + float reflection_coefficients[kNumRoomSurfaces]; + std::fill(std::begin(reflection_coefficients), + std::end(reflection_coefficients), 0.0f); + std::vector<Reflection> reflections(kNumRoomSurfaces); + for (size_t index = 0; index < kNumRoomSurfaces; ++index) { + reflection_coefficients[index] = 1.0f; + + ComputeReflections(kListenerPosition, kRoomDimensions, + reflection_coefficients, &reflections); + EXPECT_EQ(kNumRoomSurfaces, reflections.size()); + + // Check that the correct reflection is returned for the given call. + size_t num_returned = 0; + for (size_t i = 0; i < kNumRoomSurfaces; ++i) { + if (reflections[i].magnitude > 0.0f) { + EXPECT_NEAR(kExpectedDelays[index], reflections[i].delay_time_seconds, + kEpsilonFloat); + EXPECT_NEAR(kExpectedMagnitudes[index], reflections[i].magnitude, + kEpsilonFloat); + ++num_returned; + } + } + EXPECT_EQ(1U, num_returned); + + // Reset so that all reflection reflection_coefficients are 0.0f again. + reflection_coefficients[index] = 0.0f; + } +} + +// Tests that no reflections arrive when the listener is outside the room. +TEST(ShoeBoxRoomTest, ReflectionsOutsideRoomTest) { + const WorldPosition kListenerPosition(4.0f, 5.0f, 6.0f); + const WorldPosition kRoomDimensions(1.0f, 2.0f, 3.0f); + const float kReflectionCoefficients[] = {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}; + + std::vector<Reflection> reflections(kNumRoomSurfaces); + ComputeReflections(kListenerPosition, kRoomDimensions, + kReflectionCoefficients, &reflections); + EXPECT_EQ(kNumRoomSurfaces, reflections.size()); + + // Check that all the reflections have zeros. + for (size_t i = 0; i < kNumRoomSurfaces; ++i) { + EXPECT_NEAR(0.0f, reflections[i].delay_time_seconds, kEpsilonFloat); + EXPECT_NEAR(0.0f, reflections[i].magnitude, kEpsilonFloat); + } +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/spectral_reverb.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/spectral_reverb.cc new file mode 100644 index 000000000..e03d34ccd --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/spectral_reverb.cc @@ -0,0 +1,350 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Prevent Visual Studio from complaining about std::copy_n. +#if defined(_WIN32) +#define _SCL_SECURE_NO_WARNINGS +#endif + +#include "dsp/spectral_reverb.h" + +#include <algorithm> +#include <cstdlib> +#include <numeric> + +#include "base/constants_and_types.h" +#include "base/logging.h" +#include "base/simd_utils.h" +#include "dsp/spectral_reverb_constants_and_tables.h" +#include "dsp/utils.h" + +namespace vraudio { + +namespace { + +// FFT length and length of time domain data processed. +const size_t kFftSize = 4096; + +// Length of magnitude and phase buffers. +const size_t kMagnitudeLength = kFftSize / 2 + 1; + +// Number of overlaps per kFftSize time domain chunk of input. +const size_t kNumOverlap = 4; + +// Number of samples per overlap. +const size_t kOverlapLength = kFftSize / kNumOverlap; + +// Three quarters FFT size, used for copying chunks of input. +const size_t kThreeQuarterFftSize = kFftSize - kOverlapLength; + +// Number of channels needed to overlap add output data. +const size_t kNumQuadChannels = 4; + +// Number of buffers of delay applies to the magnitude spectrum. +const size_t kMagnitudeDelay = 3; + +// Length of a buffer of noise used to provide random phase. +const size_t kNoiseLength = 16384; + +// Length of the noise buffer from which a phase buffer can start. +const size_t kAvailableNoiselength = kNoiseLength - kMagnitudeLength; + +// Returns a random integer in the range provided. Used to index into the random +// buffer for phase values. +inline size_t GetRandomIntegerInRange(size_t min, size_t max) { + DCHECK_GE(max, min); + return min + static_cast<size_t>(std::rand()) % (max - min); +} + +} // namespace + +SpectralReverb::SpectralReverb(int sample_rate, size_t frames_per_buffer) + : sample_rate_(sample_rate), + frames_per_buffer_(frames_per_buffer), + magnitude_delay_index_(0), + overlap_add_index_(0), + fft_manager_(kFftSize / 2), + sin_cos_random_phase_buffer_(kNumStereoChannels, kNoiseLength), + unscaled_window_(kNumMonoChannels, kFftSize), + window_(kNumMonoChannels, kFftSize), + feedback_(kNumMonoChannels, kMagnitudeLength), + magnitude_compensation_(kNumMonoChannels, kMagnitudeLength), + magnitude_delay_(kMagnitudeDelay, kMagnitudeLength), + fft_size_input_(kNumMonoChannels, kFftSize), + input_circular_buffer_(kFftSize + frames_per_buffer_, frames_per_buffer_, + kOverlapLength), + output_circular_buffers_(kNumStereoChannels), + out_time_buffer_(kNumQuadChannels, kFftSize), + temp_freq_buffer_(kNumStereoChannels, kFftSize), + scaled_magnitude_buffer_(kNumMonoChannels, kMagnitudeLength), + temp_magnitude_buffer_(kNumMonoChannels, kMagnitudeLength), + temp_phase_buffer_(kNumStereoChannels, kMagnitudeLength), + output_accumulator_(kNumStereoChannels), + is_gain_near_zero_(false), + is_feedback_near_zero_(false) { + DCHECK_GT(sample_rate, 0); + DCHECK_GT(frames_per_buffer_, 0U); + + // Seed std::rand, used for phase selection. + std::srand(1); + GenerateRandomPhaseBuffer(); + GenerateAnalysisWindow(); + InitializeCircularBuffersAndAccumulators(); + fft_size_input_.Clear(); + magnitude_compensation_.Clear(); +} + +void SpectralReverb::SetGain(float gain) { + DCHECK_GE(gain, 0.0f); + ScalarMultiply(window_.num_frames(), gain, &unscaled_window_[0][0], + &window_[0][0]); + // If the gain is less than -60dB we will bypass all processing. + is_gain_near_zero_ = gain <= kNegative60dbInAmplitude; + // If we are to bypass processing we clear the circular buffer so that we + // don't process old input when the spectral reverb is restarted. + if (is_gain_near_zero_ || is_feedback_near_zero_) { + input_circular_buffer_.Clear(); + } +} + +void SpectralReverb::SetRt60PerOctaveBand(const float* rt60_values) { + DCHECK(rt60_values); + const float sample_rate_float = static_cast<float>(sample_rate_); + // Fill the entries in the feedback channel with the feedback values up to + // that frequency. The feedback channels entries are per frequency in the same + // way as the magnitude vectors. Also fill the magnitude compensation vector + // such that the peak value at each frequency for any given reverberation time + // will be |kDefaultReverbGain|. + AudioBuffer::Channel* feedback_channel = &feedback_[0]; + feedback_channel->Clear(); + AudioBuffer::Channel* magnitude_compensation_channel = + &magnitude_compensation_[0]; + magnitude_compensation_channel->Clear(); + const float frequency_step = sample_rate_float / static_cast<float>(kFftSize); + int index = GetFeedbackIndexFromRt60(rt60_values[0], sample_rate_float); + float current_feedback = + index == kInvalidIndex ? 0.0f : kSpectralReverbFeedback[index]; + float magnitude_compensation_value = + index == kInvalidIndex ? 0.0f : kMagnitudeCompensation[index]; + const size_t max_frequency_bin = + std::min(static_cast<size_t>( + (kOctaveBandCentres[kNumReverbOctaveBands - 1] * kSqrtTwo) / + frequency_step), + feedback_channel->size()); + // The upper edge of the octave band is sqrt(2) * centre_frequency : + // https://en.wikipedia.org/wiki/Octave_band#Octave_Bands + float upper_octave_band_edge = kOctaveBandCentres[0] * kSqrtTwo; + for (size_t i = 0, j = 0; i < max_frequency_bin; ++i) { + const float current_freq = static_cast<float>(i) * frequency_step; + if (current_freq > upper_octave_band_edge) { + ++j; + DCHECK_LT(j, kNumReverbOctaveBands); + upper_octave_band_edge = kOctaveBandCentres[j] * kSqrtTwo; + index = GetFeedbackIndexFromRt60(rt60_values[j], sample_rate_float); + current_feedback = + index == kInvalidIndex ? 0.0f : kSpectralReverbFeedback[index]; + magnitude_compensation_value = + index == kInvalidIndex ? 0.0f : kMagnitudeCompensation[index]; + } + (*feedback_channel)[i] = current_feedback; + (*magnitude_compensation_channel)[i] = magnitude_compensation_value; + } + // If the sum of all feedback values is below the minimum feedback value, it + // is safe to assume we can bypass the spectral reverb entirely. + is_feedback_near_zero_ = + std::accumulate(feedback_channel->begin(), feedback_channel->end(), + 0.0f) < kSpectralReverbFeedback[0]; + // If we are to bypass processing we clear the circular buffer so that we + // don't process old input when the spectral reverb is restarted. + if (is_gain_near_zero_ || is_feedback_near_zero_) { + input_circular_buffer_.Clear(); + } +} + +void SpectralReverb::Process(const AudioBuffer::Channel& input, + AudioBuffer::Channel* left_out, + AudioBuffer::Channel* right_out) { + DCHECK_EQ(input.size(), left_out->size()); + DCHECK_EQ(input.size(), right_out->size()); + DCHECK_EQ(input.size(), frames_per_buffer_); + + + if (is_gain_near_zero_ || is_feedback_near_zero_) { + left_out->Clear(); + right_out->Clear(); + return; + } + + // Here we insert |frames_per_buffer_| samples on each function call. Then, + // once there are |kOverlapLength| samples in the input circular buffer we + // retrieve |kOverlapLength| samples from it and process |kFftSize| samples of + // input at a time, sliding along by |kOverlapLength| samples. We then place + // |kOverlapLength| samples into the output buffer and we will extract + // |frames_per_buffer_| samples from the output buffer on each function call. + input_circular_buffer_.InsertBuffer(input); + while (input_circular_buffer_.GetOccupancy() >= kOverlapLength) { + std::copy_n(&fft_size_input_[0][kOverlapLength], kThreeQuarterFftSize, + &fft_size_input_[0][0]); + input_circular_buffer_.RetrieveBufferWithOffset(kThreeQuarterFftSize, + &fft_size_input_[0]); + fft_manager_.FreqFromTimeDomain(fft_size_input_[0], &temp_freq_buffer_[0]); + fft_manager_.GetCanonicalFormatFreqBuffer(temp_freq_buffer_[0], + &temp_freq_buffer_[1]); + fft_manager_.MagnitudeFromCanonicalFreqBuffer(temp_freq_buffer_[1], + &scaled_magnitude_buffer_[0]); + // Apply the magnitude compensation to the input magnitude spectrum before + // feedback is applied. + MultiplyPointwise(kMagnitudeLength, magnitude_compensation_[0].begin(), + scaled_magnitude_buffer_[0].begin(), + scaled_magnitude_buffer_[0].begin()); + // Generate time domain reverb blocks. + GetNextReverbBlock(magnitude_delay_index_, &out_time_buffer_[0], + &out_time_buffer_[1]); + magnitude_delay_index_ = (magnitude_delay_index_ + 1) % kMagnitudeDelay; + GetNextReverbBlock(magnitude_delay_index_, &out_time_buffer_[2], + &out_time_buffer_[3]); + + // Combine the reverb blocks for both left and right output. + AddPointwise(kFftSize, out_time_buffer_[0].begin(), + out_time_buffer_[2].begin(), out_time_buffer_[0].begin()); + AddPointwise(kFftSize, out_time_buffer_[1].begin(), + out_time_buffer_[3].begin(), out_time_buffer_[1].begin()); + + // Window the left and right output (While applying inverse FFT scaling). + MultiplyPointwise(kFftSize, out_time_buffer_[0].begin(), window_[0].begin(), + out_time_buffer_[0].begin()); + MultiplyPointwise(kFftSize, out_time_buffer_[1].begin(), window_[0].begin(), + out_time_buffer_[1].begin()); + + // Next perform the addition and the submission into the output buffer. + AccumulateOverlap(0 /*channel*/, out_time_buffer_[0]); + AccumulateOverlap(1 /*channel*/, out_time_buffer_[1]); + overlap_add_index_ = (overlap_add_index_ + 1) % kNumOverlap; + } + output_circular_buffers_[0]->RetrieveBuffer(left_out); + output_circular_buffers_[1]->RetrieveBuffer(right_out); +} + +void SpectralReverb::AccumulateOverlap(size_t channel_index, + const AudioBuffer::Channel& buffer) { + // Use a modulo indexed multi channel audio buffer with each channel of length + // |kOverlapLength| to perform an overlap add. + for (size_t i = 0, index = overlap_add_index_; i < kNumOverlap; + ++i, index = (index + 1) % kNumOverlap) { + float* accumulator_start_point = + output_accumulator_[channel_index][index].begin(); + AddPointwise(kOverlapLength, buffer.begin() + i * kOverlapLength, + accumulator_start_point, accumulator_start_point); + } + output_circular_buffers_[channel_index]->InsertBuffer( + output_accumulator_[channel_index][overlap_add_index_]); + output_accumulator_[channel_index][overlap_add_index_].Clear(); +} + +void SpectralReverb::GenerateAnalysisWindow() { + // Genarate a pseudo tukey window from three overlapping hann windows, scaled + // by the inverse fft scale. + AudioBuffer::Channel* window_channel = &window_[0]; + // Use the unscaled window buffer as temporary storage. + GenerateHannWindow(true /* full */, kMagnitudeLength, &unscaled_window_[0]); + float* hann_window = &unscaled_window_[0][0]; + // Scale the hann window such that the sum of three will have unity peak. + const float kThreeQuarters = 0.75f; + ScalarMultiply(kMagnitudeLength, kThreeQuarters, hann_window, hann_window); + for (size_t offset = 0; offset < kThreeQuarterFftSize; + offset += kOverlapLength) { + float* tripple_hann_window = &(*window_channel)[offset]; + AddPointwise(kMagnitudeLength, hann_window, tripple_hann_window, + tripple_hann_window); + } + fft_manager_.ApplyReverseFftScaling(window_channel); + unscaled_window_[0] = *window_channel; +} + +void SpectralReverb::GenerateRandomPhaseBuffer() { + AudioBuffer::Channel* sin_phase_channel = &sin_cos_random_phase_buffer_[0]; + AudioBuffer::Channel* cos_phase_channel = &sin_cos_random_phase_buffer_[1]; + // Initially use the sin channel to store the random data before taking sine + // and cosine. + GenerateUniformNoise(/*min=*/0.0f, /*max=*/kPi, /*seed=*/1U, + sin_phase_channel); + for (size_t i = 0; i < sin_cos_random_phase_buffer_.num_frames(); ++i) { + + (*cos_phase_channel)[i] = std::cos((*sin_phase_channel)[i]); + (*sin_phase_channel)[i] = std::sin((*sin_phase_channel)[i]); + } +} + +void SpectralReverb::GetNextReverbBlock(size_t delay_index, + AudioBuffer::Channel* left_channel, + AudioBuffer::Channel* right_channel) { + DCHECK(left_channel); + DCHECK(right_channel); + + // Generate reverb magnitude by combining the delayed magnitude with the + // current magnitude. + AudioBuffer::Channel* temp_magnitude_channel = &temp_magnitude_buffer_[0]; + *temp_magnitude_channel = scaled_magnitude_buffer_[0]; + MultiplyAndAccumulatePointwise( + kMagnitudeLength, magnitude_delay_[delay_index].begin(), + feedback_[0].begin(), temp_magnitude_channel->begin()); + // Reinsert this new reverb magnitude into the delay buffer. + magnitude_delay_[delay_index] = *temp_magnitude_channel; + + for (size_t i = 0; i < kNumStereoChannels; ++i) { + // Extract a random phase buffer. + const size_t random_offset = + GetRandomIntegerInRange(0, kAvailableNoiselength); + // We gaurantee an aligned offset as when SSE is used we need it. + const size_t phase_offset = FindNextAlignedArrayIndex( + random_offset, sizeof(float), kMemoryAlignmentBytes); + // Convert from magnitude and phase to a time domain output. + fft_manager_.CanonicalFreqBufferFromMagnitudeAndSinCosPhase( + phase_offset, (*temp_magnitude_channel), + sin_cos_random_phase_buffer_[0], sin_cos_random_phase_buffer_[1], + &temp_freq_buffer_[0]); + + AudioBuffer::Channel* out_channel = i == 0 ? left_channel : right_channel; + fft_manager_.GetPffftFormatFreqBuffer(temp_freq_buffer_[0], + &temp_freq_buffer_[1]); + fft_manager_.TimeFromFreqDomain(temp_freq_buffer_[1], out_channel); + } +} + +void SpectralReverb::InitializeCircularBuffersAndAccumulators() { + AudioBuffer zeros(kNumMonoChannels, kOverlapLength); + zeros.Clear(); + for (size_t channel = 0; channel < kNumStereoChannels; ++channel) { + // Prefill the |output_circular_buffers_| with |kOverlapLength| / + // |frames_per_buffer_| calls worth of zeros. + output_circular_buffers_[channel].reset( + new CircularBuffer(kOverlapLength + frames_per_buffer_, kOverlapLength, + frames_per_buffer_)); + // Due to differences in the |frames_per_buffer_| used for input and output + // and |kOverlapLength| used for processing, a certain number of buffers of + // zeros must be inserted into the output buffers such that enough input can + // build up to process |kOverlapLength| worth, and enough output will build + // up to return |frames_per_buffer_| worth. + const size_t zeroed_buffers_of_output = kOverlapLength / frames_per_buffer_; + for (size_t i = 0; i < zeroed_buffers_of_output; ++i) { + output_circular_buffers_[channel]->InsertBuffer(zeros[0]); + } + output_accumulator_[channel] = AudioBuffer(kNumOverlap, kOverlapLength); + output_accumulator_[channel].Clear(); + } +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/spectral_reverb.h b/src/3rdparty/resonance-audio/resonance_audio/dsp/spectral_reverb.h new file mode 100644 index 000000000..0734772e0 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/spectral_reverb.h @@ -0,0 +1,173 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_DSP_SPECTRAL_REVERB_H_ +#define RESONANCE_AUDIO_DSP_SPECTRAL_REVERB_H_ + +#include <memory> +#include <vector> + +#include "base/audio_buffer.h" +#include "dsp/circular_buffer.h" +#include "dsp/fft_manager.h" + +namespace vraudio { + +// Implements a spectral reverb producing a decorrelated stereo output. See: +// [1] E. Vickers, J-L Wu, P.G. Krishnan, R. N. K. Sadanandam, "Frequency Domain +// Artificial Reverberation using Spectral Magnitude Decay", +// https://goo.gl/hv1pdJ. +class SpectralReverb { + public: + // Constructs a spectral reverb. + // + // @param sample_rate The system sample rate. + // @param frames_per_buffer System frames per buffer of input and output. + // Note that this class expects power of two buffers of input and output. + SpectralReverb(int sample_rate, size_t frames_per_buffer); + + // Sets the overall gain to be applied to the output of the reverb. + // + // @param gain Gain to be applied to the reverb output, min value 0.0f. + void SetGain(float gain); + + // Sets the |SpectralReverb|'s reverberation times in different frequency + // bands. Supports times between: + // (0.15 * 48000 / |sample_rate|)s and (25 * 48000 / |sample_rate|)s. + // + // @param rt60_values |kNumReverbOctaveBands| values denoting the + // reverberation decay time to -60dB in octave bands starting at + // |kLowestOctaveBand|. + void SetRt60PerOctaveBand(const float* rt60_values); + + // Applies reverb to an input channel of audio data and produces a stereo + // output. + // + // @param input Mono inpu data. + // @param left_out Left channel of reverberated output. + // @param right_out Right channel of reverberated output. + void Process(const AudioBuffer::Channel& input, + AudioBuffer::Channel* left_out, AudioBuffer::Channel* right_out); + + private: + // Uses an AudioBuffer with four channels to overlap add and insert the final + // reverb into the output circular buffers. + // + // @param channel_index Denotes the (left or right) channel to output to. + // @param buffer The buffer to be added onto the pre-existing reverb output. + void AccumulateOverlap(size_t channel_index, + const AudioBuffer::Channel& buffer); + + // Generates a window function which is a normalized sum of three overlapping + // (50%) hann windows of length (|kFftSize| / 2) that also incorporates the + // inverse fft scaling. + void GenerateAnalysisWindow(); + + // Generates a large buffer of sines and cosines of random noise between 0 and + // pi to be randomly indexed into in order to cheaply generate highly + // decorrelated phase buffers, + void GenerateRandomPhaseBuffer(); + + // Obtains the next stero pair of time domain reverb blocks which can then be + // summed together in an overlap add fashion to provide the reverb output. + // + // @param delay_index An index into the frequency domain magnitude ring + // buffer. + // @param left_channel Channel to contain the left partial reverb output. + // @param right_channel Channel to contain the right partial reverb output. + void GetNextReverbBlock(size_t delay_index, + AudioBuffer::Channel* left_channel, + AudioBuffer::Channel* right_channel); + + // Initializes the output circular buffers such that they contain zeros if the + // value of |frames_per_buffer_| is sufficiently smaller than that of + // |kOverlapLength| that buffering of input will be required prior to + // processing. Also allocates memory for the output accumulators. + void InitializeCircularBuffersAndAccumulators(); + + // System sample rate. + const int sample_rate_; + + // System frames per buffer. + const size_t frames_per_buffer_; + + // Indices into the magnitude and overlap add delay lines, modulo of their + // respective lengths. + size_t magnitude_delay_index_; + size_t overlap_add_index_; + + // Manages the time-frequency transforms and phase/magnitude-frequency + // transforms. + FftManager fft_manager_; + + // Buffer containing sines and cosines of random values between 0 and pi to be + // used for phase. + AudioBuffer sin_cos_random_phase_buffer_; + + // Buffer containing a triple overlapping hann window for windowing time + // domain data. + AudioBuffer unscaled_window_; + + // Buffer containing a triple overlapping hann window for windowing time + // domain data, this window has been scaled by the output gain factor. + AudioBuffer window_; + + // Buffer containing RT60 tuned feedback values. + AudioBuffer feedback_; + + // Buffer used to store scaling values which account for the different initial + // peak magnitudes for different RT60s. + AudioBuffer magnitude_compensation_; + + // Buffer that acts as the frequency domain magnitde delay. + AudioBuffer magnitude_delay_; + + // Buffer to contain a linear |kFftSize| chunk of input data. + AudioBuffer fft_size_input_; + + // Circular buffers to sit at the input and output of the |Process()| method + // to allow |frames_per_buffer_| to differ from |kFftSize|. + CircularBuffer input_circular_buffer_; + std::vector<std::unique_ptr<CircularBuffer>> output_circular_buffers_; + + // Time domain buffer used to store reverb before the overlap add operation. + AudioBuffer out_time_buffer_; + + // Temporary frequency domain buffer, used to store frequency domain data when + // transforming between Pffft and Canonical format frequency domain data. + AudioBuffer temp_freq_buffer_; + + // Buffer used to store feedback scaled magnitude values. + AudioBuffer scaled_magnitude_buffer_; + + // Buffer used for the accumulation of scaled magnitude buffers. + AudioBuffer temp_magnitude_buffer_; + + // Buffer used to store randomized phase. + AudioBuffer temp_phase_buffer_; + + // Buffers used to calculate the overlap add at the output. + std::vector<AudioBuffer> output_accumulator_; + + // Processing of the spectral reverb is bypassed when the feedback values are + // all approximately zero OR when the gain is set to near zero. + bool is_gain_near_zero_; + bool is_feedback_near_zero_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_DSP_SPECTRAL_REVERB_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/spectral_reverb_constants_and_tables.h b/src/3rdparty/resonance-audio/resonance_audio/dsp/spectral_reverb_constants_and_tables.h new file mode 100644 index 000000000..84329f329 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/spectral_reverb_constants_and_tables.h @@ -0,0 +1,6508 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_DSP_SPECTRAL_REVERB_CONSTANTS_AND_TABLES_H_ +#define RESONANCE_AUDIO_DSP_SPECTRAL_REVERB_CONSTANTS_AND_TABLES_H_ + +#include <algorithm> +#include <cstddef> + +namespace vraudio { + +// Sampling rate for which the spectral reverb feedback coefficients are +// calculated. +static const float kDefaultSpectralReverbSampleRate = 48000.0f; + +// Minimum reverb time supported by the feedback table below (seconds). +static const float kMinReverbTimeForFeedback48kHz = 0.15f; + +// Step size between each reverberation time supported by the feedback table +// (seconds). +static const float kTimeStepSizeForFeedback48kHz = 0.01f; + +// Number of entries in the feedback table below. +static const size_t kNumFeedbackValues = 2485; + +// Value denoting an invalid index into the feedback and magnitude correction +// tables. The feedback and magnitude correction factors will be 0.0 when this +// value is returned from the index lookup method. +const int kInvalidIndex = -1; + +// Number of frames of zeros before the reverb onset curve. +const size_t kCompensationOnsetLength = 1024; + +// Length in samples of the spectral reverb onset correction curves. +const size_t kCorrectionCurveLength = 6144; + +// Index of the feedback factor and curve multipliers. below which the low +// frequency correction curves must be used. +const size_t kCurveChangeoverIndex = 51; + +// Offset and scaling values for the compensation curve polynomials. Used to +// ensure the polynomial fitting was well conditioned. +const float kCurveOffset = 3584.5f; +const float kCurveScale = 0.00027897893709025f; + +// Number of coefficients in the curve generation polynomials. +const size_t kCurvePolynomialLength = 5; + +// Coefficients of the curves making up the first part of the compensation +// envelopes, split into low and high frequency part. +const float kHighReverberationCorrectionCurve[kCurvePolynomialLength] = { + 0.207891278205479f, -0.32101050261694f, 0.124608132159297f, + 0.0119620847734548f, -0.0093840571415877f}; + +const float kLowReverberationCorrectionCurve[kCurvePolynomialLength] = { + 0.0277040197264401f, -0.0394421854392145f, 0.0519110103856154f, + -0.0778183422829366f, 0.0343221444553963f}; + +// Offset from zero for indexing into the feedback array. +const size_t kReverbLookupOffset = static_cast<size_t>( + kMinReverbTimeForFeedback48kHz / kTimeStepSizeForFeedback48kHz); + +// Returns an index to the feedback factor corresponding to the given +// reverberation time. Supports times between: +// (0.15 * 48000 / |sample_rate|)s and (25 * 48000 / |sample_rate|)s. +// +// @param reverberation_time RT60 in seconds. +// @return Index to the closest spectral reverb feedback factor in +// |kSpectralReverbFeedback|, or |kInvalidIndex| if |reverberation_time| +// is too small. +static inline int GetFeedbackIndexFromRt60(float reverberation_time, + float sample_rate) { + // Scaling factor on reverb times applied due to current |sample_rate|. All + // feedback factors calculated correspond to 48kHz. + const float scale_factor = sample_rate / kDefaultSpectralReverbSampleRate; + if (reverberation_time * scale_factor < kMinReverbTimeForFeedback48kHz) { + return kInvalidIndex; + } + return static_cast<int>( + std::min(kNumFeedbackValues - 1, + static_cast<size_t>((reverberation_time * scale_factor) / + kTimeStepSizeForFeedback48kHz) - + kReverbLookupOffset)); +} + +// Mapping between reverberation times at a given frequency and the +// corresponding feedback value required in the specral reverb. This table +// covers reverberation times between kMinReverbTimeForFeedback and +// kNumFeedbackValues * kTimeStepSizeForFeedback, i.e. 0.15s and 25s @48kHz. +static const float kSpectralReverbFeedback[kNumFeedbackValues] = { + 0.231626448720861f, 0.231626448720861f, 0.271965742827169f, + 0.291599040391330f, 0.313450848869688f, 0.332864905289138f, + 0.350285532738638f, 0.366049684043223f, 0.383143567924904f, + 0.398574170268545f, 0.427500186399158f, 0.441038795823222f, + 0.453430889363735f, 0.466397294153677f, 0.478272702354267f, + 0.490519904155818f, 0.501757394995813f, 0.512125955040727f, + 0.521739757418252f, 0.548135448526531f, 0.548135448526531f, + 0.563990087718933f, 0.571497329505391f, 0.578784680803617f, + 0.585892965998708f, 0.592858971592466f, 0.599714945736390f, + 0.606488097764666f, 0.613200097727167f, 0.619866575922435f, + 0.626496622430680f, 0.633092286646765f, 0.639648076813205f, + 0.646150459553150f, 0.652577359403386f, 0.658897658347319f, + 0.665070695347976f, 0.671045765880981f, 0.676761621467567f, + 0.682145969207546f, 0.682145969207546f, 0.687114971312322f, + 0.691572744637866f, 0.695410860217716f, 0.700728670360266f, + 0.701931035813262f, 0.700570291691920f, 0.718477641957808f, + 0.718477641957808f, 0.721948612036913f, 0.725341703212013f, + 0.728658944837357f, 0.731902322614612f, 0.735073779107144f, + 0.738175214254303f, 0.741208485885712f, 0.744175410235544f, + 0.747077762456819f, 0.749917277135678f, 0.752695648805674f, + 0.755414532462056f, 0.758075544076055f, 0.760680261109167f, + 0.763230223027440f, 0.765726931815758f, 0.768171852492127f, + 0.770566413621959f, 0.772912007832361f, 0.775209992326414f, + 0.777461689397460f, 0.779668386943394f, 0.781831338980938f, + 0.783951766159935f, 0.786030856277629f, 0.788069764792952f, + 0.790069615340812f, 0.792031500246373f, 0.793956481039341f, + 0.795845588968255f, 0.797699825514762f, 0.799520162907914f, + 0.801307544638442f, 0.803062885973050f, 0.804787074468694f, + 0.806480970486870f, 0.808145407707900f, 0.809781193645214f, + 0.811389110159637f, 0.812969913973676f, 0.814524337185802f, + 0.816053087784737f, 0.817556850163737f, 0.819036285634880f, + 0.820492032943351f, 0.821924708781723f, 0.823334908304248f, + 0.824723205641138f, 0.826090154412852f, 0.827436288244380f, + 0.828762121279528f, 0.830068148695207f, 0.831354847215712f, + 0.833872075291032f, 0.835103470659940f, 0.836317269790432f, + 0.837513864858018f, 0.838693632671302f, 0.841004120020598f, + 0.841004120020598f, 0.842135520967878f, 0.843251458511968f, + 0.844352240341239f, 0.846509506717154f, 0.847566547291731f, + 0.848609545235926f, 0.849638751975006f, 0.850654409224479f, + 0.851656749504368f, 0.852645996653501f, 0.853622366343797f, + 0.854586066594550f, 0.855537298286716f, 0.856476255677194f, + 0.857403126913115f, 0.858318094546124f, 0.859221336046671f, + 0.860113024318288f, 0.860993328211881f, 0.861862413040010f, + 0.862720441091179f, 0.863567572144118f, 0.864403963982066f, + 0.865229772907064f, 0.866045154254231f, 0.866850262906056f, + 0.867645253806680f, 0.868430282476178f, 0.869205505524854f, + 0.869971081167516f, 0.870727169737766f, 0.871473934202282f, + 0.872211540675109f, 0.872940158931938f, 0.873659962924396f, + 0.874371131294327f, 0.875073847888079f, 0.876454690240667f, + 0.877133214343288f, 0.877804084385866f, 0.878467517951544f, + 0.879123740913686f, 0.879772987950149f, 0.880415503057582f, + 0.881051540065697f, 0.881681363151570f, 0.882305247353907f, + 0.882923479087349f, 0.883536356656744f, 0.884144190771436f, + 0.884747305059551f, 0.885346036582283f, 0.885940736348172f, + 0.886531769827401f, 0.887119517466070f, 0.887704375200491f, + 0.888286754971462f, 0.888867085238566f, 0.889355868225441f, + 0.889898852584961f, 0.890436987253855f, 0.890970321870216f, + 0.891498905661491f, 0.892022787446309f, 0.892542015636295f, + 0.893056638237894f, 0.893566702854191f, 0.894072256686731f, + 0.894573346537340f, 0.895070018809945f, 0.895562319512397f, + 0.896533988268773f, 0.897013446374392f, 0.897488713016890f, + 0.897959832251035f, 0.898426847746442f, 0.898889802789393f, + 0.899348740284655f, 0.899803702757303f, 0.900254732354541f, + 0.900701870847520f, 0.901145159633161f, 0.901584639735976f, + 0.902020351809884f, 0.902452336140039f, 0.902880632644642f, + 0.903305280876771f, 0.903726320026192f, 0.904143788921187f, + 0.904557726030372f, 0.904968169464516f, 0.905375156978364f, + 0.905778725972456f, 0.906178913494948f, 0.906575756243433f, + 0.906969290566763f, 0.907359552466865f, 0.907746577600566f, + 0.908130401281413f, 0.908511058481491f, 0.908888583833246f, + 0.909263011631306f, 0.909634375834297f, 0.910002710066671f, + 0.910368047620521f, 0.910730421457403f, 0.911446408184725f, + 0.911800085361980f, 0.912150927399534f, 0.912498965633569f, + 0.912844231080653f, 0.913186754439560f, 0.913526566093093f, + 0.913863696109903f, 0.914198174246311f, 0.914530029948127f, + 0.914859292352470f, 0.915185990289593f, 0.915510152284696f, + 0.915831806559755f, 0.916150981035335f, 0.916467703332418f, + 0.916782000774218f, 0.917093900388001f, 0.917403428906911f, + 0.917710612771787f, 0.918015478132983f, 0.918318050852190f, + 0.918916420379010f, 0.919212267483073f, 0.919505922541691f, + 0.919797410000546f, 0.920086754027583f, 0.920373978514824f, + 0.920659107080196f, 0.920942163069346f, 0.921223169557465f, + 0.921502149351104f, 0.921779124990001f, 0.922054118748897f, + 0.922327152639358f, 0.922598248411594f, 0.922867427556284f, + 0.923134711306391f, 0.923400120638986f, 0.923663676277069f, + 0.923925398691385f, 0.924185308102252f, 0.924443424481376f, + 0.924699767553671f, 0.924954356799086f, 0.925207211454416f, + 0.925458350515133f, 0.925707792737198f, 0.925955556638886f, + 0.926201660502607f, 0.926446122376723f, 0.926688960077372f, + 0.926930191190288f, 0.927169833072619f, 0.927407902854752f, + 0.927644417442129f, 0.927879393517071f, 0.928112847540597f, + 0.928344795754244f, 0.928575254181890f, 0.928804238631573f, + 0.929031764697310f, 0.929257847760921f, 0.929705745358969f, + 0.929927589612438f, 0.930148050305480f, 0.930367141786232f, + 0.930584878201552f, 0.930801273498844f, 0.931016341427878f, + 0.931230095542612f, 0.931442549203009f, 0.931653715576860f, + 0.931863607641606f, 0.932072238186154f, 0.932279619812703f, + 0.932485764938561f, 0.932690685797968f, 0.932894394443911f, + 0.933096902749953f, 0.933298222412048f, 0.933498364950364f, + 0.933697341711099f, 0.933895163868310f, 0.934091842425725f, + 0.934287388218568f, 0.934481811915380f, 0.934675124019838f, + 0.934867334872576f, 0.935058454653005f, 0.935437460919395f, + 0.935625366974452f, 0.935812221099035f, 0.935998032693751f, + 0.936182811008910f, 0.936182811008910f, 0.936366565146343f, + 0.936731036563891f, 0.936911771321662f, 0.937091516860660f, + 0.937270281567636f, 0.937448073691783f, 0.937624901346560f, + 0.937800772511514f, 0.937975695034098f, 0.938149676631492f, + 0.938322724892425f, 0.938494847278995f, 0.938666051128488f, + 0.938836343655203f, 0.939005731952265f, 0.939174222993452f, + 0.939341823635016f, 0.939508540617498f, 0.939674380567555f, + 0.939839349999772f, 0.940003455318495f, 0.940166702819640f, + 0.940329098692519f, 0.940490649021660f, 0.940651359788628f, + 0.940811236873844f, 0.940970286058407f, 0.941128513025914f, + 0.941285923364282f, 0.941442522567564f, 0.941598316037775f, + 0.941753309086713f, 0.941907506937771f, 0.942060914727768f, + 0.942213537508766f, 0.942365380249885f, 0.942516447839133f, + 0.942666745085221f, 0.942816276719382f, 0.942965047397198f, + 0.943113061700413f, 0.943260324138759f, 0.943406839151775f, + 0.943552611110629f, 0.943697644319934f, 0.943841943019572f, + 0.944128353536651f, 0.944270473526585f, 0.944411875355484f, + 0.944552562966882f, 0.944692540250507f, 0.944831811044098f, + 0.944970379135229f, 0.945108248263126f, 0.945245422120492f, + 0.945381904355324f, 0.945517698572732f, 0.945652808336767f, + 0.945787237172233f, 0.945920988566514f, 0.946054065971390f, + 0.946186472804861f, 0.946318212452965f, 0.946449288271601f, + 0.946579703588348f, 0.946709461704285f, 0.946838565895813f, + 0.946967019416476f, 0.947094825498780f, 0.947221987356014f, + 0.947348508184070f, 0.947474391163268f, 0.947599639460168f, + 0.947724256229401f, 0.947848244615479f, 0.947971607754624f, + 0.948016355753743f, 0.948131749028067f, 0.948256528604491f, + 0.948381070023128f, 0.948495820690419f, 0.948619906515573f, + 0.948734238085242f, 0.948857871450992f, 0.948971786801436f, + 0.949094970810335f, 0.949208472791043f, 0.949321777358027f, + 0.949444301946475f, 0.949557197308729f, 0.949669897241678f, + 0.949782402382633f, 0.949894713365863f, 0.950016165228152f, + 0.950128073740068f, 0.950239790030961f, 0.950351314722867f, + 0.950462648434873f, 0.950573791783135f, 0.950684745380899f, + 0.950786286676309f, 0.950896878289179f, 0.951007281923720f, + 0.951117498181945f, 0.951227527663044f, 0.951328224451603f, + 0.951437897607799f, 0.951547385718536f, 0.951647587765244f, + 0.951756722847544f, 0.952281634952469f, 0.952380709437317f, + 0.952479632641358f, 0.952587376840327f, 0.952685985134598f, + 0.952784443487780f, 0.952891682098038f, 0.952989828324488f, + 0.953087825932233f, 0.953185675345440f, 0.953283376986503f, + 0.953380931276052f, 0.953478338632963f, 0.953584434118178f, + 0.953681535598181f, 0.953778491429264f, 0.953875302023227f, + 0.953963185967004f, 0.954059720427442f, 0.954156110839052f, + 0.954252357607017f, 0.954348461134851f, 0.954444421824404f, + 0.954627224309327f, 0.954627224309327f, 0.954722771738916f, + 0.954818177885333f, 0.954999925992284f, 0.955086293022387f, + 0.955181163332660f, 0.955267288091851f, 0.955361892609296f, + 0.955447776377266f, 0.955542116504091f, 0.955627760550641f, + 0.955721837678226f, 0.955807243263361f, 0.955892535735487f, + 0.955986227146404f, 0.956071282999438f, 0.956156226611782f, + 0.956241058262726f, 0.956334244095430f, 0.956418841531991f, + 0.956503327866496f, 0.956587703374087f, 0.956671968328910f, + 0.956756123004115f, 0.956840167671866f, 0.956924102603339f, + 0.957007928068733f, 0.957091644337270f, 0.957175251677203f, + 0.957258750355817f, 0.957342140639439f, 0.957425422793436f, + 0.957508597082225f, 0.957591663769275f, 0.957666332005129f, + 0.957749194971355f, 0.957831951094582f, 0.957914600634626f, + 0.957988894306034f, 0.958071342050505f, 0.958153683960035f, + 0.958227701401528f, 0.958309842929826f, 0.958383680442363f, + 0.958465622508191f, 0.958539280915875f, 0.958621024431827f, + 0.958694504553236f, 0.958776050425793f, 0.958849353074026f, + 0.958930702203609f, 0.959003828186335f, 0.959076870765751f, + 0.959157931586863f, 0.959230798681224f, 0.959303582928693f, + 0.959384357364767f, 0.959456967296096f, 0.959529494931521f, + 0.959601940447006f, 0.959674304017958f, 0.959746585819239f, + 0.959826803241069f, 0.959898912989265f, 0.959970941508314f, + 0.960042888970841f, 0.960114755548935f, 0.960186541414150f, + 0.960258246737504f, 0.960329871689484f, 0.960401416440053f, + 0.960472881158639f, 0.960544266014151f, 0.960607652309644f, + 0.960678886771683f, 0.960750041855675f, 0.960821117728505f, + 0.960892114556548f, 0.960955156623649f, 0.961026004596989f, + 0.961096774003302f, 0.961167465006980f, 0.961230235767696f, + 0.961300779124251f, 0.961433815192630f, 0.961433815192630f, + 0.961504133859284f, 0.961566574305389f, 0.961636746788933f, + 0.961699057549504f, 0.961769084422862f, 0.961901147838506f, + 0.961963200748498f, 0.961963200748498f, 0.962032938107125f, + 0.962094862846269f, 0.962164456293847f, 0.962226253361271f, + 0.962287990519784f, 0.962357373349702f, 0.962418983565341f, + 0.962480534213617f, 0.962611132054796f, 0.962672497254291f, + 0.962733803332908f, 0.962795050398145f, 0.962863882940590f, + 0.962925004958022f, 0.962986068296167f, 0.963047073061348f, + 0.963108019359607f, 0.963168907296701f, 0.963229736978111f, + 0.963290508509035f, 0.963351221994394f, 0.963411877538831f, + 0.963472475246711f, 0.963533015222125f, 0.963593497568891f, + 0.963653922390548f, 0.963714289790367f, 0.963774599871346f, + 0.963834852736211f, 0.963895048487418f, 0.963947672999015f, + 0.964007761937330f, 0.964067794055055f, 0.964127769453608f, + 0.964187688234142f, 0.964240070801961f, 0.964299883695432f, + 0.964359640260232f, 0.964471532344270f, 0.964531127521006f, + 0.964583227407251f, 0.964642717773864f, 0.964642717773864f, + 0.964702152384023f, 0.964813442340238f, 0.964865310872176f, + 0.964924537272103f, 0.964976315013494f, 0.965035437824303f, + 0.965035437824303f, 0.965146144638393f, 0.965197741696581f, + 0.965256658352829f, 0.965308165515470f, 0.965366979602982f, + 0.965418397165533f, 0.965469773015420f, 0.965528437277655f, + 0.965528437277655f, 0.965630969126429f, 0.965689484282883f, + 0.965740640709502f, 0.965791755822677f, 0.965842829685402f, + 0.965901149384097f, 0.965952135064471f, 0.966003079691463f, + 0.966053983327487f, 0.966104846034818f, 0.966155667875590f, + 0.966213700018171f, 0.966264434496339f, 0.966315128302334f, + 0.966365781497721f, 0.966416394143929f, 0.966466966302251f, + 0.966517498033843f, 0.966567989399724f, 0.966618440460782f, + 0.966668851277767f, 0.966719221911296f, 0.966769552421851f, + 0.966819842869783f, 0.966870093315306f, 0.966920303818505f, + 0.966970474439330f, 0.967020605237600f, 0.967063542843005f, + 0.967113599843356f, 0.967163617191341f, 0.967213594946271f, + 0.967263533167330f, 0.967306305935844f, 0.967356170893433f, + 0.967405996485532f, 0.967505529807874f, 0.967548138931234f, + 0.967597813233595f, 0.967647448454308f, 0.967689961867422f, + 0.967739524661887f, 0.967789048540268f, 0.967831466648599f, + 0.967880918407252f, 0.967923274783712f, 0.967972654620856f, + 0.968021995812854f, 0.968064257548057f, 0.968113527121939f, + 0.968155727554356f, 0.968204925706321f, 0.968247065003786f, + 0.968296191929245f, 0.968338270258917f, 0.968387326152496f, + 0.968429343680863f, 0.968478328736411f, 0.968520285629296f, + 0.968562214592319f, 0.968611096462447f, 0.968652965031151f, + 0.968701776537117f, 0.968743584875698f, 0.968785365470961f, + 0.968834074480683f, 0.968875795084627f, 0.968917488055148f, + 0.968959153426817f, 0.969007728191248f, 0.969090912086360f, + 0.969132462833532f, 0.969180904050232f, 0.969222395427913f, + 0.969263859458335f, 0.969305296175547f, 0.969346705613531f, + 0.969394982191855f, 0.969436332641141f, 0.969477655918411f, + 0.969518952057392f, 0.969560221091750f, 0.969601463055090f, + 0.969642677980956f, 0.969683865902829f, 0.969725026854133f, + 0.969766160868228f, 0.969807267978414f, 0.969848348217932f, + 0.969889401619961f, 0.969930428217621f, 0.969971428043973f, + 0.970012401132016f, 0.970053347514690f, 0.970094267224876f, + 0.970135160295395f, 0.970176026759010f, 0.970216866648424f, + 0.970257679996279f, 0.970298466835163f, 0.970339227197601f, + 0.970373173964662f, 0.970413885871233f, 0.970454571393199f, + 0.970495230562863f, 0.970535863412469f, 0.970650847168530f, + 0.970691379422253f, 0.970731885510953f, 0.970765620620724f, + 0.970806078823052f, 0.970846510950618f, 0.970886917035171f, + 0.970920568901195f, 0.970960927322491f, 0.971001259790416f, + 0.971034850377613f, 0.971075135346213f, 0.971115394450634f, + 0.971148923969595f, 0.971189135737796f, 0.971222625853735f, + 0.971262790390816f, 0.971302929209645f, 0.971336358601896f, + 0.971376450351361f, 0.971409840562608f, 0.971449885346942f, + 0.971483236463907f, 0.971523234387005f, 0.971556546496124f, + 0.971596497661541f, 0.971629770848972f, 0.971669675359932f, + 0.971702909711553f, 0.971742767670943f, 0.971775963272355f, + 0.971809141288259f, 0.971848931719255f, 0.971921815239525f, + 0.971954916087967f, 0.971987999445650f, 0.972027676411655f, + 0.972060721351105f, 0.972093748855591f, 0.972133358871293f, + 0.972166348080007f, 0.972199319909290f, 0.972238863187940f, + 0.972271796843339f, 0.972304713174582f, 0.972344189928433f, + 0.972377068207112f, 0.972409929216652f, 0.972442772974193f, + 0.972482162734699f, 0.972514968598050f, 0.972547757264083f, + 0.972580528749833f, 0.972613283072310f, 0.972646020248501f, + 0.972685282250713f, 0.972717981764744f, 0.972750664186678f, + 0.972783329533400f, 0.972815977821766f, 0.972848609068610f, + 0.972881223290743f, 0.972920337908315f, 0.972952914735131f, + 0.972985474590863f, 0.973018017492218f, 0.973050543455880f, + 0.973083052498507f, 0.973115544636735f, 0.973148019887177f, + 0.973180478266418f, 0.973212919791023f, 0.973245344477530f, + 0.973277752342457f, 0.973310143402295f, 0.973342517673512f, + 0.973374875172553f, 0.973407215915839f, 0.973439539919767f, + 0.973471847200711f, 0.973504137775022f, 0.973562218760164f, + 0.973594462642685f, 0.973626689880490f, 0.973658900489817f, + 0.973691094486881f, 0.973723271887874f, 0.973755432708964f, + 0.973787576966297f, 0.973813280457093f, 0.973845394940276f, + 0.973877492904786f, 0.973909574366681f, 0.973941639341995f, + 0.973967279462671f, 0.973999314802478f, 0.974031333700481f, + 0.974063336172629f, 0.974088926334435f, 0.974120899280163f, + 0.974152855844570f, 0.974184796043519f, 0.974210336430194f, + 0.974242247211220f, 0.974274141671084f, 0.974299645498340f, + 0.974331510619852f, 0.974363359464321f, 0.974388826830985f, + 0.974420646416359f, 0.974452449768633f, 0.974477880773140f, + 0.974509654945262f, 0.974541412928054f, 0.974566807668448f, + 0.974598536549719f, 0.974630249285257f, 0.974655607859195f, + 0.974687291571531f, 0.974712626946994f, 0.974744281686313f, + 0.974769593903399f, 0.974801219719758f, 0.974826508818462f, + 0.974858105761792f, 0.974889686686078f, 0.974914939902112f, + 0.974946492030788f, 0.974971722230190f, 0.975003245612865f, + 0.975028452835272f, 0.975053649858377f, 0.975085131806375f, + 0.975110305909561f, 0.975141759232295f, 0.975166910454954f, + 0.975198335201602f, 0.975223463583028f, 0.975254859802644f, + 0.975279965382029f, 0.975305060839729f, 0.975336415939985f, + 0.975361488652084f, 0.975392815344700f, 0.975417865350151f, + 0.975442905276238f, 0.975474191021417f, 0.975499208296949f, + 0.975524215518001f, 0.975555460416840f, 0.975605419639930f, + 0.975605419639930f, 0.975636623793157f, 0.975661575850883f, + 0.975686517903629f, 0.975711449958978f, 0.975742600980867f, + 0.975767510569758f, 0.975792410185843f, 0.975823520693185f, + 0.975848397898037f, 0.975898122470337f, 0.975922969852819f, + 0.975922969852819f, 0.975954015123685f, 0.976003655334843f, + 0.976028460584524f, 0.976053255940233f, 0.976084236232837f, + 0.976109009354376f, 0.976133772606159f, 0.976158525995621f, + 0.976183269530186f, 0.976208003217274f, 0.976208003217274f, + 0.976238906489402f, 0.976263618046739f, 0.976288319780646f, + 0.976313011698507f, 0.976362366115577f, 0.976387028629510f, + 0.976411681356846f, 0.976436324304925f, 0.976467114249117f, + 0.976467114249117f, 0.976491735220672f, 0.976516346436775f, + 0.976565539631852f, 0.976590121625417f, 0.976614693892712f, + 0.976639256441014f, 0.976663809277591f, 0.976688352409702f, + 0.976712885844598f, 0.976737409589524f, 0.976761923651714f, + 0.976761923651714f, 0.976786428038397f, 0.976810922756793f, + 0.976835407814114f, 0.976859883217562f, 0.976884348974336f, + 0.976908805091622f, 0.976933251576602f, 0.976957688436448f, + 0.977098013951529f, 0.977122386028731f, 0.977146748536122f, + 0.977171101480806f, 0.977195444869879f, 0.977213696145079f, + 0.977238022828887f, 0.977262339976560f, 0.977286647595167f, + 0.977310945691766f, 0.977335234273410f, 0.977353444469693f, + 0.977377716417114f, 0.977401978868934f, 0.977426231832178f, + 0.977450475313860f, 0.977474709320991f, 0.977492878612876f, + 0.977517096056381f, 0.977541304044568f, 0.977565502584416f, + 0.977583645293024f, 0.977607827315053f, 0.977631999907899f, + 0.977631999907899f, 0.977674279277313f, 0.977698425975728f, + 0.977722563270990f, 0.977722563270990f, 0.977764780932182f, + 0.977788892404450f, 0.977812994499489f, 0.977831064921111f, + 0.977855150622588f, 0.977879226965781f, 0.977897278085939f, + 0.977921338068673f, 0.977945388712013f, 0.977963420569662f, + 0.977987454885549f, 0.978011479880872f, 0.978029492514857f, + 0.978053501215636f, 0.978077500614628f, 0.978095494063675f, + 0.978119477200933f, 0.978137458461494f, 0.978161425357848f, + 0.978185382983022f, 0.978203345121770f, 0.978227286538691f, + 0.978245236529027f, 0.978269161758424f, 0.978293077747100f, + 0.978311008678442f, 0.978334908512089f, 0.978352827334900f, + 0.978376711034146f, 0.978394617763882f, 0.978418485349318f, + 0.978436380001407f, 0.978460231493585f, 0.978478114083426f, + 0.978501949502858f, 0.978519820045822f, 0.978543639412983f, + 0.978561497924412f, 0.978585301259738f, 0.978603147754944f, + 0.978626935078835f, 0.978644769573101f, 0.978668540905916f, + 0.978686363414501f, 0.978710118776561f, 0.978727929314691f, + 0.978751668726282f, 0.978769467309155f, 0.978793190790523f, + 0.978810977433311f, 0.978828758963556f, 0.978852459722509f, + 0.978870229334292f, 0.978893914212034f, 0.978911671920457f, + 0.978929424532057f, 0.978953086757244f, 0.978970827487009f, + 0.978994473879778f, 0.979012202742743f, 0.979029926524532f, + 0.979053550334292f, 0.979071262270710f, 0.979088969135123f, + 0.979112570402636f, 0.979130265443050f, 0.979147955420607f, + 0.979171534186528f, 0.979189212361401f, 0.979206885482540f, + 0.979230441787417f, 0.979248103127132f, 0.979265759422211f, + 0.979289293306483f, 0.979306937841341f, 0.979324577340638f, + 0.979348088844638f, 0.979365716604861f, 0.979383339338575f, + 0.979400957048490f, 0.979424439518260f, 0.979442045516508f, + 0.979459646499977f, 0.979477242471370f, 0.979500695974660f, + 0.979518280261699f, 0.979535859545650f, 0.979553433829205f, + 0.979576858433533f, 0.979594421059955f, 0.979611978694939f, + 0.979629531341168f, 0.979647079001324f, 0.979670468130047f, + 0.979688004166443f, 0.979723061310450f, 0.979740582423400f, + 0.979763936178014f, 0.979763936178014f, 0.979781445700440f, + 0.979816449859165f, 0.979833944500784f, 0.979851434187449f, + 0.979874746066551f, 0.979874746066551f, 0.979909697391286f, + 0.979927165637130f, 0.979944628942151f, 0.979962087308993f, + 0.979979540740298f, 0.979996989238702f, 0.980020246234401f, + 0.980037683232959f, 0.980055115307399f, 0.980072542460351f, + 0.980089964694443f, 0.980107382012301f, 0.980124794416549f, + 0.980142201909809f, 0.980159604494699f, 0.980177002173838f, + 0.980194394949842f, 0.980211782825323f, 0.980229165802893f, + 0.980252335491858f, 0.980269707051114f, 0.980287073721149f, + 0.980304435504567f, 0.980321792403967f, 0.980339144421948f, + 0.980356491561107f, 0.980373833824038f, 0.980391171213333f, + 0.980408503731583f, 0.980425831381378f, 0.980443154165302f, + 0.980460472085940f, 0.980477785145876f, 0.980495093347688f, + 0.980512396693956f, 0.980529695187255f, 0.980546988830161f, + 0.980564277625245f, 0.980581561575078f, 0.980598840682228f, + 0.980616114949261f, 0.980627628439596f, 0.980644894645466f, + 0.980662156018053f, 0.980679412559917f, 0.980696664273614f, + 0.980713911161699f, 0.980731153226725f, 0.980748390471244f, + 0.980765622897805f, 0.980782850508954f, 0.980800073307237f, + 0.980817291295196f, 0.980845977259090f, 0.980863182431940f, + 0.980880382803776f, 0.980897578377130f, 0.980914769154535f, + 0.980931955138521f, 0.980931955138521f, 0.980949136331616f, + 0.980966312736347f, 0.980994929443608f, 0.981012093091701f, + 0.981012093091701f, 0.981029251960675f, 0.981046406053047f, + 0.981063555371332f, 0.981109263565884f, 0.981126395400670f, + 0.981126395400670f, 0.981143522473080f, 0.981172057017508f, + 0.981189171402487f, 0.981206281034263f, 0.981223385915333f, + 0.981240486048191f, 0.981251883500099f, 0.981268975724811f, + 0.981286063207956f, 0.981303145952021f, 0.981320223959493f, + 0.981331606667595f, 0.981348676786265f, 0.981365742174963f, + 0.981382802836166f, 0.981394173985141f, 0.981411226772692f, + 0.981428274839352f, 0.981445318187591f, 0.981456677799626f, + 0.981473713289321f, 0.981490744067179f, 0.981507770135665f, + 0.981519118232888f, 0.981536136457894f, 0.981553149980093f, + 0.981564489716769f, 0.981581495406417f, 0.981598496399810f, + 0.981615492699399f, 0.981626820959334f, 0.981643809441364f, + 0.981660793236125f, 0.981672113163134f, 0.981689089151212f, + 0.981706060458539f, 0.981717372064213f, 0.981734335575712f, + 0.981751294412966f, 0.981762597708870f, 0.981779548761129f, + 0.981796495145636f, 0.981807790143312f, 0.981824728753634f, + 0.981841662702683f, 0.981852949413651f, 0.981869875599303f, + 0.981886797130148f, 0.981898075565905f, 0.981914989344119f, + 0.981926262613694f, 0.981943168645996f, 0.981960070033971f, + 0.981971335046964f, 0.981988228699756f, 0.982005117714653f, + 0.982016374482499f, 0.982033255772922f, 0.982044507393347f, + 0.982061380965979f, 0.982089493304948f, 0.982106354542990f, + 0.982117592800940f, 0.982134446338523f, 0.982145679465052f, + 0.982162525308829f, 0.982179366539510f, 0.982190591464981f, + 0.982207425012481f, 0.982218644818040f, 0.982235470688987f, + 0.982246685379053f, 0.982263503580068f, 0.982274713159051f, + 0.982291523696748f, 0.982302728169054f, 0.982319531050036f, + 0.982330730420066f, 0.982347525650929f, 0.982364316294885f, + 0.982375507510410f, 0.982392290514783f, 0.982403476639445f, + 0.982420252010811f, 0.982431433048991f, 0.982448200793916f, + 0.982459376749988f, 0.982476136875028f, 0.982487307753362f, + 0.982498476601823f, 0.982515226070027f, 0.982526389846857f, + 0.982543131710882f, 0.982554290420436f, 0.982571024686812f, + 0.982582178333442f, 0.982598905008691f, 0.982610053596740f, + 0.982626772687374f, 0.982637916221183f, 0.982654627733708f, + 0.982665766217610f, 0.982676902682761f, 0.982693603596846f, + 0.982704735018149f, 0.982721428369703f, 0.982732554751479f, + 0.982749240546979f, 0.982760361893543f, 0.982771481227230f, + 0.982788156455121f, 0.982799270759628f, 0.982815938446979f, + 0.982827047726609f, 0.982838154997492f, 0.982854812138917f, + 0.982865914390938f, 0.982882564007288f, 0.982893661244735f, + 0.982904756477552f, 0.982921395569597f, 0.982932485793838f, + 0.982949117376225f, 0.982960202596166f, 0.982971285815582f, + 0.982987906895218f, 0.982998985116313f, 0.983010061339272f, + 0.983026671928701f, 0.983037743159306f, 0.983048812394161f, + 0.983065412505902f, 0.983076476754361f, 0.983087539009450f, + 0.983104128656004f, 0.983115185930644f, 0.983126241214290f, + 0.983142820408134f, 0.983164919037780f, 0.983164919037780f, + 0.983181487791369f, 0.983203572508969f, 0.983203572508969f, + 0.983220130834737f, 0.983242201656853f, 0.983242201656853f, + 0.983258749567212f, 0.983280806510381f, 0.983280806510381f, + 0.983297344017722f, 0.983308366547671f, 0.983330405670727f, + 0.983330405670727f, 0.983346929820930f, 0.983368955102724f, + 0.983379964780070f, 0.983396475593535f, 0.983396475593535f, + 0.983418483103573f, 0.983418483103573f, 0.983434983558095f, + 0.983445981396647f, 0.983467971162045f, 0.983467971162045f, + 0.983484458315984f, 0.983495447291238f, 0.983517419339052f, + 0.983517419339052f, 0.983533893212681f, 0.983555851498819f, + 0.983566827695369f, 0.983577801928461f, 0.983594259598068f, + 0.983605228925419f, 0.983616196291637f, 0.983627161697383f, + 0.983627161697383f, 0.983643606131571f, 0.983654566639040f, + 0.983676481780187f, 0.983687436415187f, 0.983703864700081f, + 0.983703864700081f, 0.983714814445903f, 0.983736708074663f, + 0.983747651958919f, 0.983758593890641f, 0.983775003128660f, + 0.983785940181924f, 0.983829668899257f, 0.983846059132147f, + 0.983856983520667f, 0.983867905963228f, 0.983878826460486f, + 0.983900661621713f, 0.983917032891085f, 0.983917032891085f, + 0.983927944642585f, 0.983938854452384f, 0.983960668249489f, + 0.983971572238103f, 0.983982474287629f, 0.983998823727552f, + 0.983998823727552f, 0.984020616200059f, 0.984031509531754f, + 0.984042400927945f, 0.984053290389283f, 0.984064177916417f, + 0.984075063509998f, 0.984085947170676f, 0.984102269038919f, + 0.984113147870179f, 0.984124024770807f, 0.984134899741452f, + 0.984145772782762f, 0.984156643895383f, 0.984167513079964f, + 0.984178380337151f, 0.984189245667592f, 0.984200109071931f, + 0.984210970550817f, 0.984227259160331f, 0.984238115828405f, + 0.984248970573283f, 0.984259823395610f, 0.984270674296031f, + 0.984281523275189f, 0.984292370333728f, 0.984303215472291f, + 0.984314058691521f, 0.984324899992062f, 0.984335739374555f, + 0.984346576839642f, 0.984357412387966f, 0.984368246020168f, + 0.984379077736889f, 0.984389907538769f, 0.984400735426450f, + 0.984411561400571f, 0.984422385461773f, 0.984433207610695f, + 0.984444027847976f, 0.984460254620969f, 0.984471070081943f, + 0.984481883633511f, 0.984492695276311f, 0.984503505010981f, + 0.984514312838157f, 0.984525118758478f, 0.984535922772579f, + 0.984546724881096f, 0.984557525084666f, 0.984568323383925f, + 0.984579119779507f, 0.984589914272047f, 0.984600706862181f, + 0.984611497550543f, 0.984622286337767f, 0.984633073224487f, + 0.984643858211335f, 0.984649249992506f, 0.984670812370678f, + 0.984681590712963f, 0.984692367158224f, 0.984703141707094f, + 0.984703141707094f, 0.984713914360202f, 0.984724685118182f, + 0.984735453981663f, 0.984746220951275f, 0.984756986027650f, + 0.984767749211417f, 0.984778510503206f, 0.984789269903646f, + 0.984800027413365f, 0.984810783032994f, 0.984821536763160f, + 0.984832288604491f, 0.984843038557615f, 0.984853786623160f, + 0.984869905183637f, 0.984880648532976f, 0.984891389996930f, + 0.984902129576124f, 0.984912867271185f, 0.984923603082736f, + 0.984934337011405f, 0.984945069057816f, 0.984955799222593f, + 0.984966527506360f, 0.984977253909743f, 0.984987978433363f, + 0.984993339990457f, 0.985004061695604f, 0.985014781522546f, + 0.985025499471908f, 0.985036215544310f, 0.985046929740375f, + 0.985057642060725f, 0.985068352505981f, 0.985079061076764f, + 0.985084414659422f, 0.985095120419660f, 0.985105824306976f, + 0.985116526321991f, 0.985127226465325f, 0.985137924737595f, + 0.985148621139423f, 0.985159315671427f, 0.985164662236437f, + 0.985175353964864f, 0.985186043825012f, 0.985196731817500f, + 0.985207417942944f, 0.985218102201962f, 0.985228784595170f, + 0.985244804687940f, 0.985255482419322f, 0.985266158287053f, + 0.985266158287053f, 0.985276832291746f, 0.985292839806936f, + 0.985303509156728f, 0.985314176645634f, 0.985324842274270f, + 0.985324842274270f, 0.985335506043249f, 0.985351498211202f, + 0.985362157333723f, 0.985372814598732f, 0.985383470006843f, + 0.985383470006843f, 0.985399449638666f, 0.985410100407211f, + 0.985420749320998f, 0.985431396380641f, 0.985431396380641f, + 0.985442041586749f, 0.985458005921870f, 0.985468646496813f, + 0.985479285220358f, 0.985479285220358f, 0.985495239835637f, + 0.985505873933352f, 0.985516506181800f, 0.985527136581592f, + 0.985527136581592f, 0.985543078716377f, 0.985553704497184f, + 0.985564328431462f, 0.985574950519818f, 0.985585570762857f, + 0.985590880192573f, 0.985601497668777f, 0.985612113301180f, + 0.985622727090390f, 0.985628033293987f, 0.985638644319542f, + 0.985649253503417f, 0.985659860846216f, 0.985665163827402f, + 0.985675768409722f, 0.985686371152477f, 0.985696972056273f, + 0.985702271818749f, 0.985712869965236f, 0.985723466274270f, + 0.985734060746455f, 0.985739357293918f, 0.985749949011959f, + 0.985760538894657f, 0.985776420278752f, 0.985787005575724f, + 0.985797589039459f, 0.985802880084049f, 0.985813460799053f, + 0.985824039682320f, 0.985834616734451f, 0.985839904574028f, + 0.985850478880578f, 0.985861051357492f, 0.985866336910022f, + 0.985876906643602f, 0.985887474549043f, 0.985898040626941f, + 0.985903322980749f, 0.985913886318456f, 0.985924447830116f, + 0.985929727901365f, 0.985940286675075f, 0.985950843624230f, + 0.985956121414786f, 0.985966675628229f, 0.985977228018608f, + 0.985987778586520f, 0.985993053187237f, 0.986003601022566f, + 0.986014147036915f, 0.986019419361409f, 0.986029962645406f, + 0.986040504109912f, 0.986045774160041f, 0.986056312896423f, + 0.986066849814798f, 0.986072117592419f, 0.986082651784897f, + 0.986093184160851f, 0.986098449667817f, 0.986108979320098f, + 0.986119507157337f, 0.986124770395501f, 0.986135295511287f, + 0.986145818813510f, 0.986151079784721f, 0.986161600367710f, + 0.986166859979636f, 0.986177377844719f, 0.986187893898605f, + 0.986193151246533f, 0.986203664584727f, 0.986214176113198f, + 0.986219431198972f, 0.986229940013964f, 0.986240447020705f, + 0.986245699846166f, 0.986256204141637f, 0.986261455611793f, + 0.986271957197316f, 0.986282456976943f, 0.986287706189730f, + 0.986298203261614f, 0.986308698529071f, 0.986313945486323f, + 0.986324438048239f, 0.986329683653050f, 0.986340173510745f, + 0.986350661566360f, 0.986355904918569f, 0.986366390272160f, + 0.986382114926572f, 0.986382114926572f, 0.986392595779718f, + 0.986397835531571f, 0.986413552089130f, 0.986424027546565f, + 0.986424027546565f, 0.986434501206598f, 0.986450208327797f, + 0.986455443136794f, 0.986465911408123f, 0.986465911408123f, + 0.986481610449544f, 0.986492074234293f, 0.986497305454026f, + 0.986507766548573f, 0.986512996423533f, 0.986523454829188f, + 0.986533911442975f, 0.986539139078099f, 0.986549593005172f, + 0.986554819297267f, 0.986565270538934f, 0.986570495488651f, + 0.986580944046217f, 0.986591390815106f, 0.986596613528979f, + 0.986607057615941f, 0.986612278989175f, 0.986622720395512f, + 0.986627940428760f, 0.986638379155774f, 0.986643597849686f, + 0.986654033898679f, 0.986659251253905f, 0.986669684626176f, + 0.986680116214689f, 0.986685331340216f, 0.986695760254172f, + 0.986700974042745f, 0.986711400283442f, 0.986716612735710f, + 0.986727036304445f, 0.986732247421057f, 0.986742668319126f, + 0.986747878100727f, 0.986758296329425f, 0.986763504776665f, + 0.986773920337285f, 0.986779127450809f, 0.986789540344645f, + 0.986794746125101f, 0.986805156353445f, 0.986810360801477f, + 0.986820768365621f, 0.986831174154347f, 0.986836376383108f, + 0.986846779509784f, 0.986851980407841f, 0.986862380873755f, + 0.986867580441755f, 0.986877978248194f, 0.986883176486778f, + 0.986893571635030f, 0.986898768544842f, 0.986909161036194f, + 0.986914356617876f, 0.986924746453612f, 0.986935134519650f, + 0.986945520816562f, 0.986950713301773f, 0.986961096946065f, + 0.986966288105286f, 0.986976669098239f, 0.986981858932111f, + 0.986992237275006f, 0.986997425784170f, 0.987007801478288f, + 0.987012988663383f, 0.987023361710002f, 0.987028547571668f, + 0.987038917972068f, 0.987044102510944f, 0.987054470266402f, + 0.987059653483127f, 0.987059653483127f, 0.987070018594919f, + 0.987080381944977f, 0.987090743533868f, 0.987095923668051f, + 0.987106282616252f, 0.987111461430409f, 0.987121817739193f, + 0.987126995233961f, 0.987137348904601f, 0.987142525080615f, + 0.987147700817186f, 0.987158050972279f, 0.987163225390944f, + 0.987173572910860f, 0.987178746012253f, 0.987189090898262f, + 0.987194262683019f, 0.987204604936389f, 0.987209775405143f, + 0.987214945435371f, 0.987225284180528f, 0.987230452895598f, + 0.987240789011073f, 0.987245956411619f, 0.987256289898679f, + 0.987261455985333f, 0.987292443313425f, 0.987276951618636f, + 0.987302768922823f, 0.987313092783174f, 0.987318254057631f, + 0.987328575295461f, 0.987333735258974f, 0.987344053875544f, + 0.987349212528741f, 0.987354370745308f, 0.987364685868824f, + 0.987369842775915f, 0.987380155281111f, 0.987385310879356f, + 0.987390466041458f, 0.987400775057515f, 0.987405928911610f, + 0.987416235312279f, 0.987421387858994f, 0.987426539970055f, + 0.987436842885494f, 0.987441993690012f, 0.987447144059154f, + 0.987457443491593f, 0.987462592555029f, 0.987472889376680f, + 0.987478037135035f, 0.987483184458503f, 0.987493477801052f, + 0.987503769404885f, 0.987503769404885f, 0.987514059270557f, + 0.987519203551756f, 0.987529490811229f, 0.987534633789641f, + 0.987539776333929f, 0.987550060120412f, 0.987555201362745f, + 0.987560342171233f, 0.987570622486945f, 0.987580901068104f, + 0.987580901068104f, 0.987591177915262f, 0.987596315688765f, + 0.987606589935962f, 0.987611726409794f, 0.987616862450543f, + 0.987627133233060f, 0.987632267974967f, 0.987637402284067f, + 0.987647669604116f, 0.987652802615204f, 0.987657935193759f, + 0.987668199053549f, 0.987673330334921f, 0.987678461184036f, + 0.987688721585774f, 0.987693851138533f, 0.987698980259311f, + 0.987709237205201f, 0.987714365030449f, 0.987719492423992f, + 0.987729745916237f, 0.987734872015076f, 0.987739997682485f, + 0.987750247723285f, 0.987760496039188f, 0.987760496039188f, + 0.987770742630743f, 0.987780987498498f, 0.987780987498498f, + 0.987791230643004f, 0.987796351569210f, 0.987801472064809f, + 0.987811711764460f, 0.987816830968650f, 0.987827068086100f, + 0.987827068086100f, 0.987837303482766f, 0.987842420535977f, + 0.987847537159197f, 0.987857769115940f, 0.987862884449599f, + 0.987867999353541f, 0.987878227872547f, 0.987883341487748f, + 0.987888454673505f, 0.987898679756961f, 0.987903791654796f, + 0.987908903123460f, 0.987914014163022f, 0.987924234955111f, + 0.987929344707774f, 0.987934454031607f, 0.987944671393056f, + 0.987949779430808f, 0.987959994220708f, 0.987959994220708f, + 0.987970207296921f, 0.987975313192566f, 0.987995732493650f, + 0.988000836248887f, 0.988005939576247f, 0.988016144947605f, + 0.988021246991739f, 0.988026348608267f, 0.988031449797257f, + 0.988041650892892f, 0.988046750799674f, 0.988051850279188f, + 0.988062047956687f, 0.988067146154806f, 0.988072243925930f, + 0.988077341270126f, 0.988087534678001f, 0.988092630741817f, + 0.988097726378975f, 0.988102821589542f, 0.988113010731178f, + 0.988118104662380f, 0.988123198167263f, 0.988128291245894f, + 0.988138476124667f, 0.988143567924946f, 0.988148659299242f, + 0.988153750247623f, 0.988163930866911f, 0.988169020537952f, + 0.988174109783348f, 0.988179198603165f, 0.988189374966338f, + 0.988194462509826f, 0.988199549628007f, 0.988204636320946f, + 0.988214808431370f, 0.988219893848989f, 0.988224978841637f, + 0.988230063409379f, 0.988235147552284f, 0.988245314563850f, + 0.988250397432645f, 0.988255479876871f, 0.988260561896595f, + 0.988270724662807f, 0.988275805409428f, 0.988280885731816f, + 0.988285965630037f, 0.988296124154249f, 0.988301202780374f, + 0.988306280982600f, 0.988311358760995f, 0.988316436115625f, + 0.988331665637599f, 0.988336741297841f, 0.988341816534654f, + 0.988346891348103f, 0.988357039705180f, 0.988362113248942f, + 0.988367186369607f, 0.988372259067245f, 0.988377331341919f, + 0.988387474622650f, 0.988392545628840f, 0.988397616212334f, + 0.988402686373199f, 0.988407756111503f, 0.988417894320692f, + 0.988422962791711f, 0.988428030840435f, 0.988433098466929f, + 0.988438165671262f, 0.988448298813708f, 0.988448298813708f, + 0.988453364751954f, 0.988463495362826f, 0.988468560035585f, + 0.988478688116080f, 0.988478688116080f, 0.988488814510321f, + 0.988493877075263f, 0.988498939218840f, 0.988509062242168f, + 0.988509062242168f, 0.988514123122051f, 0.988519183580837f, + 0.988529303235375f, 0.988534362431262f, 0.988534362431262f, + 0.988544479560602f, 0.988549537494187f, 0.988554595007138f, + 0.988564708771400f, 0.988569765022844f, 0.988579876264688f, + 0.988584931255221f, 0.988589985825582f, 0.988595039975838f, + 0.988605147016298f, 0.988605147016298f, 0.988615252377130f, + 0.988620304427851f, 0.988625356058862f, 0.988630407270231f, + 0.988635458062022f, 0.988640508434303f, 0.988650607920595f, + 0.988655657034738f, 0.988660705729634f, 0.988665754005349f, + 0.988670801861948f, 0.988680896318064f, 0.988680896318064f, + 0.988690989098507f, 0.988696034860517f, 0.988701080203806f, + 0.988706125128441f, 0.988711169634487f, 0.988716213722009f, + 0.988721257391074f, 0.988731343474094f, 0.988736385888181f, + 0.988741427884073f, 0.988746469461837f, 0.988751510621536f, + 0.988761591687008f, 0.988771671081014f, 0.988771671081014f, + 0.988781748804078f, 0.988786787039171f, 0.988791824856726f, + 0.988796862256806f, 0.988801899239479f, 0.988806935804810f, + 0.988817007683707f, 0.988817007683707f, 0.988822042997403f, + 0.988827077894019f, 0.988837146436271f, 0.988842180082038f, + 0.988847213310986f, 0.988852246123180f, 0.988862310497568f, + 0.988862310497568f, 0.988867342059893f, 0.988872373205725f, + 0.988877403935130f, 0.988887464144920f, 0.988892493625434f, + 0.988897522689783f, 0.988902551338030f, 0.988912607386482f, + 0.988912607386482f, 0.988922661771312f, 0.988927688340031f, + 0.988932714493039f, 0.988937740230403f, 0.988942765552186f, + 0.988947790458455f, 0.988952814949272f, 0.988957839024706f, + 0.988967885929677f, 0.988967885929677f, 0.988972908759345f, + 0.988982953173371f, 0.988987974757859f, 0.988992995927416f, + 0.988998016682109f, 0.989003037022000f, 0.989008056947156f, + 0.989013076457642f, 0.989018095553521f, 0.989028132501721f, + 0.989033150354172f, 0.989038167792276f, 0.989043184816098f, + 0.989048201425703f, 0.989053217621155f, 0.989058233402521f, + 0.989063248769862f, 0.989068263723246f, 0.989073278262736f, + 0.989078292388397f, 0.989083306100294f, 0.989093332283054f, + 0.989093332283054f, 0.989098344754046f, 0.989103356811533f, + 0.989108368455578f, 0.989113379686246f, 0.989123400907711f, + 0.989128410898637f, 0.989133420476444f, 0.989138429641197f, + 0.989143438392961f, 0.989148446731799f, 0.989153454657776f, + 0.989158462170958f, 0.989163469271408f, 0.989173482234369f, + 0.989173482234369f, 0.989178488097010f, 0.989188498584932f, + 0.989193503210343f, 0.989198507423472f, 0.989203511224385f, + 0.989208514613144f, 0.989213517589816f, 0.989218520154462f, + 0.989223522307149f, 0.989228524047940f, 0.989233525376900f, + 0.989238526294092f, 0.989243526799581f, 0.989248526893431f, + 0.989253526575707f, 0.989258525846471f, 0.989263524705789f, + 0.989268523153725f, 0.989278518815704f, 0.989283516029877f, + 0.989288512832923f, 0.989293509224907f, 0.989298505205893f, + 0.989303500775945f, 0.989308495935126f, 0.989313490683502f, + 0.989318485021135f, 0.989323478948089f, 0.989328472464430f, + 0.989333465570220f, 0.989338458265524f, 0.989343450550404f, + 0.989348442424926f, 0.989353433889154f, 0.989358424943150f, + 0.989363415586979f, 0.989368405820704f, 0.989373395644390f, + 0.989378385058100f, 0.989383374061898f, 0.989388362655847f, + 0.989393350840012f, 0.989398338614456f, 0.989403325979243f, + 0.989408312934436f, 0.989413299480100f, 0.989418285616298f, + 0.989423271343093f, 0.989428256660550f, 0.989433241568731f, + 0.989438226067700f, 0.989443210157522f, 0.989448193838259f, + 0.989453177109976f, 0.989458159972735f, 0.989463142426600f, + 0.989468124471636f, 0.989473106107904f, 0.989478087335469f, + 0.989483068154395f, 0.989488048564745f, 0.989493028566581f, + 0.989498008159969f, 0.989502987344970f, 0.989507966121648f, + 0.989512944490068f, 0.989517922450292f, 0.989522900002383f, + 0.989527877146405f, 0.989532853882422f, 0.989537830210496f, + 0.989542806130691f, 0.989547781643070f, 0.989552756747697f, + 0.989557731444634f, 0.989562705733946f, 0.989567679615695f, + 0.989572653089944f, 0.989577626156756f, 0.989582598816196f, + 0.989587571068325f, 0.989592542913208f, 0.989597514350907f, + 0.989602485381486f, 0.989607456005007f, 0.989612426221533f, + 0.989617396031129f, 0.989622365433856f, 0.989627334429779f, + 0.989632303018959f, 0.989637271201460f, 0.989642238977345f, + 0.989647206346678f, 0.989652173309519f, 0.989657139865935f, + 0.989662106015986f, 0.989667071759736f, 0.989667071759736f, + 0.989672037097247f, 0.989677002028584f, 0.989681966553808f, + 0.989686930672983f, 0.989691894386171f, 0.989696857693435f, + 0.989701820594839f, 0.989706783090444f, 0.989711745180314f, + 0.989716706864512f, 0.989721668143100f, 0.989726629016141f, + 0.989731589483698f, 0.989736549545834f, 0.989741509202611f, + 0.989746468454092f, 0.989751427300340f, 0.989756385741417f, + 0.989761343777386f, 0.989761343777386f, 0.989766301408311f, + 0.989771258634252f, 0.989776215455275f, 0.989781171871439f, + 0.989786127882809f, 0.989791083489447f, 0.989796038691415f, + 0.989800993488776f, 0.989805947881593f, 0.989810901869928f, + 0.989815855453843f, 0.989820808633402f, 0.989825761408667f, + 0.989830713779699f, 0.989835665746562f, 0.989840617309318f, + 0.989840617309318f, 0.989845568468030f, 0.989850519222759f, + 0.989855469573569f, 0.989860419520521f, 0.989865369063679f, + 0.989870318203104f, 0.989875266938858f, 0.989880215271005f, + 0.989885163199606f, 0.989885163199606f, 0.989890110724724f, + 0.989895057846421f, 0.989900004564759f, 0.989904950879801f, + 0.989909896791608f, 0.989914842300244f, 0.989919787405770f, + 0.989924732108248f, 0.989929676407741f, 0.989934620304311f, + 0.989939563798019f, 0.989944506888929f, 0.989944506888929f, + 0.989949449577102f, 0.989954391862601f, 0.989959333745488f, + 0.989964275225823f, 0.989969216303671f, 0.989974156979092f, + 0.989979097252149f, 0.989984037122904f, 0.989988976591419f, + 0.989993915657756f, 0.990102473491435f, 0.989998854321976f, + 0.990003792584142f, 0.990008730444317f, 0.990013667902561f, + 0.990018604958937f, 0.990127118651726f, 0.990132046482196f, + 0.990136973912279f, 0.990141900942038f, 0.990146827571534f, + 0.990097543257357f, 0.990048218861426f, 0.990053153106457f, + 0.990058086950115f, 0.990166530088123f, 0.990171454717229f, + 0.990176378946441f, 0.990181302775822f, 0.990077818312251f, + 0.990082750149969f, 0.990087681586685f, 0.990092612622461f, + 0.990200994096251f, 0.990205915927393f, 0.990210837359071f, + 0.990215758391347f, 0.990220679024282f, 0.990220679024282f, + 0.990225599257938f, 0.990230519092376f, 0.990235438527657f, + 0.990240357563843f, 0.990245276200994f, 0.990250194439173f, + 0.990255112278440f, 0.990255112278440f, 0.990260029718857f, + 0.990264946760485f, 0.990269863403386f, 0.990274779647619f, + 0.990279695493247f, 0.990284610940331f, 0.990284610940331f, + 0.990289525988932f, 0.990294440639111f, 0.990299354890930f, + 0.990304268744449f, 0.990309182199729f, 0.990314095256832f, + 0.990314095256832f, 0.990319007915819f, 0.990323920176751f, + 0.990328832039688f, 0.990333743504692f, 0.990338654571825f, + 0.990338654571825f, 0.990343565241146f, 0.990348475512717f, + 0.990353385386600f, 0.990358294862854f, 0.990363203941541f, + 0.990363203941541f, 0.990368112622723f, 0.990373020906459f, + 0.990377928792811f, 0.990382836281839f, 0.990387743373605f, + 0.990387743373605f, 0.990392650068170f, 0.990397556365594f, + 0.990402462265938f, 0.990407367769264f, 0.990412272875631f, + 0.990412272875631f, 0.990417177585100f, 0.990422081897733f, + 0.990426985813592f, 0.990431889332734f, 0.990436792455222f, + 0.990441695181118f, 0.990446597510480f, 0.990446597510480f, + 0.990451499443371f, 0.990456400979850f, 0.990461302119979f, + 0.990466202863817f, 0.990471103211427f, 0.990471103211427f, + 0.990476003162868f, 0.990480902718201f, 0.990485801877487f, + 0.990490700640787f, 0.990490700640787f, 0.990495599008160f, + 0.990500496979667f, 0.990505394555370f, 0.990510291735329f, + 0.990515188519603f, 0.990515188519603f, 0.990520084908255f, + 0.990524980901343f, 0.990529876498930f, 0.990534771701074f, + 0.990539666507838f, 0.990539666507838f, 0.990544560919280f, + 0.990549454935462f, 0.990554348556445f, 0.990559241782288f, + 0.990564134613051f, 0.990564134613051f, 0.990569027048796f, + 0.990573919089582f, 0.990578810735471f, 0.990583701986522f, + 0.990583701986522f, 0.990588592842796f, 0.990593483304352f, + 0.990598373371253f, 0.990598373371253f, 0.990603263043557f, + 0.990608152321324f, 0.990613041204617f, 0.990617929693493f, + 0.990622817788014f, 0.990622817788014f, 0.990627705488241f, + 0.990632592794232f, 0.990637479706049f, 0.990642366223752f, + 0.990642366223752f, 0.990647252347400f, 0.990652138077054f, + 0.990657023412774f, 0.990661908354621f, 0.990661908354621f, + 0.990666792902654f, 0.990671677056933f, 0.990676560817519f, + 0.990681444184472f, 0.990681444184472f, 0.990686327157852f, + 0.990691209737718f, 0.990696091924130f, 0.990700973717151f, + 0.990700973717151f, 0.990705855116837f, 0.990710736123251f, + 0.990715616736451f, 0.990715616736451f, 0.990720496956498f, + 0.990725376783453f, 0.990730256217374f, 0.990735135258321f, + 0.990735135258321f, 0.990740013906355f, 0.990744892161536f, + 0.990749770023923f, 0.990754647493576f, 0.990754647493576f, + 0.990759524570556f, 0.990764401254922f, 0.990769277546733f, + 0.990774153446051f, 0.990774153446051f, 0.990779028952933f, + 0.990783904067441f, 0.990788778789634f, 0.990788778789634f, + 0.990793653119573f, 0.990798527057315f, 0.990803400602923f, + 0.990808273756453f, 0.990808273756453f, 0.990813146517969f, + 0.990818018887528f, 0.990822890865190f, 0.990827762451014f, + 0.990827762451014f, 0.990832633645062f, 0.990837504447392f, + 0.990842374858063f, 0.990842374858063f, 0.990847244877137f, + 0.990852114504671f, 0.990856983740726f, 0.990856983740726f, + 0.990861852585361f, 0.990866721038637f, 0.990871589100612f, + 0.990876456771346f, 0.990876456771346f, 0.990881324050898f, + 0.990886190939329f, 0.990891057436697f, 0.990891057436697f, + 0.990895923543062f, 0.990900789258484f, 0.990905654583022f, + 0.990905654583022f, 0.990910519516736f, 0.990915384059684f, + 0.990920248211927f, 0.990920248211927f, 0.990925111973523f, + 0.990929975344533f, 0.990934838325015f, 0.990939700915029f, + 0.990939700915029f, 0.990944563114635f, 0.990949424923891f, + 0.990954286342857f, 0.990954286342857f, 0.990959147371593f, + 0.990964008010157f, 0.990968868258609f, 0.990968868258609f, + 0.990973728117009f, 0.990978587585414f, 0.990983446663886f, + 0.990988305352482f, 0.990988305352482f, 0.990993163651263f, + 0.990998021560288f, 0.991002879079614f, 0.991002879079614f, + 0.991007736209303f, 0.991012592949413f, 0.991017449300003f, + 0.991017449300003f, 0.991022305261132f, 0.991027160832860f, + 0.991032016015245f, 0.991032016015245f, 0.991036870808346f, + 0.991041725212224f, 0.991046579226937f, 0.991046579226937f, + 0.991051432852543f, 0.991056286089103f, 0.991061138936675f, + 0.991061138936675f, 0.991065991395318f, 0.991070843465090f, + 0.991075695146052f, 0.991075695146052f, 0.991080546438262f, + 0.991085397341780f, 0.991090247856663f, 0.991090247856663f, + 0.991095097982971f, 0.991099947720763f, 0.991104797070098f, + 0.991109646031035f, 0.991109646031035f, 0.991114494603633f, + 0.991119342787949f, 0.991119342787949f, 0.991124190584045f, + 0.991129037991978f, 0.991133885011807f, 0.991133885011807f, + 0.991138731643591f, 0.991143577887389f, 0.991148423743260f, + 0.991148423743260f, 0.991153269211262f, 0.991158114291454f, + 0.991158114291454f, 0.991162958983895f, 0.991167803288644f, + 0.991172647205759f, 0.991172647205759f, 0.991177490735300f, + 0.991182333877324f, 0.991187176631891f, 0.991187176631891f, + 0.991192040874586f, 0.991196904726712f, 0.991196904726712f, + 0.991201768188329f, 0.991206631259494f, 0.991211493940268f, + 0.991211493940268f, 0.991216356230710f, 0.991221218130880f, + 0.991226079640835f, 0.991230940760638f, 0.991230940760638f, + 0.991235801490345f, 0.991240661830017f, 0.991245521779712f, + 0.991245521779712f, 0.991250381339491f, 0.991255240509413f, + 0.991260099289535f, 0.991260099289535f, 0.991264957679918f, + 0.991269815680622f, 0.991274673291704f, 0.991274673291704f, + 0.991279530513224f, 0.991284387345243f, 0.991289243787817f, + 0.991289243787817f, 0.991294099841006f, 0.991298955504871f, + 0.991303810779470f, 0.991303810779470f, 0.991308665664861f, + 0.991313520161105f, 0.991318374268260f, 0.991318374268260f, + 0.991323227986385f, 0.991328081315538f, 0.991332934255781f, + 0.991332934255781f, 0.991337786807170f, 0.991342638969767f, + 0.991347490743627f, 0.991347490743627f, 0.991352342128812f, + 0.991357193125380f, 0.991357193125380f, 0.991362043733390f, + 0.991366893952901f, 0.991371743783973f, 0.991371743783973f, + 0.991376593226661f, 0.991381442281029f, 0.991386290947134f, + 0.991386290947134f, 0.991391139225033f, 0.991395987114787f, + 0.991400834616454f, 0.991400834616454f, 0.991405681730093f, + 0.991410528455763f, 0.991410528455763f, 0.991415374793522f, + 0.991420220743429f, 0.991425066305544f, 0.991425066305544f, + 0.991429911479924f, 0.991434756266630f, 0.991439600665718f, + 0.991439600665718f, 0.991444444677248f, 0.991449288301280f, + 0.991449288301280f}; + +// Mapping between reverberation times at a given frequency and the +// corresponding magnitude compensation value required in the specral reverb to +// achieve 1.0 magnitude for any decay rate when combined with the convolution +// compensator. This table covers reverberation times between +// kMinReverbTimeForFeedback and kNumFeedbackValues * kTimeStepSizeForFeedback, +// i.e. 0.15s and 25s @48kHz. +static const float kMagnitudeCompensation[kNumFeedbackValues] = { + 17.3996715487709f, 17.4034178858004f, 16.6795907475848f, 16.3502965109599f, + 15.9879028507326f, 15.6718483944106f, 15.4042251167111f, 15.1677088170684f, + 14.9119678952536f, 14.6906933374371f, 14.2839187463763f, 14.0999475816121f, + 13.9338828421032f, 13.7652114598195f, 13.6133888181873f, 13.4638764218013f, + 13.3247278048121f, 13.1976499042192f, 13.0833841919256f, 12.7669049046296f, + 12.7761019806869f, 12.7855596748707f, 12.5979372062571f, 12.5244917829334f, + 12.4453918694998f, 12.3672592847373f, 12.3020244979211f, 12.2257529341565f, + 12.1547662449381f, 12.0845455398564f, 12.0231689748723f, 11.9586881174297f, + 11.8906434508319f, 11.8264759328308f, 11.7652619217413f, 11.7033346037227f, + 11.6379964417238f, 11.5862703081324f, 11.5299758955061f, 11.4741541469989f, + 11.4224679633351f, 11.4302928484179f, 11.3885962828649f, 11.3488611750486f, + 11.3151942251170f, 11.2687417305887f, 11.2651043092380f, 11.2873091969354f, + 11.1115917717737f, 11.1187139401529f, 11.0873969606168f, 11.0667794924678f, + 11.0355838523984f, 11.0107298415034f, 10.9796516432030f, 10.9630980289917f, + 10.9420391333514f, 10.9144740761416f, 10.8934689087169f, 10.8708578658852f, + 10.8533772904939f, 10.8325917807584f, 10.8084481007036f, 10.7910279996611f, + 10.7720483888874f, 10.7547079194992f, 10.7374196414357f, 10.7165315149605f, + 10.6992241376899f, 10.6906704210781f, 10.6696884648005f, 10.6557033369952f, + 10.6417418339839f, 10.6206945653294f, 10.6123225679390f, 10.5984100558311f, + 10.5804524022014f, 10.5664834165837f, 10.5556799009337f, 10.5448786038089f, + 10.5294926904862f, 10.5186888881000f, 10.5078877209760f, 10.4896735339343f, + 10.4818821922801f, 10.4710009885916f, 10.4601237996041f, 10.4509410528977f, + 10.4400494357799f, 10.4322036844072f, 10.4213123997993f, 10.4058408037523f, + 10.3978977400326f, 10.3899444363220f, 10.3853855448482f, 10.3697415198647f, + 10.3617555934757f, 10.3567416154720f, 10.3487308415134f, 10.3329739194789f, + 10.3248849674214f, 10.3167888305905f, 10.3116328550137f, 10.2935369581880f, + 10.2854263705648f, 10.2802322447455f, 10.2721029640593f, 10.2668785541679f, + 10.2421605382004f, 10.2455580945816f, 10.2402346533852f, 10.2417289351959f, + 10.2364188981879f, 10.2144782407169f, 10.2091021586862f, 10.2037115797613f, + 10.1983068112122f, 10.1849493802464f, 10.1794698792260f, 10.1739775209827f, + 10.1684725944209f, 10.1699972796703f, 10.1673363396048f, 10.1537525491825f, + 10.1481899931936f, 10.1454318458448f, 10.1398423821346f, 10.1370496372401f, + 10.1314343707374f, 10.1205181210259f, 10.1148376525328f, 10.1119389454472f, + 10.1062355079601f, 10.1105790660401f, 10.1076676719351f, 10.1019633798750f, + 10.0907918971796f, 10.0877973262721f, 10.0820222224882f, 10.0790001296191f, + 10.0759630305115f, 10.0729111643779f, 10.0698447671938f, 10.0585205719156f, + 10.0553915426054f, 10.0495078552707f, 10.0463551070209f, 10.0452327409897f, + 10.0420899577109f, 10.0416595363245f, 10.0301423062663f, 10.0269263405620f, + 10.0236983112226f, 10.0204584174658f, 10.0172068557167f, 10.0139438196431f, + 10.0106695001895f, 10.0100859757974f, 10.0144981569414f, 10.0027240655604f, + 9.99940079964503f, 9.99875825234152f, 9.99541139468150f, 9.99205454764473f, + 9.98868788138219f, 9.98799186037639f, 9.98460299286733f, 9.98120480749684f, + 9.97196520049622f, 9.96851944732945f, 9.96773313504258f, 9.97209005664967f, + 9.97130280431796f, 9.96784064615800f, 9.96702924221558f, 9.96354778825994f, + 9.96271284320390f, 9.95921268399594f, 9.94972407107315f, 9.94618172696550f, + 9.94527759238012f, 9.93907565972122f, 9.93815157408174f, 9.93457833610339f, + 9.94163812518103f, 9.93806970926284f, 9.93712514990341f, 9.93616969007899f, + 9.92384118906784f, 9.92284379437166f, 9.92183604073516f, 9.91819656036143f, + 9.91716993715598f, 9.91613331652545f, 9.91508681621950f, 9.91141646117235f, + 9.91035202464596f, 9.90927805124168f, 9.90819465265145f, 9.90449521562619f, + 9.89464259105904f, 9.90163545023412f, 9.90051546012025f, 9.89938659067352f, + 9.89565102186460f, 9.89450616426505f, 9.89335273158198f, 9.89219082303091f, + 9.89102053655261f, 9.88984196883182f, 9.88865521531579f, 9.88746037023253f, + 9.88367272054804f, 9.88246342154956f, 9.87235666909112f, 9.87111421978793f, + 9.86728668752777f, 9.86603072697377f, 9.86476741507816f, 9.87181651341944f, + 9.87055453050722f, 9.86928534086147f, 9.86800902634652f, 9.86672566780610f, + 9.86543534507815f, 9.86413813700939f, 9.86283412146973f, 9.85253909146675f, + 9.85120575616663f, 9.84730850541530f, 9.84340913473107f, 9.83695739915978f, + 9.83305682291249f, 9.82661110215619f, 9.82270954762832f, 9.81627003605364f, + 9.81236772402631f, 9.80593460975945f, 9.79446129569662f, 9.79056524322650f, + 9.79518744944566f, 9.78878964277671f, 9.78490541018618f, 9.76941213950054f, + 9.76551357605385f, 9.76161372925909f, 9.75771265131274f, 9.75131744052475f, + 9.74741680228965f, 9.74351508082574f, 9.73961232562214f, 9.73322796347969f, + 9.72932601448363f, 9.72542317182318f, 9.72151948243115f, 9.71761499262133f, + 9.71124407362971f, 9.70734082271271f, 9.70343690224853f, 9.69953235617044f, + 9.69562722783490f, 9.69172156002971f, 9.67869145869549f, 9.67477248299185f, + 9.67085316486435f, 9.66693354428727f, 9.67164058015198f, 9.66773182471560f, + 9.66382281819403f, 9.65991359906596f, 9.65600420530895f, 9.65209467440629f, + 9.64818504335383f, 9.64427534866659f, 9.64036562638542f, 9.63645591208342f, + 9.63254624087234f, 9.62863664740886f, 9.62713438108161f, 9.61841452486423f, + 9.61450796945654f, 9.61060162177350f, 9.59745989603663f, 9.59593610568275f, + 9.59201716961870f, 9.58809863249534f, 9.58418052465712f, 9.58026287605631f, + 9.57872704411307f, 9.57480783656106f, 9.57088917994674f, 9.56934602396844f, + 9.56542599874574f, 9.56150661276656f, 9.55758789336276f, 9.55603586291498f, + 9.56094674392723f, 9.55703713657417f, 9.55548790574812f, 9.55157724555479f, + 9.54766736708134f, 9.54611162685054f, 9.54220086685072f, 9.54064051939564f, + 9.53672899077399f, 9.53281837539013f, 9.51720800158667f, 9.51563186381870f, + 9.51171473845506f, 9.51013443950261f, 9.50621695380252f, 9.50696588424213f, + 9.50071486073663f, 9.49214537555389f, 9.48823490695630f, 9.48432562212793f, + 9.48041754157442f, 9.47651068553726f, 9.47260507399707f, 9.46870072667699f, + 9.46479766304589f, 9.45859100251033f, 9.45469300691147f, 9.45079634803183f, + 9.44690104436323f, 9.44300711415882f, 9.43911457543615f, 9.43522344598003f, + 9.43133374334554f, 9.42744548486084f, 9.42355868763001f, 9.42195400960424f, + 9.41806778052289f, 9.41374743291046f, 9.40986387622231f, 9.40598186864776f, + 9.40210142598266f, 9.39822256381766f, 9.39434529754074f, 9.39046964233973f, + 9.38659561320474f, 9.38498117319374f, 9.38110807138758f, 9.37723664318189f, + 9.37336690279205f, 9.36949886424754f, 9.36563254139424f, 9.36401291268887f, + 9.36014771343569f, 9.35628427401529f, 9.35242260755338f, 9.34856272700286f, + 9.34693907021185f, 9.34308046758153f, 9.33922369235477f, 9.33536875680650f, + 9.33374195178015f, 9.32988841242305f, 9.32381510855490f, 9.31996765124740f, + 9.31612209259297f, 9.31227844391980f, 9.31064913327039f, 9.30680704022146f, + 9.29348811817957f, 9.29184974751964f, 9.28800440783775f, 9.28636432840859f, + 9.28252074101649f, 9.27867919003329f, 9.27703723070236f, 9.27319750721753f, + 9.26935985268265f, 9.26771614205892f, 9.26388038770541f, 9.26223520182348f, + 9.25840139650094f, 9.25675479157226f, 9.25292298305829f, 9.24909331708271f, + 9.25658949582583f, 9.25276798764379f, 9.25112470249905f, 9.24730525026412f, + 9.24566068030482f, 9.24184332892114f, 9.24019752634543f, 9.23638231973555f, + 9.23690246959584f, 9.23308722316596f, 9.23143799550340f, 9.22762498397928f, + 9.22597467697943f, 9.22432329385452f, 9.22051260344843f, 9.21886021442665f, + 9.21505186252288f, 9.21339851457425f, 9.20959254004180f, 9.20578887012963f, + 9.20198751127673f, 9.19073957709422f, 9.18693490086577f, 9.18527430481475f, + 9.18361278881117f, 9.17981083017848f, 9.17814860221564f, 9.17648548821270f, + 9.17268633214397f, 9.17102256976036f, 9.16935795435656f, 9.16556168321056f, + 9.16389648126049f, 9.16223045844461f, 9.15843715205156f, 9.15677060283272f, + 9.15510326406306f, 9.15131299980263f, 9.14964519313344f, 9.14797662741422f, + 9.14630731290934f, 9.13195283996486f, 9.13028849958014f, 9.12862343577599f, + 9.12485055311793f, 9.12318514040497f, 9.12151903298212f, 9.11985224052471f, + 9.11608300035933f, 9.11441593072157f, 9.11274820378056f, 9.11107982889063f, + 9.10941081532760f, 9.10774117228957f, 9.10397656900970f, 9.10230675285809f, + 9.10063633356450f, 9.10830768064681f, 9.10664083749441f, 9.10288539850849f, + 9.10121842744103f, 9.09955087784672f, 9.09788275836467f, 9.08651657603331f, + 9.08068020302688f, 9.08108732540316f, 9.07941328828146f, 9.07773874094359f, + 9.07398723838858f, 9.07231273992199f, 9.07063775460245f, 9.06896229034989f, + 9.06521511911848f, 9.06353976158507f, 9.06186394770784f, 9.06018768515226f, + 9.05851098152150f, 9.05683384435697f, 9.05515628113895f, 9.05347829928709f, + 9.05179990616104f, 9.05012110906098f, 9.04844191522816f, 9.04676233184547f, + 9.04302460323958f, 9.04134529301830f, 9.03966561367638f, 9.03798557216934f, + 9.03630517539697f, 9.03462443020383f, 9.03294334337977f, 9.03126192166043f, + 9.02958017172773f, 9.02789810021035f, 9.02621571368424f, 9.02453301867307f, + 9.02285002164873f, 9.02116672903177f, 9.01948314719191f, 9.01779928244843f, + 9.01611514107069f, 9.01443072927854f, 9.01274605324280f, 9.01106111908564f, + 9.00937593288109f, 9.00769050065541f, 9.00600482838754f, 9.00431892200953f, + 9.00263278740694f, 9.00094643041926f, 8.99925985684034f, 8.99757307241874f, + 8.99588608285818f, 8.99419889381793f, 8.99251151091317f, 8.99082393971542f, + 8.99116133873637f, 8.98947240095708f, 8.97807476175783f, 8.97638164966389f, + 8.97468839063631f, 8.97299498992199f, 8.97130145272722f, 8.96960778421804f, + 8.96791398952060f, 8.96622007372152f, 8.96452604186820f, 8.96283189896917f, + 8.96113764999443f, 8.96145642376267f, 8.95976097981039f, 8.95806544520587f, + 8.95636982476753f, 8.95467412327692f, 8.95297834547896f, 8.95128249608231f, + 8.94958657975968f, 8.95734018337137f, 8.95765278435025f, 8.95595916486210f, + 8.95426548584055f, 8.95257175184105f, 8.95087796738447f, 8.94918413695742f, + 8.94949047792581f, 8.94779558087857f, 8.94610065184975f, 8.94440569519098f, + 8.94271071522106f, 8.93902043601862f, 8.93932070246017f, 8.93961997400109f, + 8.93792395970352f, 8.93622794393283f, 8.93453193081558f, 8.93283592444736f, + 8.93113992889301f, 8.92944394818690f, 8.92973639134932f, 8.92803947305345f, + 8.92634258225175f, 8.92464572285846f, 8.92493436813772f, 8.92323660608605f, + 8.92153888773201f, 8.91984121687354f, 8.91814359728028f, 8.91644603269380f, + 8.91474852682785f, 8.91305108336851f, 8.91135370597452f, 8.91163407099939f, + 8.90993586525176f, 8.90823773709895f, 8.90653969009158f, 8.90681648856557f, + 8.90511764518828f, 8.90341889416476f, 8.90172023894098f, 8.90199353826512f, + 8.90029411780736f, 8.89859480404659f, 8.89689560032744f, 8.89519650996990f, + 8.89546552758263f, 8.89376570958158f, 8.89206601546450f, 8.88053809639265f, + 8.88080074152891f, 8.87909746670552f, 8.87739433859366f, 8.87765453752411f, + 8.87595075137300f, 8.87424712182442f, 8.87254365189478f, 8.87280064185817f, + 8.87109654154655f, 8.86743422965210f, 8.86768885156784f, 8.86598526773548f, + 8.86428186186200f, 8.86453414821111f, 8.86478566315377f, 8.86112634248555f, + 8.86137632515970f, 8.85967194497569f, 8.85796776101988f, 8.85626377603165f, + 8.85651073575462f, 8.85480620525780f, 8.85310188252331f, 8.85334661076713f, + 8.85164176132608f, 8.84993712826266f, 8.85017965504828f, 8.84847451372278f, + 8.84676959721478f, 8.84700995202877f, 8.84530454535357f, 8.84359937176427f, + 8.84383758357059f, 8.84213193756712f, 8.84042653275027f, 8.84066263000176f, + 8.83895677018950f, 8.83725115950061f, 8.83748517015020f, 8.83577912155772f, + 8.83407332986507f, 8.83430528137662f, 8.83259906855242f, 8.83089312024770f, + 8.83112303960668f, 8.82941668662965f, 8.82771060563841f, 8.82793851936218f, + 8.82623204985186f, 8.82645864099079f, 8.82475179379158f, 8.82304523091814f, + 8.82326985960455f, 8.82156293469962f, 8.81985630119355f, 8.82007899274514f, + 8.81837201257628f, 8.81666533073787f, 8.81688611003994f, 8.81517909662492f, + 8.81539861470977f, 8.82335720989358f, 8.82165261003093f, 8.82187267469875f, + 8.82016775424161f, 8.82038657061920f, 8.81675884387406f, 8.81697642252574f, + 8.81527182158995f, 8.81548817296149f, 8.81378328023903f, 8.81207870996388f, + 8.81229323974577f, 8.81058839169725f, 8.81080171980556f, 8.80909660340947f, + 8.80739182006356f, 8.80760336445859f, 8.80589832635901f, 8.80610869398147f, + 8.80440341027862f, 8.80461261089678f, 8.80290709063344f, 8.80120191782847f, + 8.80140938602084f, 8.79970398968595f, 8.79991031482745f, 8.78825930793902f, + 8.78846219390559f, 8.78675407755697f, 8.78504632874136f, 8.78524755053805f, + 8.78353962067476f, 8.78373974434793f, 8.78203164176273f, 8.78223067625635f, + 8.78052240917839f, 8.77881452261966f, 8.77901193990290f, 8.77730390068432f, + 8.77559624718409f, 8.77579206684317f, 8.77408427230694f, 8.77427903760903f, + 8.77257110989291f, 8.77276482931164f, 8.77105677618141f, 8.76934912094798f, + 8.76954128723245f, 8.76783351773637f, 8.76802465892895f, 8.76631678271580f, + 8.76650690697914f, 8.76479893150834f, 8.76498804691885f, 8.76327997956479f, + 8.76346809411380f, 8.76175994216697f, 8.76194706376180f, 8.76023883442988f, + 8.76042497089499f, 8.75871667130397f, 8.75890183038202f, 8.75719346757723f, + 8.75737765693012f, 8.75566923787733f, 8.75585246508727f, 8.75414399667368f, + 8.75432626924423f, 8.75261775827954f, 8.75090968234295f, 8.75109053685399f, + 8.75127092234682f, 8.74956234640392f, 8.74785421147492f, 8.74803320078602f, + 8.74632504621761f, 8.74461733673038f, 8.74291007344532f, 8.74308722620506f, + 8.74326392423471f, 8.74155620054932f, 8.74173199434523f, 8.74002427303862f, + 8.74019916951030f, 8.73849145680757f, 8.73866546279447f, 8.73695776485170f, + 8.73713088712407f, 8.73542321002923f, 8.73559545528903f, 8.73388780506281f, + 8.73405917994453f, 8.73235156254122f, 8.73252207361275f, 8.73081449492107f, + 8.73098414868451f, 8.72927661452841f, 8.72944541742091f, 8.72773793356043f, + 8.72790589195497f, 8.72619846408700f, 8.72636558429323f, 8.72653228775183f, + 8.72482450631728f, 8.72499038093493f, 8.72328266978537f, 8.72344772171672f, + 8.72174008633388f, 8.72003293816951f, 8.72019676747887f, 8.71848970287163f, + 8.71865272461753f, 8.71881534481493f, 8.71710796902965f, 8.71726979056008f, + 8.71556251187906f, 8.71572354060119f, 8.71401636421506f, 8.71417660593049f, + 8.71246953697379f, 8.71262899742776f, 8.71278806938804f, 8.71108072586171f, + 8.71123902507422f, 8.70953180189128f, 8.70968933396333f, 8.70798223606704f, + 8.70813900655191f, 8.70829539830896f, 8.70658805323002f, 8.70674369162996f, + 8.70503648428256f, 8.70519137474645f, 8.70348430989052f, 8.70363845778761f, + 8.70379223639875f, 8.70208495078059f, 8.70223799478307f, 8.70053086365168f, + 8.70068317828796f, 8.69897620622755f, 8.69912779669041f, 8.68932884670592f, + 8.68762000302773f, 8.68776884448380f, 8.68606017937344f, 8.68620831732905f, + 8.68449983514613f, 8.68464727456896f, 8.68479436657368f, 8.68308572543799f, + 8.68323212627427f, 8.68152367907888f, 8.68166939359894f, 8.68181476676632f, + 8.68010617755452f, 8.67839813079483f, 8.67854248705877f, 8.67868650672127f, + 8.67697833094137f, 8.67712168091770f, 8.67541371794501f, 8.67555640288794f, + 8.67384865672637f, 8.67399068124574f, 8.68390062160107f, 8.68219432235443f, + 8.68233691470609f, 8.68247917838292f, 8.68077277138796f, 8.68091438110988f, + 8.67920820006687f, 8.67934916032569f, 8.67948979752457f, 8.67778352432750f, + 8.67792351872432f, 8.67621748133180f, 8.67635683731680f, 8.67649587572979f, + 8.67478976139895f, 8.67492816791096f, 8.67506626008425f, 8.67336007789188f, + 8.67349754458695f, 8.67179161357488f, 8.67192845902535f, 8.67206499542924f, + 8.67035901118758f, 8.67049493262608f, 8.67063054813644f, 8.66892451939922f, + 8.66905952613920f, 8.66735376335083f, 8.66748816540435f, 8.66762226663473f, + 8.66591647342483f, 8.66421125283483f, 8.66250660524407f, 8.66263951321076f, + 8.66093514316382f, 8.65923134784848f, 8.65752812762827f, 8.65582548286296f, + 8.65412341390855f, 8.65242192111739f, 8.65072100483809f, 8.64902066541564f, + 8.64732090319137f, 8.64745089666342f, 8.64575142580246f, 8.64405253375412f, + 8.64235422084484f, 8.64065648739748f, 8.63895933373144f, 8.63726276016262f, + 8.63556676700344f, 8.63387135456289f, 8.63399880607199f, 8.63230369681987f, + 8.63060916980038f, 8.62891522530837f, 8.62722186363537f, 8.62552908506957f, + 8.62383688989589f, 8.62214527839595f, 8.62045425084813f, 8.62057923413287f, + 8.61888852099306f, 8.61719839322406f, 8.61550885109083f, 8.61381989485520f, + 8.61213152477584f, 8.61225490214502f, 8.61056685383226f, 8.60887939303327f, + 8.60719251999378f, 8.60369847555976f, 8.60382053816116f, 8.60394234032854f, + 8.60225697240927f, 8.59876697957841f, 8.59708363808768f, 8.59720440679893f, + 8.59552139839225f, 8.59383898041714f, 8.59215715308918f, 8.59047591662109f, + 8.58879527122275f, 8.58891451420684f, 8.58903350571243f, 8.58735294942756f, + 8.58567298620775f, 8.58399361624780f, 8.58231483973975f, 8.58063665687294f, + 8.57895906783397f, 8.57728207280678f, 8.57560567197261f, 8.57392986551003f, + 8.57404638533917f, 8.57237093049576f, 8.57248696522609f, 8.56902180741298f, + 8.56734813950615f, 8.56746345224170f, 8.56579014113883f, 8.56411742691198f, + 8.56244530971544f, 8.56077378970094f, 8.56088791398092f, 8.55921675580945f, + 8.55933040984012f, 8.55765961583721f, 8.55598942090227f, 8.55431982517126f, + 8.55443254737061f, 8.55276331980773f, 8.55109469242306f, 8.54942666534252f, + 8.54775923868966f, 8.54076218161341f, 8.53909843766319f, 8.53743529276498f, + 8.53754618531528f, 8.53588341401169f, 8.53422124267654f, 8.53255967141845f, + 8.53266966578484f, 8.53100847186734f, 8.52934787891323f, 8.52768788702179f, + 8.52779699406003f, 8.52613738317202f, 8.52447837420358f, 8.52281996724478f, + 8.52292819763660f, 8.52127017526276f, 8.51961275572668f, 8.51795593910948f, + 8.51806330336606f, 8.51640687483569f, 8.51475105002415f, 8.51485777147656f, + 8.50313962983211f, 8.50324475785744f, 8.49982824152465f, 8.49993295139312f, + 8.49827795948697f, 8.49838225368573f, 8.49496979858900f, 8.49507367958556f, + 8.49342030501101f, 8.49176753881319f, 8.49187080459044f, 8.49021844295144f, + 8.48856669037454f, 8.48691554689607f, 8.48701800083704f, 8.48536726502665f, + 8.48371713897484f, 8.48206762271016f, 8.48216927434053f, 8.48052016878301f, + 8.47887167364807f, 8.47897272970743f, 8.47732464765384f, 8.47567717663991f, + 8.47577764234150f, 8.47413058674447f, 8.47248414278601f, 8.47083831047568f, + 8.47093799703846f, 8.46929258301410f, 8.46764778121340f, 8.46774688934850f, + 8.46610250808181f, 8.46445873959686f, 8.46455727431892f, 8.46291392857930f, + 8.46127119616231f, 8.46136916242822f, 8.45972685493173f, 8.45808516128181f, + 8.45818256399157f, 8.45654129740170f, 8.45490064516572f, 8.45499748916322f, + 8.45335726609174f, 8.45171765786506f, 8.45007866445932f, 8.45017477094663f, + 8.44853620927392f, 8.44863195015821f, 8.44699382175575f, 8.44535630913176f, + 8.44371941225248f, 8.44381442815541f, 8.44217796702569f, 8.44054212207913f, + 8.44063659963106f, 8.43900119239275f, 8.43909531357936f, 8.43746034551955f, + 8.43582599452207f, 8.43591958492567f, 8.43428567501132f, 8.43265238255709f, + 8.43274544665058f, 8.43111259715250f, 8.42948036549767f, 8.42957290770386f, + 8.42794112084706f, 8.42630995220218f, 8.42640197689452f, 8.42477125485887f, + 8.42486293694762f, 8.42323266289285f, 8.42160300781375f, 8.42169417956989f, + 8.42006497423241f, 8.41843638820147f, 8.41852705387014f, 8.41689891931203f, + 8.41527140437771f, 8.41536156815713f, 8.41373450639779f, 8.41210806456602f, + 8.41219773060816f, 8.41057174362487f, 8.40894637685971f, 8.40903554927091f, + 8.40741063899950f, 8.40749948458245f, 8.40587503206534f, 8.40425120040078f, + 8.40433955909700f, 8.40271618678552f, 8.40109343558253f, 8.40118131137970f, + 8.39955902110142f, 8.39964657715982f, 8.39802474901748f, 8.39640354256068f, + 8.39649062227007f, 8.39486987947612f, 8.39324975859040f, 8.39163025950670f, + 8.39171671010196f, 8.39180300448298f, 8.39018381508821f, 8.38856524812366f, + 8.38694730347691f, 8.38703297721227f, 8.38711849683149f, 8.38550086657194f, + 8.38388385923108f, 8.38226747469047f, 8.38235238198133f, 8.38073646806615f, + 8.38082107165390f, 8.37920562946549f, 8.37759081052727f, 8.37767496160383f, + 8.37606061575931f, 8.37614446717391f, 8.37453059549704f, 8.37461414884609f, + 8.37300075239995f, 8.37138797988615f, 8.37147108910541f, 8.36985879313347f, + 8.36994160822893f, 8.36832978983540f, 8.36671859574903f, 8.36680097257521f, + 8.36519025733977f, 8.36527234391476f, 8.36366210854089f, 8.36205249781910f, + 8.36213415188276f, 8.36052502225735f, 8.36060638987481f, 8.35899774233109f, + 8.36906328366319f, 8.36745599896731f, 8.36584933792354f, 8.36593087269790f, + 8.36432469282948f, 8.36440594331694f, 8.36280024559060f, 8.36288121327418f, + 8.36127599864676f, 8.35967140818186f, 8.35975195441777f, 8.35814784820884f, + 8.35822811530357f, 8.35662449428343f, 8.35670448368437f, 8.35510134877630f, + 8.35518106192085f, 8.35357841403867f, 8.35365785235434f, 8.35205569240248f, + 8.35045415745305f, 8.35053318618056f, 8.34893214024213f, 8.34901089766681f, + 8.34741034162095f, 8.34581041077519f, 8.34588876385553f, 8.34428932394983f, + 8.34269050924751f, 8.34276846108694f, 8.34284627986358f, 8.34124782440677f, + 8.34132537807708f, 8.33972741611141f, 8.33813007984327f, 8.33820723838498f, + 8.33828426588748f, 8.33668729339379f, 8.33676405981657f, 8.33349522624397f, + 8.33190013122409f, 8.33197650850716f, 8.33205275673382f, 8.33045802938747f, + 8.32886392796943f, 8.32893979098194f, 8.32734618754917f, 8.32742179537051f, + 8.32582869070157f, 8.32590404461620f, 8.32431143948147f, 8.32438654076529f, + 8.32279443592703f, 8.32286928584740f, 8.32127768205985f, 8.32135228187567f, + 8.31976117988511f, 8.31983553084688f, 8.31824493139170f, 8.31831903474163f, + 8.31672893855239f, 8.31680279552442f, 8.31521320332393f, 8.31362423794623f, + 8.31369772764720f, 8.31210926707506f, 8.31218251344738f, 8.31059455838471f, + 8.31066756263400f, 8.30908011377724f, 8.30915287710116f, 8.30756593513926f, + 8.30763845872760f, 8.30605202434212f, 8.30612430937688f, 8.30453838324206f, + 8.30461043089745f, 8.30302501368025f, 8.30309682512281f, 8.30151191748300f, + 8.30158349387164f, 8.29999909646184f, 8.30007043894792f, 8.29848655241367f, + 8.29855766214103f, 8.29697428712085f, 8.29704516522590f, 8.29546230235136f, + 8.29553294996313f, 8.29395059985889f, 8.29402101809908f, 8.29243918138296f, + 8.29250937136602f, 8.29092804864905f, 8.29099801148223f, 8.28941720336873f, + 8.28948694015212f, 8.28790664723974f, 8.28797615906635f, 8.28639638194612f, + 8.28646566990194f, 8.28488640915835f, 8.28495547432238f, 8.28337673053339f, + 8.28344557397773f, 8.28186734771489f, 8.28193597050476f, 8.27017819449824f, + 8.27024585417908f, 8.26866792233384f, 8.26873536584578f, 8.26715795557297f, + 8.26722518394961f, 8.26564829578422f, 8.26571531005260f, 8.26413894452349f, + 8.26420574570412f, 8.26262990333412f, 8.26269649244104f, 8.26276297588936f, + 8.26118755178779f, 8.25961275728051f, 8.25967892525651f, 8.25810465544098f, + 8.25817061434708f, 8.25823646908936f, 8.25666262054728f, 8.25672826770033f, + 8.25515494533288f, 8.25522038587603f, 8.25364759016769f, 8.25371282507417f, + 8.25214055650372f, 8.25220558674063f, 8.25063384578123f, 8.25069867230963f, + 8.24912745942885f, 8.24919208320380f, 8.24925660595830f, 8.24768582083426f, + 8.24775014225673f, 8.24617988660073f, 8.24624400763143f, 8.24467428189162f, + 8.24473820346495f, 8.24316900808411f, 8.24323273112868f, 8.24166406654423f, + 8.24172759198290f, 8.24179101896291f, 8.24022278737693f, 8.24028601812464f, + 8.23871831864912f, 8.23878135407322f, 8.23721418712705f, 8.23727702813061f, + 8.23733977226080f, 8.23577304160806f, 8.23583559266193f, 8.23426939580623f, + 8.23433175467303f, 8.23276609201523f, 8.23282825957879f, 8.23126313151474f, + 8.23132510865348f, 8.22976051557408f, 8.22982230316103f, 8.22988399629783f, + 8.22831984435517f, 8.22838134923978f, 8.22681773347944f, 8.22687905097167f, + 8.22694027508851f, 8.22537710272175f, 8.22543814072614f, 8.22387550570830f, + 8.22393635844707f, 8.22237426113974f, 8.22243492945458f, 8.22087337021466f, + 8.22093385494218f, 8.22099424818818f, 8.21943313609370f, 8.21787265404081f, + 8.21793277408334f, 8.21799280346968f, 8.21643277007580f, 8.21649261876342f, + 8.21493312522628f, 8.21499279402937f, 8.21505237319400f, 8.21349333040847f, + 8.21355273090070f, 8.21199422903237f, 8.21205345165425f, 8.21211258564023f, + 8.21055453657232f, 8.20899711749525f, 8.20905598676346f, 8.20911476818710f, + 8.20755780332721f, 8.20761640965019f, 8.20767492871591f, 8.20611841935722f, + 8.20456253992591f, 8.20462079838524f, 8.20467897036231f, 8.20473705604943f, + 8.20318154739142f, 8.20323946107331f, 8.20168449663402f, 8.20174223907312f, + 8.20018781913647f, 8.20024539109073f, 8.19869151593658f, 8.19874891815950f, + 8.19719558806369f, 8.19725282130430f, 8.19730997033329f, 8.19736703533627f, + 8.19581408270369f, 8.19265325254094f, 8.19271006657415f, 8.19115900249672f, + 8.19121565013490f, 8.18966513207149f, 8.18650958272842f, 8.18656598214056f, + 8.18501734969057f, 8.18346934519575f, 8.18192196836458f, 8.17717170379845f, + 8.17562699749445f, 8.17408291704024f, 8.17253946214471f, 8.17099663251667f, + 8.16945442786483f, 8.16791284789780f, 8.16796835096100f, 8.16642731496180f, + 8.16488690323892f, 8.16334711550016f, 8.16180795145325f, 8.16026941080583f, + 8.15873149326542f, 8.15878643699898f, 8.15724906305914f, 8.15571231181225f, + 8.15417618296508f, 8.15264067622436f, 8.15269522559432f, 8.15116026230331f, + 8.14962592070057f, 8.14809220049210f, 8.14655910138382f, 8.14502662308157f, + 8.14508070479135f, 8.14354876964219f, 8.14201745487630f, 8.14048676019884f, + 8.14054053346655f, 8.13901038188417f, 8.13748084996421f, 8.13595193741119f, + 8.13442364392953f, 8.13289596922361f, 8.13294928474971f, 8.13142215280177f, + 8.12989563919925f, 8.12836974364590f, 8.12842275724862f, 8.12689740436780f, + 8.12537266910277f, 8.12384855115666f, 8.12390126539209f, 8.12237769002218f, + 8.12085473153483f, 8.11933238963259f, 8.11938480702804f, 8.11786300759498f, + 8.11634182430774f, 8.11482125686830f, 8.11487337992274f, 8.11335335483497f, + 8.11183394515285f, 8.11031515057783f, 8.10879697081129f, 8.10727940555463f, + 8.10733109172207f, 8.10581406839611f, 8.10429765913410f, 8.10278186363689f, + 8.10283326158928f, 8.10131800788042f, 8.09980336748775f, 8.09828934011163f, + 8.09834045225178f, 8.09682696651178f, 8.09531409333708f, 8.09380183242756f, + 8.09229018348308f, 8.09234094169636f, 8.09082983407241f, 8.09088045174303f, + 8.08780945305625f, 8.08630017906285f, 8.08635058701088f, 8.08484185400836f, + 8.08489212286426f, 8.08338393098375f, 8.08187634939786f, 8.08192641069197f, + 8.08041937019749f, 8.07891293953684f, 8.07740711840818f, 8.07745690494605f, + 8.07595162471175f, 8.07444695354630f, 8.07294289114748f, 8.07143943721306f, + 8.07148888347334f, 8.06998597006411f, 8.06848366465346f, 8.06698196693880f, + 8.06548087661756f, 8.06552998607523f, 8.06402943589713f, 8.06252949264403f, + 8.06103015601301f, 8.06107899849888f, 8.05958020178404f, 8.05808201122071f, + 8.05658442650561f, 8.05663300418972f, 8.05513595915549f, 8.05363951949682f, + 8.05368789998958f, 8.05219199993907f, 8.05069670478969f, 8.04920201423756f, + 8.04925013366991f, 8.04775598247604f, 8.04626243540297f, 8.04476949214651f, + 8.04481735262478f, 8.04332494846872f, 8.04183314765086f, 8.04188081528225f, + 8.04038955347468f, 8.03889889452528f, 8.03740883812929f, 8.03745625043850f, + 8.03596673278105f, 8.03447781719510f, 8.03452503935586f, 8.03303666240761f, + 8.03154888704735f, 8.03006171296983f, 8.03010868337276f, 8.02862204764774f, + 8.02713601272015f, 8.02565057828453f, 8.02569729893665f, 8.02421240256096f, + 8.02272810619019f, 8.02124440951864f, 8.02129088240572f, 8.01980772349432f, + 8.01985407323696f, 8.01837145214721f, 8.01688943008388f, 8.01540800674083f, + 8.01392718181196f, 8.01397322582548f, 8.01401920905608f, 8.01253886078973f, + 8.01105911056564f, 8.00957995807722f, 8.00810140301796f, 8.00814708413104f, + 8.00819270517784f, 8.00519164505985f, 8.00523714632877f, 8.00376026217617f, + 8.00380564413810f, 8.00232929694652f, 8.00085354600357f, 7.99937839100213f, + 7.99942353575290f, 7.99946862149134f, 7.99647489443730f, 7.99651986249756f, + 7.99504637603060f, 7.99357348450695f, 7.99210118761919f, 7.99214592170044f, + 7.99067416093170f, 7.98920299429772f, 7.98924755409054f, 7.98777692341388f, + 7.98782136757941f, 7.98635127289017f, 7.98488177163773f, 7.98492604320402f, + 7.98345707777071f, 7.98198870526966f, 7.98052092539286f, 7.98056496838578f, + 7.97909772395840f, 7.97914165332759f, 7.97767494437153f, 7.97620882733528f, + 7.97625258709119f, 7.97478700534684f, 7.97332201501439f, 7.97185761578544f, + 7.97039380735165f, 7.97043728659283f, 7.96897401286660f, 7.96751132942632f, + 7.96755464165088f, 7.96609249272969f, 7.96463093358420f, 7.96467407975223f, + 7.96321305493386f, 7.96325609092729f, 7.96179560044416f, 7.96033569902192f, + 7.96037857054268f, 7.95891920325775f, 7.95746042452055f, 7.95750313250930f, + 7.95604488770810f, 7.95458723094048f, 7.95462977633074f, 7.95317265329447f, + 7.95171611777669f, 7.95175850149487f, 7.95030249950052f, 7.95034477594852f, + 7.94888930747411f, 7.94743442579445f, 7.94747654209885f, 7.94602219372557f, + 7.94456843162926f, 7.94461038869789f, 7.94315715969122f, 7.94319901110364f, + 7.94174631517672f, 7.94029420479812f, 7.94033589847266f, 7.93888432095161f, + 7.93743332845856f, 7.93747486528495f, 7.93602440542419f, 7.93457453007028f, + 7.93461591093157f, 7.93316656798164f, 7.93320784535352f, 7.93175903478923f, + 7.93031080799686f, 7.93035193086073f, 7.92890423622049f, 7.92745712482865f, + 7.92749809405007f, 7.92605151457391f, 7.92609238184470f, 7.92464633425995f, + 7.92320086918425f, 7.92324158424087f, 7.92179665081508f, 7.92035229937259f, + 7.92039286306353f, 7.91894904302663f, 7.91898950627539f, 7.91754621761402f, + 7.91610351019241f, 7.91614382347568f, 7.91470164718040f, 7.91474186094918f, + 7.91330021574662f, 7.91185915103762f, 7.91041866651197f, 7.91045868235363f, + 7.91049864894173f, 7.90905864587311f, 7.90761922254927f, 7.90618037865992f, + 7.90622014914004f, 7.90625987081860f, 7.90334410091085f, 7.90338372525529f, + 7.90194652065353f, 7.90198604802103f, 7.90054937343709f, 7.89911327700454f, + 7.89915265957254f, 7.89771709289137f, 7.89775637936779f, 7.89632134239166f, + 7.89488688281196f, 7.89492602580883f, 7.89349209566332f, 7.89353114344322f, + 7.89209774268232f, 7.89213669559212f, 7.89070382416489f, 7.88927152915260f, + 7.88931034040396f, 7.88787857444832f, 7.88791729168944f, 7.88648605473636f, + 7.88652467830826f, 7.88509397030232f, 7.87347922114931f, 7.87351729104996f, + 7.87208727383296f, 7.87212525190789f, 7.87069576429284f, 7.86926685183945f, + 7.86930469279725f, 7.86787630965334f, 7.86791405961169f, 7.86648620571214f, + 7.86505892620407f, 7.86509654027728f, 7.86366978971717f, 7.86370731360733f, + 7.86228109192717f, 7.86231852595811f, 7.86089283308866f, 7.86093017758265f, + 7.85950501345345f, 7.85808042248116f, 7.85811763327064f, 7.85669357073507f, + 7.85673069278658f, 7.85530715861446f, 7.85388419682346f, 7.85392118636180f, + 7.85249875262700f, 7.85253565421687f, 7.85111374846223f, 7.84969241431120f, + 7.84972918456513f, 7.84830837808339f, 7.84834506116903f, 7.84692478227781f, + 7.84696137850464f, 7.84554162712403f, 7.84412244633333f, 7.84415891284905f, + 7.84274025925302f, 7.85141288690937f, 7.85144961512784f, 7.85003244871711f, + 7.85006909037938f, 7.84865244984218f, 7.84868900525374f, 7.84727289050818f, + 7.84730935997309f, 7.84589377093614f, 7.84593015475706f, 7.84451509134460f, + 7.84310059603029f, 7.84313685194958f, 7.84172288193889f, 7.84175905296461f, + 7.84179518165529f, 7.84038169460074f, 7.83896877493622f, 7.83900477706657f, + 7.83759238244247f, 7.83618055466107f, 7.83621643089471f, 7.83480512782671f, + 7.83484092049560f, 7.83343014204975f, 7.83346585144543f, 7.83205559752943f, + 7.83209122394205f, 7.83068149446259f, 7.83071703818099f, 7.82930783304372f, + 7.82789919324926f, 7.82793461346494f, 7.82652649767837f, 7.82656183591605f, + 7.82515424404068f, 7.82518950058452f, 7.82378243252265f, 7.82381760765553f, + 7.82241106330847f, 7.82244615731193f, 7.81815980032046f, 7.81963467968251f, + 7.81822978630920f, 7.81682545615005f, 7.81542168889519f, 7.81545654118684f, + 7.81405329648404f, 7.81408806875962f, 7.81268534650601f, 7.81272003904066f, + 7.81131783913241f, 7.81135245220004f, 7.80995077453237f, 7.80998530840561f, + 7.80858415287283f, 7.80718355872843f, 7.80721797431856f, 7.80581790196027f, + 7.80585223903216f, 7.80445268835214f, 7.80448694717412f, 7.80308791806361f, + 7.80312209890276f, 7.80172359125211f, 7.80175769437431f, 7.80035970807297f, + 7.80039373374288f, 7.79899626867941f, 7.79903021716051f, 7.79763327322259f, + 7.79766714477715f, 7.79627072185158f, 7.79630451674069f, 7.79490861471341f, + 7.79494233319698f, 7.79354695195305f, 7.79215212935342f, 7.79218573371345f, + 7.79221930016098f, 7.79082496013553f, 7.78943117826415f, 7.78946463135824f, + 7.78807136967477f, 7.78810474751849f, 7.78671200590435f, 7.78674530875124f, + 7.78677857419443f, 7.78538631518947f, 7.78399461335494f, 7.78402776696423f, + 7.78263658483832f, 7.78266966420460f, 7.78270270654283f, 7.78131200703774f, + 7.77992186396267f, 7.77995479558889f, 7.77856517185444f, 7.77859802998139f, + 7.77863085145093f, 7.77724171033669f, 7.77585312490987f, 7.77588583677433f, + 7.77449777031297f, 7.77453040941203f, 7.77456301221936f, 7.77317542836560f, + 7.77178839945454f, 7.77182089374904f, 7.77043438342129f, 7.77046680567449f, + 7.77049919199683f, 7.76911316425229f, 7.76772769070368f, 7.76775996959096f, + 7.76637501423668f, 7.76640722179721f, 7.76502278450610f, 7.76505492097597f, + 7.76367100161614f, 7.76370306723039f, 7.76373509750465f, 7.76235166066185f, + 7.76096877676598f, 7.76100070136998f, 7.75961833500535f, 7.75965018945267f, + 7.75968200890831f, 7.75830012500606f, 7.75691879329882f, 7.75695050812457f, + 7.75698218818832f, 7.75560133890093f, 7.75563294961187f, 7.75425261742615f, + 7.75287283668173f, 7.75290434378954f, 7.75152507975708f, 7.75155651807875f, + 7.75158792209177f, 7.75020914037975f, 7.74883090935359f, 7.74745322870535f, + 7.74748449604369f, 7.74610733143631f, 7.74613853077156f, 7.74476188206386f, + 7.74479301361756f, 7.74341688066770f, 7.74344794466043f, 7.74347897495509f, + 7.74210332397728f, 7.74072822211493f, 7.74075915164364f, 7.74079004769272f, + 7.73941542773344f, 7.73944625698609f, 7.73807215231902f, 7.73810291499155f, + 7.73672932547114f, 7.73676002177895f, 7.73538694725902f, 7.73541757741657f, + 7.73404501775030f, 7.73407558197112f, 7.73270353701110f, 7.73273403550780f, + 7.73136250510597f, 7.73139293809028f, 7.73142333839748f, 7.73005228978068f, + 7.72868178804865f, 7.72871209063963f, 7.72874236076320f, 7.72737234072627f, + 7.72740254607128f, 7.72603304009822f, 7.72606318087234f, 7.72469418881164f, + 7.72472426522164f, 7.72335578692120f, 7.72338579917296f, 7.72341577942255f, + 7.72204778277866f, 7.72068033154013f, 7.72071021608960f, 7.72074006884081f, + 7.71937309915523f, 7.71940288846195f, 7.71803643202356f, 7.71806615808777f, + 7.71670021474117f, 7.71672987776400f, 7.71536444735324f, 7.71399956106752f, + 7.71402912990351f, 7.71405866744353f, 7.71269426243436f, 7.71272373753127f, + 7.71135984498676f, 7.71138925783831f, 7.71002587760030f, 7.71005522840340f, + 7.70869236031320f, 7.70872164926393f, 7.70875090736174f, 7.70738852045593f, + 7.70602667618316f, 7.70605584201409f, 7.70608497718635f, 7.70472361397175f, + 7.70475268797144f, 7.70339183636089f, 7.70342084938044f, 7.70344983198180f, + 7.70208946144320f, 7.70211838335150f, 7.70075852418828f, 7.70078738559392f, + 7.69942803764294f, 7.69945683873552f, 7.69809800183312f, 7.69812674280145f, + 7.69815545377820f, 7.69679709781559f, 7.69682574894983f, 7.69546790380054f, + 7.69549649527912f, 7.69552505699968f, 7.69416769278661f, 7.69419619513033f, + 7.69283934149161f, 7.69286778464339f, 7.69151144141215f, 7.69015563785611f, + 7.69018399256504f, 7.69021231788464f, 7.68885699496583f, 7.68888526164366f, + 7.68891349906885f, 7.68755865684680f, 7.68620435356727f, 7.68623250350642f, + 7.68626062437385f, 7.68490680163368f, 7.68493486449261f, 7.68358155123853f, + 7.68360955626822f, 7.68225675232974f, 7.68228469970871f, 7.68231261832904f, + 7.68096029482092f, 7.68098815605693f, 7.67963634161052f, 7.67966414563891f, + 7.67831284008200f, 7.67834058707873f, 7.67836830562541f, 7.67701748037903f, + 7.67704514215666f, 7.67569482554126f, 7.67572243072402f, 7.67437262256575f, + 7.67440017132709f, 7.67442769194256f, 7.67307836396437f, 7.67172957219709f, + 7.67175700863318f, 7.67040872479895f, 7.67043610532978f, 7.67046345797203f, + 7.66911565404928f, 7.66914295104199f, 7.66917022027359f, 7.66782289629849f, + 7.66785011013471f, 7.66650329373374f, 7.66653045234317f, 7.66518414333889f, + 7.66521124688943f, 7.66523832297326f, 7.66389249376287f, 7.66254719901806f, + 7.66257419295179f, 7.66260115958519f, 7.66262809895966f, 7.66128325660027f, + 7.66131014158067f, 7.65996580606912f, 7.65999263682000f, 7.65864880797621f, + 7.65867558466144f, 7.65870233437509f, 7.65735898508777f, 7.65738568098033f, + 7.65604283808071f, 7.65606948031442f, 7.65472714362097f, 7.65475373235741f, + 7.65478029440560f, 7.65343843708835f, 7.65346494588041f, 7.65349142810445f, + 7.65215005018116f, 7.65080920452514f, 7.65083560728422f, 7.65086198363444f, + 7.64952161716499f, 7.64954794081617f, 7.64957423817702f, 7.64823435090775f, + 7.64826059580594f, 7.64692121388197f, 7.64694740647428f, 7.64560852971063f, + 7.64563467015326f, 7.64566078457940f, 7.64432238681307f, 7.64434844932262f, + 7.64301055642301f, 7.64303656717059f, 7.64306255209497f, 7.64172513809099f, + 7.64175107148420f, 7.64041416205060f, 7.64044004406572f, 7.64046590044903f, + 7.63912946980476f, 7.63915527503849f, 7.63918105475433f, 7.63784510290134f, + 7.63787083169477f, 7.63653538400034f, 7.63656106202205f, 7.63522611829733f, + 7.63525174569743f, 7.63527734784282f, 7.63394288268123f, 7.63396843442906f, + 7.63263447293292f, 7.63265997443180f, 7.63268545086176f, 7.63135196780901f, + 7.63137739421188f, 7.63004441451777f, 7.63006979104072f, 7.63009514267872f, + 7.62876264130389f, 7.62878794328165f, 7.62745594495614f, 7.62748119741940f, + 7.62750642517989f, 7.62617490504528f, 7.62620008350880f, 7.62622523737801f, + 7.62489419542260f, 7.62491930021111f, 7.62358876087232f, 7.62361381672361f, + 7.62228377980812f, 7.62230878686512f, 7.62233376957839f, 7.62100421058442f, + 7.62102914471672f, 7.61970008782946f, 7.61972497352234f, 7.61974983504840f, + 7.61842125594179f, 7.61844606923969f, 7.61847085847612f, 7.61714275713074f, + 7.61716749834905f, 7.61583989866594f, 7.61586459200549f, 7.61588926145774f, + 7.61456213938845f, 7.61458676116997f, 7.61326014044020f, 7.61328471468899f, + 7.61330926522300f, 7.61198312195571f, 7.61200762516301f, 7.61203210475825f, + 7.61070643892753f, 7.61073087140089f, 7.60940570645537f, 7.60943009194275f, + 7.60945445398794f, 7.60812976632134f, 7.60815408158342f, 7.60817837350445f, + 7.60685416308698f, 7.60687840842660f, 7.60555469843407f, 7.60557889732612f, + 7.60560307304439f, 7.60427984013719f, 7.60430396960766f, 7.60432807600398f, + 7.60300532014849f, 7.60302938049563f, 7.60170712459905f, 7.60173113902882f, + 7.60175513054917f, 7.60043335153450f, 7.60045729733421f, 7.60048122032260f, + 7.59915991815251f, 7.59918379561589f, 7.59786299293304f, 7.59788682500121f, + 7.59791063442030f, 7.59659030840635f, 7.59661407262401f, 7.59663781428919f, + 7.59531796490316f, 7.59534166155958f, 7.59402231118364f, 7.59404596295913f, + 7.59406959234195f, 7.59275071841253f, 7.59277430310528f, 7.59279786550050f, + 7.59147946797307f, 7.59150298586800f, 7.59018508686814f, 7.59020856038869f, + 7.59023201176912f, 7.58891458898425f, 7.58893799617829f, 7.58896138132594f, + 7.58764443470803f, 7.58766777585621f, 7.58635132727817f, 7.58637462455093f, + 7.58639789993235f, 7.58508192732879f, 7.58510515901996f, 7.58512836891210f, + 7.58515155703590f, 7.58383603861766f, 7.58385918329649f, 7.58254416248636f, + 7.58256726384219f, 7.58259034358211f, 7.58127579858836f, 7.58129883518735f, + 7.58132185026121f, 7.58000778102858f, 7.58003075314258f, 7.57871718101943f, + 7.57874011029371f, 7.57876301819304f, 7.57744992162713f, 7.57747278686613f, + 7.57749563081961f, 7.57618300975245f, 7.57620581122397f, 7.57622859149893f, + 7.57491644587078f, 7.57493918384116f, 7.57496190070343f, 7.57365023045334f, + 7.57367290518743f, 7.57369555890135f, 7.57238436396716f, 7.57240697572833f, + 7.57109627695034f, 7.57111884687516f, 7.57114139592532f, 7.56983117224850f, + 7.56985367963612f, 7.56987616623575f, 7.56856641759483f, 7.56858886270476f, + 7.56861128711287f, 7.56730201344145f, 7.56732439653175f, 7.56734675900595f, + 7.56603796023641f, 7.56606028156374f, 7.56608258236019f, 7.56477425842381f, + 7.56479651824339f, 7.56348868948821f, 7.56351090844373f, 7.56353310700940f, + 7.56355528521325f, 7.56224791073235f, 7.56227004829653f, 7.56229216558275f, + 7.56098526572191f, 7.56100734253566f, 7.55970093738852f, 7.55972297384077f, + 7.55974499015377f, 7.55843905939626f, 7.55846103551346f, 7.55848299157400f, + 7.55850492760535f, 7.55719945116063f, 7.55722134721568f, 7.55724322332355f, + 7.55593822119919f, 7.55596005749435f, 7.55465554945120f, 7.55467734604222f, + 7.55469912282176f, 7.55339508886160f, 7.54282966651951f, 7.55343854360594f, + 7.55346024140898f, 7.55215666177545f, 7.55217832025123f, 7.55087523421793f, + 7.54031819934936f, 7.54033979903436f, 7.53904143572760f, 7.53906299635162f, + 7.53908453748486f, 7.54306747979114f, 7.54837830652259f, 7.54839978934178f, + 7.54709812458524f, 7.53655365351434f, 7.53657507825590f, 7.53527812830351f, + 7.53529951445738f, 7.54588255528895f, 7.54590390295925f, 7.54460316609662f, + 7.54462447538711f, 7.53408833025382f, 7.53279230299647f, 7.53281355491099f, + 7.53151801756132f, 7.53153923135380f, 7.53156042612371f, 7.53158160189662f, + 7.53028651627133f, 7.53030765412677f, 7.53032877306164f, 7.52903415812099f, + 7.52905523929085f, 7.52776111376522f, 7.52778215727126f, 7.52780318198321f, + 7.52782418792625f, 7.52653051408721f, 7.52655148256772f, 7.52657243235452f, + 7.52527922893053f, 7.52530014140483f, 7.52532103526024f, 7.52402830215954f, + 7.52404915885167f, 7.52406999699933f, 7.52277773412920f, 7.52279853526202f, + 7.52281931792438f, 7.52284008214087f, 7.52154827098634f, 7.52156899838468f, + 7.52027767569381f, 7.52029836637182f, 7.52031903872623f, 7.52033969278137f, + 7.51904882176237f, 7.51906943929178f, 7.51909003859465f, 7.51779963749868f, + 7.51782020042086f, 7.51784074518883f, 7.51655081391820f, 7.51657132244977f, + 7.51659181289909f, 7.51530235135519f, 7.51532280571164f, 7.51534324205739f, + 7.51536366041622f, 7.51407465053640f, 7.51409503299255f, 7.51280651060282f, + 7.51282685725088f, 7.51284718603027f, 7.51286749696450f, 7.51157942617877f, + 7.51159970149310f, 7.51161995903261f, 7.51033235764065f, 7.51035257970054f, + 7.51037278405555f, 7.50908565195404f, 7.50910582096898f, 7.50912597234864f, + 7.50783930943340f, 7.50785942561180f, 7.50787952422413f, 7.50789960529340f, + 7.50661339393931f, 7.50663343999128f, 7.50534771513269f, 7.50536772625885f, + 7.50538771995631f, 7.50540769624784f, 7.50412242287476f, 7.50414236442252f, + 7.50416228863237f, 7.50418219552691f, 7.50289737369020f, 7.50291724602175f, + 7.50163291019824f, 7.50165274805672f, 7.50167256871229f, 7.50169237218731f, + 7.50040848781655f, 7.50042825699740f, 7.50044800906457f, 7.49916459326124f, + 7.49918431116761f, 7.49920401202679f, 7.49792106467948f, 7.49794073151054f, + 7.49796038136060f, 7.49667790235710f, 7.49669751831102f, 7.49671711734975f, + 7.49673669949516f, 7.49545467185099f, 7.49547422027520f, 7.49549375187145f, + 7.49421219240963f, 7.49423169041509f, 7.49425117165763f, 7.49297008026330f, + 7.49298952804478f, 7.49300895912805f, 7.49302837353459f, 7.49174773343687f, + 7.49176711455430f, 7.49178647905926f, 7.49050630686120f, 7.49052563820524f, + 7.49054495300073f, 7.48926524858490f, 7.48928453034700f, 7.48930379562416f, + 7.48932304443751f, 7.48804379124302f, 7.48806300719200f, 7.48808220674033f, + 7.48680342115406f, 7.48682258796403f, 7.48684173843620f, 7.48556342033819f, + 7.48558253819737f, 7.48560163978127f, 7.48562072511066f, 7.48434285814650f, + 7.48436191102906f, 7.48438094771922f, 7.48310354806328f, 7.48312255243049f, + 7.48314154066708f, 7.48316051279358f, 7.48188356423386f, 7.48190250420160f, + 7.48192142812062f, 7.48064494668490f, 7.48066383856759f, 7.48068271446260f, + 7.47940670002678f, 7.47942554400726f, 7.47944437206081f, 7.47946318420761f, + 7.47818762076032f, 7.47820640115404f, 7.47822516570133f, 7.47824391442227f, + 7.47696880197852f, 7.47698751910672f, 7.47571288915281f, 7.47573157476804f, + 7.47575024465661f, 7.47576889883838f, 7.47449471975386f, 7.47451334258139f, + 7.47453194976145f, 7.47455054131374f, 7.47327681310897f, 7.47329537346471f, + 7.47202212722719f, 7.47204065646482f, 7.47205917017273f, 7.47207766837044f, + 7.47080487287198f, 7.47082334010767f, 7.47084179189150f, 7.47086022824284f, + 7.46958788348929f, 7.46960628903368f, 7.46962467920354f, 7.46835280053496f, + 7.46837116001347f, 7.46838950417511f, 7.46840783303904f, 7.46713640504500f, + 7.46715470337084f, 7.46717298645624f, 7.46719125432022f, 7.46592027700163f, + 7.46593851447977f, 7.46595673679339f, 7.46597494396140f, 7.46470441731737f, + 7.46472259425075f, 7.46345254840048f, 7.46347069517447f, 7.46348882689689f, + 7.46350694358646f, 7.46223734825421f, 7.46225543493411f, 7.46227350663713f, + 7.46229156338184f, 7.46102241856245f, 7.46104044544623f, 7.46105845742730f, + 7.45978977797966f, 7.45980776021071f, 7.45982572759437f, 7.45855751338129f, + 7.45857545112528f, 7.45859337407692f, 7.45861128225450f, 7.45734351838243f, + 7.45736139706664f, 7.45737926103147f, 7.45611196217244f, 7.45612979675298f, + 7.45614761666857f, 7.45616542193726f, 7.45489857332325f, 7.45491634935229f, + 7.45493411078849f, 7.45495185764981f, 7.45368545926693f, 7.45370317703227f, + 7.45372088027644f, 7.45245494659468f, 7.45247262084999f, 7.45249028063757f, + 7.45250792597517f, 7.45124244242089f, 7.45126005891167f, 7.45127766100555f, + 7.45129524872019f, 7.45003021527506f, 7.45004777428398f, 7.44878321952857f, + 7.44880074990199f, 7.44881826598392f, 7.44883576779183f, 7.43727548639956f, + 7.43729277382540f, 7.43731004718073f, 7.43732730648274f, 7.43606346612857f, + 7.43608069737530f, 7.43609791462004f, 7.43483453871790f, 7.43485172800980f, + 7.43486890335078f, 7.43488606475783f, 7.43362313924374f, 7.43364027283374f, + 7.43365739254057f, 7.43367449838109f, 7.43241202322571f, 7.43242910138409f, + 7.43244616572660f, 7.43118415468149f, 7.43120119144248f, 7.43121821443777f, + 7.43123522368404f, 7.42997366286784f, 7.42999064466599f, 7.43000761276498f, + 7.43002456718139f, 7.42876345656070f, 7.42878038366152f, 7.42879729712929f, + 7.42753665026940f, 7.42755353652043f, 7.42757040918772f, 7.42758726828765f, + 7.42632707148660f, 7.42634390350084f, 7.42636072199670f, 7.42637752699047f, + 7.42511778021092f, 7.42513455824921f, 7.42387528831073f, 7.42389203945833f, + 7.42390877718483f, 7.42392550150634f, 7.42266668138160f, 7.42268337894139f, + 7.42270006314441f, 7.42271673400666f, 7.42145836365485f, 7.42147500788352f, + 7.42149163881933f, 7.42023373145931f, 7.42025033585711f, 7.42026692700973f, + 7.42028350493299f, 7.41902604719678f, 7.41904259870875f, 7.41905913703874f, + 7.41907566220248f, 7.41781865404551f, 7.41783515292387f, 7.41785163868306f, + 7.41659509314730f, 7.41661155271502f, 7.41662799921040f, 7.41664443264901f, + 7.41538833653703f, 7.41540474390869f, 7.41542113827012f, 7.41543751963679f, + 7.41418187290033f, 7.41419822832378f, 7.41421457079872f, 7.41295938630599f, + 7.41297570292999f, 7.41299200665152f, 7.41300829748587f, 7.41175356220701f, + 7.41176982731279f, 7.41178607957713f, 7.41180231901523f, 7.41054803289840f, + 7.41056424672953f, 7.40931043523787f, 7.40932662352253f, 7.40934279905659f, + 7.40935896185510f, 7.40937511193310f, 7.40812173667013f, 7.40813786135211f, + 7.40815397335853f, 7.40817007270433f, 7.40691714638039f, 7.40693322044968f, + 7.40568076819554f, 7.40569681704778f, 7.40571285331374f, 7.40572887700822f, + 7.40574488814600f}; + +// Set of scalers, one per reverberation time, by which the +// |kHighCorrectionCurve| and |kLowCorrectionCurve| curves are multiplied in +// order to generate the correct onset compensation curves. +static const float kCurveCorrectionMultipliers[kNumFeedbackValues] = { + 0.000249027612622225f, 0.0475827382701585f, 0.0982856973675534f, + 0.144077780284872f, 0.187710331328442f, 0.229214552437128f, + 0.268761703298196f, 0.306384317687497f, 0.342116205684862f, + 0.376122154314109f, 0.408019265118341f, 0.438826303187204f, + 0.468213758592444f, 0.496269855121223f, 0.523103160344453f, + 0.548734562484123f, 0.573191744554386f, 0.596776538466997f, + 0.619556090502133f, 0.646894092669191f, 0.661870435136273f, + 0.675494733319630f, 0.701821797958321f, 0.713358543560036f, + 0.731743894313465f, 0.749694785147017f, 0.758967096040535f, + 0.775801399270244f, 0.792218438223182f, 0.808206900102085f, + 0.815142557606016f, 0.830420164791760f, 0.845380788487056f, + 0.859865023229817f, 0.865296767216337f, 0.879331838452952f, + 0.893145291831122f, 0.897272345317607f, 0.910373690471610f, + 0.923470208927849f, 0.936268613920194f, 0.940724814169454f, + 0.943721434222406f, 0.956465808252697f, 0.959628673835810f, + 0.971718784707243f, 0.974554123797995f, 0.977943937294408f, + 0.997154666216203f, 0.999999999999996f, 0.00847079214371323f, + 0.0104396896429595f, 0.0383165227008017f, 0.0405943838116957f, + 0.0685361482242258f, 0.0701917817593536f, 0.0713216738657547f, + 0.0986119509733789f, 0.0995868953874107f, 0.127034424762083f, + 0.127777689282131f, 0.128246886910136f, 0.155790763336654f, + 0.156203946276228f, 0.183007628720406f, 0.182664049029335f, + 0.182636700276451f, 0.209980982953741f, 0.209930104556972f, + 0.209208261790760f, 0.236016550641923f, 0.235475840865507f, + 0.234825009787230f, 0.262017587951252f, 0.260795540834379f, + 0.259744215394585f, 0.286998748503651f, 0.286344090244485f, + 0.285483640041615f, 0.284539872612409f, 0.311891500333310f, + 0.310660815240075f, 0.309428379806926f, 0.336752370009492f, + 0.335612040225831f, 0.334119868753728f, 0.332881268861172f, + 0.360256878080152f, 0.358861080534800f, 0.357458025447185f, + 0.356097715473274f, 0.383869923377680f, 0.382590514632971f, + 0.381266229326427f, 0.379680871429225f, 0.407164010760711f, + 0.405597486292448f, 0.404000115485411f, 0.402266215035868f, + 0.429891973669762f, 0.428453570844335f, 0.426999237668900f, + 0.425435248817597f, 0.450978617489268f, 0.449350785137532f, + 0.447825393407652f, 0.446242285152424f, 0.444699143006987f, + 0.471928084570479f, 0.471460865818911f, 0.469975899073161f, + 0.468110718285786f, 0.466267910972978f, 0.492953377248067f, + 0.491503646910692f, 0.490004860015654f, 0.488499316392171f, + 0.516893492513121f, 0.515545795838069f, 0.514220820105042f, + 0.512893659480599f, 0.511085398658644f, 0.509264930798830f, + 0.537621048957386f, 0.536165057418577f, 0.534661930692635f, + 0.533210489850429f, 0.531756722686728f, 0.530384047322116f, + 0.559038131029643f, 0.557822105594252f, 0.556569707547800f, + 0.555271214486959f, 0.553432792863170f, 0.551315743703168f, + 0.549392825464220f, 0.577982280854402f, 0.576562976619477f, + 0.575372635620591f, 0.574072847452013f, 0.572872688648164f, + 0.571573384448578f, 0.570329067593286f, 0.599518572118246f, + 0.598348121604011f, 0.597160846836955f, 0.595942913014291f, + 0.592262360278945f, 0.590949984551833f, 0.589596537782439f, + 0.618986568059361f, 0.617798845452536f, 0.616549708449253f, + 0.615384701753942f, 0.614192476100642f, 0.612892560298438f, + 0.611692826552139f, 0.610490163023757f, 0.609174362863638f, + 0.638463194928117f, 0.637028017578980f, 0.635559187798163f, + 0.633766413298784f, 0.632208978019593f, 0.630423678935153f, + 0.629100361498165f, 0.627683147303628f, 0.626307825147069f, + 0.656136973617732f, 0.655009501850360f, 0.653877493459184f, + 0.652665127999537f, 0.651558827307289f, 0.650386889118506f, + 0.649111048361781f, 0.648028207288007f, 0.646759703278198f, + 0.645519124438692f, 0.675436924850795f, 0.674280803735362f, + 0.673132340969860f, 0.671373346843144f, 0.670134946644868f, + 0.668731343028307f, 0.667428990512785f, 0.666412032897529f, + 0.665280139756495f, 0.664193037044164f, 0.694188573172643f, + 0.693133764391982f, 0.692048704636038f, 0.690982103678122f, + 0.689946461409433f, 0.688925040506954f, 0.687918657972205f, + 0.686814035317773f, 0.685752672736224f, 0.684629857976692f, + 0.683538212068733f, 0.682502742914018f, 0.712650390189218f, + 0.711488074001633f, 0.710454507990280f, 0.709369576447197f, + 0.708293103236850f, 0.707161341261432f, 0.706142174509451f, + 0.705109933856033f, 0.703938122923563f, 0.702882741466471f, + 0.701795805729764f, 0.700970588654440f, 0.699907893809526f, + 0.698848748944422f, 0.729459722297675f, 0.728559828909388f, + 0.727267491809098f, 0.726368916168934f, 0.725549425012621f, + 0.724701162719866f, 0.723729724234480f, 0.722805513390802f, + 0.721877511846754f, 0.720923464905931f, 0.720063488137338f, + 0.719192489800377f, 0.718234018896418f, 0.748886813504199f, + 0.748055970602122f, 0.747192096825651f, 0.746255128515039f, + 0.745432542499951f, 0.744527975028176f, 0.743657128539979f, + 0.742815568594195f, 0.742016966380996f, 0.741081662145480f, + 0.740212516669136f, 0.738752388065193f, 0.737838959848034f, + 0.737079784380805f, 0.736207833150401f, 0.735328920674480f, + 0.766147300368082f, 0.765383964612946f, 0.764598877501888f, + 0.763830319512426f, 0.763071341941492f, 0.762261754967287f, + 0.761421505873705f, 0.760683170206014f, 0.759876633831137f, + 0.759111799483625f, 0.758326333824362f, 0.757541464414185f, + 0.756805357349899f, 0.755956399690542f, 0.755084876598806f, + 0.754190942656102f, 0.753440741268157f, 0.752571082952576f, + 0.751825304285737f, 0.782969421663051f, 0.782195656629464f, + 0.781491207489953f, 0.780796925868243f, 0.780038270784649f, + 0.779327959705502f, 0.778571499532576f, 0.777927257784694f, + 0.777170483964829f, 0.776461606951210f, 0.775702452890459f, + 0.774964094372046f, 0.774261007484278f, 0.773530800147826f, + 0.772829253854413f, 0.772012428008542f, 0.771256939594450f, + 0.770263007078531f, 0.769608778166788f, 0.768868370726016f, + 0.800132675567284f, 0.799414457498671f, 0.798801140955651f, + 0.798201875508813f, 0.797562927111111f, 0.796973690523352f, + 0.796325305652599f, 0.795751914017312f, 0.795062104562951f, + 0.794392825660217f, 0.793833580415710f, 0.793228467372150f, + 0.792632556794021f, 0.791987407625815f, 0.791349198936522f, + 0.790758300136560f, 0.790155406100658f, 0.789502309522568f, + 0.788850663559873f, 0.788244230667833f, 0.787690453856242f, + 0.787119319515071f, 0.786556345654314f, 0.785871663518153f, + 0.817012925306181f, 0.816430511458010f, 0.815893193830343f, + 0.815310874396162f, 0.814695628192059f, 0.814349340761576f, + 0.813807195954924f, 0.812960708290203f, 0.812353982324263f, + 0.811760341393159f, 0.811129283770446f, 0.810570029805507f, + 0.810041997821215f, 0.809539025954020f, 0.808890057582255f, + 0.808376532093617f, 0.807788126711313f, 0.807229044495192f, + 0.806691599106826f, 0.806133405950557f, 0.805529337345578f, + 0.804943724604441f, 0.804359069634119f, 0.803604313367394f, + 0.803039009317611f, 0.802427016830483f, 0.801897188869670f, + 0.833460875990872f, 0.832877329385786f, 0.832411116216460f, + 0.831855344205032f, 0.831306648690096f, 0.830826737217004f, + 0.830306337776770f, 0.829837395963940f, 0.829322442611281f, + 0.828828529233458f, 0.828227895618978f, 0.827688095121686f, + 0.827224825179024f, 0.826744576227558f, 0.826190089131040f, + 0.825644348776801f, 0.825111095394709f, 0.824576204498035f, + 0.824095489895994f, 0.823551761115446f, 0.823018027544573f, + 0.822476448619841f, 0.821809962673621f, 0.821291765423256f, + 0.820650961031543f, 0.819911412926238f, 0.819376250438516f, + 0.818827507074517f, 0.818298634930539f, 0.817837741058990f, + 0.817377454006327f, 0.849050771870725f, 0.848598343034601f, + 0.848137761126842f, 0.847720880735919f, 0.847272612414349f, + 0.846839043139818f, 0.846310781171764f, 0.845917411128721f, + 0.845432276174650f, 0.845016330807497f, 0.844560412891491f, + 0.844113268000604f, 0.843717326097535f, 0.843254715180455f, + 0.842771923047896f, 0.842317456952523f, 0.841894102789524f, + 0.841478900234186f, 0.841073526044248f, 0.840678089199553f, + 0.840282881990563f, 0.839851728795932f, 0.839437044180938f, + 0.839032992924171f, 0.838674041436160f, 0.838208899506653f, + 0.837815592964711f, 0.837413586062667f, 0.836945051441415f, + 0.836579931119682f, 0.836103168868015f, 0.835691507888582f, + 0.835190824315056f, 0.834796921260984f, 0.834397547217396f, + 0.833770693471246f, 0.833382919978899f, 0.865249714600488f, + 0.864778611194893f, 0.864359035626832f, 0.863974109667504f, + 0.863599275330096f, 0.863175308582704f, 0.862750738409356f, + 0.862410936451282f, 0.862060660613492f, 0.861703069468698f, + 0.861338903148747f, 0.860962071126888f, 0.860613235617068f, + 0.860255115049830f, 0.859941728589139f, 0.859556867553160f, + 0.859129368056343f, 0.858719511777174f, 0.858375007251166f, + 0.857991574276656f, 0.857077372527853f, 0.856709203488606f, + 0.856338576783691f, 0.855985971077822f, 0.855629124525839f, + 0.855290191537388f, 0.854966165735379f, 0.854612291162095f, + 0.854327456524765f, 0.854015841793238f, 0.853649356402945f, + 0.853317984411307f, 0.852952982901721f, 0.852579010983093f, + 0.852262745823692f, 0.851907995774951f, 0.851574167335807f, + 0.851264191083115f, 0.850902019876735f, 0.850580011598579f, + 0.850225307767087f, 0.849838013922043f, 0.881877389068535f, + 0.881425634756149f, 0.881222935599140f, 0.880862887645329f, + 0.880558209099982f, 0.880114949740768f, 0.879806159434422f, + 0.879538932468558f, 0.879173454249472f, 0.878803642529878f, + 0.878488774313953f, 0.878152689099870f, 0.877787934867571f, + 0.877472544637065f, 0.877200689447213f, 0.876929820734263f, + 0.876616544942982f, 0.876339352367848f, 0.876037900545338f, + 0.875737826612020f, 0.875450927592556f, 0.875146075620635f, + 0.874847876858510f, 0.874531696275083f, 0.874232625014850f, + 0.873956654705092f, 0.873657406655665f, 0.873376193316924f, + 0.873085430435485f, 0.872758743496618f, 0.872421095892080f, + 0.872098708018361f, 0.871788288227254f, 0.871530138982730f, + 0.871236056832414f, 0.870910572694844f, 0.870635091498100f, + 0.870312602324108f, 0.870054759273025f, 0.869714810151487f, + 0.869366798437594f, 0.869122644903574f, 0.868812734247120f, + 0.868513864159224f, 0.868223756623966f, 0.867982715181480f, + 0.867671997828998f, 0.867407549259445f, 0.867121915940559f, + 0.866777786882641f, 0.866494871692684f, 0.866169558022502f, + 0.865824390903947f, 0.865583948922912f, 0.865260577587179f, + 0.897442560230671f, 0.897197975041446f, 0.896936601622887f, + 0.896639930442358f, 0.896358496055820f, 0.896149517619677f, + 0.895865666553406f, 0.895617470802557f, 0.895302338320577f, + 0.895008903956437f, 0.894766131546458f, 0.894431674742994f, + 0.894121880084823f, 0.893833250070113f, 0.893486559142867f, + 0.893208534201067f, 0.892971102121326f, 0.892730883606376f, + 0.892486495783999f, 0.892266606766457f, 0.891912616713162f, + 0.891668012816153f, 0.891389344583694f, 0.891127210236977f, + 0.890877014364885f, 0.890647919776319f, 0.890418186238544f, + 0.890123493822626f, 0.889882930209191f, 0.889638857058379f, + 0.889365113843686f, 0.889063011963076f, 0.888904806210479f, + 0.888595224427484f, 0.888348740136566f, 0.888084256999739f, + 0.887840636004391f, 0.887568183405399f, 0.887244627634201f, + 0.886968902204163f, 0.886813404979471f, 0.886525300589349f, + 0.886292070226496f, 0.886001016727455f, 0.885775223543453f, + 0.885522319161176f, 0.885169241885031f, 0.884891814369998f, + 0.884613124661923f, 0.884274453041876f, 0.884000697538040f, + 0.883779270240537f, 0.883552044352765f, 0.883321450052730f, + 0.883113458915741f, 0.882876096712848f, 0.882646681017793f, + 0.882443778020906f, 0.882173341068617f, 0.881932486077329f, + 0.881703097548580f, 0.881512430676287f, 0.881324988614956f, + 0.881078415830145f, 0.880836019375906f, 0.880592380876914f, + 0.880399045447751f, 0.880144055756519f, 0.879916264512867f, + 0.912262761203549f, 0.912050025877916f, 0.911837032776423f, + 0.911637002893336f, 0.911410664361064f, 0.911128179710946f, + 0.910915857381980f, 0.910713468187444f, 0.910514321228273f, + 0.910306658353875f, 0.910045217950504f, 0.909852480850540f, + 0.909661981811356f, 0.909400554409887f, 0.909267222071404f, + 0.909082328173087f, 0.908854885017709f, 0.908645654905872f, + 0.908429066044714f, 0.908203229057788f, 0.907975524348559f, + 0.907844076324775f, 0.907589558754535f, 0.907386017765108f, + 0.907146503676051f, 0.906950671939951f, 0.906734850193100f, + 0.906517924929938f, 0.906308309667034f, 0.906112492983346f, + 0.905983410335819f, 0.905742671195132f, 0.905504049165944f, + 0.905260974598286f, 0.905102165719060f, 0.904889117286156f, + 0.904704031490403f, 0.904530549352657f, 0.904335967396413f, + 0.904167284044056f, 0.903943525425265f, 0.903781177741016f, + 0.903561239085713f, 0.903395399945175f, 0.903235371390208f, + 0.903016653613898f, 0.902845104975041f, 0.902610542350492f, + 0.902398193708679f, 0.902225960341570f, 0.902021465390646f, + 0.901838424668229f, 0.901655758835068f, 0.901444820937381f, + 0.901245949833824f, 0.901069151471710f, 0.900915074109771f, + 0.900760359450201f, 0.900578847724700f, 0.900395965994494f, + 0.900228168805928f, 0.900066616036273f, 0.899902868316372f, + 0.899691672138911f, 0.899502275011599f, 0.899326630297481f, + 0.899147517398502f, 0.898891311929557f, 0.898737589588815f, + 0.898516468975794f, 0.898279435824810f, 0.898133379766176f, + 0.897940625694500f, 0.897769012071762f, 0.897584094034260f, + 0.897386399090749f, 0.897204596940482f, 0.897029990887014f, + 0.896843984043430f, 0.896659433456950f, 0.896499592362130f, + 0.896326359805770f, 0.896161841889783f, 0.896002962811382f, + 0.895858985647756f, 0.895679053342135f, 0.895472630838065f, + 0.895298117862088f, 0.927754563825053f, 0.927595758579098f, + 0.927423098901489f, 0.927274066423043f, 0.927064318376423f, + 0.926889227267958f, 0.926684097147831f, 0.926538641708822f, + 0.926383924569828f, 0.926221023888201f, 0.926069942094448f, + 0.925936867669842f, 0.925764476234829f, 0.925474258843906f, + 0.925341480568837f, 0.925184458584946f, 0.924997556397188f, + 0.924807778651479f, 0.924662367744565f, 0.924518831561345f, + 0.924374320422518f, 0.924239452756272f, 0.924035083031803f, + 0.923866605961103f, 0.923708308696909f, 0.923561861399422f, + 0.923342111379313f, 0.923181861833928f, 0.923022655494797f, + 0.922830553518427f, 0.922680580975269f, 0.922530343438704f, + 0.922379253084900f, 0.922203220902534f, 0.922017473475193f, + 0.921852509706996f, 0.921632506820494f, 0.921478764462909f, + 0.921328663166398f, 0.921188703978730f, 0.920977965994368f, + 0.920847607574230f, 0.920703577888137f, 0.920525889242200f, + 0.920380588508078f, 0.920253615844872f, 0.920139601102595f, + 0.920016687689548f, 0.919852868556918f, 0.919622503761834f, + 0.919458278730055f, 0.919295852055926f, 0.919134591570401f, + 0.918979062183651f, 0.918841010886602f, 0.918716051757939f, + 0.918569984788460f, 0.918369062451782f, 0.918215421152890f, + 0.918088828939449f, 0.917919780942217f, 0.917733211893808f, + 0.917574574952106f, 0.917438548196200f, 0.917323625618981f, + 0.917182002664427f, 0.917002289446623f, 0.916827583253820f, + 0.916714472811547f, 0.916594418971258f, 0.916460972969900f, + 0.916335756364354f, 0.916196649287878f, 0.916065572511741f, + 0.915947599776994f, 0.915818586940659f, 0.915632981571024f, + 0.915459212086808f, 0.915265459093725f, 0.915128178161501f, + 0.914998176624468f, 0.914860825831538f, 0.914625484964234f, + 0.914493843678021f, 0.914371779571637f, 0.914248620563387f, + 0.914100275148066f, 0.913965738470841f, 0.913826791405259f, + 0.913703250466401f, 0.913546026990571f, 0.913439303707573f, + 0.913292218567478f, 0.913182541953684f, 0.913036917749445f, + 0.912916054634268f, 0.912755642259817f, 0.912614205240910f, + 0.912468319200128f, 0.912352343404882f, 0.912236216864747f, + 0.912140734213469f, 0.911996828465493f, 0.911863354404087f, + 0.911709187715872f, 0.911569284462607f, 0.911451616353229f, + 0.911303512456831f, 0.911177218626202f, 0.911072557873086f, + 0.910948265059310f, 0.910791791835805f, 0.910670891976214f, + 0.910537919420196f, 0.910401546439265f, 0.910274713004068f, + 0.942972886116868f, 0.942873255216079f, 0.942736199328047f, + 0.942643396902211f, 0.942519604430019f, 0.942405794729767f, + 0.942276761652276f, 0.942183483677777f, 0.942062466352059f, + 0.941936635001252f, 0.941827790721750f, 0.941701752257506f, + 0.941569181480861f, 0.941428171184760f, 0.941266420007758f, + 0.941177983355581f, 0.941068131173925f, 0.940966459861221f, + 0.940802771917656f, 0.940699319454695f, 0.940546307666082f, + 0.940436629963075f, 0.940335105964667f, 0.940233168267479f, + 0.940070294791264f, 0.939946864568674f, 0.939806207003117f, + 0.939699917303889f, 0.939575570410864f, 0.939449267180147f, + 0.939335059915008f, 0.939221634003170f, 0.939100130210696f, + 0.938992079038466f, 0.938888993643325f, 0.938794272868458f, + 0.938679778519673f, 0.938565198624842f, 0.938447781743272f, + 0.938348887694662f, 0.938231674973109f, 0.938100618555889f, + 0.937929151200885f, 0.937821433643558f, 0.937688230231913f, + 0.937566420334848f, 0.937429830160073f, 0.937311443695009f, + 0.937190423215931f, 0.937063169325198f, 0.936979087920893f, + 0.936811325718775f, 0.936731671218813f, 0.936634805076269f, + 0.936532298789202f, 0.936416186739817f, 0.936286492122839f, + 0.936179817873514f, 0.936053037111203f, 0.935955439730240f, + 0.935819042190242f, 0.935683118325825f, 0.935582469083303f, + 0.935446994096024f, 0.935322108430966f, 0.935220912945627f, + 0.935131513738277f, 0.934997955601702f, 0.934903645484091f, + 0.934768972260724f, 0.934650110711938f, 0.934559208948897f, + 0.934458767641773f, 0.934334472974010f, 0.934235823798025f, + 0.934104732808613f, 0.934019917031945f, 0.933918957071512f, + 0.933828574280968f, 0.933739424110845f, 0.933624421615613f, + 0.933536789816350f, 0.933419733757098f, 0.933286841283564f, + 0.933217203121627f, 0.933126139166788f, 0.933030450650661f, + 0.932884936176351f, 0.932779450273416f, 0.932651017715157f, + 0.932545945362592f, 0.932455958346926f, 0.932337686419334f, + 0.932227058302166f, 0.932025539224666f, 0.931920795784066f, + 0.931852577738301f, 0.931756049103193f, 0.931621152063621f, + 0.931541102847978f, 0.931464901339151f, 0.931333487828105f, + 0.931222317831001f, 0.931145274335559f, 0.931048712065999f, + 0.930944549228741f, 0.930833966893561f, 0.930767031403833f, + 0.930661219955564f, 0.930573682517608f, 0.930490587789090f, + 0.930399861447101f, 0.930260043354527f, 0.930174757179721f, + 0.930082735120729f, 0.929980062024817f, 0.929881070592492f, + 0.929815269359710f, 0.929685646724316f, 0.929601626627662f, + 0.929475080077155f, 0.929384083536765f, 0.929293843820907f, + 0.929197517689133f, 0.929071496407846f, 0.928928767504129f, + 0.928850812213896f, 0.928755858560461f, 0.928683595051250f, + 0.928619097034910f, 0.928515473276713f, 0.928415904127483f, + 0.928296589209896f, 0.928156514230767f, 0.928075740622194f, + 0.927976281024834f, 0.927893758602397f, 0.927793223751259f, + 0.927533278933111f, 0.927443413613116f, 0.927330462282895f, + 0.927224518557079f, 0.927121263013897f, 0.927032513483020f, + 0.926946653005310f, 0.926852163943676f, 0.926782799964173f, + 0.926704377754193f, 0.926593212214682f, 0.926514271256028f, + 0.926434387396852f, 0.926338455745648f, 0.926241793129996f, + 0.926151243722815f, 0.926062446007553f, 0.925975437993879f, + 0.925900994878278f, 0.925769198338033f, 0.925673561554096f, + 0.925575663106531f, 0.925500150132689f, 0.958275427245807f, + 0.958217720942176f, 0.958088351980738f, 0.958006794628661f, + 0.957895379103873f, 0.957838037581917f, 0.957720979374284f, + 0.957627497340097f, 0.957500947647853f, 0.957418120264750f, + 0.957316257911455f, 0.957217318469398f, 0.957134253993810f, + 0.957060018503037f, 0.956986278198766f, 0.956895348950843f, + 0.956777435571824f, 0.956705582605570f, 0.956594026023218f, + 0.956516152335888f, 0.956357855496398f, 0.956298018292689f, + 0.956199349632202f, 0.956126323885033f, 0.956032137481781f, + 0.955946413187796f, 0.955847188872627f, 0.955754430213996f, + 0.955687353291728f, 0.955609174338888f, 0.955480819432032f, + 0.955422957826911f, 0.955337878180787f, 0.955277719747449f, + 0.955209090297961f, 0.955115833029069f, 0.955013029263148f, + 0.954929052626773f, 0.954858410226777f, 0.954780241057584f, + 0.954681910461255f, 0.954620468882399f, 0.954550468034354f, + 0.954461164292287f, 0.954381926562481f, 0.954303937678699f, + 0.954176727439442f, 0.954102874300900f, 0.953998321230092f, + 0.953890423951280f, 0.953784655913618f, 0.953722369794478f, + 0.953623105806388f, 0.953565738290012f, 0.953492683988373f, + 0.953413141506086f, 0.953326969854586f, 0.953262954351555f, + 0.953184013301923f, 0.953101894037007f, 0.953041902190311f, + 0.952969534053552f, 0.952869749867014f, 0.952789835366490f, + 0.952716435862726f, 0.952645254649320f, 0.952542492593948f, + 0.952463994814448f, 0.952349666425407f, 0.952275493737817f, + 0.952196002674587f, 0.952116683488068f, 0.952054020589248f, + 0.951971212262823f, 0.951862394841917f, 0.951768820782406f, + 0.951688868658540f, 0.951600614570797f, 0.951492713205816f, + 0.951425735970941f, 0.951361524070373f, 0.951238590334566f, + 0.951128486638037f, 0.951065909028615f, 0.951010270116159f, + 0.950902781595030f, 0.950831870621427f, 0.950771893006073f, + 0.950692220598345f, 0.950620597115025f, 0.950534510887917f, + 0.950403380584764f, 0.950334478890738f, 0.950272098021135f, + 0.950196260665432f, 0.950113227599961f, 0.950054926159529f, + 0.949969340742870f, 0.949881762321030f, 0.949768876353368f, + 0.949694926630816f, 0.949573394239407f, 0.949506256443609f, + 0.949443701324821f, 0.949394489146048f, 0.949300730229581f, + 0.949198332808624f, 0.949133287613908f, 0.949025351436334f, + 0.948942100841359f, 0.948893326812499f, 0.948797799093608f, + 0.948739418726981f, 0.948668659827771f, 0.948605293473731f, + 0.948508263335470f, 0.948422283102023f, 0.948334646705041f, + 0.948229838411611f, 0.948135977369838f, 0.948064621226811f, + 0.947975836227614f, 0.947869887504853f, 0.947787963676601f, + 0.947725593707634f, 0.947662889604619f, 0.947606582024487f, + 0.947529016389140f, 0.947467718279620f, 0.947393788461035f, + 0.947318309358938f, 0.947265244768804f, 0.947190494072727f, + 0.947120952789888f, 0.947051021658373f, 0.946947790362777f, + 0.946867304156026f, 0.946794285196121f, 0.946721140964183f, + 0.946641154869108f, 0.946546018566680f, 0.946455030201867f, + 0.946381856581210f, 0.946325274031613f, 0.946255337478520f, + 0.946177270118115f, 0.946111078958173f, 0.946023514156008f, + 0.945959212905784f, 0.945849080079382f, 0.945786239425211f, + 0.945714124061532f, 0.945658743681213f, 0.945601619947753f, + 0.945519484258529f, 0.945455400385708f, 0.945357119411477f, + 0.945264635751721f, 0.945201727920235f, 0.945141917936100f, + 0.945079684332379f, 0.945015928228411f, 0.944946280980823f, + 0.944877156498061f, 0.944804451411428f, 0.944739649858619f, + 0.944673589496270f, 0.944611136357352f, 0.944511223725923f, + 0.944424321938049f, 0.944379812730986f, 0.944289367399254f, + 0.944211374628178f, 0.944134509881182f, 0.944064570023833f, + 0.944020390576029f, 0.943935471478227f, 0.943859733313642f, + 0.943783658792479f, 0.943611497969536f, 0.943543421957604f, + 0.943499628090061f, 0.943413995369032f, 0.943307061234208f, + 0.943209211047420f, 0.943139391056753f, 0.943036473656225f, + 0.942967493742562f, 0.942890220197419f, 0.942826382100022f, + 0.942762325628337f, 0.942694266534490f, 0.942624647563169f, + 0.942553430443271f, 0.942488591032619f, 0.942370472457731f, + 0.942305639341609f, 0.942234519293505f, 0.942182960322720f, + 0.942120517409552f, 0.942027480395684f, 0.941952932339245f, + 0.941891750910681f, 0.941835281530227f, 0.941772951246597f, + 0.941725887717769f, 0.941683506530303f, 0.941593394184890f, + 0.941515512558213f, 0.941473311245300f, 0.941404233632176f, + 0.941325255534943f, 0.941251396941407f, 0.941202439199741f, + 0.941105194862420f, 0.941038074691643f, 0.940974096891109f, + 0.940919561213800f, 0.940827436688845f, 0.940746282858963f, + 0.940672810072720f, 0.940626178969142f, 0.940581240903949f, + 0.940525846543147f, 0.940475217956959f, 0.940413763147880f, + 0.940361426762319f, 0.940301758975821f, 0.940257761802955f, + 0.940172669790372f, 0.940105121519804f, 0.940055180311003f, + 0.940010615028732f, 0.939935007349598f, 0.939881036050097f, + 0.939814506904525f, 0.939749695798634f, 0.939696784747100f, + 0.939625052341671f, 0.939556008686751f, 0.939512250891858f, + 0.939433258280986f, 0.939307887545587f, 0.939263971661818f, + 0.939215889164479f, 0.939167376618607f, 0.939121752120582f, + 0.971967121890630f, 0.971911994887031f, 0.971870201384295f, + 0.971825989837210f, 0.971772117969243f, 0.971732534447172f, + 0.971671680117055f, 0.971613103848265f, 0.971568277310130f, + 0.971497005410360f, 0.971437965611784f, 0.971374177119527f, + 0.971311337696788f, 0.971265675503488f, 0.971211728334830f, + 0.971173654659489f, 0.971097115191358f, 0.971055546830150f, + 0.970984778394942f, 0.970938216168554f, 0.970854927036070f, + 0.970811611972044f, 0.970768836659056f, 0.970693057804116f, + 0.970619816488717f, 0.970549808560914f, 0.970503576286217f, + 0.970432624714843f, 0.970388770439317f, 0.970349084551768f, + 0.970298841533354f, 0.970239776951519f, 0.970194884492194f, + 0.970152831029440f, 0.970090756146688f, 0.970000755530803f, + 0.969956773446540f, 0.969911684742526f, 0.969822649120895f, + 0.969768380795225f, 0.969704307081910f, 0.969664862298864f, + 0.969522047689548f, 0.969481769085865f, 0.969436588593615f, + 0.969381675858440f, 0.969343203466823f, 0.969266922648030f, + 0.969211596747585f, 0.969146961317084f, 0.969103250668287f, + 0.969062089905576f, 0.968999137424737f, 0.968951901823687f, + 0.968911916700945f, 0.968839412499583f, 0.968786327157115f, + 0.968751440609257f, 0.968695995382998f, 0.968656550332251f, + 0.968616734717366f, 0.968567111115919f, 0.968522709698953f, + 0.968475903432075f, 0.968425439658996f, 0.968384501083956f, + 0.968344193764876f, 0.968302257085111f, 0.968251151994677f, + 0.968211894760266f, 0.968149794153557f, 0.968115330462226f, + 0.968048914429886f, 0.967991768898303f, 0.967957662757341f, + 0.967901867217052f, 0.967842068702658f, 0.967808096936220f, + 0.967746019972052f, 0.967668943461478f, 0.967635103661283f, + 0.967593146864882f, 0.967519676893017f, 0.967485969937633f, + 0.967444577221587f, 0.967394666397157f, 0.967359767192470f, + 0.967326238566736f, 0.967269924014565f, 0.967205488183220f, + 0.967150317626980f, 0.967112403335753f, 0.967079092591829f, + 0.967028848415111f, 0.966995625287745f, 0.966944244501088f, + 0.966911108413841f, 0.966837881450369f, 0.966804830861701f, + 0.966754806420632f, 0.966711974994014f, 0.966667104208814f, + 0.966634226055970f, 0.966584175955802f, 0.966508735173705f, + 0.966412422599927f, 0.966337465181747f, 0.966304793467667f, + 0.966245985853935f, 0.966195744077399f, 0.966162098544657f, + 0.966129595993283f, 0.966065142768099f, 0.966008381118610f, + 0.965958352967421f, 0.965907874942570f, 0.965860700300948f, + 0.965828446705695f, 0.965778827683499f, 0.965702907650509f, + 0.965666139199334f, 0.965634050335282f, 0.965562124832480f, + 0.965517675557660f, 0.965436730013674f, 0.965359948253595f, + 0.965303173457664f, 0.965250492014372f, 0.965212160348909f, + 0.965170781733245f, 0.965118350471165f, 0.965086664224710f, + 0.965045725545761f, 0.965008421977835f, 0.964951359793605f, + 0.964899196027095f, 0.964824463593891f, 0.964772498398080f, + 0.964741091666004f, 0.964670443460613f, 0.964607787072424f, + 0.964551286041044f, 0.964496863047423f, 0.964465651940309f, + 0.964429879490986f, 0.964398594355012f, 0.964351600338458f, + 0.964307369250915f, 0.964271608387851f, 0.964227417766565f, + 0.964171256711235f, 0.964081060807346f, 0.964045880993023f, + 0.963985242031280f, 0.963945647749731f, 0.963907826366924f, + 0.963868197486238f, 0.963799938452283f, 0.963765207634998f, + 0.963720978726527f, 0.963684133853149f, 0.963649040588658f, + 0.963607534831113f, 0.963551321580042f, 0.963497204430698f, + 0.963424714887615f, 0.963374725409701f, 0.963344439312775f, + 0.963310870684628f, 0.963276090213696f, 0.963222993617856f, + 0.963129610624736f, 0.963090851003960f, 0.963060656182045f, + 0.963002655525515f, 0.962963164663654f, 0.962922597291628f, + 0.962882895853952f, 0.962848459982529f, 0.962771647590578f, + 0.962711992610457f, 0.962654371383541f, 0.962616259829856f, + 0.962562805802114f, 0.962525034500491f, 0.962484652724640f, + 0.962439417576559f, 0.962379290577768f, 0.962332639787563f, + 0.962297429044519f, 0.962249888365194f, 0.962200268067524f, + 0.962140347398233f, 0.962107323902686f, 0.962041853572779f, + 0.962008176643193f, 0.961975646236443f, 0.961924711249088f, + 0.961879023273176f, 0.961831509403864f, 0.961775868784700f, + 0.961746822676295f, 0.961716324502158f, 0.961640369374947f, + 0.961611429245648f, 0.961547547285081f, 0.961509489301074f, + 0.961480654833700f, 0.961447331320540f, 0.961387391917649f, + 0.961341468430511f, 0.961304123251159f, 0.961249351920283f, + 0.961212476662826f, 0.961178912062857f, 0.961090639886684f, + 0.961045097435303f, 0.961003493534426f, 0.960967973701429f, + 0.960938077569799f, 0.960884289021088f, 0.960819067244493f, + 0.960780320069444f, 0.960743327089429f, 0.960698279588637f, + 0.960667663213218f, 0.960634721461931f, 0.960594821404335f, + 0.960541761992586f, 0.960508945187094f, 0.960469856554093f, + 0.960426570022417f, 0.960387981064035f, 0.960347680867052f, + 0.960307472352100f, 0.960253653207329f, 0.960218687435176f, + 0.960186390857838f, 0.960158615758153f, 0.960112507807049f, + 0.960071344004724f, 0.960020983326931f, 0.959987667027966f, + 0.959956805300620f, 0.959927767865339f, 0.959896946101205f, + 0.959833975596898f, 0.959804107476303f, 0.959775204077521f, + 0.959747793439038f, 0.959702341797309f, 0.959634363246830f, + 0.959569029679109f, 0.959533052569412f, 0.959501336485215f, + 0.959474119229892f, 0.959419839626233f, 0.959341023507977f, + 0.959295438847227f, 0.959260524216600f, 0.959227089969235f, + 0.959200063868628f, 0.959173070436206f, 0.959125094986595f, + 0.959090861914065f, 0.959045960373426f, 0.959019094445528f, + 0.958992260803902f, 0.958954605304605f, 0.958916769209807f, + 0.958886764667757f, 0.958841502703832f, 0.958814827566503f, + 0.958777559544421f, 0.958702397967575f, 0.958671995235790f, + 0.958641005586528f, 0.958614486425838f, 0.958577069623433f, + 0.958549172046238f, 0.958512135033094f, 0.958441537076639f, + 0.958415172644034f, 0.958365776019084f, 0.958303096769660f, + 0.958263716843560f, 0.958185028692631f, 0.958145375013881f, + 0.958119191626474f, 0.958068694182465f, 0.958018918920774f, + 0.957949774071843f, 0.957901198070889f, 0.957866533472984f, + 0.957836112980788f, 0.957808708199475f, 0.957782766016934f, + 0.957746388685411f, 0.957713730335926f, 0.957680192807017f, + 0.957652942114070f, 0.957612146941780f, 0.957579663093048f, + 0.957545516193684f, 0.957506903711571f, 0.957472054074152f, + 0.957436503578588f, 0.957395181689988f, 0.957359417445984f, + 0.957311450421340f, 0.957282679922514f, 0.957209067658546f, + 0.957175128902001f, 0.957146509039227f, 0.957112476412398f, + 0.957074501733838f, 0.957036984482748f, 0.956999759246114f, + 0.956970851060728f, 0.956927264285104f, 0.956871217307326f, + 0.956844594547104f, 0.956813890060049f, 0.956780501144467f, + 0.956744461342733f, 0.956714994383490f, 0.956665821298225f, + 0.956640789566225f, 0.956610849917976f, 0.956550012130147f, + 0.956496846132444f, 0.956456019909584f, 0.956407330143974f, + 0.956337082514063f, 0.956307879496556f, 0.956240737167033f, + 0.956198057147075f, 0.956160694192385f, 0.956121596304570f, + 0.956084736949496f, 0.956060067954893f, 0.956028844508730f, + 0.956004232084434f, 0.955958879345404f, 0.955924575071532f, + 0.955883193326281f, 0.955847442906133f, 0.955813069719572f, + 0.955772060592762f, 0.955716387710303f, 0.955685728196237f, + 0.955645039455566f, 0.955586534578155f, 0.955554652983814f, + 0.955528968538545f, 0.955494147326420f, 0.955433145575848f, + 0.955398247978298f, 0.955366951631532f, 0.955341405225951f, + 0.955313054458997f, 0.955274786773442f, 0.955242793028928f, + 0.955217357860814f, 0.955166187024662f, 0.955135160237832f, + 0.955097685925402f, 0.955066290889999f, 0.955040052475596f, + 0.955002923204769f, 0.954979072001785f, 0.954946002019177f, + 0.954922204505984f, 0.954894464435323f, 0.954869328998638f, + 0.954822936955329f, 0.954799245855306f, 0.954763225287706f, + 0.954739587182904f, 0.954702687836680f, 0.954650651504783f, + 0.954627091600106f, 0.954586735039616f, 0.954543509103832f, + 0.954487508586822f, 0.954455423137410f, 0.954431992380845f, + 0.954395246407735f, 0.954368158761341f, 0.954328519361469f, + 0.954305192349057f, 0.954278682066255f, 0.954244790518497f, + 0.954210949137707f, 0.954183768594435f, 0.954155136691491f, + 0.954118315432295f, 0.954088453549574f, 0.954056625115569f, + 0.954015559093910f, 0.953982138679812f, 0.953953458629932f, + 0.953924167157067f, 0.953876244476431f, 0.953843277247482f, + 0.953820332674190f, 0.953783435648809f, 0.986922760337215f, + 0.986893347132258f, 0.986853719027791f, 0.986802177140835f, + 0.986780595774923f, 0.986735948250994f, 0.986714413193494f, + 0.986691544524921f, 0.986650126059355f, 0.986617659263719f, + 0.986574768928646f, 0.986553349155405f, 0.986509490127068f, + 0.986488116171129f, 0.986459808203524f, 0.986425749277674f, + 0.986401282919961f, 0.986377715956588f, 0.986347680503076f, + 0.986320259258079f, 0.986271810607144f, 0.986245050428010f, + 0.986223881966244f, 0.986176681328462f, 0.986145917556990f, + 0.986110482684939f, 0.986085509969948f, 0.986055947339547f, + 0.986034913700289f, 0.986000776580319f, 0.985965492876594f, + 0.985919261677516f, 0.985892883092507f, 0.985830609056223f, + 0.985801336195789f, 0.985779112009499f, 0.985758255139701f, + 0.985733238386015f, 0.985712426258423f, 0.985655501978720f, + 0.985626353966535f, 0.985590903455782f, 0.985563563636873f, + 0.985523439019124f, 0.985500817867769f, 0.985474456307468f, + 0.985449609353800f, 0.985405907406883f, 0.985360059543588f, + 0.985310904032878f, 0.985270714901873f, 0.985226727495785f, + 0.985193263279922f, 0.985172811115538f, 0.985115330253415f, + 0.985063087425340f, 0.985008336715826f, 0.984978613966300f, + 0.984941852360105f, 0.984912509332049f, 0.984850180222146f, + 0.984824312074071f, 0.984799854405289f, 0.984753768215678f, + 0.984732561511123f, 0.984706135723891f, 0.984678185530603f, + 0.984655684360588f, 0.984631067011433f, 0.984587731589628f, + 0.984558318722489f, 0.984523715071264f, 0.984502265042700f, + 0.984481759317740f, 0.984445908996804f, 0.984416448023547f, + 0.984384996037713f, 0.984346921353555f, 0.984323353794763f, + 0.984302492135683f, 0.984234826486034f, 0.984229190516759f, + 0.984189170519958f, 0.984154292087286f, 0.984130608803616f, + 0.984094955038999f, 0.984074738744969f, 0.984037558142149f, + 0.983994580486844f, 0.983967885641745f, 0.983943737430130f, + 0.983915944574975f, 0.983884236637719f, 0.983864172195582f, + 0.983831831959887f, 0.983802938958836f, 0.983775833669071f, + 0.983753187871529f, 0.983730105132050f, 0.983703133254244f, + 0.983669828705679f, 0.983644384789753f, 0.983620626656529f, + 0.983600307731900f, 0.983567972476239f, 0.983543210140435f, + 0.983491238528487f, 0.983454493564921f, 0.983367632603668f, + 0.983347051720359f, 0.983327810408723f, 0.983301876305209f, + 0.983276092919806f, 0.983251755401310f, 0.983232135834041f, + 0.983208069222592f, 0.983172240353604f, 0.983147868868929f, + 0.983081868982781f, 0.983045201512550f, 0.983012533352780f, + 0.982993510710699f, 0.982961813650806f, 0.982942002878203f, + 0.982889024861584f, 0.982867015386914f, 0.982847649649163f, + 0.982824371463141f, 0.982805048997046f, 0.982773218775641f, + 0.982748418352122f, 0.982729158100405f, 0.982703997651039f, + 0.982682108753123f, 0.982655505774959f, 0.982623018430868f, + 0.982593842523021f, 0.982573406804049f, 0.982547353367972f, + 0.982519585224023f, 0.982459288284645f, 0.982436394370087f, + 0.982415125919954f, 0.982387582740862f, 0.982367292542386f, + 0.982334965662622f, 0.982312635851139f, 0.982294117369415f, + 0.982251715517260f, 0.982224369337035f, 0.982205907157262f, + 0.982182761751450f, 0.982159903255411f, 0.982126373095434f, + 0.982107184687307f, 0.982084954430489f, 0.982041099920316f, + 0.982022769212310f, 0.981977331062889f, 0.981939690609133f, + 0.981914923334885f, 0.981856360333239f, 0.981834102582585f, + 0.981809598272734f, 0.981770121636027f, 0.981739618531661f, + 0.981717245565463f, 0.981683230681818f, 0.981652689781140f, + 0.981634196393471f, 0.981613495190574f, 0.981564013372326f, + 0.981524277410168f, 0.981498926071219f, 0.981450560157412f, + 0.981425266998042f, 0.981402192708996f, 0.981384225827395f, + 0.981365906020084f, 0.981343753206944f, 0.981300572619224f, + 0.981282678369968f, 0.981264437359234f, 0.981228534383292f, + 0.981209049239098f, 0.981182762882197f, 0.981158372244174f, + 0.981134700930964f, 0.981112259373601f, 0.981094152386932f, + 0.981068500570816f, 0.981036844052188f, 0.980986685235874f, + 0.980968652491355f, 0.980913591726037f, 0.980889441073170f, + 0.980848240136851f, 0.980807605621403f, 0.980774106571861f, + 0.980755251245704f, 0.980729769330780f, 0.980680692805421f, + 0.980654718270742f, 0.980636884095367f, 0.980615965819145f, + 0.980595938595629f, 0.980561974203509f, 0.980496049774783f, + 0.980478290932049f, 0.980460550538442f, 0.980432150774978f, + 0.980410648593876f, 0.980393295011126f, 0.980357014272223f, + 0.980326248344638f, 0.980299534184272f, 0.980264913469273f, + 0.980247317913468f, 0.980230066117568f, 0.980208414358917f, + 0.980187260862336f, 0.980132312570413f, 0.980109049768295f, + 0.980075371162398f, 0.980051209784426f, 0.980031865718607f, + 0.979994042174202f, 0.979937198188725f, 0.979920111293897f, + 0.979898807860237f, 0.979877564940339f, 0.979856737310176f, + 0.979835492266172f, 0.979818488652277f, 0.979774221453093f, + 0.979745444849853f, 0.979727225403310f, 0.979700314470918f, + 0.979683392321718f, 0.979666182005815f, 0.979611629473783f, + 0.979590852554355f, 0.979573692364784f, 0.979556851511248f, + 0.979534389742842f, 0.979506478571303f, 0.979478322317171f, + 0.979455398954295f, 0.979438638890386f, 0.979412895074252f, + 0.979392385523425f, 0.979371783642596f, 0.979355088151969f, + 0.979333607767233f, 0.979313189453699f, 0.979292658573124f, + 0.979263179385256f, 0.979246564075690f, 0.979198755309747f, + 0.979174516662574f, 0.979146797165384f, 0.979123464872970f, + 0.979106928065550f, 0.979069685325203f, 0.979053179759749f, + 0.979030332693243f, 0.978986657751362f, 0.978970198965351f, + 0.978946974036735f, 0.978926682675456f, 0.978905244289410f, + 0.978862765525649f, 0.978846097879915f, 0.978821788954481f, + 0.978803246624047f, 0.978766048702116f, 0.978746831450756f, + 0.978725426164543f, 0.978709138346453f, 0.978685752931826f, + 0.978669496205975f, 0.978649410758398f, 0.978624780793713f, + 0.978608570268773f, 0.978592094654600f, 0.978550920367351f, + 0.978513470662572f, 0.978497042038316f, 0.978480907605864f, + 0.978464788541799f, 0.978434611643655f, 0.978418522841283f, + 0.978390826033207f, 0.978374767625467f, 0.978353654444658f, + 0.978337626389741f, 0.978305022211732f, 0.978282789753918f, + 0.978266806773291f, 0.978234606202049f, 0.978203779970373f, + 0.978184787014873f, 0.978165052701335f, 0.978112832751907f, + 0.978096668038331f, 0.978058855230544f, 0.978042990406293f, + 0.978026872130719f, 0.978008154881375f, 0.977992335006128f, + 0.977968977528089f, 0.977951947084624f, 0.977932854511879f, + 0.977913681988638f, 0.977897936222764f, 0.977878414356271f, + 0.977862698102290f, 0.977844552571458f, 0.977825081855045f, + 0.977804621661370f, 0.977787726784343f, 0.977768071434286f, + 0.977752443360056f, 0.977733052522146f, 0.977717453632544f, + 0.977698822040011f, 0.977673199611945f, 0.977619793686062f, + 0.977602075088056f, 0.977576669465553f, 0.977542954045465f, + 0.977527455035169f, 0.977504152387557f, 0.977488682089939f, + 0.977472970424132f, 0.977448260145925f, 0.977398011663338f, + 0.977372297882134f, 0.977355666246754f, 0.977330698924145f, + 0.977298426353436f, 0.977283068966841f, 0.977250107782567f, + 0.977207119451928f, 0.977191803723299f, 0.977176502215050f, + 0.977161214907559f, 0.977102300457946f, 0.977063703061534f, + 0.977040908723148f, 0.977025676110134f, 0.977004557438028f, + 0.976989105443786f, 0.976970178122162f, 0.976938120674094f, + 0.976922957790298f, 0.976906582534861f, 0.976891202359989f, + 0.976872365503571f, 0.976857258525261f, 0.976833112633702f, + 0.976815174516876f, 0.976791566318235f, 0.976756563193258f, + 0.976741524718733f, 0.976698317437522f, 0.976651346433524f, + 0.976632645407126f, 0.976617419614971f, 0.976595534775329f, + 0.976576881856942f, 0.976561937972295f, 0.976546768986203f, + 0.976528820169054f, 0.976508065016963f, 0.976482720813795f, + 0.976467844882108f, 0.976446124920447f, 0.976403186250199f, + 0.976388350299752f, 0.976349887902191f, 0.976319445660324f, + 0.976304414770861f, 0.976289632025076f, 0.976264145624284f, + 0.976240532650802f, 0.976209273788994f, 0.976194311547957f, + 0.976179595220112f, 0.976161231562614f, 0.976146541915870f, + 0.976123701179350f, 0.976105154089893f, 0.976082960591513f, + 0.976042090614826f, 0.976026251263904f, 0.976007993676071f, + 0.975989338013885f, 0.975962781592164f, 0.975944570379698f, + 0.975930011937128f, 0.975907027483125f, 0.975888636596245f, + 0.975874117446146f, 0.975858399012390f, 0.975814767533837f, + 0.975770098214803f, 0.975755629917150f, 0.975740950932038f, + 0.975700126458313f, 0.975679056973671f, 0.975664640074239f, + 0.975650236167557f, 0.975580150537865f, 0.975564561643136f, + 0.975550195260922f, 0.975511963306388f, 0.975491182846468f, + 0.975476854410694f, 0.975458937949595f, 0.975441037900350f, + 0.975403340889900f, 0.975378834602348f, 0.975354759608012f, + 0.975337489516777f, 0.975321825596625f, 0.975307598487264f, + 0.975283675623818f, 0.975269473727394f, 0.975250515677110f, + 0.975222503407069f, 0.975208339209176f, 0.975185839227530f, + 0.975164426313621f, 0.975150299633837f, 0.975132834025883f, + 0.975114957906684f, 0.975095028384530f, 0.975080951799970f, + 0.975066887752846f, 0.975041741905158f, 0.975027702605693f, + 0.975005031349011f, 0.974987469475237f, 0.974973467389524f, + 0.974937686203278f, 0.974920168212932f, 0.974902828972747f, + 0.974872881920399f, 0.974855407841305f, 0.974841479047993f, + 0.974824552649550f, 0.974807122649889f, 0.974793024340947f, + 0.974779144750431f, 0.974761758864481f, 0.974743009828295f, + 0.974702179104436f, 0.974688347653034f, 0.974654844155268f, + 0.974629896769268f, 0.974609196289333f, 0.974595412768757f, + 0.974578144647479f, 0.974554761535420f, 0.974512105515984f, + 0.974493684651349f, 0.974479960679643f, 0.974445019848412f, + 0.974420342456999f, 0.974406653869030f, 0.974389302704651f, + 0.974375638158094f, 0.974356380485032f, 0.974342739773739f, + 0.974329111024600f, 0.974308667973371f, 0.974295062936910f, + 0.974277268860387f, 0.974257232359766f, 0.974243662862119f, + 0.974226418880396f, 0.974209427476104f, 0.974160143649720f, + 0.974135024493451f, 0.974121512976261f, 0.974108013252395f, + 0.974080630570305f, 0.974065964331530f, 0.974015781527702f, + 0.974002134457994f, 0.973968181110526f, 0.973941430008605f, + 0.973900624962740f, 0.973887216439222f, 0.973870418549401f, + 0.973850724231017f, 0.973837350500092f, 0.973820594740841f, + 0.973798522327485f, 0.973777807158058f, 0.973762348240277f, + 0.973720684603163f, 0.973707190440952f, 0.973690525402443f, + 0.973642098553068f, 0.973616296682685f, 0.973596682908872f, + 0.973574106353138f, 0.973560681801021f, 0.973542922676264f, + 0.973514709802091f, 0.973501505700315f, 0.973484979448508f, + 0.973471798042410f, 0.973456500215393f, 0.973431510917770f, + 0.973406975669394f, 0.973387629418889f, 0.973367520795037f, + 0.973303609216807f, 0.973284719314881f, 0.973267149528169f, + 0.973250597568072f, 0.973237526949800f, 0.973221187129731f, + 0.973199716550181f, 0.973183406462760f, 0.973164990751466f, + 0.973148718186700f, 0.973132284540874f, 0.973116049604857f, + 0.973096165761680f, 0.973079968266462f, 0.973062611300583f, + 0.973011998962548f, 0.972975003152676f, 0.972962075047062f, + 0.972945970132927f, 0.972923360946523f, 0.972900115256482f, + 0.972870276402172f, 0.972833571656061f, 0.972811347406848f, + 0.972777650857546f, 0.972760603364386f, 0.972744749946391f, + 0.972728915316704f, 0.972491425539914f, 0.972700300574877f, + 0.972657493357715f, 0.972632608746347f, 0.972616859264461f, + 0.972598619191692f, 0.972345186679530f, 0.972332446611726f, + 0.972312645617935f, 0.972296953045242f, 0.972283079206423f, + 0.972354259937865f, 0.972459209202242f, 0.972446537807611f, + 0.972430934483078f, 0.972176086196692f, 0.972163441757064f, + 0.972150807994720f, 0.972135323233078f, 0.972332090264623f, + 0.972296656512621f, 0.972281150355395f, 0.972261199146408f, + 0.972040937679384f, 0.972020853805726f, 0.971986352971951f, + 0.971970171296574f, 0.971957642162397f, 0.971945123560269f, + 0.971925366207946f, 0.971903023702926f, 0.971890536316386f, + 0.971869250638017f, 0.971845667162464f, 0.971833210739844f, + 0.971804277862056f, 0.971791841948099f, 0.971779416449271f, + 0.971764022143080f, 0.971747980164594f, 0.971735585761423f, + 0.971717421387968f, 0.971705047573062f, 0.971692684095817f, + 0.971677569616385f, 0.971654562083561f, 0.971639423267713f, + 0.971627100732641f, 0.971586709457336f, 0.971574406851170f, + 0.971562114493840f, 0.971547034241681f, 0.971528565510735f, + 0.971512518293816f, 0.971500266599958f, 0.971464929056654f, + 0.971452697226202f, 0.971418394914989f, 0.971406182921065f, + 0.971393981063291f, 0.971376907284318f, 0.971343341781312f, + 0.971331169765085f, 0.971319007835423f, 0.971298578641970f, + 0.971283662437030f, 0.971271530457587f, 0.971252454892243f, + 0.971240342827092f, 0.971225487479047f, 0.971204546317460f, + 0.971192464074439f, 0.971180391807126f, 0.971165370878734f, + 0.971153318463628f, 0.971140124638148f, 0.971123237224655f, + 0.971106480118568f, 0.971083157368327f, 0.971071154129043f, + 0.971034062159235f, 0.971018455159871f, 0.971006480918128f, + 0.970994516519120f, 0.970977092320320f, 0.970965003529432f, + 0.970950326173765f, 0.970937252551404f, 0.970925337052152f, + 0.970913431323699f, 0.970898580575337f, 0.970880595387861f, + 0.970865984305819f, 0.970854117296896f, 0.970827109552462f, + 0.970787121116513f, 0.970771664135147f, 0.970734868566354f, + 0.970720322134539f, 0.970708511741481f, 0.970696710990446f, + 0.970670091597675f, 0.970638391535586f, 0.970594506910305f, + 0.970571972858635f, 0.970560218611173f, 0.970545523090248f, + 0.970524813815583f, 0.970506896143813f, 0.970470838904634f, + 0.970456283769786f, 0.970444585924069f, 0.970432897582902f, + 0.970421218734771f, 0.970408406937207f, 0.970396747017460f, + 0.970385096556351f, 0.970370753011372f, 0.970353198100957f, + 0.970341440710966f, 0.970329827852595f, 0.970315487202049f, + 0.970303893080486f, 0.970269092080499f, 0.970251821409422f, + 0.970240254808422f, 0.970228697553167f, 0.970204163935976f, + 0.970191352781056f, 0.970179823267678f, 0.970165616505559f, + 0.970154105529172f, 0.970125646226645f, 0.970114153457064f, + 0.970102669944216f, 0.970087197011956f, 0.970075731899408f, + 0.970061466817810f, 0.970050020142580f, 0.970019988110384f, + 0.969972599795979f, 0.969961179607361f, 0.969948632115153f, + 0.969908746684612f, 0.969894682809931f, 0.969883170119172f, + 0.969871795082014f, 0.969860429159958f, 0.969846343221063f, + 0.969834995442697f, 0.969823656746862f, 0.969812327122728f, + 0.969798343939773f, 0.969787032374211f, 0.969772004681985f, + 0.969760711166196f, 0.969749426668237f, 0.969688426500697f, + 0.969677159032194f, 0.969663245949753f, 0.969651996391660f, + 0.969625257076576f, 0.969605977523474f, 0.969594754369765f, + 0.969583416110931f, 0.969572210865374f, 0.969561014532258f, + 0.969547180480629f, 0.969513246925795f, 0.969502076773797f, + 0.969485003257335f, 0.969472720232916f, 0.969461576525169f, + 0.969450319795174f, 0.969439193805961f, 0.969420426798349f, + 0.969406597238909f, 0.969395497473808f, 0.969376382415080f, + 0.969365300059954f, 0.969354226471582f, 0.969343161639623f, + 0.969325363726951f, 0.969314316299151f, 0.969299688455677f, + 0.969287530932357f, 0.969276509560222f, 0.969265496882931f, + 0.969254492890265f, 0.969243497572002f, 0.969229888366585f, + 0.969216720237517f, 0.969205750840223f, 0.969191858846575f, + 0.969172668560500f, 0.969153335935209f, 0.969142400641392f, + 0.969126015512418f, 0.969115097292469f, 0.969077096987749f, + 0.969066195393082f, 0.969054177875027f, 0.969035207922252f, + 0.969016820085756f, 0.969005952381187f, 0.968992380215632f, + 0.968981529478561f, 0.968968080712556f, 0.968955177833115f, + 0.968944352461800f, 0.968926930585352f, 0.968913081597044f, + 0.968902281458119f, 0.968891489754488f, 0.968877127176801f, + 0.968865230815253f, 0.968851865757681f, 0.968841107561196f, + 0.968827062840517f, 0.968816321345894f, 0.968790935192768f, + 0.968768188742240f, 0.968757471871783f, 0.968732819132344f, + 0.968722118670559f, 0.968708836038462f, 0.968698152157404f, + 0.968657086013096f, 0.968646418161465f, 0.968624158304073f, + 0.968613506791441f, 0.968596572545570f, 0.968579061738822f, + 0.968561014979841f, 0.968550396126364f, 0.968537202998056f, + 0.968526600536290f, 0.968516006279852f, 0.968505420219268f, + 0.968494842345072f, 0.968484165957274f, 0.968448598566619f, + 0.968438044781854f, 0.968427499146142f, 0.968390635470169f, + 0.968375420846665f, 0.968364899038218f, 0.968354385341824f, + 0.968332577679763f, 0.968322079974605f, 0.968311590353718f, + 0.968301004306507f, 0.968290530868138f, 1.00143961392577f, + 1.00142968724369f, 1.00141976816660f, 1.00140446209241f, + 1.00137317954622f, 1.00136328275028f, 1.00135014137470f, + 1.00133902200513f, 1.00131146903538f, 1.00130160207297f, + 1.00128450674836f, 1.00127465472096f, 1.00126481021308f, + 1.00125497321619f, 1.00124225940996f, 1.00122500973736f, + 1.00121519503829f, 1.00120538781624f, 1.00119545326034f, + 1.00118313784023f, 1.00117335295109f, 1.00116357550511f, + 1.00115380549385f, 1.00113942387171f, 1.00112966862280f, + 1.00109314412405f, 1.00107946844994f, 1.00106973486180f, + 1.00105418133174f, 1.00104229664803f, 1.00103006985615f, + 1.00102036557219f, 1.00100778834931f, 1.00096616031686f, + 1.00095647745121f, 1.00092507998694f, 1.00091541138654f, + 1.00090575009718f, 1.00089487173027f, 1.00087920118928f, + 1.00086956165688f, 1.00084041349058f, 1.00082467685478f, + 1.00080693845908f, 1.00079732740243f, 1.00078092556175f, + 1.00074906862448f, 1.00073947877630f, 1.00072989615011f, + 1.00072019315343f, 1.00066830628723f, 1.00065874454301f, + 1.00064918998899f, 1.00063246831712f, 1.00061832499982f, + 1.00060879176472f, 1.00059926568779f, 1.00058974676105f, + 1.00058023497646f, 1.00055457310189f, 1.00054507529979f, + 1.00049419893387f, 1.00048471465008f, 1.00044437316419f, + 1.00043490254958f, 1.00042341268056f, 1.00039435986397f, + 1.00038491011887f, 1.00037546743804f, 1.00036303684998f, + 1.00034636152703f, 1.00033693981721f, 1.00032504191268f, + 1.00031563421870f, 1.00030623354207f, 1.00029683987498f, + 1.00028352575811f, 1.00026603619147f, 1.00025666330226f, + 1.00024729739164f, 1.00023128713687f, 1.00022193504773f, + 1.00020066879491f, 1.00019133040773f, 1.00017618304649f, + 1.00016577323552f, 1.00015645552966f, 1.00013420200033f, + 1.00009861649226f, 1.00008931885617f, 1.00008002811490f, + 1.00007062639745f, 1.00005669782209f, 1.00004496048148f, + 1.00003569712223f, 1.00002644061987f, 1.00000924294396f, + 0.999999999999995f}; + +// Curve which is scaled by |kCurveCorrectionMultipliers| and added to the curve +// generated by the |kHighReverberationCorrectionCurve| polynomial, for +// reverberation times which result in a feedback factor index greater than +// |kCurveChangeoverIndex|. +static const float kHighCorrectionCurve[kCorrectionCurveLength] = { + 0.00905424704513036f, 0.00926599025691877f, 0.00947778313262726f, + 0.00968962562016418f, 0.00990151766744984f, 0.0101134592224174f, + 0.0103254502330116f, 0.0105374906471899f, 0.0107495804129221f, + 0.0109617194781898f, 0.0111739077909878f, 0.0113861452993219f, + 0.0115984319512115f, 0.0118107676946871f, 0.0120231524777923f, + 0.0122355862485828f, 0.0124480689551263f, 0.0126606005455030f, + 0.0128731809678055f, 0.0130858101701389f, 0.0132984881006193f, + 0.0135112147073766f, 0.0137239899385524f, 0.0139368137423006f, + 0.0141496860667869f, 0.0143626068601902f, 0.0145755760707011f, + 0.0147885936465225f, 0.0150016595358697f, 0.0152147736869704f, + 0.0154279360480643f, 0.0156411465674036f, 0.0158544051932527f, + 0.0160677118738880f, 0.0162810665575990f, 0.0164944691926864f, + 0.0167079197274641f, 0.0169214181102577f, 0.0171349642894055f, + 0.0173485582132578f, 0.0175621998301772f, 0.0177758890885386f, + 0.0179896259367293f, 0.0182034103231487f, 0.0184172421962087f, + 0.0186311215043333f, 0.0188450481959587f, 0.0190590222195339f, + 0.0192730435235194f, 0.0194871120563886f, 0.0197012277666270f, + 0.0199153906027323f, 0.0201296005132146f, 0.0203438574465959f, + 0.0205581613514112f, 0.0207725121762073f, 0.0209869098695431f, + 0.0212013543799902f, 0.0214158456561324f, 0.0216303836465656f, + 0.0218449682998980f, 0.0220595995647505f, 0.0222742773897556f, + 0.0224890017235585f, 0.0227037725148169f, 0.0229185897122002f, + 0.0231334532643903f, 0.0233483631200817f, 0.0235633192279807f, + 0.0237783215368064f, 0.0239933699952896f, 0.0242084645521738f, + 0.0244236051562147f, 0.0246387917561803f, 0.0248540243008508f, + 0.0250693027390185f, 0.0252846270194886f, 0.0254999970910778f, + 0.0257154129026155f, 0.0259308744029435f, 0.0261463815409154f, + 0.0263619342653978f, 0.0265775325252690f, 0.0267931762694196f, + 0.0270088654467529f, 0.0272246000061842f, 0.0274403798966409f, + 0.0276562050670630f, 0.0278720754664027f, 0.0280879910436245f, + 0.0283039517477049f, 0.0285199575276330f, 0.0287360083324103f, + 0.0289521041110502f, 0.0291682448125785f, 0.0293844303860333f, + 0.0296006607804654f, 0.0298169359449371f, 0.0300332558285236f, + 0.0302496203803120f, 0.0304660295494019f, 0.0306824832849052f, + 0.0308989815359457f, 0.0311155242516602f, 0.0313321113811973f, + 0.0315487428737179f, 0.0317654186783949f, 0.0319821387444144f, + 0.0321989030209739f, 0.0324157114572834f, 0.0326325640025653f, + 0.0328494606060544f, 0.0330664012169974f, 0.0332833857846536f, + 0.0335004142582945f, 0.0337174865872039f, 0.0339346027206777f, + 0.0341517626080246f, 0.0343689661985646f, 0.0345862134416310f, + 0.0348035042865692f, 0.0350208386827363f, 0.0352382165795020f, + 0.0354556379262483f, 0.0356731026723698f, 0.0358906107672728f, + 0.0361081621603763f, 0.0363257568011115f, 0.0365433946389216f, + 0.0367610756232627f, 0.0369787997036024f, 0.0371965668294212f, + 0.0374143769502114f, 0.0376322300154781f, 0.0378501259747384f, + 0.0380680647775213f, 0.0382860463733691f, 0.0385040707118353f, + 0.0387221377424862f, 0.0389402474149004f, 0.0391583996786689f, + 0.0393765944833944f, 0.0395948317786927f, 0.0398131115141910f, + 0.0400314336395295f, 0.0402497981043605f, 0.0404682048583482f, + 0.0406866538511695f, 0.0409051450325135f, 0.0411236783520815f, + 0.0413422537595872f, 0.0415608712047565f, 0.0417795306373275f, + 0.0419982320070504f, 0.0422169752636886f, 0.0424357603570167f, + 0.0426545872368219f, 0.0428734558529040f, 0.0430923661550748f, + 0.0433113180931585f, 0.0435303116169914f, 0.0437493466764225f, + 0.0439684232213125f, 0.0441875412015349f, 0.0444067005669753f, + 0.0446259012675311f, 0.0448451432531127f, 0.0450644264736426f, + 0.0452837508790555f, 0.0455031164192982f, 0.0457225230443301f, + 0.0459419707041227f, 0.0461614593486596f, 0.0463809889279372f, + 0.0466005593919636f, 0.0468201706907599f, 0.0470398227743584f, + 0.0472595155928048f, 0.0474792490961565f, 0.0476990232344832f, + 0.0479188379578670f, 0.0481386932164022f, 0.0483585889601954f, + 0.0485785251393657f, 0.0487985017040441f, 0.0490185186043742f, + 0.0492385757905118f, 0.0494586732126247f, 0.0496788108208932f, + 0.0498989885655103f, 0.0501192063966806f, 0.0401356533420085f, + 0.0403559511969490f, 0.0405762889891309f, 0.0407966666688085f, + 0.0410170841862478f, 0.0412375414917278f, 0.0414580385355386f, + 0.0416785752679840f, 0.0418991516393793f, 0.0421197676000519f, + 0.0423404231003423f, 0.0425611180906021f, 0.0427818525211963f, + 0.0430026263425014f, 0.0432234395049069f, 0.0434442919588138f, + 0.0436651836546360f, 0.0438861145427996f, 0.0441070845737422f, + 0.0443280936979149f, 0.0445491418657804f, 0.0447702290278135f, + 0.0449913551345018f, 0.0452125201363448f, 0.0454337239838546f, + 0.0456549666275551f, 0.0458762480179831f, 0.0460975681056872f, + 0.0463189268412284f, 0.0465403241751802f, 0.0467617600581280f, + 0.0469832344406698f, 0.0472047472734157f, 0.0474262985069884f, + 0.0476478880920221f, 0.0478695159791643f, 0.0480911821190742f, + 0.0483128864624232f, 0.0485346289598951f, 0.0487564095621865f, + 0.0489782282200054f, 0.0492000848840726f, 0.0494219795051212f, + 0.0496439120338962f, 0.0498658824211554f, 0.0500878906176685f, + 0.0503099365742177f, 0.0505320202415973f, 0.0507541415706141f, + 0.0509763005120871f, 0.0511984970168471f, 0.0514207310357381f, + 0.0516430025196157f, 0.0518653114193479f, 0.0520876576858154f, + 0.0523100412699105f, 0.0525324621225380f, 0.0527549201946156f, + 0.0529774154370724f, 0.0531999478008504f, 0.0534225172369033f, + 0.0536451236961979f, 0.0538677671297125f, 0.0540904474884381f, + 0.0543131647233778f, 0.0545359187855472f, 0.0547587096259737f, + 0.0549815371956978f, 0.0552044014457715f, 0.0554273023272596f, + 0.0556502397912386f, 0.0558732137887982f, 0.0560962242710392f, + 0.0563192711890757f, 0.0565423544940334f, 0.0567654741370510f, + 0.0569886300692788f, 0.0572118222418796f, 0.0574350506060288f, + 0.0576583151129133f, 0.0578816157137332f, 0.0581049523597000f, + 0.0583283250020387f, 0.0585517335919851f, 0.0485713671581760f, + 0.0487948474970971f, 0.0490183636374095f, 0.0492419155303987f, + 0.0494655031273628f, 0.0496891263796120f, 0.0499127852384687f, + 0.0501364796552677f, 0.0503602095813560f, 0.0505839749680932f, + 0.0508077757668506f, 0.0510316119290123f, 0.0512554834059744f, + 0.0514793901491452f, 0.0517033321099457f, 0.0519273092398090f, + 0.0521513214901801f, 0.0523753688125170f, 0.0525994511582891f, + 0.0528235684789790f, 0.0530477207260808f, 0.0532719078511013f, + 0.0534961298055596f, 0.0537203865409869f, 0.0539446780089268f, + 0.0541690041609351f, 0.0543933649485798f, 0.0546177603234417f, + 0.0548421902371131f, 0.0550666546411992f, 0.0552911534873171f, + 0.0555156867270965f, 0.0557402543121789f, 0.0559648561942188f, + 0.0561894923248822f, 0.0564141626558481f, 0.0566388671388072f, + 0.0568636057254629f, 0.0570883783675304f, 0.0573131850167378f, + 0.0575380256248254f, 0.0577629001435447f, 0.0579878085246610f, + 0.0582127507199510f, 0.0584377266812042f, 0.0586627363602216f, + 0.0588877797088170f, 0.0591128566788167f, 0.0593379672220590f, + 0.0595631112903944f, 0.0597882888356857f, 0.0600134998098081f, + 0.0602387441646493f, 0.0604640218521088f, 0.0606893328240985f, + 0.0609146770325428f, 0.0611400544293785f, 0.0613654649665540f, + 0.0615909085960309f, 0.0618163852697822f, 0.0620418949397940f, + 0.0622674375580639f, 0.0624930130766024f, 0.0627186214474318f, + 0.0629442626225872f, 0.0631699365541155f, 0.0633956431940764f, + 0.0534175715719286f, 0.0536433434849815f, 0.0538691479627182f, + 0.0540949849572480f, 0.0543208544206912f, 0.0545467563051810f, + 0.0547726905628627f, 0.0549986571458940f, 0.0552246560064449f, + 0.0554506870966974f, 0.0556767503688461f, 0.0559028457750979f, + 0.0561289732676714f, 0.0563551327987983f, 0.0565813243207222f, + 0.0568075477856989f, 0.0570338031459965f, 0.0572600903538957f, + 0.0574864093616889f, 0.0577127601216814f, 0.0579391425861903f, + 0.0581655567075452f, 0.0583920024380880f, 0.0586184797301730f, + 0.0588449885361663f, 0.0590715288084468f, 0.0592981004994055f, + 0.0595247035614457f, 0.0597513379469827f, 0.0599780036084447f, + 0.0602047004982714f, 0.0604314285689156f, 0.0606581877728418f, + 0.0608849780625269f, 0.0611117993904601f, 0.0613386517091431f, + 0.0615655349710893f, 0.0617924491288254f, 0.0620193941348894f, + 0.0622463699418319f, 0.0624733765022160f, 0.0627004137686168f, + 0.0629274816936218f, 0.0631545802298307f, 0.0633817093298557f, + 0.0636088689463210f, 0.0638360590318633f, 0.0640632795391316f, + 0.0642905304207867f, 0.0645178116295023f, 0.0647451231179644f, + 0.0649724648388708f, 0.0651998367449316f, 0.0654272387888696f, + 0.0656546709234196f, 0.0658821331013287f, 0.0661096252753566f, + 0.0663371473982747f, 0.0665646994228671f, 0.0667922813019303f, + 0.0568160820656600f, 0.0570437235121022f, 0.0572713946714774f, + 0.0574990954966311f, 0.0577268259404211f, 0.0579545859557173f, + 0.0581823754954017f, 0.0584101945123692f, 0.0586380429595264f, + 0.0588659207897921f, 0.0590938279560984f, 0.0593217644113885f, + 0.0595497301086181f, 0.0597777250007558f, 0.0600057490407822f, + 0.0602338021816895f, 0.0604618843764834f, 0.0606899955781809f, + 0.0609181357398114f, 0.0611463048144174f, 0.0613745027550527f, + 0.0616027295147840f, 0.0618309850466897f, 0.0620592693038611f, + 0.0622875822394012f, 0.0625159238064261f, 0.0627442939580634f, + 0.0629726926474533f, 0.0632011198277480f, 0.0634295754521126f, + 0.0636580594737237f, 0.0638865718457710f, 0.0641151125214557f, + 0.0643436814539919f, 0.0645722785966058f, 0.0648009039025354f, + 0.0650295573250316f, 0.0652582388173575f, 0.0654869483327883f, + 0.0657156858246115f, 0.0659444512461268f, 0.0661732445506464f, + 0.0664020656914949f, 0.0666309146220087f, 0.0668597912955365f, + 0.0670886956654401f, 0.0673176276850925f, 0.0675465873078798f, + 0.0677755744872001f, 0.0680045891764636f, 0.0682336313290928f, + 0.0684627008985227f, 0.0686917978382008f, 0.0689209221015861f, + 0.0691500736421506f, 0.0693792524133784f, 0.0594046474461534f, + 0.0596338805392087f, 0.0598631407234532f, 0.0600924279524199f, + 0.0603217421796543f, 0.0605510833587138f, 0.0607804514431689f, + 0.0610098463866017f, 0.0612392681426066f, 0.0614687166647906f, + 0.0616981919067730f, 0.0619276938221849f, 0.0621572223646700f, + 0.0623867774878845f, 0.0626163591454967f, 0.0628459672911870f, + 0.0630756018786481f, 0.0633052628615854f, 0.0635349501937161f, + 0.0637646638287699f, 0.0639944037204887f, 0.0642241698226267f, + 0.0644539620889507f, 0.0646837804732392f, 0.0649136249292833f, + 0.0651434954108865f, 0.0653733918718643f, 0.0656033142660447f, + 0.0658332625472679f, 0.0660632366693863f, 0.0662932365862647f, + 0.0665232622517804f, 0.0667533136198224f, 0.0669833906442924f, + 0.0672134932791041f, 0.0674436214781841f, 0.0676737751954706f, + 0.0679039543849144f, 0.0681341590004783f, 0.0683643889961381f, + 0.0685946443258808f, 0.0688249249437064f, 0.0690552308036274f, + 0.0692855618596678f, 0.0695159180658648f, 0.0697462993762669f, + 0.0699767057449354f, 0.0702071371259442f, 0.0704375934733789f, + 0.0706680747413375f, 0.0708985808839306f, 0.0711291118552808f, + 0.0713596676095232f, 0.0715902481008047f, 0.0616170423606727f, + 0.0618476721885236f, 0.0620783266159294f, 0.0623090055970863f, + 0.0625397090862030f, 0.0627704370375001f, 0.0630011894052112f, + 0.0632319661435816f, 0.0634627672068692f, 0.0636935925493440f, + 0.0639244421252883f, 0.0641553158889967f, 0.0643862137947761f, + 0.0646171357969457f, 0.0648480818498370f, 0.0650790519077937f, + 0.0653100459251719f, 0.0655410638563397f, 0.0657721056556779f, + 0.0660031712775794f, 0.0662342606764491f, 0.0664653738067047f, + 0.0666965106227758f, 0.0669276710791043f, 0.0671588551301446f, + 0.0673900627303632f, 0.0676212938342389f, 0.0678525483962630f, + 0.0680838263709389f, 0.0683151277127820f, 0.0685464523763204f, + 0.0687778003160946f, 0.0690091714866568f, 0.0692405658425719f, + 0.0694719833384170f, 0.0697034239287815f, 0.0699348875682669f, + 0.0701663742114875f, 0.0703978838130692f, 0.0706294163276505f, + 0.0708609717098825f, 0.0710925499144278f, 0.0713241508959620f, + 0.0715557746091728f, 0.0717874210087600f, 0.0720190900494359f, + 0.0722507816859246f, 0.0724824958729632f, 0.0727142325653007f, + 0.0729459917176984f, 0.0731777732849299f, 0.0734095772217809f, + 0.0736414034830499f, 0.0738732520235469f, 0.0741051227980950f, + 0.0641332048389163f, 0.0643651199460837f, 0.0645970571518437f, + 0.0648290164110684f, 0.0650609976786417f, 0.0652930009094604f, + 0.0655250260584330f, 0.0657570730804803f, 0.0659891419305358f, + 0.0662212325635450f, 0.0664533449344658f, 0.0666854789982679f, + 0.0669176347099340f, 0.0671498120244587f, 0.0673820108968489f, + 0.0676142312821242f, 0.0678464731353154f, 0.0680787364114669f, + 0.0683110210656344f, 0.0685433270528865f, 0.0687756543283037f, + 0.0690080028469791f, 0.0692403725640174f, 0.0694727634345366f, + 0.0697051754136662f, 0.0699376084565483f, 0.0701700625183374f, + 0.0704025375541997f, 0.0706350335193146f, 0.0708675503688727f, + 0.0711000880580778f, 0.0713326465421456f, 0.0715652257763042f, + 0.0717978257157935f, 0.0720304463158665f, 0.0722630875317879f, + 0.0724957493188349f, 0.0727284316322967f, 0.0729611344274751f, + 0.0731938576596843f, 0.0734266012842503f, 0.0736593652565118f, + 0.0738921495318196f, 0.0741249540655367f, 0.0743577788130385f, + 0.0745906237297128f, 0.0748234887709595f, 0.0750563738921907f, + 0.0752892790488311f, 0.0755222041963173f, 0.0757551492900986f, + 0.0759881142856362f, 0.0762210991384039f, 0.0764541038038875f, + 0.0766871282375853f, 0.0769201723950076f, 0.0669494253090649f, + 0.0671825087805169f, 0.0674156118422988f, 0.0676487344499700f, + 0.0678818765591026f, 0.0681150381252804f, 0.0683482191041003f, + 0.0685814194511707f, 0.0688146391221127f, 0.0690478780725596f, + 0.0692811362581574f, 0.0695144136345632f, 0.0697477101574477f, + 0.0699810257824931f, 0.0702143604653941f, 0.0704477141618579f, + 0.0706810868276036f, 0.0709144784183627f, 0.0711478888898790f, + 0.0713813181979088f, 0.0716147662982205f, 0.0718482331465947f, + 0.0720817186988244f, 0.0723152229107147f, 0.0725487457380832f, + 0.0727822871367598f, 0.0730158470625864f, 0.0732494254714176f, + 0.0734830223191197f, 0.0737166375615722f, 0.0739502711546658f, + 0.0741839230543041f, 0.0744175932164030f, 0.0746512815968905f, + 0.0748849881517070f, 0.0751187128368048f, 0.0753524556081493f, + 0.0755862164217173f, 0.0758199952334984f, 0.0760537919994943f, + 0.0762876066757191f, 0.0765214392181990f, 0.0767552895829728f, + 0.0769891577260911f, 0.0772230436036172f, 0.0774569471716267f, + 0.0776908683862069f, 0.0779248072034582f, 0.0781587635794926f, + 0.0783927374704347f, 0.0786267288324215f, 0.0788607376216020f, + 0.0790947637941377f, 0.0793288073062022f, 0.0795628681139814f, + 0.0797969461736736f, 0.0800310414414898f, 0.0802651538736520f, + 0.0804992834263956f, 0.0807334300559682f, 0.0707637827960168f, + 0.0709979634480382f, 0.0712321610457045f, 0.0714663755453118f, + 0.0717006069031690f, 0.0719348550755973f, 0.0721691200189298f, + 0.0724034016895124f, 0.0726377000437030f, 0.0728720150378716f, + 0.0731063466284005f, 0.0733406947716849f, 0.0735750594241317f, + 0.0738094405421600f, 0.0740438380822017f, 0.0742782520007005f, + 0.0745126822541126f, 0.0747471287989061f, 0.0749815915915625f, + 0.0752160705885742f, 0.0754505657464466f, 0.0756850770216972f, + 0.0759196043708562f, 0.0761541477504655f, 0.0763887071170792f, + 0.0766232824272645f, 0.0768578736376001f, 0.0770924807046775f, + 0.0773271035850998f, 0.0775617422354831f, 0.0777963966124555f, + 0.0780310666726576f, 0.0782657523727417f, 0.0785004536693729f, + 0.0787351705192282f, 0.0789699028789975f, 0.0792046507053824f, + 0.0794394139550970f, 0.0796741925848675f, 0.0799089865514328f, + 0.0801437958115436f, 0.0803786203219632f, 0.0806134600394671f, + 0.0808483149208430f, 0.0810831849228909f, 0.0813180700024232f, + 0.0815529701162645f, 0.0817878852212516f, 0.0820228152742338f, + 0.0822577602320724f, 0.0824927200516413f, 0.0827276946898264f, + 0.0829626841035263f, 0.0831976882496509f, 0.0834327070851236f, + 0.0836677405668795f, 0.0839027886518659f, 0.0841378512970423f, + 0.0843729284593812f, 0.0846080200958663f, 0.0848431261634945f, + 0.0850782466192747f, 0.0853133814202278f, 0.0855485305233871f, + 0.0857836938857984f, 0.0860188714645199f, 0.0862540632166216f, + 0.0762854581765733f, 0.0765206781466951f, 0.0767559121614814f, + 0.0769911601780519f, 0.0772264221535377f, 0.0774616980450831f, + 0.0776969878098444f, 0.0779322914049897f, 0.0781676087877001f, + 0.0784029399151685f, 0.0786382847446002f, 0.0788736432332130f, + 0.0791090153382364f, 0.0793444010169132f, 0.0795798002264975f, + 0.0798152129242558f, 0.0800506390674675f, 0.0802860786134237f, + 0.0805215315194281f, 0.0807569977427964f, 0.0809924772408571f, + 0.0812279699709503f, 0.0814634758904288f, 0.0816989949566574f, + 0.0819345271270137f, 0.0821700723588872f, 0.0824056306096794f, + 0.0826412018368049f, 0.0828767859976898f, 0.0831123830497728f, + 0.0833479929505050f, 0.0835836156573494f, 0.0838192511277818f, + 0.0840548993192899f, 0.0842905601893735f, 0.0845262336955456f, + 0.0847619197953302f, 0.0849976184462647f, 0.0852333296058980f, + 0.0854690532317919f, 0.0857047892815197f, 0.0859405377126680f, + 0.0861762984828349f, 0.0864120715496310f, 0.0866478568706792f, + 0.0868836544036147f, 0.0871194641060850f, 0.0873552859357498f, + 0.0875911198502811f, 0.0878269658073632f, 0.0880628237646929f, + 0.0882986936799788f, 0.0885345755109422f, 0.0887704692153166f, + 0.0890063747508475f, 0.0892422920752930f, 0.0894782211464236f, + 0.0897141619220215f, 0.0899501143598818f, 0.0901860784178115f, + 0.0904220540536300f, 0.0906580412251691f, 0.0908940398902726f, + 0.0911300500067969f, 0.0913660715326105f, 0.0916021044255941f, + 0.0918381486436412f, 0.0920742041446566f, 0.0923102708865582f, + 0.0925463488272760f, 0.0927824379247522f, 0.0930185381369413f, + 0.0932546494218102f, 0.0934907717373377f, 0.0937269050415152f, + 0.0939630492923465f, 0.0941992044478474f, 0.0944353704660461f, + 0.0946715473049831f, 0.0949077349227112f, 0.0951439332772953f, + 0.0953801423268129f, 0.0956163620293533f, 0.0856487814204062f, + 0.0858850223033106f, 0.0861212737135806f, 0.0863575356093548f, + 0.0865938079487840f, 0.0868300906900319f, 0.0870663837912740f, + 0.0873026872106978f, 0.0875390009065038f, 0.0877753248369042f, + 0.0880116589601236f, 0.0882480032343993f, 0.0884843576179805f, + 0.0887207220691284f, 0.0889570965461172f, 0.0891934810072328f, + 0.0894298754107737f, 0.0896662797150505f, 0.0899026938783861f, + 0.0901391178591160f, 0.0903755516155873f, 0.0906119951061600f, + 0.0908484482892064f, 0.0910849111231104f, 0.0913213835662688f, + 0.0915578655770907f, 0.0917943571139971f, 0.0920308581354216f, + 0.0922673685998101f, 0.0925038884656203f, 0.0927404176913226f, + 0.0929769562353998f, 0.0932135040563467f, 0.0934500611126704f, + 0.0936866273628904f, 0.0939232027655385f, 0.0941597872791585f, + 0.0943963808623070f, 0.0946329834735524f, 0.0948695950714755f, + 0.0951062156146697f, 0.0953428450617401f, 0.0955794833713046f, + 0.0958161305019931f, 0.0960527864124481f, 0.0962894510613237f, + 0.0965261244072870f, 0.0967628064090171f, 0.0969994970252055f, + 0.0972361962145558f, 0.0974729039357838f, 0.0977096201476179f, + 0.0979463448087987f, 0.0981830778780787f, 0.0984198193142229f, + 0.0986565690760093f, 0.0988933271222270f, 0.0991300934116780f, + 0.0993668679031768f, 0.0996036505555495f, 0.0998404413276351f, + 0.100077240178285f, 0.100314047066361f, 0.100550861950741f, + 0.100787684790311f, 0.101024515543973f, 0.101261354170637f, + 0.101498200629230f, 0.101735054878688f, 0.101971916877960f, + 0.102208786586009f, 0.102445663961807f, 0.102682548964341f, + 0.102919441552610f, 0.103156341685624f, 0.103393249322406f, + 0.103630164421992f, 0.103867086943429f, 0.104104016845776f, + 0.104340954088107f, 0.104577898629505f, 0.104814850429067f, + 0.105051809445902f, 0.105288775639131f, 0.105525748967888f, + 0.105762729391318f, 0.105999716868580f, 0.106236711358845f, + 0.106473712821294f, 0.106710721215122f, 0.106947736499538f, + 0.107184758633760f, 0.107421787577020f, 0.107658823288562f, + 0.107895865727643f, 0.108132914853530f, 0.108369970625506f, + 0.108607033002864f, 0.108844101944907f, 0.109081177410956f, + 0.109318259360339f, 0.109555347752398f, 0.109792442546489f, + 0.110029543701978f, 0.110266651178245f, 0.110503764934681f, + 0.110740884930689f, 0.110978011125686f, 0.111215143479099f, + 0.111452281950370f, 0.111689426498952f, 0.111926577084308f, + 0.112163733665917f, 0.112400896203270f, 0.112638064655866f, + 0.112875238983221f, 0.113112419144862f, 0.113349605100326f, + 0.113586796809166f, 0.113823994230944f, 0.114061197325236f, + 0.114298406051631f, 0.114535620369728f, 0.114772840239140f, + 0.115010065619491f, 0.115247296470419f, 0.115484532751574f, + 0.115721774422616f, 0.115959021443219f, 0.116196273773071f, + 0.116433531371868f, 0.116670794199323f, 0.116908062215158f, + 0.117145335379108f, 0.117382613650922f, 0.117619896990358f, + 0.117857185357189f, 0.118094478711200f, 0.118331777012186f, + 0.118569080219959f, 0.118806388294337f, 0.119043701195156f, + 0.119281018882261f, 0.119518341315510f, 0.119755668454773f, + 0.119993000259933f, 0.120230336690886f, 0.120467677707538f, + 0.120705023269809f, 0.120942373337631f, 0.121179727870948f, + 0.121417086829716f, 0.121654450173904f, 0.121891817863493f, + 0.122129189858476f, 0.122366566118858f, 0.122603946604659f, + 0.122841331275906f, 0.123078720092644f, 0.123316113014926f, + 0.123553510002819f, 0.123790911016403f, 0.124028316015769f, + 0.124265724961022f, 0.124503137812276f, 0.124740554529660f, + 0.124977975073316f, 0.125215399403394f, 0.125452827480062f, + 0.125690259263497f, 0.125927694713887f, 0.126165133791435f, + 0.126402576456356f, 0.126640022668876f, 0.126877472389234f, + 0.127114925577681f, 0.127352382194480f, 0.127589842199908f, + 0.127827305554251f, 0.128064772217811f, 0.128302242150900f, + 0.128539715313843f, 0.118573380744364f, 0.118810860248038f, + 0.119048342862613f, 0.119285828548465f, 0.119523317265979f, + 0.119760808975554f, 0.119998303637600f, 0.120235801212540f, + 0.120473301660811f, 0.120710804942859f, 0.120948311019145f, + 0.121185819850140f, 0.121423331396330f, 0.121660845618210f, + 0.121898362476291f, 0.122135881931093f, 0.122373403943149f, + 0.122610928473007f, 0.122848455481224f, 0.123085984928370f, + 0.123323516775029f, 0.123561050981795f, 0.123798587509275f, + 0.124036126318090f, 0.124273667368871f, 0.124511210622262f, + 0.124748756038919f, 0.124986303579512f, 0.125223853204721f, + 0.125461404875240f, 0.125698958551774f, 0.125936514195041f, + 0.126174071765771f, 0.126411631224707f, 0.126649192532603f, + 0.126886755650226f, 0.127124320538356f, 0.127361887157783f, + 0.127599455469312f, 0.127837025433760f, 0.128074597011953f, + 0.128312170164733f, 0.128549744852953f, 0.128787321037478f, + 0.129024898679186f, 0.129262477738966f, 0.129500058177720f, + 0.129737639956363f, 0.129975223035822f, 0.130212807377034f, + 0.130450392940952f, 0.130687979688539f, 0.130925567580771f, + 0.131163156578635f, 0.131400746643133f, 0.131638337735276f, + 0.131875929816089f, 0.132113522846610f, 0.132351116787888f, + 0.132588711600985f, 0.132826307246975f, 0.133063903686944f, + 0.133301500881990f, 0.133539098793225f, 0.133776697381772f, + 0.134014296608765f, 0.134251896435354f, 0.134489496822697f, + 0.134727097731967f, 0.134964699124348f, 0.135202300961038f, + 0.135439903203245f, 0.135677505812191f, 0.135915108749109f, + 0.136152711975246f, 0.136390315451860f, 0.136627919140220f, + 0.136865523001611f, 0.137103126997326f, 0.137340731088674f, + 0.137578335236974f, 0.137815939403557f, 0.138053543549769f, + 0.138291147636965f, 0.138528751626515f, 0.138766355479799f, + 0.139003959158210f, 0.139241562623155f, 0.139479165836051f, + 0.139716768758329f, 0.139954371351430f, 0.140191973576809f, + 0.140429575395934f, 0.140667176770284f, 0.140904777661350f, + 0.141142378030637f, 0.141379977839660f, 0.141617577049948f, + 0.141855175623041f, 0.142092773520492f, 0.142330370703868f, + 0.142567967134744f, 0.142805562774712f, 0.143043157585373f, + 0.143280751528341f, 0.143518344565244f, 0.143755936657720f, + 0.143993527767421f, 0.144231117856009f, 0.144468706885161f, + 0.144706294816565f, 0.144943881611921f, 0.145181467232942f, + 0.145419051641353f, 0.145656634798891f, 0.145894216667305f, + 0.146131797208357f, 0.146369376383822f, 0.146606954155485f, + 0.146844530485146f, 0.147082105334614f, 0.147319678665714f, + 0.147557250440280f, 0.147794820620161f, 0.148032389167217f, + 0.148269956043319f, 0.148507521210353f, 0.148745084630214f, + 0.148982646264813f, 0.149220206076071f, 0.149457764025922f, + 0.149695320076311f, 0.149932874189196f, 0.150170426326549f, + 0.150407976450352f, 0.150645524522601f, 0.150883070505302f, + 0.151120614360475f, 0.151358156050152f, 0.151595695536379f, + 0.151833232781210f, 0.152070767746715f, 0.152308300394975f, + 0.152545830688083f, 0.152783358588146f, 0.153020884057280f, + 0.153258407057616f, 0.153495927551297f, 0.153733445500477f, + 0.153970960867323f, 0.154208473614016f, 0.154445983702745f, + 0.154683491095716f, 0.154920995755145f, 0.155158497643259f, + 0.155395996722300f, 0.155633492954521f, 0.155870986302186f, + 0.156108476727575f, 0.156345964192975f, 0.156583448660691f, + 0.156820930093035f, 0.157058408452335f, 0.157295883700930f, + 0.157533355801171f, 0.147567013792809f, 0.147804479483445f, + 0.148041941912855f, 0.148279401043438f, 0.148516856837608f, + 0.148754309257790f, 0.148991758266421f, 0.149229203825950f, + 0.149466645898839f, 0.149704084447562f, 0.149941519434606f, + 0.150178950822470f, 0.150416378573664f, 0.150653802650711f, + 0.150891223016147f, 0.151128639632520f, 0.151366052462390f, + 0.151603461468329f, 0.151840866612921f, 0.152078267858765f, + 0.152315665168468f, 0.152553058504653f, 0.152790447829953f, + 0.153027833107014f, 0.153265214298494f, 0.153502591367065f, + 0.153739964275408f, 0.153977332986220f, 0.154214697462207f, + 0.154452057666090f, 0.154689413560599f, 0.154926765108481f, + 0.155164112272490f, 0.155401455015396f, 0.155638793299981f, + 0.155876127089037f, 0.156113456345370f, 0.156350781031798f, + 0.156588101111152f, 0.156825416546274f, 0.157062727300019f, + 0.157300033335253f, 0.157537334614857f, 0.157774631101722f, + 0.158011922758752f, 0.158249209548862f, 0.158486491434983f, + 0.158723768380054f, 0.158961040347028f, 0.159198307298871f, + 0.159435569198561f, 0.159672826009087f, 0.159910077693452f, + 0.160147324214669f, 0.160384565535767f, 0.160621801619783f, + 0.160859032429770f, 0.161096257928790f, 0.161333478079920f, + 0.161570692846248f, 0.161807902190875f, 0.162045106076912f, + 0.162282304467485f, 0.162519497325732f, 0.162756684614802f, + 0.162993866297856f, 0.153027231415457f, 0.153264401776015f, + 0.153501566420118f, 0.153738725310976f, 0.153975878411812f, + 0.154213025685861f, 0.154450167096373f, 0.154687302606606f, + 0.154924432179834f, 0.155161555779340f, 0.155398673368422f, + 0.155635784910389f, 0.155872890368562f, 0.156109989706276f, + 0.156347082886876f, 0.156584169873721f, 0.156821250630182f, + 0.157058325119641f, 0.157295393305493f, 0.157532455151146f, + 0.157769510620020f, 0.158006559675547f, 0.158243602281171f, + 0.158480638400349f, 0.158717667996550f, 0.158954691033254f, + 0.159191707473956f, 0.159428717282161f, 0.159665720421387f, + 0.159902716855164f, 0.160139706547035f, 0.160376689460556f, + 0.160613665559292f, 0.160850634806823f, 0.161087597166742f, + 0.161324552602651f, 0.161561501078168f, 0.161798442556921f, + 0.162035377002551f, 0.162272304378710f, 0.162509224649065f, + 0.162746137777293f, 0.162983043727084f, 0.163219942462139f, + 0.163456833946175f, 0.163693718142917f, 0.163930595016105f, + 0.164167464529489f, 0.164404326646834f, 0.164641181331916f, + 0.164878028548522f, 0.165114868260454f, 0.165351700431524f, + 0.155384714102944f, 0.155621531083777f, 0.155858340415260f, + 0.156095142061255f, 0.156331935985636f, 0.156568722152290f, + 0.156805500525115f, 0.157042271068022f, 0.157279033744935f, + 0.157515788519790f, 0.157752535356533f, 0.157989274219126f, + 0.158226005071540f, 0.158462727877761f, 0.158699442601785f, + 0.158936149207623f, 0.159172847659294f, 0.159409537920833f, + 0.159646219956287f, 0.159882893729713f, 0.160119559205183f, + 0.160356216346779f, 0.160592865118596f, 0.160829505484743f, + 0.161066137409338f, 0.161302760856514f, 0.161539375790416f, + 0.161775982175200f, 0.162012579975035f, 0.162249169154102f, + 0.162485749676594f, 0.162722321506719f, 0.162958884608692f, + 0.163195438946746f, 0.163431984485122f, 0.163668521188075f, + 0.163905049019873f, 0.164141567944796f, 0.164378077927134f, + 0.164614578931192f, 0.164851070921286f, 0.165087553861745f, + 0.165324027716909f, 0.165560492451133f, 0.165796948028780f, + 0.166033394414230f, 0.156066020649259f, 0.156302448543495f, + 0.156538867138740f, 0.156775276399421f, 0.157011676289976f, + 0.157248066774858f, 0.157484447818530f, 0.157720819385467f, + 0.157957181440159f, 0.158193533947106f, 0.158429876870820f, + 0.158666210175827f, 0.158902533826663f, 0.159138847787880f, + 0.159375152024038f, 0.159611446499712f, 0.159847731179489f, + 0.160084006027966f, 0.160320271009756f, 0.160556526089482f, + 0.160792771231779f, 0.161029006401295f, 0.161265231562690f, + 0.161501446680637f, 0.161737651719821f, 0.161973846644939f, + 0.162210031420699f, 0.162446206011824f, 0.162682370383048f, + 0.162918524499116f, 0.163154668324787f, 0.163390801824832f, + 0.163626924964034f, 0.163863037707188f, 0.164099140019102f, + 0.164335231864596f, 0.164571313208501f, 0.164807384015662f, + 0.165043444250936f, 0.165279493879192f, 0.165515532865311f, + 0.165751561174187f, 0.155783767848112f, 0.156019774697231f, + 0.156255770763861f, 0.156491756012944f, 0.156727730409436f, + 0.156963693918304f, 0.157199646504527f, 0.157435588133098f, + 0.157671518769019f, 0.157907438377308f, 0.158143346922994f, + 0.158379244371116f, 0.158615130686729f, 0.158851005834898f, + 0.159086869780700f, 0.159322722489227f, 0.159558563925579f, + 0.159794394054872f, 0.160030212842232f, 0.160266020252799f, + 0.160501816251724f, 0.160737600804171f, 0.160973373875317f, + 0.161209135430348f, 0.161444885434466f, 0.161680623852884f, + 0.161916350650826f, 0.162152065793531f, 0.162387769246248f, + 0.162623460974239f, 0.162859140942778f, 0.163094809117152f, + 0.163330465462660f, 0.163566109944612f, 0.163801742528333f, + 0.164037363179157f, 0.164272971862433f, 0.164508568543521f, + 0.164744153187794f, 0.154775914838023f, 0.155011475304832f, + 0.155247023631016f, 0.155482559781998f, 0.155718083723212f, + 0.155953595420103f, 0.156189094838130f, 0.156424581942764f, + 0.156660056699488f, 0.156895519073797f, 0.157130969031199f, + 0.157366406537214f, 0.157601831557375f, 0.157837244057224f, + 0.158072644002320f, 0.158308031358231f, 0.158543406090539f, + 0.158778768164837f, 0.159014117546731f, 0.159249454201839f, + 0.159484778095791f, 0.159720089194232f, 0.159955387462814f, + 0.160190672867206f, 0.160425945373088f, 0.160661204946150f, + 0.160896451552098f, 0.161131685156647f, 0.161366905725526f, + 0.161602113224477f, 0.161837307619252f, 0.162072488875617f, + 0.162307656959350f, 0.162542811836242f, 0.162777953472093f, + 0.163013081832719f, 0.163248196883948f, 0.163483298591617f, + 0.153514575998966f, 0.153749650917085f, 0.153984712389236f, + 0.154219760381308f, 0.154454794859201f, 0.154689815788829f, + 0.154924823136117f, 0.155159816867001f, 0.155394796947432f, + 0.155629763343373f, 0.155864716020796f, 0.156099654945689f, + 0.156334580084050f, 0.156569491401892f, 0.156804388865237f, + 0.157039272440121f, 0.157274142092592f, 0.157508997788710f, + 0.157743839494548f, 0.157978667176190f, 0.158213480799735f, + 0.158448280331291f, 0.158683065736980f, 0.158917836982936f, + 0.159152594035305f, 0.159387336860246f, 0.159622065423929f, + 0.159856779692538f, 0.160091479632268f, 0.160326165209327f, + 0.160560836389934f, 0.160795493140323f, 0.161030135426737f, + 0.161264763215433f, 0.161499376472680f, 0.161733975164760f, + 0.161968559257967f, 0.151999317795992f, 0.152233872590381f, + 0.152468412684851f, 0.152702938045745f, 0.152937448639417f, + 0.153171944432235f, 0.153406425390579f, 0.153640891480840f, + 0.153875342669423f, 0.154109778922744f, 0.154344200207233f, + 0.154578606489328f, 0.154812997735486f, 0.155047373912170f, + 0.155281734985858f, 0.155516080923042f, 0.155750411690222f, + 0.155984727253915f, 0.156219027580646f, 0.156453312636956f, + 0.156687582389395f, 0.156921836804528f, 0.157156075848931f, + 0.157390299489191f, 0.157624507691911f, 0.157858700423702f, + 0.158092877651190f, 0.158327039341012f, 0.158561185459819f, + 0.158795315974272f, 0.159029430851045f, 0.159263530056826f, + 0.159497613558314f, 0.159731681322219f, 0.159965733315264f, + 0.160199769504187f, 0.150229978933122f, 0.150463983414054f, + 0.150697971991144f, 0.150931944631177f, 0.151165901300950f, + 0.151399841967272f, 0.151633766596964f, 0.151867675156863f, + 0.152101567613812f, 0.152335443934671f, 0.152569304086311f, + 0.152803148035615f, 0.153036975749479f, 0.153270787194809f, + 0.153504582338527f, 0.153738361147564f, 0.153972123588865f, + 0.154205869629387f, 0.154439599236099f, 0.154673312375982f, + 0.154907009016030f, 0.155140689123249f, 0.155374352664657f, + 0.155607999607285f, 0.155841629918175f, 0.156075243564383f, + 0.156308840512976f, 0.156542420731034f, 0.156775984185648f, + 0.157009530843923f, 0.157243060672975f, 0.157476573639934f, + 0.157710069711939f, 0.157943548856145f, 0.158177011039717f, + 0.158410456229833f, 0.158643884393684f, 0.148673484575858f, + 0.148906878588797f, 0.149140255477114f, 0.149373615208048f, + 0.149606957748852f, 0.149840283066790f, 0.150073591129136f, + 0.150306881903181f, 0.150540155356224f, 0.150773411455578f, + 0.151006650168570f, 0.151239871462536f, 0.151473075304826f, + 0.151706261662802f, 0.151939430503840f, 0.152172581795324f, + 0.152405715504656f, 0.152638831599245f, 0.152871930046515f, + 0.153105010813902f, 0.153338073868854f, 0.153571119178832f, + 0.153804146711309f, 0.154037156433768f, 0.154270148313708f, + 0.154503122318637f, 0.154736078416078f, 0.154969016573565f, + 0.155201936758644f, 0.155434838938874f, 0.155667723081826f, + 0.155900589155082f, 0.156133437126239f, 0.156366266962903f, + 0.156599078632696f, 0.156831872103249f, 0.157064647342207f, + 0.147093593394615f, 0.147326332073366f, 0.147559052423530f, + 0.147791754412800f, 0.148024438008882f, 0.148257103179495f, + 0.148489749892369f, 0.148722378115246f, 0.148954987815883f, + 0.149187578962047f, 0.149420151521517f, 0.149652705462085f, + 0.149885240751555f, 0.150117757357745f, 0.150350255248482f, + 0.150582734391608f, 0.150815194754977f, 0.151047636306454f, + 0.151280059013917f, 0.151512462845255f, 0.151744847768373f, + 0.151977213751184f, 0.152209560761616f, 0.152441888767607f, + 0.152674197737110f, 0.152906487638089f, 0.153138758438519f, + 0.153371010106390f, 0.153603242609701f, 0.153835455916467f, + 0.154067649994712f, 0.154299824812474f, 0.154531980337803f, + 0.154764116538760f, 0.154996233383421f, 0.155228330839873f, + 0.155460408876213f, 0.145488656537942f, 0.145720695638407f, + 0.145952715223132f, 0.146184715260266f, 0.146416695717967f, + 0.146648656564411f, 0.146880597767780f, 0.147112519296273f, + 0.147344421118099f, 0.147576303201479f, 0.147808165514649f, + 0.148040008025854f, 0.148271830703353f, 0.148503633515418f, + 0.148735416430330f, 0.148967179416387f, 0.149198922441895f, + 0.149430645475174f, 0.149662348484558f, 0.149894031438390f, + 0.150125694305028f, 0.150357337052841f, 0.150588959650210f, + 0.150820562065529f, 0.151052144267205f, 0.151283706223655f, + 0.151515247903310f, 0.151746769274613f, 0.151978270306019f, + 0.152209750965996f, 0.152441211223024f, 0.152672651045593f, + 0.152904070402210f, 0.153135469261390f, 0.153366847591662f, + 0.153598205361567f, 0.153829542539659f, 0.154060859094504f, + 0.154292154994679f, 0.154523430208775f, 0.144550873782782f, + 0.144782107530540f, 0.145013320498062f, 0.145244512653990f, + 0.145475683966974f, 0.145706834405678f, 0.145937963938779f, + 0.146169072534965f, 0.146400160162936f, 0.146631226791407f, + 0.146862272389103f, 0.147093296924760f, 0.147324300367130f, + 0.147555282684973f, 0.147786243847065f, 0.148017183822193f, + 0.148248102579156f, 0.148479000086764f, 0.148709876313842f, + 0.148940731229225f, 0.149171564801762f, 0.149402377000313f, + 0.149633167793751f, 0.149863937150961f, 0.150094685040840f, + 0.150325411432298f, 0.150556116294257f, 0.150786799595650f, + 0.151017461305425f, 0.151248101392539f, 0.151478719825965f, + 0.151709316574685f, 0.151939891607695f, 0.152170444894002f, + 0.152400976402628f, 0.152631486102603f, 0.152861973962974f, + 0.153092439952797f, 0.153322884041140f, 0.153553306197087f, + 0.153783706389730f, 0.154014084588175f, 0.144040629838929f, + 0.144270963956348f, 0.144501275986961f, 0.144731565899924f, + 0.144961833664404f, 0.145192079249581f, 0.145422302624648f, + 0.145652503758808f, 0.145882682621278f, 0.146112839181288f, + 0.146342973408078f, 0.146573085270901f, 0.146803174739024f, + 0.147033241781725f, 0.147263286368294f, 0.147493308468033f, + 0.147723308050258f, 0.147953285084295f, 0.148183239539485f, + 0.148413171385178f, 0.148643080590738f, 0.148872967125543f, + 0.149102830958981f, 0.149332672060452f, 0.149562490399370f, + 0.149792285945159f, 0.150022058667259f, 0.150251808535119f, + 0.150481535518200f, 0.150711239585978f, 0.150940920707939f, + 0.151170578853583f, 0.151400213992420f, 0.151629826093976f, + 0.151859415127784f, 0.152088981063395f, 0.152318523870367f, + 0.152548043518276f, 0.152777539976703f, 0.153007013215249f, + 0.153236463203521f, 0.153465889911143f, 0.153695293307748f, + 0.153924673362982f, 0.154154030046506f, 0.154383363327988f, + 0.144408862254502f, 0.144638148640966f, 0.144867411534476f, + 0.145096650904754f, 0.145325866721530f, 0.145555058954550f, + 0.145784227573570f, 0.146013372548361f, 0.146242493848704f, + 0.146471591444392f, 0.146700665305232f, 0.146929715401042f, + 0.147158741701652f, 0.147387744176907f, 0.147616722796661f, + 0.147845677530781f, 0.148074608349148f, 0.148303515221653f, + 0.148532398118202f, 0.148761257008710f, 0.148990091863106f, + 0.149218902651333f, 0.149447689343343f, 0.149676451909102f, + 0.149905190318589f, 0.150133904541793f, 0.150362594548718f, + 0.150591260309378f, 0.150819901793800f, 0.151048518972024f, + 0.151277111814102f, 0.151505680290097f, 0.151734224370087f, + 0.151962744024160f, 0.152191239222416f, 0.152419709934969f, + 0.152648156131945f, 0.152876577783480f, 0.153104974859726f, + 0.153333347330843f, 0.153561695167008f, 0.153790018338407f, + 0.154018316815238f, 0.154246590567715f, 0.154474839566059f, + 0.154703063780507f, 0.154931263181308f, 0.155159437738722f, + 0.155387587423022f, 0.155615712204492f, 0.155843812053431f, + 0.156071886940148f, 0.156299936834965f, 0.146324150785604f, + 0.146552150607636f, 0.146780125348808f, 0.147008074979490f, + 0.147235999470067f, 0.147463898790933f, 0.147691772912497f, + 0.147919621805179f, 0.148147445439411f, 0.148375243785639f, + 0.148603016814319f, 0.148830764495920f, 0.149058486800924f, + 0.149286183699826f, 0.149513855163131f, 0.149741501161358f, + 0.149969121665038f, 0.150196716644712f, 0.150424286070939f, + 0.150651829914283f, 0.150879348145326f, 0.151106840734659f, + 0.151334307652887f, 0.151561748870628f, 0.151789164358508f, + 0.152016554087171f, 0.152243918027269f, 0.152471256149469f, + 0.152698568424449f, 0.152925854822898f, 0.153153115315520f, + 0.153380349873030f, 0.153607558466155f, 0.153834741065634f, + 0.154061897642219f, 0.154289028166675f, 0.154516132609777f, + 0.154743210942315f, 0.154970263135090f, 0.155197289158914f, + 0.155424288984613f, 0.155651262583025f, 0.155878209925000f, + 0.156105130981400f, 0.156332025723100f, 0.156558894120987f, + 0.156785736145960f, 0.157012551768931f, 0.157239340960823f, + 0.157466103692572f, 0.157692839935127f, 0.157919549659449f, + 0.158146232836509f, 0.158372889437294f, 0.158599519432801f, + 0.158826122794039f, 0.159052699492031f, 0.159279249497811f, + 0.159505772782425f, 0.159732269316933f, 0.159958739072404f, + 0.160185182019924f, 0.160411598130588f, 0.160637987375502f, + 0.160864349725789f, 0.161090685152580f, 0.161316993627020f, + 0.161543275120267f, 0.151565718680876f, 0.151791946125256f, + 0.152018146501987f, 0.152244319782275f, 0.152470465937339f, + 0.152696584938411f, 0.152922676756732f, 0.153148741363559f, + 0.153374778730158f, 0.153600788827810f, 0.153826771627808f, + 0.154052727101455f, 0.154278655220068f, 0.154504555954977f, + 0.154730429277522f, 0.154956275159058f, 0.155182093570950f, + 0.155407884484577f, 0.155633647871328f, 0.155859383702607f, + 0.156085091949829f, 0.156310772584421f, 0.156536425577822f, + 0.156762050901484f, 0.156987648526872f, 0.157213218425462f, + 0.157438760568743f, 0.157664274928215f, 0.157889761475393f, + 0.158115220181800f, 0.158340651018977f, 0.158566053958471f, + 0.158791428971847f, 0.159016776030678f, 0.159242095106551f, + 0.159467386171067f, 0.159692649195835f, 0.159917884152481f, + 0.160143091012640f, 0.160368269747960f, 0.160593420330103f, + 0.160818542730740f, 0.161043636921558f, 0.161268702874254f, + 0.161493740560537f, 0.161718749952131f, 0.161943731020768f, + 0.162168683738195f, 0.162393608076173f, 0.162618504006471f, + 0.162843371500873f, 0.163068210531175f, 0.163293021069186f, + 0.163517803086724f, 0.163742556555623f, 0.163967281447728f, + 0.164191977734896f, 0.164416645388997f, 0.164641284381911f, + 0.164865894685533f, 0.165090476271770f, 0.165315029112540f, + 0.165539553179773f, 0.165764048445414f, 0.165988514881416f, + 0.166212952459749f, 0.166437361152392f, 0.166661740931336f, + 0.166886091768588f, 0.167110413636163f, 0.167334706506090f, + 0.167558970350412f, 0.167783205141181f, 0.168007410850464f, + 0.168231587450339f, 0.168455734912896f, 0.168679853210239f, + 0.168903942314481f, 0.169128002197751f, 0.169352032832189f, + 0.169576034189945f, 0.169800006243185f, 0.170023948964084f, + 0.170247862324832f, 0.170471746297630f, 0.170695600854690f, + 0.170919425968239f, 0.171143221610514f, 0.171366987753766f, + 0.171590724370256f, 0.171814431432261f, 0.172038108912066f, + 0.172261756781971f, 0.172485375014288f, 0.172708963581339f, + 0.172932522455463f, 0.173156051609007f, 0.173379551014331f, + 0.173603020643809f, 0.173826460469826f, 0.174049870464779f, + 0.174273250601078f, 0.174496600851146f, 0.174719921187417f, + 0.174943211582337f, 0.175166472008365f, 0.175389702437973f, + 0.175612902843644f, 0.175836073197874f, 0.176059213473171f, + 0.176282323642056f, 0.176505403677060f, 0.176728453550729f, + 0.176951473235621f, 0.177174462704304f, 0.177397421929360f, + 0.177620350883383f, 0.177843249538981f, 0.178066117868771f, + 0.178288955845384f, 0.178511763441463f, 0.178734540629664f, + 0.178957287382655f, 0.179180003673115f, 0.179402689473738f, + 0.179625344757226f, 0.179847969496298f, 0.180070563663683f, + 0.180293127232121f, 0.180515660174367f, 0.180738162463187f, + 0.180960634071358f, 0.181183074971672f, 0.181405485136931f, + 0.181627864539950f, 0.181850213153558f, 0.182072530950592f, + 0.182294817903906f, 0.182517073986364f, 0.182739299170841f, + 0.182961493430228f, 0.183183656737425f, 0.183405789065345f, + 0.183627890386914f, 0.183849960675070f, 0.184071999902763f, + 0.184294008042956f, 0.184515985068623f, 0.184737930952752f, + 0.184959845668342f, 0.185181729188404f, 0.185403581485962f, + 0.185625402534053f, 0.185847192305725f, 0.186068950774038f, + 0.186290677912066f, 0.186512373692895f, 0.186734038089621f, + 0.186955671075354f, 0.187177272623217f, 0.187398842706344f, + 0.187620381297882f, 0.187841888370990f, 0.188063363898839f, + 0.188284807854612f, 0.188506220211506f, 0.188727600942729f, + 0.188948950021500f, 0.189170267421053f, 0.189391553114633f, + 0.189612807075496f, 0.189834029276914f, 0.190055219692166f, + 0.190276378294547f, 0.190497505057364f, 0.190718599953936f, + 0.190939662957593f, 0.191160694041678f, 0.191381693179547f, + 0.191602660344568f, 0.181619784587509f, 0.181840687726985f, + 0.182061558813791f, 0.182282397821343f, 0.182503204723069f, + 0.182723979492412f, 0.182944722102825f, 0.183165432527773f, + 0.183386110740737f, 0.183606756715205f, 0.183827370424682f, + 0.184047951842681f, 0.184268500942731f, 0.184489017698371f, + 0.184709502083153f, 0.184929954070642f, 0.185150373634414f, + 0.185370760748058f, 0.185591115385175f, 0.185811437519379f, + 0.186031727124294f, 0.186251984173560f, 0.186472208640826f, + 0.186692400499755f, 0.186912559724022f, 0.187132686287313f, + 0.187352780163329f, 0.187572841325781f, 0.187792869748393f, + 0.188012865404900f, 0.188232828269052f, 0.188452758314610f, + 0.188672655515346f, 0.188892519845045f, 0.189112351277506f, + 0.189332149786539f, 0.189551915345965f, 0.189771647929618f, + 0.189991347511347f, 0.190211014065009f, 0.190430647564477f, + 0.190650247983633f, 0.190869815296374f, 0.191089349476607f, + 0.191308850498254f, 0.191528318335246f, 0.191747752961530f, + 0.191967154351062f, 0.192186522477811f, 0.192405857315761f, + 0.192625158838903f, 0.192844427021247f, 0.193063661836809f, + 0.193282863259621f, 0.193502031263726f, 0.193721165823180f, + 0.193940266912050f, 0.194159334504417f, 0.194378368574374f, + 0.194597369096023f, 0.194816336043484f, 0.195035269390884f, + 0.195254169112365f, 0.195473035182082f, 0.195691867574200f, + 0.195910666262898f, 0.196129431222366f, 0.196348162426807f, + 0.196566859850436f, 0.196785523467482f, 0.197004153252183f, + 0.197222749178791f, 0.197441311221572f, 0.197659839354801f, + 0.197878333552768f, 0.198096793789773f, 0.198315220040131f, + 0.198533612278166f, 0.198751970478218f, 0.198970294614635f, + 0.199188584661781f, 0.199406840594031f, 0.199625062385772f, + 0.199843250011403f, 0.200061403445335f, 0.200279522661994f, + 0.200497607635815f, 0.200715658341246f, 0.200933674752750f, + 0.201151656844797f, 0.201369604591875f, 0.201587517968481f, + 0.201805396949124f, 0.202023241508327f, 0.202241051620625f, + 0.202458827260564f, 0.202676568402703f, 0.202894275021614f, + 0.203111947091881f, 0.203329584588098f, 0.203547187484876f, + 0.203764755756833f, 0.203982289378603f, 0.204199788324830f, + 0.204417252570173f, 0.204634682089301f, 0.204852076856895f, + 0.205069436847651f, 0.205286762036273f, 0.195300241474869f, + 0.195517496983395f, 0.195734717613981f, 0.195951903341383f, + 0.196169054140369f, 0.196386169985719f, 0.196603250852226f, + 0.196820296714693f, 0.197037307547939f, 0.197254283326792f, + 0.197471224026094f, 0.197688129620699f, 0.197905000085473f, + 0.198121835395295f, 0.198338635525054f, 0.198555400449654f, + 0.198772130144011f, 0.198988824583052f, 0.199205483741716f, + 0.199422107594955f, 0.199638696117735f, 0.199855249285032f, + 0.200071767071834f, 0.200288249453143f, 0.200504696403972f, + 0.200721107899348f, 0.200937483914308f, 0.201153824423902f, + 0.201370129403194f, 0.201586398827258f, 0.201802632671180f, + 0.202018830910062f, 0.202234993519015f, 0.202451120473161f, + 0.202667211747639f, 0.202883267317596f, 0.203099287158194f, + 0.203315271244605f, 0.203531219552015f, 0.203747132055621f, + 0.203963008730634f, 0.204178849552277f, 0.204394654495782f, + 0.204610423536399f, 0.204826156649384f, 0.205041853810011f, + 0.205257514993562f, 0.205473140175333f, 0.205688729330634f, + 0.205904282434784f, 0.206119799463116f, 0.206335280390975f, + 0.206550725193719f, 0.206766133846718f, 0.206981506325353f, + 0.207196842605018f, 0.197208331738507f, 0.197423595546465f, + 0.197638823081710f, 0.197854014319685f, 0.198069169235847f, + 0.198284287805661f, 0.198499370004610f, 0.198714415808185f, + 0.198929425191892f, 0.199144398131246f, 0.199359334601778f, + 0.199574234579030f, 0.199789098038554f, 0.200003924955918f, + 0.200218715306700f, 0.200433469066490f, 0.200648186210891f, + 0.200862866715519f, 0.201077510556001f, 0.201292117707978f, + 0.201506688147100f, 0.201721221849034f, 0.201935718789455f, + 0.202150178944052f, 0.202364602288527f, 0.202578988798593f, + 0.202793338449976f, 0.203007651218415f, 0.203221927079658f, + 0.203436166009470f, 0.203650367983626f, 0.203864532977911f, + 0.204078660968127f, 0.204292751930084f, 0.204506805839607f, + 0.204720822672532f, 0.204934802404708f, 0.205148745011996f, + 0.205362650470268f, 0.205576518755411f, 0.205790349843322f, + 0.206004143709912f, 0.206217900331101f, 0.206431619682827f, + 0.196441490818421f, 0.196655135559069f, 0.196868742958131f, + 0.197082312991588f, 0.197295845635438f, 0.197509340865688f, + 0.197722798658360f, 0.197936218989487f, 0.198149601835112f, + 0.198362947171294f, 0.198576254974103f, 0.198789525219620f, + 0.199002757883940f, 0.199215952943169f, 0.199429110373427f, + 0.199642230150843f, 0.199855312251563f, 0.200068356651740f, + 0.200281363327544f, 0.200494332255154f, 0.200707263410764f, + 0.200920156770577f, 0.201133012310811f, 0.201345830007696f, + 0.201558609837472f, 0.201771351776394f, 0.201984055800729f, + 0.202196721886754f, 0.202409350010760f, 0.202621940149051f, + 0.202834492277942f, 0.203047006373760f, 0.203259482412845f, + 0.203471920371550f, 0.203684320226238f, 0.203896681953288f, + 0.204109005529087f, 0.204321290930037f, 0.204533538132551f, + 0.194541936190444f, 0.194754106925378f, 0.194966239391191f, + 0.195178333564346f, 0.195390389421317f, 0.195602406938593f, + 0.195814386092672f, 0.196026326860067f, 0.196238229217301f, + 0.196450093140911f, 0.196661918607446f, 0.196873705593467f, + 0.197085454075546f, 0.197297164030270f, 0.197508835434236f, + 0.197720468264055f, 0.197932062496348f, 0.198143618107750f, + 0.198355135074909f, 0.198566613374484f, 0.198778052983145f, + 0.198989453877578f, 0.199200816034477f, 0.199412139430552f, + 0.199623424042522f, 0.199834669847121f, 0.200045876821094f, + 0.200257044941199f, 0.200468174184204f, 0.200679264526893f, + 0.200890315946060f, 0.201101328418510f, 0.201312301921064f, + 0.201523236430552f, 0.201734131923817f, 0.191741177455104f, + 0.191951994846504f, 0.192162773152286f, 0.192373512349342f, + 0.192584212414577f, 0.192794873324908f, 0.193005495057265f, + 0.193216077588589f, 0.193426620895835f, 0.193637124955968f, + 0.193847589745967f, 0.194058015242823f, 0.194268401423539f, + 0.194478748265130f, 0.194689055744625f, 0.194899323839062f, + 0.195109552525495f, 0.195319741780988f, 0.195529891582617f, + 0.195740001907471f, 0.195950072732653f, 0.196160104035275f, + 0.196370095792464f, 0.196580047981357f, 0.196789960579106f, + 0.196999833562872f, 0.197209666909832f, 0.197419460597172f, + 0.197629214602091f, 0.197838928901803f, 0.198048603473531f, + 0.198258238294511f, 0.188264022419379f, 0.188473577670623f, + 0.188683093102902f, 0.188892568693502f, 0.189102004419720f, + 0.189311400258868f, 0.189520756188268f, 0.189730072185253f, + 0.189939348227172f, 0.190148584291383f, 0.190357780355258f, + 0.190566936396181f, 0.190776052391548f, 0.190985128318767f, + 0.191194164155259f, 0.191403159878457f, 0.191612115465806f, + 0.191821030894764f, 0.192029906142801f, 0.192238741187397f, + 0.192447536006049f, 0.192656290576263f, 0.192865004875557f, + 0.193073678881463f, 0.193282312571524f, 0.193490905923295f, + 0.193699458914346f, 0.193907971522257f, 0.194116443724619f, + 0.194324875499038f, 0.194533266823131f, 0.184537806751915f, + 0.184746117108257f, 0.184954386947198f, 0.185162616246405f, + 0.185370804983555f, 0.185578953136341f, 0.185787060682465f, + 0.185995127599643f, 0.186203153865602f, 0.186411139458082f, + 0.186619084354836f, 0.186826988533627f, 0.187034851972234f, + 0.187242674648445f, 0.187450456540062f, 0.187658197624897f, + 0.187865897880778f, 0.188073557285542f, 0.188281175817040f, + 0.188488753453134f, 0.188696290171701f, 0.188903785950626f, + 0.189111240767811f, 0.189318654601166f, 0.189526027428616f, + 0.189733359228097f, 0.189940649977559f, 0.190147899654962f, + 0.190355108238280f, 0.180358464782885f, 0.180565591112002f, + 0.180772676281026f, 0.180979720267981f, 0.181186723050902f, + 0.181393684607836f, 0.181600604916841f, 0.181807483955990f, + 0.182014321703366f, 0.182221118137065f, 0.182427873235197f, + 0.182634586975881f, 0.182841259337251f, 0.183047890297452f, + 0.183254479834642f, 0.183461027926990f, 0.183667534552678f, + 0.183873999689901f, 0.184080423316866f, 0.184286805411791f, + 0.184493145952909f, 0.184699444918461f, 0.184905702286705f, + 0.185111918035908f, 0.185318092144350f, 0.185524224590325f, + 0.185730315352137f, 0.185936364408104f, 0.175938560813942f, + 0.176144526393218f, 0.176350450201674f, 0.176556332217677f, + 0.176762172419604f, 0.176967970785847f, 0.177173727294808f, + 0.177379441924904f, 0.177585114654563f, 0.177790745462223f, + 0.177996334326338f, 0.178201881225372f, 0.178407386137802f, + 0.178612849042117f, 0.178818269916818f, 0.179023648740420f, + 0.179228985491448f, 0.179434280148441f, 0.179639532689949f, + 0.179844743094535f, 0.180049911340774f, 0.180255037407254f, + 0.180460121272574f, 0.180665162915346f, 0.180870162314195f, + 0.181075119447757f, 0.181280034294680f, 0.181484906833627f, + 0.171485926120658f, 0.171690713979683f, 0.171895459466788f, + 0.172100162560683f, 0.172304823240091f, 0.172509441483747f, + 0.172714017270397f, 0.172918550578801f, 0.173123041387730f, + 0.173327489675969f, 0.173531895422314f, 0.173736258605573f, + 0.173940579204567f, 0.174144857198129f, 0.174349092565105f, + 0.174553285284351f, 0.174757435334738f, 0.174961542695148f, + 0.175165607344476f, 0.175369629261627f, 0.175573608425522f, + 0.175777544815091f, 0.175981438409279f, 0.176185289187040f, + 0.176389097127344f, 0.176592862209170f, 0.176796584411511f, + 0.177000263713372f, 0.167000089171158f, 0.167203682609124f, + 0.167407233083699f, 0.167610740573936f, 0.167814205058903f, + 0.168017626517678f, 0.168221004929352f, 0.168424340273029f, + 0.168627632527822f, 0.168830881672862f, 0.169034087687287f, + 0.169237250550250f, 0.169440370240916f, 0.169643446738461f, + 0.169846480022075f, 0.170049470070959f, 0.170252416864327f, + 0.170455320381406f, 0.170658180601432f, 0.170860997503658f, + 0.171063771067346f, 0.171266501271771f, 0.171469188096220f, + 0.171671831519994f, 0.171874431522404f, 0.172076988082775f, + 0.172279501180443f, 0.162278159872144f, 0.162480585982465f, + 0.162682968568167f, 0.162885307608635f, 0.163087603083268f, + 0.163289854971476f, 0.163492063252680f, 0.163694227906317f, + 0.163896348911833f, 0.164098426248687f, 0.164300459896352f, + 0.164502449834311f, 0.164704396042060f, 0.164906298499108f, + 0.165108157184976f, 0.165309972079197f, 0.165511743161317f, + 0.165713470410892f, 0.165915153807493f, 0.166116793330702f, + 0.166318388960114f, 0.166519940675335f, 0.166721448455984f, + 0.166922912281694f, 0.167124332132106f, 0.167325707986878f, + 0.167527039825678f, 0.157524516705573f, 0.157725760451481f, + 0.157926960120494f, 0.158128115692331f, 0.158329227146721f, + 0.158530294463405f, 0.158731317622137f, 0.158932296602685f, + 0.159133231384826f, 0.159334121948353f, 0.159534968273067f, + 0.159735770338785f, 0.159936528125335f, 0.160137241612557f, + 0.160337910780302f, 0.160538535608436f, 0.160739116076836f, + 0.160939652165391f, 0.161140143854002f, 0.161340591122584f, + 0.161540993951062f, 0.161741352319375f, 0.161941666207473f, + 0.162141935595320f, 0.162342160462890f, 0.162542340790172f, + 0.162742476557164f, 0.162942567743880f, 0.152938803407730f, + 0.153138805373977f, 0.153338762700057f, 0.153538675366031f, + 0.153738543351972f, 0.153938366637967f, 0.154138145204113f, + 0.154337879030521f, 0.154537568097313f, 0.154737212384624f, + 0.154936811872602f, 0.155136366541405f, 0.155335876371206f, + 0.155535341342188f, 0.155734761434549f, 0.155934136628495f, + 0.156133466904249f, 0.156332752242043f, 0.156531992622123f, + 0.156731188024747f, 0.156930338430184f, 0.157129443818716f, + 0.157328504170639f, 0.157527519466260f, 0.157726489685896f, + 0.157925414809880f, 0.158124294818555f, 0.158323129692277f, + 0.148318108488803f, 0.148516853033737f, 0.148715552384859f, + 0.148914206522576f, 0.149112815427303f, 0.149311379079472f, + 0.149509897459523f, 0.149708370547911f, 0.149906798325103f, + 0.150105180771577f, 0.150303517867824f, 0.150501809594347f, + 0.150700055931663f, 0.150898256860299f, 0.151096412360795f, + 0.151294522413704f, 0.151492586999591f, 0.151690606099032f, + 0.151888579692616f, 0.152086507760946f, 0.152284390284635f, + 0.152482227244310f, 0.152680018620608f, 0.152877764394181f, + 0.153075464545691f, 0.153273119055814f, 0.153470727905238f, + 0.153668291074661f, 0.143661997622184f, 0.143859469373757f, + 0.144056895387502f, 0.144254275644170f, 0.144451610124522f, + 0.144648898809330f, 0.144846141679382f, 0.145043338715474f, + 0.145240489898417f, 0.145437595209035f, 0.145634654628161f, + 0.145831668136644f, 0.146028635715342f, 0.146225557345127f, + 0.146422433006884f, 0.146619262681509f, 0.146816046349909f, + 0.147012783993008f, 0.147209475591736f, 0.147406121127041f, + 0.147602720579879f, 0.147799273931220f, 0.147995781162048f, + 0.148192242253357f, 0.148388657186152f, 0.148585025941455f, + 0.148781348500295f, 0.148977624843717f, 0.149173854952777f, + 0.149370038808543f, 0.139362365469483f, 0.139558456761915f, + 0.139754501744331f, 0.139950500397849f, 0.140146452703599f, + 0.140342358642723f, 0.140538218196374f, 0.140734031345720f, + 0.140929798071938f, 0.141125518356222f, 0.141321192179772f, + 0.141516819523806f, 0.141712400369552f, 0.141907934698248f, + 0.142103422491149f, 0.142298863729519f, 0.142494258394634f, + 0.142689606467784f, 0.142884907930271f, 0.143080162763409f, + 0.143275370948524f, 0.143470532466954f, 0.143665647300050f, + 0.143860715429175f, 0.144055736835705f, 0.144250711501027f, + 0.144445639406540f, 0.144640520533658f, 0.144835354863804f, + 0.145030142378415f, 0.145224883058941f, 0.135215765964230f, + 0.135410412920980f, 0.135605012988065f, 0.135799566146984f, + 0.135994072379245f, 0.136188531666373f, 0.136382943989902f, + 0.136577309331380f, 0.136771627672365f, 0.136965898994431f, + 0.137160123279160f, 0.137354300508149f, 0.137548430663007f, + 0.137742513725355f, 0.137936549676826f, 0.138130538499066f, + 0.138324480173732f, 0.138518374682495f, 0.138712222007036f, + 0.138906022129051f, 0.139099775030246f, 0.139293480692340f, + 0.139487139097066f, 0.139680750226166f, 0.139874314061398f, + 0.140067830584528f, 0.140261299777338f, 0.140454721621621f, + 0.140648096099182f, 0.140841423191837f, 0.141034702881418f, + 0.141227935149766f, 0.141421119978735f, 0.131410446427580f, + 0.131603536323403f, 0.131796578725485f, 0.131989573615728f, + 0.132182520976047f, 0.132375420788372f, 0.132568273034642f, + 0.132761077696810f, 0.132953834756840f, 0.133146544196710f, + 0.133339205998410f, 0.133531820143939f, 0.133724386615314f, + 0.133916905394560f, 0.134109376463715f, 0.134301799804830f, + 0.134494175399969f, 0.134686503231206f, 0.134878783280630f, + 0.135071015530340f, 0.135263199962448f, 0.135455336559080f, + 0.135647425302371f, 0.135839466174471f, 0.136031459157541f, + 0.136223404233754f, 0.136415301385298f, 0.136607150594369f, + 0.136798951843178f, 0.136990705113949f, 0.137182410388915f, + 0.137374067650325f, 0.137565676880437f, 0.137757238061525f, + 0.137948751175871f, 0.127936405283160f, 0.128127822210926f, + 0.128319191018876f, 0.128510511689345f, 0.128701784204678f, + 0.128893008547232f, 0.129084184699378f, 0.129275312643499f, + 0.129466392361988f, 0.129657423837253f, 0.129848407051713f, + 0.130039341987800f, 0.130230228627958f, 0.130421066954641f, + 0.130611856950320f, 0.130802598597474f, 0.130993291878596f, + 0.131183936776193f, 0.131374533272780f, 0.131565081350888f, + 0.131755580993060f, 0.131946032181848f, 0.132136434899821f, + 0.132326789129556f, 0.132517094853645f, 0.132707352054692f, + 0.132897560715312f, 0.133087720818134f, 0.133277832345797f, + 0.133467895280954f, 0.133657909606270f, 0.133847875304422f, + 0.134037792358100f, 0.134227660750005f, 0.134417480462852f, + 0.134607251479366f, 0.134796973782287f, 0.134986647354364f, + 0.135176272178362f, 0.125162037314443f, 0.125351564590619f, + 0.125541043067079f, 0.125730472726634f, 0.125919853552109f, + 0.126109185526341f, 0.126298468632178f, 0.126487702852483f, + 0.126676888170129f, 0.126866024568002f, 0.127055112029000f, + 0.127244150536033f, 0.127433140072025f, 0.127622080619911f, + 0.127810972162637f, 0.127999814683163f, 0.128188608164462f, + 0.128377352589517f, 0.128566047941326f, 0.128754694202896f, + 0.128943291357249f, 0.129131839387418f, 0.129320338276448f, + 0.129508788007399f, 0.129697188563339f, 0.129885539927351f, + 0.130073842082530f, 0.130262095011983f, 0.130450298698829f, + 0.130638453126200f, 0.130826558277239f, 0.131014614135104f, + 0.131202620682961f, 0.131390577903992f, 0.131578485781390f, + 0.131766344298360f, 0.131954153438120f, 0.132141913183899f, + 0.132329623518940f, 0.132517284426497f, 0.132704895889836f, + 0.132892457892237f, 0.133079970416991f, 0.133267433447401f, + 0.133454846966783f, 0.133642210958465f, 0.123625714483175f, + 0.123812979369491f, 0.124000194678165f, 0.124187360392574f, + 0.124374476496108f, 0.124561542972168f, 0.124748559804169f, + 0.124935526975537f, 0.125122444469710f, 0.125309312270140f, + 0.125496130360289f, 0.125682898723633f, 0.125869617343660f, + 0.126056286203869f, 0.126242905287774f, 0.126429474578898f, + 0.126615994060779f, 0.126802463716965f, 0.126988883531018f, + 0.127175253486512f, 0.127361573567033f, 0.127547843756178f, + 0.127734064037559f, 0.127920234394799f, 0.128106354811532f, + 0.128292425271406f, 0.128478445758080f, 0.128664416255227f, + 0.128850336746530f, 0.129036207215687f, 0.129222027646406f, + 0.129407798022407f, 0.129593518327426f, 0.129779188545206f, + 0.129964808659507f, 0.130150378654098f, 0.130335898512761f, + 0.130521368219292f, 0.130706787757498f, 0.130892157111197f, + 0.131077476264222f, 0.131262745200416f, 0.131447963903636f, + 0.131633132357750f, 0.131818250546639f, 0.132003318454195f, + 0.132188336064325f, 0.132373303360945f, 0.132558220327986f, + 0.132743086949389f, 0.132927903209110f, 0.133112669091115f, + 0.133297384579382f, 0.133482049657903f, 0.133666664310682f, + 0.133851228521735f, 0.134035742275089f, 0.134220205554786f, + 0.134404618344876f, 0.134588980629427f, 0.124569481469902f, + 0.124753742695616f, 0.124937953368058f, 0.125122113471342f, + 0.125306222989596f, 0.125490281906956f, 0.125674290207575f, + 0.125858247875615f, 0.126042154895252f, 0.126226011250674f, + 0.126409816926081f, 0.126593571905685f, 0.126777276173711f, + 0.126960929714395f, 0.127144532511988f, 0.127328084550749f, + 0.127511585814954f, 0.127695036288888f, 0.127878435956849f, + 0.128061784803149f, 0.128245082812109f, 0.128428329968065f, + 0.128611526255365f, 0.128794671658368f, 0.128977766161446f, + 0.129160809748984f, 0.129343802405377f, 0.129526744115036f, + 0.129709634862381f, 0.129892474631845f, 0.130075263407874f, + 0.130258001174926f, 0.130440687917472f, 0.130623323619993f, + 0.130805908266985f, 0.130988441842955f, 0.131170924332422f, + 0.131353355719918f, 0.131535735989987f, 0.131718065127185f, + 0.131900343116080f, 0.132082569941253f, 0.132264745587298f, + 0.132446870038820f, 0.132628943280436f, 0.132810965296776f, + 0.132992936072482f, 0.133174855592210f, 0.133356723840624f, + 0.133538540802406f, 0.133720306462246f, 0.133902020804847f, + 0.134083683814926f, 0.134265295477211f, 0.134446855776441f, + 0.134628364697371f, 0.134809822224764f, 0.134991228343398f, + 0.135172583038063f, 0.135353886293561f, 0.135535138094705f, + 0.135716338426323f, 0.135897487273252f, 0.136078584620344f, + 0.136259630452462f, 0.136440624754481f, 0.136621567511290f, + 0.136802458707789f, 0.136983298328889f, 0.137164086359516f, + 0.137344822784607f, 0.137525507589110f, 0.137706140757987f, + 0.137886722276213f, 0.138067252128772f, 0.138247730300664f, + 0.138428156776899f, 0.138608531542500f, 0.138788854582503f, + 0.138969125881954f, 0.139149345425913f, 0.139329513199453f, + 0.139509629187658f, 0.139689693375625f, 0.139869705748462f, + 0.140049666291290f, 0.140229574989243f, 0.140409431827467f, + 0.140589236791119f, 0.140768989865371f, 0.140948691035404f, + 0.141128340286414f, 0.141307937603607f, 0.141487482972203f, + 0.141666976377434f, 0.141846417804544f, 0.142025807238789f, + 0.142205144665437f, 0.142384430069770f, 0.142563663437080f, + 0.142742844752673f, 0.142921974001866f, 0.143101051169990f, + 0.143280076242387f, 0.143459049204410f, 0.143637970041428f, + 0.143816838738820f, 0.143995655281975f, 0.144174419656299f, + 0.144353131847207f, 0.144531791840127f, 0.144710399620500f, + 0.144888955173778f, 0.145067458485427f, 0.145245909540924f, + 0.145424308325758f, 0.145602654825432f, 0.145780949025460f, + 0.145959190911367f, 0.146137380468694f, 0.146315517682990f, + 0.146493602539820f, 0.146671635024758f, 0.146849615123393f, + 0.147027542821324f, 0.147205418104165f, 0.147383240957540f, + 0.147561011367085f, 0.147738729318451f, 0.147916394797298f, + 0.148094007789301f, 0.148271568280146f, 0.148449076255531f, + 0.148626531701166f, 0.148803934602776f, 0.148981284946094f, + 0.149158582716869f, 0.149335827900860f, 0.149513020483839f, + 0.149690160451591f, 0.149867247789913f, 0.150044282484613f, + 0.150221264521513f, 0.150398193886446f, 0.150575070565257f, + 0.150751894543806f, 0.150928665807962f, 0.151105384343607f, + 0.151282050136638f, 0.151458663172960f, 0.151635223438494f, + 0.151811730919171f, 0.151988185600936f, 0.152164587469744f, + 0.152340936511564f, 0.152517232712377f, 0.152693476058176f, + 0.152869666534967f, 0.153045804128767f, 0.153221888825607f, + 0.153397920611528f, 0.153573899472585f, 0.153749825394845f, + 0.153925698364388f, 0.154101518367304f, 0.154277285389697f, + 0.154452999417684f, 0.154628660437392f, 0.154804268434962f, + 0.154979823396547f, 0.155155325308312f, 0.155330774156434f, + 0.155506169927103f, 0.155681512606521f, 0.155856802180902f, + 0.145828227713860f, 0.146003411036860f, 0.146178541213538f, + 0.146353618230160f, 0.146528642073000f, 0.146703612728346f, + 0.146878530182500f, 0.147053394421772f, 0.147228205432488f, + 0.147402963200985f, 0.147577667713612f, 0.147752318956730f, + 0.147926916916715f, 0.148101461579951f, 0.148275952932838f, + 0.148450390961786f, 0.148624775653217f, 0.148799106993568f, + 0.148973384969286f, 0.149147609566831f, 0.149321780772674f, + 0.149495898573301f, 0.149669962955208f, 0.149843973904904f, + 0.150017931408910f, 0.150191835453759f, 0.150365686025999f, + 0.150539483112186f, 0.150713226698891f, 0.150886916772697f, + 0.151060553320199f, 0.151234136328003f, 0.151407665782731f, + 0.151581141671012f, 0.151754563979492f, 0.151927932694826f, + 0.152101247803684f, 0.152274509292746f, 0.152447717148705f, + 0.152620871358268f, 0.152793971908151f, 0.152967018785084f, + 0.153140011975811f, 0.153312951467086f, 0.153485837245675f, + 0.153658669298358f, 0.153831447611926f, 0.154004172173183f, + 0.154176842968946f, 0.154349459986041f, 0.154522023211310f, + 0.154694532631606f, 0.154866988233794f, 0.155039390004751f, + 0.155211737931367f, 0.155384032000544f, 0.155556272199196f, + 0.155728458514250f, 0.155900590932645f, 0.156072669441331f, + 0.156244694027273f, 0.156416664677445f, 0.156588581378836f, + 0.156760444118446f, 0.156932252883288f, 0.157104007660386f, + 0.157275708436777f, 0.157447355199512f, 0.157618947935650f, + 0.157790486632267f, 0.157961971276449f, 0.158133401855293f, + 0.158304778355912f, 0.158476100765427f, 0.158647369070975f, + 0.158818583259702f, 0.158989743318769f, 0.159160849235348f, + 0.159331900996624f, 0.159502898589793f, 0.159673842002064f, + 0.159844731220658f, 0.160015566232809f, 0.160186347025763f, + 0.160357073586779f, 0.160527745903126f, 0.160698363962087f, + 0.160868927750957f, 0.161039437257044f, 0.161209892467666f, + 0.161380293370157f, 0.161550639951860f, 0.161720932200131f, + 0.161891170102339f, 0.151857542723253f, 0.152027671895491f, + 0.152197746683846f, 0.152367767075735f, 0.152537733058589f, + 0.152707644619850f, 0.152877501746973f, 0.153047304427424f, + 0.153217052648684f, 0.153386746398243f, 0.153556385663605f, + 0.153725970432286f, 0.153895500691816f, 0.154064976429733f, + 0.154234397633591f, 0.154403764290956f, 0.154573076389405f, + 0.154742333916527f, 0.154911536859925f, 0.155080685207213f, + 0.155249778946017f, 0.155418818063977f, 0.155587802548743f, + 0.155756732387980f, 0.155925607569361f, 0.156094428080577f, + 0.156263193909327f, 0.156431905043323f, 0.156600561470291f, + 0.156769163177967f, 0.156937710154101f, 0.157106202386455f, + 0.157274639862803f, 0.157443022570930f, 0.157611350498636f, + 0.157779623633731f, 0.157947841964038f, 0.158116005477393f, + 0.158284114161644f, 0.158452168004650f, 0.158620166994283f, + 0.158788111118428f, 0.158956000364983f, 0.159123834721855f, + 0.159291614176966f, 0.159459338718251f, 0.159627008333654f, + 0.159794623011134f, 0.159962182738662f, 0.160129687504220f, + 0.160297137295804f, 0.160464532101419f, 0.160631871909088f, + 0.160799156706840f, 0.160966386482721f, 0.150929750302173f, + 0.151096869998492f, 0.151263934637146f, 0.151430944206227f, + 0.151597898693841f, 0.151764798088106f, 0.151931642377153f, + 0.152098431549124f, 0.152265165592173f, 0.152431844494468f, + 0.152598468244187f, 0.152765036829522f, 0.152931550238678f, + 0.153098008459870f, 0.153264411481326f, 0.153430759291287f, + 0.153597051878007f, 0.153763289229750f, 0.153929471334795f, + 0.154095598181429f, 0.154261669757957f, 0.154427686052692f, + 0.154593647053961f, 0.154759552750102f, 0.154925403129467f, + 0.155091198180420f, 0.155256937891336f, 0.155422622250604f, + 0.155588251246622f, 0.155753824867805f, 0.155919343102577f, + 0.156084805939376f, 0.156250213366650f, 0.156415565372862f, + 0.156580861946485f, 0.156746103076005f, 0.156911288749923f, + 0.157076418956747f, 0.157241493685002f, 0.157406512923222f, + 0.157571476659956f, 0.157736384883764f, 0.157901237583217f, + 0.147862223824288f, 0.148026965440799f, 0.148191651498746f, + 0.148356281986751f, 0.148520856893447f, 0.148685376207480f, + 0.148849839917509f, 0.149014248012204f, 0.149178600480248f, + 0.149342897310336f, 0.149507138491175f, 0.149671324011486f, + 0.149835453859999f, 0.149999528025459f, 0.150163546496623f, + 0.150327509262259f, 0.150491416311149f, 0.150655267632085f, + 0.150819063213874f, 0.150982803045333f, 0.151146487115293f, + 0.151310115412596f, 0.151473687926096f, 0.151637204644661f, + 0.151800665557170f, 0.151964070652515f, 0.152127419919599f, + 0.152290713347339f, 0.152453950924663f, 0.152617132640512f, + 0.152780258483839f, 0.152943328443608f, 0.153106342508799f, + 0.153269300668400f, 0.153432202911413f, 0.153595049226854f, + 0.153757839603748f, 0.153920574031135f, 0.154083252498065f, + 0.144042064070991f, 0.144204630584213f, 0.144367141104205f, + 0.144529595620070f, 0.144691994120919f, 0.144854336595877f, + 0.145016623034082f, 0.145178853424683f, 0.145341027756842f, + 0.145503146019733f, 0.145665208202542f, 0.145827214294467f, + 0.145989164284721f, 0.146151058162526f, 0.146312895917117f, + 0.146474677537742f, 0.146636403013661f, 0.146798072334147f, + 0.146959685488484f, 0.147121242465970f, 0.147282743255912f, + 0.147444187847633f, 0.147605576230467f, 0.147766908393758f, + 0.147928184326867f, 0.148089404019162f, 0.148250567460028f, + 0.148411674638859f, 0.148572725545063f, 0.148733720168058f, + 0.148894658497279f, 0.149055540522167f, 0.149216366232181f, + 0.149377135616789f, 0.139334037742859f, 0.139494694445110f, + 0.139655294790435f, 0.139815838768352f, 0.139976326368392f, + 0.140136757580096f, 0.140297132393020f, 0.140457450796730f, + 0.140617712780806f, 0.140777918334840f, 0.140938067448435f, + 0.141098160111208f, 0.141258196312788f, 0.141418176042814f, + 0.141578099290940f, 0.141737966046832f, 0.141897776300166f, + 0.142057530040633f, 0.142217227257936f, 0.142376867941787f, + 0.142536452081915f, 0.142695979668058f, 0.142855450689968f, + 0.143014865137407f, 0.143174223000153f, 0.143333524267992f, + 0.143492768930726f, 0.143651956978168f, 0.143811088400141f, + 0.143970163186484f, 0.144129181327045f, 0.144288142811688f, + 0.144447047630285f, 0.134402084850112f, 0.134560876306290f, + 0.134719611066120f, 0.134878289119524f, 0.135036910456437f, + 0.135195475066808f, 0.135353982940597f, 0.135512434067775f, + 0.135670828438328f, 0.135829166042253f, 0.135987446869558f, + 0.136145670910266f, 0.136303838154409f, 0.136461948592034f, + 0.136620002213200f, 0.136777999007977f, 0.136935938966447f, + 0.137093822078707f, 0.137251648334863f, 0.137409417725035f, + 0.137567130239356f, 0.137724785867969f, 0.137882384601031f, + 0.138039926428711f, 0.138197411341190f, 0.138354839328662f, + 0.138512210381332f, 0.138669524489419f, 0.138826781643152f, + 0.138983981832774f, 0.128937314125928f, 0.129094400358105f, + 0.129251429596973f, 0.129408401832822f, 0.129565317055958f, + 0.129722175256696f, 0.129878976425365f, 0.130035720552306f, + 0.130192407627872f, 0.130349037642427f, 0.130505610586351f, + 0.130662126450032f, 0.130818585223873f, 0.130974986898289f, + 0.131131331463705f, 0.131287618910562f, 0.131443849229311f, + 0.131600022410415f, 0.131756138444350f, 0.131912197321604f, + 0.132068199032678f, 0.132224143568085f, 0.132380030918349f, + 0.132535861074008f, 0.132691634025612f, 0.132847349763722f, + 0.133003008278912f, 0.133158609561769f, 0.133314153602892f, + 0.133469640392892f, 0.123421258999779f, 0.123576631259414f, + 0.123731946239833f, 0.123887203931694f, 0.124042404325672f, + 0.124197547412451f, 0.124352633182726f, 0.124507661627209f, + 0.124662632736620f, 0.124817546501693f, 0.124972402913174f, + 0.125127201961822f, 0.125281943638407f, 0.125436627933713f, + 0.125591254838533f, 0.125745824343677f, 0.125900336439963f, + 0.126054791118225f, 0.126209188369305f, 0.126363528184061f, + 0.126517810553362f, 0.126672035468088f, 0.126826202919134f, + 0.126980312897405f, 0.127134365393820f, 0.127288360399307f, + 0.127442297904811f, 0.127596177901286f, 0.117546189457086f, + 0.117699954408416f, 0.117853661823656f, 0.118007311693808f, + 0.118160904009891f, 0.118314438762931f, 0.118467915943971f, + 0.118621335544062f, 0.118774697554272f, 0.118928001965676f, + 0.119081248769366f, 0.119234437956443f, 0.119387569518022f, + 0.119540643445230f, 0.119693659729205f, 0.119846618361101f, + 0.119999519332079f, 0.120152362633316f, 0.120305148256000f, + 0.120457876191332f, 0.120610546430524f, 0.120763158964802f, + 0.120915713785403f, 0.121068210883577f, 0.121220650250584f, + 0.121373031877701f, 0.121525355756213f, 0.121677621877418f, + 0.111626019310016f, 0.111778169890555f, 0.111930262687758f, + 0.112082297692972f, 0.112234274897559f, 0.112386194292890f, + 0.112538055870350f, 0.112689859621336f, 0.112841605537257f, + 0.112993293609536f, 0.113144923829605f, 0.113296496188912f, + 0.113448010678914f, 0.113599467291082f, 0.113750866016899f, + 0.113902206847861f, 0.114053489775474f, 0.114204714791260f, + 0.114355881886749f, 0.114506991053486f, 0.114658042283029f, + 0.114809035566945f, 0.114959970896817f, 0.115110848264237f, + 0.115261667660812f, 0.115412429078159f, 0.115563132507910f, + 0.115713777941706f, 0.105660554448589f, 0.105811083865453f, + 0.105961555261365f, 0.106111968628015f, 0.106262323957108f, + 0.106412621240361f, 0.106562860469501f, 0.106713041636271f, + 0.106863164732422f, 0.107013229749721f, 0.107163236679946f, + 0.107313185514885f, 0.107463076246342f, 0.107612908866132f, + 0.107762683366080f, 0.107912399738026f, 0.108062057973823f, + 0.108211658065332f, 0.108361200004431f, 0.108510683783007f, + 0.108660109392962f, 0.108809476826207f, 0.108958786074669f, + 0.109108037130284f, 0.109257229985002f, 0.109406364630786f, + 0.109555441059609f, 0.109704459263458f, 0.0996496083117188f, + 0.0997985100416282f, 0.0999473535225969f, 0.100096138746660f, + 0.100244865705867f, 0.100393534392276f, 0.100542144797961f, + 0.100690696915006f, 0.100839190735508f, 0.100987626251577f, + 0.101136003455335f, 0.101284322338914f, 0.101432582894463f, + 0.101580785114137f, 0.101728928990110f, 0.101877014514563f, + 0.102025041679692f, 0.102173010477705f, 0.102320920900821f, + 0.102468772941273f, 0.102616566591305f, 0.102764301843173f, + 0.102911978689148f, 0.103059597121509f, 0.103207157132551f, + 0.103354658714579f, 0.103502101859912f, 0.0934456756382679f, + 0.0935930018872135f, 0.0937402696764917f, 0.0938874789984700f, + 0.0940346298455276f, 0.0941817222100565f, 0.0943287560844607f, + 0.0944757314611567f, 0.0946226483325731f, 0.0947695066911507f, + 0.0949163065293431f, 0.0950630478396154f, 0.0952097306144455f, + 0.0953563548463236f, 0.0955029205277519f, 0.0956494276512452f, + 0.0957958762093304f, 0.0959422661945465f, 0.0960885975994451f, + 0.0962348704165899f, 0.0963810846385570f, 0.0965272402579347f, + 0.0966733372673236f, 0.0968193756593367f, 0.0969653554265990f, + 0.0971112765617480f, 0.0972571390574334f, 0.0974029429063172f, + 0.0873448771784613f, 0.0874905637117771f, 0.0876361915763511f, + 0.0877817607648946f, 0.0879272712701305f, 0.0880727230847950f, + 0.0882181162016359f, 0.0883634506134133f, 0.0885087263129001f, + 0.0886539432928808f, 0.0887991015461526f, 0.0889442010655249f, + 0.0890892418438194f, 0.0892342238738701f, 0.0893791471485232f, + 0.0895240116606371f, 0.0896688174030826f, 0.0898135643687430f, + 0.0899582525505135f, 0.0901028819413016f, 0.0902474525340274f, + 0.0903919643216231f, 0.0905364172970332f, 0.0906808114532143f, + 0.0908251467831358f, 0.0909694232797786f, 0.0911136409361365f, + 0.0912577997452155f, 0.0811980887774211f, 0.0813421298710088f, + 0.0814861120964089f, 0.0816300354466764f, 0.0817738999148785f, + 0.0819177054940949f, 0.0820614521774175f, 0.0822051399579502f, + 0.0823487688288098f, 0.0824923387831246f, 0.0826358498140358f, + 0.0827793019146968f, 0.0829226950782728f, 0.0830660292979419f, + 0.0832093045668942f, 0.0833525208783320f, 0.0834956782254701f, + 0.0836387766015353f, 0.0837818159997669f, 0.0839247964134165f, + 0.0840677178357478f, 0.0842105802600369f, 0.0843533836795723f, + 0.0844961280876545f, 0.0846388134775965f, 0.0847814398427233f, + 0.0849240071763727f, 0.0850665154718944f, 0.0852089647226503f, + 0.0751475439994024f, 0.0752898751407623f, 0.0754321472175162f, + 0.0755743602230756f, 0.0757165141508637f, 0.0758586089943163f, + 0.0760006447468815f, 0.0761426214020196f, 0.0762845389532032f, + 0.0764263973939170f, 0.0765681967176584f, 0.0767099369179366f, + 0.0768516179882736f, 0.0769932399222031f, 0.0771348027132715f, + 0.0772763063550374f, 0.0774177508410716f, 0.0775591361649572f, + 0.0777004623202896f, 0.0778417293006766f, 0.0779829370997380f, + 0.0781240857111062f, 0.0782651751284255f, 0.0784062053453531f, + 0.0785471763555577f, 0.0786880881527207f, 0.0788289407305362f, + 0.0789697340827096f, 0.0791104682029593f, 0.0792511430850161f, + 0.0691879478000098f, 0.0693285041869207f, 0.0694690013169037f, + 0.0696094391837383f, 0.0697498177812166f, 0.0698901371031425f, + 0.0700303971433326f, 0.0701705978956158f, 0.0703107393538329f, + 0.0704508215118373f, 0.0705908443634948f, 0.0707308079026829f, + 0.0708707121232921f, 0.0710105570192245f, 0.0711503425843951f, + 0.0712900688127309f, 0.0714297356981711f, 0.0715693432346672f, + 0.0717088914161831f, 0.0718483802366950f, 0.0719878096901912f, + 0.0721271797706725f, 0.0722664904721519f, 0.0724057417886545f, + 0.0725449337142179f, 0.0726840662428920f, 0.0728231393687387f, + 0.0729621530858327f, 0.0731011073882604f, 0.0732400022701208f, + 0.0733788377255252f, 0.0735176137485970f, 0.0634525194108596f, + 0.0635911765516861f, 0.0637297742426242f, 0.0638683124778465f, + 0.0640067912515382f, 0.0641452105578962f, 0.0642835703911301f, + 0.0644218707454617f, 0.0645601116151250f, 0.0646982929943664f, + 0.0648364148774443f, 0.0649744772586297f, 0.0651124801322058f, + 0.0652504234924680f, 0.0653883073337242f, 0.0655261316502941f, + 0.0656638964365102f, 0.0658016016867171f, 0.0659392473952715f, + 0.0660768335565426f, 0.0662143601649120f, 0.0663518272147731f, + 0.0664892347005321f, 0.0666265826166071f, 0.0667638709574289f, + 0.0669010997174400f, 0.0670382688910958f, 0.0671753784728635f, + 0.0673124284572228f, 0.0674494188386658f, 0.0675863496116965f, + 0.0677232207708315f, 0.0678600323105998f, 0.0679967842255422f, + 0.0579296655875996f, 0.0580662982365629f, 0.0582028712443972f, + 0.0583393846056928f, 0.0584758383150523f, 0.0586122323670904f, + 0.0587485667564341f, 0.0588848414777228f, 0.0590210565256080f, + 0.0591572118947538f, 0.0592933075798361f, 0.0594293435755436f, + 0.0595653198765770f, 0.0597012364776492f, 0.0598370933734855f, + 0.0599728905588236f, 0.0601086280284134f, 0.0602443057770169f, + 0.0603799237994086f, 0.0605154820903751f, 0.0606509806447155f, + 0.0607864194572412f, 0.0609217985227755f, 0.0610571178361543f, + 0.0611923773922258f, 0.0613275771858503f, 0.0614627172119006f, + 0.0615977974652616f, 0.0617328179408305f, 0.0618677786335169f, + 0.0620026795382425f, 0.0621375206499414f, 0.0622723019635602f, + 0.0624070234740572f, 0.0625416851764035f, 0.0626762870655824f, + 0.0526070182139767f, 0.0527415004618193f, 0.0528759228815178f, + 0.0530102854681045f, 0.0531445882166238f, 0.0532788311221330f, + 0.0534130141797010f, 0.0535471373844093f, 0.0536812007313518f, + 0.0538152042156344f, 0.0539491478323753f, 0.0540830315767053f, + 0.0542168554437671f, 0.0543506194287161f, 0.0544843235267195f, + 0.0546179677329570f, 0.0547515520426208f, 0.0548850764509150f, + 0.0550185409530562f, 0.0551519455442734f, 0.0552852902198074f, + 0.0554185749749120f, 0.0555517998048525f, 0.0556849647049072f, + 0.0558180696703662f, 0.0559511146965319f, 0.0560840997787193f, + 0.0562170249122554f, 0.0563498900924798f, 0.0564826953147438f, + 0.0566154405744115f, 0.0567481258668592f, 0.0568807511874753f, + 0.0570133165316607f, 0.0571458218948283f, 0.0572782672724036f, + 0.0574106526598240f, 0.0575429780525398f, 0.0576752434460127f, + 0.0578074488357176f, 0.0579395942171410f, 0.0478678686631696f, + 0.0479998940145396f, 0.0481318593441621f, 0.0482637646475730f, + 0.0483956099203204f, 0.0485273951579650f, 0.0486591203560793f, + 0.0487907855102481f, 0.0489223906160692f, 0.0490539356691518f, + 0.0491854206651178f, 0.0493168455996015f, 0.0494482104682491f, + 0.0495795152667194f, 0.0497107599906833f, 0.0498419446358243f, + 0.0499730691978377f, 0.0501041336724313f, 0.0502351380553253f, + 0.0503660823422521f, 0.0504969665289563f, 0.0506277906111949f, + 0.0507585545847371f, 0.0508892584453644f, 0.0510199021888707f, + 0.0511504858110618f, 0.0512810093077563f, 0.0514114726747848f, + 0.0515418759079902f, 0.0516722190032277f, 0.0518025019563648f, + 0.0519327247632811f, 0.0520628874198688f, 0.0521929899220323f, + 0.0523230322656880f, 0.0524530144467649f, 0.0525829364612041f, + 0.0527127983049592f, 0.0528425999739958f, 0.0529723414642920f, + 0.0531020227718380f, 0.0532316438926364f, 0.0533612048227022f, + 0.0534907055580622f, 0.0536201460947562f, 0.0537495264288357f, + 0.0538788465563647f, 0.0540081064734196f, 0.0541373061760887f, + 0.0542664456604730f, 0.0441917140000732f, 0.0443207330362394f, + 0.0444496918424971f, 0.0445785904149960f, 0.0447074287498985f, + 0.0448362068433790f, 0.0449649246916242f, 0.0450935822908335f, + 0.0452221796372181f, 0.0453507167270016f, 0.0454791935564199f, + 0.0456076101217214f, 0.0457359664191663f, 0.0458642624450276f, + 0.0459924981955904f, 0.0461206736671516f, 0.0462487888560214f, + 0.0463768437585213f, 0.0465048383709856f, 0.0466327726897607f, + 0.0467606467112054f, 0.0468884604316909f, 0.0470162138476001f, + 0.0471439069553288f, 0.0472715397512850f, 0.0473991122318885f, + 0.0475266243935721f, 0.0476540762327803f, 0.0477814677459702f, + 0.0479087989296110f, 0.0480360697801842f, 0.0481632802941839f, + 0.0482904304681158f, 0.0484175202984986f, 0.0485445497818628f, + 0.0486715189147517f, 0.0487984276937201f, 0.0489252761153358f, + 0.0490520641761785f, 0.0491787918728405f, 0.0493054592019259f, + 0.0494320661600513f, 0.0495586127438459f, 0.0496850989499509f, + 0.0498115247750195f, 0.0499378902157179f, 0.0500641952687237f, + 0.0501904399307276f, 0.0503166241984321f, 0.0504427480685521f, + 0.0505688115378147f, 0.0506948146029596f, 0.0508207572607382f, + 0.0509466395079148f, 0.0510724613412656f, 0.0511982227575792f, + 0.0513239237536566f, 0.0514495643263109f, 0.0515751444723674f, + 0.0517006641886638f, 0.0518261234720504f, 0.0519515223193892f, + 0.0520768607275548f, 0.0522021386934342f, 0.0523273562139263f, + 0.0524525132859427f, 0.0525776099064070f, 0.0527026460722553f, + 0.0426238108578230f, 0.0427487261052961f, 0.0428735808890346f, + 0.0429983752060237f, 0.0431231090532607f, 0.0432477824277554f, + 0.0433723953265295f, 0.0434969477466176f, 0.0436214396850659f, + 0.0437458711389333f, 0.0438702421052907f, 0.0439945525812217f, + 0.0441188025638218f, 0.0442429920501990f, 0.0443671210374733f, + 0.0444911895227774f, 0.0446151975032558f, 0.0447391449760657f, + 0.0448630319383766f, 0.0449868583873697f, 0.0451106243202392f, + 0.0452343297341911f, 0.0453579746264439f, 0.0454815589942283f, + 0.0456050828347873f, 0.0457285461453763f, 0.0458519489232627f, + 0.0459752911657265f, 0.0460985728700598f, 0.0462217940335669f, + 0.0463449546535647f, 0.0464680547273820f, 0.0465910942523600f, + 0.0467140732258525f, 0.0468369916452251f, 0.0469598495078560f, + 0.0470826468111357f, 0.0472053835524666f, 0.0473280597292639f, + 0.0474506753389546f, 0.0475732303789784f, 0.0476957248467870f, + 0.0478181587398445f, 0.0479405320556273f, 0.0480628447916239f, + 0.0481850969453354f, 0.0483072885142748f, 0.0484294194959678f, + 0.0485514898879519f, 0.0486734996877775f, 0.0487954488930066f, + 0.0489173375012139f, 0.0490391655099864f, 0.0491609329169232f, + 0.0492826397196357f, 0.0494042859157477f, 0.0495258715028952f, + 0.0496473964787265f, 0.0497688608409022f, 0.0498902645870951f, + 0.0500116077149904f, 0.0501328902222853f, 0.0502541121066900f, + 0.0503752733659261f, 0.0504963739977278f, 0.0506174139998419f, + 0.0507383933700272f, 0.0508593121060547f, 0.0509801702057078f, + 0.0511009676667822f, 0.0512217044870858f, 0.0513423806644390f, + 0.0514629961966743f, 0.0515835510816363f, 0.0517040453171823f, + 0.0518244789011815f, 0.0519448518315156f, 0.0520651641060786f, + 0.0521854157227766f, 0.0523056066795282f, 0.0524257369742641f, + 0.0525458066049274f, 0.0526658155694733f, 0.0527857638658696f, + 0.0529056514920961f, 0.0530254784461450f, 0.0531452447260207f, + 0.0532649503297400f, 0.0533845952553320f, 0.0535041795008378f, + 0.0536237030643112f, 0.0537431659438180f, 0.0538625681374362f, + 0.0539819096432564f, 0.0541011904593813f, 0.0542204105839259f, + 0.0543395700150173f, 0.0544586687507953f, 0.0545777067894117f, + 0.0546966841290304f, 0.0548156007678281f, 0.0549344567039931f, + 0.0550532519357268f, 0.0551719864612422f, 0.0552906602787649f, + 0.0554092733865326f, 0.0555278257827956f, 0.0556463174658160f, + 0.0557647484338686f, 0.0558831186852405f, 0.0560014282182306f, + 0.0561196770311506f, 0.0562378651223242f, 0.0563559924900874f, + 0.0564740591327887f, 0.0565920650487887f, 0.0567100102364602f, + 0.0568278946941885f, 0.0569457184203710f, 0.0570634814134173f, + 0.0571811836717497f, 0.0572988251938025f, 0.0574164059780221f, + 0.0575339260228674f, 0.0576513853268097f, 0.0577687838883325f, + 0.0578861217059312f, 0.0580033987781141f, 0.0581206151034013f, + 0.0582377706803254f, 0.0583548655074314f, 0.0584718995832763f, + 0.0585888729064294f, 0.0587057854754726f, 0.0588226372889998f, + 0.0589394283456172f, 0.0590561586439433f, 0.0591728281826091f, + 0.0592894369602576f, 0.0594059849755440f, 0.0595224722271363f, + 0.0596388987137142f, 0.0597552644339699f, 0.0598715693866082f, + 0.0599878135703456f, 0.0601039969839111f, 0.0602201196260463f, + 0.0603361814955049f, 0.0604521825910525f, 0.0605681229114674f, + 0.0606840024555402f, 0.0607998212220735f, 0.0609155792098825f, + 0.0610312764177944f, 0.0611469128446488f, 0.0612624884892977f, + 0.0613780033506051f, 0.0614934574274476f, 0.0616088507187138f, + 0.0617241832233048f, 0.0618394549401339f, 0.0619546658681265f, + 0.0620698160062207f, 0.0621849053533666f, 0.0622999339085263f, + 0.0624149016706749f, 0.0625298086387993f, 0.0626446548118985f, + 0.0627594401889842f, 0.0628741647690804f, 0.0629888285512230f, + 0.0631034315344604f, 0.0632179737178534f, 0.0633324551004748f, + 0.0634468756814098f, 0.0635612354597560f, 0.0636755344346234f, + 0.0637897726051337f, 0.0639039499704215f, 0.0640180665296333f, + 0.0641321222819281f, 0.0642461172264772f, 0.0643600513624639f, + 0.0644739246890841f, 0.0645877372055457f, 0.0647014889110692f, + 0.0648151798048871f, 0.0649288098862443f, 0.0650423791543980f, + 0.0651558876086177f, 0.0652693352481850f, 0.0653827220723941f, + 0.0654960480805510f, 0.0656093132719747f, 0.0657225176459958f, + 0.0658356612019574f, 0.0659487439392150f, 0.0660617658571363f, + 0.0661747269551014f, 0.0662876272325024f, 0.0664004666887441f, + 0.0665132453232429f, 0.0666259631354284f, 0.0667386201247416f, + 0.0668512162906364f, 0.0669637516325786f, 0.0670762261500467f, + 0.0671886398425310f, 0.0673009927095344f, 0.0674132847505718f, + 0.0675255159651708f, 0.0676376863528710f, 0.0677497959132243f, + 0.0678618446457948f, 0.0679738325501591f, 0.0680857596259061f, + 0.0681976258726366f, 0.0683094312899641f, 0.0684211758775141f, + 0.0685328596349247f, 0.0686444825618459f, 0.0687560446579401f, + 0.0688675459228822f, 0.0689789863563593f, 0.0690903659580705f, + 0.0692016847277276f, 0.0693129426650541f, 0.0694241397697866f, + 0.0695352760416732f, 0.0594425405578624f, 0.0595535551633519f, + 0.0596645089353147f, 0.0597754018735483f, 0.0598862339778625f, + 0.0599970052480793f, 0.0601077156840334f, 0.0602183652855713f, + 0.0603289540525519f, 0.0604394819848466f, 0.0605499490823388f, + 0.0606603553449245f, 0.0607707007725117f, 0.0608809853650206f, + 0.0609912091223840f, 0.0611013720445469f, 0.0612114741314665f, + 0.0613215153831122f, 0.0614314957994659f, 0.0615414153805216f, + 0.0616512741262855f, 0.0617610720367764f, 0.0618708091120252f, + 0.0619804853520752f, 0.0620901007569816f, 0.0621996553268123f, + 0.0623091490616473f, 0.0624185819615789f, 0.0625279540267118f, + 0.0626372652571628f, 0.0627465156530610f, 0.0628557052145480f, + 0.0629648339417774f, 0.0630739018349152f, 0.0631829088941397f, + 0.0632918551196415f, 0.0634007405116234f, 0.0635095650703006f, + 0.0636183287959005f, 0.0637270316886627f, 0.0638356737488393f, + 0.0639442549766944f, 0.0640527753725047f, 0.0641612349365589f, + 0.0642696336691582f, 0.0643779715706160f, 0.0644862486412577f, + 0.0645944648814216f, 0.0647026202914576f, 0.0648107148717285f, + 0.0649187486226089f, 0.0650267215444860f, 0.0651346336377590f, + 0.0652424849028396f, 0.0653502753401518f, 0.0654580049501316f, + 0.0655656737332277f, 0.0656732816899009f, 0.0657808288206239f, + 0.0658883151258823f, 0.0659957406061736f, 0.0661031052620077f, + 0.0662104090939068f, 0.0663176521024054f, 0.0664248342880501f, + 0.0665319556513999f, 0.0666390161930263f, 0.0667460159135127f, + 0.0668529548134549f, 0.0669598328934612f, 0.0670666501541520f, + 0.0671734065961599f, 0.0672801022201299f, 0.0673867370267193f, + 0.0674933110165977f, 0.0675998241904468f, 0.0677062765489609f, + 0.0678126680928461f, 0.0679189988228213f, 0.0680252687396174f, + 0.0681314778439776f, 0.0682376261366575f, 0.0683437136184247f, + 0.0684497402900595f, 0.0685557061523543f, 0.0686616112061134f, + 0.0687674554521540f, 0.0688732388913053f, 0.0689789615244086f, + 0.0690846233523179f, 0.0691902243758991f, 0.0692957645960306f, + 0.0694012440136030f, 0.0695066626295191f, 0.0696120204446942f, + 0.0697173174600557f, 0.0698225536765433f, 0.0699277290951092f, + 0.0700328437167174f, 0.0701378975423447f, 0.0702428905729799f, + 0.0703478228096242f, 0.0704526942532909f, 0.0705575049050059f, + 0.0706622547658070f, 0.0707669438367446f, 0.0708715721188812f, + 0.0607723286906791f, 0.0608768353984505f, 0.0609812813206822f, + 0.0610856664584861f, 0.0611899908129857f, 0.0612942543853178f, + 0.0613984571766304f, 0.0615025991880848f, 0.0616066804208537f, + 0.0617107008761226f, 0.0618146605550893f, 0.0619185594589635f, + 0.0620223975889676f, 0.0621261749463358f, 0.0622298915323152f, + 0.0623335473481647f, 0.0624371423951557f, 0.0625406766745718f, + 0.0626441501877088f, 0.0627475629358750f, 0.0628509149203909f, + 0.0629542061425891f, 0.0630574366038147f, 0.0631606063054251f, + 0.0632637152487898f, 0.0633667634352907f, 0.0634697508663218f, + 0.0635726775432898f, 0.0636755434676132f, 0.0637783486407231f, + 0.0638810930640628f, 0.0639837767390877f, 0.0640863996672659f, + 0.0641889618500774f, 0.0642914632890144f, 0.0643939039855820f, + 0.0644962839412969f, 0.0645986031576883f, 0.0647008616362979f, + 0.0648030593786793f, 0.0649051963863989f, 0.0650072726610348f, + 0.0651092882041778f, 0.0652112430174308f, 0.0653131371024089f, + 0.0654149704607399f, 0.0655167430940633f, 0.0656184550040314f, + 0.0657201061923084f, 0.0658216966605708f, 0.0659232264105077f, + 0.0660246954438204f, 0.0661261037622221f, 0.0662274513674386f, + 0.0663287382612081f, 0.0664299644452808f, 0.0665311299214192f, + 0.0666322346913985f, 0.0667332787570056f, 0.0668342621200399f, + 0.0669351847823132f, 0.0670360467456497f, 0.0671368480118855f, + 0.0672375885828691f, 0.0673382684604614f, 0.0674388876465356f, + 0.0675394461429772f, 0.0676399439516836f, 0.0677403810745650f, + 0.0678407575135437f, 0.0679410732705540f, 0.0680413283475430f, + 0.0681415227464696f, 0.0682416564693053f, 0.0683417295180337f, + 0.0582379309720382f, 0.0583378826785522f, 0.0584377737169835f, + 0.0585376040893650f, 0.0586373737977419f, 0.0587370828441713f, + 0.0588367312307229f, 0.0589363189594787f, 0.0590358460325329f, + 0.0591353124519920f, 0.0592347182199747f, 0.0593340633386119f, + 0.0594333478100472f, 0.0595325716364361f, 0.0596317348199464f, + 0.0597308373627584f, 0.0598298792670645f, 0.0599288605350693f, + 0.0600277811689900f, 0.0601266411710558f, 0.0602254405435084f, + 0.0603241792886015f, 0.0604228574086014f, 0.0605214749057863f, + 0.0606200317824470f, 0.0607185280408865f, 0.0608169636834202f, + 0.0609153387123754f, 0.0610136531300921f, 0.0611119069389223f, + 0.0612101001412306f, 0.0613082327393934f, 0.0614063047357998f, + 0.0615043161328510f, 0.0616022669329606f, 0.0617001571385542f, + 0.0617979867520701f, 0.0618957557759585f, 0.0619934642126822f, + 0.0620911120647159f, 0.0621886993345471f, 0.0622862260246751f, + 0.0623836921376117f, 0.0624810976758809f, 0.0625784426420190f, + 0.0626757270385749f, 0.0627729508681091f, 0.0628701141331951f, + 0.0629672168364182f, 0.0630642589803762f, 0.0631612405676790f, + 0.0632581616009490f, 0.0633550220828208f, 0.0634518220159413f, + 0.0635485614029694f, 0.0636452402465769f, 0.0637418585494472f, + 0.0638384163142765f, 0.0639349135437730f, 0.0640313502406572f, + 0.0641277264076620f, 0.0642240420475324f, 0.0643202971630261f, + 0.0644164917569124f, 0.0645126258319735f, 0.0544048884683912f, + 0.0545009015141968f, 0.0545968540495967f, 0.0546927460774221f, + 0.0547885776005161f, 0.0548843486217346f, 0.0549800591439455f, + 0.0550757091700289f, 0.0551712987028775f, 0.0552668277453958f, + 0.0553622963005011f, 0.0554577043711226f, 0.0555530519602019f, + 0.0556483390706931f, 0.0557435657055621f, 0.0558387318677877f, + 0.0559338375603603f, 0.0560288827862831f, 0.0561238675485714f, + 0.0562187918502528f, 0.0563136556943673f, 0.0564084590839668f, + 0.0565032020221159f, 0.0565978845118913f, 0.0566925065563819f, + 0.0567870681586891f, 0.0568815693219265f, 0.0569760100492198f, + 0.0570703903437071f, 0.0571647102085391f, 0.0572589696468781f, + 0.0573531686618994f, 0.0574473072567899f, 0.0575413854347496f, + 0.0576354031989899f, 0.0577293605527351f, 0.0578232574992215f, + 0.0579170940416977f, 0.0580108701834249f, 0.0581045859276761f, + 0.0581982412777368f, 0.0582918362369048f, 0.0583853708084903f, + 0.0584788449958155f, 0.0585722588022151f, 0.0586656122310360f, + 0.0587589052856374f, 0.0588521379693908f, 0.0589453102856798f, + 0.0590384222379006f, 0.0591314738294614f, 0.0592244650637830f, + 0.0593173959442981f, 0.0594102664744519f, 0.0595030766577018f, + 0.0595958264975176f, 0.0596885159973813f, 0.0597811451607873f, + 0.0598737139912419f, 0.0599662224922641f, 0.0498548597447726f, + 0.0499472475975357f, 0.0500395751314966f, 0.0501318423502235f, + 0.0502240492572963f, 0.0503161958563079f, 0.0504082821508629f, + 0.0505003081445783f, 0.0505922738410838f, 0.0506841792440209f, + 0.0507760243570434f, 0.0508678091838178f, 0.0509595337280224f, + 0.0510511979933482f, 0.0511428019834981f, 0.0512343457021875f, + 0.0513258291531440f, 0.0514172523401077f, 0.0515086152668306f, + 0.0515999179370773f, 0.0516911603546245f, 0.0517823425232611f, + 0.0518734644467888f, 0.0519645261290209f, 0.0520555275737834f, + 0.0521464687849144f, 0.0522373497662645f, 0.0523281705216963f, + 0.0524189310550848f, 0.0525096313703174f, 0.0526002714712936f, + 0.0526908513619254f, 0.0527813710461367f, 0.0528718305278641f, + 0.0529622298110562f, 0.0530525688996742f, 0.0531428477976911f, + 0.0532330665090925f, 0.0533232250378763f, 0.0534133233880527f, + 0.0535033615636440f, 0.0535933395686848f, 0.0536832574072221f, + 0.0537731150833152f, 0.0538629126010356f, 0.0539526499644671f, + 0.0540423271777057f, 0.0541319442448598f, 0.0542215011700501f, + 0.0543109979574096f, 0.0544004346110833f, 0.0544898111352288f, + 0.0545791275340160f, 0.0546683838116266f, 0.0547575799722552f, + 0.0548467160201085f, 0.0549357919594052f, 0.0448209968717641f, + 0.0449099526066536f, 0.0449988482457168f, 0.0450876837932221f, + 0.0451764592534494f, 0.0452651746306915f, 0.0453538299292532f, + 0.0454424251534517f, 0.0455309603076164f, 0.0456194353960890f, + 0.0457078504232236f, 0.0457962053933864f, 0.0458845003109558f, + 0.0459727351803229f, 0.0460609100058907f, 0.0461490247920745f, + 0.0462370795433022f, 0.0463250742640136f, 0.0464130089586610f, + 0.0465008836317089f, 0.0465886982876342f, 0.0466764529309258f, + 0.0467641475660853f, 0.0468517821976262f, 0.0469393568300745f, + 0.0470268714679684f, 0.0471143261158584f, 0.0472017207783073f, + 0.0472890554598900f, 0.0473763301651941f, 0.0474635448988190f, + 0.0475506996653768f, 0.0476377944694915f, 0.0477248293157998f, + 0.0478118042089502f, 0.0478987191536039f, 0.0479855741544340f, + 0.0480723692161264f, 0.0481591043433788f, 0.0482457795409013f, + 0.0483323948134164f, 0.0484189501656588f, 0.0485054456023757f, + 0.0485918811283262f, 0.0486782567482818f, 0.0487645724670264f, + 0.0488508282893562f, 0.0489370242200796f, 0.0490231602640172f, + 0.0491092364260021f, 0.0491952527108794f, 0.0492812091235068f, + 0.0493671056687539f, 0.0494529423515031f, 0.0495387191766484f, + 0.0496244361490969f, 0.0395062823511546f, 0.0395918796329782f, + 0.0396774170768982f, 0.0397628946878707f, 0.0398483124708638f, + 0.0399336704308575f, 0.0400189685728449f, 0.0401042069018306f, + 0.0401893854228319f, 0.0402745041408782f, 0.0403595630610115f, + 0.0404445621882854f, 0.0405295015277667f, 0.0406143810845336f, + 0.0406992008636773f, 0.0407839608703008f, 0.0408686611095195f, + 0.0409533015864613f, 0.0410378823062660f, 0.0411224032740861f, + 0.0412068644950861f, 0.0412912659744427f, 0.0413756077173453f, + 0.0414598897289950f, 0.0415441120146058f, 0.0416282745794035f, + 0.0417123774286264f, 0.0417964205675252f, 0.0418804040013625f, + 0.0419643277354134f, 0.0420481917749655f, 0.0421319961253183f, + 0.0422157407917838f, 0.0422994257796863f, 0.0423830510943621f, + 0.0424666167411601f, 0.0425501227254416f, 0.0426335690525796f, + 0.0427169557279601f, 0.0428002827569807f, 0.0428835501450517f, + 0.0429667578975957f, 0.0430499060200474f, 0.0431329945178538f, + 0.0432160233964742f, 0.0432989926613803f, 0.0433819023180559f, + 0.0434647523719972f, 0.0435475428287129f, 0.0436302736937234f, + 0.0437129449725618f, 0.0437955566707733f, 0.0438781087939159f, + 0.0439606013475590f, 0.0440430343372850f, 0.0441254077686882f, + 0.0442077216473753f, 0.0340861650563528f, 0.0341683598464772f, + 0.0342504951007791f, 0.0343325708249148f, 0.0344145870245521f, + 0.0344965437053716f, 0.0345784408730658f, 0.0346602785333397f, + 0.0347420566919107f, 0.0348237753545080f, 0.0349054345268737f, + 0.0349870342147618f, 0.0350685744239386f, 0.0351500551601828f, + 0.0352314764292854f, 0.0353128382370495f, 0.0353941405892905f, + 0.0354753834918364f, 0.0355565669505271f, 0.0356376909712150f, + 0.0357187555597648f, 0.0357997607220532f, 0.0358807064639694f, + 0.0359615927914151f, 0.0360424197103039f, 0.0361231872265616f, + 0.0362038953461269f, 0.0362845440749502f, 0.0363651334189943f, + 0.0364456633842345f, 0.0365261339766582f, 0.0366065452022649f, + 0.0366868970670670f, 0.0367671895770884f, 0.0368474227383658f, + 0.0369275965569481f, 0.0370077110388962f, 0.0370877661902839f, + 0.0371677620171965f, 0.0372476985257322f, 0.0373275757220012f, + 0.0374073936121259f, 0.0374871522022413f, 0.0375668514984943f, + 0.0376464915070444f, 0.0377260722340632f, 0.0378055936857347f, + 0.0378850558682551f, 0.0379644587878328f, 0.0380438024506888f, + 0.0381230868630559f, 0.0382023120311796f, 0.0382814779613175f, + 0.0383605846597395f, 0.0384396321327278f, 0.0385186203865768f, + 0.0385975494275934f, 0.0386764192620965f, 0.0387552298964174f, + 0.0388339813368998f, 0.0389126735898995f, 0.0287874957391723f, + 0.0288660696363234f, 0.0289445843651332f, 0.0290230399320067f, + 0.0291014363433610f, 0.0291797736056260f, 0.0292580517252432f, + 0.0293362707086670f, 0.0294144305623635f, 0.0294925312928117f, + 0.0295705729065025f, 0.0296485554099390f, 0.0297264788096369f, + 0.0298043431121241f, 0.0298821483239404f, 0.0299598944516384f, + 0.0300375815017827f, 0.0301152094809503f, 0.0301927783957304f, + 0.0302702882527243f, 0.0303477390585462f, 0.0304251308198219f, + 0.0305024635431897f, 0.0305797372353004f, 0.0306569519028170f, + 0.0307341075524143f, 0.0308112041907801f, 0.0308882418246141f, + 0.0309652204606283f, 0.0310421401055471f, 0.0311190007661070f, + 0.0311958024490569f, 0.0312725451611581f, 0.0313492289091839f, + 0.0314258536999200f, 0.0315024195401646f, 0.0315789264367278f, + 0.0316553743964323f, 0.0317317634261129f, 0.0318080935326168f, + 0.0318843647228033f, 0.0319605770035441f, 0.0320367303817234f, + 0.0321128248642373f, 0.0321888604579942f, 0.0322648371699152f, + 0.0323407550069333f, 0.0324166139759939f, 0.0324924140840545f, + 0.0325681553380854f, 0.0326438377450685f, 0.0327194613119984f, + 0.0327950260458821f, 0.0328705319537385f, 0.0329459790425989f, + 0.0330213673195070f, 0.0330966967915189f, 0.0331719674657026f, + 0.0332471793491386f, 0.0333223324489197f, 0.0333974267721510f, + 0.0334724623259497f, 0.0335474391174456f, 0.0336223571537804f, + 0.0336972164421084f, 0.0337720169895960f, 0.0338467588034219f, + 0.0237176309681649f, 0.0237922553362531f, 0.0238668209922893f, + 0.0239413279435017f, 0.0240157761971304f, 0.0240901657604278f, + 0.0241644966406585f, 0.0242387688450997f, 0.0243129823810404f, + 0.0243871372557825f, 0.0244612334766396f, 0.0245352710509378f, + 0.0246092499860156f, 0.0246831702892237f, 0.0247570319679249f, + 0.0248308350294947f, 0.0249045794813202f, 0.0249782653308016f, + 0.0250518925853510f, 0.0251254612523924f, 0.0251989713393627f, + 0.0252724228537110f, 0.0253458158028981f, 0.0254191501943977f, + 0.0254924260356957f, 0.0255656433342901f, 0.0256388020976910f, + 0.0257119023334213f, 0.0257849440490158f, 0.0258579272520216f, + 0.0259308519499983f, 0.0260037181505175f, 0.0260765258611635f, + 0.0261492750895322f, 0.0262219658432325f, 0.0262945981298852f, + 0.0263671719571235f, 0.0264396873325928f, 0.0265121442639506f, + 0.0265845427588672f, 0.0266568828250247f, 0.0267291644701179f, + 0.0268013877018532f, 0.0268735525279502f, 0.0269456589561400f, + 0.0270177069941663f, 0.0270896966497854f, 0.0271616279307651f, + 0.0272335008448862f, 0.0273053153999414f, 0.0273770716037359f, + 0.0274487694640868f, 0.0275204089888242f, 0.0275919901857897f, + 0.0276635130628376f, 0.0277349776278345f, 0.0278063838886591f, + 0.0278777318532024f, 0.0279490215293680f, 0.0280202529250712f, + 0.0280914260482401f, 0.0281625409068147f, 0.0282335975087478f, + 0.0283045958620039f, 0.0283755359745601f, 0.0284464178544057f, + 0.0285172415095424f, 0.0285880069479839f, 0.0286587141777566f, + 0.0287293632068986f, 0.0287999540434609f, 0.0288704866955066f, + 0.0289409611711107f, 0.0290113774783608f, 0.0290817356253569f, + 0.0291520356202110f, 0.0292222774710476f, 0.0292924611860035f, + 0.0293625867732274f, 0.0294326542408808f, 0.0295026635971371f, + 0.0295726148501822f, 0.0194386970856016f, 0.0195085321568307f, + 0.0195783091494799f, 0.0196480280717839f, 0.0197176889319899f, + 0.0197872917383575f, 0.0198568364991585f, 0.0199263232226770f, + 0.0199957519172094f, 0.0200651225910641f, 0.0201344352525623f, + 0.0202036899100369f, 0.0202728865718336f, 0.0203420252463102f, + 0.0204111059418366f, 0.0204801286667952f, 0.0205490934295804f, + 0.0206180002385994f, 0.0206868491022712f, 0.0207556400290274f, + 0.0208243730273115f, 0.0208930481055796f, 0.0209616652723001f, + 0.0210302245359534f, 0.0210987259050326f, 0.0211671693880426f, + 0.0212355549935009f, 0.0213038827299372f, 0.0213721526058936f, + 0.0214403646299242f, 0.0215085188105956f, 0.0215766151564867f, + 0.0216446536761885f, 0.0217126343783045f, 0.0217805572714503f, + 0.0218484223642538f, 0.0219162296653552f, 0.0219839791834073f, + 0.0220516709270747f, 0.0221193049050345f, 0.0221868811259760f, + 0.0222543995986009f, 0.0223218603316232f, 0.0223892633337690f, + 0.0224566086137768f, 0.0225238961803974f, 0.0225911260423939f, + 0.0226582982085414f, 0.0227254126876278f, 0.0227924694884529f, + 0.0228594686198288f, 0.0229264100905801f, 0.0229932939095435f, + 0.0230601200855678f, 0.0231268886275146f, 0.0231935995442573f, + 0.0232602528446819f, 0.0233268485376864f, 0.0233933866321813f, + 0.0234598671370893f, 0.0235262900613454f, 0.0235926554138970f, + 0.0236589632037034f, 0.0237252134397367f, 0.0237914061309807f, + 0.0238575412864321f, 0.0239236189150995f, 0.0239896390260038f, + 0.0240556016281782f, 0.0241215067306683f, 0.0241873543425319f, + 0.0242531444728392f, 0.0243188771306724f, 0.0243845523251262f, + 0.0244501700653075f, 0.0245157303603356f, 0.0245812332193420f, + 0.0246466786514703f, 0.0247120666658767f, 0.0247773972717296f, + 0.0248426704782095f, 0.0249078862945094f, 0.0249730447298344f, + 0.0250381457934021f, 0.0251031894944420f, 0.0251681758421963f, + 0.0252331048459193f, 0.0252979765148776f, 0.0253627908583501f, + 0.0254275478856277f, 0.0254922476060143f, 0.0255568900288252f, + 0.0256214751633887f, 0.0256860030190448f, 0.0257504736051463f, + 0.0258148869310579f, 0.0258792430061567f, 0.0259435418398322f, + 0.0260077834414862f, 0.0260719678205325f, 0.0261360949863972f, + 0.0262001649485192f, 0.0262641777163491f, 0.0263281332993501f, + 0.0263920317069973f, 0.0264558729487787f, 0.0265196570341941f, + 0.0265833839727558f, 0.0266470537739881f, 0.0267106664474281f, + 0.0267742220026244f, 0.0268377204491388f, 0.0269011617965448f, + 0.0269645460544282f, 0.0270278732323873f, 0.0270911433400325f, + 0.0271543563869866f, 0.0272175123828847f, 0.0272806113373739f, + 0.0273436532601140f, 0.0274066381607769f, 0.0274695660490466f, + 0.0275324369346197f, 0.0275952508272048f, 0.0276580077365231f, + 0.0277207076723076f, 0.0277833506443041f, 0.0278459366622703f, + 0.0279084657359765f, 0.0279709378752049f, 0.0280333530897504f, + 0.0280957113894200f, 0.0281580127840327f, 0.0282202572834203f, + 0.0180786339748140f, 0.0181407647132949f, 0.0182028385861189f, + 0.0182648556031667f, 0.0183268157743312f, 0.0183887191095176f, + 0.0184505656186436f, 0.0185123553116388f, 0.0185740881984453f, + 0.0186357642890174f, 0.0186973835933219f, 0.0187589461213378f, + 0.0188204518830560f, 0.0188819008884801f, 0.0189432931476259f, + 0.0190046286705214f, 0.0190659074672070f, 0.0191271295477353f, + 0.0191882949221711f, 0.0192494036005917f, 0.0193104555930864f, + 0.0193714509097570f, 0.0194323895607176f, 0.0194932715560944f, + 0.0195540969060260f, 0.0196148656206632f, 0.0196755777101692f, + 0.0197362331847194f, 0.0197968320545017f, 0.0198573743297157f, + 0.0199178600205741f, 0.0199782891373010f, 0.0200386616901335f, + 0.0200989776893208f, 0.0201592371451241f, 0.0202194400678171f, + 0.0202795864676859f, 0.0203396763550284f, 0.0203997097401555f, + 0.0204596866333899f, 0.0205196070450666f, 0.0205794709855330f, + 0.0206392784651486f, 0.0206990294942856f, 0.0207587240833281f, + 0.0208183622426724f, 0.0208779439827276f, 0.0209374693139144f, + 0.0209969382466666f, 0.0210563507914293f, 0.0211157069586608f, + 0.0211750067588309f, 0.0212342502024225f, 0.0212934372999299f, + 0.0213525680618605f, 0.0214116424987335f, 0.0214706606210802f, + 0.0215296224394448f, 0.0215885279643833f, 0.0216473772064642f, + 0.0217061701762682f, 0.0217649068843883f, 0.0218235873414297f, + 0.0218822115580100f, 0.0219407795447591f, 0.0219992913123190f, + 0.0220577468713443f, 0.0221161462325014f, 0.0221744894064695f, + 0.0222327764039397f, 0.0222910072356156f, 0.0223491819122130f, + 0.0224073004444599f, 0.0224653628430968f, 0.0225233691188765f, + 0.0225813192825635f, 0.0226392133449354f, 0.0226970513167814f, + 0.0227548332089035f, 0.0228125590321158f, 0.0228702287972445f, + 0.0229278425151282f, 0.0229854001966177f, 0.0230429018525766f, + 0.0231003474938801f, 0.0231577371314159f, 0.0232150707760841f, + 0.0232723484387969f, 0.0233295701304792f, 0.0233867358620675f, + 0.0234438456445113f, 0.0235008994887718f, 0.0235578974058227f, + 0.0236148394066502f, 0.0236717255022525f, 0.0237285557036400f, + 0.0237853300218359f, 0.0238420484678749f, 0.0238987110528048f, + 0.0239553177876850f, 0.0240118686835876f, 0.0240683637515969f, + 0.0241248030028093f, 0.0241811864483337f, 0.0242375140992912f, + 0.0242937859668152f, 0.0243500020620512f, 0.0244061623961573f, + 0.0244622669803039f, 0.0245183158256730f, 0.0245743089434599f, + 0.0246302463448713f, 0.0246861280411268f, 0.0247419540434580f, + 0.0247977243631086f, 0.0248534390113350f, 0.0249090979994056f, + 0.0249647013386012f, 0.0250202490402147f, 0.0250757411155516f, + 0.0251311775759295f, 0.0251865584326782f, 0.0252418836971398f, + 0.0252971533806688f, 0.0253523674946320f, 0.0254075260504084f, + 0.0254626290593893f, 0.0255176765329782f, 0.0255726684825909f, + 0.0256276049196556f, 0.0256824858556128f, 0.0257373113019150f, + 0.0257920812700275f, 0.0258467957714273f, 0.0259014548176039f, + 0.0259560584200594f, 0.0260106065903076f, 0.0260650993398752f, + 0.0261195366803005f, 0.0261739186231348f, 0.0262282451799411f, + 0.0262825163622949f, 0.0263367321817843f, 0.0263908926500090f, + 0.0264449977785816f, 0.0264990475791265f, 0.0265530420632809f, + 0.0266069812426939f, 0.0266608651290270f, 0.0267146937339538f, + 0.0267684670691607f, 0.0268221851463456f, 0.0268758479772194f, + 0.0269294555735048f, 0.0269830079469371f, 0.0270365051092639f, + 0.0270899470722445f, 0.0271433338476514f, 0.0271966654472685f, + 0.0272499418828926f, 0.0273031631663327f, 0.0273563293094096f, + 0.0274094403239570f, 0.0274624962218204f, 0.0275154970148579f, + 0.0275684427149397f, 0.0276213333339486f, 0.0276741688837791f, + 0.0277269493763384f, 0.0277796748235459f, 0.0278323452373335f, + 0.0278849606296448f, 0.0279375210124363f, 0.0279900263976765f, + 0.0280424767973460f, 0.0280948722234382f, 0.0281472126879581f, + 0.0281994982029236f, 0.0282517287803646f, 0.0283039044323233f, + 0.0283560251708540f, 0.0284080910080237f, 0.0284601019559115f, + 0.0285120580266086f, 0.0285639592322186f, 0.0286158055848575f, + 0.0286675970966534f, 0.0287193337797467f, 0.0287710156462904f, + 0.0288226427084491f, 0.0288742149784006f, 0.0289257324683340f, + 0.0289771951904517f, 0.0290286031569674f, 0.0290799563801078f, + 0.0291312548721115f, 0.0291824986452294f, 0.0292336877117249f, + 0.0292848220838736f, 0.0293359017739633f, 0.0293869267942941f, + 0.0294378971571783f, 0.0294888128749408f, 0.0295396739599185f, + 0.0295904804244605f, 0.0296412322809285f, 0.0296919295416962f, + 0.0297425722191498f, 0.0297931603256875f, 0.0298436938737202f, + 0.0298941728756706f, 0.0299445973439740f, 0.0299949672910779f, + 0.0300452827294422f, 0.0300955436715389f, 0.0301457501298522f, + 0.0301959021168788f, 0.0302459996451278f, 0.0302960427271200f, + 0.0303460313753892f, 0.0303959656024811f, 0.0304458454209537f, + 0.0304956708433771f, 0.0305454418823342f, 0.0305951585504198f, + 0.0306448208602409f, 0.0306944288244172f, 0.0307439824555803f, + 0.0307934817663740f, 0.0308429267694549f, 0.0308923174774914f, + 0.0309416539031645f, 0.0309909360591671f, 0.0310401639582047f, + 0.0310893376129952f, 0.0311384570362682f, 0.0311875222407662f, + 0.0312365332392437f, 0.0312854900444675f, 0.0313343926692167f, + 0.0313832411262826f, 0.0314320354284691f, 0.0314807755885919f, + 0.0315294616194793f, 0.0315780935339718f, 0.0316266713449223f, + 0.0316751950651957f, 0.0317236647076694f, 0.0317720802852332f, + 0.0318204418107888f, 0.0318687492972504f, 0.0319170027575447f, + 0.0319652022046102f, 0.0320133476513981f, 0.0320614391108717f, + 0.0321094765960065f, 0.0321574601197905f, 0.0322053896952238f, + 0.0322532653353189f, 0.0323010870531006f, 0.0323488548616057f, + 0.0323965687738836f, 0.0324442288029959f, 0.0324918349620166f, + 0.0325393872640315f, 0.0325868857221393f, 0.0326343303494504f, + 0.0326817211590881f, 0.0327290581641876f, 0.0327763413778963f, + 0.0328235708133741f, 0.0328707464837932f, 0.0329178684023379f, + 0.0329649365822048f, 0.0330119510366030f, 0.0330589117787536f, + 0.0331058188218902f, 0.0331526721792586f, 0.0331994718641168f, + 0.0332462178897352f, 0.0332929102693964f, 0.0333395490163956f, + 0.0333861341440396f, 0.0334326656656481f, 0.0334791435945528f, + 0.0335255679440978f, 0.0335719387276394f, 0.0336182559585463f, + 0.0336645196501992f, 0.0337107298159914f, 0.0337568864693283f, + 0.0338029896236278f, 0.0338490392923198f, 0.0338950354888466f, + 0.0339409782266627f, 0.0339868675192350f, 0.0340327033800429f, + 0.0340784858225774f, 0.0341242148603425f, 0.0341698905068542f, + 0.0342155127756406f, 0.0342610816802423f, 0.0343065972342121f, + 0.0343520594511153f, 0.0343974683445292f, 0.0344428239280434f, + 0.0344881262152600f, 0.0345333752197932f, 0.0345785709552694f, + 0.0346237134353274f, 0.0346688026736185f, 0.0347138386838059f, + 0.0347588214795652f, 0.0348037510745845f, 0.0348486274825640f, + 0.0348934507172161f, 0.0349382207922655f, 0.0349829377214494f, + 0.0350276015185171f, 0.0350722121972303f, 0.0351167697713627f, + 0.0351612742547006f, 0.0352057256610425f, 0.0352501240041991f, + 0.0352944692979934f, 0.0353387615562609f, 0.0353830007928488f, + 0.0354271870216173f, 0.0354713202564384f, 0.0355154005111967f, + 0.0355594277997888f, 0.0356034021361236f, 0.0356473235341225f, + 0.0356911920077191f, 0.0357350075708592f, 0.0357787702375008f, + 0.0358224800216146f, 0.0256623260145703f, 0.0257059300755882f, + 0.0257494812960632f, 0.0257929796900145f, 0.0258364252714742f, + 0.0258798180544862f, 0.0259231580531069f, 0.0259664452814049f, + 0.0260096797534614f, 0.0260528614833693f, 0.0260959904852343f, + 0.0261390667731740f, 0.0261820903613187f, 0.0262250612638107f, + 0.0262679794948044f, 0.0263108450684670f, 0.0263536579989775f, + 0.0263964183005274f, 0.0264391259873206f, 0.0264817810735729f, + 0.0265243835735127f, 0.0265669335013807f, 0.0266094308714296f, + 0.0266518756979248f, 0.0266942679951436f, 0.0267366077773757f, + 0.0267788950589231f, 0.0268211298541002f, 0.0268633121772335f, + 0.0269054420426618f, 0.0269475194647364f, 0.0269895444578204f, + 0.0270315170362898f, 0.0270734372145324f, 0.0271153050069485f, + 0.0271571204279507f, 0.0271988834919638f, 0.0272405942134248f, + 0.0272822526067832f, 0.0273238586865006f, 0.0273654124670510f, + 0.0274069139629206f, 0.0274483631886078f, 0.0274897601586236f, + 0.0275311048874910f, 0.0275723973897453f, 0.0276136376799340f, + 0.0276548257726174f, 0.0276959616823674f, 0.0277370454237685f, + 0.0277780770114174f, 0.0278190564599235f, 0.0278599837839078f, + 0.0279008589980039f, 0.0279416821168578f, 0.0279824531551276f, + 0.0280231721274838f, 0.0280638390486091f, 0.0281044539331986f, + 0.0281450167959595f, 0.0281855276516114f, 0.0282259865148862f, + 0.0282663934005280f, 0.0283067483232932f, 0.0283470512979505f, + 0.0283873023392809f, 0.0284275014620776f, 0.0284676486811463f, + 0.0285077440113047f, 0.0285477874673831f, 0.0285877790642237f, + 0.0286277188166811f, 0.0286676067396224f, 0.0287074428479268f, + 0.0287472271564859f, 0.0287869596802033f, 0.0288266404339953f, + 0.0288662694327901f, 0.0289058466915284f, 0.0289453722251632f, + 0.0289848460486596f, 0.0290242681769951f, 0.0290636386251596f, + 0.0291029574081548f, 0.0291422245409955f, 0.0291814400387080f, + 0.0292206039163312f, 0.0292597161889165f, 0.0292987768715272f, + 0.0293377859792390f, 0.0293767435271399f, 0.0294156495303304f, + 0.0294545040039229f, 0.0294933069630424f, 0.0295320584228259f, + 0.0295707583984229f, 0.0296094069049953f, 0.0296480039577166f, + 0.0296865495717735f, 0.0297250437623645f, 0.0297634865447003f, + 0.0298018779340040f, 0.0298402179455111f, 0.0298785065944694f, + 0.0299167438961386f, 0.0299549298657911f, 0.0299930645187115f, + 0.0300311478701964f, 0.0300691799355550f, 0.0301071607301086f, + 0.0301450902691910f, 0.0301829685681479f, 0.0302207956423377f, + 0.0302585715071309f, 0.0302962961779103f, 0.0303339696700707f, + 0.0303715919990197f, 0.0304091631801768f, 0.0304466832289741f, + 0.0304841521608554f, 0.0305215699912776f, 0.0305589367357091f, + 0.0305962524096311f, 0.0306335170285369f, 0.0306707306079320f, + 0.0307078931633345f, 0.0307450047102743f, 0.0307820652642940f, + 0.0308190748409481f, 0.0308560334558039f, 0.0308929411244405f, + 0.0309297978624495f, 0.0309666036854346f, 0.0310033586090122f, + 0.0310400626488105f, 0.0310767158204703f, 0.0311133181396446f, + 0.0311498696219986f, 0.0311863702832097f, 0.0210190092163552f, + 0.0210554082823626f, 0.0210917565743333f, 0.0211280541079942f, + 0.0211643008990843f, 0.0212004969633546f, 0.0212366423165687f, + 0.0212727369745025f, 0.0213087809529439f, 0.0213447742676934f, + 0.0213807169345635f, 0.0214166089693792f, 0.0214524503879776f, + 0.0214882412062082f, 0.0215239814399328f, 0.0215596711050254f, + 0.0215953102173724f, 0.0216308987928723f, 0.0216664368474359f, + 0.0217019243969867f, 0.0217373614574597f, 0.0217727480448030f, + 0.0218080841749764f, 0.0218433698639522f, 0.0218786051277151f, + 0.0219137899822618f, 0.0219489244436015f, 0.0219840085277557f, + 0.0220190422507578f, 0.0220540256286542f, 0.0220889586775028f, + 0.0221238414133744f, 0.0221586738523517f, 0.0221934560105297f, + 0.0222281879040160f, 0.0222628695489301f, 0.0222975009614039f, + 0.0223320821575819f, 0.0223666131536204f, 0.0224010939656882f, + 0.0224355246099665f, 0.0224699051026486f, 0.0225042354599400f, + 0.0225385156980587f, 0.0225727458332349f, 0.0226069258817113f, + 0.0226410558597423f, 0.0226751357835951f, 0.0227091656695491f, + 0.0227431455338958f, 0.0227770753929393f, 0.0228109552629954f, + 0.0228447851603930f, 0.0228785651014724f, 0.0229122951025869f, + 0.0229459751801015f, 0.0229796053503942f, 0.0230131856298546f, + 0.0230467160348848f, 0.0230801965818993f, 0.0231136272873249f, + 0.0231470081676005f, 0.0231803392391772f, 0.0232136205185188f, + 0.0232468520221011f, 0.0232800337664121f, 0.0233131657679523f, + 0.0233462480432343f, 0.0233792806087829f, 0.0234122634811357f, + 0.0234451966768419f, 0.0234780802124634f, 0.0235109141045745f, + 0.0235436983697613f, 0.0235764330246225f, 0.0236091180857689f, + 0.0236417535698240f, 0.0236743394934231f, 0.0237068758732141f, + 0.0237393627258569f, 0.0237718000680239f, 0.0238041879163996f, + 0.0238365262876811f, 0.0238688151985775f, 0.0239010546658102f, + 0.0239332447061131f, 0.0239653853362320f, 0.0239974765729254f, + 0.0240295184329637f, 0.0240615109331300f, 0.0240934540902192f, + 0.0241253479210389f, 0.0241571924424087f, 0.0241889876711606f, + 0.0242207336241391f, 0.0242524303182004f, 0.0242840777702138f, + 0.0243156759970598f, 0.0243472250156324f, 0.0243787248428368f, + 0.0244101754955914f, 0.0244415769908261f, 0.0244729293454835f, + 0.0245042325765185f, 0.0245354867008983f, 0.0245666917356021f, + 0.0245978476976215f, 0.0246289546039604f, 0.0246600124716354f, + 0.0246910213176745f, 0.0247219811591188f, 0.0247528920130213f, + 0.0247837538964472f, 0.0248145668264743f, 0.0248453308201923f, + 0.0248760458947036f, 0.0249067120671227f, 0.0249373293545762f, + 0.0249678977742031f, 0.0249984173431548f, 0.0148250771559823f, + 0.0148554990750867f, 0.0148858721950435f, 0.0149161965330530f, + 0.0149464721063281f, 0.0149766989320937f, 0.0150068770275872f, + 0.0150370064100582f, 0.0150670870967684f, 0.0150971191049919f, + 0.0151271024520153f, 0.0151570371551371f, 0.0151869232316685f, + 0.0152167606989325f, 0.0152465495742647f, 0.0152762898750130f, + 0.0153059816185374f, 0.0153356248222104f, 0.0153652195034166f, + 0.0153947656795529f, 0.0154242633680285f, 0.0154537125862650f, + 0.0154831133516962f, 0.0155124656817680f, 0.0155417695939390f, + 0.0155710251056796f, 0.0156002322344730f, 0.0156293909978140f, + 0.0156585014132103f, 0.0156875634981817f, 0.0157165772702601f, + 0.0157455427469900f, 0.0157744599459278f, 0.0158033288846426f, + 0.0158321495807153f, 0.0158609220517395f, 0.0158896463153209f, + 0.0159183223890776f, 0.0159469502906399f, 0.0159755300376501f, + 0.0160040616477634f, 0.0160325451386467f, 0.0160609805279795f, + 0.0160893678334535f, 0.0161177070727727f, 0.0161459982636532f, + 0.0161742414238237f, 0.0162024365710251f, 0.0162305837230104f, + 0.0162586828975450f, 0.0162867341124064f, 0.0163147373853849f, + 0.0163426927342824f, 0.0163706001769136f, 0.0163984597311053f, + 0.0164262714146964f, 0.0164540352455384f, 0.0164817512414950f, + 0.0165094194204420f, 0.0165370398002675f, 0.0165646123988723f, + 0.0165921372341689f, 0.0166196143240825f, 0.0166470436865503f, + 0.0166744253395220f, 0.0167017593009595f, 0.0167290455888369f, + 0.0167562842211408f, 0.0167834752158696f, 0.0168106185910347f, + 0.0168377143646592f, 0.0168647625547787f, 0.0168917631794411f, + 0.0169187162567065f, 0.0169456218046473f, 0.0169724798413484f, + 0.0169992903849065f, 0.0170260534534310f, 0.0170527690650435f, + 0.0170794372378779f, 0.0171060579900800f, 0.0171326313398085f, + 0.0171591573052340f, 0.0171856359045393f, 0.0172120671559199f, + 0.0172384510775832f, 0.0172647876877488f, 0.0172910770046491f, + 0.0173173190465285f, 0.0173435138316433f, 0.0173696613782627f, + 0.0173957617046678f, 0.0174218148291521f, 0.0174478207700215f, + 0.0174737795455938f, 0.0174996911741995f, 0.0175255556741812f, + 0.0175513730638939f, 0.0175771433617046f, 0.0176028665859928f, + 0.0176285427551502f, 0.0176541718875811f, 0.0176797540017015f, + 0.0177052891159402f, 0.0177307772487379f, 0.0177562184185478f, + 0.0177816126438354f, 0.0178069599430784f, 0.0178322603347666f, + 0.0178575138374025f, 0.0178827204695006f, 0.0179078802495878f, + 0.0179329931962031f, 0.0179580593278980f, 0.0179830786632361f, + 0.0180080512207934f, 0.0180329770191581f, 0.0180578560769309f, + 0.0180826884127244f, 0.0181074740451639f, 0.0181322129928866f, + 0.00795309435192959f, 0.00797773998617998f, 0.00800233899169950f, + 0.00802689138717461f, 0.00805139719130404f, 0.00807585642279884f, + 0.00810026910038247f, 0.00812463524279045f, 0.00814895486877063f, + 0.00817322799708334f, 0.00819745464650101f, 0.00822163483580835f, + 0.00824576858380241f, 0.00826985590929247f, 0.00829389683110024f, + 0.00831789136805938f, 0.00834183953901635f, 0.00836574136282939f, + 0.00838959685836918f, 0.00841340604451885f, 0.00843716894017363f, + 0.00846088556424110f, 0.00848455593564104f, 0.00850818007330578f, + 0.00853175799617945f, 0.00855528972321903f, 0.00857877527339326f, + 0.00860221466568337f, 0.00862560791908315f, 0.00864895505259825f, + 0.00867225608524677f, 0.00869551103605912f, 0.00871871992407797f, + 0.00874188276835819f, 0.00876499958796712f, 0.00878807040198421f, + 0.00881109522950124f, 0.00883407408962228f, 0.00885700700146366f, + 0.00887989398415420f, 0.00890273505683453f, 0.00892553023865803f, + 0.00894827954879010f, 0.00897098300640842f, 0.00899364063070327f, + 0.00901625244087678f, 0.00903881845614354f, 0.00906133869573070f, + 0.00908381317887699f, 0.00910624192483434f, 0.00912862495286607f, + 0.00915096228224840f, 0.00917325393226956f, 0.00919549992223012f, + 0.00921770027144295f, 0.00923985499923324f, 0.00926196412493843f, + 0.00928402766790812f, 0.00930604564750417f, 0.00932801808310113f, + 0.00934994499408540f, 0.00937182639985590f, 0.00939366231982353f, + 0.00941545277341183f, 0.00943719778005636f, 0.00945889735920535f, + 0.00948055153031865f, 0.00950216031286910f, 0.00952372372634122f, + 0.00954524179023242f, 0.00956671452405170f, 0.00958814194732097f, + 0.00960952407957394f, 0.00963086094035709f, 0.00965215254922858f, + 0.00967339892575941f, 0.00969460008953246f, 0.00971575606014319f, + 0.00973686685719929f, 0.00975793250032039f, 0.00977895300913892f, + 0.00979992840329916f, 0.00982085870245789f, 0.00984174392628431f, + 0.00986258409445931f, 0.00988337922667690f, 0.00990412934264276f, + 0.00992483446207504f, 0.00994549460470412f, 0.00996610979027271f, + 0.00998668003853587f, 0.0100072053692609f, 0.0100276858022273f, + 0.0100481213572269f, 0.0100685120540638f, 0.0100888579125544f, + 0.0101091589525275f, 0.0101294151938238f, 0.0101496266562968f, + 0.0101697933598117f, 0.0101899153242468f, 0.0102099925694918f, + 0.0102300251154490f, 0.0102500129820334f, 0.0102699561891717f, + 0.0102898547568033f, 0.0103097087048794f, 0.0103295180533639f, + 0.0103492828222330f, 0.0103690030314749f, 0.0103886787010901f, + 0.0104083098510918f, 0.0104278965015051f, 0.0104474386723672f, + 0.0104669363837280f, 0.0104863896556498f, 0.0105057985082065f, + 0.0105251629614849f, 0.0105444830355838f, 0.0105637587506144f, + 0.0105829901267000f, 0.0106021771839766f, 0.0106213199425919f, + 0.0106404184227065f, 0.0106594726444926f, 0.0106784826281353f, + 0.0106974483938314f, 0.0107163699617908f, 0.0107352473522349f, + 0.0107540805853976f, 0.0107728696815252f, 0.0107916146608764f, + 0.0108103155437217f, 0.0108289723503445f, 0.0108475851010400f, + 0.0108661538161160f, 0.0108846785158922f, 0.0109031592207011f, + 0.0109215959508869f, 0.0109399887268067f, 0.0109583375688294f, + 0.0109766424973363f, 0.0109949035327211f, 0.0110131206953896f, + 0.0110312940057603f, 0.0110494234842632f, 0.000863698228728893f, + 0.000881740104837381f, 0.000899738210443285f, 0.000917692566026501f, + 0.000935603192078749f, 0.000953470109104099f, 0.000971293337619111f, + 0.000989072898152504f, 0.00100680881124518f, 0.00102450109745059f, + 0.00104214977733416f, 0.00105975487147383f, 0.00107731640045972f, + 0.00109483438489416f, 0.00111230884539187f, 0.00112973980257991f, + 0.00114712727709757f, 0.00116447128959615f, 0.00118177186073964f, + 0.00119902901120417f, 0.00121624276167817f, 0.00123341313286207f, + 0.00125054014546896f, 0.00126762382022416f, 0.00128466417786494f, + 0.00130166123914147f, 0.00131861502481540f, 0.00133552555566130f, + 0.00135239285246572f, 0.00136921693602776f, 0.00138599782715843f, + 0.00140273554668116f, 0.00141943011543194f, 0.00143608155425867f, + 0.00145268988402170f, 0.00146925512559357f, 0.00148577729985921f, + 0.00150225642771590f, 0.00151869253007300f, 0.00153508562785212f, + 0.00155143574198749f, 0.00156774289342521f, 0.00158400710312404f, + 0.00160022839205470f, 0.00161640678120040f, 0.00163254229155643f, + 0.00164863494413064f, 0.00166468475994314f, 0.00168069176002578f, + 0.00169665596542351f, 0.00171257739719283f, 0.00172845607640326f, + 0.00174429202413584f, 0.00176008526148430f, 0.00177583580955454f, + 0.00179154368946505f, 0.00180720892234626f, 0.00182283152934076f, + 0.00183841153160380f, 0.00185394895030278f, 0.00186944380661724f, + 0.00188489612173903f, 0.00190030591687257f, 0.00191567321323430f, + 0.00193099803205288f, 0.00194628039456934f, 0.00196152032203714f, + 0.00197671783572181f, 0.00199187295690129f, 0.00200698570686583f, + 0.00202205610691772f, 0.00203708417837167f, 0.00205206994255494f, + 0.00206701342080665f, 0.00208191463447846f, 0.00209677360493415f, + 0.00211159035354996f, 0.00212636490171420f, 0.00214109727082776f, + 0.00215578748230350f, 0.00217043555756677f, 0.00218504151805510f, + 0.00219960538521813f, 0.00221412718051822f, 0.00222860692542973f, + 0.00224304464143935f, 0.00225744035004599f, 0.00227179407276090f, + 0.00228610583110747f, 0.00230037564662183f, 0.00231460354085172f, + 0.00232878953535784f, 0.00234293365171256f, 0.00235703591150094f, + 0.00237109633632010f, 0.00238511494777985f, 0.00239909176750147f, + 0.00241302681711936f, 0.00242692011827975f, 0.00244077169264129f, + 0.00245458156187497f, 0.00246834974766388f, 0.00248207627170341f, + 0.00249576115570141f, 0.00250940442137795f, 0.00252300609046513f, + 0.00253656618470771f, 0.00255008472586260f, 0.00256356173569897f, + 0.00257699723599797f, 0.00259039124855365f, 0.00260374379517186f, + 0.00261705489767092f, 0.00263032457788148f, 0.00264355285764614f, + 0.00265673975882014f, 0.00266988530327097f, 0.00268298951287835f, + 0.00269605240953399f, 0.00270907401514245f, 0.00272205435162004f, + 0.00273499344089564f, 0.00274789130491049f, 0.00276074796561768f, + 0.00277356344498309f, 0.00278633776498463f, 0.00279907094761245f, + 0.00281176301486905f, 0.00282441398876931f, 0.00283702389134027f, + 0.00284959274462127f, 0.00286212057066382f, 0.00287460739153189f, + 0.00288705322930180f, 0.00289945810606193f, 0.00291182204391308f, + 0.00292414506496813f, 0.00293642719135262f, 0.00294866844520389f, + 0.00296086884867222f, 0.00297302842391931f, 0.00298514719311982f, + 0.00299722517846057f, 0.00300926240214039f, 0.00302125888637081f, + 0.00303321465337506f, 0.00304512972538920f, 0.00305700412466148f, + 0.00306883787345200f, 0.00308063099403366f, 0.00309238350869140f, + 0.00310409543972254f, 0.00311576680943643f, 0.00312739764015507f, + 0.00313898795421264f, 0.00315053777395527f, 0.00316204712174176f, + 0.00317351601994309f, 0.00318494449094245f, 0.00319633255713528f, + 0.00320768024092954f, 0.00321898756474520f, 0.00323025455101456f, + 0.00324148122218240f, 0.00325266760070547f, 0.00326381370905315f, + 0.00327491956970674f, 0.00328598520516016f, 0.00329701063791929f, + 0.00330799589050260f, 0.00331894098544075f, 0.00332984594527644f, + 0.00334071079256490f, 0.00335153554987361f, 0.00336232023978234f, + 0.00337306488488309f, 0.00338376950778002f, 0.00339443413108997f, + 0.00340505877744157f, 0.00341564346947612f, 0.00342618822984689f, + 0.00343669308121961f, 0.00344715804627233f, 0.00345758314769543f, + 0.00346796840819122f, 0.00347831385047465f, 0.00348861949727269f, + 0.00349888537132503f, 0.00350911149538313f, 0.00351929789221103f, + 0.00352944458458487f, 0.00353955159529323f, 0.00354961894713696f, + 0.00355964666292907f, 0.00356963476549479f, 0.00357958327767202f, + 0.00358949222231061f, 0.00359936162227278f, 0.00360919150043287f, + 0.00361898187967777f, 0.00362873278290654f, 0.00363844423303059f, + 0.00364811625297337f, 0.00365774886567080f, 0.00366734209407113f, + 0.00367689596113485f, 0.00368641048983448f, 0.00369588570315532f, + 0.00370532162409457f, 0.00371471827566178f, 0.00372407568087890f, + 0.00373339386277999f, 0.00374267284441140f, 0.00375191264883207f, + 0.00376111329911286f, 0.00377027481833711f, 0.00377939722960025f, + 0.00378848055601022f, 0.00379752482068707f, 0.00380653004676323f, + 0.00381549625738342f, 0.00382442347570458f, 0.00383331172489607f, + 0.00384216102813920f, 0.00385097140862786f, 0.00385974288956820f, + 0.00386847549417849f, 0.00387716924568945f, 0.00388582416734407f, + 0.00389444028239744f, 0.00390301761411704f, 0.00391155618578290f, + 0.00392005602068687f, 0.00392851714213324f, 0.00393693957343871f}; + +// Curve which is scaled by |kCurveCorrectionMultipliers| and added to the curve +// generated by the |kLowReverberationCorrectionCurve| polynomial, for +// reverberation times which result in a feedback factor index less than +// |kCurveChangeoverIndex|. +static const float kLowCorrectionCurve[kCorrectionCurveLength] = { + 0.0355835132858260f, 0.0361471820669677f, 0.0367104001412021f, + 0.0372731677107544f, 0.0378354849778072f, 0.0383973521445001f, + 0.0389587694129315f, 0.0395197369851564f, 0.0400802550631881f, + 0.0406403238489973f, 0.0411999435445122f, 0.0417591143516188f, + 0.0423178364721608f, 0.0428761101079397f, 0.0434339354607135f, + 0.0439913127321995f, 0.0445482421240714f, 0.0451047238379613f, + 0.0456607580754581f, 0.0462163450381097f, 0.0467714849274197f, + 0.0473261779448504f, 0.0478804242918223f, 0.0484342241697123f, + 0.0489875777798562f, 0.0495404853235463f, 0.0500929470020328f, + 0.0506449630165243f, 0.0511965335681860f, 0.0517476588581411f, + 0.0522983390874708f, 0.0528485744572133f, 0.0533983651683647f, + 0.0539477114218787f, 0.0544966134186670f, 0.0550450713595981f, + 0.0555930854454990f, 0.0561406558771536f, 0.0566877828553037f, + 0.0572344665806487f, 0.0577807072538459f, 0.0583265050755101f, + 0.0588718602462131f, 0.0594167729664848f, 0.0599612434368130f, + 0.0605052718576424f, 0.0610488584293762f, 0.0615920033523746f, + 0.0621347068269552f, 0.0626769690533939f, 0.0632187902319240f, + 0.0637601705627360f, 0.0643011102459783f, 0.0648416094817569f, + 0.0653816684701354f, 0.0659212874111350f, 0.0664604665047346f, + 0.0669992059508706f, 0.0675375059494370f, 0.0680753667002852f, + 0.0686127884032250f, 0.0691497712580228f, 0.0696863154644029f, + 0.0702224212220476f, 0.0707580887305970f, 0.0712933181896472f, + 0.0718281097987538f, 0.0723624637574293f, 0.0728963802651431f, + 0.0734298595213236f, 0.0739629017253557f, 0.0744955070765820f, + 0.0750276757743035f, 0.0755594080177776f, 0.0760907040062205f, + 0.0766215639388050f, 0.0771519880146623f, 0.0776819764328806f, + 0.0782115293925059f, 0.0787406470925415f, 0.0792693297319489f, + 0.0797975775096472f, 0.0803253906245122f, 0.0808527692753781f, + 0.0813797136610364f, 0.0819062239802365f, 0.0824323004316849f, + 0.0829579432140462f, 0.0834831525259416f, 0.0840079285659512f, + 0.0845322715326120f, 0.0850561816244186f, 0.0855796590398233f, + 0.0861027039772357f, 0.0866253166350237f, 0.0871474972115118f, + 0.0876692459049832f, 0.0881905629136774f, 0.0887114484357927f, + 0.0892319026694841f, 0.0897519258128650f, 0.0902715180640052f, + 0.0907906796209331f, 0.0913094106816348f, 0.0918277114440527f, + 0.0923455821060883f, 0.0928630228655998f, 0.0933800339204031f, + 0.0938966154682718f, 0.0944127677069371f, 0.0949284908340879f, + 0.0954437850473701f, 0.0959586505443875f, 0.0964730875227018f, + 0.0969870961798317f, 0.0975006767132541f, 0.0980138293204033f, + 0.0985265541986706f, 0.0990388515454054f, 0.0995507215579148f, + 0.100062164433463f, 0.100573180369272f, 0.101083769562521f, + 0.101593932210348f, 0.102103668509848f, 0.102612978658072f, + 0.103121862852030f, 0.103630321288690f, 0.104138354164977f, + 0.104645961677773f, 0.105153144023918f, 0.105659901400211f, + 0.106166234003407f, 0.106672142030217f, 0.107177625677312f, + 0.107682685141321f, 0.108187320618829f, 0.108691532306379f, + 0.109195320400472f, 0.109698685097565f, 0.110201626594074f, + 0.110704145086373f, 0.111206240770793f, 0.111707913843621f, + 0.112209164501105f, 0.112709992939447f, 0.113210399354808f, + 0.113710383943307f, 0.114209946901020f, 0.114709088423982f, + 0.115207808708182f, 0.115706107949571f, 0.116203986344054f, + 0.116701444087495f, 0.117198481375716f, 0.117695098404497f, + 0.118191295369573f, 0.118687072466638f, 0.119182429891344f, + 0.119677367839301f, 0.120171886506076f, 0.120665986087191f, + 0.121159666778131f, 0.121652928774333f, 0.122145772271196f, + 0.122638197464072f, 0.123130204548276f, 0.123621793719075f, + 0.124112965171697f, 0.124603719101328f, 0.125094055703108f, + 0.125583975172139f, 0.126073477703476f, 0.126562563492136f, + 0.127051232733090f, 0.127539485621269f, 0.128027322351559f, + 0.128514743118806f, 0.129001748117814f, 0.129488337543340f, + 0.129974511590103f, 0.130460270452779f, 0.130945614326000f, + 0.131430543404356f, 0.131915057882395f, 0.132399157954622f, + 0.132882843815500f, 0.133366115659450f, 0.133848973680849f, + 0.134331418074033f, 0.134813449033295f, 0.130933338745307f, + 0.131414543419434f, 0.131895335242262f, 0.132375714407915f, + 0.132855681110473f, 0.133335235543976f, 0.133814377902417f, + 0.134293108379751f, 0.134771427169888f, 0.135249334466697f, + 0.135726830464003f, 0.136203915355590f, 0.136680589335197f, + 0.137156852596525f, 0.137632705333228f, 0.138108147738920f, + 0.138583180007172f, 0.139057802331512f, 0.139532014905426f, + 0.140005817922358f, 0.140479211575709f, 0.140952196058836f, + 0.141424771565058f, 0.141896938287645f, 0.142368696419831f, + 0.142840046154803f, 0.143310987685707f, 0.143781521205648f, + 0.144251646907686f, 0.144721364984839f, 0.145190675630085f, + 0.145659579036357f, 0.146128075396545f, 0.146596164903499f, + 0.147063847750025f, 0.147531124128886f, 0.147997994232804f, + 0.148464458254458f, 0.148930516386483f, 0.149396168821473f, + 0.149861415751981f, 0.150326257370514f, 0.150790693869538f, + 0.151254725441479f, 0.151718352278716f, 0.152181574573589f, + 0.152644392518394f, 0.153106806305386f, 0.153568816126774f, + 0.154030422174728f, 0.154491624641374f, 0.154952423718796f, + 0.155412819599036f, 0.155872812474093f, 0.156332402535922f, + 0.156791589976438f, 0.157250374987511f, 0.157708757760971f, + 0.158166738488604f, 0.158624317362153f, 0.159081494573321f, + 0.159538270313766f, 0.159994644775104f, 0.160450618148909f, + 0.160906190626713f, 0.161361362400004f, 0.161816133660228f, + 0.162270504598790f, 0.162724475407050f, 0.163178046276328f, + 0.163631217397900f, 0.164083988962999f, 0.164536361162817f, + 0.164988334188502f, 0.165439908231161f, 0.165891083481858f, + 0.166341860131613f, 0.166792238371406f, 0.167242218392172f, + 0.167691800384806f, 0.168140984540158f, 0.168589771049037f, + 0.169038160102209f, 0.169486151890398f, 0.165572018596708f, + 0.166019216426932f, 0.166466017564089f, 0.166912422198731f, + 0.167358430521371f, 0.167804042722478f, 0.168249258992475f, + 0.168694079521749f, 0.169138504500638f, 0.169582534119443f, + 0.170026168568418f, 0.170469408037777f, 0.170912252717692f, + 0.171354702798289f, 0.171796758469656f, 0.172238419921836f, + 0.172679687344829f, 0.173120560928594f, 0.173561040863046f, + 0.174001127338058f, 0.174440820543462f, 0.174880120669046f, + 0.175319027904554f, 0.175757542439691f, 0.176195664464117f, + 0.176633394167450f, 0.177070731739265f, 0.177507677369096f, + 0.177944231246432f, 0.178380393560723f, 0.178816164501372f, + 0.179251544257744f, 0.179686533019159f, 0.180121130974894f, + 0.180555338314184f, 0.180989155226223f, 0.181422581900160f, + 0.181855618525104f, 0.182288265290119f, 0.182720522384228f, + 0.183152389996411f, 0.183583868315606f, 0.184014957530707f, + 0.184445657830568f, 0.184875969403997f, 0.185305892439763f, + 0.185735427126589f, 0.186164573653160f, 0.186593332208113f, + 0.187021702980047f, 0.187449686157516f, 0.187877281929033f, + 0.188304490483066f, 0.188731312008043f, 0.189157746692349f, + 0.189583794724325f, 0.190009456292270f, 0.190434731584443f, + 0.190859620789056f, 0.191284124094282f, 0.191708241688250f, + 0.192131973759046f, 0.192555320494716f, 0.192978282083259f, + 0.193400858712636f, 0.193823050570763f, 0.194244857845513f, + 0.190304552717141f, 0.190725591388591f, 0.191146246040030f, + 0.191566516859164f, 0.191986404033653f, 0.192405907751115f, + 0.192825028199128f, 0.193243765565223f, 0.193662120036893f, + 0.194080091801586f, 0.194497681046707f, 0.194914887959619f, + 0.195331712727645f, 0.195748155538060f, 0.196164216578102f, + 0.196579896034964f, 0.196995194095795f, 0.197410110947704f, + 0.197824646777756f, 0.198238801772974f, 0.198652576120338f, + 0.199065970006786f, 0.199478983619212f, 0.199891617144471f, + 0.200303870769370f, 0.200715744680679f, 0.201127239065121f, + 0.201538354109379f, 0.201949090000093f, 0.202359446923861f, + 0.202769425067235f, 0.203179024616730f, 0.203588245758813f, + 0.203997088679912f, 0.204405553566412f, 0.204813640604653f, + 0.205221349980936f, 0.205628681881516f, 0.206035636492609f, + 0.206442214000385f, 0.206848414590972f, 0.207254238450459f, + 0.207659685764888f, 0.208064756720260f, 0.208469451502535f, + 0.208873770297627f, 0.209277713291411f, 0.209681280669718f, + 0.210084472618335f, 0.210487289323009f, 0.210889730969442f, + 0.211291797743296f, 0.211693489830188f, 0.212094807415693f, + 0.212495750685345f, 0.212896319824633f, 0.213296515019006f, + 0.213696336453868f, 0.214095784314582f, 0.214494858786468f, + 0.210531832047226f, 0.210930160297245f, 0.211328115714141f, + 0.211725698483062f, 0.212122908789116f, 0.212519746817367f, + 0.212916212752837f, 0.213312306780505f, 0.213708029085308f, + 0.214103379852140f, 0.214498359265853f, 0.214892967511255f, + 0.215287204773112f, 0.215681071236149f, 0.216074567085046f, + 0.216467692504443f, 0.216860447678934f, 0.217252832793074f, + 0.217644848031373f, 0.218036493578299f, 0.218427769618278f, + 0.218818676335693f, 0.219209213914884f, 0.219599382540149f, + 0.219989182395744f, 0.220378613665880f, 0.220767676534728f, + 0.221156371186415f, 0.221544697805026f, 0.221932656574604f, + 0.222320247679147f, 0.222707471302613f, 0.223094327628916f, + 0.223480816841929f, 0.223866939125480f, 0.224252694663356f, + 0.224638083639300f, 0.225023106237015f, 0.225407762640159f, + 0.225792053032349f, 0.226175977597157f, 0.226559536518116f, + 0.226942729978713f, 0.227325558162394f, 0.227708021252562f, + 0.228090119432578f, 0.228471852885760f, 0.228853221795383f, + 0.229234226344680f, 0.229614866716842f, 0.229995143095015f, + 0.230375055662304f, 0.230754604601773f, 0.231133790096440f, + 0.231512612329282f, 0.231891071483235f, 0.227907439733612f, + 0.228285173278418f, 0.228662544292882f, 0.229039552959768f, + 0.229416199461797f, 0.229792483981648f, 0.230168406701958f, + 0.230543967805320f, 0.230919167474285f, 0.231294005891360f, + 0.231668483239013f, 0.232042599699666f, 0.232416355455699f, + 0.232789750689451f, 0.233162785583216f, 0.233535460319247f, + 0.233907775079755f, 0.234279730046907f, 0.234651325402827f, + 0.235022561329597f, 0.235393438009258f, 0.235763955623805f, + 0.236134114355194f, 0.236503914385335f, 0.236873355896098f, + 0.237242439069310f, 0.237611164086754f, 0.237979531130171f, + 0.238347540381260f, 0.238715192021677f, 0.239082486233034f, + 0.239449423196903f, 0.239816003094811f, 0.240182226108245f, + 0.240548092418646f, 0.240913602207415f, 0.241278755655909f, + 0.241643552945443f, 0.242007994257290f, 0.242372079772679f, + 0.242735809672797f, 0.243099184138788f, 0.243462203351754f, + 0.243824867492754f, 0.244187176742804f, 0.244549131282879f, + 0.244910731293909f, 0.245271976956783f, 0.245632868452347f, + 0.245993405961404f, 0.246353589664714f, 0.246713419742997f, + 0.247072896376926f, 0.247432019747135f, 0.243429062026635f, + 0.243787479411131f, 0.244145544073548f, 0.244503256194350f, + 0.244860615953955f, 0.245217623532740f, 0.245574279111040f, + 0.245930582869146f, 0.246286534987307f, 0.246642135645729f, + 0.246997385024576f, 0.247352283303969f, 0.247706830663986f, + 0.248061027284663f, 0.248414873345993f, 0.248768369027926f, + 0.249121514510370f, 0.249474309973191f, 0.249826755596210f, + 0.250178851559207f, 0.250530598041920f, 0.250881995224043f, + 0.251233043285228f, 0.251583742405084f, 0.251934092763177f, + 0.252284094539032f, 0.252633747912130f, 0.252983053061909f, + 0.253332010167765f, 0.253680619409052f, 0.254028880965080f, + 0.254376795015117f, 0.254724361738388f, 0.255071581314077f, + 0.255418453921322f, 0.255764979739221f, 0.256111158946830f, + 0.256456991723160f, 0.256802478247180f, 0.257147618697817f, + 0.257492413253955f, 0.257836862094436f, 0.258180965398058f, + 0.258524723343577f, 0.258868136109707f, 0.259211203875118f, + 0.259553926818439f, 0.259896305118254f, 0.260238338953107f, + 0.260580028501497f, 0.260921373941883f, 0.261262375452678f, + 0.261603033212256f, 0.261943347398944f, 0.262283318191031f, + 0.258261217759182f, 0.258600502296753f, 0.258939443974328f, + 0.259278042970020f, 0.259616299461904f, 0.259954213628010f, + 0.260291785646327f, 0.260629015694800f, 0.260965903951331f, + 0.261302450593780f, 0.261638655799966f, 0.261974519747662f, + 0.262310042614600f, 0.262645224578471f, 0.262980065816920f, + 0.263314566507552f, 0.263648726827928f, 0.263982546955566f, + 0.264316027067943f, 0.264649167342491f, 0.264981967956602f, + 0.265314429087624f, 0.265646550912860f, 0.265978333609575f, + 0.266309777354988f, 0.266640882326276f, 0.266971648700574f, + 0.267302076654974f, 0.267632166366524f, 0.267961918012231f, + 0.268291331769060f, 0.268620407813930f, 0.268949146323722f, + 0.269277547475269f, 0.269605611445366f, 0.269933338410763f, + 0.270260728548167f, 0.270587782034243f, 0.270914499045615f, + 0.271240879758860f, 0.271566924350517f, 0.271892632997080f, + 0.272218005874999f, 0.272543043160685f, 0.272867745030503f, + 0.273192111660776f, 0.273516143227786f, 0.273839839907771f, + 0.274163201876925f, 0.274486229311402f, 0.274808922387312f, + 0.275131281280722f, 0.275453306167657f, 0.275774997224098f, + 0.276096354625986f, 0.276417378549215f, 0.272376341162064f, + 0.272696698655497f, 0.273016723197707f, 0.273336414964418f, + 0.273655774131314f, 0.273974800874035f, 0.274293495368180f, + 0.274611857789301f, 0.274929888312913f, 0.275247587114485f, + 0.275564954369443f, 0.275881990253173f, 0.276198694941014f, + 0.276515068608266f, 0.276831111430185f, 0.277146823581984f, + 0.277462205238834f, 0.277777256575864f, 0.278091977768158f, + 0.278406368990758f, 0.278720430418665f, 0.279034162226836f, + 0.279347564590185f, 0.279660637683583f, 0.279973381681861f, + 0.280285796759803f, 0.280597883092155f, 0.280909640853616f, + 0.281221070218844f, 0.281532171362457f, 0.281842944459025f, + 0.282153389683078f, 0.282463507209106f, 0.282773297211551f, + 0.283082759864817f, 0.283391895343261f, 0.283700703821200f, + 0.284009185472909f, 0.284317340472619f, 0.284625168994516f, + 0.284932671212748f, 0.285239847301418f, 0.285546697434584f, + 0.285853221786265f, 0.286159420530436f, 0.286465293841028f, + 0.286770841891931f, 0.287076064856991f, 0.287380962910013f, + 0.287685536224756f, 0.287989784974941f, 0.288293709334242f, + 0.288597309476293f, 0.288900585574683f, 0.289203537802961f, + 0.289506166334631f, 0.289808471343155f, 0.290110453001953f, + 0.290412111484400f, 0.290713446963832f, 0.286652731605961f, + 0.286953421599191f, 0.287253789109152f, 0.287553834309004f, + 0.287853557371869f, 0.288152958470824f, 0.288452037778905f, + 0.288750795469103f, 0.289049231714367f, 0.289347346687604f, + 0.289645140561678f, 0.289942613509411f, 0.290239765703580f, + 0.290536597316922f, 0.290833108522128f, 0.291129299491851f, + 0.291425170398696f, 0.291720721415230f, 0.292015952713973f, + 0.292310864467405f, 0.292605456847963f, 0.292899730028041f, + 0.293193684179989f, 0.293487319476116f, 0.293780636088688f, + 0.294073634189927f, 0.294366313952014f, 0.294658675547085f, + 0.294950719147237f, 0.295242444924520f, 0.295533853050944f, + 0.295824943698475f, 0.296115717039037f, 0.296406173244510f, + 0.296696312486734f, 0.296986134937503f, 0.297275640768571f, + 0.297564830151647f, 0.297853703258398f, 0.298142260260449f, + 0.298430501329382f, 0.298718426636735f, 0.299006036354006f, + 0.299293330652647f, 0.299580309704069f, 0.299866973679640f, + 0.300153322750686f, 0.300439357088489f, 0.300725076864288f, + 0.301010482249282f, 0.301295573414623f, 0.301580350531424f, + 0.301864813770753f, 0.302148963303636f, 0.302432799301057f, + 0.302716321933956f, 0.302999531373231f, 0.303282427789736f, + 0.303565011354285f, 0.303847282237646f, 0.304129240610547f, + 0.304410886643670f, 0.304692220507658f, 0.304973242373109f, + 0.305253952410579f, 0.305534350790580f, 0.305814437683583f, + 0.301732485252438f, 0.302011949682684f, 0.302291103137086f, + 0.302569945785942f, 0.302848477799510f, 0.303126699348003f, + 0.303404610601592f, 0.303682211730404f, 0.303959502904526f, + 0.304236484294000f, 0.304513156068825f, 0.304789518398959f, + 0.305065571454316f, 0.305341315404768f, 0.305616750420142f, + 0.305891876670227f, 0.306166694324763f, 0.306441203553453f, + 0.306715404525953f, 0.306989297411878f, 0.307262882380802f, + 0.307536159602252f, 0.307809129245716f, 0.308081791480638f, + 0.308354146476418f, 0.308626194402416f, 0.308897935427946f, + 0.309169369722281f, 0.309440497454651f, 0.309711318794243f, + 0.309981833910203f, 0.310252042971631f, 0.310521946147587f, + 0.310791543607086f, 0.311060835519102f, 0.311329822052565f, + 0.311598503376364f, 0.311866879659343f, 0.312134951070305f, + 0.312402717778008f, 0.312670179951171f, 0.312937337758465f, + 0.313204191368524f, 0.313470740949935f, 0.313736986671243f, + 0.314002928700952f, 0.314268567207521f, 0.314533902359368f, + 0.314798934324867f, 0.315063663272350f, 0.315328089370105f, + 0.315592212786379f, 0.315856033689375f, 0.316119552247253f, + 0.316382768628132f, 0.316645683000087f, 0.316908295531148f, + 0.317170606389307f, 0.317432615742509f, 0.317694323758658f, + 0.317955730605615f, 0.318216836451199f, 0.318477641463185f, + 0.318738145809305f, 0.318998349657250f, 0.319258253174666f, + 0.319517856529158f, 0.319777159888288f, 0.320036163419573f, + 0.320294867290491f, 0.320553271668473f, 0.320811376720911f, + 0.321069182615152f, 0.321326689518500f, 0.321583897598218f, + 0.321840807021525f, 0.322097417955597f, 0.322353730567567f, + 0.322609745024527f, 0.322865461493525f, 0.323120880141565f, + 0.323376001135609f, 0.323630824642579f, 0.319523622821772f, + 0.319777851855178f, 0.320031783902010f, 0.320285419129017f, + 0.320538757702904f, 0.320791799790335f, 0.321044545557929f, + 0.321296995172263f, 0.321549148799872f, 0.321801006607247f, + 0.322052568760837f, 0.322303835427048f, 0.322554806772243f, + 0.322805482962742f, 0.323055864164824f, 0.323305950544722f, + 0.323555742268629f, 0.323805239502694f, 0.324054442413022f, + 0.324303351165679f, 0.324551965926684f, 0.324800286862015f, + 0.325048314137607f, 0.325296047919353f, 0.325543488373102f, + 0.325790635664660f, 0.326037489959792f, 0.326284051424219f, + 0.326530320223619f, 0.326776296523626f, 0.327021980489835f, + 0.327267372287794f, 0.327512472083010f, 0.327757280040949f, + 0.328001796327030f, 0.328246021106633f, 0.328489954545092f, + 0.328733596807703f, 0.328976948059713f, 0.329220008466331f, + 0.329462778192721f, 0.329705257404004f, 0.329947446265259f, + 0.330189344941523f, 0.330430953597789f, 0.330672272399006f, + 0.330913301510083f, 0.331154041095883f, 0.331394491321230f, + 0.331634652350901f, 0.331874524349634f, 0.332114107482121f, + 0.332353401913014f, 0.332592407806920f, 0.332831125328403f, + 0.333069554641987f, 0.333307695912150f, 0.333545549303328f, + 0.333783114979916f, 0.334020393106265f, 0.334257383846681f, + 0.334494087365431f, 0.334730503826737f, 0.334966633394778f, + 0.335202476233691f, 0.335438032507570f, 0.335673302380465f, + 0.335908286016386f, 0.336142983579296f, 0.336377395233120f, + 0.336611521141736f, 0.336845361468981f, 0.337078916378650f, + 0.337312186034494f, 0.337545170600220f, 0.337777870239495f, + 0.338010285115941f, 0.338242415393138f, 0.338474261234623f, + 0.338705822803890f, 0.338937100264391f, 0.339168093779534f, + 0.339398803512685f, 0.339629229627166f, 0.339859372286258f, + 0.340089231653198f, 0.340318807891179f, 0.340548101163354f, + 0.340777111632832f, 0.341005839462677f, 0.341234284815913f, + 0.341462447855520f, 0.341690328744436f, 0.341917927645554f, + 0.342145244721727f, 0.342372280135763f, 0.342599034050427f, + 0.342825506628444f, 0.343051698032493f, 0.343277608425212f, + 0.343503237969194f, 0.343728586826993f, 0.343953655161116f, + 0.344178443134029f, 0.344402950908157f, 0.344627178645878f, + 0.344851126509531f, 0.345074794661410f, 0.345298183263766f, + 0.345521292478809f, 0.345744122468705f, 0.345966673395577f, + 0.346188945421505f, 0.346410938708527f, 0.346632653418637f, + 0.346854089713787f, 0.347075247755886f, 0.347296127706800f, + 0.347516729728352f, 0.347737053982323f, 0.347957100630451f, + 0.348176869834429f, 0.348396361755910f, 0.348615576556502f, + 0.348834514397771f, 0.349053175441242f, 0.349271559848394f, + 0.349489667780664f, 0.349707499399448f, 0.349925054866097f, + 0.350142334341920f, 0.350359337988183f, 0.350576065966110f, + 0.350792518436880f, 0.351008695561632f, 0.351224597501459f, + 0.351440224417413f, 0.351655576470505f, 0.351870653821698f, + 0.352085456631918f, 0.352299985062043f, 0.352514239272912f, + 0.352728219425319f, 0.352941925680015f, 0.353155358197710f, + 0.353368517139069f, 0.353581402664716f, 0.353794014935231f, + 0.354006354111151f, 0.354218420352971f, 0.354430213821142f, + 0.354641734676074f, 0.354852983078131f, 0.355063959187638f, + 0.355274663164873f, 0.355485095170075f, 0.355695255363438f, + 0.355905143905114f, 0.356114760955211f, 0.356324106673794f, + 0.356533181220887f, 0.356741984756471f, 0.356950517440481f, + 0.357158779432813f, 0.357366770893317f, 0.357574491981803f, + 0.357781942858036f, 0.357989123681738f, 0.358196034612590f, + 0.358402675810229f, 0.358609047434249f, 0.358815149644201f, + 0.359020982599593f, 0.359226546459892f, 0.359431841384519f, + 0.359636867532855f, 0.359841625064236f, 0.360046114137957f, + 0.360250334913268f, 0.360454287549378f, 0.360657972205452f, + 0.360861389040612f, 0.356702810206361f, 0.356905691876891f, + 0.357108306203617f, 0.357310653345491f, 0.357512733461420f, + 0.357714546710270f, 0.357916093250863f, 0.358117373241979f, + 0.358318386842353f, 0.358519134210680f, 0.358719615505611f, + 0.358919830885752f, 0.359119780509670f, 0.359319464535885f, + 0.359518883122878f, 0.359718036429085f, 0.359916924612898f, + 0.360115547832669f, 0.360313906246705f, 0.360512000013271f, + 0.360709829290588f, 0.360907394236835f, 0.361104695010150f, + 0.361301731768624f, 0.361498504670307f, 0.361695013873208f, + 0.361891259535291f, 0.362087241814476f, 0.362282960868644f, + 0.362478416855630f, 0.362673609933225f, 0.362868540259182f, + 0.363063207991205f, 0.363257613286961f, 0.363451756304069f, + 0.363645637200109f, 0.363839256132616f, 0.364032613259082f, + 0.364225708736957f, 0.364418542723648f, 0.364611115376518f, + 0.364803426852889f, 0.364995477310039f, 0.365187266905203f, + 0.365378795795572f, 0.365570064138297f, 0.365761072090484f, + 0.365951819809197f, 0.366142307451455f, 0.366332535174237f, + 0.366522503134478f, 0.366712211489069f, 0.366901660394860f, + 0.367090850008657f, 0.367279780487222f, 0.367468451987276f, + 0.367656864665497f, 0.367845018678518f, 0.368032914182932f, + 0.368220551335288f, 0.368407930292089f, 0.368595051209801f, + 0.368781914244842f, 0.368968519553590f, 0.369154867292378f, + 0.369340957617498f, 0.369526790685198f, 0.369712366651684f, + 0.369897685673117f, 0.370082747905618f, 0.370267553505262f, + 0.370452102628084f, 0.370636395430075f, 0.370820432067181f, + 0.371004212695309f, 0.371187737470320f, 0.371371006548033f, + 0.371554020084224f, 0.371736778234628f, 0.371919281154933f, + 0.372101529000787f, 0.372283521927796f, 0.372465260091519f, + 0.372646743647477f, 0.372827972751145f, 0.373008947557956f, + 0.373189668223299f, 0.373370134902522f, 0.373550347750928f, + 0.373730306923778f, 0.373910012576292f, 0.374089464863644f, + 0.374268663940966f, 0.374447609963348f, 0.374626303085837f, + 0.374804743463435f, 0.374982931251104f, 0.375160866603761f, + 0.375338549676281f, 0.375515980623497f, 0.375693159600195f, + 0.375870086761124f, 0.376046762260986f, 0.376223186254440f, + 0.376399358896105f, 0.376575280340554f, 0.376750950742319f, + 0.376926370255889f, 0.377101539035708f, 0.377276457236179f, + 0.377451125011662f, 0.377625542516473f, 0.377799709904887f, + 0.377973627331134f, 0.378147294949401f, 0.378320712913835f, + 0.378493881378536f, 0.378666800497564f, 0.378839470424936f, + 0.379011891314623f, 0.379184063320557f, 0.379355986596625f, + 0.379527661296671f, 0.379699087574496f, 0.379870265583860f, + 0.380041195478477f, 0.380211877412020f, 0.380382311538119f, + 0.380552498010361f, 0.380722436982289f, 0.380892128607405f, + 0.381061573039165f, 0.381230770430986f, 0.381399720936239f, + 0.381568424708253f, 0.381736881900314f, 0.381905092665666f, + 0.382073057157508f, 0.382240775528999f, 0.382408247933252f, + 0.382575474523339f, 0.382742455452288f, 0.382909190873084f, + 0.383075680938671f, 0.383241925801948f, 0.383407925615771f, + 0.383573680532955f, 0.383739190706270f, 0.383904456288443f, + 0.384069477432160f, 0.384234254290062f, 0.384398787014749f, + 0.384563075758777f, 0.384727120674658f, 0.384890921914863f, + 0.385054479631818f, 0.385217793977909f, 0.385380865105476f, + 0.385543693166817f, 0.385706278314188f, 0.385868620699801f, + 0.386030720475826f, 0.386192577794389f, 0.386354192807573f, + 0.386515565667420f, 0.386676696525926f, 0.386837585535046f, + 0.386998232846693f, 0.387158638612734f, 0.387318802984995f, + 0.387478726115260f, 0.387638408155268f, 0.383436121249137f, + 0.383595321563679f, 0.383754281242925f, 0.383913000438444f, + 0.384071479301760f, 0.384229717984357f, 0.384387716637672f, + 0.384545475413102f, 0.384702994462001f, 0.384860273935677f, + 0.385017313985400f, 0.385174114762392f, 0.385330676417835f, + 0.385486999102868f, 0.385643082968586f, 0.385798928166041f, + 0.385954534846243f, 0.386109903160157f, 0.386265033258709f, + 0.386419925292778f, 0.386574579413201f, 0.386728995770773f, + 0.386883174516247f, 0.387037115800330f, 0.387190819773689f, + 0.387344286586945f, 0.387497516390679f, 0.387650509335427f, + 0.387803265571684f, 0.387955785249900f, 0.388108068520482f, + 0.388260115533796f, 0.388411926440164f, 0.388563501389864f, + 0.388714840533132f, 0.388865944020162f, 0.389016812001104f, + 0.389167444626063f, 0.389317842045105f, 0.389468004408251f, + 0.389617931865478f, 0.389767624566722f, 0.389917082661874f, + 0.390066306300784f, 0.390215295633258f, 0.390364050809059f, + 0.390512571977908f, 0.390660859289481f, 0.390808912893412f, + 0.390956732939294f, 0.391104319576674f, 0.391251672955058f, + 0.391398793223907f, 0.391545680532642f, 0.391692335030638f, + 0.391838756867229f, 0.391984946191705f, 0.392130903153313f, + 0.392276627901259f, 0.392422120584702f, 0.392567381352763f, + 0.392712410354515f, 0.392857207738992f, 0.393001773655183f, + 0.393146108252035f, 0.393290211678450f, 0.393434084083290f, + 0.393577725615372f, 0.393721136423470f, 0.393864316656316f, + 0.394007266462598f, 0.389788257983385f, 0.389930747382434f, + 0.390073006800726f, 0.390215036386779f, 0.390356836289067f, + 0.390498406656018f, 0.390639747636022f, 0.390780859377423f, + 0.390921742028522f, 0.391062395737578f, 0.391202820652806f, + 0.391343016922379f, 0.391482984694427f, 0.391622724117036f, + 0.391762235338250f, 0.391901518506070f, 0.392040573768452f, + 0.392179401273312f, 0.392318001168520f, 0.392456373601907f, + 0.392594518721257f, 0.392732436674313f, 0.392870127608774f, + 0.393007591672297f, 0.393144829012496f, 0.393281839776942f, + 0.393418624113161f, 0.393555182168638f, 0.393691514090816f, + 0.393827620027092f, 0.393963500124822f, 0.394099154531318f, + 0.394234583393851f, 0.394369786859647f, 0.394504765075889f, + 0.394639518189717f, 0.394774046348230f, 0.394908349698482f, + 0.395042428387484f, 0.395176282562205f, 0.395309912369570f, + 0.395443317956462f, 0.395576499469721f, 0.395709457056142f, + 0.395842190862479f, 0.395974701035442f, 0.396106987721700f, + 0.396239051067876f, 0.396370891220552f, 0.396502508326267f, + 0.396633902531515f, 0.396765073982748f, 0.396896022826377f, + 0.397026749208768f, 0.397157253276243f, 0.392925807167507f, + 0.393055867043950f, 0.393185705044190f, 0.393315321314378f, + 0.393444716000622f, 0.393573889248988f, 0.393702841205497f, + 0.393831572016130f, 0.393960081826821f, 0.394088370783464f, + 0.394216439031910f, 0.394344286717965f, 0.394471913987393f, + 0.394599320985916f, 0.394726507859211f, 0.394853474752914f, + 0.394980221812616f, 0.395106749183867f, 0.395233057012172f, + 0.395359145442995f, 0.395485014621754f, 0.395610664693828f, + 0.395736095804550f, 0.395861308099210f, 0.395986301723057f, + 0.396111076821296f, 0.396235633539087f, 0.396359972021550f, + 0.396484092413761f, 0.396607994860752f, 0.396731679507512f, + 0.396855146498989f, 0.396978395980086f, 0.397101428095663f, + 0.397224242990538f, 0.397346840809486f, 0.397469221697237f, + 0.397591385798481f, 0.397713333257862f, 0.397835064219984f, + 0.397956578829405f, 0.398077877230641f, 0.398198959568167f, + 0.398319825986412f, 0.398440476629763f, 0.398560911642564f, + 0.398681131169117f, 0.398801135353680f, 0.398920924340467f, + 0.394678770266074f, 0.394798129289784f, 0.394917273548105f, + 0.395036203185081f, 0.395154918344712f, 0.395273419170954f, + 0.395391705807720f, 0.395509778398882f, 0.395627637088268f, + 0.395745282019662f, 0.395862713336806f, 0.395979931183398f, + 0.396096935703093f, 0.396213727039505f, 0.396330305336203f, + 0.396446670736713f, 0.396562823384518f, 0.396678763423060f, + 0.396794490995734f, 0.396910006245896f, 0.397025309316857f, + 0.397140400351884f, 0.397255279494203f, 0.397369946886996f, + 0.397484402673401f, 0.397598646996516f, 0.397712679999392f, + 0.397826501825040f, 0.397940112616427f, 0.398053512516475f, + 0.398166701668066f, 0.398279680214038f, 0.398392448297186f, + 0.398505006060259f, 0.398617353645969f, 0.398729491196979f, + 0.398841418855912f, 0.398953136765347f, 0.399064645067821f, + 0.399175943905828f, 0.399287033421816f, 0.399397913758195f, + 0.399508585057326f, 0.399619047461532f, 0.399729301113091f, + 0.395477618146659f, 0.395587454719585f, 0.395697082966438f, + 0.395806503029326f, 0.395915715050310f, 0.396024719171410f, + 0.396133515534604f, 0.396242104281823f, 0.396350485554960f, + 0.396458659495860f, 0.396566626246330f, 0.396674385948129f, + 0.396781938742976f, 0.396889284772547f, 0.396996424178473f, + 0.397103357102343f, 0.397210083685704f, 0.397316604070058f, + 0.397422918396866f, 0.397529026807543f, 0.397634929443464f, + 0.397740626445960f, 0.397846117956317f, 0.397951404115781f, + 0.398056485065553f, 0.398161360946792f, 0.398266031900612f, + 0.398370498068087f, 0.398474759590244f, 0.398578816608071f, + 0.398682669262511f, 0.398786317694462f, 0.398889762044784f, + 0.398993002454288f, 0.399096039063747f, 0.399198872013887f, + 0.399301501445394f, 0.399403927498908f, 0.399506150315030f, + 0.399608170034313f, 0.399709986797270f, 0.399811600744371f, + 0.395551284008465f, 0.395652492745088f, 0.395753499087005f, + 0.395854303174512f, 0.395954905147863f, 0.396055305147269f, + 0.396155503312898f, 0.396255499784873f, 0.396355294703278f, + 0.396454888208150f, 0.396554280439485f, 0.396653471537236f, + 0.396752461641311f, 0.396851250891577f, 0.396949839427857f, + 0.397048227389931f, 0.397146414917536f, 0.397244402150366f, + 0.397342189228072f, 0.397439776290262f, 0.397537163476500f, + 0.397634350926308f, 0.397731338779165f, 0.397828127174505f, + 0.397924716251721f, 0.398021106150163f, 0.398117297009136f, + 0.398213288967904f, 0.398309082165687f, 0.398404676741662f, + 0.398500072834962f, 0.398595270584678f, 0.398690270129858f, + 0.398785071609507f, 0.398879675162585f, 0.398974080928012f, + 0.399068289044663f, 0.399162299651370f, 0.399256112886922f, + 0.399349728890064f, 0.399443147799501f, 0.395174641746314f, + 0.395267666884276f, 0.395360495344382f, 0.395453127265163f, + 0.395545562785107f, 0.395637802042658f, 0.395729845176217f, + 0.395821692324142f, 0.395913343624749f, 0.396004799216310f, + 0.396096059237054f, 0.396187123825166f, 0.396277993118789f, + 0.396368667256023f, 0.396459146374925f, 0.396549430613508f, + 0.396639520109742f, 0.396729415001555f, 0.396819115426830f, + 0.396908621523410f, 0.396997933429091f, 0.397087051281630f, + 0.397175975218736f, 0.397264705378080f, 0.397353241897287f, + 0.397441584913940f, 0.397529734565577f, 0.397617690989695f, + 0.397705454323747f, 0.397793024705144f, 0.397880402271252f, + 0.397967587159395f, 0.398054579506854f, 0.398141379450867f, + 0.398227987128627f, 0.398314402677288f, 0.398400626233956f, + 0.398486657935698f, 0.398572497919536f, 0.398658146322447f, + 0.394381875273792f, 0.394467140925618f, 0.394552215407196f, + 0.394637098855334f, 0.394721791406795f, 0.394806293198300f, + 0.394890604366525f, 0.394974725048106f, 0.395058655379632f, + 0.395142395497653f, 0.395225945538672f, 0.395309305639152f, + 0.395392475935511f, 0.395475456564125f, 0.395558247661327f, + 0.395640849363404f, 0.395723261806605f, 0.395805485127131f, + 0.395887519461143f, 0.395969364944757f, 0.396051021714048f, + 0.396132489905045f, 0.396213769653737f, 0.396294861096067f, + 0.396375764367938f, 0.396456479605206f, 0.396537006943688f, + 0.396617346519155f, 0.396697498467336f, 0.396777462923917f, + 0.396857240024539f, 0.396936829904804f, 0.397016232700266f, + 0.397095448546439f, 0.397174477578793f, 0.397253319932756f, + 0.397331975743711f, 0.397410445146998f, 0.397488728277915f, + 0.397566825271718f, 0.393283008256039f, 0.393360733381202f, + 0.393438272774754f, 0.393515626571778f, 0.393592794907312f, + 0.393669777916352f, 0.393746575733851f, 0.393823188494718f, + 0.393899616333820f, 0.393975859385979f, 0.394051917785976f, + 0.394127791668548f, 0.394203481168389f, 0.394278986420149f, + 0.394354307558436f, 0.394429444717814f, 0.394504398032805f, + 0.394579167637888f, 0.394653753667496f, 0.394728156256022f, + 0.394802375537816f, 0.394876411647182f, 0.394950264718382f, + 0.395023934885638f, 0.395097422283124f, 0.395170727044974f, + 0.395243849305278f, 0.395316789198083f, 0.395389546857393f, + 0.395462122417168f, 0.395534516011326f, 0.395606727773741f, + 0.395678757838244f, 0.395750606338623f, 0.395822273408624f, + 0.395893759181948f, 0.395965063792254f, 0.396036187373157f, + 0.396107130058230f, 0.396177891981003f, 0.396248473274960f, + 0.391957146065968f, 0.392027366502581f, 0.392097406710579f, + 0.392167266823275f, 0.392236946973940f, 0.392306447295801f, + 0.392375767922042f, 0.392444908985804f, 0.392513870620185f, + 0.392582652958240f, 0.392651256132980f, 0.392719680277373f, + 0.392787925524346f, 0.392855992006779f, 0.392923879857513f, + 0.392991589209342f, 0.393059120195020f, 0.393126472947256f, + 0.393193647598717f, 0.393260644282026f, 0.393327463129763f, + 0.393394104274464f, 0.393460567848624f, 0.393526853984694f, + 0.393592962815081f, 0.393658894472149f, 0.393724649088219f, + 0.393790226795571f, 0.393855627726437f, 0.393920852013011f, + 0.393985899787441f, 0.394050771181833f, 0.394115466328248f, + 0.394179985358706f, 0.394244328405183f, 0.394308495599612f, + 0.394372487073883f, 0.394436302959841f, 0.394499943389291f, + 0.394563408493992f, 0.394626698405663f, 0.390328085248398f, + 0.390391025168985f, 0.390453790291433f, 0.390516380747287f, + 0.390578796668047f, 0.390641038185172f, 0.390703105430078f, + 0.390764998534136f, 0.390826717628674f, 0.390888262844978f, + 0.390949634314291f, 0.391010832167811f, 0.391071856536696f, + 0.391132707552056f, 0.391193385344964f, 0.391253890046444f, + 0.391314221787480f, 0.391374380699013f, 0.391434366911939f, + 0.391494180557114f, 0.391553821765346f, 0.391613290667404f, + 0.391672587394012f, 0.391731712075852f, 0.391790664843562f, + 0.391849445827736f, 0.391908055158926f, 0.391966492967641f, + 0.392024759384347f, 0.392082854539465f, 0.392140778563375f, + 0.392198531586412f, 0.392256113738870f, 0.392313525150997f, + 0.392370765953001f, 0.392427836275045f, 0.392484736247248f, + 0.392541465999688f, 0.392598025662398f, 0.392654415365369f, + 0.392710635238548f, 0.392766685411840f, 0.392822566015106f, + 0.392878277178163f, 0.388572091023208f, 0.388627463695129f, + 0.388682667316036f, 0.388737702015574f, 0.388792567923345f, + 0.388847265168909f, 0.388901793881780f, 0.388956154191431f, + 0.389010346227292f, 0.389064370118749f, 0.389118225995145f, + 0.389171913985779f, 0.389225434219908f, 0.389278786826745f, + 0.389331971935462f, 0.389384989675184f, 0.389437840174996f, + 0.389490523563938f, 0.389543039971008f, 0.389595389525160f, + 0.389647572355306f, 0.389699588590314f, 0.389751438359007f, + 0.389803121790169f, 0.389854639012537f, 0.389905990154806f, + 0.389957175345630f, 0.390008194713615f, 0.390059048387330f, + 0.390109736495295f, 0.390160259165990f, 0.390210616527851f, + 0.390260808709272f, 0.390310835838602f, 0.390360698044147f, + 0.390410395454172f, 0.390459928196896f, 0.390509296400497f, + 0.390558500193108f, 0.390607539702819f, 0.390656415057679f, + 0.390705126385692f, 0.390753673814819f, 0.390802057472977f, + 0.390850277488041f, 0.390898333987844f, 0.390946227100173f, + 0.390993956952773f, 0.386679795665769f, 0.386727199381975f, + 0.386774440221429f, 0.386821518311702f, 0.386868433780325f, + 0.386915186754783f, 0.386961777362520f, 0.387008205730934f, + 0.387054471987382f, 0.387100576259178f, 0.387146518673590f, + 0.387192299357847f, 0.387237918439132f, 0.387283376044585f, + 0.387328672301304f, 0.387373807336342f, 0.387418781276710f, + 0.387463594249377f, 0.387508246381265f, 0.387552737799258f, + 0.387597068630192f, 0.387641239000863f, 0.387685249038022f, + 0.387729098868378f, 0.387772788618595f, 0.387816318415297f, + 0.387859688385061f, 0.387902898654424f, 0.387945949349877f, + 0.387988840597871f, 0.388031572524811f, 0.388074145257060f, + 0.388116558920937f, 0.388158813642719f, 0.388200909548640f, + 0.388242846764889f, 0.388284625417613f, 0.388326245632916f, + 0.388367707536857f, 0.388409011255455f, 0.388450156914683f, + 0.388491144640472f, 0.388531974558710f, 0.388572646795240f, + 0.388613161475865f, 0.388653518726341f, 0.388693718672384f, + 0.388733761439666f, 0.388773647153814f, 0.388813375940413f, + 0.388852947925006f, 0.388892363233092f, 0.388931621990125f, + 0.384608996313940f, 0.384647942345062f, 0.384686732201239f, + 0.384725366007753f, 0.384763843889844f, 0.384802165972708f, + 0.384840332381498f, 0.384878343241323f, 0.384916198677251f, + 0.384953898814305f, 0.384991443777464f, 0.385028833691666f, + 0.385066068681804f, 0.385103148872729f, 0.385140074389248f, + 0.385176845356125f, 0.385213461898081f, 0.385249924139793f, + 0.385286232205896f, 0.385322386220981f, 0.385358386309596f, + 0.385394232596246f, 0.385429925205392f, 0.385465464261452f, + 0.385500849888801f, 0.385536082211772f, 0.385571161354653f, + 0.385606087441689f, 0.385640860597082f, 0.385675480944992f, + 0.385709948609534f, 0.385744263714780f, 0.385778426384760f, + 0.385812436743460f, 0.385846294914823f, 0.385880001022748f, + 0.385913555191092f, 0.385946957543667f, 0.385980208204245f, + 0.386013307296550f, 0.386046254944268f, 0.386079051271037f, + 0.386111696400456f, 0.386144190456078f, 0.386176533561413f, + 0.386208725839929f, 0.386240767415049f, 0.386272658410155f, + 0.386304398948585f, 0.386335989153633f, 0.386367429148549f, + 0.386398719056542f, 0.386429859000777f, 0.386460849104374f, + 0.386491689490413f, 0.386522380281929f, 0.386552921601912f, + 0.386583313573312f, 0.386613556319033f, 0.386643649961939f, + 0.386673594624847f, 0.386703390430534f, 0.386733037501732f, + 0.386762535961129f, 0.382430157923795f, 0.382459359527486f, + 0.382488412887186f, 0.382517318125409f, 0.382546075364630f, + 0.382574684727277f, 0.382603146335737f, 0.382631460312354f, + 0.382659626779427f, 0.382687645859213f, 0.382715517673926f, + 0.382743242345736f, 0.382770819996769f, 0.382798250749110f, + 0.382825534724800f, 0.382852672045835f, 0.382879662834169f, + 0.382906507211714f, 0.382933205300338f, 0.382959757221863f, + 0.382986163098072f, 0.383012423050702f, 0.383038537201448f, + 0.383064505671961f, 0.383090328583849f, 0.383116006058678f, + 0.383141538217968f, 0.383166925183199f, 0.383192167075804f, + 0.383217264017177f, 0.383242216128666f, 0.383267023531576f, + 0.383291686347169f, 0.383316204696664f, 0.383340578701237f, + 0.383364808482021f, 0.383388894160103f, 0.383412835856531f, + 0.383436633692307f, 0.383460287788389f, 0.383483798265695f, + 0.383507165245098f, 0.383530388847426f, 0.383553469193466f, + 0.383576406403962f, 0.383599200599613f, 0.383621851901075f, + 0.383644360428963f, 0.383666726303846f, 0.383688949646252f, + 0.383711030576663f, 0.383732969215520f, 0.383754765683220f, + 0.383776420100117f, 0.383797932586521f, 0.383819303262701f, + 0.383840532248879f, 0.383861619665237f, 0.383882565631912f, + 0.383903370268999f, 0.383924033696548f, 0.383944556034568f, + 0.383964937403022f, 0.383985177921832f, 0.384005277710877f, + 0.384025236889990f, 0.384045055578963f, 0.384064733897545f, + 0.384084271965440f, 0.384103669902310f, 0.384122927827773f, + 0.384142045861405f, 0.384161024122738f, 0.384179862731259f, + 0.384198561806415f, 0.384217121467608f, 0.384235541834196f, + 0.384253823025494f, 0.384271965160776f, 0.384289968359270f, + 0.384307832740163f, 0.384325558422595f, 0.384343145525668f, + 0.384360594168436f, 0.384377904469912f, 0.384395076549067f, + 0.384412110524825f, 0.384429006516070f, 0.384445764641642f, + 0.384462385020336f, 0.384478867770907f, 0.384495213012063f, + 0.384511420862471f, 0.384527491440755f, 0.384543424865495f, + 0.384559221255226f, 0.380213152720866f, 0.380228675396019f, + 0.380244061391516f, 0.380259310825718f, 0.380274423816947f, + 0.380289400483481f, 0.380304240943551f, 0.380318945315350f, + 0.380333513717025f, 0.380347946266679f, 0.380362243082373f, + 0.380376404282125f, 0.380390429983908f, 0.380404320305654f, + 0.380418075365251f, 0.380431695280542f, 0.380445180169328f, + 0.380458530149368f, 0.380471745338376f, 0.380484825854023f, + 0.380497771813937f, 0.380510583335702f, 0.380523260536860f, + 0.380535803534910f, 0.380548212447305f, 0.380560487391458f, + 0.380572628484737f, 0.380584635844466f, 0.380596509587927f, + 0.380608249832359f, 0.380619856694957f, 0.380631330292873f, + 0.380642670743215f, 0.380653878163048f, 0.380664952669394f, + 0.380675894379233f, 0.380686703409499f, 0.380697379877085f, + 0.380707923898839f, 0.380718335591567f, 0.380728615072032f, + 0.380738762456952f, 0.380748777863002f, 0.380758661406816f, + 0.380768413204982f, 0.380778033374047f, 0.380787522030512f, + 0.380796879290837f, 0.380806105271438f, 0.380815200088688f, + 0.380824163858915f, 0.380832996698407f, 0.380841698723405f, + 0.380850270050110f, 0.380858710794677f, 0.380867021073220f, + 0.380875201001808f, 0.380883250696466f, 0.380891170273180f, + 0.380898959847887f, 0.380906619536485f, 0.380914149454827f, + 0.380921549718722f, 0.380928820443937f, 0.380935961746195f, + 0.380942973741177f, 0.380949856544519f, 0.380956610271814f, + 0.380963235038613f, 0.380969730960421f, 0.380976098152703f, + 0.380982336730880f, 0.380988446810326f, 0.380994428506377f, + 0.381000281934323f, 0.381006007209410f, 0.381011604446843f, + 0.381017073761781f, 0.381022415269341f, 0.381027629084598f, + 0.381032715322582f, 0.381037674098280f, 0.381042505526636f, + 0.381047209722551f, 0.381051786800881f, 0.381056236876441f, + 0.381060560064002f, 0.381064756478290f, 0.381068826233990f, + 0.381072769445743f, 0.381076586228145f, 0.381080276695752f, + 0.381083840963073f, 0.381087279144577f, 0.381090591354688f, + 0.381093777707787f, 0.381096838318210f, 0.381099773300253f, + 0.381102582768166f, 0.381105266836158f, 0.381107825618392f, + 0.381110259228989f, 0.381112567782028f, 0.381114751391543f, + 0.381116810171524f, 0.381118744235920f, 0.381120553698635f, + 0.381122238673531f, 0.381123799274425f, 0.381125235615091f, + 0.381126547809262f, 0.381127735970624f, 0.381128800212824f, + 0.381129740649461f, 0.381130557394094f, 0.381131250560238f, + 0.381131820261363f, 0.381132266610899f, 0.381132589722230f, + 0.381132789708696f, 0.381132866683598f, 0.381132820760188f, + 0.381132652051679f, 0.381132360671238f, 0.381131946731992f, + 0.381131410347020f, 0.381130751629362f, 0.381129970692011f, + 0.381129067647921f, 0.381128042609999f, 0.381126895691109f, + 0.381125627004075f, 0.381124236661673f, 0.381122724776639f, + 0.381121091461664f, 0.381119336829397f, 0.381117460992443f, + 0.381115464063363f, 0.381113346154677f, 0.381111107378858f, + 0.381108747848338f, 0.381106267675507f, 0.381103666972709f, + 0.381100945852245f, 0.381098104426374f, 0.381095142807312f, + 0.381092061107230f, 0.381088859438256f, 0.381085537912476f, + 0.381082096641931f, 0.381078535738620f, 0.381074855314498f, + 0.381071055481477f, 0.381067136351425f, 0.381063098036167f, + 0.381058940647486f, 0.381054664297119f, 0.381050269096762f, + 0.381045755158066f, 0.381041122592641f, 0.381036371512051f, + 0.381031502027818f, 0.381026514251421f, 0.381021408294295f, + 0.381016184267832f, 0.381010842283379f, 0.381005382452243f, + 0.380999804885685f, 0.380994109694924f, 0.380988296991135f, + 0.380982366885450f, 0.380976319488957f, 0.380970154912701f, + 0.380963873267684f, 0.380957474664866f, 0.380950959215160f, + 0.380944327029439f, 0.380937578218531f, 0.380930712893222f, + 0.380923731164253f, 0.380916633142322f, 0.380909418938086f, + 0.380902088662155f, 0.380894642425098f, 0.380887080337441f, + 0.380879402509665f, 0.380871609052208f, 0.380863700075466f, + 0.380855675689790f, 0.380847536005489f, 0.380839281132828f, + 0.380830911182028f, 0.380822426263269f, 0.380813826486684f, + 0.380805111962367f, 0.380796282800365f, 0.380787339110683f, + 0.380778281003283f, 0.380769108588083f, 0.380759821974958f, + 0.380750421273741f, 0.380740906594218f, 0.380731278046136f, + 0.380721535739195f, 0.380711679783055f, 0.380701710287330f, + 0.380691627361591f, 0.380681431115367f, 0.380671121658143f, + 0.380660699099360f, 0.380650163548417f, 0.376277787107090f, + 0.376267025899846f, 0.376256152028377f, 0.376245165601906f, + 0.376234066729615f, 0.376222855520642f, 0.376211532084083f, + 0.376200096528988f, 0.376188548964365f, 0.376176889499179f, + 0.376165118242352f, 0.376153235302761f, 0.376141240789241f, + 0.376129134810584f, 0.376116917475536f, 0.376104588892804f, + 0.376092149171047f, 0.376079598418884f, 0.376066936744890f, + 0.376054164257595f, 0.376041281065488f, 0.376028287277012f, + 0.376015183000569f, 0.376001968344516f, 0.375988643417169f, + 0.375975208326797f, 0.375961663181629f, 0.375948008089849f, + 0.375934243159598f, 0.375920368498973f, 0.375906384216028f, + 0.375892290418775f, 0.375878087215181f, 0.375863774713170f, + 0.375849353020623f, 0.375834822245377f, 0.375820182495227f, + 0.375805433877922f, 0.375790576501171f, 0.375775610472637f, + 0.375760535899941f, 0.375745352890661f, 0.375730061552329f, + 0.375714661992437f, 0.375699154318431f, 0.375683538637716f, + 0.375667815057652f, 0.375651983685556f, 0.375636044628702f, + 0.375619997994319f, 0.375603843889596f, 0.375587582421675f, + 0.375571213697657f, 0.375554737824598f, 0.375538154909513f, + 0.375521465059370f, 0.375504668381098f, 0.375487764981579f, + 0.375470754967654f, 0.375453638446118f, 0.375436415523726f, + 0.375419086307187f, 0.375401650903168f, 0.375384109418291f, + 0.375366461959138f, 0.375348708632243f, 0.375330849544101f, + 0.375312884801161f, 0.375294814509829f, 0.370914910768890f, + 0.370896629699820f, 0.370878243401316f, 0.370859751979612f, + 0.370841155540898f, 0.370822454191318f, 0.370803648036976f, + 0.370784737183932f, 0.370765721738200f, 0.370746601805755f, + 0.370727377492524f, 0.370708048904394f, 0.370688616147207f, + 0.370669079326762f, 0.370649438548815f, 0.370629693919078f, + 0.370609845543221f, 0.370589893526868f, 0.370569837975601f, + 0.370549678994961f, 0.370529416690441f, 0.370509051167494f, + 0.370488582531529f, 0.370468010887911f, 0.370447336341962f, + 0.370426558998959f, 0.370405678964140f, 0.370384696342694f, + 0.370363611239771f, 0.370342423760476f, 0.370321134009869f, + 0.370299742092970f, 0.370278248114753f, 0.370256652180149f, + 0.370234954394047f, 0.370213154861292f, 0.370191253686683f, + 0.370169250974980f, 0.370147146830898f, 0.370124941359106f, + 0.370102634664233f, 0.370080226850863f, 0.370057718023537f, + 0.370035108286753f, 0.370012397744965f, 0.369989586502583f, + 0.369966674663976f, 0.369943662333467f, 0.369920549615336f, + 0.369897336613821f, 0.369874023433116f, 0.369850610177372f, + 0.365465368943117f, 0.365441755849571f, 0.365418042993176f, + 0.365394230477909f, 0.365370318407704f, 0.365346306886452f, + 0.365322196017997f, 0.365297985906144f, 0.365273676654654f, + 0.365249268367241f, 0.365224761147580f, 0.365200155099300f, + 0.365175450325988f, 0.365150646931187f, 0.365125745018395f, + 0.365100744691070f, 0.365075646052624f, 0.365050449206426f, + 0.365025154255803f, 0.364999761304037f, 0.364974270454366f, + 0.364948681809988f, 0.364922995474054f, 0.364897211549674f, + 0.364871330139912f, 0.364845351347791f, 0.364819275276291f, + 0.364793102028345f, 0.364766831706847f, 0.364740464414645f, + 0.364714000254544f, 0.364687439329305f, 0.364660781741648f, + 0.364634027594247f, 0.364607176989734f, 0.364580230030696f, + 0.364553186819680f, 0.364526047459185f, 0.364498812051671f, + 0.364471480699551f, 0.364444053505197f, 0.364416530570936f, + 0.364388911999053f, 0.364361197891790f, 0.359971660343764f, + 0.359943755472287f, 0.359915755371891f, 0.359887660144644f, + 0.359859469892569f, 0.359831184717647f, 0.359802804721815f, + 0.359774330006967f, 0.359745760674952f, 0.359717096827579f, + 0.359688338566610f, 0.359659485993765f, 0.359630539210721f, + 0.359601498319112f, 0.359572363420526f, 0.359543134616511f, + 0.359513812008569f, 0.359484395698161f, 0.359454885786701f, + 0.359425282375564f, 0.359395585566078f, 0.359365795459529f, + 0.359335912157159f, 0.359305935760169f, 0.359275866369713f, + 0.359245704086904f, 0.359215449012811f, 0.359185101248459f, + 0.359154660894830f, 0.359124128052863f, 0.359093502823453f, + 0.359062785307452f, 0.359031975605668f, 0.359001073818866f, + 0.358970080047767f, 0.358938994393051f, 0.358907816955351f, + 0.358876547835258f, 0.358845187133321f, 0.354452006942467f, + 0.354420463378311f, 0.354388828533694f, 0.354357102508990f, + 0.354325285404530f, 0.354293377320600f, 0.354261378357445f, + 0.354229288615266f, 0.354197108194219f, 0.354164837194419f, + 0.354132475715934f, 0.354100023858793f, 0.354067481722978f, + 0.354034849408430f, 0.354002127015045f, 0.353969314642676f, + 0.353936412391133f, 0.353903420360182f, 0.353870338649546f, + 0.353837167358905f, 0.353803906587893f, 0.353770556436105f, + 0.353737117003089f, 0.353703588388350f, 0.353669970691352f, + 0.353636264011513f, 0.353602468448208f, 0.353568584100769f, + 0.353534611068485f, 0.353500549450601f, 0.353466399346319f, + 0.353432160854798f, 0.353397834075151f, 0.353363419106450f, + 0.353328916047724f, 0.353294324997957f, 0.353259646056089f, + 0.348863151313442f, 0.348828296884025f, 0.348793354859070f, + 0.348758325337345f, 0.348723208417575f, 0.348688004198440f, + 0.348652712778576f, 0.348617334256578f, 0.348581868730996f, + 0.348546316300336f, 0.348510677063062f, 0.348474951117594f, + 0.348439138562308f, 0.348403239495538f, 0.348367254015573f, + 0.348331182220659f, 0.348295024208999f, 0.348258780078753f, + 0.348222449928035f, 0.348186033854919f, 0.348149531957434f, + 0.348112944333565f, 0.348076271081254f, 0.348039512298401f, + 0.348002668082859f, 0.347965738532442f, 0.347928723744916f, + 0.347891623818009f, 0.347854438849400f, 0.347817168936728f, + 0.347779814177587f, 0.347742374669529f, 0.347704850510061f, + 0.347667241796648f, 0.343267820619132f, 0.343230043090047f, + 0.343192181299149f, 0.343154235343728f, 0.343116205321032f, + 0.343078091328263f, 0.343039893462582f, 0.343001611821106f, + 0.342963246500909f, 0.342924797599019f, 0.342886265212423f, + 0.342847649438065f, 0.342808950372843f, 0.342770168113615f, + 0.342731302757191f, 0.342692354400342f, 0.342653323139794f, + 0.342614209072227f, 0.342575012294282f, 0.342535732902554f, + 0.342496370993594f, 0.342456926663912f, 0.342417400009971f, + 0.342377791128193f, 0.342338100114958f, 0.342298327066598f, + 0.342258472079406f, 0.342218535249630f, 0.342178516673473f, + 0.342138416447096f, 0.342098234666617f, 0.342057971428110f, + 0.342017626827605f, 0.337615472953512f, 0.337574965916929f, + 0.337534377806179f, 0.337493708717118f, 0.337452958745561f, + 0.337412127987276f, 0.337371216537989f, 0.337330224493385f, + 0.337289151949102f, 0.337247999000736f, 0.337206765743839f, + 0.337165452273921f, 0.337124058686446f, 0.337082585076838f, + 0.337041031540475f, 0.336999398172692f, 0.336957685068781f, + 0.336915892323990f, 0.336874020033523f, 0.336832068292543f, + 0.336790037196167f, 0.336747926839469f, 0.336705737317481f, + 0.336663468725190f, 0.336621121157539f, 0.336578694709430f, + 0.336536189475720f, 0.336493605551223f, 0.336450943030708f, + 0.336408202008902f, 0.336365382580488f, 0.336322484840108f, + 0.336279508882355f, 0.331874726794207f, 0.331831594685327f, + 0.331788384642604f, 0.331745096760461f, 0.331701731133276f, + 0.331658287855384f, 0.331614767021079f, 0.331571168724609f, + 0.331527493060178f, 0.331483740121949f, 0.331439910004039f, + 0.331396002800524f, 0.331352018605435f, 0.331307957512759f, + 0.331263819616440f, 0.331219605010381f, 0.331175313788438f, + 0.331130946044424f, 0.331086501872111f, 0.331041981365225f, + 0.330997384617451f, 0.330952711722427f, 0.330907962773751f, + 0.330863137864976f, 0.330818237089610f, 0.330773260541122f, + 0.330728208312932f, 0.330683080498421f, 0.330637877190924f, + 0.330592598483733f, 0.330547244470097f, 0.330501815243221f, + 0.326094582888691f, 0.326049003514778f, 0.326003349206981f, + 0.325957620058331f, 0.325911816161815f, 0.325865937610379f, + 0.325819984496923f, 0.325773956914305f, 0.325727854955339f, + 0.325681678712794f, 0.325635428279400f, 0.325589103747838f, + 0.325542705210749f, 0.325496232760730f, 0.325449686490334f, + 0.325403066492070f, 0.325356372858405f, 0.325309605681762f, + 0.325262765054519f, 0.325215851069012f, 0.325168863817535f, + 0.325121803392334f, 0.325074669885617f, 0.325027463389544f, + 0.324980183996233f, 0.324932831797761f, 0.324885406886157f, + 0.324837909353411f, 0.324790339291466f, 0.324742696792224f, + 0.324694981947541f, 0.320285466841654f, 0.320237607581489f, + 0.320189676251195f, 0.320141672942456f, 0.320093597746911f, + 0.320045450756158f, 0.319997232061749f, 0.319948941755193f, + 0.319900579927957f, 0.319852146671464f, 0.319803642077092f, + 0.319755066236177f, 0.319706419240011f, 0.319657701179843f, + 0.319608912146878f, 0.319560052232277f, 0.319511121527159f, + 0.319462120122598f, 0.319413048109625f, 0.319363905579229f, + 0.319314692622353f, 0.319265409329898f, 0.319216055792721f, + 0.319166632101636f, 0.319117138347413f, 0.319067574620779f, + 0.319017941012417f, 0.318968237612967f, 0.318918464513024f, + 0.318868621803143f, 0.318818709573831f, 0.318768727915555f, + 0.314356948911160f, 0.314306828666179f, 0.314256639263369f, + 0.314206380793024f, 0.314156053345391f, 0.314105657010674f, + 0.314055191879036f, 0.314004658040593f, 0.313954055585421f, + 0.313903384603549f, 0.313852645184966f, 0.313801837419615f, + 0.313750961397396f, 0.313700017208166f, 0.313649004941739f, + 0.313597924687883f, 0.313546776536326f, 0.313495560576751f, + 0.313444276898796f, 0.313392925592057f, 0.313341506746087f, + 0.313290020450394f, 0.313238466794443f, 0.313186845867658f, + 0.313135157759415f, 0.313083402559049f, 0.313031580355852f, + 0.312979691239072f, 0.312927735297912f, 0.312875712621533f, + 0.312823623299054f, 0.312771467419547f, 0.308357517064465f, + 0.308305228337950f, 0.308252873321368f, 0.308200452103618f, + 0.308147964773557f, 0.308095411419997f, 0.308042792131707f, + 0.307990106997414f, 0.307937356105799f, 0.307884539545501f, + 0.307831657405116f, 0.307778709773194f, 0.307725696738244f, + 0.307672618388731f, 0.307619474813076f, 0.307566266099656f, + 0.307512992336805f, 0.307459653612815f, 0.307406250015931f, + 0.307352781634359f, 0.307299248556257f, 0.307245650869742f, + 0.307191988662888f, 0.307138262023724f, 0.307084471040236f, + 0.307030615800366f, 0.306976696392013f, 0.306922712903033f, + 0.306868665421238f, 0.306814554034397f, 0.306760378830233f, + 0.306706139896429f, 0.306651837320622f, 0.302235743182830f, + 0.302181313585758f, 0.302126820609336f, 0.302072264341027f, + 0.302017644868253f, 0.301962962278390f, 0.301908216658771f, + 0.301853408096686f, 0.301798536679381f, 0.301743602494059f, + 0.301688605627880f, 0.301633546167958f, 0.301578424201366f, + 0.301523239815132f, 0.301467993096242f, 0.301412684131638f, + 0.301357313008217f, 0.301301879812834f, 0.301246384632300f, + 0.301190827553382f, 0.301135208662805f, 0.301079528047249f, + 0.301023785793351f, 0.300967981987704f, 0.300912116716858f, + 0.300856190067319f, 0.300800202125552f, 0.300744152977974f, + 0.300688042710961f, 0.300631871410846f, 0.300575639163918f, + 0.300519346056422f, 0.300462992174559f, 0.300406577604487f, + 0.295988374424745f, 0.295931838736557f, 0.295875242618375f, + 0.295818586156181f, 0.295761869435917f, 0.295705092543480f, + 0.295648255564723f, 0.295591358585456f, 0.295534401691445f, + 0.295477384968414f, 0.295420308502041f, 0.295363172377963f, + 0.295305976681771f, 0.295248721499016f, 0.295191406915201f, + 0.295134033015788f, 0.295076599886197f, 0.295019107611801f, + 0.294961556277931f, 0.294903945969875f, 0.294846276772877f, + 0.294788548772138f, 0.294730762052815f, 0.294672916700020f, + 0.294615012798824f, 0.294557050434254f, 0.294499029691292f, + 0.294440950654877f, 0.294382813409906f, 0.294324618041230f, + 0.294266364633658f, 0.294208053271955f, 0.294149684040843f, + 0.294091257025001f, 0.294032772309062f, 0.293974229977617f, + 0.289553902107637f, 0.289495244798780f, 0.289436530127930f, + 0.289377758179504f, 0.289318929037873f, 0.289260042787369f, + 0.289201099512278f, 0.289142099296842f, 0.289083042225260f, + 0.289023928381689f, 0.288964757850239f, 0.288905530714981f, + 0.288846247059938f, 0.288786906969092f, 0.288727510526381f, + 0.288668057815699f, 0.288608548920898f, 0.288548983925785f, + 0.288489362914122f, 0.288429685969631f, 0.288369953175988f, + 0.288310164616827f, 0.288250320375736f, 0.288190420536262f, + 0.288130465181907f, 0.288070454396130f, 0.288010388262347f, + 0.287950266863930f, 0.287890090284206f, 0.287829858606460f, + 0.287769571913934f, 0.287709230289826f, 0.287648833817288f, + 0.287588382579433f, 0.287527876659327f, 0.287467316139992f, + 0.287406701104411f, 0.287346031635518f, 0.287285307816206f, + 0.282862801721747f, 0.282801969450102f, 0.282741083076456f, + 0.282680142683527f, 0.282619148353990f, 0.282558100170477f, + 0.282496998215576f, 0.282435842571830f, 0.282374633321742f, + 0.282313370547767f, 0.282252054332321f, 0.282190684757772f, + 0.282129261906449f, 0.282067785860633f, 0.282006256702564f, + 0.281944674514439f, 0.281883039378409f, 0.281821351376584f, + 0.281759610591029f, 0.281697817103765f, 0.281635970996772f, + 0.281574072351982f, 0.281512121251289f, 0.281450117776538f, + 0.281388062009534f, 0.281325954032037f, 0.281263793925765f, + 0.281201581772391f, 0.281139317653543f, 0.281077001650809f, + 0.281014633845731f, 0.280952214319808f, 0.280889743154496f, + 0.280827220431206f, 0.280764646231307f, 0.280702020636124f, + 0.280639343726938f, 0.280576615584986f, 0.280513836291463f, + 0.280451005927520f, 0.280388124574262f, 0.280325192312755f, + 0.275900481216440f, 0.275837447381448f, 0.275774362881135f, + 0.275711227796389f, 0.275648042208058f, 0.275584806196942f, + 0.275521519843800f, 0.275458183229346f, 0.275394796434254f, + 0.275331359539149f, 0.275267872624617f, 0.275204335771198f, + 0.275140749059389f, 0.275077112569644f, 0.275013426382372f, + 0.274949690577941f, 0.274885905236672f, 0.274822070438846f, + 0.274758186264697f, 0.274694252794419f, 0.274630270108159f, + 0.274566238286022f, 0.274502157408071f, 0.274438027554322f, + 0.274373848804750f, 0.274309621239286f, 0.274245344937817f, + 0.274181019980186f, 0.274116646446194f, 0.274052224415596f, + 0.273987753968107f, 0.273923235183394f, 0.273858668141084f, + 0.273794052920759f, 0.273729389601958f, 0.273664678264174f, + 0.273599918986861f, 0.273535111849425f, 0.273470256931231f, + 0.273405354311600f, 0.273340404069808f, 0.273275406285090f, + 0.273210361036635f, 0.273145268403589f, 0.273080128465056f, + 0.273014941300095f, 0.272949706987721f, 0.272884425606906f, + 0.272819097236579f, 0.268391993948047f, 0.268326571835307f, + 0.268261102969579f, 0.268195587429617f, 0.268130025294132f, + 0.268064416641790f, 0.267998761551216f, 0.267933060100989f, + 0.267867312369645f, 0.267801518435677f, 0.267735678377535f, + 0.267669792273623f, 0.267603860202304f, 0.267537882241896f, + 0.267471858470674f, 0.267405788966870f, 0.267339673808669f, + 0.267273513074218f, 0.267207306841617f, 0.267141055188921f, + 0.267074758194145f, 0.267008415935258f, 0.266942028490186f, + 0.266875595936813f, 0.266809118352976f, 0.266742595816472f, + 0.266676028405051f, 0.266609416196424f, 0.266542759268253f, + 0.266476057698160f, 0.266409311563722f, 0.266342520942473f, + 0.266275685911905f, 0.266208806549462f, 0.266141882932549f, + 0.266074915138525f, 0.266007903244705f, 0.265940847328363f, + 0.265873747466726f, 0.265806603736981f, 0.265739416216268f, + 0.265672184981686f, 0.265604910110289f, 0.265537591679087f, + 0.265470229765049f, 0.265402824445097f, 0.265335375796112f, + 0.265267883894930f, 0.265200348818344f, 0.265132770643102f, + 0.265065149445912f, 0.264997485303435f, 0.264929778292289f, + 0.264862028489049f, 0.264794235970247f, 0.264726400812370f, + 0.264658523091862f, 0.264590602885124f, 0.264522640268512f, + 0.264454635318341f, 0.264386588110879f, 0.264318498722353f, + 0.264250367228946f, 0.259820465699218f, 0.259752250224420f, + 0.259683992873027f, 0.259615693721046f, 0.259547352844442f, + 0.259478970319136f, 0.259410546221005f, 0.259342080625883f, + 0.259273573609560f, 0.259205025247783f, 0.259136435616255f, + 0.259067804790635f, 0.258999132846538f, 0.258930419859537f, + 0.258861665905160f, 0.258792871058893f, 0.258724035396177f, + 0.258655158992408f, 0.258586241922943f, 0.258517284263090f, + 0.258448286088117f, 0.258379247473248f, 0.258310168493662f, + 0.258241049224495f, 0.258171889740839f, 0.258102690117744f, + 0.258033450430215f, 0.257964170753213f, 0.257894851161658f, + 0.257825491730422f, 0.257756092534337f, 0.257686653648191f, + 0.257617175146727f, 0.257547657104645f, 0.257478099596601f, + 0.257408502697209f, 0.257338866481037f, 0.257269191022612f, + 0.257199476396415f, 0.257129722676885f, 0.257059929938416f, + 0.256990098255361f, 0.256920227702025f, 0.256850318352674f, + 0.256780370281527f, 0.256710383562763f, 0.256640358270512f, + 0.256570294478866f, 0.256500192261870f, 0.256430051693527f, + 0.256359872847795f, 0.256289655798589f, 0.256219400619781f, + 0.256149107385198f, 0.256078776168626f, 0.256008407043804f, + 0.255938000084429f, 0.255867555364156f, 0.255797072956594f, + 0.255726552935309f, 0.255655995373823f, 0.255585400345616f, + 0.255514767924123f, 0.255444098182735f, 0.255373391194802f, + 0.255302647033627f, 0.255231865772471f, 0.255161047484552f, + 0.255090192243042f, 0.255019300121073f, 0.254948371191731f, + 0.254877405528058f, 0.254806403203054f, 0.254735364289674f, + 0.254664288860830f, 0.254593176989391f, 0.254522028748181f, + 0.254450844209981f, 0.254379623447529f, 0.254308366533519f, + 0.254237073540600f, 0.254165744541380f, 0.254094379608422f, + 0.254022978814245f, 0.253951542231324f, 0.253880069932092f, + 0.253808561988937f, 0.253737018474204f, 0.253665439460195f, + 0.253593825019167f, 0.253522175223334f, 0.253450490144866f, + 0.253378769855891f, 0.253307014428491f, 0.253235223934706f, + 0.253163398446532f, 0.253091538035921f, 0.253019642774782f, + 0.252947712734979f, 0.252875747988335f, 0.252803748606627f, + 0.252731714661588f, 0.252659646224911f, 0.252587543368241f, + 0.252515406163181f, 0.252443234681292f, 0.252371028994090f, + 0.252298789173046f, 0.252226515289590f, 0.252154207415106f, + 0.252081865620937f, 0.252009489978380f, 0.251937080558689f, + 0.251864637433075f, 0.251792160672704f, 0.251719650348701f, + 0.251647106532145f, 0.251574529294072f, 0.251501918705475f, + 0.251429274837301f, 0.251356597760457f, 0.251283887545805f, + 0.251211144264161f, 0.251138367986300f, 0.251065558782954f, + 0.250992716724808f, 0.250919841882507f, 0.250846934326649f, + 0.250773994127792f, 0.250701021356448f, 0.250628016083085f, + 0.250554978378129f, 0.250481908311962f, 0.250408805954920f, + 0.250335671377300f, 0.250262504649351f, 0.250189305841280f, + 0.250116075023251f, 0.250042812265383f, 0.249969517637753f, + 0.249896191210394f, 0.249822833053293f, 0.249749443236397f, + 0.249676021829606f, 0.249602568902779f, 0.249529084525730f, + 0.249455568768230f, 0.249382021700006f, 0.249308443390741f, + 0.249234833910075f, 0.249161193327604f, 0.249087521712880f, + 0.249013819135412f, 0.248940085664666f, 0.248866321370063f, + 0.248792526320980f, 0.248718700586753f, 0.248644844236672f, + 0.248570957339983f, 0.248497039965891f, 0.248423092183554f, + 0.248349114062088f, 0.248275105670567f, 0.248201067078019f, + 0.248126998353429f, 0.248052899565738f, 0.247978770783845f, + 0.247904612076603f, 0.247830423512822f, 0.247756205161271f, + 0.247681957090672f, 0.247607679369705f, 0.247533372067005f, + 0.247459035251165f, 0.247384668990733f, 0.247310273354215f, + 0.247235848410072f, 0.247161394226722f, 0.247086910872538f, + 0.247012398415851f, 0.246937856924948f, 0.242501558460495f, + 0.242426959105845f, 0.242352330921577f, 0.242277673975804f, + 0.242202988336594f, 0.242128274071971f, 0.242053531249917f, + 0.241978759938370f, 0.241903960205222f, 0.241829132118326f, + 0.241754275745486f, 0.241679391154467f, 0.241604478412987f, + 0.241529537588722f, 0.241454568749305f, 0.241379571962322f, + 0.241304547295320f, 0.241229494815798f, 0.241154414591216f, + 0.241079306688985f, 0.241004171176476f, 0.240929008121016f, + 0.240853817589887f, 0.240778599650329f, 0.240703354369537f, + 0.240628081814662f, 0.240552782052813f, 0.240477455151054f, + 0.240402101176407f, 0.240326720195847f, 0.240251312276309f, + 0.240175877484683f, 0.240100415887814f, 0.240024927552506f, + 0.239949412545517f, 0.239873870933562f, 0.239798302783312f, + 0.239722708161397f, 0.239647087134399f, 0.239571439768860f, + 0.239495766131276f, 0.239420066288100f, 0.239344340305743f, + 0.239268588250570f, 0.239192810188903f, 0.239117006187022f, + 0.239041176311160f, 0.238965320627509f, 0.238889439202216f, + 0.238813532101387f, 0.238737599391080f, 0.238661641137313f, + 0.238585657406058f, 0.238509648263246f, 0.238433613774760f, + 0.238357554006444f, 0.238281469024095f, 0.238205358893469f, + 0.238129223680276f, 0.238053063450183f, 0.237976878268814f, + 0.237900668201750f, 0.237824433314526f, 0.237748173672635f, + 0.237671889341525f, 0.237595580386603f, 0.237519246873230f, + 0.237442888866723f, 0.237366506432358f, 0.237290099635364f, + 0.237213668540929f, 0.237137213214196f, 0.237060733720264f, + 0.236984230124190f, 0.236907702490985f, 0.236831150885619f, + 0.236754575373016f, 0.236677976018057f, 0.236601352885581f, + 0.236524706040380f, 0.236448035547206f, 0.236371341470764f, + 0.236294623875718f, 0.236217882826687f, 0.236141118388246f, + 0.236064330624927f, 0.235987519601219f, 0.235910685381566f, + 0.235833828030369f, 0.235756947611985f, 0.235680044190727f, + 0.235603117830866f, 0.235526168596627f, 0.235449196552193f, + 0.235372201761703f, 0.235295184289252f, 0.235218144198891f, + 0.235141081554629f, 0.235063996420428f, 0.234986888860210f, + 0.234909758937852f, 0.234832606717186f, 0.234755432262002f, + 0.234678235636045f, 0.234601016903018f, 0.234523776126579f, + 0.234446513370342f, 0.234369228697878f, 0.234291922172716f, + 0.234214593858338f, 0.234137243818184f, 0.234059872115652f, + 0.233982478814092f, 0.233905063976814f, 0.233827627667084f, + 0.233750169948123f, 0.233672690883109f, 0.233595190535175f, + 0.233517668967413f, 0.233440126242869f, 0.233362562424547f, + 0.233284977575406f, 0.233207371758361f, 0.233129745036284f, + 0.233052097472005f, 0.232974429128308f, 0.232896740067933f, + 0.232819030353578f, 0.232741300047898f, 0.232663549213500f, + 0.232585777912953f, 0.232507986208779f, 0.232430174163456f, + 0.232352341839420f, 0.232274489299062f, 0.232196616604730f, + 0.227756995811151f, 0.227679082995740f, 0.227601150213137f, + 0.227523197525514f, 0.227445224995001f, 0.227367232683684f, + 0.227289220653605f, 0.227211188966762f, 0.227133137685110f, + 0.227055066870559f, 0.226976976584978f, 0.226898866890189f, + 0.226820737847972f, 0.226742589520064f, 0.226664421968157f, + 0.226586235253900f, 0.226508029438898f, 0.226429804584713f, + 0.226351560752862f, 0.226273298004819f, 0.226195016402015f, + 0.226116716005836f, 0.226038396877625f, 0.225960059078682f, + 0.225881702670262f, 0.225803327713576f, 0.225724934269794f, + 0.225646522400040f, 0.225568092165393f, 0.225489643626893f, + 0.225411176845531f, 0.225332691882258f, 0.225254188797980f, + 0.225175667653558f, 0.225097128509812f, 0.225018571427517f, + 0.224939996467403f, 0.224861403690158f, 0.224782793156426f, + 0.224704164926808f, 0.224625519061859f, 0.224546855622093f, + 0.224468174667977f, 0.224389476259939f, 0.224310760458359f, + 0.224232027323575f, 0.224153276915881f, 0.224074509295528f, + 0.223995724522724f, 0.223916922657630f, 0.223838103760367f, + 0.223759267891009f, 0.223680415109590f, 0.223601545476097f, + 0.223522659050475f, 0.223443755892626f, 0.223364836062405f, + 0.223285899619627f, 0.223206946624062f, 0.223127977135435f, + 0.223048991213429f, 0.222969988917683f, 0.222890970307791f, + 0.222811935443306f, 0.222732884383734f, 0.222653817188539f, + 0.222574733917142f, 0.222495634628919f, 0.222416519383203f, + 0.222337388239283f, 0.222258241256404f, 0.222179078493767f, + 0.217738172002954f, 0.217658977858233f, 0.217579768111098f, + 0.217500542820575f, 0.217421302045647f, 0.217342045845254f, + 0.217262774278292f, 0.217183487403611f, 0.217104185280022f, + 0.217024867966288f, 0.216945535521129f, 0.216866188003224f, + 0.216786825471206f, 0.216707447983663f, 0.216628055599143f, + 0.216548648376148f, 0.216469226373135f, 0.216389789648521f, + 0.216310338260676f, 0.216230872267927f, 0.216151391728558f, + 0.216071896700810f, 0.215992387242879f, 0.215912863412916f, + 0.215833325269032f, 0.215753772869290f, 0.215674206271714f, + 0.215594625534279f, 0.215515030714921f, 0.215435421871530f, + 0.215355799061952f, 0.215276162343990f, 0.215196511775403f, + 0.215116847413907f, 0.215037169317173f, 0.214957477542830f, + 0.214877772148461f, 0.214798053191607f, 0.214718320729766f, + 0.214638574820389f, 0.214558815520887f, 0.214479042888626f, + 0.214399256980927f, 0.214319457855068f, 0.214239645568284f, + 0.214159820177766f, 0.214079981740661f, 0.214000130314072f, + 0.213920265955059f, 0.213840388720638f, 0.213760498667781f, + 0.213680595853417f, 0.213600680334430f, 0.213520752167662f, + 0.213440811409909f, 0.213360858117925f, 0.213280892348421f, + 0.213200914158062f, 0.213120923603470f, 0.208679192733648f, + 0.208599177620285f, 0.208519150312294f, 0.208439110866124f, + 0.208359059338178f, 0.208278995784815f, 0.208198920262354f, + 0.208118832827066f, 0.208038733535179f, 0.207958622442880f, + 0.207878499606310f, 0.207798365081566f, 0.207718218924703f, + 0.207638061191730f, 0.207557891938615f, 0.207477711221280f, + 0.207397519095604f, 0.207317315617423f, 0.207237100842528f, + 0.207156874826667f, 0.207076637625544f, 0.206996389294820f, + 0.206916129890112f, 0.206835859466992f, 0.206755578080990f, + 0.206675285787592f, 0.206594982642238f, 0.206514668700328f, + 0.206434344017215f, 0.206354008648211f, 0.206273662648582f, + 0.206193306073550f, 0.206112938978297f, 0.206032561417956f, + 0.205952173447621f, 0.205871775122340f, 0.205791366497116f, + 0.205710947626911f, 0.205630518566642f, 0.205550079371182f, + 0.205469630095361f, 0.205389170793964f, 0.205308701521733f, + 0.205228222333368f, 0.205147733283521f, 0.205067234426806f, + 0.204986725817787f, 0.204906207510990f, 0.204825679560893f, + 0.204745142021933f, 0.204664594948501f, 0.200222310387369f, + 0.200141744407996f, 0.200061169057067f, 0.199980584388797f, + 0.199899990457361f, 0.199819387316889f, 0.199738775021466f, + 0.199658153625134f, 0.199577523181893f, 0.199496883745697f, + 0.199416235370458f, 0.199335578110042f, 0.199254912018273f, + 0.199174237148931f, 0.199093553555752f, 0.199012861292428f, + 0.198932160412609f, 0.198851450969898f, 0.198770733017858f, + 0.198690006610006f, 0.198609271799814f, 0.198528528640714f, + 0.198447777186091f, 0.198367017489288f, 0.198286249603603f, + 0.198205473582292f, 0.198124689478565f, 0.198043897345590f, + 0.197963097236492f, 0.197882289204349f, 0.197801473302198f, + 0.197720649583031f, 0.197639818099798f, 0.197558978905402f, + 0.197478132052707f, 0.197397277594528f, 0.197316415583640f, + 0.197235546072773f, 0.197154669114612f, 0.197073784761801f, + 0.196992893066939f, 0.196911994082579f, 0.196831087861235f, + 0.196750174455372f, 0.196669253917415f, 0.196588326299744f, + 0.196507391654695f, 0.196426450034560f, 0.191983773484012f, + 0.191902818070409f, 0.191821855838336f, 0.191740886839911f, + 0.191659911127206f, 0.191578928752253f, 0.191497939767036f, + 0.191416944223500f, 0.191335942173542f, 0.191254933669018f, + 0.191173918761738f, 0.191092897503471f, 0.191011869945940f, + 0.190930836140826f, 0.190849796139763f, 0.190768749994346f, + 0.190687697756122f, 0.190606639476596f, 0.190525575207231f, + 0.190444504999443f, 0.190363428904605f, 0.190282346974049f, + 0.190201259259060f, 0.190120165810881f, 0.190039066680710f, + 0.189957961919703f, 0.189876851578970f, 0.189795735709579f, + 0.189714614362553f, 0.189633487588873f, 0.189552355439475f, + 0.189471217965251f, 0.189390075217050f, 0.189308927245676f, + 0.189227774101891f, 0.189146615836412f, 0.189065452499913f, + 0.188984284143024f, 0.188903110816330f, 0.188821932570374f, + 0.188740749455655f, 0.188659561522627f, 0.188578368821702f, + 0.188497171403246f, 0.188415969317584f, 0.188334762614995f, + 0.183891823338137f, 0.183810607552358f, 0.183729387300228f, + 0.183648162631853f, 0.183566933597294f, 0.183485700246567f, + 0.183404462629646f, 0.183323220796461f, 0.183241974796898f, + 0.183160724680798f, 0.183079470497960f, 0.182998212298140f, + 0.182916950131046f, 0.182835684046347f, 0.182754414093667f, + 0.182673140322583f, 0.182591862782633f, 0.182510581523309f, + 0.182429296594058f, 0.182348008044284f, 0.182266715923350f, + 0.182185420280571f, 0.182104121165221f, 0.182022818626530f, + 0.181941512713682f, 0.181860203475820f, 0.181778890962042f, + 0.181697575221403f, 0.181616256302912f, 0.181534934255536f, + 0.181453609128199f, 0.181372280969781f, 0.181290949829115f, + 0.181209615754995f, 0.181128278796168f, 0.181046939001338f, + 0.180965596419165f, 0.180884251098267f, 0.180802903087216f, + 0.180721552434541f, 0.180640199188728f, 0.180558843398218f, + 0.180477485111409f, 0.180396124376655f, 0.175953033234688f, + 0.175871667748931f, 0.175790299960027f, 0.175708929916157f, + 0.175627557665455f, 0.175546183256013f, 0.175464806735878f, + 0.175383428153054f, 0.175302047555502f, 0.175220664991136f, + 0.175139280507831f, 0.175057894153414f, 0.174976505975670f, + 0.174895116022341f, 0.174813724341124f, 0.174732330979673f, + 0.174650935985597f, 0.174569539406463f, 0.174488141289793f, + 0.174406741683064f, 0.174325340633713f, 0.174243938189129f, + 0.174162534396661f, 0.174081129303611f, 0.173999722957239f, + 0.173918315404761f, 0.173836906693349f, 0.173755496870131f, + 0.173674085982191f, 0.173592674076571f, 0.173511261200267f, + 0.173429847400233f, 0.173348432723377f, 0.173267017216566f, + 0.173185600926620f, 0.173104183900319f, 0.173022766184396f, + 0.172941347825542f, 0.172859928870403f, 0.172778509365582f, + 0.172697089357639f, 0.172615668893089f, 0.172534248018403f, + 0.172452826780009f, 0.168009677216713f, 0.167928255390010f, + 0.167846833338621f, 0.167765411108796f, 0.167683988746745f, + 0.167602566298634f, 0.167521143810582f, 0.167439721328668f, + 0.167358298898926f, 0.167276876567344f, 0.167195454379870f, + 0.167114032382406f, 0.167032610620809f, 0.166951189140896f, + 0.166869767988436f, 0.166788347209158f, 0.166706926848743f, + 0.166625506952833f, 0.166544087567022f, 0.166462668736864f, + 0.166381250507865f, 0.166299832925490f, 0.166218416035160f, + 0.166136999882253f, 0.166055584512100f, 0.165974169969991f, + 0.165892756301173f, 0.165811343550845f, 0.165729931764167f, + 0.165648520986252f, 0.165567111262170f, 0.165485702636949f, + 0.165404295155570f, 0.165322888862974f, 0.165241483804054f, + 0.165160080023663f, 0.165078677566607f, 0.164997276477651f, + 0.164915876801515f, 0.164834478582874f, 0.164753081866361f, + 0.164671686696565f, 0.164590293118030f, 0.164508901175258f, + 0.164427510912706f, 0.159984394367208f, 0.159903007598291f, + 0.159821622642703f, 0.159740239544724f, 0.159658858348595f, + 0.159577479098508f, 0.159496101838615f, 0.159414726613023f, + 0.159333353465794f, 0.159251982440948f, 0.159170613582459f, + 0.159089246934261f, 0.159007882540240f, 0.158926520444241f, + 0.158845160690063f, 0.158763803321464f, 0.158682448382156f, + 0.158601095915807f, 0.158519745966043f, 0.158438398576445f, + 0.158357053790551f, 0.158275711651853f, 0.158194372203803f, + 0.158113035489805f, 0.158031701553222f, 0.157950370437373f, + 0.157869042185531f, 0.157787716840929f, 0.157706394446753f, + 0.157625075046145f, 0.157543758682207f, 0.157462445397992f, + 0.157381135236513f, 0.157299828240738f, 0.157218524453592f, + 0.157137223917953f, 0.157055926676660f, 0.156974632772505f, + 0.156893342248236f, 0.156812055146560f, 0.156730771510136f, + 0.156649491381584f, 0.156568214803476f, 0.156486941818342f, + 0.156405672468670f, 0.156324406796900f, 0.151881416837854f, + 0.151800158649042f, 0.151718904265197f, 0.151637653728586f, + 0.151556407081432f, 0.151475164365916f, 0.151393925624172f, + 0.151312690898293f, 0.151231460230326f, 0.151150233662276f, + 0.151069011236103f, 0.150987792993723f, 0.150906578977011f, + 0.150825369227794f, 0.150744163787857f, 0.150662962698943f, + 0.150581766002748f, 0.150500573740927f, 0.150419385955089f, + 0.150338202686801f, 0.150257023977585f, 0.150175849868919f, + 0.150094680402237f, 0.150013515618932f, 0.149932355560350f, + 0.149851200267794f, 0.149770049782523f, 0.149688904145754f, + 0.149607763398657f, 0.149526627582362f, 0.149445496737952f, + 0.149364370906467f, 0.149283250128905f, 0.149202134446217f, + 0.149121023899313f, 0.149039918529058f, 0.148958818376273f, + 0.148877723481736f, 0.148796633886180f, 0.148715549630295f, + 0.148634470754727f, 0.148553397300078f, 0.148472329306907f, + 0.148391266815728f, 0.148310209867012f, 0.148229158501186f, + 0.148148112758633f, 0.148067072679692f, 0.143624310297081f, + 0.143543281666207f, 0.143462258819700f, 0.143381241797725f, + 0.143300230640400f, 0.143219225387804f, 0.143138226079968f, + 0.143057232756881f, 0.142976245458488f, 0.142895264224689f, + 0.142814289095343f, 0.142733320110263f, 0.142652357309218f, + 0.142571400731934f, 0.142490450418093f, 0.142409506407333f, + 0.142328568739249f, 0.142247637453390f, 0.142166712589265f, + 0.142085794186334f, 0.142004882284019f, 0.141923976921693f, + 0.141843078138688f, 0.141762185974292f, 0.141681300467748f, + 0.141600421658257f, 0.141519549584974f, 0.141438684287012f, + 0.141357825803438f, 0.141276974173279f, 0.141196129435513f, + 0.141115291629079f, 0.141034460792870f, 0.140953636965734f, + 0.140872820186478f, 0.140792010493862f, 0.140711207926605f, + 0.140630412523381f, 0.140549624322819f, 0.140468843363506f, + 0.140388069683985f, 0.140307303322754f, 0.140226544318269f, + 0.140145792708939f, 0.140065048533132f, 0.139984311829173f, + 0.139903582635339f, 0.139822860989867f, 0.139742146930948f, + 0.139661440496731f, 0.139580741725320f, 0.139500050654775f, + 0.139419367323112f, 0.134976963760728f, 0.134896296020705f, + 0.134815636133351f, 0.134734984136508f, 0.134654340067973f, + 0.134573703965498f, 0.134493075866795f, 0.134412455809528f, + 0.134331843831321f, 0.134251239969750f, 0.134170644262351f, + 0.134090056746613f, 0.134009477459984f, 0.133928906439867f, + 0.133848343723620f, 0.133767789348558f, 0.133687243351954f, + 0.133606705771033f, 0.133526176642981f, 0.133445656004937f, + 0.133365143893997f, 0.133284640347212f, 0.133204145401592f, + 0.133123659094101f, 0.133043181461659f, 0.132962712541143f, + 0.132882252369387f, 0.132801800983178f, 0.132721358419264f, + 0.132640924714344f, 0.132560499905078f, 0.132480084028077f, + 0.132399677119913f, 0.132319279217111f, 0.132238890356154f, + 0.132158510573480f, 0.132078139905483f, 0.131997778388515f, + 0.131917426058882f, 0.131837082952847f, 0.131756749106629f, + 0.131676424556405f, 0.131596109338304f, 0.131515803488415f, + 0.131435507042783f, 0.131355220037405f, 0.131274942508240f, + 0.131194674491199f, 0.131114416022150f, 0.131034167136919f, + 0.130953927871285f, 0.130873698260986f, 0.130793478341716f, + 0.130713268149122f, 0.130633067718811f, 0.130552877086344f, + 0.130472696287238f, 0.130392525356968f, 0.130312364330964f, + 0.130232213244611f, 0.130152072133252f, 0.130071941032186f, + 0.125630091969089f, 0.125549980994328f, 0.125469880135491f, + 0.125389789427702f, 0.125309708906040f, 0.125229638605540f, + 0.125149578561194f, 0.125069528807950f, 0.124989489380710f, + 0.124909460314337f, 0.124829441643644f, 0.124749433403405f, + 0.124669435628348f, 0.124589448353158f, 0.124509471612476f, + 0.124429505440897f, 0.124349549872976f, 0.124269604943221f, + 0.124189670686097f, 0.124109747136027f, 0.124029834327388f, + 0.123949932294513f, 0.123870041071692f, 0.123790160693172f, + 0.123710291193154f, 0.123630432605798f, 0.123550584965217f, + 0.123470748305482f, 0.123390922660619f, 0.123311108064613f, + 0.123231304551401f, 0.123151512154880f, 0.123071730908899f, + 0.122991960847268f, 0.122912202003750f, 0.122832454412063f, + 0.122752718105885f, 0.122672993118847f, 0.122593279484538f, + 0.122513577236502f, 0.122433886408239f, 0.122354207033207f, + 0.122274539144817f, 0.122194882776439f, 0.122115237961398f, + 0.122035604732975f, 0.121955983124407f, 0.121876373168889f, + 0.121796774899569f, 0.121717188349554f, 0.121637613551904f, + 0.121558050539640f, 0.121478499345734f, 0.121398960003116f, + 0.121319432544675f, 0.121239917003251f, 0.121160413411644f, + 0.121080921802609f, 0.121001442208857f, 0.120921974663054f, + 0.120842519197825f, 0.120763075845748f, 0.120683644639360f, + 0.120604225611152f, 0.120524818793572f, 0.120445424219023f, + 0.120366041919867f, 0.120286671928419f, 0.120207314276951f, + 0.120127968997693f, 0.120048636122829f, 0.119969315684500f, + 0.119890007714802f, 0.119810712245789f, 0.119731429309471f, + 0.119652158937811f, 0.119572901162733f, 0.119493656016113f, + 0.119414423529785f, 0.119335203735540f, 0.114894268657545f, + 0.114815074342658f, 0.114735892814960f, 0.114656724106065f, + 0.114577568247544f, 0.114498425270923f, 0.114419295207686f, + 0.114340178089270f, 0.114261073947072f, 0.114181982812442f, + 0.114102904716688f, 0.114023839691073f, 0.113944787766818f, + 0.113865748975096f, 0.113786723347042f, 0.113707710913742f, + 0.113628711706240f, 0.113549725755538f, 0.113470753092591f, + 0.113391793748312f, 0.113312847753569f, 0.113233915139187f, + 0.113154995935948f, 0.113076090174588f, 0.112997197885800f, + 0.112918319100234f, 0.112839453848495f, 0.112760602161145f, + 0.112681764068701f, 0.112602939601637f, 0.112524128790383f, + 0.112445331665325f, 0.112366548256805f, 0.112287778595122f, + 0.112209022710530f, 0.112130280633239f, 0.112051552393417f, + 0.111972838021186f, 0.111894137546624f, 0.111815450999768f, + 0.111736778410607f, 0.111658119809091f, 0.111579475225121f, + 0.111500844688557f, 0.111422228229216f, 0.111343625876868f, + 0.111265037661242f, 0.111186463612022f, 0.111107903758848f, + 0.111029358131316f, 0.110950826758979f, 0.110872309671346f, + 0.110793806897879f, 0.110715318468002f, 0.110636844411090f, + 0.110558384756477f, 0.110479939533451f, 0.110401508771258f, + 0.110323092499099f, 0.110244690746132f, 0.110166303541471f, + 0.110087930914184f, 0.110009572893298f, 0.109931229507795f, + 0.109852900786614f, 0.109774586758647f, 0.109696287452746f, + 0.109618002897716f, 0.109539733122321f, 0.109461478155279f, + 0.109383238025265f, 0.109305012760910f, 0.109226802390801f, + 0.109148606943480f, 0.109070426447448f, 0.108992260931160f, + 0.108914110423026f, 0.108835974951415f, 0.108757854544651f, + 0.108679749231012f, 0.108601659038736f, 0.108523583996014f, + 0.108445524130995f, 0.108367479471782f, 0.108289450046436f, + 0.108211435882973f, 0.108133437009367f, 0.108055453453546f, + 0.107977485243395f, 0.107899532406754f, 0.107821594971421f, + 0.107743672965149f, 0.107665766415648f, 0.107587875350582f, + 0.107509999797574f, 0.107432139784200f, 0.107354295337995f, + 0.107276466486449f, 0.107198653257007f, 0.107120855677072f, + 0.107043073774001f, 0.106965307575109f, 0.106887557107667f, + 0.106809822398901f, 0.106732103475993f, 0.106654400366083f, + 0.106576713096265f, 0.106499041693590f, 0.106421386185065f, + 0.106343746597653f, 0.106266122958274f, 0.106188515293803f, + 0.106110923631072f, 0.106033347996867f, 0.105955788417934f, + 0.105878244920971f, 0.105800717532635f, 0.105723206279537f, + 0.105645711188245f, 0.105568232285285f, 0.105490769597136f, + 0.105413323150235f, 0.105335892970973f, 0.105258479085701f, + 0.105181081520723f, 0.105103700302298f, 0.105026335456646f, + 0.104948987009938f, 0.104871654988304f, 0.104794339417828f, + 0.104717040324553f, 0.104639757734476f, 0.104562491673551f, + 0.104485242167686f, 0.104408009242749f, 0.104330792924560f, + 0.104253593238899f, 0.104176410211498f, 0.104099243868048f, + 0.104022094234196f, 0.103944961335543f, 0.103867845197649f, + 0.103790745846028f, 0.103713663306151f, 0.103636597603444f, + 0.103559548763290f, 0.103482516811028f, 0.103405501771954f, + 0.103328503671319f, 0.103251522534330f, 0.103174558386149f, + 0.103097611251898f, 0.103020681156651f, 0.102943768125441f, + 0.102866872183254f, 0.102789993355035f, 0.102713131665683f, + 0.102636287140056f, 0.102559459802964f, 0.102482649679177f, + 0.102405856793418f, 0.102329081170369f, 0.102252322834665f, + 0.102175581810900f, 0.102098858123621f, 0.102022151797335f, + 0.101945462856502f, 0.101868791325538f, 0.101792137228817f, + 0.101715500590669f, 0.101638881435378f, 0.101562279787186f, + 0.101485695670291f, 0.101409129108846f, 0.101332580126960f, + 0.101256048748701f, 0.101179534998088f, 0.101103038899101f, + 0.101026560475674f, 0.100950099751696f, 0.100873656751014f, + 0.100797231497430f, 0.100720824014703f, 0.100644434326547f, + 0.100568062456633f, 0.100491708428587f, 0.100415372265992f, + 0.100339053992388f, 0.100262753631269f, 0.100186471206086f, + 0.100110206740246f, 0.100033960257113f, 0.0999577317800052f, + 0.0998815213321992f, 0.0998053289369260f, 0.0997291546173731f, + 0.0996529983966844f, 0.0995768602979594f, 0.0995007403442542f, + 0.0994246385585807f, 0.0993485549639068f, 0.0992724895831567f, + 0.0991964424392106f, 0.0991204135549048f, 0.0990444029530317f, + 0.0989684106563397f, 0.0988924366875334f, 0.0988164810692734f, + 0.0987405438241764f, 0.0986646249748153f, 0.0985887245437189f, + 0.0985128425533722f, 0.0984369790262163f, 0.0983611339846482f, + 0.0982853074510214f, 0.0982094994476449f, 0.0981337099967844f, + 0.0980579391206610f, 0.0979821868414527f, 0.0979064531812928f, + 0.0978307381622712f, 0.0977550418064336f, 0.0976793641357820f, + 0.0976037051722743f, 0.0975280649378246f, 0.0974524434543029f, + 0.0973768407435356f, 0.0973012568273050f, 0.0972256917273493f, + 0.0971501454653631f, 0.0970746180629969f, 0.0969991095418574f, + 0.0969236199235071f, 0.0968481492294650f, 0.0967726974812058f, + 0.0966972647001606f, 0.0966218509077163f, 0.0965464561252159f, + 0.0964710803739588f, 0.0963957236752001f, 0.0963203860501512f, + 0.0962450675199795f, 0.0961697681058085f, 0.0960944878287176f, + 0.0960192267097427f, 0.0959439847698754f, 0.0958687620300635f, + 0.0957935585112108f, 0.0957183742341774f, 0.0956432092197791f, + 0.0955680634887883f, 0.0954929370619329f, 0.0954178299598974f, + 0.0953427422033218f, 0.0952676738128029f, 0.0951926248088929f, + 0.0951175952121004f, 0.0950425850428900f, 0.0949675943216825f, + 0.0948926230688546f, 0.0948176713047392f, 0.0947427390496252f, + 0.0946678263237576f, 0.0945929331473374f, 0.0945180595405218f, + 0.0944432055234240f, 0.0943683711161133f, 0.0942935563386150f, + 0.0942187612109106f, 0.0941439857529376f, 0.0940692299845894f, + 0.0939944939257160f, 0.0939197775961227f, 0.0938450810155716f, + 0.0937704042037804f, 0.0936957471804231f, 0.0936211099651296f, + 0.0935464925774860f, 0.0934718950370345f, 0.0933973173632733f, + 0.0933227595756565f, 0.0932482216935947f, 0.0931737037364541f, + 0.0930992057235573f, 0.0930247276741828f, 0.0929502696075653f, + 0.0928758315428953f, 0.0928014134993197f, 0.0927270154959413f, + 0.0926526375518190f, 0.0925782796859676f, 0.0925039419173584f, + 0.0924296242649182f, 0.0923553267475303f, 0.0922810493840339f, + 0.0922067921932243f, 0.0921325551938528f, 0.0920583384046269f, + 0.0919841418442100f, 0.0919099655312217f, 0.0918358094842375f, + 0.0917616737217892f, 0.0916875582623645f, 0.0916134631244072f, + 0.0915393883263172f, 0.0914653338864503f, 0.0913912998231188f, + 0.0913172861545903f, 0.0912432928990893f, 0.0911693200747959f, + 0.0910953676998462f, 0.0910214357923326f, 0.0909475243703036f, + 0.0908736334517634f, 0.0907997630546726f, 0.0907259131969478f, + 0.0906520838964616f, 0.0905782751710426f, 0.0905044870384756f, + 0.0904307195165015f, 0.0903569726228168f, 0.0902832463750749f, + 0.0902095407908844f, 0.0901358558878106f, 0.0900621916833744f, + 0.0899885481950531f, 0.0899149254402798f, 0.0898413234364439f, + 0.0897677422008906f, 0.0896941817509215f, 0.0896206421037937f, + 0.0895471232767210f, 0.0894736252868730f, 0.0894001481513751f, + 0.0893266918873091f, 0.0892532565117127f, 0.0891798420415798f, + 0.0891064484938601f, 0.0890330758854596f, 0.0889597242332403f, + 0.0888863935540201f, 0.0888130838645731f, 0.0887397951816295f, + 0.0886665275218755f, 0.0885932809019533f, 0.0885200553384611f, + 0.0884468508479535f, 0.0883736674469406f, 0.0883005051518891f, + 0.0882273639792214f, 0.0881542439453161f, 0.0880811450665078f, + 0.0880080673590872f, 0.0879350108393010f, 0.0878619755233519f, + 0.0877889614273989f, 0.0877159685675568f, 0.0876429969598966f, + 0.0875700466204452f, 0.0874971175651857f, 0.0874242098100572f, + 0.0873513233709547f, 0.0872784582637296f, 0.0872056145041890f, + 0.0871327921080962f, 0.0870599910911707f, 0.0869872114690878f, + 0.0869144532574789f, 0.0868417164719315f, 0.0867690011279892f, + 0.0866963072411517f, 0.0866236348268744f, 0.0865509839005692f, + 0.0864783544776037f, 0.0864057465733018f, 0.0863331602029433f, + 0.0862605953817640f, 0.0861880521249560f, 0.0861155304476673f, + 0.0860430303650017f, 0.0859705518920195f, 0.0858980950437366f, + 0.0858256598351254f, 0.0857532462811140f, 0.0856808543965868f, + 0.0856084841963839f, 0.0855361356953018f, 0.0854638089080928f, + 0.0853915038494655f, 0.0853192205340843f, 0.0852469589765697f, + 0.0851747191914984f, 0.0851025011934029f, 0.0850303049967719f, + 0.0849581306160502f, 0.0848859780656385f, 0.0848138473598935f, + 0.0847417385131283f, 0.0846696515396116f, 0.0845975864535684f, + 0.0845255432691795f, 0.0844535220005823f, 0.0843815226618695f, + 0.0843095452670904f, 0.0842375898302500f, 0.0841656563653097f, + 0.0840937448861865f, 0.0840218554067539f, 0.0839499879408410f, + 0.0838781425022332f, 0.0838063191046720f, 0.0837345177618548f, + 0.0836627384874350f, 0.0835909812950222f, 0.0835192461981819f, + 0.0834475332104356f, 0.0833758423452611f, 0.0833041736160921f, + 0.0832325270363181f, 0.0831609026192851f, 0.0830893003782947f, + 0.0830177203266048f, 0.0829461624774291f, 0.0828746268439379f, + 0.0828031134392568f, 0.0827316222764678f, 0.0826601533686091f, + 0.0825887067286746f, 0.0825172823696144f, 0.0824458803043347f, + 0.0823745005456977f, 0.0823031431065214f, 0.0822318079995802f, + 0.0821604952376043f, 0.0820892048332801f, 0.0820179367992499f, + 0.0819466911481119f, 0.0818754678924208f, 0.0818042670446868f, + 0.0817330886173767f, 0.0816619326229126f, 0.0815907990736732f, + 0.0815196879819933f, 0.0814485993601633f, 0.0813775332204299f, + 0.0813064895749958f, 0.0812354684360197f, 0.0811644698156163f, + 0.0810934937258564f, 0.0810225401787669f, 0.0809516091863306f, + 0.0808807007604862f, 0.0808098149131290f, 0.0807389516561096f, + 0.0806681110012352f, 0.0805972929602686f, 0.0805264975449289f, + 0.0804557247668912f, 0.0803849746377866f, 0.0803142471692022f, + 0.0802435423726812f, 0.0801728602597226f, 0.0801022008417819f, + 0.0800315641302702f, 0.0799609501365547f, 0.0798903588719589f, + 0.0798197903477619f, 0.0797492445751992f, 0.0796787215654622f, + 0.0796082213296982f, 0.0795377438790107f, 0.0794672892244592f, + 0.0793968573770592f, 0.0793264483477822f, 0.0792560621475557f, + 0.0791856987872634f, 0.0791153582777449f, 0.0790450406297956f, + 0.0789747458541675f, 0.0789044739615680f, 0.0788342249626610f, + 0.0787639988680661f, 0.0786937956883591f, 0.0786236154340719f, + 0.0785534581156922f, 0.0784833237436637f, 0.0784132123283866f, + 0.0783431238802165f, 0.0782730584094654f, 0.0782030159264013f, + 0.0781329964412480f, 0.0780629999641855f, 0.0779930265053501f, + 0.0779230760748334f, 0.0778531486826837f, 0.0777832443389050f, + 0.0777133630534574f, 0.0776435048362570f, 0.0775736696971760f, + 0.0775038576460424f, 0.0774340686926407f, 0.0773643028467107f, + 0.0772945601179488f, 0.0772248405160073f, 0.0771551440504945f, + 0.0770854707309746f, 0.0770158205669677f, 0.0769461935679505f, + 0.0768765897433551f, 0.0768070091025700f, 0.0767374516549394f, + 0.0766679174097639f, 0.0765984063762999f, 0.0765289185637596f, + 0.0764594539813117f, 0.0763900126380807f, 0.0763205945431470f, + 0.0762511997055470f, 0.0761818281342734f, 0.0761124798382747f, + 0.0760431548264555f, 0.0759738531076762f, 0.0759045746907537f, + 0.0758353195844604f, 0.0757660877975250f, 0.0756968793386320f, + 0.0756276942164223f, 0.0755585324394925f, 0.0754893940163953f, + 0.0754202789556392f, 0.0753511872656892f, 0.0752821189549660f, + 0.0752130740318462f, 0.0751440525046627f, 0.0750750543817043f, + 0.0750060796712157f, 0.0749371283813978f, 0.0748682005204073f, + 0.0747992960963572f, 0.0747304151173163f, 0.0746615575913094f, + 0.0745927235263174f, 0.0745239129302773f, 0.0744551258110818f, + 0.0743863621765800f, 0.0699558940269990f, 0.0698871773852553f, + 0.0698184842514879f, 0.0697498146333701f, 0.0696811685385305f, + 0.0696125459745543f, 0.0695439469489825f, 0.0694753714693121f, + 0.0694068195429960f, 0.0693382911774433f, 0.0692697863800191f, + 0.0692013051580444f, 0.0691328475187962f, 0.0690644134695077f, + 0.0689960030173678f, 0.0689276161695217f, 0.0688592529330704f, + 0.0687909133150711f, 0.0687225973225368f, 0.0686543049624368f, + 0.0685860362416959f, 0.0685177911671956f, 0.0684495697457728f, + 0.0683813719842208f, 0.0683131978892886f, 0.0682450474676814f, + 0.0681769207260604f, 0.0681088176710428f, 0.0680407383092017f, + 0.0679726826470663f, 0.0679046506911219f, 0.0678366424478096f, + 0.0677686579235266f, 0.0677006971246261f, 0.0676327600574174f, + 0.0675648467281656f, 0.0674969571430920f, 0.0674290913083737f, + 0.0673612492301441f, 0.0672934309144923f, 0.0672256363674636f, + 0.0671578655950593f, 0.0670901186032365f, 0.0670223953979085f, + 0.0669546959849446f, 0.0668870203701700f, 0.0668193685593660f, + 0.0667517405582697f, 0.0666841363725746f, 0.0666165560079299f, + 0.0665489994699407f, 0.0664814667641684f, 0.0664139578961303f, + 0.0663464728712994f, 0.0662790116951054f, 0.0662115743729332f, + 0.0661441609101242f, 0.0660767713119756f, 0.0660094055837408f, + 0.0659420637306289f, 0.0658747457578054f, 0.0658074516703914f, + 0.0657401814734641f, 0.0656729351720569f, 0.0656057127711590f, + 0.0655385142757157f, 0.0654713396906282f, 0.0654041890207538f, + 0.0653370622709057f, 0.0652699594458532f, 0.0652028805503216f, + 0.0651358255889920f, 0.0650687945665018f, 0.0650017874874442f, + 0.0649348043563684f, 0.0648678451777795f, 0.0648009099561390f, + 0.0647339986958640f, 0.0646671114013277f, 0.0646002480768593f, + 0.0645334087267441f, 0.0644665933552233f, 0.0643998019664940f, + 0.0643330345647095f, 0.0642662911539789f, 0.0641995717383674f, + 0.0641328763218963f, 0.0640662049085427f, 0.0639995575022397f, + 0.0639329341068766f, 0.0638663347262984f, 0.0637997593643063f, + 0.0637332080246576f, 0.0636666807110652f, 0.0636001774271983f, + 0.0635336981766821f, 0.0634672429630977f, 0.0634008117899821f, + 0.0633344046608284f, 0.0632680215790859f, 0.0632016625481595f, + 0.0631353275714102f, 0.0630690166521551f, 0.0630027297936674f, + 0.0629364669991760f, 0.0628702282718661f, 0.0628040136148784f, + 0.0627378230313101f, 0.0626716565242143f, 0.0626055140965999f, + 0.0625393957514318f, 0.0624733014916310f, 0.0624072313200746f, + 0.0623411852395954f, 0.0622751632529823f, 0.0622091653629804f, + 0.0621431915722904f, 0.0620772418835694f, 0.0620113162994302f, + 0.0619454148224416f, 0.0618795374551285f, 0.0618136841999718f, + 0.0617478550594083f, 0.0616820500358309f, 0.0616162691315882f, + 0.0615505123489852f, 0.0614847796902825f, 0.0614190711576971f, + 0.0613533867534016f, 0.0612877264795248f, 0.0612220903381512f, + 0.0611564783313218f, 0.0610908904610332f, 0.0610253267292380f, + 0.0609597871378448f, 0.0608942716887185f, 0.0608287803836795f, + 0.0607633132245045f, 0.0606978702129261f, 0.0606324513506329f, + 0.0605670566392694f, 0.0605016860804361f, 0.0604363396756896f, + 0.0603710174265425f, 0.0603057193344631f, 0.0602404454008760f, + 0.0601751956271616f, 0.0601099700146564f, 0.0600447685646528f, + 0.0599795912783991f, 0.0599144381570998f, 0.0598493092019153f, + 0.0597842044139619f, 0.0597191237943117f, 0.0596540673439933f, + 0.0595890350639909f, 0.0595240269552448f, 0.0594590430186511f, + 0.0593940832550622f, 0.0593291476652861f, 0.0592642362500872f, + 0.0591993490101854f, 0.0591344859462571f, 0.0590696470589343f, + 0.0590048323488052f, 0.0589400418164136f, 0.0588752754622598f, + 0.0588105332867999f, 0.0587458152904457f, 0.0586811214735651f, + 0.0586164518364824f, 0.0585518063794773f, 0.0584871851027858f, + 0.0584225880065996f, 0.0583580150910669f, 0.0582934663562913f, + 0.0582289418023328f, 0.0581644414292068f, 0.0580999652368856f, + 0.0580355132252967f, 0.0579710853943238f, 0.0579066817438065f, + 0.0578423022735407f, 0.0577779469832779f, 0.0577136158727258f, + 0.0576493089415478f, 0.0575850261893637f, 0.0575207676157490f, + 0.0574565332202351f, 0.0573923230023095f, 0.0573281369614158f, + 0.0572639750969533f, 0.0571998374082775f, 0.0571357238946997f, + 0.0570716345554873f, 0.0570075693898635f, 0.0569435283970079f, + 0.0568795115760553f, 0.0568155189260974f, 0.0567515504461811f, + 0.0566876061353098f, 0.0566236859924424f, 0.0565597900164942f, + 0.0564959182063363f, 0.0564320705607957f, 0.0563682470786554f, + 0.0563044477586545f, 0.0562406725994879f, 0.0561769215998064f, + 0.0561131947582173f, 0.0560494920732831f, 0.0559858135435229f, + 0.0559221591674113f, 0.0558585289433793f, 0.0557949228698135f, + 0.0557313409450567f, 0.0556677831674074f, 0.0556042495351205f, + 0.0555407400464066f, 0.0554772546994322f, 0.0554137934923198f, + 0.0553503564231481f, 0.0552869434899515f, 0.0552235546907205f, + 0.0551601900234014f, 0.0550968494858967f, 0.0550335330760648f, + 0.0549702407917199f, 0.0549069726306323f, 0.0548437285905283f, + 0.0547805086690902f, 0.0547173128639560f, 0.0546541411727200f, + 0.0545909935929322f, 0.0545278701220988f, 0.0544647707576817f, + 0.0544016954970989f, 0.0543386443377246f, 0.0542756172768886f, + 0.0542126143118766f, 0.0541496354399307f, 0.0540866806582487f, + 0.0540237499639842f, 0.0539608433542472f, 0.0538979608261031f, + 0.0538351023765739f, 0.0537722680026371f, 0.0537094577012262f, + 0.0536466714692308f, 0.0535839093034965f, 0.0535211712008247f, + 0.0534584571579729f, 0.0533957671716543f, 0.0533331012385385f, + 0.0532704593552508f, 0.0532078415183724f, 0.0531452477244405f, + 0.0530826779699484f, 0.0530201322513452f, 0.0529576105650360f, + 0.0528951129073819f, 0.0528326392746999f, 0.0527701896632632f, + 0.0527077640693004f, 0.0526453624889966f, 0.0525829849184928f, + 0.0525206313538857f, 0.0524583017912279f, 0.0523959962265285f, + 0.0523337146557520f, 0.0522714570748190f, 0.0522092234796062f, + 0.0521470138659462f, 0.0520848282296275f, 0.0520226665663946f, + 0.0519605288719478f, 0.0518984151419437f, 0.0518363253719946f, + 0.0517742595576687f, 0.0517122176944903f, 0.0516501997779397f, + 0.0515882058034530f, 0.0515262357664224f, 0.0514642896621959f, + 0.0514023674860775f, 0.0513404692333274f, 0.0512785948991613f, + 0.0512167444787511f, 0.0511549179672250f, 0.0510931153596663f, + 0.0510313366511152f, 0.0509695818365671f, 0.0509078509109738f, + 0.0508461438692430f, 0.0507844607062381f, 0.0507228014167787f, + 0.0506611659956403f, 0.0505995544375543f, 0.0505379667372080f, + 0.0504764028892448f, 0.0504148628882640f, 0.0503533467288209f, + 0.0502918544054265f, 0.0502303859125480f, 0.0501689412446085f, + 0.0501075203959871f, 0.0500461233610187f, 0.0499847501339942f, + 0.0499234007091606f, 0.0498620750807206f, 0.0498007732428331f, + 0.0497394951896126f, 0.0496782409151302f, 0.0496170104134122f, + 0.0495558036784412f, 0.0494946207041559f, 0.0494334614844505f, + 0.0493723260131757f, 0.0493112142841377f, 0.0492501262910988f, + 0.0491890620277774f, 0.0491280214878476f, 0.0490670046649396f, + 0.0490060115526395f, 0.0489450421444893f, 0.0488840964339871f, + 0.0488231744145867f, 0.0487622760796981f, 0.0487014014226872f, + 0.0486405504368756f, 0.0485797231155411f, 0.0485189194519174f, + 0.0484581394391941f, 0.0483973830705168f, 0.0483366503389869f, + 0.0482759412376620f, 0.0482152557595553f, 0.0481545938976364f, + 0.0480939556448302f, 0.0480333409940183f, 0.0479727499380377f, + 0.0479121824696815f, 0.0478516385816988f, 0.0477911182667946f, + 0.0477306215176298f, 0.0476701483268213f, 0.0476096986869419f, + 0.0475492725905205f, 0.0474888700300417f, 0.0474284909979461f, + 0.0473681354866303f, 0.0473078034884470f, 0.0472474949957045f, + 0.0471872100006673f, 0.0471269484955557f, 0.0470667104725461f, + 0.0470064959237706f, 0.0469463048413175f, 0.0468861372172308f, + 0.0468259930435107f, 0.0467658723121130f, 0.0467057750149499f, + 0.0466457011438889f, 0.0465856506907542f, 0.0465256236473254f, + 0.0464656200053382f, 0.0464056397564841f, 0.0463456828924109f, + 0.0462857494047220f, 0.0462258392849768f, 0.0461659525246906f, + 0.0461060891153350f, 0.0460462490483370f, 0.0459864323150799f, + 0.0459266389069028f, 0.0458668688151008f, 0.0458071220309249f, + 0.0457473985455820f, 0.0456876983502350f, 0.0456280214360027f, + 0.0455683677939599f, 0.0455087374151372f, 0.0454491302905213f, + 0.0453895464110547f, 0.0453299857676360f, 0.0452704483511194f, + 0.0452109341523154f, 0.0451514431619903f, 0.0450919753708663f, + 0.0450325307696215f, 0.0449731093488901f, 0.0449137110992621f, + 0.0448543360112835f, 0.0447949840754560f, 0.0447356552822377f, + 0.0446763496220422f, 0.0446170670852392f, 0.0445578076621543f, + 0.0444985713430691f, 0.0444393581182212f, 0.0443801679778039f, + 0.0443210009119664f, 0.0442618569108143f, 0.0442027359644086f, + 0.0441436380627666f, 0.0440845631958612f, 0.0440255113536215f, + 0.0439664825259325f, 0.0439074767026350f, 0.0438484938735257f, + 0.0437895340283575f, 0.0437305971568391f, 0.0436716832486349f, + 0.0436127922933654f, 0.0435539242806073f, 0.0434950791998928f, + 0.0434362570407102f, 0.0433774577925038f, 0.0433186814446737f, + 0.0432599279865760f, 0.0432011974075228f, 0.0431424896967819f, + 0.0430838048435774f, 0.0430251428370888f, 0.0429665036664521f, + 0.0429078873207587f, 0.0428492937890565f, 0.0427907230603488f, + 0.0427321751235950f, 0.0426736499677106f, 0.0426151475815668f, + 0.0425566679539909f, 0.0424982110737659f, 0.0424397769296309f, + 0.0423813655102811f, 0.0423229768043671f, 0.0422646108004960f, + 0.0422062674872303f, 0.0421479468530891f, 0.0420896488865467f, + 0.0420313735760336f, 0.0419731209099364f, 0.0419148908765975f, + 0.0418566834643152f, 0.0417984986613437f, 0.0417403364558930f, + 0.0416821968361295f, 0.0416240797901751f, 0.0415659853061075f, + 0.0415079133719608f, 0.0414498639757248f, 0.0413918371053450f, + 0.0413338327487231f, 0.0412758508937167f, 0.0412178915281392f, + 0.0411599546397599f, 0.0411020402163043f, 0.0410441482454534f, + 0.0409862787148446f, 0.0409284316120707f, 0.0408706069246808f, + 0.0408128046401798f, 0.0407550247460286f, 0.0406972672296439f, + 0.0406395320783983f, 0.0405818192796204f, 0.0405241288205948f, + 0.0404664606885619f, 0.0404088148707178f, 0.0403511913542151f, + 0.0402935901261618f, 0.0402360111736221f, 0.0401784544836158f, + 0.0401209200431191f, 0.0400634078390637f, 0.0400059178583375f, + 0.0399484500877839f, 0.0398910045142029f, 0.0398335811243497f, + 0.0397761799049359f, 0.0397188008426288f, 0.0396614439240516f, + 0.0396041091357837f, 0.0395467964643600f, 0.0394895058962715f, + 0.0394322374179654f, 0.0393749910158443f, 0.0393177666762671f, + 0.0392605643855483f, 0.0392033841299587f, 0.0391462258957247f, + 0.0390890896690288f, 0.0390319754360092f, 0.0389748831827604f, + 0.0389178128953323f, 0.0388607645597312f, 0.0388037381619189f, + 0.0387467336878135f, 0.0386897511232887f, 0.0386327904541742f, + 0.0385758516662558f, 0.0385189347452749f, 0.0384620396769291f, + 0.0384051664468718f, 0.0383483150407121f, 0.0382914854440155f, + 0.0382346776423029f, 0.0381778916210515f, 0.0381211273656940f, + 0.0380643848616195f, 0.0380076640941726f, 0.0379509650486541f, + 0.0378942877103206f, 0.0378376320643845f, 0.0377809980960143f, + 0.0377243857903341f, 0.0376677951324245f, 0.0376112261073215f, + 0.0375546787000170f, 0.0374981528954590f, 0.0374416486785515f, + 0.0373851660341542f, 0.0373287049470828f, 0.0372722654021088f, + 0.0372158473839598f, 0.0371594508773193f, 0.0371030758668266f, + 0.0370467223370766f, 0.0369903902726209f, 0.0369340796579662f, + 0.0368777904775757f, 0.0368215227158680f, 0.0367652763572181f, + 0.0367090513859566f, 0.0366528477863700f, 0.0365966655427007f, + 0.0365405046391474f, 0.0364843650598642f, 0.0364282467889613f, + 0.0363721498105048f, 0.0363160741085168f, 0.0362600196669752f, + 0.0362039864698137f, 0.0361479745009222f, 0.0360919837441462f, + 0.0360360141832873f, 0.0359800658021030f, 0.0359241385843064f, + 0.0358682325135671f, 0.0358123475735100f, 0.0357564837477163f, + 0.0357006410197228f, 0.0356448193730226f, 0.0355890187910643f, + 0.0355332392572527f, 0.0354774807549481f, 0.0354217432674673f, + 0.0353660267780825f, 0.0353103312700220f, 0.0352546567264699f, + 0.0351990031305666f, 0.0351433704654078f, 0.0350877587140454f, + 0.0350321678594873f, 0.0349765978846971f, 0.0349210487725945f, + 0.0348655205060549f, 0.0348100130679097f, 0.0347545264409461f, + 0.0346990606079075f, 0.0346436155514929f, 0.0345881912543573f, + 0.0345327876991115f, 0.0344774048683225f, 0.0344220427445127f, + 0.0343667013101609f, 0.0343113805477016f, 0.0342560804395250f, + 0.0342008009679776f, 0.0341455421153614f, 0.0340903038639347f, + 0.0340350861959113f, 0.0339798890934610f, 0.0339247125387098f, + 0.0338695565137393f, 0.0338144210005871f, 0.0337593059812464f, + 0.0337042114376669f, 0.0336491373517538f, 0.0335940837053681f, + 0.0335390504803269f, 0.0334840376584033f, 0.0334290452213260f, + 0.0333740731507797f, 0.0333191214284051f, 0.0332641900357988f, + 0.0332092789545132f, 0.0331543881660566f, 0.0330995176518931f, + 0.0330446673934429f, 0.0329898373720821f, 0.0329350275691426f, + 0.0328802379659119f, 0.0328254685436341f, 0.0327707192835085f, + 0.0327159901666906f, 0.0326612811742918f, 0.0326065922873795f, + 0.0325519234869767f, 0.0324972747540625f, 0.0324426460695717f, + 0.0323880374143954f, 0.0323334487693801f, 0.0322788801153286f, + 0.0322243314329991f, 0.0321698027031064f, 0.0321152939063204f, + 0.0320608050232677f, 0.0320063360345299f, 0.0319518869206454f, + 0.0318974576621078f, 0.0318430482393670f, 0.0317886586328284f, + 0.0317342888228537f, 0.0316799387897604f, 0.0316256085138217f, + 0.0315712979752667f, 0.0315170071542806f, 0.0314627360310044f, + 0.0314084845855349f, 0.0313542527979248f, 0.0313000406481829f, + 0.0312458481162737f, 0.0311916751821174f, 0.0311375218255906f, + 0.0310833880265254f, 0.0310292737647098f, 0.0309751790198877f, + 0.0309211037717592f, 0.0308670479999799f, 0.0308130116841615f, + 0.0307589948038714f, 0.0307049973386331f, 0.0306510192679259f, + 0.0305970605711850f, 0.0305431212278014f, 0.0304892012171220f, + 0.0304353005184498f, 0.0303814191110434f, 0.0303275569741174f, + 0.0302737140868423f, 0.0302198904283447f, 0.0301660859777065f, + 0.0301123007139661f, 0.0300585346161174f, 0.0300047876631105f, + 0.0299510598338510f, 0.0298973511072006f, 0.0298436614619771f, + 0.0297899908769537f, 0.0297363393308599f, 0.0296827068023808f, + 0.0296290932701575f, 0.0295754987127871f, 0.0295219231088224f, + 0.0294683664367722f, 0.0294148286751011f, 0.0293613098022296f, + 0.0293078097965342f, 0.0292543286363469f, 0.0292008662999562f, + 0.0291474227656060f, 0.0290939980114962f, 0.0290405920157826f, + 0.0289872047565770f, 0.0289338362119469f, 0.0288804863599157f, + 0.0288271551784627f, 0.0287738426455233f, 0.0287205487389884f, + 0.0286672734367052f, 0.0286140167164761f, 0.0285607785560604f, + 0.0285075589331723f, 0.0284543578254825f, 0.0284011752106171f, + 0.0283480110661588f, 0.0282948653696453f, 0.0282417380985708f, + 0.0281886292303851f, 0.0281355387424940f, 0.0280824666122592f, + 0.0280294128169981f, 0.0279763773339841f, 0.0279233601404466f, + 0.0278703612135707f, 0.0278173805304972f, 0.0277644180683234f, + 0.0277114738041018f, 0.0276585477148412f, 0.0276056397775058f, + 0.0275527499690165f, 0.0274998782662493f, 0.0274470246460364f, + 0.0273941890851657f, 0.0273413715603814f, 0.0272885720483831f, + 0.0272357905258265f, 0.0271830269693230f, 0.0271302813554403f, + 0.0270775536607015f, 0.0270248438615858f, 0.0269721519345281f, + 0.0269194778559194f, 0.0268668216021066f, 0.0268141831493923f, + 0.0267615624740348f, 0.0267089595522488f, 0.0266563743602045f, + 0.0266038068740280f, 0.0265512570698012f, 0.0264987249235622f, + 0.0264462104113047f, 0.0263937135089783f, 0.0263412341924885f, + 0.0262887724376968f, 0.0262363282204203f, 0.0261839015164322f, + 0.0261314923014614f, 0.0260791005511929f, 0.0260267262412675f, + 0.0259743693472816f, 0.0259220298447877f, 0.0258697077092944f, + 0.0258174029162656f, 0.0257651154411217f, 0.0257128452592383f, + 0.0256605923459475f, 0.0256083566765370f, 0.0255561382262503f, + 0.0255039369702867f, 0.0254517528838017f, 0.0253995859419064f, + 0.0253474361196679f, 0.0252953033921090f, 0.0252431877342086f, + 0.0251910891209013f, 0.0251390075270776f, 0.0250869429275840f, + 0.0250348952972226f, 0.0249828646107516f, 0.0249308508428848f, + 0.0248788539682924f, 0.0248268739615999f, 0.0247749107973889f, + 0.0247229644501969f, 0.0246710348945172f, 0.0246191221047991f, + 0.0245672260554474f, 0.0245153467208232f, 0.0244634840752433f, + 0.0244116380929803f, 0.0243598087482627f, 0.0243079960152748f, + 0.0242561998681570f, 0.0242044202810054f, 0.0241526572278720f, + 0.0241009106827644f, 0.0240491806196466f, 0.0239974670124380f, + 0.0239457698350141f, 0.0238940890612061f, 0.0238424246648012f, + 0.0237907766195426f, 0.0237391448991289f, 0.0236875294772150f, + 0.0236359303274115f, 0.0235843474232849f, 0.0235327807383576f, + 0.0234812302461075f, 0.0234296959199691f, 0.0233781777333319f, + 0.0233266756595420f, 0.0232751896719008f, 0.0232237197436660f, + 0.0231722658480509f, 0.0231208279582248f, 0.0230694060473126f, + 0.0230180000883954f, 0.0229666100545100f, 0.0229152359186491f, + 0.0228638776537611f, 0.0228125352327506f, 0.0227612086284777f, + 0.0227098978137585f, 0.0226586027613650f, 0.0226073234440251f, + 0.0225560598344226f, 0.0225048119051967f, 0.0224535796289430f, + 0.0224023629782129f, 0.0223511619255133f, 0.0222999764433073f, + 0.0222488065040137f, 0.0221976520800073f, 0.0221465131436186f, + 0.0220953896671340f, 0.0220442816227956f, 0.0219931889828018f, + 0.0219421117193066f, 0.0218910498044196f, 0.0218400032102066f, + 0.0217889719086893f, 0.0217379558718450f, 0.0216869550716069f, + 0.0216359694798642f, 0.0215849990684619f, 0.0215340438092009f, + 0.0214831036738377f, 0.0214321786340850f, 0.0213812686616112f, + 0.0213303737280405f, 0.0212794938049529f, 0.0212286288638847f, + 0.0255395068839051f, 0.0254886718213066f, 0.0254378516550702f, + 0.0253870463565555f, 0.0253362558970777f, 0.0252854802479078f, + 0.0252347193802728f, 0.0251839732653554f, 0.0251332418742945f, + 0.0250825251781844f, 0.0250318231480754f, 0.0249811357549739f, + 0.0249304629698419f, 0.0248798047635972f, 0.0248291611071137f, + 0.0247785319712210f, 0.0247279173267045f, 0.0246773171443056f, + 0.0246267313947214f, 0.0245761600486050f, 0.0245256030765652f, + 0.0244750604491668f, 0.0244245321369303f, 0.0243740181103322f, + 0.0243235183398047f, 0.0242730327957360f, 0.0242225614484700f, + 0.0241721042683066f, 0.0241216612255015f, 0.0240712322902661f, + 0.0240208174327678f, 0.0239704166231300f, 0.0239200298314316f, + 0.0238696570277076f, 0.0238192981819486f, 0.0237689532641015f, + 0.0237186222440686f, 0.0236683050917082f, 0.0236180017768345f, + 0.0235677122692175f, 0.0235174365385832f, 0.0234671745546130f, + 0.0234169262869447f, 0.0233666917051716f, 0.0233164707788430f, + 0.0232662634774639f, 0.0232160697704954f, 0.0231658896273542f, + 0.0231157230174128f, 0.0230655699099999f, 0.0230154302743997f, + 0.0229653040798524f, 0.0229151912955540f, 0.0228650918906563f, + 0.0228150058342672f, 0.0227649330954500f, 0.0227148736432244f, + 0.0226648274465652f, 0.0226147944744040f, 0.0225647746956273f, + 0.0225147680790781f, 0.0224647745935549f, 0.0224147942078123f, + 0.0223648268905605f, 0.0223148726104657f, 0.0222649313361497f, + 0.0222150030361906f, 0.0221650876791220f, 0.0221151852334334f, + 0.0220652956675701f, 0.0220154189499333f, 0.0219655550488802f, + 0.0219157039327235f, 0.0218658655697320f, 0.0218160399281303f, + 0.0217662269760989f, 0.0217164266817738f, 0.0216666390132473f, + 0.0216168639385673f, 0.0215671014257376f, 0.0215173514427178f, + 0.0214676139574232f, 0.0214178889377253f, 0.0213681763514512f, + 0.0213184761663838f, 0.0212687883502620f, 0.0212191128707804f, + 0.0211694496955896f, 0.0211197987922958f, 0.0210701601284612f, + 0.0210205336716039f, 0.0209709193891977f, 0.0209213172486724f, + 0.0208717272174133f, 0.0208221492627619f, 0.0207725833520154f, + 0.0207230294524270f, 0.0206734875312053f, 0.0206239575555151f, + 0.0205744394924771f, 0.0205249333091677f, 0.0204754389726188f, + 0.0204259564498189f, 0.0203764857077117f, 0.0203270267131969f, + 0.0202775794331301f, 0.0202281438343228f, 0.0201787198835422f, + 0.0201293075475113f, 0.0200799067929092f, 0.0200305175863706f, + 0.0199811398944860f, 0.0199317736838018f, 0.0198824189208204f, + 0.0198330755720000f, 0.0197837436037543f, 0.0197344229824530f, + 0.0196851136744220f, 0.0196358156459426f, 0.0195865288632520f, + 0.0195372532925434f, 0.0194879888999657f, 0.0194387356516237f, + 0.0193894935135779f, 0.0193402624518448f, 0.0192910424323968f, + 0.0192418334211618f, 0.0191926353840239f, 0.0191434482868227f, + 0.0190942720953539f, 0.0190451067753690f, 0.0189959522925752f, + 0.0189468086126355f, 0.0188889190431616f, 0.0188228895446890f, + 0.0187568748920607f, 0.0186908750819390f, 0.0186248901109865f, + 0.0185589199758671f, 0.0184929646732450f, 0.0184270241997851f, + 0.0183610985521537f, 0.0182951877270173f, 0.0182292917210430f, + 0.0181634105308991f, 0.0180975441532544f, 0.0180316925847787f, + 0.0179658558221421f, 0.0179000338620157f, 0.0178342267010716f, + 0.0177684343359822f, 0.0177026567634210f, 0.0176368939800620f, + 0.0175711459825800f, 0.0175054127676507f, 0.0174396943319504f, + 0.0217357186797338f, 0.0216700297925235f, 0.0216043556745757f, + 0.0215386963225698f, 0.0214730517331858f, 0.0214074219031045f, + 0.0213418068290075f, 0.0212762065075772f, 0.0212106209354964f, + 0.0211450501094493f, 0.0210794940261201f, 0.0210139526821943f, + 0.0209484260743579f, 0.0208829141992977f, 0.0208174170537012f, + 0.0207519346342566f, 0.0206864669376530f, 0.0206210139605803f, + 0.0205555756997288f, 0.0204901521517898f, 0.0204247433134555f, + 0.0203593491814184f, 0.0202939697523721f, 0.0202286050230107f, + 0.0201632549900294f, 0.0200979196501238f, 0.0200325989999903f, + 0.0199672930363261f, 0.0199020017558292f, 0.0198367251551984f, + 0.0197714632311330f, 0.0197062159803331f, 0.0196409833994998f, + 0.0195757654853346f, 0.0195105622345400f, 0.0194453736438190f, + 0.0193801997098757f, 0.0193150404294146f, 0.0192498957991409f, + 0.0191847658157610f, 0.0191196504759815f, 0.0190545497765102f, + 0.0189894637140552f, 0.0189243922853257f, 0.0188593354870315f, + 0.0187942933158832f, 0.0187292657685919f, 0.0186642528418697f, + 0.0185992545324294f, 0.0185342708369846f, 0.0184693017522493f, + 0.0184043472749386f, 0.0183394074017683f, 0.0182744821294548f, + 0.0182095714547152f, 0.0181446753742675f, 0.0180797938848304f, + 0.0180149269831233f, 0.0179500746658664f, 0.0178852369297804f, + 0.0178204137715871f, 0.0177556051880087f, 0.0176908111757684f, + 0.0176260317315900f, 0.0175612668521982f, 0.0174965165343181f, + 0.0174317807746758f, 0.0173670595699981f, 0.0173023529170125f, + 0.0172376608124473f, 0.0171729832530314f, 0.0171083202354946f, + 0.0170436717565673f, 0.0169790378129807f, 0.0169144184014667f, + 0.0168498135187580f, 0.0167852231615880f, 0.0167206473266909f, + 0.0166560860108013f, 0.0165915392106550f, 0.0165270069229884f, + 0.0164624891445385f, 0.0163979858720428f, 0.0163334971022403f, + 0.0162690228318700f, 0.0162045630576720f, 0.0161401177763870f, + 0.0160756869847564f, 0.0160112706795224f, 0.0159468688574280f, + 0.0158824815152168f, 0.0158181086496333f, 0.0157537502574225f, + 0.0156894063353304f, 0.0156250768801034f, 0.0155607618884889f, + 0.0154964613572351f, 0.0154321752830905f, 0.0153679036628048f, + 0.0153036464931283f, 0.0152394037708118f, 0.0151751754926072f, + 0.0151109616552667f, 0.0150467622555436f, 0.0149825772901918f, + 0.0149184067559660f, 0.0148542506496213f, 0.0147901089679141f, + 0.0147259817076010f, 0.0146618688654396f, 0.0145977704381882f, + 0.0145336864226058f, 0.0144696168154521f, 0.0144055616134876f, + 0.0143415208134734f, 0.0142774944121715f, 0.0142134824063446f, + 0.0141494847927560f, 0.0140855015681696f, 0.0140215327293505f, + 0.0139575782730643f, 0.0182553662036547f, 0.0181914405027335f, + 0.0181275291746461f, 0.0180636322161610f, 0.0179997496240473f, + 0.0179358813950749f, 0.0178720275260146f, 0.0178081880136376f, + 0.0177443628547161f, 0.0176805520460228f, 0.0176167555843314f, + 0.0175529734664161f, 0.0174892056890518f, 0.0174254522490144f, + 0.0173617131430802f, 0.0172979883680264f, 0.0172342779206310f, + 0.0171705817976724f, 0.0171068999959302f, 0.0170432325121844f, + 0.0169795793432156f, 0.0169159404858055f, 0.0168523159367363f, + 0.0167887056927909f, 0.0167251097507530f, 0.0166615281074072f, + 0.0165979607595384f, 0.0165344077039326f, 0.0164708689373761f, + 0.0164073444566565f, 0.0163438342585618f, 0.0162803383398807f, + 0.0162168566974025f, 0.0161533893279177f, 0.0160899362282169f, + 0.0160264973950919f, 0.0159630728253350f, 0.0158996625157394f, + 0.0158362664630987f, 0.0157728846642075f, 0.0157095171158610f, + 0.0156461638148553f, 0.0155828247579869f, 0.0155194999420533f, + 0.0154561893638526f, 0.0153928930201836f, 0.0153296109078459f, + 0.0152663430236398f, 0.0152030893643661f, 0.0151398499268268f, + 0.0150766247078242f, 0.0150134137041614f, 0.0149502169126424f, + 0.0148870343300717f, 0.0148238659532546f, 0.0147607117789972f, + 0.0146975718041061f, 0.0146344460253890f, 0.0145713344396539f, + 0.0145082370437097f, 0.0144451538343661f, 0.0143820848084334f, + 0.0143190299627227f, 0.0142559892940457f, 0.0141929627992148f, + 0.0141299504750434f, 0.0140669523183453f, 0.0140039683259351f, + 0.0139409984946283f, 0.0138780428212408f, 0.0138151013025895f, + 0.0137521739354917f, 0.0136892607167660f, 0.0136263616432309f, + 0.0135634767117064f, 0.0135006059190125f, 0.0134377492619707f, + 0.0133749067374024f, 0.0133120783421303f, 0.0132492640729776f, + 0.0131864639267682f, 0.0131236779003268f, 0.0130609059904787f, + 0.0129981481940499f, 0.0129354045078674f, 0.0128726749287585f, + 0.0128099594535516f, 0.0127472580790754f, 0.0126845708021597f, + 0.0126218976196349f, 0.0125592385283320f, 0.0124965935250826f, + 0.0124339626067194f, 0.0123713457700756f, 0.0123087430119851f, + 0.0122461543292824f, 0.0121835797188031f, 0.0121210191773830f, + 0.0120584727018589f, 0.0119959402890683f, 0.0119334219358495f, + 0.0118709176390412f, 0.0118084273954832f, 0.0117459512020156f, + 0.0116834890554797f, 0.0116210409527169f, 0.0115586068905700f, + 0.0114961868658818f, 0.0114337808754965f, 0.0113713889162585f, + 0.0113090109850131f, 0.0112466470786063f, 0.0111842971938848f, + 0.0111219613276961f, 0.0110596394768881f, 0.0109973316383098f, + 0.0152967658163884f, 0.0152344859928189f, 0.0151722201720297f, + 0.0151099683508726f, 0.0150477305262000f, 0.0149855066948649f, + 0.0149232968537211f, 0.0148611009996230f, 0.0147989191294260f, + 0.0147367512399860f, 0.0146745973281595f, 0.0146124573908037f, + 0.0145503314247770f, 0.0144882194269378f, 0.0144261213941456f, + 0.0143640373232607f, 0.0143019672111439f, 0.0142399110546566f, + 0.0141778688506612f, 0.0141158405960207f, 0.0140538262875987f, + 0.0139918259222596f, 0.0139298394968685f, 0.0138678670082912f, + 0.0138059084533941f, 0.0137439638290446f, 0.0136820331321104f, + 0.0136201163594601f, 0.0135582135079633f, 0.0134963245744897f, + 0.0134344495559101f, 0.0133725884490961f, 0.0133107412509195f, + 0.0132489079582535f, 0.0131870885679713f, 0.0131252830769473f, + 0.0130634914820565f, 0.0130017137801745f, 0.0129399499681775f, + 0.0128782000429427f, 0.0128164640013478f, 0.0127547418402713f, + 0.0126930335565922f, 0.0126313391471905f, 0.0125696586089468f, + 0.0125079919387422f, 0.0124463391334587f, 0.0123847001899790f, + 0.0123230751051865f, 0.0122614638759652f, 0.0121998664991998f, + 0.0121382829717759f, 0.0120767132905796f, 0.0120151574524978f, + 0.0119536154544179f, 0.0118920872932283f, 0.0118305729658180f, + 0.0117690724690765f, 0.0117075857998943f, 0.0116461129551625f, + 0.0115846539317728f, 0.0115232087266176f, 0.0114617773365900f, + 0.0114003597585841f, 0.0113389559894942f, 0.0112775660262157f, + 0.0112161898656444f, 0.0111548275046771f, 0.0110934789402111f, + 0.0110321441691445f, 0.0109708231883759f, 0.0109095159948048f, + 0.0108482225853314f, 0.0107869429568564f, 0.0107256771062815f, + 0.0106644250305088f, 0.0106031867264412f, 0.0105419621909824f, + 0.0104807514210367f, 0.0104195544135091f, 0.0103583711653052f, + 0.0102972016733315f, 0.0102360459344952f, 0.0101749039457040f, + 0.0101137757038663f, 0.0100526612058913f, 0.00999156044868904f, + 0.00993047342916997f, 0.00986940014424537f, 0.00980834059082714f, + 0.00974729476582803f, 0.00968626266616141f, 0.00962524428874123f, + 0.00956423963048225f, 0.00950324868829994f, 0.00944227145911042f, + 0.00938130793983050f, 0.00932035812737764f, 0.00925942201867014f, + 0.00919849961062680f, 0.00913759090016730f, 0.00907669588421184f, + 0.00901581455968142f, 0.00895494692349780f, 0.00889409297258320f, + 0.00883325270386071f, 0.00877242611425416f, 0.00871161320068786f, + 0.00865081396008705f, 0.00859002838937745f, 0.00852925648548564f, + 0.00846849824533880f, 0.00840775366586477f, 0.00834702274399218f, + 0.00828630547665032f, 0.00822560186076915f, 0.00816491189327928f, + 0.00810423557111206f, 0.00804357289119956f, 0.00798292385047450f, + 0.00792228844587023f, 0.00786166667432087f, 0.00780105853276131f, + 0.00774046401812689f, 0.00767988312735390f, 0.00761931585737907f, + 0.00755876220514012f, 0.00749822216757512f, 0.0117994237492008f, + 0.0117389109318013f, 0.0116784117198946f, 0.0116179261104218f, + 0.0115574541003245f, 0.0114969956865451f, 0.0114365508660266f, + 0.0113761196357128f, 0.0113157019925481f, 0.0112552979334775f, + 0.0111949074554470f, 0.0111345305554030f, 0.0110741672302926f, + 0.0110138174770638f, 0.0109534812926650f, 0.0108931586740455f, + 0.0108328496181552f, 0.0107725541219447f, 0.0107122721823654f, + 0.0106520037963691f, 0.0105917489609085f, 0.0105315076729371f, + 0.0104712799294088f, 0.0104110657272783f, 0.0103508650635009f, + 0.0102906779350330f, 0.0102305043388311f, 0.0101703442718527f, + 0.0101101977310560f, 0.0100500647133998f, 0.00998994521584351f, + 0.00992983923534746f, 0.00986974676887237f, 0.00980966781338000f, + 0.00974960236583244f, 0.00968955042319258f, 0.00962951198242412f, + 0.00956948704049132f, 0.00950947559435911f, 0.00944947764099319f, + 0.00938949317735982f, 0.00932952220042610f, 0.00926956470715973f, + 0.00920962069452902f, 0.00914969015950301f, 0.00908977309905157f, + 0.00902986951014501f, 0.00896997938975452f, 0.00891010273485171f, + 0.00885023954240927f, 0.00879038980940028f, 0.00873055353279850f, + 0.00867073070957841f, 0.00861092133671532f, 0.00855112541118508f, + 0.00849134292996412f, 0.00843157389002974f, 0.00837181828835992f, + 0.00831207612193319f, 0.00825234738772879f, 0.00819263208272664f, + 0.00813293020390743f, 0.00807324174825247f, 0.00801356671274367f, + 0.00795390509436372f, 0.00789425689009604f, 0.00783462209692454f, + 0.00777500071183396f, 0.00771539273180966f, 0.00765579815383771f, + 0.00759621697490487f, 0.00753664919199842f, 0.00747709480210662f, + 0.00741755380221809f, 0.00735802618932235f, 0.00729851196040943f, + 0.00723901111247016f, 0.00717952364249613f, 0.00712004954747930f, + 0.00706058882441252f, 0.00700114147028941f, 0.00694170748210410f, + 0.00688228685685138f, 0.00682287959152672f, 0.00676348568312646f, + 0.00670410512864744f, 0.00664473792508719f, 0.00658538406944387f, + 0.00652604355871650f, 0.00646671638990454f, 0.00640740256000838f, + 0.00634810206602876f, 0.00628881490496747f, 0.00622954107382667f, + 0.00617028056960933f, 0.00611103338931901f, 0.00605179952996010f, + 0.00599257898853756f, 0.00593337176205699f, 0.00587417784752470f, + 0.00581499724194773f, 0.00575582994233370f, 0.00569667594569095f, + 0.00563753524902838f, 0.00557840784935593f, 0.00551929374368371f, + 0.00546019292902289f, 0.00540110540238498f, 0.00534203116078258f, + 0.00528297020122859f, 0.00522392252073672f, 0.00516488811632138f, + 0.00510586698499765f, 0.00504685912378122f, 0.00498786452968852f, + 0.00492888319973650f, 0.00486991513094304f, 0.00481096032032652f, + 0.00475201876490594f, 0.00469309046170108f, 0.00463417540773242f, + 0.00457527360002097f, 0.00451638503558854f, 0.00445750971145748f, + 0.00439864762465103f, 0.00433979877219282f, 0.00428096315110732f, + 0.00422214075841959f, 0.00416333159115556f, 0.00410453564634156f, + 0.00404575292100462f, 0.00398698341217263f, 0.00392822711687407f, + 0.00386948403213799f, 0.00381075415499416f, 0.00375203748247305f, + 0.00369333401160576f, 0.00363464373942418f, 0.00357596666296056f, + 0.00351730277924822f, 0.00345865208532087f, 0.00340001457821293f, + 0.00334139025495950f, 0.00328277911259650f, 0.00322418114816031f, + 0.00316559635868802f, 0.00310702474121738f, 0.00304846629278699f, + 0.00298992101043588f, 0.00293138889120381f, 0.00287286993213121f, + 0.00281436413025932f, 0.00275587148262979f, 0.00269739198628516f, + 0.00263892563826845f, 0.00258047243562354f, 0.00252203237539478f, + 0.00246360545462732f, 0.00240519167036685f, 0.00234679101965996f, + 0.00228840349955362f, 0.00223002910709563f, 0.00217166783933437f, + 0.00211331969331902f, 0.00205498466609932f, 0.00199666275472560f, + 0.00193835395624892f, 0.00188005826772120f, 0.00182177568619468f, + 0.00176350620872251f, 0.00170524983235837f, 0.00164700655415667f, + 0.00158877637117250f, 0.00153055928046153f, 0.00147235527908007f, + 0.00141416436408531f, 0.00135598653253494f, 0.00129782178148719f, + 0.00123967010800113f, 0.00118153150913658f, 0.00112340598195376f, + 0.00106529352351364f, 0.00100719413087796f, 0.000949107801109128f, + 0.000891034531269985f, 0.000832974318424273f, 0.000774927159636230f, + 0.000716893051970924f, 0.000658871992493926f, 0.000600863978271471f, + 0.000542869006370628f, 0.000484887073858964f, 0.000426918177804714f, + 0.00473069032285445f, 0.00467274749092250f, 0.00461481768665678f, + 0.00455690090712813f, 0.00449899714940810f, 0.00444110641056905f, + 0.00438322868768370f, 0.00432536397782568f, 0.00426751227806910f, + 0.00420967358548896f, 0.00415184789716061f, 0.00409403521016033f, + 0.00403623552156485f, 0.00397844882845178f, 0.00392067512789918f, + 0.00386291441698589f, 0.00380516669279124f, 0.00374743195239552f, + 0.00368971019287939f, 0.00363200141132430f, 0.00357430560481226f, + 0.00351662277042614f, 0.00345895290524928f, 0.00340129600636568f, + 0.00334365207086007f, 0.00328602109581783f, 0.00322840307832495f, + 0.00317079801546810f, 0.00311320590433462f, 0.00305562674201249f, + 0.00299806052559032f, 0.00294050725215744f, 0.00288296691880374f, + 0.00282543952261988f, 0.00276792506069706f, 0.00271042353012718f, + 0.00265293492800284f, 0.00259545925141724f, 0.00253799649746422f, + 0.00248054666323838f, 0.00242310974583471f, 0.00236568574234924f, + 0.00230827464987843f, 0.00225087646551925f, 0.00219349118636958f, + 0.00213611880952797f, 0.00207875933209334f, 0.00202141275116546f, + 0.00196407906384477f, 0.00190675826723236f, 0.00184945035842982f, + 0.00179215533453958f, 0.00173487319266458f, 0.00167760392990851f, + 0.00162034754337570f, 0.00156310403017101f, 0.00150587338740016f, + 0.00144865561216939f, 0.00139145070158553f, 0.00133425865275616f, + 0.00127707946278949f, 0.00121991312879444f, 0.00116275964788048f, + 0.00110561901715772f, 0.00104849123373701f, 0.000991376294729840f, + 0.000934274197248231f, 0.000877184938404996f, 0.000820108515313556f, + 0.000763044925087941f, 0.000705994164842849f, 0.000648956231693587f, + 0.000591931122756240f, 0.000534918835147447f, 0.000477919365984458f, + 0.000420932712385191f, 0.000363958871468284f, 0.000306997840353040f, + 0.000250049616159320f, 0.000193114196007482f, 0.000136191577018996f, + 7.92817563154968e-05f, 2.23847310195091e-05f, 0.000000000000000000f, + 0.000000000000000000f, 0.000000000000000000f, 0.000000000000000000f, + 0.000000000000000000f, 0.000000000000000000f, 0.000000000000000000f, + 0.000000000000000000f, 0.000000000000000000f, 0.000000000000000000f, + 0.000000000000000000f, 0.000000000000000000f, 0.000000000000000000f, + 0.000000000000000000f, 0.000000000000000000f, 0.000000000000000000f, + 0.000000000000000000f, 0.000000000000000000f, 0.000000000000000000f, + 0.000000000000000000f, 0.000000000000000000f, 0.000000000000000000f, + 0.000000000000000000f, 0.000000000000000000f, 0.000000000000000000f, + 0.000000000000000000f, 0.000000000000000000f, 0.000000000000000000f, + 0.000000000000000000f, 0.000000000000000000f, 0.000000000000000000f}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_DSP_SPECTRAL_REVERB_CONSTANTS_AND_TABLES_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/spectral_reverb_test.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/spectral_reverb_test.cc new file mode 100644 index 000000000..015159fa1 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/spectral_reverb_test.cc @@ -0,0 +1,324 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/spectral_reverb.h" + +#include <algorithm> +#include <cmath> +#include <numeric> +#include <vector> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/constants_and_types.h" +#include "dsp/biquad_filter.h" +#include "dsp/fft_manager.h" +#include "dsp/filter_coefficient_generators.h" +#include "utils/test_util.h" + +namespace vraudio { + +namespace { + +const size_t kFramesPerBuffer512 = 512; +const size_t kFramesPerBuffer2048 = 2048; +const size_t kFramesPerBuffer713 = 713; +const size_t kNumReverbOverlap = 4; +const size_t kReverbFftSize = 4096; +const int kSampleFrequency16 = 16000; +const int kSampleFrequency24 = 24000; + +// A set of 9 RT60 values for each of the octave bands with center frequencies +// between 31.25Hz and 8kHz. +const float kRt60s[kNumReverbOctaveBands] = {0.8f, 0.8f, 0.7f, 0.7f, 0.65f, + 0.65f, 0.6f, 0.6f, 0.5f}; + +void ImpulseResponse(float length_sec, int sample_frequency, + size_t frames_per_buffer, SpectralReverb* reverb, + std::vector<float>* left_response, + std::vector<float>* right_response) { + AudioBuffer input(kNumMonoChannels, frames_per_buffer); + input.Clear(); + input[0][0] = 1.0f; + AudioBuffer output(kNumStereoChannels, frames_per_buffer); + + reverb->Process(input[0], &output[0], &output[1]); + + // The number of iterations required so that we will have no more than a few + // zeros following each of our impulse responses. + const float tail_length_samples = + kReverbFftSize + length_sec * static_cast<float>(sample_frequency); + const size_t num_iterations = + 100 + 1 + + static_cast<size_t>(std::ceil(tail_length_samples / + static_cast<float>(frames_per_buffer))); + + for (size_t i = 0; i < num_iterations; ++i) { + left_response->insert(left_response->end(), output[0].begin(), + output[0].end()); + right_response->insert(right_response->end(), output[1].begin(), + output[1].end()); + + input.Clear(); + reverb->Process(input[0], &output[0], &output[1]); + } +} + +void StereoOutputTestHelper(int sample_frequency, size_t frames_per_buffer) { + SpectralReverb reverb(sample_frequency, frames_per_buffer); + reverb.SetRt60PerOctaveBand(kRt60s); + + std::vector<float> output_collect_left; + std::vector<float> output_collect_right; + ImpulseResponse(1.0f /*length [sec]*/, sample_frequency, frames_per_buffer, + &reverb, &output_collect_left, &output_collect_right); + + // First test that there are kReverbFftSize / kNumReverbOverlap zeros at the + // beginning of the system impulse responses ONLY if this value is greater + // than |frames_per_buffer|, otherwise expect no onset zeros. + const size_t kOverlapLength = kReverbFftSize / kNumReverbOverlap; + const size_t onset = kOverlapLength > frames_per_buffer ? kOverlapLength : 0; + for (size_t i = 0; i < onset; ++i) { + EXPECT_NEAR(0.0f, output_collect_left[i], kEpsilonFloat); + EXPECT_NEAR(0.0f, output_collect_right[i], kEpsilonFloat); + } + // Test the sample five samples later is non zero. i.e. the tail has begun. + EXPECT_NE(0.0f, output_collect_left[onset + 5]); + EXPECT_NE(0.0f, output_collect_right[onset + 5]); +} + +void ZeroRt60TestHelper(int sample_frequency, float rt_60) { + SpectralReverb reverb(sample_frequency, kFramesPerBuffer512); + reverb.SetRt60PerOctaveBand(kRt60s); + + std::vector<float> output_collect_left; + std::vector<float> output_collect_right; + ImpulseResponse(1.0f /*length [sec]*/, sample_frequency, kFramesPerBuffer512, + &reverb, &output_collect_left, &output_collect_right); + + // Test that all the frames of the tail buffer are zeros. + for (size_t i = 0; i < kFramesPerBuffer512; ++i) { + EXPECT_NEAR(0.0f, output_collect_left[i], kEpsilonFloat); + EXPECT_NEAR(0.0f, output_collect_right[i], kEpsilonFloat); + } +} + +void TailDecayTestHelper(int sample_frequency, size_t frames_per_buffer) { + // Butterworth Lowpass filter with cutoff frequency at 3Hz. + BiquadCoefficients low_pass_coefficients( + 1.0f, -1.999444639647f, 0.999444793816755f, 1e-5f, 2e-5f, 1e-5f); + BiquadFilter low_pass_filter(low_pass_coefficients, frames_per_buffer); + const std::vector<float> kUniformRt60s(kNumReverbOctaveBands, kRt60s[0]); + SpectralReverb reverb(sample_frequency, frames_per_buffer); + reverb.SetRt60PerOctaveBand(kUniformRt60s.data()); + + std::vector<float> output_collect_left; + std::vector<float> output_collect_right; + ImpulseResponse(2.0f /*length [sec]*/, sample_frequency, frames_per_buffer, + &reverb, &output_collect_left, &output_collect_right); + + for (size_t i = 0; i < output_collect_left.size(); ++i) { + output_collect_left[i] = std::abs(output_collect_left[i]); + output_collect_right[i] = std::abs(output_collect_right[i]); + } + + const size_t response_length = output_collect_left.size(); + + AudioBuffer test_buffer(kNumMonoChannels, response_length); + test_buffer[0] = output_collect_left; + + // Very low frequency content of tail. This should essentially just preserve + // the decay. + low_pass_filter.Filter(test_buffer[0], &test_buffer[0]); + + const size_t max_location = static_cast<size_t>( + std::max_element(test_buffer[0].begin(), test_buffer[0].end()) - + test_buffer[0].begin()); + + // Stop before the very end of the tail as it goes to zero. + const size_t end_point = max_location + kReverbFftSize; + const size_t step_size = (end_point - max_location) / 20; + + // Test for decay. + for (size_t i = max_location + step_size; i < end_point; i += step_size) { + EXPECT_GT(std::abs(test_buffer[0][i - step_size]), + std::abs(test_buffer[0][i])); + } +} + +void DecorrelatedTailsTestHelper(int sample_frequency, + size_t frames_per_buffer) { + // This value has been found empirically in MATLAB. + const float kMaxCrossCorrelation = 12.0f; + const std::vector<float> kUniformRt60s(kNumReverbOctaveBands, 0.7f); + SpectralReverb reverb(sample_frequency, frames_per_buffer); + reverb.SetRt60PerOctaveBand(kUniformRt60s.data()); + + std::vector<float> output_collect_left; + std::vector<float> output_collect_right; + ImpulseResponse(0.7f /*length [sec]*/, sample_frequency, frames_per_buffer, + &reverb, &output_collect_left, &output_collect_right); + + // Find the absolute maximum elements of each vector. + auto min_max_left = std::minmax_element(output_collect_left.begin(), + output_collect_left.end()); + size_t left_max_index = + std::abs(*min_max_left.first) > std::abs(*min_max_left.second) + ? (min_max_left.first - output_collect_left.begin()) + : (min_max_left.second - output_collect_left.begin()); + auto min_max_right = std::minmax_element(output_collect_right.begin(), + output_collect_right.end()); + size_t right_max_index = + std::abs(*min_max_right.first) > std::abs(*min_max_right.second) + ? (min_max_right.first - output_collect_right.begin()) + : (min_max_right.second - output_collect_right.begin()); + + // Take a sample of the tails for cross correlation. + AudioBuffer pair(kNumStereoChannels, kReverbFftSize); + for (size_t i = 0; i < kReverbFftSize; ++i) { + pair[0][i] = output_collect_left[i + left_max_index] / + output_collect_left[left_max_index]; + pair[1][i] = output_collect_right[i + right_max_index] / + output_collect_right[right_max_index]; + } + + // The cross correlation is not normalized. Thus we can expect a very small + // value. Naturally, if the RT60 inputs are changed the expected value would + // thus be different. + const float max_xcorr = MaxCrossCorrelation(pair[0], pair[1]); + EXPECT_LT(max_xcorr, kMaxCrossCorrelation); +} + +} // namespace + +// Tests that the stereo output from the Reverbs Process fuction has the +// expected properties of predelay and length. +TEST(SpectralReverbTest, StereoOutputTest) { + StereoOutputTestHelper(kSampleFrequency24, kFramesPerBuffer512); + StereoOutputTestHelper(kSampleFrequency24, kFramesPerBuffer2048); + StereoOutputTestHelper(kSampleFrequency24, kFramesPerBuffer713); + StereoOutputTestHelper(kSampleFrequency16, kFramesPerBuffer512); + StereoOutputTestHelper(kSampleFrequency16, kFramesPerBuffer2048); + StereoOutputTestHelper(kSampleFrequency16, kFramesPerBuffer713); +} + +// Tests that the stereo output from the Reverb's Process function has the +// output of all zeros when the RT60 values are all zero. +TEST(SpectralReverbTest, ZeroRt60Test) { + const float kZeroRt = 0.0f; + const float kBelowMinRt = 0.12f; + ZeroRt60TestHelper(kSampleFrequency24, kZeroRt); + ZeroRt60TestHelper(kSampleFrequency16, kBelowMinRt); + ZeroRt60TestHelper(kSampleFrequency16, kZeroRt); + ZeroRt60TestHelper(kSampleFrequency16, kBelowMinRt); +} + +// Tests that the tail is decaying over time. +TEST(SpectralReverbTest, TailDecayTest) { + TailDecayTestHelper(kSampleFrequency24, kFramesPerBuffer512); + TailDecayTestHelper(kSampleFrequency24, kFramesPerBuffer2048); + TailDecayTestHelper(kSampleFrequency16, kFramesPerBuffer512); + TailDecayTestHelper(kSampleFrequency16, kFramesPerBuffer2048); +} + +// Tests that the stereo tail pairs are highy decorrelated. +TEST(SpectralReverbTest, DecorrelatedTailsTest) { + DecorrelatedTailsTestHelper(kSampleFrequency24, kFramesPerBuffer512); + DecorrelatedTailsTestHelper(kSampleFrequency24, kFramesPerBuffer2048); + DecorrelatedTailsTestHelper(kSampleFrequency24, kFramesPerBuffer713); + DecorrelatedTailsTestHelper(kSampleFrequency16, kFramesPerBuffer512); + DecorrelatedTailsTestHelper(kSampleFrequency16, kFramesPerBuffer2048); + DecorrelatedTailsTestHelper(kSampleFrequency16, kFramesPerBuffer713); +} + +// Tests that the gain parameter behaves as expected. +TEST(SpecralReverbTest, GainTest) { + const float kReverbLength = 0.5f; + const float kGain = 100.0f; + const float kGainEpsilon = 0.32f; + const std::vector<float> kUniformRt60s(kNumReverbOctaveBands, kReverbLength); + SpectralReverb reverb(kSampleFrequency24, kFramesPerBuffer512); + reverb.SetRt60PerOctaveBand(kUniformRt60s.data()); + + // Calculate scaled and unscaled impulse responses. + std::vector<float> output_left; + std::vector<float> output_right; + ImpulseResponse(kReverbLength, kSampleFrequency24, kFramesPerBuffer512, + &reverb, &output_left, &output_right); + std::vector<float> output_left_scaled; + reverb.SetGain(kGain); + ImpulseResponse(kReverbLength, kSampleFrequency24, kFramesPerBuffer512, + &reverb, &output_left_scaled, &output_right); + + // Determine the max absolute entry in each impulse response. + std::transform(output_left.begin(), output_left.end(), output_left.begin(), + static_cast<float (*)(float)>(&std::abs)); + std::transform(output_left_scaled.begin(), output_left_scaled.end(), + output_left_scaled.begin(), + static_cast<float (*)(float)>(&std::abs)); + const float max_unscaled = + *std::max_element(output_left.begin(), output_left.end()); + const float max_scaled = + *std::max_element(output_left_scaled.begin(), output_left_scaled.end()); + EXPECT_GT(max_scaled, max_unscaled); + EXPECT_NEAR((max_unscaled / max_scaled) / (1.0f / kGain), 1.0f, kGainEpsilon); +} + +// Tests that when the feedback values are all ~0.0f, no processing is +// performed (output is all zero). Also tests that if even one of the rt60s +// result in a non zero feedback that the result will be non zero. +TEST(SpectralReverbTest, DisabledProcessingTest) { + const float kReverbLength = 0.1f; + const std::vector<float> kUniformRt60s(kNumReverbOctaveBands, kReverbLength); + SpectralReverb reverb(kSampleFrequency24, kFramesPerBuffer512); + reverb.SetRt60PerOctaveBand(kUniformRt60s.data()); + std::vector<float> output_left; + std::vector<float> output_right; + ImpulseResponse(kReverbLength, kSampleFrequency24, kFramesPerBuffer512, + &reverb, &output_left, &output_right); + for (size_t i = 0; i < output_left.size(); ++i) { + EXPECT_FLOAT_EQ(output_left[i], 0.0f); + EXPECT_FLOAT_EQ(output_right[i], 0.0f); + } + + // Test a non zero case. + const float kLongerReverbLength = 0.4f; + std::vector<float> rt60s(kNumReverbOctaveBands, 0.0f); + rt60s[0] = kLongerReverbLength; + output_left.resize(0); + output_right.resize(0); + reverb.SetRt60PerOctaveBand(rt60s.data()); + ImpulseResponse(kReverbLength, kSampleFrequency24, kFramesPerBuffer512, + &reverb, &output_left, &output_right); + const float sum_left = + std::accumulate(output_left.begin(), output_left.end(), 0.0f); + EXPECT_NE(sum_left, 0.0f); + const float sum_right = + std::accumulate(output_right.begin(), output_right.end(), 0.0f); + EXPECT_NE(sum_right, 0.0f); + + // Set gain to zero and test again. + output_left.resize(0); + output_right.resize(0); + reverb.SetGain(0.0f); + ImpulseResponse(kReverbLength, kSampleFrequency24, kFramesPerBuffer512, + &reverb, &output_left, &output_right); + for (size_t i = 0; i < output_left.size(); ++i) { + EXPECT_FLOAT_EQ(output_left[i], 0.0f); + EXPECT_FLOAT_EQ(output_right[i], 0.0f); + } +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/stereo_panner.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/stereo_panner.cc new file mode 100644 index 000000000..373db9989 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/stereo_panner.cc @@ -0,0 +1,46 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/stereo_panner.h" + +#include <cmath> + +#include "base/constants_and_types.h" + +namespace vraudio { + +const float kStereoLeftRadians = kRadiansFromDegrees * kStereoLeftDegrees; +const float kStereoRightRadians = kRadiansFromDegrees * kStereoRightDegrees; + +void CalculateStereoPanGains(const SphericalAngle& source_direction, + std::vector<float>* stereo_gains) { + // Note this applies the same panning law as was applied by the ambisonic + // equivalent panner to ensure consistency. + DCHECK(stereo_gains); + stereo_gains->resize(kNumStereoChannels); + + const float cos_direction_elevation = std::cos(source_direction.elevation()); + + (*stereo_gains)[0] = + 0.5f * (1.0f + std::cos(kStereoLeftRadians - source_direction.azimuth()) * + cos_direction_elevation); + (*stereo_gains)[1] = + 0.5f * + (1.0f + std::cos(kStereoRightRadians - source_direction.azimuth()) * + cos_direction_elevation); +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/stereo_panner.h b/src/3rdparty/resonance-audio/resonance_audio/dsp/stereo_panner.h new file mode 100644 index 000000000..bf4cf1708 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/stereo_panner.h @@ -0,0 +1,35 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_DSP_STEREO_PANNER_H_ +#define RESONANCE_AUDIO_DSP_STEREO_PANNER_H_ + +#include <vector> + +#include "base/spherical_angle.h" + +namespace vraudio { + +// Computes a pair of stereo panner gains based on the |source_direction|. +// +// @param source_direction Azimuth and elevation of the sound source. +// @param stereo_gains A pointer to vector of stereo loudspeaker gains. +void CalculateStereoPanGains(const SphericalAngle& source_direction, + std::vector<float>* stereo_gains); + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_DSP_STEREO_PANNER_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/stereo_panner_test.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/stereo_panner_test.cc new file mode 100644 index 000000000..d24230241 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/stereo_panner_test.cc @@ -0,0 +1,88 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/stereo_panner.h" + +#include <cmath> +#include <vector> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/constants_and_types.h" + +namespace vraudio { + +namespace { + +const float kMinusThreeDecibels = kInverseSqrtTwo; + +// Tests that the |CalculateStereoPanGains| method will generate correct stereo +// pan gains. +TEST(StereoPannerTest, StereoTest) { + std::vector<float> speaker_gains; + + SphericalAngle source_direction = SphericalAngle::FromDegrees(-90.0f, 0.0f); + CalculateStereoPanGains(source_direction, &speaker_gains); + EXPECT_NEAR(0.0f, speaker_gains[0], kEpsilonFloat); + EXPECT_NEAR(1.0f, speaker_gains[1], kEpsilonFloat); + + source_direction = SphericalAngle::FromDegrees(-45.0f, 0.0f); + CalculateStereoPanGains(source_direction, &speaker_gains); + EXPECT_NEAR(kMinusThreeDecibels, speaker_gains[1] - speaker_gains[0], + kEpsilonFloat); + + source_direction = SphericalAngle::FromDegrees(0.0f, 0.0f); + CalculateStereoPanGains(source_direction, &speaker_gains); + EXPECT_NEAR(speaker_gains[0], speaker_gains[1], kEpsilonFloat); + + source_direction = SphericalAngle::FromDegrees(45.0f, 0.0f); + CalculateStereoPanGains(source_direction, &speaker_gains); + EXPECT_NEAR(kMinusThreeDecibels, speaker_gains[0] - speaker_gains[1], + kEpsilonFloat); + + source_direction = SphericalAngle::FromDegrees(90.0f, 0.0f); + CalculateStereoPanGains(source_direction, &speaker_gains); + EXPECT_NEAR(1.0f, speaker_gains[0], kEpsilonFloat); + EXPECT_NEAR(0.0f, speaker_gains[1], kEpsilonFloat); + + source_direction = SphericalAngle::FromDegrees(0.0f, 45.0f); + CalculateStereoPanGains(source_direction, &speaker_gains); + EXPECT_NEAR(0.5f, speaker_gains[0], kEpsilonFloat); + EXPECT_NEAR(0.5f, speaker_gains[1], kEpsilonFloat); + + source_direction = SphericalAngle::FromDegrees(0.0f, -60.0f); + CalculateStereoPanGains(source_direction, &speaker_gains); + EXPECT_NEAR(0.5f, speaker_gains[0], kEpsilonFloat); + EXPECT_NEAR(0.5f, speaker_gains[1], kEpsilonFloat); + + source_direction = SphericalAngle::FromDegrees(0.0f, 90.0f); + CalculateStereoPanGains(source_direction, &speaker_gains); + EXPECT_NEAR(0.5f, speaker_gains[0], kEpsilonFloat); + EXPECT_NEAR(0.5f, speaker_gains[1], kEpsilonFloat); + + source_direction = SphericalAngle::FromDegrees(0.0f, -90.0f); + CalculateStereoPanGains(source_direction, &speaker_gains); + EXPECT_NEAR(0.5f, speaker_gains[0], kEpsilonFloat); + EXPECT_NEAR(0.5f, speaker_gains[1], kEpsilonFloat); + + source_direction = SphericalAngle::FromDegrees(45.0f, 45.0f); + CalculateStereoPanGains(source_direction, &speaker_gains); + EXPECT_NEAR(0.75f, speaker_gains[0], kEpsilonFloat); + EXPECT_NEAR(0.25f, speaker_gains[1], kEpsilonFloat); +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/utils.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/utils.cc new file mode 100644 index 000000000..e1d8396a0 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/utils.cc @@ -0,0 +1,190 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/utils.h" + +#include <algorithm> +#include <cmath> +#include <limits> +#include <random> +#include <vector> + +#include "base/constants_and_types.h" +#include "base/logging.h" +#include "base/misc_math.h" + +#include "dsp/biquad_filter.h" +#include "dsp/filter_coefficient_generators.h" + +namespace { + +// The mean and standard deviation of the normal distribution for bandlimited +// Gaussian noise. +const float kMean = 0.0f; +const float kStandardDeviation = 1.0f; + +// Maximum group delay in seconds for each filter. In order to avoid audible +// distortion, the maximum phase shift of a re-combined stereo sequence should +// not exceed 5ms at high frequencies. That is why, maximum phase shift of +// each filter is set to 1/2 of that value. +const float kMaxGroupDelaySeconds = 0.0025f; + +// Phase modulation depth, chosen so that for a given max group delay filters +// provide the lowest cross-correlation coefficient. +const float kPhaseModulationDepth = 1.18f; + +// Constants used in the generation of uniform random number distributions. +// https://en.wikipedia.org/wiki/Linear_congruential_generator +const uint64 kMultiplier = 1664525L; +const uint64 kIncrement = 1013904223L; +const float kInt32ToFloat = + 1.0f / static_cast<float>(std::numeric_limits<uint32>::max()); + +} // namespace + +namespace vraudio { + +void GenerateGaussianNoise(float mean, float std_deviation, unsigned seed, + AudioBuffer::Channel* noise_channel) { + DCHECK(noise_channel); + // First generate uniform noise. + GenerateUniformNoise(0.0f, 1.0f, seed, noise_channel); + const size_t length = noise_channel->size(); + + // Gaussian distribution with mean and standard deviation in pairs via the + // box-muller transform + // https://en.wikipedia.org/wiki/Box%E2%80%93Muller_transform. + for (size_t i = 0; i < length - 1; i += 2) { + const float part_one = std::sqrt(-2.0f * std::log((*noise_channel)[i])); + const float part_two = kTwoPi * (*noise_channel)[i + 1]; + const float z0 = part_one * std::cos(part_two); + const float z1 = part_one * std::sin(part_two); + (*noise_channel)[i] = std_deviation * z0 + mean; + (*noise_channel)[i + 1] = std_deviation * z1 + mean; + } + // Handle the odd buffer length case cheaply. + if (length % 2 > 0) { + (*noise_channel)[length - 1] = (*noise_channel)[0]; + } +} + +void GenerateUniformNoise(float min, float max, unsigned seed, + AudioBuffer::Channel* noise_channel) { + // Simple random generator to avoid the use of std::uniform_real_distribution + // affected by https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56202 + DCHECK(noise_channel); + DCHECK_LT(min, max); + const float scaled_conversion_factor = kInt32ToFloat * (max - min); + uint32 state = static_cast<uint32>(seed); + for (float& sample : *noise_channel) { + state = static_cast<uint32>(state * kMultiplier + kIncrement); + sample = min + static_cast<float>(state) * scaled_conversion_factor; + } +} + +void GenerateBandLimitedGaussianNoise(float center_frequency, int sampling_rate, + unsigned seed, + AudioBuffer* noise_buffer) { + + + DCHECK(noise_buffer); + DCHECK_GT(sampling_rate, 0); + DCHECK_LT(center_frequency, static_cast<float>(sampling_rate) / 2.0f); + const size_t num_frames = noise_buffer->num_frames(); + + BiquadCoefficients bandpass_coefficients = ComputeBandPassBiquadCoefficients( + sampling_rate, center_frequency, /*bandwidth=*/1); + BiquadFilter bandpass_filter(bandpass_coefficients, num_frames); + + for (auto& channel : *noise_buffer) { + GenerateGaussianNoise(kMean, kStandardDeviation, seed, &channel); + bandpass_filter.Filter(channel, &channel); + bandpass_filter.Clear(); + } +} + +std::unique_ptr<AudioBuffer> GenerateDecorrelationFilters(int sampling_rate) { + + const int kMaxGroupDelaySamples = static_cast<int>( + roundf(kMaxGroupDelaySeconds * static_cast<float>(sampling_rate))); + + // Filter coefficients according to: + // [1] F. Zotter, M. Frank, "Efficient Phantom Source Widening", Archives of + // Acoustics, Vol. 38, No. 1, pp. 27–37 (2013). + const float g0 = 1.0f - 0.25f * IntegerPow(kPhaseModulationDepth, 2); + const float g1 = 0.5f * kPhaseModulationDepth - + 0.0625f * IntegerPow(kPhaseModulationDepth, 3); + const float g2 = 0.1250f * IntegerPow(kPhaseModulationDepth, 2); + std::vector<float> filter1_coefficients{g2, g1, g0, -g1, g2}; + std::vector<float> filter2_coefficients{g2, -g1, g0, g1, g2}; + + const size_t filter_length = + filter1_coefficients.size() * kMaxGroupDelaySamples; + std::unique_ptr<AudioBuffer> decorrelation_filters( + new AudioBuffer(kNumStereoChannels, filter_length)); + decorrelation_filters->Clear(); + + for (size_t coefficient = 0; coefficient < filter1_coefficients.size(); + ++coefficient) { + (*decorrelation_filters)[0][coefficient * kMaxGroupDelaySamples] = + filter1_coefficients[coefficient]; + (*decorrelation_filters)[1][coefficient * kMaxGroupDelaySamples] = + filter2_coefficients[coefficient]; + } + + return decorrelation_filters; +} + +size_t GetNumReverbOctaveBands(int sampling_rate) { + DCHECK_GT(sampling_rate, 0); + + const float max_band = + log2f(0.5f * static_cast<float>(sampling_rate) / kLowestOctaveBandHz); + return std::min(kNumReverbOctaveBands, static_cast<size_t>(roundf(max_band))); +} + +size_t GetNumSamplesFromMilliseconds(float milliseconds, int sampling_rate) { + DCHECK_GE(milliseconds, 0.0f); + DCHECK_GT(sampling_rate, 0); + return static_cast<size_t>(milliseconds * kSecondsFromMilliseconds * + static_cast<float>(sampling_rate)); +} + +size_t CeilToMultipleOfFramesPerBuffer(size_t size, size_t frames_per_buffer) { + DCHECK_NE(frames_per_buffer, 0U); + const size_t remainder = size % frames_per_buffer; + return remainder == 0 ? std::max(size, frames_per_buffer) + : size + frames_per_buffer - remainder; +} + +void GenerateHannWindow(bool full_window, size_t window_length, + AudioBuffer::Channel* buffer) { + + DCHECK(buffer); + DCHECK_LE(window_length, buffer->size()); + const float full_window_scaling_factor = + kTwoPi / (static_cast<float>(window_length) - 1.0f); + const float half_window_scaling_factor = + kTwoPi / (2.0f * static_cast<float>(window_length) - 1.0f); + const float scaling_factor = + (full_window) ? full_window_scaling_factor : half_window_scaling_factor; + for (size_t i = 0; i < window_length; ++i) { + (*buffer)[i] = + 0.5f * (1.0f - std::cos(scaling_factor * static_cast<float>(i))); + } +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/utils.h b/src/3rdparty/resonance-audio/resonance_audio/dsp/utils.h new file mode 100644 index 000000000..ceb3060de --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/utils.h @@ -0,0 +1,93 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_DSP_UTILS_H_ +#define RESONANCE_AUDIO_DSP_UTILS_H_ + +#include "base/audio_buffer.h" + +namespace vraudio { + +// Generates a gaussian white noise signal. +// +// @param mean The mean distribution parameter. +// @param std_deviation The standard deviation distribution parameter. +// @param seed A seed for the random generator. +// @param noise_channel Buffer channel in which to store the noise. +void GenerateGaussianNoise(float mean, float std_deviation, unsigned seed, + AudioBuffer::Channel* noise_channel); + +// Generates a gaussian white noise signal. +// +// @param min The lowest value in the distribution. +// @param max The highest value in te distribution, must be > min. +// @param seed A seed for the random generator. +// @param noise_channel Buffer channel in which to store the noise. +void GenerateUniformNoise(float min, float max, unsigned seed, + AudioBuffer::Channel* noise_channel); + +// Generates a band limited gaussian white noise signal, one octave band wide. +// +// @param center_frequency Center frequency of the given octave band in Hz. +// @param sampling_rate System sampling rate in Hz. +// @param seed A seed for the random generator. +// @param noise_buffer Buffer in which to store the band limited noise. +void GenerateBandLimitedGaussianNoise(float center_frequency, int sampling_rate, + unsigned seed, AudioBuffer* noise_buffer); + +// Genarates a pair of decorrelation filters (for use in low quality/high +// effiency mode reverb). +// +// @param sampling_rate System sampling rate in Hz. +// @return Buffer containing the stereo filters. +std::unique_ptr<AudioBuffer> GenerateDecorrelationFilters(int sampling_rate); + +// Returns the number of octave bands necessary for the given |sampling_rate|. +// +// @param sampling_rate Sampling rate in Hertz. +// @return Number of reverb octave bands. +size_t GetNumReverbOctaveBands(int sampling_rate); + +// Converts the given |milliseconds| to number of samples with the given +// |sampling_rate|. This method should *not* be used when more precise +// (double-precission) value is desired. +// +// @param milliseconds Milliseconds in single-precission floating point. +// @param sampling_rate Sampling rate in Hertz. +// @return Number of samples. +size_t GetNumSamplesFromMilliseconds(float milliseconds, int sampling_rate); + +// Ceils the given |size| to the next multiple of given |frames_per_buffer|. +// +// @param size Input size in frames. +// @param frames_per_buffer Frames per buffer. +// @return Ceiled size in frames. +size_t CeilToMultipleOfFramesPerBuffer(size_t size, size_t frames_per_buffer); + +// Generates a Hann window (used for smooth onset and tapering of the generated +// reverb response tails). +// +// @param full_window True to generate a full window, false to generate a half. +// @param window_length Length of the window to be generated. Must be less than +// or equal to the number of frames in the |buffer|. +// @param buffer AudioBuffer::Channel to which the window is written, the number +// of frames will be the length in samples of the generated Hann window. +void GenerateHannWindow(bool full_window, size_t window_length, + AudioBuffer::Channel* buffer); + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_DSP_UTILS_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/dsp/utils_test.cc b/src/3rdparty/resonance-audio/resonance_audio/dsp/utils_test.cc new file mode 100644 index 000000000..dffe991ec --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/dsp/utils_test.cc @@ -0,0 +1,194 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "dsp/utils.h" + +#include <cmath> +#include <limits> +#include <memory> +#include <vector> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/constants_and_types.h" +#include "dsp/fft_manager.h" +#include "dsp/partitioned_fft_filter.h" +#include "utils/test_util.h" + +namespace vraudio { + +namespace { + +const int kSamplingRate = 48000; + +const size_t kHalfHannWindowLength = 8; +const float kExpectedHalfHannWindow[] = {0.0000000f, 0.04322727f, 0.1654347f, + 0.3454915f, 0.55226423f, 0.7500000f, + 0.9045085f, 0.98907380f}; + +const size_t kHannWindowLength = 15; +const float kExpectedHannWindow[] = { + 0.0000000f, 0.0495156f, 0.1882551f, 0.3887395f, 0.6112605f, + 0.8117449f, 0.9504844f, 1.0000000f, 0.9504844f, 0.8117449f, + 0.6112605f, 0.3887395f, 0.1882551f, 0.0495156f, 0.0000000f}; + +// Test that the noise generation functions create noise vectors with the +// expected means etc. +TEST(DspUtilsTest, NoiseTest) { + const size_t kNoiseBufferLength = 1e5; + // A high epsilon is used to determine if mean, std dev etc. are acurate as it + // would be infeasible to have enough samples to take advantage of the central + // limit theorem. + const float kEpsilon = 1e-1f; + + AudioBuffer gaussian(kNumMonoChannels, kNoiseBufferLength); + AudioBuffer uniform(kNumMonoChannels, kNoiseBufferLength); + + // Generate Gaussian Noise with mean 0 and std deviation 2. + const float kGaussianMean = 0.0f; + const float kStdDeviation = 2.0f; + GenerateGaussianNoise(kGaussianMean, kStdDeviation, /*seed=*/1U, + &gaussian[0]); + + // Calculate the mean and compare to that specified. + float mean = 0.0f; + for (auto& sample : gaussian[0]) { + mean += sample; + } + mean /= static_cast<float>(gaussian.num_frames()); + EXPECT_NEAR(mean, kGaussianMean, kEpsilon); + + // Calculate the std deviation and compare to that specified. + float std_dev = 0.0f; + for (auto& sample : gaussian[0]) { + std_dev += (sample - mean) * (sample - mean); + } + std_dev /= static_cast<float>(gaussian.num_frames()); + std_dev = std::sqrt(std_dev); + EXPECT_NEAR(std_dev, kStdDeviation, kEpsilon); + + // Genarate uniformly distributed noise min -1, max 1 and thus mean 0. + const float kMin = -1.0f; + const float kMax = 1.0f; + GenerateUniformNoise(kMin, kMax, /*seed=*/1U, &uniform[0]); + // Calculate the mean and min/max values, compare to expected values. + mean = 0.0f; + float min = std::numeric_limits<float>::max(); + float max = std::numeric_limits<float>::min(); + for (auto& sample : uniform[0]) { + mean += sample; + min = sample < min ? sample : min; + max = sample > max ? sample : max; + } + mean /= static_cast<float>(uniform.num_frames()); + EXPECT_NEAR(mean, (kMax + kMin) / 2.0f, kEpsilon); + EXPECT_GE(kMax, max); + EXPECT_LE(kMin, min); +} + +// Tests that the ceiled input size in frames matches the expected multiple of +// frames per buffer for arbitrary inputs. +TEST(DspUtilsTest, CeilToMultipleOfFramesPerBufferTest) { + const size_t kFramesPerBuffer = 512; + const std::vector<size_t> kInput = {0, 100, 512, 1000, 5000, 10240}; + const std::vector<size_t> kExpectedOutput = {512, 512, 512, + 1024, 5120, 10240}; + + for (size_t i = 0; i < kInput.size(); ++i) { + EXPECT_EQ(kExpectedOutput[i], + CeilToMultipleOfFramesPerBuffer(kInput[i], kFramesPerBuffer)); + } +} + +// Tests that on filtering a noise sample with a pair of decorrelation filters, +// the correlation between those outputs is less than the result of an +// autocorrelation. +TEST(DspUtilsTest, GenerateDecorrelationFiltersTest) { + // Size of FFT to be used in |GenerateDecorrelationFiltersTest|. + const size_t kBufferSize = 512; + // Centre frequency for noise used in |GenerateDecorrelationFiltersTest|. + const float kNoiseCenter = 1000.0f; + std::unique_ptr<AudioBuffer> kernels = + GenerateDecorrelationFilters(kSamplingRate); + AudioBuffer noise(kNumMonoChannels, kBufferSize); + GenerateBandLimitedGaussianNoise(kNoiseCenter, kSamplingRate, /*seed=*/1U, + &noise); + FftManager fft_manager(kBufferSize); + PartitionedFftFilter fft_filter(kernels->num_frames(), kBufferSize, + &fft_manager); + fft_filter.SetTimeDomainKernel((*kernels)[0]); + AudioBuffer output_one(kNumMonoChannels, kBufferSize); + + PartitionedFftFilter::FreqDomainBuffer freq_domain_buffer(kNumMonoChannels, + kBufferSize * 2); + fft_manager.FreqFromTimeDomain(noise[0], &freq_domain_buffer[0]); + fft_filter.Filter(freq_domain_buffer[0]); + fft_filter.GetFilteredSignal(&output_one[0]); + fft_filter.SetTimeDomainKernel((*kernels)[1]); + AudioBuffer output_two(kNumMonoChannels, kBufferSize); + fft_manager.FreqFromTimeDomain(noise[0], &freq_domain_buffer[0]); + fft_filter.Filter(freq_domain_buffer[0]); + fft_filter.GetFilteredSignal(&output_two[0]); + const float auto_correlation = MaxCrossCorrelation(noise[0], noise[0]); + const float decorrelated = MaxCrossCorrelation(output_one[0], output_two[0]); + EXPECT_LT(decorrelated, auto_correlation); +} + +// Tests half-Hann window calculation against values returned by MATLAB's hann() +// function. +TEST(DspUtilsTest, GenerateHalfHannWindowTest) { + AudioBuffer half_hann_window(kNumMonoChannels, kHalfHannWindowLength); + GenerateHannWindow(false, kHalfHannWindowLength, &half_hann_window[0]); + for (size_t i = 0; i < kHalfHannWindowLength; ++i) { + EXPECT_NEAR(half_hann_window[0][i], kExpectedHalfHannWindow[i], + kEpsilonFloat); + } +} + +// Tests Hann window generation for odd window lengths. +TEST(DspUtilsTest, GenerateHannWindowOddLengthTest) { + AudioBuffer hann_window(kNumMonoChannels, kHannWindowLength); + GenerateHannWindow(true, kHannWindowLength, &hann_window[0]); + for (size_t i = 0; i < kHannWindowLength; ++i) { + EXPECT_NEAR(hann_window[0][i], kExpectedHannWindow[i], kEpsilonFloat); + } +} + +// Tests that the calculated number of reverb octave bands matches the +// pre-computed results with arbitrary sampling rates. +TEST(DspUtilsTest, GetNumReverbOctaveBandsTest) { + const std::vector<int> kSamplingRates = {8000, 22050, 44100, 48000, 96000}; + const std::vector<size_t> kExpectedOutput = {7, 8, 9, 9, 9}; + + for (size_t i = 0; i < kSamplingRates.size(); ++i) { + EXPECT_EQ(kExpectedOutput[i], GetNumReverbOctaveBands(kSamplingRates[i])); + } +} + +// Tests that the calculated number of samples for arbitrary milliseconds values +// matches the pre-computed results with a specific sampling rate. +TEST(DspUtilsTest, GetNumSamplesFromMillisecondsTest) { + const std::vector<float> kInput = {0.0f, 2.5f, 50.0f, 123.45f, 1000.0f}; + const std::vector<size_t> kExpectedOutput = {0, 120, 2400, 5925, 48000}; + + for (size_t i = 0; i < kInput.size(); ++i) { + EXPECT_EQ(kExpectedOutput[i], + GetNumSamplesFromMilliseconds(kInput[i], kSamplingRate)); + } +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/acoustic_listener.h b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/acoustic_listener.h new file mode 100644 index 000000000..7ae5c7a3f --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/acoustic_listener.h @@ -0,0 +1,57 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_ACOUSTIC_LISTENER_H_ +#define RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_ACOUSTIC_LISTENER_H_ + +#include <array> +#include <cstddef> +#include <vector> + +#include "Eigen/Core" +#include "base/constants_and_types.h" + +namespace vraudio { +struct AcousticListener { + // Constructor. + // + // @param listener_position Position of the listener. + // @param impulse_response_length Number of samples in the energy impulse + // response for each frequency band. + AcousticListener(const Eigen::Vector3f& listener_position, + size_t impulse_response_length) + : position(listener_position) { + for (size_t i = 0; i < kNumReverbOctaveBands; ++i) { + energy_impulse_responses[i] = + std::vector<float>(impulse_response_length, 0.0f); + } + } + + const Eigen::Vector3f position; + + // Impulse responses in terms of energies for all frequency bands. + // Need to take square roots before applying it to an input signal, which + // models pressure. The reason we store response in energy instead of in + // pressure is because the geometrical acoustics computation is energy-based, + // and the following kind of operations are performed extensively: + // energy_impulse_responses[i][t] += <energy contribution from a ray>. + std::array<std::vector<float>, kNumReverbOctaveBands> + energy_impulse_responses; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_ACOUSTIC_LISTENER_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/acoustic_ray.cc b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/acoustic_ray.cc new file mode 100644 index 000000000..6385f639b --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/acoustic_ray.cc @@ -0,0 +1,27 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "geometrical_acoustics/acoustic_ray.h" + +#include <limits> + +namespace vraudio { + +// Static member initialization. +const float AcousticRay::kInfinity = std::numeric_limits<float>::infinity(); +const float AcousticRay::kRayEpsilon = 1e-4f; + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/acoustic_ray.h b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/acoustic_ray.h new file mode 100644 index 000000000..ffe910270 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/acoustic_ray.h @@ -0,0 +1,195 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_ACOUSTIC_RAY_H_ +#define RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_ACOUSTIC_RAY_H_ + +#include <array> +#include <vector> + +#include "embree2/rtcore.h" +#include "embree2/rtcore_ray.h" +#include "base/constants_and_types.h" + +namespace vraudio { + +// A class extending Embree's RTCRay (https://embree.github.io/api.html) with +// data needed for acoustic computations. +// It exposes useful fields through accessors. +class RTCORE_ALIGN(16) AcousticRay : public RTCRay { + public: + enum class RayType { + kSpecular, + kDiffuse, + }; + + // A constant used to indicate that the ray extends to infinity, if + // ray.t_far() == AcousticRay::kInfinity. + static const float kInfinity; + + // Used to offset a ray's origin slightly so that it will not + // intersect with the same geometry/primitive that it was generated from + // (by reflection, transmission, diffraction, etc.). + static const float kRayEpsilon; + + // Default constructor. Constructs a ray whose origin is at (0, 0, 0) and + // points in the +x direction. + AcousticRay() + : energies_(), type_(RayType::kSpecular), prior_distance_(0.0f) { + org[0] = 0.0f; + org[1] = 0.0f; + org[2] = 0.0f; + dir[0] = 1.0f; + dir[1] = 0.0f; + dir[2] = 0.0f; + tnear = 0.0f; + tfar = kInfinity; + Ng[0] = 0.0f; + Ng[1] = 0.0f; + Ng[2] = 0.0f; + geomID = RTC_INVALID_GEOMETRY_ID; + + // Members in RTCRay that we do not use (or whose initial values we do not + // care) are not initialized: + // align0, align1, align2, time, mask, u, v, primID, instID. + } + + // Constructor. + // + // @param origin Origin of the ray. + // @param direction Direction of the ray. + // @param t_near Ray parameter corresponding to the start of the ray. + // @param t_far Ray parameter corresponding to the end of the ray. Pass in + // AcousticRay::kInfinity if there is no end point. + // @param energies Ray energies for all frequency bands. + // @param ray_type Type of ray. + // @param prior_distance Distance traveled before this ray. + AcousticRay(const float origin[3], const float direction[3], float t_near, + float t_far, + const std::array<float, kNumReverbOctaveBands>& energies, + RayType ray_type, float prior_distance) + : energies_(energies), type_(ray_type), prior_distance_(prior_distance) { + org[0] = origin[0]; + org[1] = origin[1]; + org[2] = origin[2]; + dir[0] = direction[0]; + dir[1] = direction[1]; + dir[2] = direction[2]; + tnear = t_near; + tfar = t_far; + Ng[0] = 0.0f; + Ng[1] = 0.0f; + Ng[2] = 0.0f; + geomID = RTC_INVALID_GEOMETRY_ID; + + // Members in RTCRay that we do not use (or whose initial values we do not + // care) are not initialized: + // align0, align1, align2, time, mask, u, v, primID, instID. + } + + // Ray origin. + const float* origin() const { return org; } + void set_origin(const float origin[3]) { + org[0] = origin[0]; + org[1] = origin[1]; + org[2] = origin[2]; + } + + // Ray direction. + const float* direction() const { return dir; } + void set_direction(const float direction[3]) { + dir[0] = direction[0]; + dir[1] = direction[1]; + dir[2] = direction[2]; + } + + // Ray parameter t corresponding to the start of the ray segment. + const float t_near() const { return tnear; } + void set_t_near(float t_near) { tnear = t_near; } + + // Ray parameter t corresponding to the end of the ray segment. + const float t_far() const { return tfar; } + void set_t_far(float t_far) { tfar = t_far; } + + // Functions intersected_*() will only return meaningful results after + // Intersect() is called, otherwise they return default values as + // described below. + // + // Not normalized geometry normal at the intersection point. + // Default value: Vec3fa(0, 0, 0). + const float* intersected_geometry_normal() const { return Ng; } + void set_intersected_geometry_normal( + const float intersected_geometry_normal[3]) { + Ng[0] = intersected_geometry_normal[0]; + Ng[1] = intersected_geometry_normal[1]; + Ng[2] = intersected_geometry_normal[2]; + } + + // Id of the intersected geometry. + // Default value: kInvalidGeometryId. + const unsigned int intersected_geometry_id() const { return geomID; } + + // Id of the intersected primitive. + // Default value: kInvalidPrimitiveId. + const unsigned int intersected_primitive_id() const { return primID; } + + // Ray energies for all frequency bands. + const std::array<float, kNumReverbOctaveBands>& energies() const { + return energies_; + } + void set_energies(const std::array<float, kNumReverbOctaveBands>& energies) { + energies_ = energies; + } + + // Ray type. + const RayType type() const { return type_; } + void set_type(const RayType type) { type_ = type; } + + // Prior distance. + const float prior_distance() const { return prior_distance_; } + void set_prior_distance(float prior_distance) { + prior_distance_ = prior_distance; + } + + // Finds the first intersection between this ray and a scene. Some fields + // will be filled/mutated, which can be examined by the following functions: + // - t_far() + // - intersected_geometry_normal() + // - intersected_geometry_id() + // - intersected_primitive_id() + // + // @param scene An RTCScene to test the intersection. + // @return True if an intersection is found. + bool Intersect(RTCScene scene) { + rtcIntersect(scene, *this); + return geomID != RTC_INVALID_GEOMETRY_ID; + } + + private: + // Used to determine early-termination of rays. May also be used to model + // source strength. + std::array<float, kNumReverbOctaveBands> energies_; + + // Ray type. + RayType type_ = RayType::kSpecular; + + // Accumulated distance traveled on the same path before this ray starts. + float prior_distance_ = 0.0f; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_ACOUSTIC_RAY_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/acoustic_ray_test.cc b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/acoustic_ray_test.cc new file mode 100644 index 000000000..29ef269ed --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/acoustic_ray_test.cc @@ -0,0 +1,243 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "geometrical_acoustics/acoustic_ray.h" + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/logging.h" +#include "geometrical_acoustics/test_util.h" + +namespace vraudio { + +namespace { +const float kFloatErrorTolerance = 5e-7f; + +class AcousticRayTest : public testing::Test { + protected: + void SetUp() override { + // Use a single RTCDevice for all tests. + static RTCDevice device = rtcNewDevice(nullptr); + CHECK_NOTNULL(device); + scene_ = rtcDeviceNewScene( + device, RTC_SCENE_STATIC | RTC_SCENE_HIGH_QUALITY, RTC_INTERSECT1); + } + + void TearDown() override { rtcDeleteScene(scene_); } + + // Normalizes the vector in place. + void NormalizeVector(float* vector) { + const float norm = std::sqrt(vector[0] * vector[0] + vector[1] * vector[1] + + vector[2] * vector[2]); + ASSERT_GT(norm, 0.0f); + vector[0] /= norm; + vector[1] /= norm; + vector[2] /= norm; + } + + const std::array<float, kNumReverbOctaveBands> kZeroEnergies{ + {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}}; + RTCScene scene_ = nullptr; +}; + +TEST_F(AcousticRayTest, DefaultConstructorTest) { + AcousticRay ray; + const float default_origin[3] = {0.0f, 0.0f, 0.0f}; + const float default_direction[3] = {1.0f, 0.0f, 0.0f}; + ExpectFloat3Close(ray.origin(), default_origin); + ExpectFloat3Close(ray.direction(), default_direction); + EXPECT_FLOAT_EQ(ray.t_near(), 0.0f); + EXPECT_FLOAT_EQ(ray.t_far(), AcousticRay::kInfinity); + for (const float energy : ray.energies()) { + EXPECT_FLOAT_EQ(energy, 0.0f); + } + EXPECT_EQ(ray.type(), AcousticRay::RayType::kSpecular); + EXPECT_FLOAT_EQ(ray.prior_distance(), 0.0f); +} + +TEST_F(AcousticRayTest, AccessorsTest) { + const float origin[3] = {0.0f, 0.0f, 0.25f}; + const float direction[3] = {0.0f, 0.0f, 1.0f}; + + const float t_near = 0.0f; + const float t_far = AcousticRay::kInfinity; + const std::array<float, kNumReverbOctaveBands> energies = { + {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f}}; + const AcousticRay::RayType ray_type = AcousticRay::RayType::kDiffuse; + const float prior_distance = 0.0f; + AcousticRay ray(origin, direction, t_near, t_far, energies, ray_type, + prior_distance); + + // Validate fields passed to the constructor. + ExpectFloat3Close(ray.origin(), origin); + ExpectFloat3Close(ray.direction(), direction); + EXPECT_FLOAT_EQ(ray.t_near(), t_near); + EXPECT_FLOAT_EQ(ray.t_far(), t_far); + for (size_t i = 0; i < energies.size(); ++i) { + EXPECT_FLOAT_EQ(ray.energies().at(i), energies.at(i)); + } + EXPECT_EQ(ray.type(), ray_type); + EXPECT_FLOAT_EQ(ray.prior_distance(), prior_distance); + + // Validate explicit setters. + const float new_origin[3] = {1.0f, 2.0f, 3.0f}; + ray.set_origin(new_origin); + ExpectFloat3Close(ray.origin(), new_origin); + + const float new_direction[3] = {0.0f, 1.0f, 0.0f}; + ray.set_direction(new_direction); + ExpectFloat3Close(ray.direction(), new_direction); + + const float new_t_near = 2.0f; + ray.set_t_near(new_t_near); + EXPECT_FLOAT_EQ(ray.t_near(), new_t_near); + + const float new_t_far = 4.0f; + ray.set_t_far(new_t_far); + EXPECT_FLOAT_EQ(ray.t_far(), new_t_far); + + const float intersected_geometry_normal[3] = {0.0f, 1.0f, 2.0f}; + ray.set_intersected_geometry_normal(intersected_geometry_normal); + ExpectFloat3Close(ray.intersected_geometry_normal(), + intersected_geometry_normal); + + const std::array<float, kNumReverbOctaveBands> new_energies = { + {2.0f, 4.0f, 6.0f, 8.0f, 10.0f, 12.0f, 14.0f, 16.0f, 18.0f}}; + ray.set_energies(new_energies); + for (size_t i = 0; i < energies.size(); ++i) { + EXPECT_FLOAT_EQ(ray.energies().at(i), new_energies.at(i)); + } + + const AcousticRay::RayType new_ray_type = AcousticRay::RayType::kSpecular; + ray.set_type(new_ray_type); + EXPECT_EQ(ray.type(), new_ray_type); + + const float new_prior_distance = 7.0f; + ray.set_prior_distance(new_prior_distance); + EXPECT_FLOAT_EQ(ray.prior_distance(), new_prior_distance); +} + +TEST_F(AcousticRayTest, IntersectTriangleInGroundSceneTest) { + // Add a ground to the scene and commit. + AddTestGround(scene_); + rtcCommit(scene_); + + const float t_near = 0.0f; + const float t_far = AcousticRay::kInfinity; + const float prior_distance = 0.0f; + { + // This ray should intersect the ground geometry (id: 0) and the first + // triangle (id: 0). + const float origin[3] = {0.0f, 0.0f, 0.25f}; + float direction[3] = {1.0f, 1.0f, -1.0f}; + NormalizeVector(direction); + AcousticRay ray(origin, direction, t_near, t_far, kZeroEnergies, + AcousticRay::RayType::kSpecular, prior_distance); + EXPECT_TRUE(ray.Intersect(scene_)); + EXPECT_EQ(ray.intersected_geometry_id(), 0u); + EXPECT_EQ(ray.intersected_primitive_id(), 0u); + + // Check the intersection point. + EXPECT_NEAR(0.4330127f, ray.t_far(), 1e-7); + float intersection_point[3]; + intersection_point[0] = ray.origin()[0] + ray.t_far() * ray.direction()[0]; + intersection_point[1] = ray.origin()[1] + ray.t_far() * ray.direction()[1]; + intersection_point[2] = ray.origin()[2] + ray.t_far() * ray.direction()[2]; + + const float expected_intersection_point[3] = {0.25f, 0.25f, 0.0f}; + ExpectFloat3Close(intersection_point, expected_intersection_point, + kFloatErrorTolerance); + + // Normal at the intersection point. + float normal[3]; + normal[0] = ray.intersected_geometry_normal()[0]; + normal[1] = ray.intersected_geometry_normal()[1]; + normal[2] = ray.intersected_geometry_normal()[2]; + NormalizeVector(normal); + const float expected_normal[3] = {0.0f, 0.0f, 1.0f}; + ExpectFloat3Close(normal, expected_normal, kFloatErrorTolerance); + } + { + // This ray should intersect the ground geometry (id: 0) and the second + // triangle (id: 1). + const float origin[3] = {0.0f, 0.0f, 0.75f}; + const float direction[3] = {1.0f, 1.0f, -1.0f}; + AcousticRay ray(origin, direction, t_near, t_far, kZeroEnergies, + AcousticRay::RayType::kSpecular, prior_distance); + EXPECT_TRUE(ray.Intersect(scene_)); + EXPECT_EQ(ray.intersected_geometry_id(), 0u); + EXPECT_EQ(ray.intersected_primitive_id(), 1u); + } + { + // This ray shoots upward (away from the ground) and therefore should not + // intersect anything. + const float origin[3] = {0.0f, 0.0f, 0.25}; + const float direction[3] = {1.0f, 1.0f, 1.0f}; + AcousticRay ray(origin, direction, t_near, t_far, kZeroEnergies, + AcousticRay::RayType::kSpecular, prior_distance); + EXPECT_FALSE(ray.Intersect(scene_)); + } +} + +TEST_F(AcousticRayTest, IntersectNothingBackfaceTest) { + // Add a ground to the scene and commit. + AddTestGround(scene_); + rtcCommit(scene_); + + // This ray is on the "back side" of the ground. So even if the ray passes + // through a triangle, the intersection should not be reported. + const float origin[3] = {0.0f, 0.0f, -0.25f}; + const float direction[3] = {1.0f, 1.0f, 1.0f}; + const float t_near = 0.0f; + const float t_far = AcousticRay::kInfinity; + const float prior_distance = 0.0f; + AcousticRay ray(origin, direction, t_near, t_far, kZeroEnergies, + AcousticRay::RayType::kSpecular, prior_distance); + EXPECT_FALSE(ray.Intersect(scene_)); +} + +TEST_F(AcousticRayTest, IntersectNothingInEmptySceneTest) { + // Commit without adding any geometry. + rtcCommit(scene_); + + // This ray should not intersect anything. + const float origin[3] = {0.0f, 0.0f, 0.25f}; + const float direction[3] = {1.0f, 1.0f, -1.0f}; + const float t_near = 0.0f; + const float t_far = AcousticRay::kInfinity; + const float prior_distance = 0.0f; + AcousticRay ray(origin, direction, t_near, t_far, kZeroEnergies, + AcousticRay::RayType::kSpecular, prior_distance); + EXPECT_FALSE(ray.Intersect(scene_)); +} + +TEST_F(AcousticRayTest, IntersectNothingInUnCommitedSceneTest) { + // Add a ground to the scene but forget to commit. + AddTestGround(scene_); + + // This ray should not intersect anything. + const float origin[3] = {0.0f, 0.0f, 0.25f}; + const float direction[3] = {1.0f, 1.0f, -1.0f}; + const float t_near = 0.0f; + const float t_far = AcousticRay::kInfinity; + const float prior_distance = 0.0f; + AcousticRay ray(origin, direction, t_near, t_far, kZeroEnergies, + AcousticRay::RayType::kSpecular, prior_distance); + EXPECT_FALSE(ray.Intersect(scene_)); +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/acoustic_source.h b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/acoustic_source.h new file mode 100644 index 000000000..b119df76b --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/acoustic_source.h @@ -0,0 +1,111 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_ACOUSTIC_SOURCE_H_ +#define RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_ACOUSTIC_SOURCE_H_ + +#include <array> +#include <functional> +#include <utility> +#include <vector> + +#include "Eigen/Core" +#include "base/constants_and_types.h" +#include "geometrical_acoustics/acoustic_ray.h" +#include "geometrical_acoustics/sampling.h" + +namespace vraudio { + +// A class modeling a sound source. Currently we only support point source +// that emits rays uniformly in all directions. +class AcousticSource { + public: + // Constructor. + // + // @param position Source position. + // @param energies Source energies. This will be the initial energies for + // all frequency bands shared by all rays generated from this source. + // @param random_number_generator Random number generator used to sample + // ray directions. It should implement operator() that returns a + // random value in [0.0, 1.0). + AcousticSource(const Eigen::Vector3f& position, + const std::array<float, kNumReverbOctaveBands>& energies, + std::function<float()> random_number_generator) + : position_(position), + energies_(energies), + random_number_generator_(std::move(random_number_generator)) {} + + // Generates one ray. + // + // @return AcousticRay starting from this source. + AcousticRay GenerateRay() const { + const Eigen::Vector3f& direction = UniformSampleSphere( + random_number_generator_(), random_number_generator_()); + return AcousticRay(position_.data(), direction.data(), 0.0f, + AcousticRay::kInfinity, energies_, + AcousticRay::RayType::kSpecular, 0.0f); + } + + // Generates a vector of rays at once, using stratified sampling. The rays + // generated this way are more "evenly spaced", with fewer holes and clusters. + // + // One caveat: only the whole set of the returned rays is uniformly + // distributed (the expected number of rays found in a finite solid angle + // is the same in any direction), while any subset with fewer than + // |num_rays| rays is not. + // + // In contrast, the GenerateRay() above guarantees any subset is a uniformly + // distributed set of rays. This is why the function is designed to return + // a vector of rays, which are meant to be used as a whole and not partially. + // + // @param num_rays Number of rays; must be equal to |sqrt_num_rays|^2. + // @param sqrt_num_rays The square root of number of rays to emit. + // @return A vector of |sqrt_num_rays|^2 AcousticRays starting from this + // source. + std::vector<AcousticRay> GenerateStratifiedRays(size_t num_rays, + size_t sqrt_num_rays) const { + // To save computation time, it is the caller's responsibility to make sure + // that |num_rays| is equal to |sqrt_num_rays|^2. + DCHECK_EQ(sqrt_num_rays * sqrt_num_rays, num_rays); + + std::vector<AcousticRay> rays; + rays.reserve(num_rays); + for (size_t ray_index = 0; ray_index < num_rays; ++ray_index) { + const Eigen::Vector3f& direction = StratifiedSampleSphere( + random_number_generator_(), random_number_generator_(), sqrt_num_rays, + ray_index); + rays.push_back(AcousticRay(position_.data(), direction.data(), + 0.0f /* t_near */, AcousticRay::kInfinity, + energies_, AcousticRay::RayType::kSpecular, + 0.0f /* prior_distance */)); + } + return rays; + } + + private: + // The position of this point source. + const Eigen::Vector3f position_; + + // The energy of this source. + const std::array<float, kNumReverbOctaveBands> energies_; + + // Randon number generator for sampling ray directions. + std::function<float()> random_number_generator_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_ACOUSTIC_SOURCE_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/acoustic_source_test.cc b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/acoustic_source_test.cc new file mode 100644 index 000000000..aa05a8112 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/acoustic_source_test.cc @@ -0,0 +1,136 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "geometrical_acoustics/acoustic_source.h" + +#include <cmath> +#include <random> +#include <vector> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/constants_and_types.h" +#include "geometrical_acoustics/test_util.h" + +namespace vraudio { + +namespace { + +TEST(AcousticSource, GeneratedRayNonDirectionalDataTest) { + std::default_random_engine engine(0); + std::uniform_real_distribution<float> distribution(0.0f, 1.0f); + const std::array<float, kNumReverbOctaveBands> energies{ + {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f}}; + AcousticSource source({0.1f, 0.2f, 0.3f}, energies, [&engine, &distribution] { + return distribution(engine); + }); + AcousticRay ray = source.GenerateRay(); + + EXPECT_EQ(ray.type(), AcousticRay::RayType::kSpecular); + for (size_t i = 0; i < kNumReverbOctaveBands; ++i) { + EXPECT_FLOAT_EQ(ray.energies().at(i), energies.at(i)); + } + EXPECT_FLOAT_EQ(ray.t_near(), 0.0f); + const float expected_origin[3] = {0.1f, 0.2f, 0.3f}; + ExpectFloat3Close(ray.origin(), expected_origin); +} + +TEST(AcousticSource, GeneratedRayDirectionDistributionTest) { + std::default_random_engine engine(0); + std::uniform_real_distribution<float> distribution(0.0f, 1.0f); + const std::array<float, kNumReverbOctaveBands> energies{ + {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}}; + AcousticSource source({0.0f, 0.0f, 0.0f}, energies, [&engine, &distribution] { + return distribution(engine); + }); + + // Check that the direction vectors on generated rays are all normalized, + // i.e., of unit lengths. + for (size_t i = 0; i < 1000; ++i) { + AcousticRay ray = source.GenerateRay(); + const float direction_squared_norm = + ray.direction()[0] * ray.direction()[0] + + ray.direction()[1] * ray.direction()[1] + + ray.direction()[2] * ray.direction()[2]; + EXPECT_FLOAT_EQ(direction_squared_norm, 1.0f); + } + + // For a ray whose direction is uniformly distributed over a sphere: + // - The PDF of theta is sin(theta), and the CDF is (1 - cos(theta)) / 2. + // - The PDF of phi is 1 / 2 pi, and the CDF is 0.5 + phi / 2 pi. + ValidateDistribution(100000, 100, [&source]() { + AcousticRay ray = source.GenerateRay(); + const float cos_theta = ray.direction()[2]; + return 0.5f * (1.0f - cos_theta); + }); + + ValidateDistribution(100000, 100, [&source]() { + AcousticRay ray = source.GenerateRay(); + const float* direction = ray.direction(); + const float phi = std::atan2(direction[1], direction[0]); + return 0.5f + phi / 2.0f / static_cast<float>(M_PI); + }); +} + +// Similar to GeneratedRayDirectionDistributionTest, but tests on the whole +// set of rays generated by AcousticSource::GenerateStratifiedRays() that they +// are uniformly distributed. +TEST(AcousticSource, GeneratedStratifiedRaysDirectionDistributionTest) { + std::default_random_engine engine(0); + std::uniform_real_distribution<float> distribution(0.0f, 1.0f); + const std::array<float, kNumReverbOctaveBands> energies{ + {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}}; + AcousticSource source({0.0f, 0.0f, 0.0f}, energies, [&engine, &distribution] { + return distribution(engine); + }); + + // Check that the direction vectors on generated rays are all normalized, + // i.e., of unit lengths. + const size_t num_rays = 10000; + const size_t sqrt_num_rays = 100; + std::vector<AcousticRay> rays = + source.GenerateStratifiedRays(num_rays, sqrt_num_rays); + for (const AcousticRay& ray : rays) { + const float direction_squared_norm = + ray.direction()[0] * ray.direction()[0] + + ray.direction()[1] * ray.direction()[1] + + ray.direction()[2] * ray.direction()[2]; + EXPECT_FLOAT_EQ(direction_squared_norm, 1.0f); + } + + // For a ray whose direction is uniformly distributed over a sphere: + // - The PDF of theta is sin(theta), and the CDF is (1 - cos(theta)) / 2. + // - The PDF of phi is 1 / 2 pi, and the CDF is 0.5 + phi / 2 pi. + size_t ray_index = 0; + ValidateDistribution(num_rays, 100, [&rays, &ray_index]() { + const AcousticRay& ray = rays[ray_index]; + ++ray_index; + const float cos_theta = ray.direction()[2]; + return 0.5f * (1.0f - cos_theta); + }); + + ray_index = 0; + ValidateDistribution(num_rays, 100, [&rays, &ray_index]() { + const AcousticRay& ray = rays[ray_index]; + ++ray_index; + const float* direction = ray.direction(); + const float phi = std::atan2(direction[1], direction[0]); + return 0.5f + phi / kTwoPi; + }); +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/collection_kernel.cc b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/collection_kernel.cc new file mode 100644 index 000000000..86b4888b2 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/collection_kernel.cc @@ -0,0 +1,128 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "geometrical_acoustics/collection_kernel.h" + +#include <array> +#include <cmath> +#include <vector> + +#include "Eigen/Core" +#include "base/constants_and_types.h" +#include "base/logging.h" + +namespace vraudio { + +using Eigen::Vector3f; + +namespace { + +// Adds a response to the output |energy_impulse_responses| array at an index +// computed based on |total_source_listener_distance| and +// |distance_to_impulse_response_index|. The values to be added are |energies| +// multiplied by |energy_factor|. +void AddResponse(float total_source_listener_distance, + float distance_to_impulse_response_index, float energy_factor, + const std::array<float, kNumReverbOctaveBands>& energies, + std::array<std::vector<float>, kNumReverbOctaveBands>* + energy_impulse_responses) { + const size_t impulse_response_index = static_cast<size_t>( + total_source_listener_distance * distance_to_impulse_response_index); + + // It is OK if |impulse_response_index| exceeds the size of the listener's + // impulse response array (e.g. if the user only cares about the first second + // of the response), in which case we simply discard the contribution. + if (impulse_response_index >= (*energy_impulse_responses)[0].size()) { + return; + } + + for (size_t i = 0; i < energy_impulse_responses->size(); ++i) { + (*energy_impulse_responses)[i][impulse_response_index] += + energy_factor * energies.at(i); + } +} + +} // namespace + +// In our implementation, |sphere_size_energy_factor_| is defined such that a +// listener 1.0 meter away from the source would give an attenuation of 1.0. +// Therefore the value is computed by: +// 4.0 * PI * 1.0^2 / (PI * R^2) = 4.0 / R^2. +CollectionKernel::CollectionKernel(float listener_sphere_radius, + float sampling_rate) + : sphere_size_energy_factor_(4.0f / listener_sphere_radius / + listener_sphere_radius), + distance_to_impulse_response_index_(sampling_rate / kSpeedOfSound) {} + +void CollectionKernel::Collect(const AcousticRay& ray, float weight, + AcousticListener* listener) const { + CHECK_EQ(ray.energies().size(), listener->energy_impulse_responses.size()); + + // Collect the energy contribution to the listener's impulse response at the + // arrival time. + // The distance to listener on this ray is approximated by projecting + // (listener.position - sub_ray's starting point) onto the ray direction. + const Vector3f ray_direction(ray.direction()); + const Vector3f ray_starting_point = + Vector3f(ray.origin()) + ray.t_near() * ray_direction; + const float distance_to_listener_on_ray = + (listener->position - ray_starting_point).dot(ray_direction.normalized()); + AddResponse(ray.prior_distance() + distance_to_listener_on_ray, + distance_to_impulse_response_index_, + weight * sphere_size_energy_factor_, ray.energies(), + &listener->energy_impulse_responses); +} + +// In a diffuse rain algorithm, instead of relying on the Monte Carlo process +// to estimate expected energy gathered by the sphere, we directly multiply +// the probability of a ray intersecting the sphere to the energy to be +// collected, thus ensuring the expected gathered energies are the same (see +// also [internal ref] +// +// <Energy by Monte Carlo process> = <Energy by diffuse rain> +// sum_i (Prob[ray_i intersects sphere] * Energy_i) = +// sum_i (factor_i * Energy_i) +// +// So factor_i = Prob[ray_i intersects sphere] +// ~ PDF(ray_i in the direction pointing to the listener) * +// (projected solid angle of the listener sphere) +// ~ PDF * PI * R^2 / (4.0 * PI * D^2) +// = PDF * R^2 / (4.0 * D^2), +// +// where PDF is the probability density function, R the radius of the +// listener sphere, and D the distance between the listener and the +// reflection point. +// +// Combining |factor_i| with |sphere_size_energy_factor_| = 4.0 / R^2, +// the total energy factor that needs to be multiplied to the energies on a +// diffuse-rain ray is therefore (PDF / D^2). +void CollectionKernel::CollectDiffuseRain(const AcousticRay& diffuse_rain_ray, + float weight, float direction_pdf, + AcousticListener* listener) const { + // Since a diffuse-rain ray already connects to the listener, and its + // direction already normalized, the distance to listener is its + // t_far - t_near. + const float distance_to_listener_on_ray = + diffuse_rain_ray.t_far() - diffuse_rain_ray.t_near(); + const float diffuse_rain_energy_factor = + direction_pdf / distance_to_listener_on_ray / distance_to_listener_on_ray; + AddResponse(diffuse_rain_ray.prior_distance() + distance_to_listener_on_ray, + distance_to_impulse_response_index_, + weight * diffuse_rain_energy_factor, diffuse_rain_ray.energies(), + &listener->energy_impulse_responses); +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/collection_kernel.h b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/collection_kernel.h new file mode 100644 index 000000000..970cc8dd4 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/collection_kernel.h @@ -0,0 +1,79 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_COLLECTION_KERNEL_H_ +#define RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_COLLECTION_KERNEL_H_ + +#include "geometrical_acoustics/acoustic_listener.h" +#include "geometrical_acoustics/acoustic_ray.h" + +namespace vraudio { + +// A class modeling the collection of energy contributions from an acoustic ray. +class CollectionKernel { + public: + // Constructor. + // + // @param listener_sphere_radius Radius of listener spheres (m). + // @param sampling_rate Sampling rate (Hz). Together with speed of sound we + // can convert a physical time to an index in an impulse response array. + CollectionKernel(float listener_sphere_radius, float sampling_rate); + + // Collects contributions from a ray to a listener's impulse response. + // + // @param ray AcousticRay whose contribution is to be collected. + // @param weight Weight of the contribution on |ray|. + // @param listener AcousticListener to which the contribution from |ray| is + // added in-place. + void Collect(const AcousticRay& ray, float weight, + AcousticListener* listener) const; + + // Collects contributions from a diffuse-rain ray to a listener's impulse + // response. + // + // @param diffuse_rain_ray Diffuse-rain ray whose contribution is to be + // collected. + // @param weight Weight of the contribution on |diffuse_rain_ray|. + // @param direction_pdf The probability density function that a ray is in + // the direction of |diffuse_rain_ray|, with density taken over the solid + // angle. Used to scale the energy contributions. The unit is + // 1 / steradian. + // @param listener AcousticListener to which the contribution from |ray| is + // added in-place. + void CollectDiffuseRain(const AcousticRay& diffuse_rain_ray, float weight, + float direction_pdf, + AcousticListener* listener) const; + + private: + // Because we use a sphere to collect rays, the number of rays (and therefore + // the amount of energy) collected is proportional to the cross section area + // of the sphere, i.e. energy ~ R^2, where R is the sphere radius. But + // because the energy at a listener position should be independent of the + // sphere radius (which is an artificial construct), we need to factor out + // the radius-dependency, by multiplying the sum of energy with a constant, + // |sphere_size_energy_factor_|. + // + // In our implementation, this constant is defined such that a listener + // 1.0 meter away from the source would give an attenuation of 1.0. + const float sphere_size_energy_factor_; + + // Converting traveled distance in meter to an index in the impulse response + // array: index = distance (m) / speed_of_sound (m/s) * sampling_rate (1/s). + const float distance_to_impulse_response_index_; +}; + +} // namespace vraudio +#endif // RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_COLLECTION_KERNEL_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/collection_kernel_test.cc b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/collection_kernel_test.cc new file mode 100644 index 000000000..369c55691 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/collection_kernel_test.cc @@ -0,0 +1,353 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "geometrical_acoustics/collection_kernel.h" + +#include <cmath> +#include <random> +#include <vector> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "Eigen/Core" +#include "base/constants_and_types.h" +#include "geometrical_acoustics/acoustic_listener.h" +#include "geometrical_acoustics/acoustic_ray.h" +#include "geometrical_acoustics/acoustic_source.h" +#include "geometrical_acoustics/sphere.h" +#include "geometrical_acoustics/test_util.h" + +namespace vraudio { + +namespace { + +using Eigen::Vector3f; + +const float kSamplingRateHz = 1000.0f; +const size_t kImpulseResponseNumSamples = 1000; + +class CollectionKernelTest : public testing::Test { + protected: + const std::array<float, kNumReverbOctaveBands> kUnitEnergies{ + {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}}; +}; + +TEST_F(CollectionKernelTest, CollectOneRayTest) { + // A listener at (5, 0, 0). + AcousticListener listener({5.0f, 0.0f, 0.0f}, kImpulseResponseNumSamples); + + // One ray. + const Vector3f origin(0.0f, 0.0f, 0.0f); + const Vector3f direction(1.0f, 0.0f, 0.0f); + const float t_near = 0.0f; + const float t_far = 100.0f; + const float prior_distance = 0.0f; + const AcousticRay ray(origin.data(), direction.data(), t_near, t_far, + kUnitEnergies, AcousticRay::RayType::kSpecular, + prior_distance); + + // Collect impulse response. + const float listener_sphere_radius = 0.1f; + CollectionKernel collection_kernel( + listener_sphere_radius, kSamplingRateHz); + collection_kernel.Collect(ray, 1.0f, &listener); + + // Validate the impulse response. + // The theoretical index of the single non-zero element is: + // floor(distance (m) / kSpeedOfSound (m/s) * kSamplingRate (1/s)). + const float distance = (listener.position - origin).norm(); + const size_t expected_index = static_cast<size_t>( + std::floor(distance / kSpeedOfSound * kSamplingRateHz)); + + // The theoretical energy value is energy * sphere_size_energy_factor. + const float expected_sphere_size_energy_factor = + 4.0f / listener_sphere_radius / listener_sphere_radius; + for (size_t i = 0; i < kNumReverbOctaveBands; ++i) { + ValidateSparseFloatArray( + listener.energy_impulse_responses.at(i), {expected_index}, + {kUnitEnergies.at(i) * expected_sphere_size_energy_factor}, + kEpsilonFloat); + } +} + +TEST_F(CollectionKernelTest, DiscardContributionTest) { + // A listener far away that it takes 2 seconds for the ray to arrive, which + // is longer than the impulse response of interest, i.e. + // kImpulseResponseNumSamples / kSamplingRateHz = 1 second. + // The contribution should be discarded. + AcousticListener listener({kSpeedOfSound * 2.0f, 0.0f, 0.0f}, + kImpulseResponseNumSamples); + + // One ray. + const Vector3f origin(0.0f, 0.0f, 0.0f); + const Vector3f direction(1.0f, 0.0f, 0.0f); + const float t_near = 0.0f; + const float t_far = 100.0f; + const float prior_distance = 0.0f; + const AcousticRay ray(origin.data(), direction.data(), t_near, t_far, + kUnitEnergies, AcousticRay::RayType::kSpecular, + prior_distance); + + // Collect impulse response. + const float listener_sphere_radius = 0.1f; + CollectionKernel collection_kernel( + listener_sphere_radius, kSamplingRateHz); + collection_kernel.Collect(ray, 1.0f, &listener); + + // Validate that the impulse responses are all zero. + for (const std::vector<float>& energy_impulse_response : + listener.energy_impulse_responses) { + ValidateSparseFloatArray(energy_impulse_response, {}, {}, kEpsilonFloat); + } +} + +TEST_F(CollectionKernelTest, CollectOneRayWithPriorDistanceTest) { + // A listener at (5, 0, 0). + AcousticListener listener({5.0f, 0.0f, 0.0f}, kImpulseResponseNumSamples); + + // One ray. + const Vector3f origin(0.0f, 0.0f, 0.0f); + const Vector3f direction(1.0f, 0.0f, 0.0f); + const float t_near = 0.0f; + const float t_far = 100.0f; + const float prior_distance = 10.0f; + const AcousticRay ray(origin.data(), direction.data(), t_near, t_far, + kUnitEnergies, AcousticRay::RayType::kSpecular, + prior_distance); + + // Collect impulse response. + const float listener_sphere_radius = 0.1f; + CollectionKernel collection_kernel( + listener_sphere_radius, kSamplingRateHz); + collection_kernel.Collect(ray, 1.0f, &listener); + + // Validate the impulse response. + // The theoretical index of the single non-zero element is: + // floor(distance (m) / kSpeedOfSound (m/s) * kSamplingRate (1/s)). + // In this test distance = prior_distance + <distance on this ray>. + const float distance = prior_distance + (listener.position - origin).norm(); + const size_t expected_index = static_cast<size_t>( + std::floor(distance / kSpeedOfSound * kSamplingRateHz)); + + // The theoretical energy value is energy * sphere_size_energy_factor. + const float expected_sphere_size_energy_factor = + 4.0f / listener_sphere_radius / listener_sphere_radius; + for (size_t i = 0; i < kNumReverbOctaveBands; ++i) { + ValidateSparseFloatArray( + listener.energy_impulse_responses.at(i), {expected_index}, + {kUnitEnergies.at(i) * expected_sphere_size_energy_factor}, + kEpsilonFloat); + } +} + +TEST_F(CollectionKernelTest, SphereSizeEnergyFactorTest) { + // All listeners are at (0, 1, 0). + const Vector3f listener_position(0.0f, 1.0f, 0.0f); + + // One ray. + const Vector3f origin(0.0f, 0.0f, 0.0f); + const Vector3f direction(0.0f, 1.0f, 0.0f); + const float t_near = 0.0f; + const float t_far = 100.0f; + const float prior_distance = 0.0f; + const AcousticRay ray(origin.data(), direction.data(), t_near, t_far, + kUnitEnergies, AcousticRay::RayType::kSpecular, + prior_distance); + + // All listeners' impulse responses should have a non-zero element at index + // floor(distance (m) / kSpeedOfSound (m/s) * kSamplingRate (1/s)). + const float distance = (listener_position - origin).norm(); + const size_t expected_index = static_cast<size_t>( + std::floor(distance / kSpeedOfSound * kSamplingRateHz)); + + // Collect impulse response using spheres with different radii. + // Expect that the energy values are energy * (4 / radius^2). + for (const float listener_sphere_radius : {0.1f, 0.2f, 0.3f, 0.4f, 0.5f}) { + AcousticListener listener(listener_position, kImpulseResponseNumSamples); + CollectionKernel collection_kernel( + listener_sphere_radius, kSamplingRateHz); + collection_kernel.Collect(ray, 1.0f, &listener); + const float expected_sphere_size_energy_factor = + 4.0f / listener_sphere_radius / listener_sphere_radius; + for (size_t i = 0; i < kNumReverbOctaveBands; ++i) { + ValidateSparseFloatArray( + listener.energy_impulse_responses.at(i), {expected_index}, + {kUnitEnergies.at(i) * expected_sphere_size_energy_factor}, + kEpsilonFloat); + } + } +} + +TEST_F(CollectionKernelTest, CollectMultipleRaysWithWeightsTest) { + // A listener at (0, 0, 1). + AcousticListener listener({0.0f, 0.0f, 1.0f}, kImpulseResponseNumSamples); + + // Add many rays with different energies. + const Vector3f origin(0.0f, 0.0f, 0.0f); + const Vector3f direction(0.0f, 0.0f, 1.0f); + const float t_near = 0.0f; + const float t_far = 100.0f; + const float prior_distance = 2.0f; + std::vector<AcousticRay> rays; + std::array<float, kNumReverbOctaveBands> energies = {}; + for (const float energy : {1.0f, 2.0f, 3.0f, 4.0f, 5.0f}) { + energies.fill(energy); + rays.emplace_back(origin.data(), direction.data(), t_near, t_far, energies, + AcousticRay::RayType::kSpecular, prior_distance); + } + + // Collect impulse responses with different weights. + const float listener_sphere_radius = 0.1f; + CollectionKernel collection_kernel( + listener_sphere_radius, kSamplingRateHz); + const std::vector<float> weights = {8.0f, 4.0f, 2.0f, 1.0f, 0.5f}; + for (size_t i = 0; i < rays.size(); ++i) { + collection_kernel.Collect(rays[i], weights[i], &listener); + } + + // Validate the impulse responses. + // The theoretical index of the single non-zero element is: + // floor(distance (m) / kSpeedOfSound (m/s) * kSamplingRate (1/s)). + const float distance = prior_distance + (listener.position - origin).norm(); + const size_t expected_index = static_cast<size_t>( + std::floor(distance / kSpeedOfSound * kSamplingRateHz)); + + // The theoretical energy value is the sum of + // energy * sphere_size_energy_factor * weight for all rays. + const float expected_sphere_size_energy_factor = + 4.0f / listener_sphere_radius / listener_sphere_radius; + for (size_t i = 0; i < kNumReverbOctaveBands; ++i) { + float expected_energy = 0.0f; + for (size_t j = 0; j < rays.size(); ++j) { + expected_energy += rays[j].energies().at(i) * + expected_sphere_size_energy_factor * weights[j]; + } + ValidateSparseFloatArray(listener.energy_impulse_responses.at(i), + {expected_index}, {expected_energy}, + kEpsilonFloat); + } +} + +// This test emulates a physically meaningful setting: +// 1. N rays are shot in uniformly distributed directions from a source. +// 2. Some rays intersect with a listener sphere and their contributions are +// collected, weighted by 1/N; others miss and do not contribute. +// 3. The total effect of partial collections and the sphere size cancel out, +// so that the energy collected is actually proportional to 1/distance^2. +TEST_F(CollectionKernelTest, CollectRaysInMonteCarloIntegrationTest) { + // A listener at (0, 0, 2). + AcousticListener listener({0.0f, 0.0f, 2.0f}, kImpulseResponseNumSamples); + + // Use AcousticSource to generate N = 100,000 rays with uniformly distributed + // directions. + const Vector3f source_position(0.0f, 0.0f, 0.0f); + const size_t total_num_rays = 100000; + std::vector<AcousticRay> rays; + std::default_random_engine engine(0); + std::uniform_real_distribution<float> distribution(0.0f, 1.0f); + AcousticSource source( + source_position, kUnitEnergies, + [&engine, &distribution] { return distribution(engine); }); + for (size_t i = 0; i < total_num_rays; ++i) { + rays.push_back(source.GenerateRay()); + } + + // Collect impulse responses using 1/N as weights. Only those rays + // intersecting with the listener sphere is collected. + const float listener_sphere_radius = 0.1f; + Sphere listener_sphere; + listener_sphere.center[0] = listener.position[0]; + listener_sphere.center[1] = listener.position[1]; + listener_sphere.center[2] = listener.position[2]; + listener_sphere.radius = listener_sphere_radius; + listener_sphere.geometry_id = 1; + CollectionKernel collection_kernel( + listener_sphere_radius, kSamplingRateHz); + const float monte_carlo_weight = 1.0f / static_cast<float>(total_num_rays); + for (AcousticRay& ray : rays) { + SphereIntersection(listener_sphere, &ray); + if (ray.intersected_geometry_id() == listener_sphere.geometry_id) { + collection_kernel.Collect(ray, monte_carlo_weight, &listener); + } + } + + // Validate the impulse response. + // The theoretical index of the single non-zero element is: + // floor(distance (m) / kSpeedOfSound (m/s) * kSamplingRate (1/s)). + const float distance = (listener.position - source_position).norm(); + const size_t expected_index = static_cast<size_t>( + std::floor(distance / kSpeedOfSound * kSamplingRateHz)); + + // The expected relative error of a Monte Carlo integration is O(1/sqrt(M)), + // where M is the expected number of samples. A listener sphere of radius R + // at a distance D away from the source is expected to intersect + // M = N * (PI * R^2) / (4 * PI * D^2) = 0.25 * N * R^2 /D^2 rays. + // We use 2 / sqrt(M) as the tolerance for relative errors. + const float expected_num_intersecting_rays = + 0.25f * static_cast<float>(total_num_rays) * listener_sphere_radius * + listener_sphere_radius / (distance * distance); + const float relative_error_tolerance = + 2.0f / std::sqrt(expected_num_intersecting_rays); + + for (size_t i = 0; i < kNumReverbOctaveBands; ++i) { + // The theoretical energy value is energy / distance^2. + const float expected_energy = kUnitEnergies.at(i) / (distance * distance); + ValidateSparseFloatArray(listener.energy_impulse_responses.at(i), + {expected_index}, {expected_energy}, + relative_error_tolerance); + } +} + +TEST_F(CollectionKernelTest, CollectOneDiffuseRainRayTest) { + // A listener at (5, 0, 0). + AcousticListener listener({5.0f, 0.0f, 0.0f}, kImpulseResponseNumSamples); + + // A diffuse-rain ray. + const Vector3f origin(0.0f, 0.0f, 0.0f); + const Vector3f direction(1.0f, 0.0f, 0.0f); + const float t_near = 0.0f; + const float t_far = 5.0f; + const float prior_distance = 0.0f; + const AcousticRay diffuse_rain_ray( + origin.data(), direction.data(), t_near, t_far, kUnitEnergies, + AcousticRay::RayType::kDiffuse, prior_distance); + + // Collect impulse response, assuming the ray is reflected with a PDF of 0.1. + const float direction_pdf = 0.1f; + const float listener_sphere_radius = 0.1f; + CollectionKernel collection_kernel(listener_sphere_radius, kSamplingRateHz); + collection_kernel.CollectDiffuseRain(diffuse_rain_ray, direction_pdf, 1.0f, + &listener); + + // Validate the impulse response. + // The theoretical index of the single non-zero element is: + // floor(distance (m) / kSpeedOfSound (m/s) * kSamplingRate (1/s)). + const float distance = (listener.position - origin).norm(); + const size_t expected_index = + static_cast<size_t>(distance / kSpeedOfSound * kSamplingRateHz); + + // The theoretical energy value is energy * energy_factor, where + // energy_factor is PDF / distance^2. + const float expected_energy_factor = direction_pdf / distance / distance; + for (size_t i = 0; i < kNumReverbOctaveBands; ++i) { + ValidateSparseFloatArray( + listener.energy_impulse_responses.at(i), {expected_index}, + {kUnitEnergies.at(i) * expected_energy_factor}, kEpsilonFloat); + } +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/estimating_rt60.cc b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/estimating_rt60.cc new file mode 100644 index 000000000..547f5c1ab --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/estimating_rt60.cc @@ -0,0 +1,155 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include <cmath> + +#include "base/logging.h" +#include "base/misc_math.h" +#include "geometrical_acoustics/estimating_rt60.h" + +namespace vraudio { + +// The RT60 is estimated by fitting a line to the log of energy. We collect +// energy from individual impulse responses into bins and use each bin's +// midpoint and height (average energy inside the bin) as a data point (x, y) +// for the line fitting algorithm. For example, for a bin that collects 100.0 +// units of energy from 0.0s to 2.0s has a midpoint of 1.0s and height of +// log10(100.0 / 2.0). +// +// In general we have some liberty in how we divide the whole reverb tail as +// bins. For example, for a reverb tail of 3.5 seconds long: +// -----------------------------------------------------------------------> +// +// we may divide it into 7 bins of widths 0.5 seconds: +// |---------|---------|---------|---------|---------|---------|---------|> +// 0.0s 0.5s 1.0s 1.5s 2.0s 2.5s 3.0s 3.5s +// +// or we may divide it into 3 bins of widths {0.5s, 1.0s, 2.0s}: +// |---------|-------------------|---------------------------------------|> +// 0.0s 0.5s 1.5s 3.5s +// +// In this implementation, we adopted a scheme that lets the accumulated energy +// determine the width. The first bin would contain 0.5 of the total energy of +// the whole reverb tail, the second bin would contain 0.5 of the remaining +// energy after the first bin, and so on. The number 0.5 can be replaced by any +// value in [0.0, 1.0), which is defined as |kBinEnergyRatio| in our +// implementation. +// +// The advantage of this scheme is that there will not be any empty bin (of +// height zero), which might occur because the impulse responses can be sparse +// at certain regions. An empty bin is a particularly bad data point and +// will greatly reduce the efficacy of the linear fitting. (For a more +// detailed discussion of the sparsity of impulse responses, see [internal ref] +float EstimateRT60(const std::vector<float>& energy_impulse_responses, + const float sampling_rate) { + // First initialize the remaining energy to be the total energy. + float remaining_energy = 0.0f; + for (const float energy : energy_impulse_responses) { + remaining_energy += energy; + } + + // The collected data points: the bin's midpoints and heights. + std::vector<float> bin_midpoints; + std::vector<float> bin_heights; + + // The target energy (as a ratio of the remaining energy) that a bin should + // collect before closing. + const float kBinEnergyRatio = 0.5f; + float target_accumulated_energy = remaining_energy * kBinEnergyRatio; + + // Stop when the remaining energy is below this threshold. + const float kRemainingThreshold = remaining_energy * 1e-3f; + + // The iteration goes as follows: + // 1. A bin is opened. + // 2. Energy is accumulated from impulse responses. + // 3. When the accumulated energy exceeds the target, the bin is closed and + // its midpoint and height recorded. + // 4. Repeat 1-3 until the remaining energy is below a threshold. + float accumulated_energy = 0.0f; + float bin_begin = -1.0f; + for (size_t i = 0; i < energy_impulse_responses.size(); ++i) { + if (energy_impulse_responses[i] <= 0.0f) { + continue; + } + + // The first non-zero response is found; set the |bin_begin|. + if (bin_begin < 0.0f) { + bin_begin = static_cast<float>(i) / sampling_rate; + } + + accumulated_energy += energy_impulse_responses[i]; + + // Close the bin if the accumulated energy meets the target. + if (accumulated_energy > target_accumulated_energy) { + // Compute the bin's midpoint, in the unit of second. + const float bin_end = static_cast<float>(i + 1) / sampling_rate; + const float bin_midpoint = 0.5f * (bin_begin + bin_end); + + // Compute the bin's height as the average energy inside the bin, in the + // unit of dB. + const float bin_width = bin_end - bin_begin; + const float bin_height = + 10.0f * std::log10(accumulated_energy / bin_width); + + // Collect the data point. + bin_midpoints.push_back(bin_midpoint); + bin_heights.push_back(bin_height); + + // Terminate the data point collection when the remaining energy is below + // threshold. + remaining_energy -= accumulated_energy; + if (remaining_energy < kRemainingThreshold) { + break; + } + + // Start a new bin and update the remaining energy. + target_accumulated_energy = remaining_energy * kBinEnergyRatio; + bin_begin = bin_end; + accumulated_energy = 0.0f; + } + } + + // Require at least some data points to perform linear fitting. + const size_t kMinNumDataPointsForFitting = 3; + if (bin_midpoints.size() < kMinNumDataPointsForFitting) { + LOG(WARNING) << "Insufficient number of data points for fitting"; + return 0.0f; + } + + // Perform linear fitting. + float slope = 0.0f; + float intercept = 0.0f; + float r_square = 0.0f; + if (!LinearLeastSquareFitting(bin_midpoints, bin_heights, &slope, &intercept, + &r_square)) { + LOG(WARNING) << "Linear least square fitting failed"; + return 0.0f; + } + LOG(INFO) << "Fitted slope= " << slope << "; intercept= " << intercept + << "; R^2= " << r_square; + + // RT60 is defined as how long it takes for the energy to decay 60 dB. + // Note that |slope| should be negative. + if (slope < 0.0f) { + return -60.0f / slope; + } else { + LOG(WARNING) << "Invalid RT60 from non-negative slope"; + return 0.0f; + } +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/estimating_rt60.h b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/estimating_rt60.h new file mode 100644 index 000000000..6e7d5a12d --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/estimating_rt60.h @@ -0,0 +1,38 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Function to estimate RT60s. + +#ifndef RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_ESTIMATING_RT60_H_ +#define RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_ESTIMATING_RT60_H_ + +#include <vector> + +namespace vraudio { + +// Estimates the RT60 value from collected energy impulse responses. +// +// @param energy_impulse_responses Energy impulse responses. +// @param sampling_rate Sampling rate in Hz. Used to convert indices of the +// impulse response vector to time in seconds. +// @return Estimated RT60 value in seconds. Returns 0.0f (meaning no reverb +// effect at all) when the estimation fails. +float EstimateRT60(const std::vector<float>& energy_impulse_responses, + float sampling_rate); + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_ESTIMATING_RT60_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/estimating_rt60_test.cc b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/estimating_rt60_test.cc new file mode 100644 index 000000000..e48386136 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/estimating_rt60_test.cc @@ -0,0 +1,185 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "geometrical_acoustics/estimating_rt60.h" + +#include <cmath> +#include <memory> +#include <vector> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/constants_and_types.h" +#include "geometrical_acoustics/impulse_response_computer.h" +#include "geometrical_acoustics/test_util.h" +#include "platforms/common/room_effects_utils.h" + +namespace vraudio { + +namespace { + +class EstimateRT60Test : public testing::Test { + public: + EstimateRT60Test() { + Vertex min_corner = {cube_center_[0] - 0.5f * cube_dimensions_[0], + cube_center_[1] - 0.5f * cube_dimensions_[1], + cube_center_[2] - 0.5f * cube_dimensions_[2]}; + Vertex max_corner = {cube_center_[0] + 0.5f * cube_dimensions_[0], + cube_center_[1] + 0.5f * cube_dimensions_[1], + cube_center_[2] + 0.5f * cube_dimensions_[2]}; + + BuildTestBoxScene(min_corner, max_corner, &cube_vertices_, &cube_triangles_, + &cube_wall_triangles_); + } + + protected: + std::vector<float> CollectImpulseResponsesAndEstimateRT60( + const std::vector<Path>& paths, SceneManager* scene_manager) { + // Listener and impulse response computer. + std::unique_ptr<std::vector<AcousticListener>> listeners( + new std::vector<AcousticListener>); + listeners->emplace_back(Eigen::Vector3f(cube_center_), + impulse_response_num_samples_); + ImpulseResponseComputer impulse_response_computer( + listener_sphere_radius_, sampling_rate_, std::move(listeners), + scene_manager); + + // Collect impulse responses. + impulse_response_computer.CollectContributions(paths); + const std::array<std::vector<float>, kNumReverbOctaveBands>& + energy_impulse_responses = + impulse_response_computer.GetFinalizedListeners() + .at(0) + .energy_impulse_responses; + + // Estimate RT60 values. + std::vector<float> output_rt60_values(kNumReverbOctaveBands, 0.0f); + for (size_t band = 0; band < kNumReverbOctaveBands; ++band) { + output_rt60_values[band] = + EstimateRT60(energy_impulse_responses[band], sampling_rate_); + } + return output_rt60_values; + } + + // Ray-tracing related fields. + const float sampling_rate_ = 48000.0f; + const float listener_sphere_radius_ = 0.1f; + const size_t impulse_response_num_samples_ = 96000; + Eigen::Vector3f listener_position_; + + // Data describing a cube scene. + std::vector<Vertex> cube_vertices_; + std::vector<Triangle> cube_triangles_; + const float cube_center_[3] = {0.5f, 0.5f, 0.5f}; + const float cube_dimensions_[3] = {1.0f, 1.0f, 1.0f}; + + std::vector<MaterialName> wall_materials_; + + // Triangles for six walls of a cube. Useful for assigning surface materials. + std::vector<std::unordered_set<unsigned int>> cube_wall_triangles_; +}; + +// Tests that estimating from an empty impulse responses vector fails. +TEST_F(EstimateRT60Test, EstimateFromEmptyImpulseResponsesFails) { + std::vector<float> empty_energy_impulse_responses; + + // Expect that the estimation function returns 0. + EXPECT_EQ(0.0f, EstimateRT60(empty_energy_impulse_responses, sampling_rate_)); +} + +// Tests that if the impulse responses are increasing in energy and therefore +// RT60 is not defined, the estimation returns 0. +TEST_F(EstimateRT60Test, EstimateFromIncreasingEnergyFails) { + std::vector<float> increasing_energy_impulse_responses(1000, 0.0f); + for (size_t i = 0; i < 1000; ++i) { + increasing_energy_impulse_responses[i] = static_cast<float>(i) * 1e-3f; + } + + // Expect that the estimation function returns 0. + EXPECT_EQ(0.0f, + EstimateRT60(increasing_energy_impulse_responses, sampling_rate_)); +} + +// Tests that if estimating from perfectly-constructed, exponentially-decaying +// impulse responses, then the known RT60 is returned. +TEST_F(EstimateRT60Test, EstimateFromExponentiallyDecayingResponses) { + std::vector<float> energy_impulse_responses(1000, 0.0f); + const std::vector<float> expected_RT60s = {0.05f, 0.1f, 0.2f, + 0.5f, 1.0f, 2.0f}; + + for (const float expected_RT60 : expected_RT60s) { + // Construct an exponentially decaying reverb tail with a known RT60, whose + // energy at index i is 10^(6 * i / sampling_rate / RT60). + for (size_t i = 1; i < energy_impulse_responses.size(); ++i) { + energy_impulse_responses[i] = std::pow( + 10.0f, -(static_cast<float>(6 * i) / sampling_rate_ / expected_RT60)); + } + + // Expect that the estimated RT60 is close to the expected one. + EXPECT_NEAR(expected_RT60, + EstimateRT60(energy_impulse_responses, sampling_rate_), 0.01f); + } +} + +// Tests that RT60s estimated from a cube scene agrees with those computed +// using RoomEffectsUtils::ComputeReverbProperties() (which uses Eyring's +// equation under the hood). +TEST_F(EstimateRT60Test, EstimateFromCubeSceneAgreesWithHeuristics) { + wall_materials_ = std::vector<MaterialName>{ + MaterialName::kPlasterSmooth, + MaterialName::kLinoleumOnConcrete, + MaterialName::kConcreteBlockPainted, + MaterialName::kGlassThin, + MaterialName::kBrickBare, + MaterialName::kPlywoodPanel, + }; + + // Trace rays in a cube scene and estimate RT60s. + SceneManager scene_manager; + std::vector<Path> paths = TracePathsInTestcene( + 100 /* min_num_rays */, 100 /* max_depth */, + 1e-12f /* energy_threshold */, cube_center_, cube_vertices_, + cube_triangles_, cube_wall_triangles_, wall_materials_, &scene_manager); + std::vector<float> estimated_rt60_values = + CollectImpulseResponsesAndEstimateRT60(paths, &scene_manager); + + // A default room properties with some fields set to non-default values. + RoomProperties room_properties; + room_properties.position[0] = cube_center_[0]; + room_properties.position[1] = cube_center_[1]; + room_properties.position[2] = cube_center_[2]; + room_properties.dimensions[0] = cube_dimensions_[0]; + room_properties.dimensions[1] = cube_dimensions_[1]; + room_properties.dimensions[2] = cube_dimensions_[2]; + for (size_t wall = 0; wall < kNumRoomSurfaces; ++wall) { + room_properties.material_names[wall] = wall_materials_[wall]; + } + + // Reverb properties computed using heuristics. + const ReverbProperties reverb_properties = + ComputeReverbProperties(room_properties); + + // Compare the two sets of RT60s. + const float rt60_error_tolerance = 0.05f; + for (size_t band = 0; band < kNumReverbOctaveBands; ++band) { + const float estimated_rt60 = estimated_rt60_values[band]; + const float expected_rt60 = reverb_properties.rt60_values[band]; + EXPECT_NEAR(estimated_rt60, expected_rt60, rt60_error_tolerance); + } +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/impulse_response_computer.cc b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/impulse_response_computer.cc new file mode 100644 index 000000000..a4771ed58 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/impulse_response_computer.cc @@ -0,0 +1,188 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "geometrical_acoustics/impulse_response_computer.h" + +#include <utility> + +#include "Eigen/Core" +#include "base/logging.h" +#include "geometrical_acoustics/parallel_for.h" +#include "geometrical_acoustics/reflection_kernel.h" + +namespace vraudio { + +using Eigen::Vector3f; + +ImpulseResponseComputer::ImpulseResponseComputer( + float listener_sphere_radius, float sampling_rate, + std::unique_ptr<std::vector<AcousticListener>> listeners, + SceneManager* scene_manager) + : listeners_(std::move(listeners)), + collection_kernel_(listener_sphere_radius, sampling_rate), + scene_manager_(scene_manager), + finalized_(false), + num_total_paths_(0) { + CHECK_NOTNULL(listeners_.get()); + scene_manager->BuildListenerScene(*listeners_, listener_sphere_radius); +} + +ImpulseResponseComputer::~ImpulseResponseComputer() {} + +void ImpulseResponseComputer::CollectContributions( + const std::vector<Path>& paths_batch) { + // Do not collect anymore if finalized. + if (finalized_) { + return; + } + + CHECK(scene_manager_->is_scene_committed()); + CHECK(scene_manager_->is_listener_scene_committed()); + + const unsigned int num_threads = GetNumberOfHardwareThreads(); + ParallelFor( + num_threads, paths_batch.size(), [&paths_batch, this](size_t path_index) { + const Path& path = paths_batch.at(path_index); + const AcousticRay* previous_ray = nullptr; + AcousticRay diffuse_rain_ray; + for (const AcousticRay& ray : path.rays) { + // Handle diffuse reflections separately using the diffuse rain + // algorithm. For details, see "A Fast Reverberation Estimator for + // Virtual Environments" by Schroder et al., 2007. + if (ray.type() == AcousticRay::RayType::kDiffuse && + previous_ray != nullptr) { + // Connect each listener from the reflection point. + for (AcousticListener& listener : *listeners_) { + const ReflectionKernel& reflection = + scene_manager_->GetAssociatedReflectionKernel( + previous_ray->intersected_primitive_id()); + + float reflection_pdf = 0.0f; + reflection.ReflectDiffuseRain(*previous_ray, ray, + listener.position, &reflection_pdf, + &diffuse_rain_ray); + + if (!diffuse_rain_ray.Intersect(scene_manager_->scene())) { + // Get PDF from the reflection kernel that the previous ray + // intersects. + collection_kernel_.CollectDiffuseRain( + diffuse_rain_ray, 1.0f /* weight */, reflection_pdf, + &listener); + } + } + + previous_ray = &ray; + continue; + } + + // |ray| may intersect multiple listener spheres. We handle the + // intersections one-by-one as following: + // 1. Find the first intersected sphere S. The ray's data will be + // modified so that ray.t_far() corresponds to the intersection + // point. Terminate if no intersection is found. + // 2. Collect contribution to the listener associated to S. + // 3. Spawn a sub-ray that starts at the intersection point (moved + // slightly inside S) and repeat 1. + // + // Since the origin of the new sub-ray is inside sphere S, it does not + // intersect with S again (according to our definition of + // intersections; see Sphere::SphereIntersection()). + // + // Each of the sub-rays has the same origin and direction as the + // original ray, but with t_near and t_far partitioning the interval + // between the original ray's t_near and t_far. For example: + // + // ray: t_near = 0.0 ------------------------------> t_far = 5.0 + // sub_ray_1: t_near = 0.0 -------> t_far = 2.0 + // sub_ray_2: t_near = 2.0 ---------> t_far = 5.0 + AcousticRay sub_ray(ray.origin(), ray.direction(), ray.t_near(), + ray.t_far(), ray.energies(), ray.type(), + ray.prior_distance()); + + // Norm of |ray.direction|. Useful in computing + // |sub_ray.prior_distance| later. + const float ray_direction_norm = Vector3f(ray.direction()).norm(); + + // In theory with sufficient AcousticRay::kRayEpsilon, the same sphere + // should not be intersected twice by the same ray segment. In + // practice, due to floating-point inaccuracy, this might still + // happen. We explicitly prevent double-counting by checking whether + // the intersected sphere id has changed. + unsigned int previous_intersected_sphere_id = RTC_INVALID_GEOMETRY_ID; + + // To prevent an infinite loop, terminate if one ray segment has more + // intersections than the number of listeners, which is an upper bound + // of the number of actually contributing sub-rays. + size_t num_intersections = 0; + while (sub_ray.Intersect(scene_manager_->listener_scene()) && + num_intersections < listeners_->size()) { + const unsigned int sphere_id = sub_ray.intersected_geometry_id(); + if (sphere_id != previous_intersected_sphere_id) { + AcousticListener& listener = listeners_->at( + scene_manager_->GetListenerIndexFromSphereId(sphere_id)); + collection_kernel_.Collect(sub_ray, 1.0f /* weight */, &listener); + } else { + LOG(WARNING) << "Double intersection with sphere[" << sphere_id + << "]; contribution skipped. Consider increasing " + << "AcousticRay::kRayEpsilon"; + } + previous_intersected_sphere_id = sphere_id; + + // Spawn a new sub-ray whose t_near corresponds to the intersection + // point. The new sub-ray's |prior_distance| field is extended by + // the distance traveled by the old sub-ray up to the intersection + // point. + const float new_prior_distance = + sub_ray.prior_distance() + + (sub_ray.t_far() - sub_ray.t_near()) * ray_direction_norm; + sub_ray = AcousticRay(ray.origin(), ray.direction(), + sub_ray.t_far() + AcousticRay::kRayEpsilon, + ray.t_far(), ray.energies(), ray.type(), + new_prior_distance); + ++num_intersections; + } + + previous_ray = &ray; + } + }); + num_total_paths_ += paths_batch.size(); +} + +const std::vector<AcousticListener>& +ImpulseResponseComputer::GetFinalizedListeners() { + DCHECK_GT(num_total_paths_, 0U); + + if (!finalized_) { + // For a Monte Carlo method that estimates a value with N samples, + // <estimated value> = 1/N * sum(<value of a sample>). We apply the + // 1/N weight after all impulse responses for all listeners are collected. + const float monte_carlo_weight = + 1.0f / static_cast<float>(num_total_paths_); + for (AcousticListener& listener : *listeners_) { + for (std::vector<float>& responses_in_band : + listener.energy_impulse_responses) { + for (float& response : responses_in_band) { + response *= monte_carlo_weight; + } + } + } + finalized_ = true; + } + + return *listeners_; +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/impulse_response_computer.h b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/impulse_response_computer.h new file mode 100644 index 000000000..59f3d91a0 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/impulse_response_computer.h @@ -0,0 +1,112 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_IMPULSE_RESPONSE_COMPUTER_H_ +#define RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_IMPULSE_RESPONSE_COMPUTER_H_ + +#include <memory> +#include <vector> + +#include "geometrical_acoustics/acoustic_listener.h" +#include "geometrical_acoustics/collection_kernel.h" +#include "geometrical_acoustics/path.h" +#include "geometrical_acoustics/scene_manager.h" + +namespace vraudio { + +// A class that computes impulse responses from traced sound propagation paths +// and modifies the listener's data (namely listener.energy_impulse_response) +// in place. +// Each listener is associated to a sphere with finite volume, and rays that +// intersect with the sphere contributes energy to the listener. +// +// This class is to be used in conjunction with a PathTracer, which produces +// batches of sound propagation paths that this class consumes. +// Example use (which follows a create-collect-finalize pattern): +// +// // Create. +// ImpulseResponseComputer impulse_response_computer(0.1f, 44100.0f, +// std::move(listeners)); +// // Collect. +// for (const auto& source : sources) { +// // Divide |total_num_paths| into |num_batch| batches, each of size +// // |num_rays_per_batch|. +// for (size_t i = 0; i < num_batch; ++i) { +// paths_batch = +// path_tracer.TracePaths(source, num_rays_per_batch, 10, 1e-6); +// impulse_response_computer.CollectContributions(paths_batch, +// total_num_paths); +// } +// } +// +// // Finalize and use the impulse responses in listeners e.g. write them to +// // a file or pass them to an audio render. +// for (const auto& listener: +// impulse_response_computer. GetFinalizedListeners()) { +// const auto& responses = listener.energy_impulse_responses; +// // Do something with |responses|. +// } +class ImpulseResponseComputer { + public: + // Constructor. + // + // @param listener_sphere_radius Radius of listener spheres (m). + // @param sampling_rate Sampling rate (Hz). + // @param listeners Vector of AcousticListener's whose impulse responses are + // to be computed. + // @param scene_manager SceneManager. + ImpulseResponseComputer( + float listener_sphere_radius, float sampling_rate, + std::unique_ptr<std::vector<AcousticListener>> listeners, + SceneManager* scene_manager); + virtual ~ImpulseResponseComputer(); + + // Collects contributions from a batch of paths to all listeners if + // the collection is not finalized yet. + // + // @param paths_batch All sound propagation paths in a batch. + void CollectContributions(const std::vector<Path>& paths_batch); + + // Finalizes the listeners and returns them. After calling this, further + // calls to CollectContributions() have no effect. + // + // @return Vector of finalized listeners. + const std::vector<AcousticListener>& GetFinalizedListeners(); + + private: + // Vector of listeners. + const std::unique_ptr<std::vector<AcousticListener>> listeners_; + + // Collection kernel used to collect contributions from rays to listeners. + CollectionKernel collection_kernel_; + + // Scene manager. Used to keep records of listener spheres for ray-sphere + // intersection tests. Also used in the diffuse rain algorithm to to test + // whether there is an un-obstructed path from a reflection point to a + // listener. + SceneManager* scene_manager_; + + // Is the collection finalized. + bool finalized_; + + // Total number of paths (contributing or not) that energies are collected + // from before the collection is finalized. This will be used to average + // energy contributions. + size_t num_total_paths_; +}; + +} // namespace vraudio +#endif // RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_IMPULSE_RESPONSE_COMPUTER_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/impulse_response_computer_test.cc b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/impulse_response_computer_test.cc new file mode 100644 index 000000000..f3e042586 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/impulse_response_computer_test.cc @@ -0,0 +1,377 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "geometrical_acoustics/impulse_response_computer.h" + +#include <array> +#include <cmath> +#include <random> +#include <utility> +#include <vector> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/constants_and_types.h" +#include "geometrical_acoustics/acoustic_listener.h" +#include "geometrical_acoustics/acoustic_ray.h" +#include "geometrical_acoustics/acoustic_source.h" +#include "geometrical_acoustics/path.h" +#include "geometrical_acoustics/scene_manager.h" +#include "geometrical_acoustics/test_util.h" + +namespace vraudio { + +namespace { + +using Eigen::Vector3f; + +const float kSamplingRateHz = 1000.0f; +const float kListenerSphereRadiusMeter = 0.1f; +const size_t kImpulseResponseNumSamples = 1000; + +// The energy collected from a ray. +// kCollectedEnergyPerRay +// = 1.0 * CollectionKernel::sphere_size_energy_factor_ +// = 1.0 * 4.0 / kListenerSphereRadius^2 = 1.0 * 4.0 / 0.01 +// = 400.0. +// See CollectionKernel::sphere_size_energy_factor_ for details. +const float kCollectedEnergyPerRay = 400.0f; + +class ImpulseResponseComputerTest : public testing::Test { + public: + void SetUp() override { paths_.clear(); } + + protected: + void AddListenersAtPositions(const std::vector<Vector3f>& positions, + std::vector<AcousticListener>* listeners) { + for (const Vector3f& position : positions) { + listeners->emplace_back(position, kImpulseResponseNumSamples); + } + } + + // Validate indices and values of the impulse responses for all listeners. + // + // param@ listeners Listeners whose impulse responses are to be validated. + // param@ expected_indices_for_listeners Expected indices of the non-zero + // elements for all listeners' impulse responses. So that + // expected_indices_for_listeners[i][j] is the index of the j-th non-zero + // element in listener i's impulse response. + // param@ expected_energies_for_listeners Expected energy values of the + // non-zero elements for all listeners' impulse responses. So that + // the value stored in expected_energies_for_listeners[i][j] is the j-th + // non-zero element in listener i's impulse response. + // param@ relative_error_tolerance_for_listeners Tolerances of relative errors + // when comparing energy impulse responses for all listeners. + void ValidateImpulseResponses( + const std::vector<AcousticListener>& listeners, + const std::vector<std::vector<size_t>>& expected_indices_for_listeners, + const std::vector<std::vector<float>>& expected_energies_for_listeners, + const std::vector<float> relative_error_tolerances_for_listeners) { + ASSERT_EQ(expected_indices_for_listeners.size(), listeners.size()); + ASSERT_EQ(expected_energies_for_listeners.size(), listeners.size()); + ASSERT_EQ(relative_error_tolerances_for_listeners.size(), listeners.size()); + for (size_t i = 0; i < listeners.size(); ++i) { + for (size_t j = 0; j < kNumReverbOctaveBands; ++j) { + ValidateSparseFloatArray(listeners[i].energy_impulse_responses.at(j), + expected_indices_for_listeners[i], + expected_energies_for_listeners[i], + relative_error_tolerances_for_listeners[i]); + } + } + } + + const std::array<float, kNumReverbOctaveBands> kUnitEnergies{ + {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}}; + + std::vector<Path> paths_; + SceneManager scene_manager_; +}; + +TEST_F(ImpulseResponseComputerTest, OnePathMultipleRaysOneListenerTest) { + // A listener at (1, 0, 0). + std::unique_ptr<std::vector<AcousticListener>> listeners( + new std::vector<AcousticListener>); + const Vector3f listener_position(1.0f, 0.0f, 0.0f); + AddListenersAtPositions({listener_position}, listeners.get()); + + scene_manager_.BuildScene({}, {}); + ImpulseResponseComputer impulse_response_computer( + kListenerSphereRadiusMeter, kSamplingRateHz, std::move(listeners), + &scene_manager_); + + // One path with multiple ray segments. + Path path; + + // Imagine a scene with two specular walls parallel to the y-z plane. + // One (called the right wall) is at (2, 0, 0) and the other (called the + // left wall) at (-2, 0, 0). A ray shooting at the +x direction will reflect + // back-and-forth between these parallel walls. + // + // First ray segment: from source at (0, 0, 0), shooting at (1, 0, 0). + const Vector3f source_position(0.0f, 0.0f, 0.0f); + const Vector3f left_to_right_direction(1.0f, 0.0f, 0.0f); + const float wall_position_x = 2.0f; + path.rays.emplace_back(source_position.data(), left_to_right_direction.data(), + 0.0f, wall_position_x, kUnitEnergies, + AcousticRay::RayType::kSpecular, 0.0f); + + // Also record the distances traveled each time the ray passes through the + // listener sphere. This will be used later to verify the indices of non-zero + // elements in the impulse response. + std::vector<float> source_listener_distances = { + (source_position - listener_position).norm()}; // First segment. + + // The rest of the reflections. Half of these rays start from the left wall + // and stop at the right wall, and the other half start from the right wall + // and stop at the left wall. We compute up to order |max_order| = 10. + const Vector3f right_wall_origin(wall_position_x, 0.0f, 0.0f); + const Vector3f left_wall_origin(-wall_position_x, 0.0f, 0.0f); + const float distance_between_walls = + (right_wall_origin - left_wall_origin).norm(); + const float listener_left_wall_distance = + (listener_position - left_wall_origin).norm(); + const float listener_right_wall_distance = + (listener_position - right_wall_origin).norm(); + const Vector3f right_to_left_direction = -left_to_right_direction; + const size_t max_order = 10; + + // Prior distance corresponds to the first ray segment. + float prior_distance = (right_wall_origin - source_position).norm(); + for (size_t order = 1; order < max_order; ++order) { + float source_listener_distance = source_listener_distances.back(); + if (order % 2 == 0) { + path.rays.emplace_back(left_wall_origin.data(), + left_to_right_direction.data(), 0.0f, + distance_between_walls, kUnitEnergies, + AcousticRay::RayType::kSpecular, prior_distance); + + // Add the distance of {listener -> left wall -> listener} to the + // accumulated source-listener distance. + source_listener_distance += 2.0f * listener_left_wall_distance; + } else { + path.rays.emplace_back(right_wall_origin.data(), + right_to_left_direction.data(), 0.0f, + distance_between_walls, kUnitEnergies, + AcousticRay::RayType::kSpecular, prior_distance); + + // Add the distance of {listener -> right wall -> listener} to the + // accumulated source-listener distance. + source_listener_distance += 2.0f * listener_right_wall_distance; + } + source_listener_distances.push_back(source_listener_distance); + + // Each reflection adds |distance_between_walls| to |prior_distance|. + prior_distance += distance_between_walls; + } + paths_.push_back(path); + + // Compute impulse response. + impulse_response_computer.CollectContributions(paths_); + + // Validate the impulse responses. + // The expected indices are + // floor(distance / kSpeedOfSound (m/s) * kSamplingRateHz (1/s)). + // The theoretical energy values are all kCollectedEnergyPerRay. + std::vector<std::vector<size_t>> expected_indices_for_listeners(1); + std::vector<std::vector<float>> expected_energies_for_listeners(1); + for (size_t order = 0; order < max_order; ++order) { + expected_indices_for_listeners.back().push_back( + static_cast<size_t>(std::floor(source_listener_distances[order] / + kSpeedOfSound * kSamplingRateHz))); + expected_energies_for_listeners.back().push_back(kCollectedEnergyPerRay); + } + ValidateImpulseResponses(impulse_response_computer.GetFinalizedListeners(), + expected_indices_for_listeners, + expected_energies_for_listeners, {kEpsilonFloat}); +} + +TEST_F(ImpulseResponseComputerTest, OnePathMultipleListenersTest) { + // A series of listeners along the z-axis. Some of them overlapping. + std::unique_ptr<std::vector<AcousticListener>> listeners( + new std::vector<AcousticListener>); + AddListenersAtPositions({{0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 2.0f}, + {0.0f, 0.0f, 3.0f}, + {0.0f, 0.0f, 3.05f}, + {0.0f, 0.0f, 3.10f}}, + listeners.get()); + + scene_manager_.BuildScene({}, {}); + ImpulseResponseComputer impulse_response_computer( + kListenerSphereRadiusMeter, kSamplingRateHz, std::move(listeners), + &scene_manager_); + + // One path with only one ray. This ray should pass through all listener + // spheres. + const Vector3f origin(0.0f, 0.0f, 0.0f); + const Vector3f direction(0.0f, 0.0f, 1.0f); + Path path; + path.rays.emplace_back(origin.data(), direction.data(), 0.0f, 100.0f, + kUnitEnergies, AcousticRay::RayType::kSpecular, 0.0f); + paths_.push_back(path); + + // Compute impulse response. + impulse_response_computer.CollectContributions(paths_); + + // Validate the impulse responses. + // The theoretical indices are + // floor(distance / kSpeedOfSound (m/s) * kSamplingRateHz (1/s)). + // The theoretical energy values are all kCollectedEnergyPerRay. + std::vector<std::vector<size_t>> expected_indices_for_listeners; + std::vector<std::vector<float>> expected_energies_for_listeners; + std::vector<float> relative_error_tolerances_for_listeners; + for (const AcousticListener& listener : + impulse_response_computer.GetFinalizedListeners()) { + const float distance = (listener.position - origin).norm(); + expected_indices_for_listeners.push_back({static_cast<size_t>( + std::floor(distance / kSpeedOfSound * kSamplingRateHz))}); + expected_energies_for_listeners.push_back({kCollectedEnergyPerRay}); + relative_error_tolerances_for_listeners.push_back(kEpsilonFloat); + } + ValidateImpulseResponses(impulse_response_computer.GetFinalizedListeners(), + expected_indices_for_listeners, + expected_energies_for_listeners, + relative_error_tolerances_for_listeners); +} + +// Test that the energies collected at different positions is proportional to +// 1/D^2, where D is the source-listener distance. +TEST_F(ImpulseResponseComputerTest, + EnergyInverselyProportionalDistanceSquaredTest) { + // A series of listeners along the y-axis. + std::unique_ptr<std::vector<AcousticListener>> listeners( + new std::vector<AcousticListener>); + AddListenersAtPositions( + { + {0.0f, 1.0f, 0.0f}, + {0.0f, 1.2f, 0.0f}, + {0.0f, 1.4f, 0.0f}, + {0.0f, 1.6f, 0.0f}, + {0.0f, 1.8f, 0.0f}, + {0.0f, 2.0f, 0.0f}, + {0.0f, 2.5f, 0.0f}, + {0.0f, 3.0f, 0.0f}, + }, + listeners.get()); + + scene_manager_.BuildScene({}, {}); + ImpulseResponseComputer impulse_response_computer( + kListenerSphereRadiusMeter, kSamplingRateHz, std::move(listeners), + &scene_manager_); + + // Add 10,000 paths from rays with uniformly distributed directions. + const size_t min_num_paths = 10000; + const Vector3f source_position(0.0f, 0.0f, 0.0f); + paths_ = GenerateUniformlyDistributedRayPaths(source_position.data(), + min_num_paths); + + // Compute impulse response. + impulse_response_computer.CollectContributions(paths_); + + // Check the index and value of the single non-zero element for each listener. + std::vector<std::vector<size_t>> expected_indices_for_listeners; + std::vector<std::vector<float>> expected_energies_for_listeners; + std::vector<float> relative_error_tolerances_for_listeners; + for (const AcousticListener& listener : + impulse_response_computer.GetFinalizedListeners()) { + // The theoretical index is + // floor(distance / kSpeedOfSound (m/s) * kSamplingRateHz (1/s)). + const float distance = (listener.position - source_position).norm(); + expected_indices_for_listeners.push_back({static_cast<size_t>( + std::floor(distance / kSpeedOfSound * kSamplingRateHz))}); + + // The theoretical energy value is 1.0 / distance^2. + expected_energies_for_listeners.push_back({1.0f / (distance * distance)}); + + // The expected relative error of a Monte Carlo integration is O(1/sqrt(M)), + // where M is the expected number of samples. A listener sphere of radius R + // at a distance D away from the source is expected to intersect + // M = N * (PI * R^2) / (4 * PI * D^2) = 0.25 * N * R^2 /D^2 rays. + // We use 2 / sqrt(M) as the tolerance for relative errors. + const float expected_num_intersecting_rays = + 0.25f * static_cast<float>(paths_.size()) * kListenerSphereRadiusMeter * + kListenerSphereRadiusMeter / (distance * distance); + relative_error_tolerances_for_listeners.push_back( + 2.0f / std::sqrt(expected_num_intersecting_rays)); + } + ValidateImpulseResponses(impulse_response_computer.GetFinalizedListeners(), + expected_indices_for_listeners, + expected_energies_for_listeners, + relative_error_tolerances_for_listeners); +} + +// Tests that collecting after GetFinalizedListeners() is called has no effect. +TEST_F(ImpulseResponseComputerTest, CollectingAfterFinalizeHasNoEffect) { + // A listener at (1, 0, 0). + std::unique_ptr<std::vector<AcousticListener>> listeners( + new std::vector<AcousticListener>); + const Vector3f listener_position(1.0f, 0.0f, 0.0f); + AddListenersAtPositions({listener_position}, listeners.get()); + + scene_manager_.BuildScene({}, {}); + ImpulseResponseComputer impulse_response_computer( + kListenerSphereRadiusMeter, kSamplingRateHz, std::move(listeners), + &scene_manager_); + + // One path with only one ray. This ray should pass through the listener + // sphere. + const Vector3f origin(0.0f, 0.0f, 0.0f); + const Vector3f direction(1.0f, 0.0f, 0.0f); + Path path; + path.rays.emplace_back(origin.data(), direction.data(), 0.0f, 100.0f, + kUnitEnergies, AcousticRay::RayType::kSpecular, 0.0f); + paths_.push_back(path); + impulse_response_computer.CollectContributions(paths_); + + // Finalize the listeners and make a copy of the energy impulse responses. + const std::array<std::vector<float>, kNumReverbOctaveBands> + old_energy_impulse_responses = + impulse_response_computer.GetFinalizedListeners() + .at(0) + .energy_impulse_responses; + + // Try to collect another set of paths. This would change the energies if + // they were collected, but they are not because the collection is finalized. + paths_.clear(); + const Vector3f another_origin(1.0f, -1.0f, 0.0f); + const Vector3f another_direction(0.0f, 1.0f, 0.0f); + Path another_path; + another_path.rays.emplace_back( + another_origin.data(), another_direction.data(), 0.0f, 100.0f, + kUnitEnergies, AcousticRay::RayType::kSpecular, 0.0f); + paths_.push_back(another_path); + impulse_response_computer.CollectContributions(paths_); + + // Verify that the energy impulse responses are the same as the copy. + const std::array<std::vector<float>, kNumReverbOctaveBands>& + new_energy_impulse_responses = + impulse_response_computer.GetFinalizedListeners() + .at(0) + .energy_impulse_responses; + for (size_t band = 0; band < kNumReverbOctaveBands; ++band) { + const std::vector<float>& old_responses_in_band = + old_energy_impulse_responses[band]; + const std::vector<float>& new_responses_in_band = + new_energy_impulse_responses[band]; + for (size_t index = 0; index < old_responses_in_band.size(); ++index) { + EXPECT_FLOAT_EQ(old_responses_in_band[index], + new_responses_in_band[index]); + } + } +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/mesh.h b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/mesh.h new file mode 100644 index 000000000..5582bcdfc --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/mesh.h @@ -0,0 +1,40 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Collection of data structures useful to define a mesh. + +#ifndef RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_MESH_H_ +#define RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_MESH_H_ + +namespace vraudio { + +// A simple vertex data structure. +struct Vertex { + float x; + float y; + float z; +}; + +// A simple triangle data structure defined as the 3 indices of vertices. +struct Triangle { + int v0; + int v1; + int v2; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_MESH_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/parallel_for.cc b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/parallel_for.cc new file mode 100644 index 000000000..bc5eef2db --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/parallel_for.cc @@ -0,0 +1,44 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "geometrical_acoustics/parallel_for.h" + +#include <condition_variable> +#include <memory> +#include <mutex> +#include <vector> + +#include "base/logging.h" +#include "utils/task_thread_pool.h" + +namespace vraudio { + +void ParallelFor(unsigned int num_threads, size_t num_iterations, + const std::function<void(const size_t)>& function) { + TaskThreadPool worker_thread_pool; + CHECK(worker_thread_pool.StartThreadPool(num_threads)); + + for (size_t i = 0; i < num_iterations; ++i) { + while (!worker_thread_pool.WaitUntilWorkerBecomesAvailable()) { + } + + const TaskThreadPool::TaskClosure task = [i, &function]() { function(i); }; + CHECK(worker_thread_pool.RunOnWorkerThread(task)); + } + worker_thread_pool.StopThreadPool(); +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/parallel_for.h b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/parallel_for.h new file mode 100644 index 000000000..d2f6951c1 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/parallel_for.h @@ -0,0 +1,53 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_PARALLEL_FOR_H_ +#define RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_PARALLEL_FOR_H_ + +#include <algorithm> +#include <functional> +#include <thread> + +namespace vraudio { + +// Gets the number of hardware threads; always returns more than zero. +// +// @return Number of hardware threads. +inline unsigned int GetNumberOfHardwareThreads() { + // According to the standard, hardware_concurrency() may return zero if "this + // value is not computable or well defined". In that case, we want to have a + // thread count of one (instead of zero). + return std::max(1U, std::thread::hardware_concurrency()); +} + +// Repeatedly executes |function| multiple times, specified by |num_iterations|. +// Different executions of |function| may occur on one of the |num_threads| +// threads. +// +// |function| has a function signature of void(const size_t i), with |i| taking +// values in the range [0, |num_iterations|). +// +// @param num_threads Number of threads to execute |function|. +// @param num_iterations Number of iterations for this for loop. +// @param function Function to be run. The function should take a single +// parameter that is of type const size_t, representing the iteration +// index, and no return value. +void ParallelFor(unsigned int num_threads, size_t num_iterations, + const std::function<void(const size_t)>& function); + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_PARALLEL_FOR_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/parallel_for_test.cc b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/parallel_for_test.cc new file mode 100644 index 000000000..2e1ec563a --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/parallel_for_test.cc @@ -0,0 +1,65 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "geometrical_acoustics/parallel_for.h" + +#include <array> +#include <atomic> +#include <condition_variable> +#include <mutex> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" + +namespace vraudio { + +namespace { + +// Tests multi-threaded increment of a single variable, using increasing numbers +// of threads. +TEST(ParallelForTest, IncreasingThreadCounts) { + const size_t kNumIterations = 1000; + for (unsigned int num_threads = 1; num_threads < 16; ++num_threads) { + std::mutex mutex; + std::condition_variable condition_variable; + size_t index = 0; + ParallelFor(num_threads, kNumIterations, [&](const size_t i) { + std::unique_lock<std::mutex> lock(mutex); + while (i != index) { + condition_variable.wait(lock); + } + ++index; + condition_variable.notify_all(); + }); + EXPECT_EQ(kNumIterations, index); + } +} + +// Tests recursive use of ParallelFor(). +TEST(ParallelForTest, RecursiveParallelFor) { + std::atomic<int> counter(0); + ParallelFor(16U, 16, [&](const size_t i) { + ParallelFor(16U, 16, [&](const size_t j) { + for (int k = 0; k < 16; ++k) { + ++counter; + } + }); + }); + EXPECT_EQ(16 * 16 * 16, counter.load()); +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/path.h b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/path.h new file mode 100644 index 000000000..f6e3d1681 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/path.h @@ -0,0 +1,36 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_PATH_H_ +#define RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_PATH_H_ + +#include <vector> + +#include "geometrical_acoustics/acoustic_ray.h" + +namespace vraudio { + +// A simple data structure of modeling a sound propagation path as a vector of +// ray segments, which contain all necessary information. For example, the +// history of triangles on this path can be found by |ray.primID|, where |ray| +// is an element |rays|. +struct Path { + std::vector<AcousticRay> rays; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_PATH_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/path_tracer.cc b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/path_tracer.cc new file mode 100644 index 000000000..6f4c6652a --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/path_tracer.cc @@ -0,0 +1,96 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "geometrical_acoustics/path_tracer.h" + +#include <cmath> + +#include "base/logging.h" +#include "geometrical_acoustics/parallel_for.h" +#include "geometrical_acoustics/reflection_kernel.h" + +namespace vraudio { + +std::vector<Path> PathTracer::TracePaths(const AcousticSource& source, + size_t min_num_rays, size_t max_depth, + float energy_threshold) { + // The tracing would not work if the scene is not committed. + CHECK(scene_manager_.is_scene_committed()); + + // Find the actual number of rays to trace as the next square number greater + // or equal to |min_num_rays|. + const size_t sqrt_num_rays = static_cast<size_t>( + std::ceil(std::sqrt(static_cast<float>(min_num_rays)))); + const size_t num_rays = sqrt_num_rays * sqrt_num_rays; + + // In the current implementation, one ray does not spawn more than one child + // ray, hence the number of paths is the same as the number of rays from the + // source. + std::vector<Path> paths(num_rays); + std::vector<AcousticRay> rays_from_source = + source.GenerateStratifiedRays(num_rays, sqrt_num_rays); + const unsigned int num_threads = GetNumberOfHardwareThreads(); + ParallelFor( + num_threads, num_rays, + [&rays_from_source, &paths, this, max_depth, + energy_threshold](size_t ray_index) { + if (max_depth == 0) return; + Path& path = paths.at(ray_index); + + // Pre-allocate memory space for better performance. + path.rays.reserve(max_depth); + + path.rays.push_back(rays_from_source[ray_index]); + size_t depth = 0; + while (true) { + AcousticRay& current_ray = path.rays.back(); + + // Stop generating new rays if the current ray escapes. + if (!current_ray.Intersect(scene_manager_.scene())) { + break; + } + + // Stop generating new rays if |depth| reaches |max_depth|. + ++depth; + if (depth >= max_depth) { + break; + } + + // Handle interactions with scene geometries. + const ReflectionKernel& reflection = + scene_manager_.GetAssociatedReflectionKernel( + current_ray.intersected_primitive_id()); + AcousticRay new_ray = reflection.Reflect(current_ray); + + // Stop tracing if all energies in all frequency bands of the new ray + // are too low in energy. + bool is_energy_high_enough = false; + for (const float energy : new_ray.energies()) { + if (energy >= energy_threshold) { + is_energy_high_enough = true; + break; + } + } + if (!is_energy_high_enough) { + break; + } + path.rays.push_back(new_ray); + } + }); + return paths; +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/path_tracer.h b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/path_tracer.h new file mode 100644 index 000000000..4a8a35a7e --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/path_tracer.h @@ -0,0 +1,59 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_PATH_TRACER_H_ +#define RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_PATH_TRACER_H_ + +#include <functional> +#include <vector> + +#include "geometrical_acoustics/acoustic_source.h" +#include "geometrical_acoustics/path.h" +#include "geometrical_acoustics/scene_manager.h" + +namespace vraudio { + +// A wrapper around Embree that finds sound propagation paths from a source. +class PathTracer { + public: + // Constructor. + // + // @param scene_manager Scene manager. + explicit PathTracer(const SceneManager& scene_manager) + : scene_manager_(scene_manager) {} + ~PathTracer() {} + + // Traces sound propagation paths from a source. + // + // @param source Source from which paths are traced. + // @param min_num_rays Minimum number of rays to be traced. + // @param max_depth Maximum depth of tracing performed along a path. The + // tracing stops when reading |max_depth| interactions with the scene + // geometries. + // @param energy_threshold Energy threshold below which the tracing stops. + // @return Vector of at least |min_num_rays| traced paths. + std::vector<Path> TracePaths(const AcousticSource& source, + size_t min_num_rays, size_t max_depth, + float energy_threshold); + + private: + // Scene manager. + const SceneManager& scene_manager_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_PATH_TRACER_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/path_tracer_test.cc b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/path_tracer_test.cc new file mode 100644 index 000000000..a1cb67395 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/path_tracer_test.cc @@ -0,0 +1,193 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "geometrical_acoustics/path_tracer.h" + +#include <memory> +#include <random> +#include <unordered_set> +#include <vector> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "geometrical_acoustics/mesh.h" +#include "geometrical_acoustics/test_util.h" + +namespace vraudio { + +namespace { + +using Eigen::Vector3f; + +class PathTracerTest : public testing::Test { + public: + PathTracerTest() : random_engine_(), distribution_(0.0f, 1.0f) { + random_number_generator_ = [this] { return distribution_(random_engine_); }; + } + + void BuildEmptyScene() { + scene_manager_.reset(new SceneManager); + scene_manager_->BuildScene(std::vector<Vertex>(), std::vector<Triangle>()); + all_triangle_indices_.clear(); + } + + void BuildGroundScene() { + scene_manager_.reset(new SceneManager); + std::vector<Vertex> ground_vertices{{0.0f, 0.0f, 0.0f}, + {0.0f, 1.0f, 0.0f}, + {1.0f, 0.0f, 0.0f}, + {1.0f, 1.0f, 0.0f}}; + std::vector<Triangle> ground_triangles{{0, 3, 1}, {0, 2, 3}}; + scene_manager_->BuildScene(ground_vertices, ground_triangles); + all_triangle_indices_ = {0, 1}; + } + + void BuildBoxScene() { + scene_manager_.reset(new SceneManager); + std::vector<Vertex> box_vertices; + std::vector<Triangle> box_triangles; + BuildTestBoxScene({0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, &box_vertices, + &box_triangles, nullptr); + scene_manager_->BuildScene(box_vertices, box_triangles); + all_triangle_indices_.clear(); + for (unsigned int i = 0; i < 12; ++i) { + all_triangle_indices_.insert(i); + } + } + + protected: + const std::array<float, kNumReverbOctaveBands> kZeroAbsorptionCoefficients = { + {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}}; + std::unique_ptr<SceneManager> scene_manager_; + std::unordered_set<unsigned int> all_triangle_indices_; + std::default_random_engine random_engine_; + std::uniform_real_distribution<float> distribution_; + std::function<float()> random_number_generator_; +}; + +TEST_F(PathTracerTest, AllPathsHaveLengthOneInEmptySceneTest) { + BuildEmptyScene(); + PathTracer path_tracer(*scene_manager_); + + AcousticSource source({0.0f, 0.0f, 0.0f}, kUnitEnergies, + random_number_generator_); + const size_t min_num_rays = 100; + const size_t max_depth = 10; + const float energy_thresold = 1e-6f; + std::vector<Path> paths = + path_tracer.TracePaths(source, min_num_rays, max_depth, energy_thresold); + + EXPECT_LE(paths.size(), min_num_rays); + for (size_t i = 0; i < paths.size(); ++i) { + EXPECT_EQ(static_cast<int>(paths[i].rays.size()), 1); + } +} + +TEST_F(PathTracerTest, GroundSceneTest) { + BuildGroundScene(); + + // Associate a perfect reflection to all triangles. + ReflectionKernel reflection_kernel(kZeroAbsorptionCoefficients, 0.0f, + random_number_generator_); + scene_manager_->AssociateReflectionKernelToTriangles(reflection_kernel, + all_triangle_indices_); + + PathTracer path_tracer(*scene_manager_); + + AcousticSource source({0.5f, 0.5f, 0.1f}, kUnitEnergies, + random_number_generator_); + const size_t min_num_rays = 100; + const size_t max_depth = 10; + const float energy_thresold = 1e-6f; + std::vector<Path> paths = + path_tracer.TracePaths(source, min_num_rays, max_depth, energy_thresold); + + // All paths have at least one ray (for those that did not hit the ground) + // and at most two rays (for those hitting the ground). + for (size_t i = 0; i < paths.size(); ++i) { + EXPECT_GE(static_cast<int>(paths[i].rays.size()), 1); + EXPECT_LE(static_cast<int>(paths[i].rays.size()), 2); + + // For those with length = 2, validate that the first ray ends on the + // ground plane. + if (paths[i].rays.size() == 2) { + const AcousticRay& first_ray = paths[i].rays.front(); + const Vector3f& end_point = + Vector3f(first_ray.origin()) + + first_ray.t_far() * Vector3f(first_ray.direction()); + EXPECT_NEAR(end_point.z(), 0.0f, 1e-7f); + } + } +} + +TEST_F(PathTracerTest, TerminateAtMaxDepthTest) { + // A box scene within which rays never escape. + BuildBoxScene(); + + // Associate a perfect reflection to all triangles. Theoretically there should + // be infinite reflections, but the path tracer stops once each path reaches + // length |max_depth|. + ReflectionKernel reflection_kernel(kZeroAbsorptionCoefficients, 0.0f, + random_number_generator_); + scene_manager_->AssociateReflectionKernelToTriangles(reflection_kernel, + all_triangle_indices_); + PathTracer path_tracer(*scene_manager_); + + AcousticSource source({0.5f, 0.5f, 0.5f}, kUnitEnergies, + random_number_generator_); + const size_t min_num_rays = 100; + const size_t max_depth = 10; + const float energy_thresold = 1e-6f; + std::vector<Path> paths = + path_tracer.TracePaths(source, min_num_rays, max_depth, energy_thresold); + + for (size_t i = 0; i < paths.size(); ++i) { + EXPECT_EQ(paths[i].rays.size(), max_depth); + } +} + +TEST_F(PathTracerTest, TerminateWhenEnergyBelowThresholdTest) { + // A box scene within which rays never escape. + BuildBoxScene(); + + // Associate an absorptive reflection with an absorption coefficient slightly + // over 90% to all triangles. + std::array<float, kNumReverbOctaveBands> absorption_coefficients; + absorption_coefficients.fill(0.9001f); + ReflectionKernel reflection_kernel(absorption_coefficients, 0.0f, + random_number_generator_); + scene_manager_->AssociateReflectionKernelToTriangles(reflection_kernel, + all_triangle_indices_); + PathTracer path_tracer(*scene_manager_); + + // Since the reflected energy is slightly below 1e-1 after each reflection, + // we expect the path tracer to stop after X reflections for an + // energy threshold of 1e-X, and that each path is of length X. + AcousticSource source({0.5f, 0.5f, 0.5f}, kUnitEnergies, + random_number_generator_); + const size_t min_num_rays = 100; + const size_t max_depth = 10; + const float energy_thresold = 1e-6f; + std::vector<Path> paths = + path_tracer.TracePaths(source, min_num_rays, max_depth, energy_thresold); + + for (size_t i = 0; i < paths.size(); ++i) { + EXPECT_EQ(static_cast<int>(paths[i].rays.size()), 6); + } +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/proxy_room_estimator.cc b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/proxy_room_estimator.cc new file mode 100644 index 000000000..d0df0369a --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/proxy_room_estimator.cc @@ -0,0 +1,438 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "geometrical_acoustics/proxy_room_estimator.h" + +#include <algorithm> +#include <cmath> + +#include "Eigen/Core" +#include "base/logging.h" +#include "platforms/common/room_effects_utils.h" + +namespace vraudio { + +namespace { + +// Normals of the walls of an axis-aligned cube. Walls are indexed like this: +// 0: Left (-x) +// 1: Right (+x) +// 2: Bottom (-y) +// 3: Top (+y) +// 4: Back (-z) +// 5: Front (+z) +static const float kCubeWallNormals[kNumRoomSurfaces][3] = { + {-1.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, {0.0f, -1.0f, 0.0f}, + {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, -1.0f}, {0.0f, 0.0f, 1.0f}}; + +// A helper function to determine if a ray (shooting from |ray_origin| in the +// |ray_direction| direction) intersects with a plane (whose normal is +// |plane_normal| and whose distance to the origin is +// |plane_distance_to_origin|). When returning true, the intersection point is +// stored in |intersection|. +bool PlaneIntersection(const WorldPosition& plane_normal, + float plane_distance_to_origin, + const WorldPosition& ray_origin, + const WorldPosition& ray_direction, + WorldPosition* intersection) { + // Do not count if the direction is too close to parallel to the plane (i.e., + // the dot product of |direction| and |plane_normal|, |d_dot_n| is too close + // to zero). + const float d_dot_n = ray_direction.dot(plane_normal); + if (std::abs(d_dot_n) < vraudio::kEpsilonFloat) { + return false; + } + + // Find the intersection point. + const float t = + (plane_distance_to_origin - ray_origin.dot(plane_normal)) / d_dot_n; + + // Do not count back-intersection, i.e., the intersection is in the negative + // |ray_direction|. + if (t < 0.0f) { + return false; + } + + *intersection = ray_origin + t * ray_direction; + return true; +} + +// A helper function to find the index of the wall with which the ray +// intersects. Returns true if such wall is found. +bool FindIntersectingWallIndex(const WorldPosition& dimensions, + const WorldPosition& origin, + const WorldPosition& direction, + size_t* wall_index) { + // The ray intersects a wall if: + // 1. It intersects the plane of the wall. + // 2. The intersection point lies in the bounding box of the wall. + const float dx = dimensions[0] * 0.5f; + const float dy = dimensions[1] * 0.5f; + const float dz = dimensions[2] * 0.5f; + + // Data used to test plane intersections (1. above): normals and distances + // of all the walls. + const float wall_distances[kNumRoomSurfaces] = {dx, dx, dy, dy, dz, dz}; + + // Data used to test whether the intersecting point is in the bounding box + // of the wall (2. above). The bounding box has a small thickness + // |wall_thickness| along the normal direction of the wall. + const float wall_thickness = AcousticRay::kRayEpsilon; + + // The centers and corresponding dimensions defining the bounding boxes of + // all the walls. + const WorldPosition wall_centers[kNumRoomSurfaces] = { + {-dx, 0.0f, 0.0f}, {+dx, 0.0f, 0.0f}, {0.0f, -dy, 0.0f}, + {0.0f, +dy, 0.0f}, {0.0f, 0.0f, -dz}, {0.0f, 0.0f, +dz}, + }; + const WorldPosition wall_dimensions[kNumRoomSurfaces] = { + {wall_thickness, dimensions[1], dimensions[2]}, + {wall_thickness, dimensions[1], dimensions[2]}, + {dimensions[0], wall_thickness, dimensions[2]}, + {dimensions[0], wall_thickness, dimensions[2]}, + {dimensions[0], dimensions[1], wall_thickness}, + {dimensions[0], dimensions[1], wall_thickness}, + }; + + // Iterate through all the walls to find the one that the ray intersects with. + for (size_t wall = 0; wall < kNumRoomSurfaces; ++wall) { + WorldPosition intersection; + if (PlaneIntersection(WorldPosition(kCubeWallNormals[wall]), + wall_distances[wall], origin, direction, + &intersection) && + IsPositionInAabb(intersection, wall_centers[wall], + wall_dimensions[wall])) { + *wall_index = wall; + return true; + } + } + + return false; +} + +// A helper function to sort a vector of {position, distance} pairs according +// to the distances and exclude outliers. +std::vector<std::pair<WorldPosition, float>> +SortPositionsAndDistancesAndFindInliers( + float outlier_portion, const std::vector<std::pair<WorldPosition, float>>& + positions_and_distances) { + std::vector<std::pair<WorldPosition, float>> + sorted_inlier_positions_and_distances(positions_and_distances); + + std::sort(sorted_inlier_positions_and_distances.begin(), + sorted_inlier_positions_and_distances.end(), + [](const std::pair<WorldPosition, float>& position_and_distance_1, + const std::pair<WorldPosition, float>& position_and_distance_2) { + return position_and_distance_1.second < + position_and_distance_2.second; + }); + + const float total_size = + static_cast<float>(sorted_inlier_positions_and_distances.size()); + const size_t inliner_begin = + static_cast<size_t>(std::floor(total_size * outlier_portion)); + const size_t inliner_end = + static_cast<size_t>(std::ceil(total_size * (1.0f - outlier_portion))); + + std::copy(sorted_inlier_positions_and_distances.begin() + inliner_begin, + sorted_inlier_positions_and_distances.begin() + inliner_end, + sorted_inlier_positions_and_distances.begin()); + sorted_inlier_positions_and_distances.resize(inliner_end - inliner_begin); + + return sorted_inlier_positions_and_distances; +} + +// A helper function to compute the average position and distance from a +// vector of positions and distances. +void ComputeAveragePositionAndDistance( + const std::vector<std::pair<WorldPosition, float>>& positions_and_distances, + WorldPosition* average_position, float* average_distance) { + DCHECK(!positions_and_distances.empty()); + WorldPosition sum_positions(0.0f, 0.0f, 0.0f); + float sum_distances = 0.0f; + for (const std::pair<WorldPosition, float>& position_and_distance : + positions_and_distances) { + sum_positions += position_and_distance.first; + sum_distances += position_and_distance.second; + } + + const float inverse_size = + 1.0f / static_cast<float>(positions_and_distances.size()); + *average_position = sum_positions * inverse_size; + *average_distance = sum_distances * inverse_size; +} + +} // namespace + +void ProxyRoomEstimator::CollectHitPointData( + const std::vector<Path>& paths_batch) { + // The size of already collected hit points before this batch. + const size_t previous_size = hit_points_.size(); + hit_points_.resize(previous_size + paths_batch.size()); + + // Collect one hit point per path. + for (size_t path_index = 0; path_index < paths_batch.size(); ++path_index) { + const size_t hit_point_index = path_index + previous_size; + const Path& path = paths_batch.at(path_index); + hit_points_[hit_point_index] = CollectHitPointDataFromPath(path); + } +} + +bool ProxyRoomEstimator::EstimateCubicProxyRoom( + float outlier_portion, RoomProperties* room_properties) { + // Check that |outlier_portion| is in the range of [0, 0.5]. + CHECK_GE(outlier_portion, 0.0f); + CHECK_LE(outlier_portion, 0.5f); + + // Estimation fails if there is no hit point. + if (hit_points_.empty()) { + return false; + } + + // The geometry part (position, dimensions, and rotation) of the proxy room. + if (!EstimateCubeGeometry(outlier_portion, room_properties->position, + room_properties->dimensions, + room_properties->rotation)) { + LOG(WARNING) << "Unable to estimate a cube without a hit point with finite " + << "|t_far|; returning a default cube."; + return false; + } + + // The surface material part of the proxy room. + EstimateSurfaceMaterials(WorldPosition(room_properties->position), + WorldPosition(room_properties->dimensions), + room_properties->material_names); + return true; +} + +ProxyRoomEstimator::HitPointData +ProxyRoomEstimator::CollectHitPointDataFromPath(const Path& path) { + CHECK(!path.rays.empty()); + HitPointData hit_point; + + // Common data. + const AcousticRay& first_order_ray = path.rays[0]; + hit_point.origin = WorldPosition(first_order_ray.origin()); + hit_point.direction = WorldPosition(first_order_ray.direction()); + + // Treating escaped and non-escaped rays differently for their |t_far| and + // |absorption_coefficients|. + if (path.rays.size() > 1) { + hit_point.t_far = first_order_ray.t_far(); + + // Figure out the absorption coefficients by examining the incoming and the + // reflected energy. + const AcousticRay& second_order_ray = path.rays[1]; + for (size_t i = 0; i < kNumReverbOctaveBands; ++i) { + const float incoming_energy = first_order_ray.energies().at(i); + const float reflected_energy = second_order_ray.energies().at(i); + CHECK_GT(incoming_energy, 0.0f); + CHECK_GE(incoming_energy, reflected_energy); + + hit_point.absorption_coefficients[i] = + 1.0f - (reflected_energy / incoming_energy); + } + } else { + hit_point.t_far = AcousticRay::kInfinity; + + // Escaped rays are considered as completely absorbed. + for (size_t i = 0; i < kNumReverbOctaveBands; ++i) { + hit_point.absorption_coefficients[i] = 1.0f; + } + } + + return hit_point; +} + +bool ProxyRoomEstimator::EstimateCubeGeometry(float outlier_portion, + float* position, + float* dimensions, + float* rotation) { + // First group the hit points according to which cube wall it would hit, + // assuming an initial guess of a cube that + // 1. centers at the origin of the first ray, + // 2. has unit dimensions, and + // 3. has no rotation. + const WorldPosition cube_dimensions(1.0f, 1.0f, 1.0f); + const std::array<std::vector<HitPointData>, kNumRoomSurfaces> + hit_points_on_walls = + GroupHitPointsByWalls(hit_points_[0].origin, cube_dimensions); + + // Compute the positions and distances of from the hit points on walls. + const std::vector<std::pair<WorldPosition, float>> positions_and_distances = + ComputeDistancesAndPositionsFromHitPoints(hit_points_on_walls); + + // Cannot estimate if there are no valid positions and distances. + if (positions_and_distances.empty()) { + return false; + } + + // Sort the |positions_and_distances| according to the distances and also + // filter out outliers (whose distances are too large or too small). + const std::vector<std::pair<WorldPosition, float>> + sorted_inlier_positions_and_distances = + SortPositionsAndDistancesAndFindInliers(outlier_portion, + positions_and_distances); + + // Cannot estimate if there are no inlying positions and distances. + if (sorted_inlier_positions_and_distances.empty()) { + return false; + } + + // Take the average distance and origin of the inlier hit points. + WorldPosition average_hit_point_poisition; + float average_distance; + ComputeAveragePositionAndDistance(sorted_inlier_positions_and_distances, + &average_hit_point_poisition, + &average_distance); + + // Use twice the average distance as the cube's dimensions. + const float estimated_cube_dimension = 2.0f * average_distance; + dimensions[0] = estimated_cube_dimension; + dimensions[1] = estimated_cube_dimension; + dimensions[2] = estimated_cube_dimension; + + // Use the average hit point position as the cube's position. + position[0] = average_hit_point_poisition[0]; + position[1] = average_hit_point_poisition[1]; + position[2] = average_hit_point_poisition[2]; + + // No rotation. + rotation[0] = 0.0f; + rotation[1] = 0.0f; + rotation[2] = 0.0f; + rotation[3] = 1.0f; + + return true; +} + +std::array<std::vector<ProxyRoomEstimator::HitPointData>, kNumRoomSurfaces> +ProxyRoomEstimator::GroupHitPointsByWalls( + const WorldPosition& room_position, const WorldPosition& room_dimensions) { + const WorldPosition kOrigin(0.0f, 0.0f, 0.0f); + + // No rotation for an axis-aligned room. + const WorldRotation kNoRotation(1.0f, 0.0f, 0.0f, 0.0f); + + // Reserve memory space for hit points on walls. + std::array<std::vector<HitPointData>, kNumRoomSurfaces> hit_points_on_walls; + for (std::vector<HitPointData>& hit_points_on_wall : hit_points_on_walls) { + hit_points_on_wall.reserve(hit_points_.size()); + } + + for (const HitPointData& hit_point : hit_points_) { + // First transform the ray's origin and direction to the local space + // of the cube centered at |position|. + WorldPosition local_direction; + GetRelativeDirection(kOrigin, kNoRotation, hit_point.direction, + &local_direction); + WorldPosition local_origin; + GetRelativeDirection(room_position, kNoRotation, hit_point.origin, + &local_origin); + + // Find the wall that the ray intersects. + size_t wall_index; + if (FindIntersectingWallIndex(room_dimensions, local_origin, + local_direction, &wall_index)) { + hit_points_on_walls[wall_index].push_back(hit_point); + } + } + + return hit_points_on_walls; +} + +std::vector<std::pair<WorldPosition, float>> +ProxyRoomEstimator::ComputeDistancesAndPositionsFromHitPoints( + const std::array<std::vector<HitPointData>, kNumRoomSurfaces>& + hit_points_on_walls) { + std::vector<std::pair<WorldPosition, float>> positions_and_distances; + for (size_t wall = 0; wall < kNumRoomSurfaces; ++wall) { + const WorldPosition wall_normal(kCubeWallNormals[wall]); + for (const HitPointData& hit_point : hit_points_on_walls[wall]) { + if (hit_point.t_far == AcousticRay::kInfinity) { + continue; + } + + std::pair<WorldPosition, float> position_and_distance; + position_and_distance.first = + hit_point.origin + (hit_point.t_far * hit_point.direction); + position_and_distance.second = + (position_and_distance.first - hit_point.origin).dot(wall_normal); + positions_and_distances.push_back(position_and_distance); + } + } + + return positions_and_distances; +} + +void ProxyRoomEstimator::EstimateSurfaceMaterials( + const WorldPosition& room_position, const WorldPosition& room_dimensions, + MaterialName* material_names) { + // Compute the average absorption coefficients on all the walls. + CoefficientsVector average_absorption_coefficients[kNumRoomSurfaces]; + + // Now that we have better estimates of the proxy room's position, dimensions, + // and rotation, re-group the hit points using these estimates. + const std::array<std::vector<HitPointData>, kNumRoomSurfaces>& + hit_points_on_walls = + GroupHitPointsByWalls(room_position, room_dimensions); + for (size_t wall = 0; wall < kNumRoomSurfaces; ++wall) { + const std::vector<HitPointData>& hit_points = hit_points_on_walls[wall]; + for (const HitPointData& hit_point : hit_points) { + average_absorption_coefficients[wall] += + CoefficientsVector(hit_point.absorption_coefficients.data()); + } + } + + for (size_t wall = 0; wall < kNumRoomSurfaces; ++wall) { + if (!hit_points_on_walls[wall].empty()) { + average_absorption_coefficients[wall] /= + static_cast<float>(hit_points_on_walls[wall].size()); + } else { + // If no hit point is found on this wall, we consider all energy absorbed + // in this direction and the absorption coefficient being 1.0. + average_absorption_coefficients[wall].fill(1.0f); + } + } + + // Find the closest surface material. The distance between two materials is + // defined as the simple Euclidean distance. + for (size_t wall = 0; wall < kNumRoomSurfaces; ++wall) { + MaterialName min_distance_material_name = MaterialName::kTransparent; + float min_material_distance = std::numeric_limits<float>::infinity(); + + // We want to exclude kUniform from fitting. + const size_t num_materials_to_fit = + static_cast<size_t>(MaterialName::kNumMaterialNames) - 1; + for (size_t material_index = 0; material_index < num_materials_to_fit; + ++material_index) { + const RoomMaterial& material = GetRoomMaterial(material_index); + const float material_distance = + (average_absorption_coefficients[wall] - + CoefficientsVector(material.absorption_coefficients)) + .norm(); + if (material_distance < min_material_distance) { + min_material_distance = material_distance; + min_distance_material_name = material.name; + } + } + material_names[wall] = min_distance_material_name; + LOG(INFO) << "Wall[" << wall << "] material= " << min_distance_material_name + << " distance= " << min_material_distance; + } +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/proxy_room_estimator.h b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/proxy_room_estimator.h new file mode 100644 index 000000000..63aa58998 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/proxy_room_estimator.h @@ -0,0 +1,156 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_PROXY_ROOM_ESTIMATOR_H_ +#define RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_PROXY_ROOM_ESTIMATOR_H_ + +#include <array> +#include <utility> +#include <vector> + +#include "Eigen/Core" +#include "api/resonance_audio_api.h" +#include "base/constants_and_types.h" +#include "base/misc_math.h" +#include "geometrical_acoustics/path.h" +#include "platforms/common/room_properties.h" + +namespace vraudio { + +// A class that estimates a "proxy room" from traced sound propagation paths. +// A proxy room is used to model dynamic early reflections as if they are +// reflected from a box-shaped room, even though the real scene geometry is +// arbitrarily complex. This is to complement the pre-computed late reverb +// effects, and it takes the same input (i.e., the ray tracing results) as the +// reverb pre-computation. The proxy room is estimated from the first order +// ray paths (i.e., those from the source to the first hit points), and the +// estimation has two stages: +// 1. Fitting the geometry (currently only as an axis-aligned cube). +// 2. Fitting the surface materials on the six walls. +class ProxyRoomEstimator { + public: + ProxyRoomEstimator() = default; + + // ProxyRoomEstimator is neither copyable nor movable. + ProxyRoomEstimator(const ProxyRoomEstimator&) = delete; + ProxyRoomEstimator& operator=(const ProxyRoomEstimator&) = delete; + + // Collects hit point data from traced ray paths, batch-by-batch. + // + // @param paths_batch A batch of ray paths. + void CollectHitPointData(const std::vector<Path>& paths_batch); + + // Estimates a cube-shaped proxy room from collected hit points. Hit points + // are sorted according to their traveled distance. In order to make the + // estimation more robust, we discard "outlier" hit points, i.e., those whose + // traveled distances are too large or too small. + // + // @param outlier_portion What portion of the hit points are considered as + // outliers. For example, a value of 0.1 means that the hit points whose + // distances are in the top 10% and bottom 10% are considered as outliers + // and discarded. The value must be in the range of [0, 0.5]. + // @param room_properties Room properties of the estimated axis-aligned cube- + // shaped room, each wall having an estimated surface material. + // @return True if the estimation is successful. + bool EstimateCubicProxyRoom(float outlier_portion, + RoomProperties* room_properties); + + private: + class CoefficientsVector : public Eigen::Matrix<float, kNumReverbOctaveBands, + 1, Eigen::DontAlign> { + public: + // Inherits all constructors with 1-or-more arguments. Necessary because + // MSVC12 doesn't support inheriting constructors. + template <typename Arg1, typename... Args> + CoefficientsVector(const Arg1& arg1, Args&&... args) + : Matrix(arg1, std::forward<Args>(args)...) {} + + // Constructs a zero vector. + CoefficientsVector() { setZero(); } + }; + + // A struct to contain data necessary for estimating a proxy room. + struct HitPointData { + // Origin of the ray that creates this hit point. + WorldPosition origin; + + // Direction of the ray that creates this hit point. + WorldPosition direction; + + // Ray parameter t corresponding to the hit point. If the ray escaped the + // scene and did not hit anything, then |t_far| takes the value of + // |AcousticRay::kInfinity|. Escaped rays are still useful in estimating + // surface materials, because they can be considered completely absorbed + // and should increase the effective absorption coefficients. + float t_far; + + // Absorption coefficients of the surface of the hit point across the + // frequency bands. + std::array<float, kNumReverbOctaveBands> absorption_coefficients; + }; + + // Collects one hit point from one traced ray path. + // + // @param path Traced ray path. + // @return Collected hit point. + HitPointData CollectHitPointDataFromPath(const Path& path); + + // Estimates the geometry of the cube. + // + // @param outlier_portion Portion of all hit points to be discarded. See + // EstimateCube() above. + // @param position Output center position of the estimated cube. + // @param dimensions Output dimensions of the estimated cube. + // @return True if the estimation is successful. + bool EstimateCubeGeometry(float outlier_portion, float* position, + float* dimensions, float* rotation); + + // Groups hit points by which walls they lie on in an assumed axis-aligned + // room. + // + // @param room_position Center position of the assumed axis-aligned room. + // @param room_dimensions Dimensions of the assumed axis-aligned room. + // @return An array of six elements, each being a vector of hit points + // on one of the six walls. + std::array<std::vector<HitPointData>, kNumRoomSurfaces> GroupHitPointsByWalls( + const WorldPosition& room_position, const WorldPosition& room_dimensions); + + // Compute the hit point positions and distances (measured along the normal + // direction of the walls that they hit) from hit points on walls. + // + // @param hit_points_on_walls Hit points on walls. + // @return A vector of {hit point position, distance} pairs. + std::vector<std::pair<WorldPosition, float>> + ComputeDistancesAndPositionsFromHitPoints( + const std::array<std::vector<HitPointData>, kNumRoomSurfaces>& + hit_points_on_walls); + + // Estimates the surface materials on the six walls of the proxy room. + // + // @param room_position Center position of the estimated proxy room. + // @param room_dimensions Dimensions of the estimated proxy room. + // @param material_names Names of the estimated surface materials. + void EstimateSurfaceMaterials(const WorldPosition& room_position, + const WorldPosition& room_dimensions, + MaterialName* material_names); + + // Collected hit points. + std::vector<HitPointData> hit_points_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_PROXY_ROOM_ESTIMATOR_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/proxy_room_estimator_test.cc b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/proxy_room_estimator_test.cc new file mode 100644 index 000000000..68f0dc030 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/proxy_room_estimator_test.cc @@ -0,0 +1,317 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "geometrical_acoustics/proxy_room_estimator.h" + +#include <random> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "geometrical_acoustics/acoustic_source.h" +#include "geometrical_acoustics/test_util.h" +#include "platforms/common/room_effects_utils.h" +#include "platforms/common/room_properties.h" + +namespace vraudio { + +namespace { + +using Eigen::Vector3f; + +class ProxyRoomEstimatorTest : public testing::Test { + public: + ProxyRoomEstimatorTest() { + BuildTestBoxScene({0.0f, 0.0f, 0.0f}, {1.0f, 1.0f, 1.0f}, &cube_vertices_, + &cube_triangles_, &cube_wall_triangles_); + } + + protected: + void ReflectRaysInPaths(float t_far, std::vector<Path>* paths) { + for (Path& path : *paths) { + // Add a reflected ray to the end of each path, whose energy is half + // the first ray. + AcousticRay& ray = path.rays.back(); + ray.set_t_far(t_far); + const float reflected_origin[3] = { + ray.origin()[0] + ray.t_far() * ray.direction()[0], + ray.origin()[1] + ray.t_far() * ray.direction()[1], + ray.origin()[2] + ray.t_far() * ray.direction()[2], + }; + AcousticRay reflected_ray(reflected_origin, kZDirection, t_far, + AcousticRay::kInfinity, kHalfUnitEnergies, + AcousticRay::RayType::kSpecular, t_far); + path.rays.push_back(reflected_ray); + } + } + + ProxyRoomEstimator estimator_; + SceneManager scene_manager_; + + // Ray-tracing related fields. + const int kNumRays = 2000; + const int kMaxDepth = 3; + const float kEnergyThresold = 1e-6f; + const std::array<float, kNumReverbOctaveBands> kHalfUnitEnergies{ + {0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f}}; + const float kSourcePosition[3] = {1.0f, 2.0f, 3.0f}; + const float kZDirection[3] = {0.0f, 0.0f, 1.0f}; + + // Data describing a cube scene. + std::vector<Vertex> cube_vertices_; + std::vector<Triangle> cube_triangles_; + const float cube_center_[3] = {0.5f, 0.5f, 0.5f}; + std::vector<MaterialName> wall_materials_; + + // Triangles for six walls of a cube. Useful for assigning surface materials. + std::vector<std::unordered_set<unsigned int>> cube_wall_triangles_; +}; + +// Tests that estimating from an empty paths batch fails. +TEST_F(ProxyRoomEstimatorTest, EstimateFromEmptyPathsFails) { + // An empty paths batch. + std::vector<Path> empty_paths_batch; + estimator_.CollectHitPointData(empty_paths_batch); + + // Expect that the estimation function returns false. + const float outlier_portion = 0.0f; + RoomProperties room_properties; + EXPECT_FALSE( + estimator_.EstimateCubicProxyRoom(outlier_portion, &room_properties)); +} + +// Tests that if all paths in a batch escape the scene before hitting anything, +// the estimation fails. +TEST_F(ProxyRoomEstimatorTest, EstimateFromEscapedPathsFails) { + // A paths batch in which all rays escape, i.e., have infinite |t_far|. + std::vector<Path> escaped_paths_batch; + for (size_t i = 0; i < 10; ++i) { + Path path; + path.rays.emplace_back(kSourcePosition, // origin + kZDirection, // direction + 0.0f, // t_near + AcousticRay::kInfinity, // t_far + kUnitEnergies, // energies + AcousticRay::RayType::kSpecular, // ray_type + 0.0f // prior_distance + ); + escaped_paths_batch.push_back(path); + } + estimator_.CollectHitPointData(escaped_paths_batch); + + // Expect that the estimation function returns false. + const float outlier_portion = 0.0f; + RoomProperties room_properties; + EXPECT_FALSE( + estimator_.EstimateCubicProxyRoom(outlier_portion, &room_properties)); +} + +// Tests that if the scene itself is a cube, then the estimated proxy room +// can recover the same cube with a certain error below tolerance. +TEST_F(ProxyRoomEstimatorTest, EstimateCubeFromCubeScene) { + wall_materials_ = std::vector<MaterialName>{ + MaterialName::kAcousticCeilingTiles, + MaterialName::kWoodPanel, + MaterialName::kConcreteBlockPainted, + MaterialName::kGlassThin, + MaterialName::kGrass, + MaterialName::kMarble, + }; + + // Trace rays in a cube scene. + std::vector<Path> paths = TracePathsInTestcene( + kNumRays, kMaxDepth, kEnergyThresold, cube_center_, cube_vertices_, + cube_triangles_, cube_wall_triangles_, wall_materials_, &scene_manager_); + + // Estimate a proxy room. + estimator_.CollectHitPointData(paths); + const float outlier_portion = 0.0f; + RoomProperties room_properties; + EXPECT_TRUE( + estimator_.EstimateCubicProxyRoom(outlier_portion, &room_properties)); + + // Expect that the estimated proxy room is the same cube, with a certain + // error in position and dimensions. + const float position_tolerance = 0.05f; + ExpectFloat3Close(room_properties.position, cube_center_, position_tolerance); + const float expected_dimensions[3] = {1.0f, 1.0f, 1.0f}; + const float dimensions_tolerance = 0.01f; + ExpectFloat3Close(room_properties.dimensions, expected_dimensions, + dimensions_tolerance); + + // Expect that the estimated surface materials are the same as the original + // cube. + for (size_t wall = 0; wall < 6; ++wall) { + EXPECT_EQ(room_properties.material_names[wall], wall_materials_[wall]); + } +} + +// Tests that if the scene is a unit sphere, then the estimated proxy room +// is a cube whose center is the same as the sphere. The dimensions cannot +// be exactly the same as the sphere, but can be expected to be within +// a range. +TEST_F(ProxyRoomEstimatorTest, EstimateCubeFromUnitSphereScene) { + const size_t min_num_paths = 1000; + std::vector<Path> paths = + GenerateUniformlyDistributedRayPaths(kSourcePosition, min_num_paths); + ReflectRaysInPaths(1.0f, &paths); + + estimator_.CollectHitPointData(paths); + + const float outlier_portion = 0.0f; + RoomProperties room_properties; + EXPECT_TRUE( + estimator_.EstimateCubicProxyRoom(outlier_portion, &room_properties)); + + // Expect the estimated proxy room is centered at the source position. + const float position_tolerance = 0.005f; + ExpectFloat3Close(room_properties.position, kSourcePosition, + position_tolerance); + + // Because the unit sphere is not a cube, the estimated proxy room will have + // dimensions between + // 1. the smallest cube enclosing the sphere, and + // 2. the largest cube inside the sphere. + // Specifically, the proxy room will have dimensions between 1.0 and 2.0, or + // 1.5 +- 0.5. + const float expected_dimensions[3] = {1.5f, 1.5f, 1.5f}; + const float dimensions_tolerance = 0.5f; + ExpectFloat3Close(room_properties.dimensions, expected_dimensions, + dimensions_tolerance); + + // Expect that the estimated surface materials are all kConcreteBlockCoarse, + // which is closest to absorbing half of the energy across all frequency + // bands. + for (size_t wall = 0; wall < 6; ++wall) { + EXPECT_EQ(room_properties.material_names[wall], + MaterialName::kConcreteBlockCoarse); + } +} + +// Tests that if the scene is a cube with some openings (no walls in some +// directions), then the estimated proxy room will have transparent (fully +// absorbent) walls corresponding to those directions. +TEST_F(ProxyRoomEstimatorTest, EstimateOpenWallsAsTransparent) { + // Remove the last four triangles (corresponding to the two walls facing + // the -z and +z directions) from the cube scene. + cube_triangles_.pop_back(); + cube_triangles_.pop_back(); + cube_triangles_.pop_back(); + cube_triangles_.pop_back(); + + wall_materials_ = std::vector<MaterialName>{ + MaterialName::kAcousticCeilingTiles, + MaterialName::kWoodPanel, + MaterialName::kConcreteBlockPainted, + MaterialName::kGlassThin, + }; + + cube_wall_triangles_.pop_back(); + cube_wall_triangles_.pop_back(); + + // Trace rays in the semi-open cube scene. + std::vector<Path> paths = TracePathsInTestcene( + kNumRays, kMaxDepth, kEnergyThresold, cube_center_, cube_vertices_, + cube_triangles_, cube_wall_triangles_, wall_materials_, &scene_manager_); + + // Estimate a proxy room. + estimator_.CollectHitPointData(paths); + const float outlier_portion = 0.0f; + RoomProperties room_properties; + EXPECT_TRUE( + estimator_.EstimateCubicProxyRoom(outlier_portion, &room_properties)); + + // Expect that the estimated proxy room is the same as the original cube. + const float position_tolerance = 0.005f; + ExpectFloat3Close(room_properties.position, cube_center_, position_tolerance); + const float expected_dimensions[3] = {1.0f, 1.0f, 1.0f}; + const float dimensions_tolerance = 0.001f; + ExpectFloat3Close(room_properties.dimensions, expected_dimensions, + dimensions_tolerance); + + // Expect that the estimated surface materials are the same as the original + // cube for the four remaining walls. + for (size_t wall = 0; wall < 4; ++wall) { + EXPECT_EQ(room_properties.material_names[wall], wall_materials_[wall]); + } + + // Expect that the estimated surface material of the walls corresponding + // to the open directions as transparent, because rays in these directions + // all escape and are considered completely absorbed. + for (size_t wall = 4; wall < 6; ++wall) { + EXPECT_EQ(room_properties.material_names[wall], MaterialName::kTransparent); + } +} + +// Tests if the scene is a cube, but some hit points are too far or too close +// to the origin, those points can be considered as outliers (if a proper +// |outlier_portion| is set) and will not affect the estimation result. +// Therefore the estimated proxy room is still the same cube. +TEST_F(ProxyRoomEstimatorTest, EstimateCubeIgnoringOutliers) { + wall_materials_ = std::vector<MaterialName>{ + MaterialName::kAcousticCeilingTiles, + MaterialName::kWoodPanel, + MaterialName::kConcreteBlockPainted, + MaterialName::kGlassThin, + MaterialName::kGrass, + MaterialName::kMarble, + }; + + // Trace rays in a cube scene. + std::vector<Path> paths = TracePathsInTestcene( + kNumRays, kMaxDepth, kEnergyThresold, cube_center_, cube_vertices_, + cube_triangles_, cube_wall_triangles_, wall_materials_, &scene_manager_); + + // Modify the traced paths so that some hit points are too close or too far. + const size_t num_points_too_close = 100; + const float distance_too_close = 0.1f; + for (size_t i = 0; i < num_points_too_close; ++i) { + paths[0].rays[0].tfar = distance_too_close; + } + const size_t num_points_too_far = 100; + const float distance_too_far = 10.0f; + for (size_t i = num_points_too_close; + i < num_points_too_far + num_points_too_close; ++i) { + paths[0].rays[0].tfar = distance_too_far; + } + + // Estimate a proxy room with a |outlier_portion| carefully chosen such that + // the hit points too close or too far do not affect the estimation result. + estimator_.CollectHitPointData(paths); + const float outlier_portion = + static_cast<float>(num_points_too_close + num_points_too_far) / + static_cast<float>(paths.size()) / 2.0f; + RoomProperties room_properties; + EXPECT_TRUE( + estimator_.EstimateCubicProxyRoom(outlier_portion, &room_properties)); + + // Expect that the estimated proxy room is still the same as the original + // cube, even in the presence of the hit points too close or too far. + const float position_tolerance = 0.05f; + ExpectFloat3Close(room_properties.position, cube_center_, position_tolerance); + const float expected_dimensions[3] = {1.0f, 1.0f, 1.0f}; + const float dimensions_tolerance = 0.01f; + ExpectFloat3Close(room_properties.dimensions, expected_dimensions, + dimensions_tolerance); + + // Expect that the estimated surface materials are the same as the original + // cube. + for (size_t wall = 0; wall < 6; ++wall) { + EXPECT_EQ(room_properties.material_names[wall], wall_materials_[wall]); + } +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/reflection_kernel.cc b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/reflection_kernel.cc new file mode 100644 index 000000000..f55f2eb72 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/reflection_kernel.cc @@ -0,0 +1,99 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "geometrical_acoustics/reflection_kernel.h" + +#include "base/logging.h" +#include "geometrical_acoustics/sampling.h" + +namespace vraudio { + +using Eigen::Vector3f; + +AcousticRay ReflectionKernel::Reflect(const AcousticRay& incident_ray) const { + // This function currently uses |random_number_generator| (which returns + // uniformly distributed numbers) to naively sample 1D and 2D points. + + AcousticRay::RayType reflected_ray_type = + (random_number_generator_() >= scattering_coefficient_) + ? AcousticRay::RayType::kSpecular + : AcousticRay::RayType::kDiffuse; + + // Compute the reflected direction. + Vector3f reflected_direction; + const Vector3f& unit_normal = + Vector3f(incident_ray.intersected_geometry_normal()).normalized(); + const Vector3f& incident_direction = Vector3f(incident_ray.direction()); + switch (reflected_ray_type) { + case AcousticRay::RayType::kSpecular: + reflected_direction = + incident_direction - + 2.0f * incident_direction.dot(unit_normal) * unit_normal; + break; + case AcousticRay::RayType::kDiffuse: + // A Lambertian reflection. + reflected_direction = CosineSampleHemisphere( + random_number_generator_(), random_number_generator_(), unit_normal); + break; + } + const Vector3f reflected_origin = Vector3f(incident_ray.origin()) + + incident_ray.t_far() * incident_direction; + const float reflected_ray_prior_distance = + incident_ray.prior_distance() + + incident_ray.t_far() * incident_direction.norm(); + + // New energies for each frequency band. + CHECK_EQ(reflection_coefficients_.size(), incident_ray.energies().size()); + std::array<float, kNumReverbOctaveBands> new_energies = + incident_ray.energies(); + + for (size_t i = 0; i < new_energies.size(); ++i) { + new_energies[i] *= reflection_coefficients_[i]; + } + + return AcousticRay(reflected_origin.data(), reflected_direction.data(), + AcousticRay::kRayEpsilon, AcousticRay::kInfinity, + new_energies, reflected_ray_type, + reflected_ray_prior_distance); +} + +void ReflectionKernel::ReflectDiffuseRain( + const AcousticRay& incident_ray, const AcousticRay& reference_reflected_ray, + const Eigen::Vector3f& listener_position, float* direction_pdf, + AcousticRay* diffuse_rain_ray) const { + const Vector3f reflection_point_to_listener = + listener_position - Vector3f(reference_reflected_ray.origin()); + const Vector3f reflection_point_to_listener_direction = + reflection_point_to_listener.normalized(); + const float diffuse_rain_ray_t_far = + reflection_point_to_listener.norm() + reference_reflected_ray.t_near(); + + *direction_pdf = CosineSampleHemispherePdf( + Vector3f(incident_ray.intersected_geometry_normal()).normalized(), + reflection_point_to_listener_direction); + + diffuse_rain_ray->set_origin(reference_reflected_ray.origin()); + diffuse_rain_ray->set_direction( + reflection_point_to_listener_direction.data()); + diffuse_rain_ray->set_t_near(reference_reflected_ray.t_near()); + diffuse_rain_ray->set_t_far(diffuse_rain_ray_t_far); + diffuse_rain_ray->set_energies(reference_reflected_ray.energies()); + diffuse_rain_ray->set_type(reference_reflected_ray.type()); + diffuse_rain_ray->set_prior_distance( + reference_reflected_ray.prior_distance()); +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/reflection_kernel.h b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/reflection_kernel.h new file mode 100644 index 000000000..10b34d5c7 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/reflection_kernel.h @@ -0,0 +1,104 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_REFLECTION_KERNEL_H_ +#define RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_REFLECTION_KERNEL_H_ + +#include <array> +#include <functional> +#include <utility> + +#include "Eigen/Core" +#include "base/constants_and_types.h" +#include "base/logging.h" +#include "geometrical_acoustics/acoustic_ray.h" + +namespace vraudio { + +// A class modeling reflections of acoustic rays. It takes in an incident ray +// and computes the corresponding reflected ray. NOTE: not to be confused with +// vraudio:Reflection, which models the reflection properties of a room. +// +// This class handles specular and diffuse reflections, randomly chosen based +// on the scattering coefficient. Besides, a certain portion of the energy is +// absorbed based on the absorption coefficient. +// + +class ReflectionKernel { + public: + // Constructor. + // + // @param absorption_coefficients Fraction of energy being absorbed for each + // frequency band. All values must be in [0.0, 1.0]. + // @param scattering_coefficient Fraction of energy being scattered + // (diffusely reflected). Must be in [0.0, 1.0]. + // @param random_number_generator Random number generator to randomly select + // reflection types as well as ray directions if needed. It should + // implement operator() that returns a random value in [0.0, 1.0). + ReflectionKernel( + const std::array<float, kNumReverbOctaveBands>& absorption_coefficients, + float scattering_coefficient, + std::function<float()> random_number_generator) + : scattering_coefficient_(scattering_coefficient), + random_number_generator_(std::move(random_number_generator)) { + for (size_t i = 0; i < kNumReverbOctaveBands; ++i) { + reflection_coefficients_[i] = 1.0f - absorption_coefficients[i]; + CHECK(reflection_coefficients_[i] >= 0.0f && + reflection_coefficients_[i] <= 1.0f); + } + CHECK(scattering_coefficient_ >= 0.0f && scattering_coefficient_ <= 1.0f); + } + + // Reflects a ray. + // + // @param incident_ray Incident (incoming) ray. + // @return Exitant (outgoing) ray, whose origin is on the intersection point, + // with direction and energy calculated based on the type of reflection + // and the incident ray. + AcousticRay Reflect(const AcousticRay& incident_ray) const; + + // Reflects an incident ray to create a diffuse-rain ray, whose |direction| + // is from the reflection point to the listener position, and whose |t_far| + // is the distance between the reflection point and the listener position. + // All the other data are the copied from the provided + // |reference_reflected_ray|. + // + // @param incident_ray Incident ray. + // @param reflected_ray Reference reflected ray, from which the output + // diffuse-rain ray copies all the data except for |direction| and + // |t_far|. + // @param listener_position Listener position. + // @param direction_pdf Output probability density function that a reflected + // ray is in the direction of the diffuse-rain ray. Will be used to + // modulate the energy contribution from the diffuse-rain ray. + // @param diffuse_rain_ray Output diffuse-rain ray. + void ReflectDiffuseRain(const AcousticRay& incident_ray, + const AcousticRay& reference_reflected_ray, + const Eigen::Vector3f& listener_position, + float* direction_pdf, + AcousticRay* diffuse_rain_ray) const; + + private: + std::array<float, kNumReverbOctaveBands> reflection_coefficients_; + const float scattering_coefficient_; + + // Randon number generator for sampling reflection types and ray directions. + std::function<float()> random_number_generator_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_REFLECTION_KERNEL_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/reflection_kernel_test.cc b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/reflection_kernel_test.cc new file mode 100644 index 000000000..bf1806996 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/reflection_kernel_test.cc @@ -0,0 +1,300 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "geometrical_acoustics/reflection_kernel.h" + +#include <cmath> +#include <functional> +#include <random> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "Eigen/Dense" +#include "geometrical_acoustics/test_util.h" + +namespace vraudio { + +namespace { + +using Eigen::Quaternionf; +using Eigen::Vector3f; + +class ReflectionKernelTest : public testing::Test { + public: + ReflectionKernelTest() + : incident_ray_(Vector3f(0.0f, 0.0f, 0.0f).data(), + Vector3f(1.0f, 1.0f, 1.0f).normalized().data(), 0.0f, + AcousticRay::kInfinity, kEnergies, + AcousticRay::RayType::kSpecular, 0.0f), + random_engine_(0), + distribution_(0.0f, 1.0f) {} + + void SetUp() override { + // Reseed the random number generator for every test to be deterministic + // and remove flakiness in tests. + random_engine_.seed(0); + random_number_generator_ = [this] { return distribution_(random_engine_); }; + } + + protected: + const std::array<float, kNumReverbOctaveBands> kEnergies = { + {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 9.0f}}; + const std::array<float, kNumReverbOctaveBands> kUnitAbsorptionCoefficients = { + {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}}; + const std::array<float, kNumReverbOctaveBands> kZeroAbsorptionCoefficients = { + {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}}; + + AcousticRay incident_ray_; + std::default_random_engine random_engine_; + std::uniform_real_distribution<float> distribution_; + std::function<float()> random_number_generator_; +}; + +TEST_F(ReflectionKernelTest, EnergyAbsorptionTest) { + const std::array<float, kNumReverbOctaveBands>& original_energies = + incident_ray_.energies(); + { + // An absorption coefficient of 1 means that no energy is reflected. + const ReflectionKernel reflection(kUnitAbsorptionCoefficients, 0.0f, + random_number_generator_); + const AcousticRay reflected_ray = reflection.Reflect(incident_ray_); + for (size_t i = 0; i < kNumReverbOctaveBands; ++i) { + EXPECT_FLOAT_EQ(reflected_ray.energies().at(i), 0.0f); + } + } + { + // An absorption coefficient of 0 means that all energy is reflected. + const ReflectionKernel reflection(kZeroAbsorptionCoefficients, 0.0f, + random_number_generator_); + const AcousticRay reflected_ray = reflection.Reflect(incident_ray_); + for (size_t i = 0; i < kNumReverbOctaveBands; ++i) { + EXPECT_FLOAT_EQ(reflected_ray.energies().at(i), original_energies.at(i)); + } + } + { + // An absorption coefficient of x means that only (1.0 - x) of the energy + // would be reflected. + std::array<float, kNumReverbOctaveBands> absorption_coefficients = { + {0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f}}; + const ReflectionKernel reflection(absorption_coefficients, 0.0f, + random_number_generator_); + const AcousticRay reflected_ray = reflection.Reflect(incident_ray_); + for (size_t i = 0; i < kNumReverbOctaveBands; ++i) { + EXPECT_FLOAT_EQ( + reflected_ray.energies().at(i), + (1.0f - absorption_coefficients.at(i)) * original_energies.at(i)); + } + } +} + +TEST_F(ReflectionKernelTest, ScatteringCoefficientTest) { + // Setting the scattering coefficient to 0.3 means 30% of the rays are + // reflected diffusely while 70% are reflected specularly. + const ReflectionKernel reflection(kUnitAbsorptionCoefficients, 0.3f, + random_number_generator_); + + float specular_fraction = 0.0f; + float diffuse_fraction = 0.0f; + const int num_samples = 10000; + const float fraction_per_sample = 1.0f / static_cast<float>(num_samples); + for (int i = 0; i < num_samples; ++i) { + const AcousticRay reflected_ray = reflection.Reflect(incident_ray_); + (reflected_ray.type() == AcousticRay::RayType::kDiffuse + ? diffuse_fraction + : specular_fraction) += fraction_per_sample; + } + EXPECT_NEAR(diffuse_fraction, 0.3f, 0.01f); + EXPECT_NEAR(specular_fraction, 0.7f, 0.01f); +} + +TEST_F(ReflectionKernelTest, MultipleReflectionsTest) { + const ReflectionKernel reflection(kUnitAbsorptionCoefficients, 0.0f, + random_number_generator_); + + // Reflect |num_reflections| times. Validate that each reflected ray starts + // where its incident ray ends. This is to simulate the effect of a typical + // path tracer. + const size_t num_reflections = 5; + const float t_between_intersections = 2.0f; + const float distance_between_intersections = t_between_intersections * + Vector3f(incident_ray_.direction()).norm(); + + AcousticRay ray = incident_ray_; + float expected_prior_distance = 0.0f; + for (size_t i = 0; i < num_reflections; ++i) { + // Emulate an intersection at t = 2, with a plane whose (unnormalized) + // normal being (0, 0, -1) for even iterations and (0, 0, 1) for odd + // iterations. + ray.set_t_far(t_between_intersections); + const float even_normal[3] = {0.0f, 0.0f, -1.0f}; + const float odd_normal[3] = {0.0f, 0.0f, 1.0f}; + ray.set_intersected_geometry_normal((i % 2 == 0) ? even_normal + : odd_normal); + const AcousticRay reflected_ray = reflection.Reflect(ray); + + // Check that the reflected ray starts where the original ray ends. + const Vector3f& expected_reflected_ray_origin = + Vector3f(ray.origin()) + ray.t_far() * Vector3f(ray.direction()); + ExpectFloat3Close(reflected_ray.origin(), + expected_reflected_ray_origin.data()); + EXPECT_FLOAT_EQ(reflected_ray.t_near(), AcousticRay::kRayEpsilon); + + // Check that the |prior_distance| field accumulates the distance traveled. + expected_prior_distance += distance_between_intersections; + EXPECT_FLOAT_EQ(reflected_ray.prior_distance(), expected_prior_distance); + + // Continue tracing the reflected ray. + ray = reflected_ray; + } +} + +TEST_F(ReflectionKernelTest, SpecularReflectionTest) { + // Emulate an intersection at t = 2, with a plane whose (unnormalized) normal + // is (-1, -1, 0). + incident_ray_.set_t_far(2.0f); + const float normal[3] = {-1.0f, -1.0f, 0.0f}; + incident_ray_.set_intersected_geometry_normal(normal); + + // Setting the scattering coefficient to zero means to always reflect + // specularly. + const ReflectionKernel reflection(kUnitAbsorptionCoefficients, 0.0f, + random_number_generator_); + const AcousticRay reflected_ray = reflection.Reflect(incident_ray_); + + // Validate non-directional data. + EXPECT_FLOAT_EQ(reflected_ray.t_near(), AcousticRay::kRayEpsilon); + EXPECT_EQ(reflected_ray.type(), AcousticRay::RayType::kSpecular); + const float expected_reflected_origin[3] = {1.15470053838f, 1.15470053838f, + 1.15470053838f}; + ExpectFloat3Close(reflected_ray.origin(), expected_reflected_origin); + + // Validate the reflected direction. + const Vector3f& expected_reflected_direction = + Vector3f(-1.0f, -1.0f, 1.0f).normalized(); + ExpectFloat3Close(reflected_ray.direction(), + expected_reflected_direction.data()); +} + +TEST_F(ReflectionKernelTest, DiffuseReflectionTest) { + // Emulate an intersection at t = 2, with a plane whose (unnormalized) normal + // is (-1, -1, 0). + incident_ray_.set_t_far(2.0f); + const float normal[3] = {-1.0f, -1.0f, 0.0f}; + incident_ray_.set_intersected_geometry_normal(normal); + + // Setting the scattering coefficient to 1.0 means to always reflect + // diffusely. + const ReflectionKernel reflection(kUnitAbsorptionCoefficients, 1.0f, + random_number_generator_); + const AcousticRay reflected_ray = reflection.Reflect(incident_ray_); + + // Validate non-directional data. + EXPECT_FLOAT_EQ(reflected_ray.t_near(), AcousticRay::kRayEpsilon); + EXPECT_EQ(reflected_ray.type(), AcousticRay::RayType::kDiffuse); + + // Validate the reflected directions. + // For a reflected ray whose direction distribution is cosine-weighted over a + // hemisphere: + // - The PDF of theta is 2 sin(theta) cos(theta), and the CDF is sin^2(theta). + // - The PDF of phi is 1 / 2 pi, and the CDF is 0.5 + phi / 2 pi. + const Vector3f& plane_unit_normal = + Vector3f(incident_ray_.intersected_geometry_normal()).normalized(); + ValidateDistribution(100000, 100, [&reflection, &plane_unit_normal, this]() { + const AcousticRay reflected_ray = reflection.Reflect(incident_ray_); + const Vector3f& local_direction = + Quaternionf::FromTwoVectors(plane_unit_normal, Vector3f::UnitZ()) * + Vector3f(reflected_ray.direction()); + const float cos_theta = local_direction.z(); + const float sin_theta_square = 1.0f - cos_theta * cos_theta; + return sin_theta_square; + }); + + ValidateDistribution(100000, 100, [&reflection, &plane_unit_normal, this]() { + const AcousticRay reflected_ray = reflection.Reflect(incident_ray_); + const Vector3f& local_direction = + Quaternionf::FromTwoVectors(plane_unit_normal, Vector3f::UnitZ()) * + Vector3f(reflected_ray.direction()); + const float phi = std::atan2(local_direction.y(), local_direction.x()); + return 0.5f + phi / 2.0f / static_cast<float>(M_PI); + }); +} + +TEST_F(ReflectionKernelTest, DiffuseRainReflectionTest) { + // Emulate an intersection at t = 2, with a plane whose (unnormalized) normal + // is (-1, -1, 0). + incident_ray_.set_t_far(2.0f); + const float normal[3] = {-1.0f, -1.0f, 0.0f}; + incident_ray_.set_intersected_geometry_normal(normal); + + // Setting the scattering coefficient to 1.0 means to always reflect + // diffusely. + const ReflectionKernel reflection(kUnitAbsorptionCoefficients, 1.0f, + random_number_generator_); + const AcousticRay reference_reflected_ray = reflection.Reflect(incident_ray_); + + // Generate a diffuse-rain ray for a listener on the same side of the plane. + float direction_pdf = -1.0f; + const Vector3f same_side_listener_position(0.0f, 0.0f, 2.0f); + AcousticRay diffuse_rain_ray; + reflection.ReflectDiffuseRain(incident_ray_, reference_reflected_ray, + same_side_listener_position, &direction_pdf, + &diffuse_rain_ray); + + // The direction of the diffuse-rain ray should be from the reflection point + // (the origin of the |reference_reflected_ray|) to the listener position. + const Vector3f reflection_point_to_listener_position = + same_side_listener_position - Vector3f(reference_reflected_ray.origin()); + const Vector3f expected_direction = + reflection_point_to_listener_position.normalized(); + ExpectFloat3Close(diffuse_rain_ray.direction(), expected_direction.data()); + + // The output |direction_pdf| should be cos(theta) / pi, where theta is the + // angle between the normal of the reflecting surface and the expected + // direction. + const float cos_theta = expected_direction.dot(Vector3f(normal).normalized()); + EXPECT_FLOAT_EQ(direction_pdf, cos_theta / kPi); + + // Verify that the |t_far| - |t_near| is the distance between the reflection + // point and the listener position. + EXPECT_FLOAT_EQ(diffuse_rain_ray.t_far() - diffuse_rain_ray.t_near(), + reflection_point_to_listener_position.norm()); + + // Verify that other data are the same as |reference_reflected_ray|. + ExpectFloat3Close(diffuse_rain_ray.origin(), + reference_reflected_ray.origin()); + EXPECT_FLOAT_EQ(diffuse_rain_ray.t_near(), reference_reflected_ray.t_near()); + ExpectFloat3Close(diffuse_rain_ray.intersected_geometry_normal(), + reference_reflected_ray.intersected_geometry_normal()); + for (size_t band = 0; band < kNumReverbOctaveBands; ++band) { + EXPECT_FLOAT_EQ(diffuse_rain_ray.energies()[band], + reference_reflected_ray.energies()[band]); + } + EXPECT_EQ(diffuse_rain_ray.type(), reference_reflected_ray.type()); + EXPECT_FLOAT_EQ(diffuse_rain_ray.prior_distance(), + reference_reflected_ray.prior_distance()); + + // Generate a diffuse-rain ray for a listener on the different side of the + // plane and expect that the output |direction_pdf| is zero, meaning there is + // no chance that the diffuse-rain ray is in that direction. + const Vector3f different_side_listener_position(10.0f, 10.0f, 10.0f); + reflection.ReflectDiffuseRain(incident_ray_, reference_reflected_ray, + different_side_listener_position, + &direction_pdf, &diffuse_rain_ray); + EXPECT_FLOAT_EQ(direction_pdf, 0.0f); +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/sampling.h b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/sampling.h new file mode 100644 index 000000000..99ace88d5 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/sampling.h @@ -0,0 +1,126 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Collection of sampling functions useful for in stochastic ray tracing. + +#ifndef RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_SAMPLING_H_ +#define RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_SAMPLING_H_ + +#include <cmath> + +#include "Eigen/Dense" +#include "base/constants_and_types.h" +#include "base/logging.h" + +namespace vraudio { + +// Samples a vector uniformly from a unit sphere given two random variables +// whose values are in [0, 1). +// +// @param u The first random variable. +// @param v The second random variable. +// @return The sampled vector. +inline Eigen::Vector3f UniformSampleSphere(float u, float v) { + DCHECK(u >= 0.0f && u <= 1.0f); + DCHECK(v >= 0.0f && v <= 1.0f); + const float cos_theta = 1.0f - 2.0f * v; + const float sin_theta = 2.0f * std::sqrt(v * (1.0f - v)); + const float phi = kTwoPi * u; + return Eigen::Vector3f(std::cos(phi) * sin_theta, std::sin(phi) * sin_theta, + cos_theta); +} + +// Samples a vector from a unit sphere in a stratified fashion given two +// random variables whose values are in [0, 1). The collective results from +// |sample_index| iterating from 0 to |sqrt_num_samples|^2 are uniformly +// distributed on a sphere. +// +// @param u The first random variable. +// @param v The second random variable. +// @param sqrt_num_samples The square root of the total number of samples. +// @param sample_index The index of the currently sampled vector. +// @return The sampled vector. +inline Eigen::Vector3f StratifiedSampleSphere(float u, float v, + size_t sqrt_num_samples, + size_t sample_index) { + DCHECK(u >= 0.0f && u <= 1.0f); + DCHECK(v >= 0.0f && v <= 1.0f); + + // Make a domain that have |sqrt_num_samples| by |sqrt_num_samples| cells. + // First decide which cell that this sample is in, denoted by the coordinates + // of the cell's top-left of corner. + const float cell_x = static_cast<float>(sample_index / sqrt_num_samples); + const float cell_y = static_cast<float>(sample_index % sqrt_num_samples); + + // Then pick a point inside the cell using the two random variables u and v. + // Normalize the point to the [0, 1) x [0, 1) domain and send it to + // UniformSampleSphere(). + const float cell_width = 1.0f / static_cast<float>(sqrt_num_samples); + return UniformSampleSphere((cell_x + u) * cell_width, + (cell_y + v) * cell_width); +} + +// Samples a vector from a unit hemisphere according to the cosine-weighted +// distribution given two random variables whose values are in [0, 1). The +// hemisphere lies on a plane whose normal is assumed to be on the +z direction. +// + +static Eigen::Vector3f CosineSampleHemisphere(float u, float v) { + DCHECK(u >= 0.0f && u <= 1.0f); + DCHECK(v >= 0.0f && v <= 1.0f); + const float cos_theta = std::sqrt(1.0f - v); + const float sin_theta = std::sqrt(v); + const float phi = kTwoPi * u; + return Eigen::Vector3f(std::cos(phi) * sin_theta, std::sin(phi) * sin_theta, + cos_theta); +} + +// Same as above but the hemisphere lies on a plane whose normal is specified +// by the user (instead of the +z direction). +// +// @param u The first random variable. +// @param v The second random variable. +// @param unit_normal The normal of the plane on which the hemisphere resides. +// @return The sampled vector. +static Eigen::Vector3f CosineSampleHemisphere( + float u, float v, const Eigen::Vector3f& unit_normal) { + Eigen::Vector3f local_vector = CosineSampleHemisphere(u, v); + + return Eigen::Quaternionf::FromTwoVectors(Eigen::Vector3f::UnitZ(), + unit_normal) * + local_vector; +} + +// The probability density function (PDF) that a vector is in a particular +// direction if the vector is sampled from a cosine-weigted distribution over +// a unit hemisphere (i.e. it is sampled from the CosineSampleHemisphere() +// above). +// +// @param unit_normal The normal of the plane on which the hemisphere resides. +// @param unit_direction The direction of the sampled vector. +// @return Probability density that a sampled vector is in the |unit_direction|. +static float CosineSampleHemispherePdf(const Eigen::Vector3f& unit_normal, + const Eigen::Vector3f& unit_direction) { + const float cos_theta = unit_normal.dot(unit_direction); + + // Returns zero probability if the |unit_normal| and |unit_direction| lie on + // different sides of the plane, i.e., their inner-product is negative. + return cos_theta >= 0.0f ? cos_theta / kPi : 0.0f; +} + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_SAMPLING_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/scene_manager.cc b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/scene_manager.cc new file mode 100644 index 000000000..38baeb7ab --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/scene_manager.cc @@ -0,0 +1,153 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "geometrical_acoustics/scene_manager.h" + +#include "base/aligned_allocator.h" +#include "geometrical_acoustics/sphere.h" + +namespace vraudio { + +namespace { + +// A function adapter from SphereBounds() to RTCBoundsFunc in order to be +// passed to rtcSetBoundsFunction(). +// The signature of RTCBoundsFunc does not comply with Google's C++ style, + +static void EmbreeSphereBoundsFunction(void* user_data, size_t index, + RTCBounds& output_bounds +) { + Sphere* spheres = static_cast<Sphere*>(user_data); + const Sphere& sphere = spheres[index]; + SphereBounds(sphere, &output_bounds); +} + +// A function adapter from SphereIntersections() to RTCIntersectFunc in order +// to be passed to rtcSetIntersectFunction(). +// The signature of RTCIntersectFunc does not comply with Google's C++ style, + +static void EmbreeSphereIntersectFunction(void* user_data, + RTCRay& ray, + size_t index) { + Sphere* const spheres = static_cast<Sphere*>(user_data); + const Sphere& sphere = spheres[index]; + SphereIntersection(sphere, &ray); +} + +} // namespace + +SceneManager::SceneManager() { + // Use a single RTCDevice for all scenes. + device_ = rtcNewDevice(nullptr); + CHECK_NOTNULL(device_); + scene_ = rtcDeviceNewScene(device_, RTC_SCENE_STATIC | RTC_SCENE_HIGH_QUALITY, + RTC_INTERSECT1); + listener_scene_ = rtcDeviceNewScene( + device_, RTC_SCENE_STATIC | RTC_SCENE_HIGH_QUALITY, RTC_INTERSECT1); +} + +SceneManager::~SceneManager() { + rtcDeleteScene(scene_); + rtcDeleteScene(listener_scene_); + rtcDeleteDevice(device_); +} + +void SceneManager::BuildScene(const std::vector<Vertex>& vertex_buffer, + const std::vector<Triangle>& triangle_buffer) { + num_vertices_ = vertex_buffer.size(); + num_triangles_ = triangle_buffer.size(); + unsigned int mesh_id = rtcNewTriangleMesh(scene_, RTC_GEOMETRY_STATIC, + num_triangles_, num_vertices_); + struct EmbreeVertex { + // Embree uses 4 floats for each vertex for alignment. The last value + // is for padding only. + float x, y, z, a; + }; + EmbreeVertex* const embree_vertex_array = static_cast<EmbreeVertex*>( + rtcMapBuffer(scene_, mesh_id, RTC_VERTEX_BUFFER)); + for (size_t i = 0; i < num_vertices_; ++i) { + embree_vertex_array[i].x = vertex_buffer[i].x; + embree_vertex_array[i].y = vertex_buffer[i].y; + embree_vertex_array[i].z = vertex_buffer[i].z; + } + rtcUnmapBuffer(scene_, mesh_id, RTC_VERTEX_BUFFER); + + // Triangles. Somehow Embree is a left-handed system, so we re-order all + // triangle indices here, i.e. {v0, v1, v2} -> {v0, v2, v1}. + int* const embree_index_array = + static_cast<int*>(rtcMapBuffer(scene_, mesh_id, RTC_INDEX_BUFFER)); + for (size_t i = 0; i < num_triangles_; ++i) { + embree_index_array[3 * i + 0] = triangle_buffer[i].v0; + embree_index_array[3 * i + 1] = triangle_buffer[i].v2; + embree_index_array[3 * i + 2] = triangle_buffer[i].v1; + } + rtcUnmapBuffer(scene_, mesh_id, RTC_INDEX_BUFFER); + rtcCommit(scene_); + is_scene_committed_ = true; +} + +bool SceneManager::AssociateReflectionKernelToTriangles( + const ReflectionKernel& reflection, + const std::unordered_set<unsigned int>& triangle_indices) { + const size_t reflection_index = reflections_.size(); + reflections_.push_back(reflection); + for (const unsigned int triangle_index : triangle_indices) { + if (triangle_index >= num_triangles_) { + return false; + } + triangle_to_reflection_map_[triangle_index] = reflection_index; + } + return true; +} + +void SceneManager::BuildListenerScene( + const std::vector<AcousticListener>& listeners, + float listener_sphere_radius) { + rtcDeleteScene(listener_scene_); + listener_scene_ = rtcDeviceNewScene( + device_, RTC_SCENE_STATIC | RTC_SCENE_HIGH_QUALITY, RTC_INTERSECT1); + + for (size_t listener_index = 0; listener_index < listeners.size(); + ++listener_index) { + // Create a sphere per listener and add to |listener_scene_|. + const AcousticListener& listener = listeners.at(listener_index); + const unsigned int sphere_id = rtcNewUserGeometry(listener_scene_, 1); + Sphere* const sphere = + AllignedMalloc<Sphere, size_t, Sphere*>(sizeof(Sphere), + /*alignment=*/64); + sphere->center[0] = listener.position[0]; + sphere->center[1] = listener.position[1]; + sphere->center[2] = listener.position[2]; + sphere->radius = listener_sphere_radius; + sphere->geometry_id = sphere_id; + + // rtcSetUserData() takes ownership of |sphere|. + rtcSetUserData(listener_scene_, sphere_id, sphere); + rtcSetBoundsFunction(listener_scene_, sphere_id, + &EmbreeSphereBoundsFunction); + rtcSetIntersectFunction(listener_scene_, sphere_id, + &EmbreeSphereIntersectFunction); + + // Associate the listener to |sphere_id| through its index in the vector + // of listeners. + sphere_to_listener_map_[sphere_id] = listener_index; + } + + rtcCommit(listener_scene_); + is_listener_scene_committed_ = true; +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/scene_manager.h b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/scene_manager.h new file mode 100644 index 000000000..85d6eb5dd --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/scene_manager.h @@ -0,0 +1,130 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_SCENE_MANAGER_H_ +#define RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_SCENE_MANAGER_H_ + +#include <functional> +#include <unordered_map> +#include <unordered_set> +#include <vector> + +#include "embree2/rtcore.h" +#include "embree2/rtcore_ray.h" +#include "base/logging.h" +#include "geometrical_acoustics/acoustic_listener.h" +#include "geometrical_acoustics/mesh.h" +#include "geometrical_acoustics/reflection_kernel.h" + +namespace vraudio { + +// A class to manage a scene modeling an acoustic environment and a scene +// modeling listeners. +class SceneManager { + public: + SceneManager(); + virtual ~SceneManager(); + + // Builds a scene with triangles describing the acoustic environment. + // + // @param vertex_buffer A vector of vertices. + // @param triangle_buffer A vector of triangles. The triangles should be + // right-handed, i.e. if a triangle is defined by {v0, v1, v2}, + // then its normal is along the right-handed cross product of (v1 - v0) + // and (v2 - v0). + void BuildScene(const std::vector<Vertex>& vertex_buffer, + const std::vector<Triangle>& triangle_buffer); + + // Associates a reflection kernel to a set of triangles. + // + // @param reflection Reflection kernel. + // @param triangle_indices Indices of triangles to be associated with + // the reflection . + // @return True on success. + bool AssociateReflectionKernelToTriangles( + const ReflectionKernel& reflection_kernel, + const std::unordered_set<unsigned int>& triangle_indices); + + // Gets the reflection kernel associated to a triangle. + // + // @param triangle_index The index of the triangle to get the reflection + // kernel from. + // @return The reflection kernel associated to the triangle; a default + // reflection if no reflection kernel is associated to the triangle. + const ReflectionKernel& GetAssociatedReflectionKernel( + unsigned int triangle_index) const { + if (triangle_to_reflection_map_.count(triangle_index) == 0) { + return kDefaultReflection; + } + return reflections_.at(triangle_to_reflection_map_.at(triangle_index)); + } + + RTCScene scene() const { return scene_; } + bool is_scene_committed() const { return is_scene_committed_; } + size_t num_vertices() const { return num_vertices_; } + size_t num_triangles() const { return num_triangles_; } + + // Builds a scene with only listener spheres, one sphere per listener. + // + // @param listeners Vector of AcousticListeners. + // @param listener_sphere_radius Radius of listener spheres (m). + void BuildListenerScene(const std::vector<AcousticListener>& listeners, + float listener_sphere_radius); + + // Gets the listener index associated to a sphere id. + // + // @param sphere_id Sphere id. + // @return listener_index Listener index associated to the sphere id. + size_t GetListenerIndexFromSphereId(unsigned int sphere_id) { + DCHECK_GT(sphere_to_listener_map_.count(sphere_id), 0); + return sphere_to_listener_map_.at(sphere_id); + } + + RTCScene listener_scene() const { return listener_scene_; } + bool is_listener_scene_committed() const { + return is_listener_scene_committed_; + } + + private: + // Perfect absorption. No randomness needed. + const std::array<float, kNumReverbOctaveBands> + kPerfectReflectionCoefficients = { + {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}}; + const ReflectionKernel kDefaultReflection = ReflectionKernel( + kPerfectReflectionCoefficients, 0.0f, []() { return 0.5f; }); + + // Embree device. + RTCDevice device_; + + // Scene modeling an acoustic environment. + RTCScene scene_; + bool is_scene_committed_ = false; + size_t num_vertices_ = 0; + size_t num_triangles_ = 0; + std::vector<ReflectionKernel> reflections_; + std::unordered_map<unsigned int, size_t> triangle_to_reflection_map_; + + // Scene with only listener spheres. + RTCScene listener_scene_; + bool is_listener_scene_committed_ = false; + + // Map from a sphere's id to the index of a listener. + std::unordered_map<unsigned int, size_t> sphere_to_listener_map_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_SCENE_MANAGER_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/scene_manager_test.cc b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/scene_manager_test.cc new file mode 100644 index 000000000..7810e100a --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/scene_manager_test.cc @@ -0,0 +1,202 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "geometrical_acoustics/scene_manager.h" + +#include <functional> +#include <random> +#include <vector> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "Eigen/Core" +#include "geometrical_acoustics/acoustic_listener.h" +#include "geometrical_acoustics/acoustic_ray.h" +#include "geometrical_acoustics/reflection_kernel.h" +#include "geometrical_acoustics/test_util.h" + +namespace vraudio { + +namespace { + +using Eigen::Vector3f; + +class SceneManagerTest : public testing::Test { + public: + SceneManagerTest() : random_engine_(0), distribution_(0.0f, 1.0f) {} + + void SetUp() override { + ground_vertices_ = {{0.0f, 0.0f, 0.0f}, + {0.0f, 1.0f, 0.0f}, + {1.0f, 0.0f, 0.0f}, + {1.0f, 1.0f, 0.0f}}; + ground_triangles_ = {{0, 2, 1}, {1, 2, 3}}; + + // Reseed the random number generator for every test to be deterministic + // and remove flakiness in tests. + random_engine_.seed(0); + random_number_generator_ = [this] { return distribution_(random_engine_); }; + } + + protected: + const std::array<float, kNumReverbOctaveBands> kUnitEnergies{ + {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}}; + const std::array<float, kNumReverbOctaveBands> kZeroAbsorptionCoefficients{ + {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}}; + std::vector<Vertex> ground_vertices_; + std::vector<Triangle> ground_triangles_; + SceneManager scene_manager_; + std::default_random_engine random_engine_; + std::uniform_real_distribution<float> distribution_; + std::function<float()> random_number_generator_; +}; + +TEST_F(SceneManagerTest, UncommittedSceneTest) { + EXPECT_FALSE(scene_manager_.is_scene_committed()); + EXPECT_FALSE(scene_manager_.is_listener_scene_committed()); +} + +TEST_F(SceneManagerTest, BuildEmptySceneTest) { + scene_manager_.BuildScene({}, {}); + EXPECT_TRUE(scene_manager_.is_scene_committed()); + EXPECT_EQ(scene_manager_.num_vertices(), 0U); + EXPECT_EQ(scene_manager_.num_triangles(), 0U); +} + +TEST_F(SceneManagerTest, BuildSceneWithTwoTrianglesTest) { + scene_manager_.BuildScene(ground_vertices_, ground_triangles_); + EXPECT_TRUE(scene_manager_.is_scene_committed()); + EXPECT_EQ(scene_manager_.num_vertices(), 4U); + EXPECT_EQ(scene_manager_.num_triangles(), 2U); +} + +TEST_F(SceneManagerTest, ReturnDefaultReflectionIfNotAssociatedTest) { + scene_manager_.BuildScene(ground_vertices_, ground_triangles_); + + // Get a reflection of triangle 0; since nothing has been associated to it, + // a default reflection is returned. + const ReflectionKernel& reflection = + scene_manager_.GetAssociatedReflectionKernel(0); + + // Test that this default reflection absorbs all energy. + AcousticRay ray(Vector3f(0.0f, 0.0f, 0.0f).data(), + Vector3f(1.0f, 0.0f, 0.0f).data(), 0.0f, 1.0f, kUnitEnergies, + AcousticRay::RayType::kSpecular, 0.0f); + AcousticRay reflected_ray = reflection.Reflect(ray); + for (size_t i = 0; i < kNumReverbOctaveBands; ++i) { + EXPECT_FLOAT_EQ(reflected_ray.energies().at(i), 0.0f); + } +} + +TEST_F(SceneManagerTest, ReturnFalseAssociatingToNonExistentTriangleTest) { + scene_manager_.BuildScene(ground_vertices_, ground_triangles_); + + // A non-absorptive, purely specular reflection. + ReflectionKernel reflection(kZeroAbsorptionCoefficients, 0.0f, + random_number_generator_); + + // Associate the reflection to triangle 2, which does not exist. + EXPECT_FALSE( + scene_manager_.AssociateReflectionKernelToTriangles(reflection, {2})); +} + +TEST_F(SceneManagerTest, ReturnAssociatedReflectionsTest) { + scene_manager_.BuildScene(ground_vertices_, ground_triangles_); + + // A half-absorptive, purely diffuse reflection. + std::array<float, kNumReverbOctaveBands> absorption_coefficients; + absorption_coefficients.fill(0.5f); + ReflectionKernel reflection_1(absorption_coefficients, 1.0f, + random_number_generator_); + + // A non-absorptive, purely specular reflection. + ReflectionKernel reflection_2(kZeroAbsorptionCoefficients, 0.0f, + random_number_generator_); + + // Associate them to triangle 1 and 0 respectively. + EXPECT_TRUE( + scene_manager_.AssociateReflectionKernelToTriangles(reflection_1, {1})); + EXPECT_TRUE( + scene_manager_.AssociateReflectionKernelToTriangles(reflection_2, {0})); + + AcousticRay ray(Vector3f(0.0f, 0.0f, 0.0f).data(), + Vector3f(1.0f, 0.0f, 0.0f).data(), 0.0f, 1.0f, kUnitEnergies, + AcousticRay::RayType::kSpecular, 0.0f); + { + // Test that the reflection from triangle 0, which should be |reflection_2|, + // absorbs none of the energy and reflects specularly. + + const ReflectionKernel& reflection = + scene_manager_.GetAssociatedReflectionKernel(0); + AcousticRay reflected_ray = reflection.Reflect(ray); + for (size_t i = 0; i < kNumReverbOctaveBands; ++i) { + EXPECT_FLOAT_EQ(reflected_ray.energies().at(i), 1.0f); + } + EXPECT_EQ(reflected_ray.type(), AcousticRay::RayType::kSpecular); + } + { + // Test that the reflection from triangle 1, which should be |reflection_1|, + // absorbs half of the energy and reflects diffusely. + const ReflectionKernel& reflection = + scene_manager_.GetAssociatedReflectionKernel(1); + AcousticRay reflected_ray = reflection.Reflect(ray); + for (size_t i = 0; i < kNumReverbOctaveBands; ++i) { + EXPECT_FLOAT_EQ(reflected_ray.energies().at(i), 0.5f); + } + EXPECT_EQ(reflected_ray.type(), AcousticRay::RayType::kDiffuse); + } +} + +TEST_F(SceneManagerTest, BuildEmptyListenerSceneTest) { + const float listener_sphere_radius = 0.1f; + scene_manager_.BuildListenerScene({}, listener_sphere_radius); + EXPECT_TRUE(scene_manager_.is_listener_scene_committed()); +} + +TEST_F(SceneManagerTest, RayIntersectListenerSceneTest) { + // Five AcousticListeners, lining up on the x-axis, with positions (i, 0, 0). + std::vector<AcousticListener> listeners; + const size_t impulse_response_length = 1000; + for (size_t i = 0; i < 5; ++i) { + const Vector3f listener_position(static_cast<float>(i), 0.0f, 0.0f); + listeners.emplace_back(listener_position, impulse_response_length); + } + + // Build the scene. + const float listener_sphere_radius = 0.1f; + scene_manager_.BuildListenerScene(listeners, listener_sphere_radius); + EXPECT_TRUE(scene_manager_.is_listener_scene_committed()); + + // Shoot 5 rays from (i, -1, 0), all with direction (0, 1, 0). Ray i is + // expected to intersect with listener i's sphere. + const float direction[3] = {0.0f, 1.0f, 0.0f}; + for (size_t i = 0; i < 5; ++i) { + const float origin[3] = {static_cast<float>(i), -1.0f, 0.0f}; + AcousticRay ray_i(origin, direction, 0.0f /* t_near */, + AcousticRay::kInfinity /* t_far */, kUnitEnergies, + AcousticRay::RayType::kDiffuse, + 0.0f /* prior_distance */); + + // Verify that |ray_i| intersects with listener i's sphere. + EXPECT_TRUE(ray_i.Intersect(scene_manager_.listener_scene())); + const unsigned int intersected_sphere_id = ray_i.intersected_geometry_id(); + EXPECT_EQ( + scene_manager_.GetListenerIndexFromSphereId(intersected_sphere_id), i); + } +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/sphere.cc b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/sphere.cc new file mode 100644 index 000000000..b0f0d141c --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/sphere.cc @@ -0,0 +1,120 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "geometrical_acoustics/sphere.h" + +#include <cmath> +#include <utility> + +#include "Eigen/Core" + +namespace vraudio { + +using Eigen::Vector3f; + +namespace { +// Fills intersection data in an RTCRay. +// +// @param sphere Sphere that intersects with the ray. +// @param t0 Ray parameter corresponding to the first intersection point. +// @param t1 Ray parameter corresponding to the second intersection point. +// @param ray RTCRay that potentially intersects with |sphere|, whose data +// fields are to be filled. +inline void FillRaySphereIntersectionData(const Sphere& sphere, float t0, + float t1, RTCRay* ray) { + // For convenience we enforce that t0 <= t1. + if (t0 > t1) { + std::swap(t0, t1); + } + + // In our application we only consider "intersecting" if the ray starts and + // ends outside the sphere, i.e. only if ray.tnear < t0 and ray.tfar > t1. + if ((ray->tnear >= t0) || (ray->tfar <= t1)) { + return; + } + + // Set intersection-related data. + ray->tfar = t0; + ray->geomID = sphere.geometry_id; + + // ray.Ng is the normal at the intersection point. For a sphere the normal is + // always pointing radially outward, i.e. normal = p - sphere.center, where + // p is the intersection point. Of the two intersection points, We use the + // first. + ray->Ng[0] = ray->org[0] + t0 * ray->dir[0] - sphere.center[0]; + ray->Ng[1] = ray->org[1] + t0 * ray->dir[1] - sphere.center[1]; + ray->Ng[2] = ray->org[2] + t0 * ray->dir[2] - sphere.center[2]; +} + +} // namespace + +void SphereIntersection(const Sphere& sphere, RTCRay* ray) { + // The intersection is tested by finding if there exists a point p that is + // both on the ray and on the sphere: + // - Point on the ray: p = ray.origin + t * ray.direction, where t is a + // parameter. + // - Point on the sphere: || p - sphere.center || = sphere.radius. + // + // Solving the above two equations for t leads to solving a quadratic + // equation: + // at^2 + bt + c = 0, + // where + // a = || ray.direction, ray.direction ||^2 + // b = 2 * Dot(ray.direction, ray.origin - sphere.center) + // c = || ray.origin - sphere.center ||^2. + // The two possible solutions to this quadratic equation are: + // t0 = - b - sqrt(b^2 - 4ac) / 2a + // t1 = - b + sqrt(b^2 - 4ac) / 2a. + // The existence of real solutions can be tested if a discriminant value, + // b^2 - 4ac, is greater than 0 (we treat discriminant == 0, which corresponds + // to the ray touching the sphere at a single point, as not intersecting). + + // Vector pointing from the sphere's center to the ray's origin, i.e. + // (ray.origin - sphere.center) in the above equations. + const Vector3f center_ray_origin_vector(ray->org[0] - sphere.center[0], + ray->org[1] - sphere.center[1], + ray->org[2] - sphere.center[2]); + const Vector3f ray_direction(ray->dir); + const float a = ray_direction.squaredNorm(); + const float b = 2.0f * center_ray_origin_vector.dot(ray_direction); + const float c = + center_ray_origin_vector.squaredNorm() - sphere.radius * sphere.radius; + const float discriminant = b * b - 4.0f * a * c; + + // No intersection; do nothing. + if (discriminant <= 0.0f) { + return; + } + + // Solve for t0 and t1. As suggested in "Physically Based Rendering" by Pharr + // and Humphreys, directly computing - b +- sqrt(b^2 - 4ac) / 2a gives poor + // numeric precision when b is close to +- sqrt(b^2 - 4ac), and a more stable + // form is used instead: + // t0 = q / a + // t1 = c / q, + // where + // q = -(b - sqrt(b^2 - 4ac)) / 2 for b < 0 + // -(b + sqrt(b^2 - 4ac)) / 2 otherwise. + const float sqrt_discriminant = std::sqrt(discriminant); + const float q = (b < 0.0f) ? -0.5f * (b - sqrt_discriminant) + : -0.5f * (b + sqrt_discriminant); + const float t0 = q / a; + const float t1 = c / q; + + FillRaySphereIntersectionData(sphere, t0, t1, ray); +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/sphere.h b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/sphere.h new file mode 100644 index 000000000..bea49ccde --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/sphere.h @@ -0,0 +1,69 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_SPHERE_H_ +#define RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_SPHERE_H_ + +#include "embree2/rtcore.h" +#include "embree2/rtcore_ray.h" + +namespace vraudio { + +// A user defined sphere geometry that enables Embree to test ray intersections. +struct RTCORE_ALIGN(16) Sphere { + // Center of the sphere. + float center[3]; + + // Radius of the sphere. + float radius; + + // Geometry Id. This will be reported by ray intersections. + unsigned int geometry_id; +}; + +// The following functions are to be called by Embree internally to enable fast +// ray-sphere intersections tests. + +// Calculates the RTCBounds (essentially a bounding box) of a given sphere. +// +// @param sphere Sphere of interest. +// @param output_bounds Output RTCBounds of the sphere. +inline void SphereBounds(const Sphere& sphere, RTCBounds* output_bounds) { + output_bounds->lower_x = sphere.center[0] - sphere.radius; + output_bounds->lower_y = sphere.center[1] - sphere.radius; + output_bounds->lower_z = sphere.center[2] - sphere.radius; + output_bounds->upper_x = sphere.center[0] + sphere.radius; + output_bounds->upper_y = sphere.center[1] + sphere.radius; + output_bounds->upper_z = sphere.center[2] + sphere.radius; +} + +// Tests whether a sphere and a ray intersects and sets the related data for +// the ray when an intersection is found: +// - |ray.tfar| will be set to corresponding to the first intersection point. +// - |ray.geomID| will be set to the geometry id of the sphere. +// - |ray.Ng| will be set to the normal at the first intersection point, +// pointing radially outward +// +// Because this function will be called internally by Embree, it works on +// Embree's native type RTCRay as opposed to AcousticRay. +// +// @param spheres Sphere of interest. +// @param ray RTCRay with which intersections are tested and related data set. +void SphereIntersection(const Sphere& sphere, RTCRay* ray); + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_SPHERE_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/sphere_test.cc b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/sphere_test.cc new file mode 100644 index 000000000..d0daea12e --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/sphere_test.cc @@ -0,0 +1,267 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "geometrical_acoustics/sphere.h" + +#include <cmath> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/aligned_allocator.h" +#include "base/logging.h" +#include "geometrical_acoustics/acoustic_ray.h" +#include "geometrical_acoustics/test_util.h" + +namespace vraudio { + +namespace { + +// A function adapter from SphereBounds() to RTCBoundsFunc in order to be +// passed to rtcSetBoundsFunction(). +// The signature of RTCBoundsFunc does not comply with Google's C++ style, + +static void EmbreeSphereBoundsFunction(void* user_data, size_t index, + RTCBounds& output_bounds + ) { + Sphere* spheres = static_cast<Sphere*>(user_data); + const Sphere& sphere = spheres[index]; + SphereBounds(sphere, &output_bounds); +} + +// A function adapter from SphereIntersections() to RTCIntersectFunc in order +// to be passed to rtcSetIntersectFunction(). +// The signature of RTCIntersectFunc does not comply with Google's C++ style, + +static void EmbreeSphereIntersectFunction(void* user_data, + RTCRay& ray, + size_t index) { + Sphere* spheres = static_cast<Sphere*>(user_data); + const Sphere& sphere = spheres[index]; + SphereIntersection(sphere, &ray); +} + +void SetSphereData(const float center[3], float radius, + unsigned int geometry_id, Sphere* sphere) { + sphere->center[0] = center[0]; + sphere->center[1] = center[1]; + sphere->center[2] = center[2]; + sphere->radius = radius; + sphere->geometry_id = geometry_id; +} + +TEST(Sphere, SphereBoundsTest) { + const float center[3] = {1.0f, 2.0f, 3.0f}; + const float radius = 3.0f; + Sphere sphere; + SetSphereData(center, radius, 0u, &sphere); + + RTCBounds bounds; + SphereBounds(sphere, &bounds); + EXPECT_FLOAT_EQ(bounds.lower_x, -2.0f); + EXPECT_FLOAT_EQ(bounds.lower_y, -1.0f); + EXPECT_FLOAT_EQ(bounds.lower_z, 0.0f); + EXPECT_FLOAT_EQ(bounds.upper_x, 4.0f); + EXPECT_FLOAT_EQ(bounds.upper_y, 5.0f); + EXPECT_FLOAT_EQ(bounds.upper_z, 6.0f); +} + +TEST(Sphere, RayIntersectSphereTest) { + const float center[3] = {0.0f, 0.0f, 0.0f}; + const float radius = 1.0f; + const unsigned sphere_geometry_id = 1u; + Sphere sphere; + SetSphereData(center, radius, sphere_geometry_id, &sphere); + + // This ray passes through the sphere. + const float origin[3] = {0.5f, 0.0f, -2.0f}; + const float direction[3] = {0.0f, 0.0f, 1.0f}; + AcousticRay ray(origin, direction, 0.0f, AcousticRay::kInfinity, {}, + AcousticRay::RayType::kSpecular, 0.0f); + + SphereIntersection(sphere, &ray); + + // Expect an intersection, so that ray.intersected_geometry_id() returns + // |sphere_geometry_id|. + EXPECT_EQ(ray.intersected_geometry_id(), sphere_geometry_id); + + // Expect that the intersection point is at (0.5, 0, -sqrt(3)/2). + const float expected_intersection_point[3] = {0.5f, 0.0f, + -0.5f * kSqrtThree}; + float intersection_point[3]; + intersection_point[0] = ray.origin()[0] + ray.t_far() * ray.direction()[0]; + intersection_point[1] = ray.origin()[1] + ray.t_far() * ray.direction()[1]; + intersection_point[2] = ray.origin()[2] + ray.t_far() * ray.direction()[2]; + ExpectFloat3Close(intersection_point, expected_intersection_point); + + // Expect that the normal at the intersection point is (0.5, 0, -sqrt(3)/2). + const float expected_normal[3] = {0.5f, 0.0f, -0.5f * kSqrtThree}; + ExpectFloat3Close(ray.intersected_geometry_normal(), expected_normal); +} + +TEST(Sphere, RayOutsideSphereNotIntersectTest) { + const float center[3] = {0.0f, 0.0f, 0.0f}; + const float radius = 1.0f; + Sphere sphere; + SetSphereData(center, radius, 1u, &sphere); + + // This ray completely lies outside of the sphere. + const float origin[3] = {2.0f, 2.0f, 2.0f}; + const float direction[3] = {0.0f, 0.0f, 1.0f}; + AcousticRay ray(origin, direction, 0.0f, AcousticRay::kInfinity, {}, + AcousticRay::RayType::kSpecular, 0.0f); + + SphereIntersection(sphere, &ray); + + // Expect no intersection, i.e. so that ray.intersected_geometry_id() returns + // RTC_INVALID_GEOMETRY_ID. + EXPECT_EQ(ray.intersected_geometry_id(), RTC_INVALID_GEOMETRY_ID); +} + +TEST(Sphere, RayStartingInsideSphereNotIntersectTest) { + const float center[3] = {0.0f, 0.0f, 0.0f}; + const float radius = 1.0f; + Sphere sphere; + SetSphereData(center, radius, 1u, &sphere); + + // This ray theoretically intersects with the sphere, but as its starting + // point is inside the sphere, our intersection test regards it as + // non-intersecting. + const float origin[3] = {0.5f, 0.5f, 0.0f}; + const float direction[3] = {0.0f, 0.0f, 1.0f}; + AcousticRay ray(origin, direction, 0.0f, AcousticRay::kInfinity, {}, + AcousticRay::RayType::kSpecular, 0.0f); + + SphereIntersection(sphere, &ray); + + // Expect no intersection, i.e. so that ray.intersected_geometry_id() returns + // RTC_INVALID_GEOMETRY_ID. + EXPECT_EQ(ray.intersected_geometry_id(), RTC_INVALID_GEOMETRY_ID); +} + +TEST(Sphere, RayEndingInsideSphereNotIntersectTest) { + const float center[3] = {0.0f, 0.0f, 0.0f}; + const float radius = 1.0f; + Sphere sphere; + SetSphereData(center, radius, 1u, &sphere); + + // This ray theoretically intersects with the sphere, but as its end point + // (controlled by |ray.tfar| is inside the sphere, our intersection test + // regards it as non-intersecting. + const float origin[3] = {0.5f, 0.0f, -2.0f}; + const float direction[3] = {0.0f, 0.0f, 1.0f}; + AcousticRay ray(origin, direction, 0.0f, 2.0f, {}, + AcousticRay::RayType::kSpecular, 0.0f); + + SphereIntersection(sphere, &ray); + + // Expect no intersection, i.e. so that ray.intersected_geometry_id() returns + // RTC_INVALID_GEOMETRY_ID. + EXPECT_EQ(ray.intersected_geometry_id(), RTC_INVALID_GEOMETRY_ID); +} + +// The following test fixture and tests mimic real use cases of ray-sphere +// intersections in our acoustics computation: +// +// 1) A sphere is constructed. +// 2) The sphere is registered to the scene as user data. Two callback +// functions of types RTCBoundsFunc and RTCIntersectFunc are registered +// to the scene as well. +// 2) An AcousticRay is constructed. +// 3) The intersection is found by AcousticRay.Intersect(). +// +// This is different from previous tests in that we do not directly invoke +// SphereBounds() and SphereIntersection(); Embree does it for us. + +class SphereTest : public testing::Test { + protected: + void SetUp() override { + // Use a single RTCDevice for all tests. + static RTCDevice device = rtcNewDevice(nullptr); + CHECK_NOTNULL(device); + scene_ = rtcDeviceNewScene( + device, RTC_SCENE_STATIC | RTC_SCENE_HIGH_QUALITY, RTC_INTERSECT1); + } + + void TearDown() override { rtcDeleteScene(scene_); } + + unsigned int AddSphereToScene() { + const unsigned int geometry_id = rtcNewUserGeometry(scene_, 1); + + const float center[3] = {0.0f, 0.0f, 0.0f}; + const float radius = 1.0f; + Sphere* const sphere = + AllignedMalloc<Sphere, size_t, Sphere*>(sizeof(Sphere), + /*alignment=*/64); + + SetSphereData(center, radius, geometry_id, sphere); + + // rtcSetUserData() takes ownership of |sphere|. + rtcSetUserData(scene_, geometry_id, sphere); + rtcSetBoundsFunction(scene_, geometry_id, &EmbreeSphereBoundsFunction); + rtcSetIntersectFunction(scene_, geometry_id, + &EmbreeSphereIntersectFunction); + return geometry_id; + } + + const std::array<float, kNumReverbOctaveBands> kUnitEnergies{ + {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}}; + RTCScene scene_ = nullptr; +}; + +TEST_F(SphereTest, AcousticRayIntersectSphereTest) { + const unsigned int sphere_geometry_id = AddSphereToScene(); + rtcCommit(scene_); + + // Construct an AcousticRay that passes through the sphere. + const float origin[3] = {0.5f, 0.0f, -2.0f}; + const float direction[3] = {0.0f, 0.0f, 1.0f}; + AcousticRay ray(origin, direction, 0.0f, AcousticRay::kInfinity, + kUnitEnergies, AcousticRay::RayType::kSpecular, 0.0f); + + // Expect intersection happens and the intersected geometry id set to + // |sphere_geometry_id|. + EXPECT_TRUE(ray.Intersect(scene_)); + EXPECT_EQ(ray.intersected_geometry_id(), sphere_geometry_id); + + // Expect that the intersection point is at (0.5, 0, -sqrt(3)/2). + const float expected_intersection_point[3] = {0.5f, 0.0f, + -0.5f * kSqrtThree}; + float intersection_point[3]; + intersection_point[0] = ray.origin()[0] + ray.t_far() * ray.direction()[0]; + intersection_point[1] = ray.origin()[1] + ray.t_far() * ray.direction()[1]; + intersection_point[2] = ray.origin()[2] + ray.t_far() * ray.direction()[2]; + ExpectFloat3Close(intersection_point, expected_intersection_point); + + // Expect that the normal at the intersection point is (0.5, 0, -sqrt(3)/2). + const float expected_normal[3] = {0.5f, 0.0f, -0.5f * kSqrtThree}; + ExpectFloat3Close(ray.intersected_geometry_normal(), expected_normal); +} + +TEST_F(SphereTest, AcousticRayIntersectNothingTest) { + rtcCommit(scene_); + + // Construct an AcousticRay completely outside of the sphere. + const float origin[3] = {2.0f, 2.0f, 2.0f}; + const float direction[3] = {0.0f, 0.0f, 1.0f}; + AcousticRay ray(origin, direction, 0.0f, AcousticRay::kInfinity, + kUnitEnergies, AcousticRay::RayType::kSpecular, 0.0f); + + // Expect no intersection. + EXPECT_FALSE(ray.Intersect(scene_)); +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/test_util.cc b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/test_util.cc new file mode 100644 index 000000000..b407f6f07 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/test_util.cc @@ -0,0 +1,231 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "geometrical_acoustics/test_util.h" + +#include <algorithm> +#include <random> +#include <vector> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/logging.h" +#include "geometrical_acoustics/path_tracer.h" + +namespace vraudio { + +namespace { +// Given N bins each having some count, validates that the counts are almost the +// same across all bins. +void ValidateUniformDistribution(const std::vector<int>& count_in_bins, + const int total_count) { + const float expected_fraction = + 1.0f / static_cast<float>(count_in_bins.size()); + const float tolerance = static_cast<float>(count_in_bins.size()) / + static_cast<float>(total_count); + for (const int count : count_in_bins) { + const float actual_fraction = + static_cast<float>(count) / static_cast<float>(total_count); + const float error = actual_fraction - expected_fraction; + const float chi_square = error * error / expected_fraction; + EXPECT_NEAR(chi_square, 0.0f, tolerance); + } +} + +// Assigns surface materials to groups of triangles, one material to one group. +void AssignSurfaceMaterialsToTriangleGroups( + const std::vector<std::unordered_set<unsigned int>>& triangle_groups, + const std::vector<MaterialName>& materials, + const std::function<float()>& random_number_generator, + SceneManager* scene_manager) { + CHECK_EQ(triangle_groups.size(), materials.size()); + + const float scattering_coefficient = 1.0f; + std::array<float, kNumReverbOctaveBands> absorption_coefficients; + for (size_t group = 0; group < materials.size(); ++group) { + const size_t material_index = static_cast<size_t>(materials[group]); + const RoomMaterial& room_material = GetRoomMaterial(material_index); + for (size_t band = 0; band < kNumReverbOctaveBands; ++band) { + absorption_coefficients[band] = + room_material.absorption_coefficients[band]; + } + ReflectionKernel reflection_kernel(absorption_coefficients, + scattering_coefficient, + random_number_generator); + scene_manager->AssociateReflectionKernelToTriangles(reflection_kernel, + triangle_groups[group]); + } +} + +} // namespace + +void ValidateDistribution(const int num_samples, const int num_bins, + std::function<float()> cdf) { + DCHECK_GT(num_bins, 0); + std::vector<int> cdf_bins(num_bins, 0); + for (int i = 0; i < num_samples; ++i) { + const float cdf_value = cdf(); + const int cdf_bin_index = std::min( + num_bins - 1, std::max(0, static_cast<int>(cdf_value * num_bins))); + ++cdf_bins[cdf_bin_index]; + } + ValidateUniformDistribution(cdf_bins, num_samples); +} + +void AddTestGround(RTCScene scene) { + unsigned int mesh_id = rtcNewTriangleMesh(scene, RTC_GEOMETRY_STATIC, 2, 4); + + // Vertices. Each vertex has 4 floats: x, y, z, and a padding float whose + // value we do not care. + float ground_vertices[] = { + 0.0f, 0.0f, 0.0f, 0.0f, // Vertex_0. + 0.0f, 1.0f, 0.0f, 0.0f, // Vertex_1. + 1.0f, 0.0f, 0.0f, 0.0f, // Vertex_2. + 1.0f, 1.0f, 0.0f, 0.0f, // Vertex_3. + }; + + // Triangles. Somehow Embree is a left-handed system. + int ground_indices[] = { + 0, 1, 2, // Triangle_0. + 1, 3, 2, // Triangle_1. + }; + float* const embree_vertices = + static_cast<float*>(rtcMapBuffer(scene, mesh_id, RTC_VERTEX_BUFFER)); + std::copy(ground_vertices, ground_vertices + 16, embree_vertices); + rtcUnmapBuffer(scene, mesh_id, RTC_VERTEX_BUFFER); + int* const embree_indices = + static_cast<int*>(rtcMapBuffer(scene, mesh_id, RTC_INDEX_BUFFER)); + std::copy(ground_indices, ground_indices + 6, embree_indices); + rtcUnmapBuffer(scene, mesh_id, RTC_INDEX_BUFFER); +} + +void BuildTestBoxScene( + const Vertex& min_corner, const Vertex& max_corner, + std::vector<Vertex>* box_vertices, std::vector<Triangle>* box_triangles, + std::vector<std::unordered_set<unsigned int>>* box_wall_triangles) { + const float min_x = min_corner.x; + const float min_y = min_corner.y; + const float min_z = min_corner.z; + const float max_x = max_corner.x; + const float max_y = max_corner.y; + const float max_z = max_corner.z; + CHECK_LT(min_x, max_x); + CHECK_LT(min_y, max_y); + CHECK_LT(min_z, max_z); + + if (box_vertices != nullptr) { + *box_vertices = std::vector<Vertex>{ + {min_x, min_y, min_z}, {min_x, max_y, min_z}, {max_x, min_y, min_z}, + {max_x, max_y, min_z}, {min_x, min_y, max_z}, {min_x, max_y, max_z}, + {max_x, min_y, max_z}, {max_x, max_y, max_z}}; + } + if (box_vertices != nullptr) { + *box_triangles = std::vector<Triangle>{ + {0, 1, 4}, {1, 5, 4}, // wall facing the -x direction. + {2, 6, 3}, {3, 6, 7}, // wall facing the +x direction. + {0, 6, 2}, {0, 4, 6}, // wall facing the -y direction. + {1, 3, 7}, {1, 7, 5}, // wall facing the +y direction. + {0, 3, 1}, {0, 2, 3}, // wall facing the -z direction. + {4, 7, 6}, {4, 5, 7}, // wall facing the +z direction. + }; + } + if (box_wall_triangles != nullptr) { + *box_wall_triangles = std::vector<std::unordered_set<unsigned int>>{ + {0, 1}, {2, 3}, {4, 5}, {6, 7}, {8, 9}, {10, 11}}; + } +} + +std::vector<Path> TracePathsInTestcene( + size_t min_num_rays, size_t max_depth, float energy_threshold, + const float source_position[3], const std::vector<Vertex>& scene_vertices, + const std::vector<Triangle>& scene_triangles, + const std::vector<std::unordered_set<unsigned int>>& scene_triangle_groups, + const std::vector<MaterialName>& materials, SceneManager* scene_manager) { + // Build the scene. + scene_manager->BuildScene(scene_vertices, scene_triangles); + + // Assign materials. + std::default_random_engine engine(0); + std::uniform_real_distribution<float> distribution(0.0f, 1.0f); + std::function<float()> random_number_generator = [&engine, &distribution] { + return distribution(engine); + }; + AssignSurfaceMaterialsToTriangleGroups( + scene_triangle_groups, materials, random_number_generator, scene_manager); + + // Trace paths. + AcousticSource source(Eigen::Vector3f(source_position), kUnitEnergies, + random_number_generator); + PathTracer path_tracer(*scene_manager); + return path_tracer.TracePaths(source, min_num_rays, max_depth, + energy_threshold); +} + +std::vector<Path> GenerateUniformlyDistributedRayPaths( + const float source_position[3], size_t min_num_rays) { + // Use AcousticSource to generate rays in uniformly distributed directions. + std::default_random_engine engine(0); + std::uniform_real_distribution<float> distribution(0.0f, 1.0f); + AcousticSource source( + Eigen::Vector3f(source_position), kUnitEnergies, + [&engine, &distribution] { return distribution(engine); }); + + const size_t sqrt_num_rays = static_cast<size_t>( + std::ceil(std::sqrt(static_cast<float>(min_num_rays)))); + const size_t num_rays = sqrt_num_rays * sqrt_num_rays; + std::vector<Path> paths(num_rays); + const std::vector<AcousticRay>& rays = + source.GenerateStratifiedRays(num_rays, sqrt_num_rays); + for (size_t i = 0; i < num_rays; ++i) { + Path path; + path.rays.push_back(rays[i]); + paths[i] = path; + } + + return paths; +} + +void ExpectFloat3Close(const float actual_vector[3], + const float expected_vector[3]) { + EXPECT_FLOAT_EQ(actual_vector[0], expected_vector[0]); + EXPECT_FLOAT_EQ(actual_vector[1], expected_vector[1]); + EXPECT_FLOAT_EQ(actual_vector[2], expected_vector[2]); +} + +void ExpectFloat3Close(const float actual_vector[3], + const float expected_vector[3], float tolerance) { + EXPECT_NEAR(actual_vector[0], expected_vector[0], tolerance); + EXPECT_NEAR(actual_vector[1], expected_vector[1], tolerance); + EXPECT_NEAR(actual_vector[2], expected_vector[2], tolerance); +} + +void ValidateSparseFloatArray(const std::vector<float>& actual_array, + const std::vector<size_t>& expected_indices, + const std::vector<float>& expected_values, + float relative_error_tolerance) { + // First construct the expected array. + std::vector<float> expected_array(actual_array.size(), 0.0f); + for (size_t i = 0; i < expected_indices.size(); ++i) { + expected_array[expected_indices[i]] = expected_values[i]; + } + + // Compare with the actual array element-by-element. + for (size_t i = 0; i < actual_array.size(); ++i) { + EXPECT_NEAR(actual_array[i], expected_array[i], + expected_array[i] * relative_error_tolerance); + } +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/test_util.h b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/test_util.h new file mode 100644 index 000000000..8d9382b35 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/geometrical_acoustics/test_util.h @@ -0,0 +1,131 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Utilities useful for testing Geometrical Acoustics related classes. +#ifndef RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_TEST_UTIL_H_ +#define RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_TEST_UTIL_H_ + +#include <cstddef> +#include <functional> +#include <unordered_set> +#include <vector> + +#include "embree2/rtcore.h" +#include "api/resonance_audio_api.h" +#include "base/constants_and_types.h" +#include "geometrical_acoustics/mesh.h" +#include "geometrical_acoustics/path.h" +#include "geometrical_acoustics/scene_manager.h" +#include "platforms/common/room_effects_utils.h" + +namespace vraudio { + +// Array of |kNumReverbOctaveBands| of unit energies. Useful to set as initial +// energies of sound sources in tests. +static const std::array<float, kNumReverbOctaveBands> kUnitEnergies{ + {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}}; + +// Validates a distribution given the cumulative distribution function (CDF). +// For any probability density function (PDF), its CDF should be uniformly +// distributed. So we generate some samples, gather them into bins based on +// their CDF values, and verify that the number of samples are almost the +// same across all bins. +// +// @param num_samples Number of samples to generate. +// @param num_bins Number of bins to gather the samples. +// @param cdf Cumulative distribution function of the distribution to test. +void ValidateDistribution(const int num_samples, const int num_bins, + std::function<float()> cdf); + +// Adds a ground (a rectangle consisting of two triangles) to a test scene. +// +// @param scene Test scene to which the ground is added. +void AddTestGround(RTCScene scene); + +// Builds a box scene with 8 vertices, 12 triangles, and 6 walls. +// +// @param min_corner Corner of the box with minimum x-, y-, z-components. +// @param max_corner Corner of the box with maximum x-, y-, z-components. +// @param box_vertices Output vertices of the box. Not filled if nullptr is +// passed in. +// @param box_triangles Output triangles of the box. Not filled if nullptr is +// passed in. +// @param box_wall_triangles Output wall-to-triangles mapping, with six walls +// each having two triangles. Not filled if nullptr is passed in. +void BuildTestBoxScene( + const Vertex& min_corner, const Vertex& max_corner, + std::vector<Vertex>* box_vertices, std::vector<Triangle>* box_triangles, + std::vector<std::unordered_set<unsigned int>>* box_wall_triangles); + +// Traces ray paths in a created test scene. +// +// @param num_rays Number of rays. +// @param max_depth Maximum depth of tracing performed along a path. +// @param energy_threshold Energy threshold below which the tracing stops. +// @param source_position Position from which to shoot rays. +// @param scene_vertices Vertices of the scene. +// @param scene_triangles Triangles of the scene. +// @param scene_triangle_groups Groups of triangles that share the same +// material. +// @param materials Materials for each triangle group. +// @param scene_manager Scene manager that holds the created test scene. +// @return Vector of traced paths. +std::vector<Path> TracePathsInTestcene( + size_t num_rays, size_t max_depth, float energy_threshold, + const float source_position[3], const std::vector<Vertex>& scene_vertices, + const std::vector<Triangle>& scene_triangles, + const std::vector<std::unordered_set<unsigned int>>& scene_triangle_groups, + const std::vector<MaterialName>& materials, SceneManager* scene_manager); + +// Generated ray paths in uniformly distributed directions. +// +// @param source_position Position from which to shoot rays. +// @param min_num_rays Minimum number of rays generated. +// @return Vector of traced paths. +std::vector<Path> GenerateUniformlyDistributedRayPaths( + const float source_position[3], size_t min_num_rays); + +// Compares two float vectors[3]s and expect that their components are close. +// +// @param actual_vector Actual vector. +// @param expected_vector Expected vector. +void ExpectFloat3Close(const float actual_vector[3], + const float expected_vector[3]); + +// Same as above but with a user-specified tolerance. +// +// @param actual_vector Actual vector. +// @param expected_vector Expected vector. +// @param tolerance Tolerance for comparisons. +void ExpectFloat3Close(const float actual_vector[3], + const float expected_vector[3], float tolerance); + +// Validates a sparse float array using the indices and values of its +// non-zero elements. +// +// @param actual_array Actual array to be validated. +// @param expected_indices Indices of the non-zero elements of the expected +// array. +// @param expected_values Values of the non-zero elements of the expected array. +// @param relative_error_tolerance Tolerance of relative errors for element +// comparisons. +void ValidateSparseFloatArray(const std::vector<float>& actual_array, + const std::vector<size_t>& expected_indices, + const std::vector<float>& expected_values, + float relative_error_tolerance); +} // namespace vraudio + +#endif // RESONANCE_AUDIO_GEOMETRICAL_ACOUSTICS_TEST_UTIL_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/ambisonic_binaural_decoder_node.cc b/src/3rdparty/resonance-audio/resonance_audio/graph/ambisonic_binaural_decoder_node.cc new file mode 100644 index 000000000..a3c8ff0cb --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/ambisonic_binaural_decoder_node.cc @@ -0,0 +1,111 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "graph/ambisonic_binaural_decoder_node.h" + +#include "ambisonics/stereo_from_soundfield_converter.h" +#include "ambisonics/utils.h" +#include "base/constants_and_types.h" + +#include "dsp/sh_hrir_creator.h" + +namespace vraudio { + +AmbisonicBinauralDecoderNode::AmbisonicBinauralDecoderNode( + const SystemSettings& system_settings, int ambisonic_order, + const std::string& sh_hrir_filename, FftManager* fft_manager, + Resampler* resampler) + : system_settings_(system_settings), + num_ambisonic_channels_(GetNumPeriphonicComponents(ambisonic_order)), + is_stereo_speaker_mode_(system_settings_.IsStereoSpeakerModeEnabled()), + num_frames_processed_on_empty_input_( + system_settings_.GetFramesPerBuffer()), + stereo_output_buffer_(kNumStereoChannels, + system_settings.GetFramesPerBuffer()), + silence_input_buffer_(num_ambisonic_channels_, + system_settings.GetFramesPerBuffer()), + crossfader_(system_settings_.GetFramesPerBuffer()), + crossfaded_output_buffer_(kNumStereoChannels, + system_settings.GetFramesPerBuffer()), + temp_crossfade_buffer_(kNumStereoChannels, + system_settings.GetFramesPerBuffer()) { + silence_input_buffer_.Clear(); + EnableProcessOnEmptyInput(true); + std::unique_ptr<AudioBuffer> sh_hrirs = CreateShHrirsFromAssets( + sh_hrir_filename, system_settings_.GetSampleRateHz(), resampler); + CHECK_EQ(sh_hrirs->num_channels(), num_ambisonic_channels_); + ambisonic_binaural_decoder_.reset(new AmbisonicBinauralDecoder( + *sh_hrirs, system_settings_.GetFramesPerBuffer(), fft_manager)); +} + +AmbisonicBinauralDecoderNode::~AmbisonicBinauralDecoderNode() {} + +const AudioBuffer* AmbisonicBinauralDecoderNode::AudioProcess( + const NodeInput& input) { + + + const bool was_stereo_speaker_mode_enabled = is_stereo_speaker_mode_; + is_stereo_speaker_mode_ = system_settings_.IsStereoSpeakerModeEnabled(); + + const size_t num_frames = system_settings_.GetFramesPerBuffer(); + const AudioBuffer* input_buffer = input.GetSingleInput(); + if (input_buffer == nullptr) { + if (num_frames_processed_on_empty_input_ < num_frames && + !was_stereo_speaker_mode_enabled) { + // If we have no input, generate a silent input buffer until the node + // states are cleared. + num_frames_processed_on_empty_input_ += num_frames; + ambisonic_binaural_decoder_->Process(silence_input_buffer_, + &stereo_output_buffer_); + return &stereo_output_buffer_; + } else { + // Skip processing entirely when the states are fully cleared. + return nullptr; + } + } + + num_frames_processed_on_empty_input_ = 0; + + DCHECK_EQ(input_buffer->num_channels(), num_ambisonic_channels_); + DCHECK_EQ(input_buffer->num_frames(), num_frames); + + // If stereo speaker mode is enabled, perform M-S stereo decode. Otherwise, + // perform binaural decode. + if (is_stereo_speaker_mode_) { + StereoFromSoundfield(*input_buffer, &stereo_output_buffer_); + } else { + ambisonic_binaural_decoder_->Process(*input_buffer, &stereo_output_buffer_); + } + + if (is_stereo_speaker_mode_ != was_stereo_speaker_mode_enabled) { + // Apply linear crossfade between binaural decode and stereo decode outputs. + if (was_stereo_speaker_mode_enabled) { + StereoFromSoundfield(*input_buffer, &temp_crossfade_buffer_); + } else { + ambisonic_binaural_decoder_->Process(*input_buffer, + &temp_crossfade_buffer_); + } + crossfader_.ApplyLinearCrossfade(stereo_output_buffer_, + temp_crossfade_buffer_, + &crossfaded_output_buffer_); + return &crossfaded_output_buffer_; + } + + // Return the rendered output directly. + return &stereo_output_buffer_; +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/ambisonic_binaural_decoder_node.h b/src/3rdparty/resonance-audio/resonance_audio/graph/ambisonic_binaural_decoder_node.h new file mode 100644 index 000000000..548d44e01 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/ambisonic_binaural_decoder_node.h @@ -0,0 +1,88 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_GRAPH_AMBISONIC_BINAURAL_DECODER_NODE_H_ +#define RESONANCE_AUDIO_GRAPH_AMBISONIC_BINAURAL_DECODER_NODE_H_ + +#include <memory> +#include <string> + +#include "ambisonics/ambisonic_binaural_decoder.h" +#include "base/audio_buffer.h" +#include "dsp/fft_manager.h" +#include "dsp/resampler.h" +#include "graph/system_settings.h" +#include "node/processing_node.h" +#include "utils/buffer_crossfader.h" + +namespace vraudio { + +// Node that takes an ambisonic soundfield as input and renders a binaural +// stereo buffer as output. +class AmbisonicBinauralDecoderNode : public ProcessingNode { + public: + // Initializes AmbisonicBinauralDecoderNode class. + // + // @param system_settings Global system configuration. + // @param ambisonic_order Ambisonic order. + // @param sh_hrir_filename Filename to load the HRIR data from. + // @param fft_manager Pointer to a manager to perform FFT transformations. + // @resampler Pointer to a resampler used to convert HRIRs to the system rate. + AmbisonicBinauralDecoderNode(const SystemSettings& system_settings, + int ambisonic_order, + const std::string& sh_hrir_filename, + FftManager* fft_manager, Resampler* resampler); + + ~AmbisonicBinauralDecoderNode() override; + + protected: + // Implements ProcessingNode. + const AudioBuffer* AudioProcess(const NodeInput& input) override; + + private: + const SystemSettings& system_settings_; + + // Number of Ambisonic channels. + const size_t num_ambisonic_channels_; + + // Denotes if the stereo speaker mode is enabled. + bool is_stereo_speaker_mode_; + + // Ambisonic decoder used to render binaural output. + std::unique_ptr<AmbisonicBinauralDecoder> ambisonic_binaural_decoder_; + + size_t num_frames_processed_on_empty_input_; + + // Stereo output buffer. + AudioBuffer stereo_output_buffer_; + + // Silence mono buffer to render reverb tails. + AudioBuffer silence_input_buffer_; + + // Buffer crossfader to apply linear crossfade when the stereo speaker mode is + // changed. + BufferCrossfader crossfader_; + + // Stereo output buffer to store the crossfaded decode output when necessary. + AudioBuffer crossfaded_output_buffer_; + + // Temporary crossfade buffer to store the intermediate stereo output. + AudioBuffer temp_crossfade_buffer_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_GRAPH_AMBISONIC_BINAURAL_DECODER_NODE_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/ambisonic_mixing_encoder_node.cc b/src/3rdparty/resonance-audio/resonance_audio/graph/ambisonic_mixing_encoder_node.cc new file mode 100644 index 000000000..88405464c --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/ambisonic_mixing_encoder_node.cc @@ -0,0 +1,69 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "graph/ambisonic_mixing_encoder_node.h" + +#include "ambisonics/utils.h" +#include "base/constants_and_types.h" +#include "base/logging.h" + + +namespace vraudio { + +AmbisonicMixingEncoderNode::AmbisonicMixingEncoderNode( + const SystemSettings& system_settings, + const AmbisonicLookupTable& lookup_table, int ambisonic_order) + : system_settings_(system_settings), + lookup_table_(lookup_table), + ambisonic_order_(ambisonic_order), + gain_mixer_(GetNumPeriphonicComponents(ambisonic_order_), + system_settings_.GetFramesPerBuffer()), + coefficients_(GetNumPeriphonicComponents(ambisonic_order_)) {} + +const AudioBuffer* AmbisonicMixingEncoderNode::AudioProcess( + const NodeInput& input) { + + + const WorldPosition& listener_position = system_settings_.GetHeadPosition(); + const WorldRotation& listener_rotation = system_settings_.GetHeadRotation(); + + gain_mixer_.Reset(); + for (auto& input_buffer : input.GetInputBuffers()) { + const int source_id = input_buffer->source_id(); + const auto source_parameters = + system_settings_.GetSourceParameters(source_id); + DCHECK_NE(source_id, kInvalidSourceId); + DCHECK_EQ(input_buffer->num_channels(), 1U); + + // Compute the relative source direction in spherical angles. + const ObjectTransform& source_transform = + source_parameters->object_transform; + WorldPosition relative_direction; + GetRelativeDirection(listener_position, listener_rotation, + source_transform.position, &relative_direction); + const SphericalAngle source_direction = + SphericalAngle::FromWorldPosition(relative_direction); + + lookup_table_.GetEncodingCoeffs(ambisonic_order_, source_direction, + source_parameters->spread_deg, + &coefficients_); + + gain_mixer_.AddInputChannel((*input_buffer)[0], source_id, coefficients_); + } + return gain_mixer_.GetOutput(); +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/ambisonic_mixing_encoder_node.h b/src/3rdparty/resonance-audio/resonance_audio/graph/ambisonic_mixing_encoder_node.h new file mode 100644 index 000000000..865677571 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/ambisonic_mixing_encoder_node.h @@ -0,0 +1,71 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_GRAPH_AMBISONIC_MIXING_ENCODER_NODE_H_ +#define RESONANCE_AUDIO_GRAPH_AMBISONIC_MIXING_ENCODER_NODE_H_ + +#include <vector> + +#include "ambisonics/ambisonic_lookup_table.h" +#include "base/audio_buffer.h" +#include "base/spherical_angle.h" +#include "dsp/gain_mixer.h" +#include "graph/system_settings.h" +#include "node/processing_node.h" + +namespace vraudio { + +// Node that accepts single mono sound object buffer as input and encodes it +// into an Ambisonic sound field. +class AmbisonicMixingEncoderNode : public ProcessingNode { + public: + // Initializes AmbisonicMixingEncoderNode class. + // + // @param system_settings Global system configuration. + // @param lookup_table Ambisonic encoding lookup table. + // @param ambisonic_order Order of Ambisonic sources. + AmbisonicMixingEncoderNode(const SystemSettings& system_settings, + const AmbisonicLookupTable& lookup_table, + int ambisonic_order); + + // Node implementation. + bool CleanUp() final { + CallCleanUpOnInputNodes(); + // Prevent node from being disconnected when all sources are removed. + return false; + } + + protected: + // Implements ProcessingNode. + const AudioBuffer* AudioProcess(const NodeInput& input) override; + + private: + const SystemSettings& system_settings_; + const AmbisonicLookupTable& lookup_table_; + + // Ambisonic order of encoded sources. + const int ambisonic_order_; + + // |GainMixer| instance. + GainMixer gain_mixer_; + + // Encoding coefficient values to be applied to encode the input. + std::vector<float> coefficients_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_GRAPH_AMBISONIC_MIXING_ENCODER_NODE_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/ambisonic_mixing_encoder_node_test.cc b/src/3rdparty/resonance-audio/resonance_audio/graph/ambisonic_mixing_encoder_node_test.cc new file mode 100644 index 000000000..1977ab646 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/ambisonic_mixing_encoder_node_test.cc @@ -0,0 +1,150 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "graph/ambisonic_mixing_encoder_node.h" + +#include <vector> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "ambisonics/ambisonic_lookup_table.h" +#include "ambisonics/utils.h" +#include "api/resonance_audio_api.h" +#include "base/constants_and_types.h" +#include "base/object_transform.h" +#include "config/source_config.h" +#include "graph/buffered_source_node.h" +#include "node/sink_node.h" +#include "node/source_node.h" +#include "utils/test_util.h" + +namespace vraudio { + +namespace { + +// Number of frames per input buffer. +const size_t kFramesPerBuffer = 16; + +// Simulated system sample rate. +const int kSampleRate = 48000; + +} // namespace + +// Provides unit tests for |AmbisonicMixingEncoderNode|. +class AmbisonicMixingEncoderNodeTest + : public ::testing::TestWithParam<SourceGraphConfig> { + protected: + AmbisonicMixingEncoderNodeTest() + : system_settings_(kNumStereoChannels, kFramesPerBuffer, kSampleRate), + lookup_table_(kMaxSupportedAmbisonicOrder) {} + + void SetUp() override { + const auto source_config = GetParam(); + ambisonic_order_ = source_config.ambisonic_order; + ambisonic_mixing_encoder_node_ = + std::make_shared<AmbisonicMixingEncoderNode>( + system_settings_, lookup_table_, ambisonic_order_); + } + + const AudioBuffer* ProcessMultipleInputs(size_t num_sources, + const WorldPosition& position, + float spread_deg) { + // Create the node graph, adding input nodes to the Ambisonic Mixing Encoder + // Node. + buffered_source_nodes_.clear(); + auto parameters_manager = system_settings_.GetSourceParametersManager(); + for (size_t i = 0; i < num_sources; ++i) { + buffered_source_nodes_.emplace_back(std::make_shared<BufferedSourceNode>( + static_cast<SourceId>(i) /*source id*/, kNumMonoChannels, + kFramesPerBuffer)); + parameters_manager->Register(static_cast<SourceId>(i)); + } + const AudioBuffer* output_buffer = nullptr; + + for (auto& input_node : buffered_source_nodes_) { + ambisonic_mixing_encoder_node_->Connect(input_node); + } + auto output_node = std::make_shared<SinkNode>(); + output_node->Connect(ambisonic_mixing_encoder_node_); + + // Input data containing unit pulses. + const std::vector<float> kInputData(kFramesPerBuffer, 1.0f); + + for (size_t index = 0; index < buffered_source_nodes_.size(); ++index) { + AudioBuffer* input_buffer = + buffered_source_nodes_[index] + ->GetMutableAudioBufferAndSetNewBufferFlag(); + (*input_buffer)[0] = kInputData; + auto source_parameters = parameters_manager->GetMutableParameters( + static_cast<SourceId>(index)); + source_parameters->object_transform.position = position; + source_parameters->spread_deg = spread_deg; + } + + const std::vector<const AudioBuffer*>& buffer_vector = + output_node->ReadInputs(); + if (!buffer_vector.empty()) { + DCHECK_EQ(buffer_vector.size(), 1U); + output_buffer = buffer_vector.front(); + } + + return output_buffer; + } + + SystemSettings system_settings_; + int ambisonic_order_; + AmbisonicLookupTable lookup_table_; + std::shared_ptr<AmbisonicMixingEncoderNode> ambisonic_mixing_encoder_node_; + std::vector<std::shared_ptr<BufferedSourceNode>> buffered_source_nodes_; +}; + +// Tests that a number of sound objects encoded in the same direction are +// correctly combined into an output buffer. +TEST_P(AmbisonicMixingEncoderNodeTest, TestEncodeAndMix) { + // Number of sources to encode and mix. + const size_t kNumSources = 4; + // Minimum angular source spread of 0 ensures that no gain correction + // coefficients are to be applied to the Ambisonic encoding coefficients. + const float kSpreadDeg = 0.0f; + // Arbitrary world position of sound sources corresponding to the 36 degrees + // azimuth and 18 degrees elevation. + const WorldPosition kPosition = + WorldPosition(-0.55901699f, 0.30901699f, -0.76942088f); + // Expected Ambisonic output for a single source at the above position (as + // generated using /matlab/ambisonics/ambix/ambencode.m Matlab function): + const std::vector<float> kExpectedSingleSourceOutput = { + 1.0f, 0.55901699f, 0.30901699f, 0.76942088f, + 0.74498856f, 0.29920441f, -0.35676274f, 0.41181955f, + 0.24206145f, 0.64679299f, 0.51477443f, -0.17888019f, + -0.38975424f, -0.24620746f, 0.16726035f, -0.21015578f}; + + const AudioBuffer* output_buffer = + ProcessMultipleInputs(kNumSources, kPosition, kSpreadDeg); + + const size_t num_channels = GetNumPeriphonicComponents(ambisonic_order_); + + for (size_t i = 0; i < num_channels; ++i) { + EXPECT_NEAR( + kExpectedSingleSourceOutput[i] * static_cast<float>(kNumSources), + (*output_buffer)[i][kFramesPerBuffer - 1], kEpsilonFloat); + } +} + +INSTANTIATE_TEST_CASE_P(TestParameters, AmbisonicMixingEncoderNodeTest, + testing::Values(BinauralLowQualityConfig(), + BinauralMediumQualityConfig(), + BinauralHighQualityConfig())); + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/binaural_surround_renderer_impl.cc b/src/3rdparty/resonance-audio/resonance_audio/graph/binaural_surround_renderer_impl.cc new file mode 100644 index 000000000..79889fabd --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/binaural_surround_renderer_impl.cc @@ -0,0 +1,495 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "graph/binaural_surround_renderer_impl.h" + +#include <algorithm> +#include <functional> + +#include "base/misc_math.h" +#include "base/simd_utils.h" +#include "base/spherical_angle.h" +#include "graph/resonance_audio_api_impl.h" +#include "platforms/common/room_effects_utils.h" +#include "platforms/common/room_properties.h" +#include "utils/planar_interleaved_conversion.h" + +namespace vraudio { + +namespace { + +// Maximum number of audio buffers in buffer queue. +const size_t kNumMaxBuffers = 64; + +// Output gain, to avoid clipping of individual virtual speaker channels. +const float kGain = 0.5f; + +} // namespace + +BinauralSurroundRendererImpl::BinauralSurroundRendererImpl( + size_t frames_per_buffer, int sample_rate_hz) + : + resonance_audio_api_(nullptr), + frames_per_buffer_(frames_per_buffer), + sample_rate_hz_(sample_rate_hz), + surround_format_(kInvalid), + num_input_channels_(0), + output_buffer_(kNumStereoChannels, frames_per_buffer), + total_frames_buffered_(0), + num_zero_padded_frames_(0), + output_gain_(1.0f) { +} + +bool BinauralSurroundRendererImpl::Init(SurroundFormat surround_format) { + surround_format_ = surround_format; + num_input_channels_ = + GetExpectedNumChannelsFromSurroundFormat(surround_format); + + temp_planar_buffer_ptrs_.resize(num_input_channels_); + + input_audio_buffer_queue_.reset(new ThreadsafeFifo<AudioBuffer>( + kNumMaxBuffers, AudioBuffer(num_input_channels_, frames_per_buffer_))); + + buffer_partitioner_.reset(new BufferPartitioner( + num_input_channels_, frames_per_buffer_, + std::bind(&BinauralSurroundRendererImpl::BufferPartitionerCallback, this, + std::placeholders::_1))); + + buffer_unpartitioner_.reset(new BufferUnpartitioner( + kNumStereoChannels, frames_per_buffer_, + std::bind(&BinauralSurroundRendererImpl::ProcessBuffer, this))); + + resonance_audio_api_.reset(CreateResonanceAudioApi( + kNumStereoChannels, frames_per_buffer_, sample_rate_hz_)); + + if (surround_format == kSurroundMono || surround_format == kSurroundStereo || + surround_format == kSurroundFiveDotOne || + surround_format == kSurroundSevenDotOne) { + InitializeRoomReverb(); + } + // Initialize rendering mode. + switch (surround_format) { + case kSurroundMono: + InitializeBinauralMono(); + break; + case kSurroundStereo: + InitializeBinauralStereo(); + break; + case kSurroundFiveDotOne: + InitializeBinauralSurround5dot1(); + break; + case kSurroundSevenDotOne: + InitializeBinauralSurround7dot1(); + break; + case kFirstOrderAmbisonics: + case kSecondOrderAmbisonics: + case kThirdOrderAmbisonics: + InitializeAmbisonics(); + break; + case kFirstOrderAmbisonicsWithNonDiegeticStereo: + case kSecondOrderAmbisonicsWithNonDiegeticStereo: + case kThirdOrderAmbisonicsWithNonDiegeticStereo: + InitializeAmbisonicsWithNonDiegeticStereo(); + break; + default: + LOG(FATAL) << "Undefined rendering mode"; + return false; + break; + } + return true; +} + +BinauralSurroundRendererImpl::BinauralSurroundRendererImpl() + : + resonance_audio_api_(nullptr), + frames_per_buffer_(0), + sample_rate_hz_(0), + total_frames_buffered_(0), + num_zero_padded_frames_(0) { +} + +AudioBuffer* BinauralSurroundRendererImpl::BufferPartitionerCallback( + AudioBuffer* processed_buffer) { + if (processed_buffer != nullptr) { + input_audio_buffer_queue_->ReleaseInputObject(processed_buffer); + } + DCHECK(!input_audio_buffer_queue_->Full()); + return input_audio_buffer_queue_->AcquireInputObject(); +} + +void BinauralSurroundRendererImpl::SetStereoSpeakerMode(bool enabled) { + resonance_audio_api_->SetStereoSpeakerMode(enabled); +} + +size_t BinauralSurroundRendererImpl::GetExpectedNumChannelsFromSurroundFormat( + SurroundFormat surround_format) { + switch (surround_format) { + case kSurroundMono: + return kNumMonoChannels; + case kSurroundStereo: + return kNumStereoChannels; + case kSurroundFiveDotOne: + return kNumSurroundFiveDotOneChannels; + case kSurroundSevenDotOne: + return kNumSurroundSevenDotOneChannels; + case kFirstOrderAmbisonics: + return kNumFirstOrderAmbisonicChannels; + case kSecondOrderAmbisonics: + return kNumSecondOrderAmbisonicChannels; + case kThirdOrderAmbisonics: + return kNumThirdOrderAmbisonicChannels; + case kFirstOrderAmbisonicsWithNonDiegeticStereo: + return kNumFirstOrderAmbisonicChannels + kNumStereoChannels; + case kSecondOrderAmbisonicsWithNonDiegeticStereo: + return kNumSecondOrderAmbisonicChannels + kNumStereoChannels; + case kThirdOrderAmbisonicsWithNonDiegeticStereo: + return kNumThirdOrderAmbisonicChannels + kNumStereoChannels; + default: + LOG(FATAL) << "Undefined surround format mode"; + return false; + break; + } + return 0; +} + +void BinauralSurroundRendererImpl::InitializeBinauralMono() { + source_ids_.resize(kNumMonoChannels); + // Front (0 degrees): + source_ids_[0] = CreateSoundObject(0.0f); + output_gain_ = kGain; +} + +void BinauralSurroundRendererImpl::InitializeBinauralStereo() { + + source_ids_.resize(kNumStereoChannels); + // Front left (30 degrees): + source_ids_[0] = CreateSoundObject(30.0f); + // Front right (-30 degrees): + source_ids_[1] = CreateSoundObject(-30.0f); + output_gain_ = kGain; +} + +void BinauralSurroundRendererImpl::InitializeBinauralSurround5dot1() { + source_ids_.resize(kNumSurroundFiveDotOneChannels); + // Left (30 degrees): + source_ids_[0] = CreateSoundObject(30.0f); + // Right (-30 degrees): + source_ids_[1] = CreateSoundObject(-30.0f); + // Center (0 degrees): + source_ids_[2] = CreateSoundObject(0.0f); + // Low frequency effects at front center: + source_ids_[3] = CreateSoundObject(0.0f); + // Left surround (110 degrees): + source_ids_[4] = CreateSoundObject(110.0f); + // Right surround (-110 degrees): + source_ids_[5] = CreateSoundObject(-110.0f); + output_gain_ = kGain; +} + +void BinauralSurroundRendererImpl::InitializeBinauralSurround7dot1() { + source_ids_.resize(kNumSurroundSevenDotOneChannels); + // Left (30 degrees): + source_ids_[0] = CreateSoundObject(30.0f); + // Right (-30 degrees): + source_ids_[1] = CreateSoundObject(-30.0f); + // Center (0 degrees): + source_ids_[2] = CreateSoundObject(0.0f); + // Low frequency effects at front center: + source_ids_[3] = CreateSoundObject(0.0f); + // Left surround 1 (90 degrees): + source_ids_[4] = CreateSoundObject(90.0f); + // Right surround 1 (-90 degrees): + source_ids_[5] = CreateSoundObject(-90.0f); + // Left surround 2 (150 degrees): + source_ids_[6] = CreateSoundObject(150.0f); + // Right surround 2 (-150 degrees): + source_ids_[7] = CreateSoundObject(-150.0f); + output_gain_ = kGain; +} + +void BinauralSurroundRendererImpl::InitializeAmbisonics() { + source_ids_.resize(1); + source_ids_[0] = + resonance_audio_api_->CreateAmbisonicSource(num_input_channels_); +} + +void BinauralSurroundRendererImpl::InitializeAmbisonicsWithNonDiegeticStereo() { + source_ids_.resize(2); + CHECK_GT(num_input_channels_, kNumStereoChannels); + source_ids_[0] = resonance_audio_api_->CreateAmbisonicSource( + num_input_channels_ - kNumStereoChannels); + source_ids_[1] = resonance_audio_api_->CreateStereoSource(kNumStereoChannels); +} + +SourceId BinauralSurroundRendererImpl::CreateSoundObject(float azimuth_deg) { + static const float kZeroElevation = 0.0f; + auto speaker_position = + vraudio::SphericalAngle::FromDegrees(azimuth_deg, kZeroElevation) + .GetWorldPositionOnUnitSphere(); + const SourceId source_id = resonance_audio_api_->CreateSoundObjectSource( + RenderingMode::kBinauralHighQuality); + resonance_audio_api_->SetSourcePosition( + source_id, speaker_position[0], speaker_position[1], speaker_position[2]); + return source_id; +} + +void BinauralSurroundRendererImpl::InitializeRoomReverb() { + // The following settings has been applied based on AESTD1001.1.01-10. + RoomProperties room_properties; + room_properties.dimensions[0] = 9.54f; + room_properties.dimensions[1] = 6.0f; + room_properties.dimensions[2] = 15.12f; + room_properties.reverb_brightness = 0.0f; + room_properties.reflection_scalar = 1.0f; + // Reduce reverb gain to compensate for virtual speakers gain. + room_properties.reverb_gain = output_gain_; + for (size_t i = 0; i < kNumRoomSurfaces; ++i) { + room_properties.material_names[i] = MaterialName::kUniform; + } + resonance_audio_api_->SetReflectionProperties( + ComputeReflectionProperties(room_properties)); + resonance_audio_api_->SetReverbProperties( + ComputeReverbProperties(room_properties)); + resonance_audio_api_->EnableRoomEffects(true); +} + +size_t BinauralSurroundRendererImpl::GetNumAvailableFramesInInputBuffer() + const { + DCHECK_NE(surround_format_, kInvalid); + if (num_zero_padded_frames_ > 0) { + // Zero padded output buffers must be consumed prior to + // |AddInterleavedBuffer| calls; + return 0; + } + if (input_audio_buffer_queue_->Full()) { + return 0; + } + // Subtract two buffers from the available input slots to ensure the buffer + // partitioner can be flushed at any time while keeping an extra buffer + // available in the |buffer_partitioner_| callback for the next incoming data. + const size_t num_frames_available_in_input_slots = + (kNumMaxBuffers - input_audio_buffer_queue_->Size() - 2) * + frames_per_buffer_; + DCHECK_GT(frames_per_buffer_, buffer_partitioner_->GetNumBufferedFrames()); + const size_t num_frames_available_in_buffer_partitioner = + frames_per_buffer_ - buffer_partitioner_->GetNumBufferedFrames(); + return num_frames_available_in_input_slots + + num_frames_available_in_buffer_partitioner; +} + +size_t BinauralSurroundRendererImpl::AddInterleavedInput( + const int16* input_buffer_ptr, size_t num_channels, size_t num_frames) { + return AddInputBufferTemplated<const int16*>(input_buffer_ptr, num_channels, + num_frames); +} + +size_t BinauralSurroundRendererImpl::AddInterleavedInput( + const float* input_buffer_ptr, size_t num_channels, size_t num_frames) { + return AddInputBufferTemplated<const float*>(input_buffer_ptr, num_channels, + num_frames); +} + +size_t BinauralSurroundRendererImpl::AddPlanarInput( + const int16* const* input_buffer_ptrs, size_t num_channels, + size_t num_frames) { + return AddInputBufferTemplated<const int16* const*>(input_buffer_ptrs, + num_channels, num_frames); +} + +size_t BinauralSurroundRendererImpl::AddPlanarInput( + const float* const* input_buffer_ptrs, size_t num_channels, + size_t num_frames) { + return AddInputBufferTemplated<const float* const*>(input_buffer_ptrs, + num_channels, num_frames); +} + +template <typename BufferType> +size_t BinauralSurroundRendererImpl::AddInputBufferTemplated( + const BufferType input_buffer_ptr, size_t num_channels, size_t num_frames) { + DCHECK_NE(surround_format_, kInvalid); + if (num_channels != num_input_channels_) { + LOG(WARNING) << "Invalid number of input channels"; + return 0; + } + + if (num_zero_padded_frames_ > 0) { + LOG(WARNING) << "Zero padded output buffers must be consumed prior to " + "|AddInterleavedBuffer| calls"; + return 0; + } + const size_t num_available_input_frames = + std::min(num_frames, GetNumAvailableFramesInInputBuffer()); + + buffer_partitioner_->AddBuffer(input_buffer_ptr, num_input_channels_, + num_available_input_frames); + total_frames_buffered_ += num_available_input_frames; + return num_available_input_frames; +} + +size_t BinauralSurroundRendererImpl::GetAvailableFramesInStereoOutputBuffer() + const { + const size_t num_available_samples_in_buffers = + (input_audio_buffer_queue_->Size() * frames_per_buffer_) + + buffer_unpartitioner_->GetNumBufferedFrames(); + return std::min(total_frames_buffered_, num_available_samples_in_buffers); +} + +size_t BinauralSurroundRendererImpl::GetInterleavedStereoOutput( + int16* output_buffer_ptr, size_t num_frames) { + return GetStereoOutputBufferTemplated<int16*>(output_buffer_ptr, num_frames); +} + +size_t BinauralSurroundRendererImpl::GetInterleavedStereoOutput( + float* output_buffer_ptr, size_t num_frames) { + return GetStereoOutputBufferTemplated<float*>(output_buffer_ptr, num_frames); +} + +size_t BinauralSurroundRendererImpl::GetPlanarStereoOutput( + int16** output_buffer_ptrs, size_t num_frames) { + return GetStereoOutputBufferTemplated<int16**>(output_buffer_ptrs, + num_frames); +} + +size_t BinauralSurroundRendererImpl::GetPlanarStereoOutput( + float** output_buffer_ptrs, size_t num_frames) { + return GetStereoOutputBufferTemplated<float**>(output_buffer_ptrs, + num_frames); +} + +template <typename BufferType> +size_t BinauralSurroundRendererImpl::GetStereoOutputBufferTemplated( + BufferType output_buffer_ptr, size_t num_frames) { + DCHECK_NE(surround_format_, kInvalid); + const size_t num_frames_available = GetAvailableFramesInStereoOutputBuffer(); + size_t num_frames_to_be_processed = + std::min(num_frames_available, num_frames); + if (num_frames_to_be_processed > total_frames_buffered_) { + // Avoid outputting zero padded input frames from |TriggerProcessing| + // calls. + num_frames_to_be_processed = total_frames_buffered_; + } + + const size_t num_frames_written = buffer_unpartitioner_->GetBuffer( + output_buffer_ptr, kNumStereoChannels, num_frames_to_be_processed); + + DCHECK_GE(total_frames_buffered_, num_frames_written); + total_frames_buffered_ -= num_frames_written; + + if (total_frames_buffered_ == 0) { + // Clear zero padded frames from |TriggerProcessing| calls. + buffer_unpartitioner_->Clear(); + num_zero_padded_frames_ = 0; + } + + return num_frames_written; +} + +void BinauralSurroundRendererImpl::Clear() { + input_audio_buffer_queue_->Clear(); + buffer_partitioner_->Clear(); + buffer_unpartitioner_->Clear(); + total_frames_buffered_ = 0; + num_zero_padded_frames_ = 0; +} + +bool BinauralSurroundRendererImpl::TriggerProcessing() { + if (num_zero_padded_frames_ > 0) { + LOG(WARNING) << "Zero padded output buffers must be consumed prior to " + "|TriggerProcessing| calls"; + return false; + } + num_zero_padded_frames_ = buffer_partitioner_->Flush(); + return num_zero_padded_frames_ > 0; +} + +void BinauralSurroundRendererImpl::SetHeadRotation(float w, float x, float y, + float z) { + resonance_audio_api_->SetHeadRotation(x, y, z, w); +} + +AudioBuffer* BinauralSurroundRendererImpl::ProcessBuffer() { + if (input_audio_buffer_queue_->Size() == 0) { + LOG(WARNING) << "Buffer underflow detected"; + return nullptr; + } + + const AudioBuffer* input = input_audio_buffer_queue_->AcquireOutputObject(); + DCHECK_EQ(input->num_frames(), frames_per_buffer_); + DCHECK_EQ(num_input_channels_, input->num_channels()); + GetRawChannelDataPointersFromAudioBuffer(*input, &temp_planar_buffer_ptrs_); + // Initialize surround rendering. + const float* planar_ptr; + + switch (surround_format_) { + case kSurroundMono: + case kSurroundStereo: + case kSurroundFiveDotOne: + case kSurroundSevenDotOne: + DCHECK_EQ(input->num_channels(), source_ids_.size()); + for (size_t source_itr = 0; source_itr < source_ids_.size(); + ++source_itr) { + planar_ptr = (*input)[source_itr].begin(); + resonance_audio_api_->SetPlanarBuffer(source_ids_[source_itr], + &planar_ptr, kNumMonoChannels, + input->num_frames()); + } + break; + case kFirstOrderAmbisonics: + case kSecondOrderAmbisonics: + case kThirdOrderAmbisonics: + DCHECK_EQ(source_ids_.size(), 1U); + resonance_audio_api_->SetPlanarBuffer( + source_ids_[0], temp_planar_buffer_ptrs_.data(), + input->num_channels(), input->num_frames()); + break; + case kFirstOrderAmbisonicsWithNonDiegeticStereo: + case kSecondOrderAmbisonicsWithNonDiegeticStereo: + case kThirdOrderAmbisonicsWithNonDiegeticStereo: + DCHECK_EQ(source_ids_.size(), 2U); + DCHECK_GT(input->num_channels(), kNumStereoChannels); + static_cast<ResonanceAudioApiImpl*>(resonance_audio_api_.get()) + ->SetPlanarBuffer(source_ids_[0], temp_planar_buffer_ptrs_.data(), + input->num_channels() - kNumStereoChannels, + input->num_frames()); + static_cast<ResonanceAudioApiImpl*>(resonance_audio_api_.get()) + ->SetPlanarBuffer(source_ids_[1], + temp_planar_buffer_ptrs_.data() + + (input->num_channels() - kNumStereoChannels), + kNumStereoChannels, input->num_frames()); + break; + default: + LOG(FATAL) << "Undefined surround format"; + break; + } + + // Create a copy of the processed |AudioBuffer| to pass it to output buffer + // queue. + auto* const vraudio_api_impl = + static_cast<ResonanceAudioApiImpl*>(resonance_audio_api_.get()); + vraudio_api_impl->ProcessNextBuffer(); + output_buffer_ = *vraudio_api_impl->GetStereoOutputBuffer(); + + if (output_gain_ != 1.0f) { + for (AudioBuffer::Channel& channel : output_buffer_) { + ScalarMultiply(output_buffer_.num_frames(), output_gain_, channel.begin(), + channel.begin()); + } + } + input_audio_buffer_queue_->ReleaseOutputObject(input); + return &output_buffer_; +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/binaural_surround_renderer_impl.h b/src/3rdparty/resonance-audio/resonance_audio/graph/binaural_surround_renderer_impl.h new file mode 100644 index 000000000..c4916d664 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/binaural_surround_renderer_impl.h @@ -0,0 +1,190 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_GRAPH_BINAURAL_SURROUND_RENDERER_IMPL_H_ +#define RESONANCE_AUDIO_GRAPH_BINAURAL_SURROUND_RENDERER_IMPL_H_ + +#include <memory> +#include <string> + +#include "api/binaural_surround_renderer.h" +#include "api/resonance_audio_api.h" +#include "base/constants_and_types.h" +#include "utils/buffer_partitioner.h" +#include "utils/buffer_unpartitioner.h" +#include "utils/threadsafe_fifo.h" + +namespace vraudio { + +// Renders virtual surround sound as well as ambisonic soundfields to binaural +// stereo. +class BinauralSurroundRendererImpl : public BinauralSurroundRenderer { + public: + // Constructor. + // + // @param frames_per_buffer Number of frames in output buffer. + // @param sample_rate_hz Sample rate of audio buffers. + BinauralSurroundRendererImpl(size_t frames_per_buffer, int sample_rate_hz); + + ~BinauralSurroundRendererImpl() override{}; + + // Initializes surround sound decoding. + // + // @param surround_format Surround sound input format. + // @return True on success. + bool Init(SurroundFormat surround_format); + + // Implements |AudioRenderer| interface. + void SetStereoSpeakerMode(bool enabled) override; + size_t GetNumAvailableFramesInInputBuffer() const override; + size_t AddInterleavedInput(const int16* input_buffer_ptr, size_t num_channels, + size_t num_frames) override; + size_t AddInterleavedInput(const float* input_buffer_ptr, size_t num_channels, + size_t num_frames) override; + size_t AddPlanarInput(const int16* const* input_buffer_ptrs, + size_t num_channels, size_t num_frames) override; + size_t AddPlanarInput(const float* const* input_buffer_ptrs, + size_t num_channels, size_t num_frames) override; + size_t GetAvailableFramesInStereoOutputBuffer() const override; + size_t GetInterleavedStereoOutput(int16* output_buffer_ptr, + size_t num_frames) override; + size_t GetInterleavedStereoOutput(float* output_buffer_ptr, + size_t num_frames) override; + size_t GetPlanarStereoOutput(int16** output_buffer_ptrs, + size_t num_frames) override; + size_t GetPlanarStereoOutput(float** output_buffer_ptrs, + size_t num_frames) override; + bool TriggerProcessing() override; + void Clear() override; + void SetHeadRotation(float w, float x, float y, float z) override; + + protected: + // Protected default constructor for mock tests. + BinauralSurroundRendererImpl(); + + private: + // Callback triggered by |buffer_partitioner_| whenever a new |AudioBuffer| + // has been generated. + // + // @param processed_buffer Pointer to processed buffer. + // @return Pointer to next |AudioBuffer| to be filled up. + AudioBuffer* BufferPartitionerCallback(AudioBuffer* processed_buffer); + + // Helper method to implement |AddInterleavedInput| independently from the + // sample type. + // + // @tparam BufferType Input buffer type. + // @param input_buffer_ptr Pointer to interleaved input data. + // @param num_channels Number of channels in input buffer. + // @param num_frames Number of frames in input buffer. + // @return The number of consumed samples. + template <typename BufferType> + size_t AddInputBufferTemplated(const BufferType input_buffer_ptr, + size_t num_channels, size_t num_frames); + + // Helper method to implement |GetInterleavedOutput| independently from the + // sample type. + // + // @tparam BufferType Output buffer type. + // @param output_buffer_ptr Pointer to allocated interleaved output buffer. + // @param num_frames Size of output buffer in frames. + // @return The number of consumed frames. + template <typename BufferType> + size_t GetStereoOutputBufferTemplated(BufferType output_buffer_ptr, + size_t num_frames); + + // Helper method to obtain the expected number of audio channels for a given + // surround format. + // + // @param surround_format Surround format query. + // @return Number of audio channels. + static size_t GetExpectedNumChannelsFromSurroundFormat( + SurroundFormat surround_format); + + // Process method executed by |buffer_unpartitioner_|. + AudioBuffer* ProcessBuffer(); + + // Initializes binaural mono rendering. + void InitializeBinauralMono(); + + // Initializes binaural stereo rendering. + void InitializeBinauralStereo(); + + // Initializes binaural 5.1 rendering. + void InitializeBinauralSurround5dot1(); + + // Initializes binaural 7.1 rendering. + void InitializeBinauralSurround7dot1(); + + // Initializes binaural ambisonic rendering. + void InitializeAmbisonics(); + + // Initializes binaural ambisonic rendering with non-diegetic stereo. + void InitializeAmbisonicsWithNonDiegeticStereo(); + + // Creates a sound object at given angle within the horizontal listener plane. + SourceId CreateSoundObject(float azimuth_deg); + + // Initializes room reverb for virtual surround sound rendering. + void InitializeRoomReverb(); + + // ResonanceAudioApi instance. + std::unique_ptr<ResonanceAudioApi> resonance_audio_api_; + + // Frames per buffer. + const size_t frames_per_buffer_; + + // System sample rate. + const int sample_rate_hz_; + + // Selected surround sound format. + SurroundFormat surround_format_; + + // Number of input channels. + size_t num_input_channels_; + + // Partitions input buffers into |AudioBuffer|s. + std::unique_ptr<BufferPartitioner> buffer_partitioner_; + + // Buffer queue containing partitioned input |AudioBuffer|s. + std::unique_ptr<ThreadsafeFifo<AudioBuffer>> input_audio_buffer_queue_; + + // Binaural stereo output buffer. + AudioBuffer output_buffer_; + + // Unpartitions processed |AudioBuffer|s into interleaved output buffers. + std::unique_ptr<BufferUnpartitioner> buffer_unpartitioner_; + + // Vector containing the source ids of all rendered sound sources. + std::vector<SourceId> source_ids_; + + // Total number of frames currently buffered. + size_t total_frames_buffered_; + + // Total number of zero padded frames from |TriggerProcessing| calls. + size_t num_zero_padded_frames_; + + // Temporary buffer to store pointers to planar ambisonic and stereo channels. + std::vector<const float*> temp_planar_buffer_ptrs_; + + // Global output gain adjustment, to avoid clipping of individual channels + // in virtual speaker modes. + float output_gain_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_GRAPH_BINAURAL_SURROUND_RENDERER_IMPL_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/binaural_surround_renderer_impl_test.cc b/src/3rdparty/resonance-audio/resonance_audio/graph/binaural_surround_renderer_impl_test.cc new file mode 100644 index 000000000..98652ca3e --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/binaural_surround_renderer_impl_test.cc @@ -0,0 +1,202 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "graph/binaural_surround_renderer_impl.h" + +#include <algorithm> +#include <vector> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/constants_and_types.h" + +namespace vraudio { + +namespace { + +class BinauralSurroundRendererTest + : public ::testing::TestWithParam< + BinauralSurroundRenderer::SurroundFormat> { + protected: + BinauralSurroundRendererTest() {} + + // Virtual methods from ::testing::Test + ~BinauralSurroundRendererTest() override {} + void SetUp() override {} + void TearDown() override {} + + void InitBinauralSurroundRenderer(size_t frames_per_buffer, + int sample_rate_hz) { + binaural_surround_renderer_api_.reset( + new BinauralSurroundRendererImpl(frames_per_buffer, sample_rate_hz)); + } + + // Processes an interleaved input vector and returns interleaved binaural + // stereo output. + std::vector<float> ProcessInterleaved( + const std::vector<float>& interleaved_input, size_t num_channels, + size_t frames_per_buffer) { + EXPECT_EQ(interleaved_input.size() % (num_channels * frames_per_buffer), + 0U); + std::vector<float> interleaved_output; + const size_t num_buffers = + interleaved_input.size() / (num_channels * frames_per_buffer); + for (size_t b = 0; b < num_buffers; ++b) { + const float* interleaved_input_ptr = + interleaved_input.data() + b * num_channels * frames_per_buffer; + binaural_surround_renderer_api_->AddInterleavedInput( + interleaved_input_ptr, num_channels, frames_per_buffer); + + interleaved_output.resize((b + 1) * kNumStereoChannels * + frames_per_buffer); + float* interleaved_output_ptr = + interleaved_output.data() + + b * kNumStereoChannels * frames_per_buffer; + + EXPECT_EQ(binaural_surround_renderer_api_->GetInterleavedStereoOutput( + interleaved_output_ptr, frames_per_buffer), + frames_per_buffer); + } + return interleaved_output; + } + + // Calculates the maximum absolute difference between adjacent samples in an + // interleaved audio buffer. + float GetMaximumSampleDiff(const std::vector<float>& interleaved_input, + size_t num_channels) { + if (interleaved_input.size() <= num_channels) { + return 0.0f; + } + + float max_sample_diff = 0.0f; + std::vector<float> prev_samples(num_channels); + for (size_t i = 0; i < num_channels; ++i) { + prev_samples[i] = interleaved_input[i]; + } + for (size_t i = num_channels; i < interleaved_input.size(); ++i) { + const size_t channel = i % num_channels; + max_sample_diff = + std::max(max_sample_diff, + std::abs(interleaved_input[i] - prev_samples[channel])); + prev_samples[channel] = interleaved_input[i]; + } + return max_sample_diff; + } + + // Helper to return the number of input channels for a given surround format. + size_t GetNumInputChannelsForSurroundFormat( + BinauralSurroundRenderer::SurroundFormat format) { + switch (format) { + case BinauralSurroundRenderer::SurroundFormat::kSurroundMono: + return kNumMonoChannels; + break; + case BinauralSurroundRenderer::SurroundFormat::kSurroundStereo: + return kNumStereoChannels; + break; + case BinauralSurroundRenderer::SurroundFormat::kSurroundFiveDotOne: + return kNumSurroundFiveDotOneChannels; + break; + case BinauralSurroundRenderer::SurroundFormat::kSurroundSevenDotOne: + return kNumSurroundSevenDotOneChannels; + break; + case BinauralSurroundRenderer::SurroundFormat::kFirstOrderAmbisonics: + return kNumFirstOrderAmbisonicChannels; + break; + case BinauralSurroundRenderer::SurroundFormat:: + kFirstOrderAmbisonicsWithNonDiegeticStereo: + return kNumFirstOrderAmbisonicWithNonDiegeticStereoChannels; + break; + case BinauralSurroundRenderer::SurroundFormat::kSecondOrderAmbisonics: + return kNumSecondOrderAmbisonicChannels; + break; + case BinauralSurroundRenderer::SurroundFormat:: + kSecondOrderAmbisonicsWithNonDiegeticStereo: + return kNumSecondOrderAmbisonicWithNonDiegeticStereoChannels; + break; + case BinauralSurroundRenderer::SurroundFormat::kThirdOrderAmbisonics: + return kNumThirdOrderAmbisonicChannels; + break; + case BinauralSurroundRenderer::SurroundFormat:: + kThirdOrderAmbisonicsWithNonDiegeticStereo: + return kNumThirdOrderAmbisonicWithNonDiegeticStereoChannels; + break; + default: + break; + } + LOG(FATAL) << "Unexpected format"; + return 0; + } + + // VR Audio API instance to test. + std::unique_ptr<BinauralSurroundRendererImpl> binaural_surround_renderer_api_; +}; + +// Processes an input signal with constant DC offset and scans the output for +// drop outs and noise. +TEST_P(BinauralSurroundRendererTest, DropOutGlitchTesting) { + + const std::vector<int> kTestSampleRates = {44100, 48000}; + const std::vector<int> kTestBufferSizes = {256, 413, 512}; + const size_t kNumTestBuffers = 16; + const size_t kNumNumChannels = + GetNumInputChannelsForSurroundFormat(GetParam()); + + for (int sample_rate : kTestSampleRates) { + for (int buffer_size : kTestBufferSizes) { + InitBinauralSurroundRenderer(buffer_size, sample_rate); + binaural_surround_renderer_api_->Init(GetParam()); + + // Create DC input signal with magnitude 0.5f. + const std::vector<float> interleaved_dc_signal( + buffer_size * kNumNumChannels * kNumTestBuffers, 0.5f); + + std::vector<float> interleaved_output = ProcessInterleaved( + interleaved_dc_signal, kNumNumChannels, buffer_size); + + // Remove first half of samples from output vector to remove initial + // filter ringing effects and initial gain ramps. + interleaved_output.erase( + interleaved_output.begin(), + interleaved_output.begin() + interleaved_output.size() / 2); + + const float kMaxExpectedMagnitudeDiff = 0.07f; + const float maximum_adjacent_frames_magnitude_diff = + GetMaximumSampleDiff(interleaved_output, kNumStereoChannels); + EXPECT_LT(maximum_adjacent_frames_magnitude_diff, + kMaxExpectedMagnitudeDiff); + } + } +} + +INSTANTIATE_TEST_CASE_P( + SurroundFormatInstances, BinauralSurroundRendererTest, + ::testing::Values( + BinauralSurroundRenderer::SurroundFormat::kSurroundMono, + BinauralSurroundRenderer::SurroundFormat::kSurroundStereo, + BinauralSurroundRenderer::SurroundFormat::kSurroundFiveDotOne, + BinauralSurroundRenderer::SurroundFormat::kSurroundSevenDotOne, + BinauralSurroundRenderer::SurroundFormat::kFirstOrderAmbisonics, + BinauralSurroundRenderer::SurroundFormat:: + kFirstOrderAmbisonicsWithNonDiegeticStereo, + BinauralSurroundRenderer::SurroundFormat::kSecondOrderAmbisonics, + BinauralSurroundRenderer::SurroundFormat:: + kSecondOrderAmbisonicsWithNonDiegeticStereo, + BinauralSurroundRenderer::SurroundFormat::kThirdOrderAmbisonics, + BinauralSurroundRenderer::SurroundFormat:: + kThirdOrderAmbisonicsWithNonDiegeticStereo)); + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/buffered_source_node.cc b/src/3rdparty/resonance-audio/resonance_audio/graph/buffered_source_node.cc new file mode 100644 index 000000000..ab3e49d73 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/buffered_source_node.cc @@ -0,0 +1,46 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "graph/buffered_source_node.h" + +#include "base/constants_and_types.h" +#include "base/logging.h" + +namespace vraudio { + +BufferedSourceNode::BufferedSourceNode(SourceId source_id, size_t num_channels, + size_t frames_per_buffer) + : source_id_(source_id), + input_audio_buffer_(num_channels, frames_per_buffer), + new_buffer_flag_(false) { + input_audio_buffer_.Clear(); +} + +AudioBuffer* BufferedSourceNode::GetMutableAudioBufferAndSetNewBufferFlag() { + new_buffer_flag_ = true; + return &input_audio_buffer_; +} + +const AudioBuffer* BufferedSourceNode::AudioProcess() { + if (!new_buffer_flag_) { + return nullptr; + } + new_buffer_flag_ = false; + input_audio_buffer_.set_source_id(source_id_); + return &input_audio_buffer_; +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/buffered_source_node.h b/src/3rdparty/resonance-audio/resonance_audio/graph/buffered_source_node.h new file mode 100644 index 000000000..3d5ce0b4a --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/buffered_source_node.h @@ -0,0 +1,62 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_GRAPH_BUFFERED_SOURCE_NODE_H_ +#define RESONANCE_AUDIO_GRAPH_BUFFERED_SOURCE_NODE_H_ + +#include "base/audio_buffer.h" +#include "base/constants_and_types.h" +#include "node/source_node.h" + +namespace vraudio { + +// Node that sets the |AudioBuffer| of a source. This class is *not* +// thread-safe and calls to this class must be synchronized with the graph +// processing. +class BufferedSourceNode : public SourceNode { + public: + // Constructor. + // + // @param source_id Source id. + // @param num_channel Number of channels in output buffers. + BufferedSourceNode(SourceId source_id, size_t num_channels, + size_t frames_per_buffer); + + // Returns a mutable pointer to the internal |AudioBuffer| and sets a flag to + // process the buffer in the next graph processing iteration. Calls to this + // method must be synchronized with the audio graph processing. + // + // @return Mutable audio buffer pointer. + AudioBuffer* GetMutableAudioBufferAndSetNewBufferFlag(); + + protected: + // Implements SourceNode. + const AudioBuffer* AudioProcess() override; + + // Source id. + const SourceId source_id_; + + // Input audio buffer. + AudioBuffer input_audio_buffer_; + + // Flag indicating if an new audio buffer has been set via + // |GetMutableAudioBufferAndSetNewBufferFlag|. + bool new_buffer_flag_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_GRAPH_BUFFERED_SOURCE_NODE_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/foa_rotator_node.cc b/src/3rdparty/resonance-audio/resonance_audio/graph/foa_rotator_node.cc new file mode 100644 index 000000000..71c81d017 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/foa_rotator_node.cc @@ -0,0 +1,67 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "graph/foa_rotator_node.h" + +#include "base/logging.h" + + +namespace vraudio { + +FoaRotatorNode::FoaRotatorNode(SourceId source_id, + const SystemSettings& system_settings) + : system_settings_(system_settings), + output_buffer_(kNumFirstOrderAmbisonicChannels, + system_settings.GetFramesPerBuffer()) { + output_buffer_.Clear(); + output_buffer_.set_source_id(source_id); +} + +const AudioBuffer* FoaRotatorNode::AudioProcess(const NodeInput& input) { + + + // Get the soundfield input buffer. + const AudioBuffer* input_buffer = input.GetSingleInput(); + DCHECK(input_buffer); + DCHECK_GT(input_buffer->num_frames(), 0U); + DCHECK_EQ(input_buffer->num_channels(), 4U); + DCHECK_EQ(input_buffer->source_id(), output_buffer_.source_id()); + + // Rotate soundfield buffer by the inverse head orientation. + const auto source_parameters = + system_settings_.GetSourceParameters(input_buffer->source_id()); + if (source_parameters == nullptr) { + LOG(WARNING) << "Could not find source parameters"; + return nullptr; + } + + const WorldRotation& source_rotation = + source_parameters->object_transform.rotation; + const WorldRotation inverse_head_rotation = + system_settings_.GetHeadRotation().conjugate(); + const WorldRotation rotation = inverse_head_rotation * source_rotation; + const bool rotation_applied = + foa_rotator_.Process(rotation, *input_buffer, &output_buffer_); + + if (!rotation_applied) { + return input_buffer; + } + + // Copy buffer parameters. + return &output_buffer_; +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/foa_rotator_node.h b/src/3rdparty/resonance-audio/resonance_audio/graph/foa_rotator_node.h new file mode 100644 index 000000000..802478a0a --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/foa_rotator_node.h @@ -0,0 +1,54 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_GRAPH_FOA_ROTATOR_NODE_H_ +#define RESONANCE_AUDIO_GRAPH_FOA_ROTATOR_NODE_H_ + +#include <memory> + +#include "ambisonics/foa_rotator.h" +#include "base/audio_buffer.h" +#include "base/constants_and_types.h" +#include "graph/system_settings.h" +#include "node/processing_node.h" + +namespace vraudio { + +// Node that accepts a single first order PeriphonicSoundfieldBuffer as input +// and outputs a rotated PeriphonicSoundfieldBuffer of the corresponding +// soundfield input using head rotation information from the system settings. +class FoaRotatorNode : public ProcessingNode { + public: + FoaRotatorNode(SourceId source_id, const SystemSettings& system_settings); + + protected: + // Implements ProcessingNode. Returns a null pointer if we are in stereo + // loudspeaker or stereo pan mode. + const AudioBuffer* AudioProcess(const NodeInput& input) override; + + private: + const SystemSettings& system_settings_; + + // Soundfield rotator used to rotate first order soundfields. + FoaRotator foa_rotator_; + + // Output buffer. + AudioBuffer output_buffer_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_GRAPH_FOA_ROTATOR_NODE_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/gain_mixer_node.cc b/src/3rdparty/resonance-audio/resonance_audio/graph/gain_mixer_node.cc new file mode 100644 index 000000000..26ab2982f --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/gain_mixer_node.cc @@ -0,0 +1,66 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "graph/gain_mixer_node.h" + +#include <vector> + +#include "base/constants_and_types.h" + + +namespace vraudio { + +GainMixerNode::GainMixerNode(const AttenuationType& attenuation_type, + const SystemSettings& system_settings, + size_t num_channels) + : mute_enabled_(false), + attenuation_type_(attenuation_type), + gain_mixer_(num_channels, system_settings.GetFramesPerBuffer()), + system_settings_(system_settings) {} + +void GainMixerNode::SetMute(bool mute_enabled) { mute_enabled_ = mute_enabled; } + +bool GainMixerNode::CleanUp() { + CallCleanUpOnInputNodes(); + // Prevent node from being disconnected when all sources are removed. + return false; +} + +const AudioBuffer* GainMixerNode::AudioProcess(const NodeInput& input) { + + + if (mute_enabled_) { + // Skip processing and output nullptr audio buffer. + return nullptr; + } + + // Apply the gain to each input buffer channel. + gain_mixer_.Reset(); + for (auto input_buffer : input.GetInputBuffers()) { + const auto source_parameters = + system_settings_.GetSourceParameters(input_buffer->source_id()); + if (source_parameters != nullptr) { + const float target_gain = + source_parameters->attenuations[attenuation_type_]; + const size_t num_channels = input_buffer->num_channels(); + gain_mixer_.AddInput(*input_buffer, + std::vector<float>(num_channels, target_gain)); + } + } + return gain_mixer_.GetOutput(); +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/gain_mixer_node.h b/src/3rdparty/resonance-audio/resonance_audio/graph/gain_mixer_node.h new file mode 100644 index 000000000..df569ccdd --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/gain_mixer_node.h @@ -0,0 +1,68 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_GRAPH_GAIN_MIXER_NODE_H_ +#define RESONANCE_AUDIO_GRAPH_GAIN_MIXER_NODE_H_ + +#include "base/audio_buffer.h" +#include "base/source_parameters.h" +#include "dsp/gain_mixer.h" +#include "graph/system_settings.h" +#include "node/processing_node.h" + +namespace vraudio { + +// Node that accepts multiple input buffers, calculates and applies a gain +// value to each buffer based upon the given |AttenuationType| and then mixes +// the results together. +class GainMixerNode : public ProcessingNode { + public: + // Constructs |GainMixerNode| with given gain calculation method. + // + // @param attenuation_type Gain attenuation type to be used. + // @param system_settings Global system settings. + // @param num_channels Number of channels. + GainMixerNode(const AttenuationType& attenuation_type, + const SystemSettings& system_settings, size_t num_channels); + + // Mute the mixer node by skipping the audio processing and outputting nullptr + // buffers. + void SetMute(bool mute_enabled); + + // Node implementation. + bool CleanUp() final; + + protected: + // Implements ProcessingNode. + const AudioBuffer* AudioProcess(const NodeInput& input) override; + + private: + // Flag indicating the mute status. + bool mute_enabled_; + + // Gain attenuation type. + const AttenuationType attenuation_type_; + + // Gain mixer. + GainMixer gain_mixer_; + + // Global system settings. + const SystemSettings& system_settings_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_GRAPH_GAIN_MIXER_NODE_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/gain_mixer_node_test.cc b/src/3rdparty/resonance-audio/resonance_audio/graph/gain_mixer_node_test.cc new file mode 100644 index 000000000..6e2a98f81 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/gain_mixer_node_test.cc @@ -0,0 +1,246 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "graph/gain_mixer_node.h" + +#include <algorithm> +#include <limits> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/audio_buffer.h" +#include "base/constants_and_types.h" +#include "base/logging.h" +#include "graph/buffered_source_node.h" +#include "node/sink_node.h" +#include "node/source_node.h" + +namespace vraudio { + +namespace { + +// Values to initialize a |SystemSettings| instance. +const size_t kNumFrames = 4; +const size_t kSampleRate = 44100; + +// Helper class to detect deletion. +class DeletionDetector { + public: + explicit DeletionDetector(bool* node_deletion_flag) + : node_deletion_flag_(node_deletion_flag) {} + ~DeletionDetector() { + if (node_deletion_flag_ != nullptr) { + *node_deletion_flag_ = true; + } + } + + private: + bool* node_deletion_flag_; +}; + +// Wraps |SourceNode| to detect its deletion. +class MySourceNode : public SourceNode, DeletionDetector { + public: + explicit MySourceNode(bool* node_deletion_flag) + : DeletionDetector(node_deletion_flag) {} + + protected: + const AudioBuffer* AudioProcess() final { return nullptr; } +}; + +// Wraps |GainMixerNode| to detect its deletion. +class MyGainMixerNode : public GainMixerNode, DeletionDetector { + public: + MyGainMixerNode(bool* node_deletion_flag, + const AttenuationType& attenuation_type, + const SystemSettings& system_settings) + : GainMixerNode(attenuation_type, system_settings, kNumMonoChannels), + DeletionDetector(node_deletion_flag) {} +}; + +// Wraps |SinkNode| to detect its deletion. +class MySinkNode : public SinkNode, DeletionDetector { + public: + explicit MySinkNode(bool* node_deletion_flag) + : DeletionDetector(node_deletion_flag) {} +}; + +// Tests that the |GainMixerNode| keeps connected at the moment all of its +// sources are removed. +TEST(AudioNodesTest, CleanUpOnEmptyInputTest) { + bool source_node_deleted = false; + bool gain_mixer_node_deleted = false; + bool sink_node_deleted = false; + + SystemSettings system_settings(kNumMonoChannels, kNumFrames, kSampleRate); + auto sink_node = std::make_shared<MySinkNode>(&sink_node_deleted); + + { + // Create a source and mixer node and connect it to sink node. + auto source_node = std::make_shared<MySourceNode>(&source_node_deleted); + auto gain_mixer_node = std::make_shared<MyGainMixerNode>( + &gain_mixer_node_deleted, AttenuationType::kInput, system_settings); + + // Connect nodes. + sink_node->Connect(gain_mixer_node); + gain_mixer_node->Connect(source_node); + + // End of stream is marked in source node. Do not expect any data anymore. + source_node->MarkEndOfStream(); + } + + EXPECT_FALSE(source_node_deleted); + EXPECT_FALSE(gain_mixer_node_deleted); + EXPECT_FALSE(sink_node_deleted); + + sink_node->CleanUp(); + + EXPECT_TRUE(source_node_deleted); + EXPECT_FALSE(gain_mixer_node_deleted); + EXPECT_FALSE(sink_node_deleted); +} + +// Provides unit tests for |GainMixerNode|. +class GainMixerNodeTest : public ::testing::Test { + protected: + GainMixerNodeTest() + : system_settings_(kNumMonoChannels, kNumFrames, kSampleRate) {} + + // Helper method to create a new input buffer. + // + // @return Mono audio buffer filled with test data. + std::unique_ptr<AudioBuffer> CreateInputBuffer( + const std::vector<float>& input_data) { + auto buffer = std::unique_ptr<AudioBuffer>( + new AudioBuffer(kNumMonoChannels, input_data.size())); + (*buffer)[0] = input_data; + return buffer; + } + + // Helper method that generates a node graph and returns the processed + // output. + // + // @param num_inputs Number of input buffers to be processed. + void CreateGraph(size_t num_inputs) { + // Tests will use |AttenuationType::kInput| which directly returns the + // local + // gain value in order to avoid extra complexity. + gain_mixer_node_ = std::make_shared<GainMixerNode>( + AttenuationType::kInput, system_settings_, kNumMonoChannels); + + output_node_ = std::make_shared<SinkNode>(); + output_node_->Connect(gain_mixer_node_); + + buffered_source_nodes_.resize(num_inputs); + auto parameters_manager = system_settings_.GetSourceParametersManager(); + for (size_t i = 0; i < num_inputs; ++i) { + const auto source_id = static_cast<SourceId>(i); + buffered_source_nodes_[i] = std::make_shared<BufferedSourceNode>( + source_id, kNumMonoChannels, kNumFrames); + gain_mixer_node_->Connect(buffered_source_nodes_[i]); + parameters_manager->Register(source_id); + } + } + + // Processes input buuffers with gains returning the mixed output. + // + // @param input_gains Gains to be processed. + // @param input_buffers Buffers to be processed. + // @return Processed output buffer. + const AudioBuffer* Process( + float input_gain, const std::vector<std::vector<float>>& input_buffers) { + DCHECK_EQ(buffered_source_nodes_.size(), input_buffers.size()); + for (size_t i = 0; i < input_buffers.size(); ++i) { + const auto source_id = static_cast<SourceId>(i); + auto input = CreateInputBuffer(input_buffers[i]); + // Set the input gain. + auto parameters = + system_settings_.GetSourceParametersManager()->GetMutableParameters( + source_id); + parameters->attenuations[AttenuationType::kInput] = input_gain; + // Process the buffer. + AudioBuffer* const input_node_buffer = + buffered_source_nodes_[i]->GetMutableAudioBufferAndSetNewBufferFlag(); + *input_node_buffer = *input; + } + const std::vector<const AudioBuffer*>& outputs = output_node_->ReadInputs(); + if (!outputs.empty()) { + DCHECK_EQ(outputs.size(), 1U); + return outputs.front(); + } + return nullptr; + } + + // System settings. + SystemSettings system_settings_; + + // Component nodes for the simple audio graph. + std::shared_ptr<GainMixerNode> gain_mixer_node_; + std::vector<std::shared_ptr<BufferedSourceNode>> buffered_source_nodes_; + std::shared_ptr<SinkNode> output_node_; +}; + +// Tests that the |GainMixerNode| returns the expected output buffers with +// different gain values. +TEST_F(GainMixerNodeTest, GainTest) { + const float kGain = 0.5f; + const std::vector<std::vector<float>> inputs({{1.0f, 1.0f, 1.0f, 1.0f}, + {2.0f, 2.0f, 2.0f, 2.0f}, + {3.0f, 3.0f, 3.0f, 3.0f}, + {4.0f, 4.0f, 4.0f, 4.0f}}); + // Zero buffer should be returned when the gain value's zero from the start. + CreateGraph(inputs.size()); + auto output = Process(0.0f, inputs); + for (size_t i = 0; i < inputs[0].size(); ++i) { + EXPECT_NEAR((*output)[0][i], 0.0f, kEpsilonFloat); + } + + // A valid output buffer should be returned when the gain value is non-zero. + output = Process(kGain, inputs); + EXPECT_NEAR((*output)[0][0], 0.0f, kEpsilonFloat); + for (size_t i = 1; i < inputs[0].size(); ++i) { + EXPECT_FALSE(std::abs((*output)[0][i]) <= + std::numeric_limits<float>::epsilon()); + } + + // Correct values should be returned after gain processor interpolation. + for (size_t i = 0; i < kUnitRampLength / 2; ++i) { + output = Process(kGain, inputs); + } + const float output_value = + kGain * (inputs[0][0] + inputs[1][0] + inputs[2][0] + inputs[3][0]); + for (size_t i = 0; i < inputs[0].size(); ++i) { + EXPECT_NEAR((*output)[0][i], output_value, kEpsilonFloat); + } + + // A valid output buffer should be returned even when the gain value is zero + // while gain processor interpolation. + output = Process(0.0f, inputs); + for (size_t i = 0; i < inputs[0].size(); ++i) { + EXPECT_NE((*output)[0][i], 0.0f); + } + + // Zero buffer should be returned after the interpolation is completed. + for (size_t i = 0; i < kUnitRampLength / 2; ++i) { + output = Process(0.0f, inputs); + } + for (size_t i = 0; i < inputs[0].size(); ++i) { + EXPECT_NEAR((*output)[0][i], 0.0f, kEpsilonFloat); + } +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/gain_node.cc b/src/3rdparty/resonance-audio/resonance_audio/graph/gain_node.cc new file mode 100644 index 000000000..ca1c8edb2 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/gain_node.cc @@ -0,0 +1,82 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "graph/gain_node.h" + +#include <cmath> + + +#include "dsp/gain.h" + +namespace vraudio { + +GainNode::GainNode(SourceId source_id, size_t num_channels, + const AttenuationType& attenuation_type, + const SystemSettings& system_settings) + : num_channels_(num_channels), + attenuation_type_(attenuation_type), + gain_processors_(num_channels_), + system_settings_(system_settings), + output_buffer_(num_channels, system_settings.GetFramesPerBuffer()) { + DCHECK_GT(num_channels, 0U); + output_buffer_.set_source_id(source_id); +} + +const AudioBuffer* GainNode::AudioProcess(const NodeInput& input) { + + + const AudioBuffer* input_buffer = input.GetSingleInput(); + DCHECK(input_buffer); + DCHECK_EQ(input_buffer->num_channels(), num_channels_); + DCHECK_EQ(input_buffer->source_id(), output_buffer_.source_id()); + + const auto source_parameters = + system_settings_.GetSourceParameters(input_buffer->source_id()); + if (source_parameters == nullptr) { + LOG(WARNING) << "Could not find source parameters"; + return nullptr; + } + + const float current_gain = gain_processors_[0].GetGain(); + const float target_gain = source_parameters->attenuations[attenuation_type_]; + if (IsGainNearZero(target_gain) && IsGainNearZero(current_gain)) { + // Make sure the gain processors are initialized. + for (size_t i = 0; i < num_channels_; ++i) { + gain_processors_[i].Reset(0.0f); + } + // Skip processing in case of zero gain. + return nullptr; + } + if (IsGainNearUnity(target_gain) && IsGainNearUnity(current_gain)) { + // Make sure the gain processors are initialized. + for (size_t i = 0; i < num_channels_; ++i) { + gain_processors_[i].Reset(1.0f); + } + // Skip processing in case of unity gain. + return input_buffer; + } + + // Apply the gain to each input buffer channel. + for (size_t i = 0; i < num_channels_; ++i) { + gain_processors_[i].ApplyGain(target_gain, (*input_buffer)[i], + &output_buffer_[i], + false /* accumulate_output */); + } + + return &output_buffer_; +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/gain_node.h b/src/3rdparty/resonance-audio/resonance_audio/graph/gain_node.h new file mode 100644 index 000000000..35f8c62b8 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/gain_node.h @@ -0,0 +1,69 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_GRAPH_GAIN_NODE_H_ +#define RESONANCE_AUDIO_GRAPH_GAIN_NODE_H_ + +#include <memory> +#include <vector> + +#include "base/audio_buffer.h" +#include "base/constants_and_types.h" +#include "base/source_parameters.h" +#include "dsp/gain_processor.h" +#include "graph/system_settings.h" +#include "node/processing_node.h" + +namespace vraudio { + +// Node that calculates and applies a gain value to each channel of an input +// buffer based upon the given |GainCalculator|. +class GainNode : public ProcessingNode { + public: + // Constructs |GainNode| with given gain attenuation method. + // + // @param source_id Output buffer source id. + // @param num_channels Number of channels in the input buffer. + // @param attenuation_type Gain attenuation type to be used. + // @param system_settings Global system settings. + GainNode(SourceId source_id, size_t num_channels, + const AttenuationType& attenuation_type, + const SystemSettings& system_settings); + + protected: + // Implements ProcessingNode. + const AudioBuffer* AudioProcess(const NodeInput& input) override; + + private: + // Number of channels of the audio buffer. + const size_t num_channels_; + + // Gain attenuation type. + const AttenuationType attenuation_type_; + + // Gain processors per each channel. + std::vector<GainProcessor> gain_processors_; + + // Global system settings. + const SystemSettings& system_settings_; + + // Output buffer. + AudioBuffer output_buffer_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_GRAPH_GAIN_NODE_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/gain_node_test.cc b/src/3rdparty/resonance-audio/resonance_audio/graph/gain_node_test.cc new file mode 100644 index 000000000..97391f876 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/gain_node_test.cc @@ -0,0 +1,138 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "graph/gain_node.h" + +#include <iterator> +#include <memory> +#include <vector> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/audio_buffer.h" +#include "base/constants_and_types.h" +#include "graph/buffered_source_node.h" +#include "node/sink_node.h" + +namespace vraudio { + +namespace { + +// Values to initialize a |SystemSettings| instance. +const size_t kNumFrames = 4; +const size_t kSampleRate = 44100; + +// Source id. +const SourceId kSourceId = 1; + +const float kInputData[kNumFrames] = {1.0f, 2.0f, 3.0f, 4.0f}; + +// Provides unit tests for |GainNode|. +class GainNodeTest : public ::testing::Test { + protected: + GainNodeTest() + : input_data_(std::begin(kInputData), std::end(kInputData)), + system_settings_(kNumMonoChannels, kNumFrames, kSampleRate) {} + + void SetUp() override { + // Tests will use |AttenuationType::kInput| which directly returns the input + // gain value in order to avoid extra complexity. + gain_node_ = std::make_shared<GainNode>( + kSourceId, kNumMonoChannels, AttenuationType::kInput, system_settings_); + input_buffer_node_ = std::make_shared<BufferedSourceNode>( + kSourceId, kNumMonoChannels, kNumFrames); + gain_node_->Connect(input_buffer_node_); + output_node_ = std::make_shared<SinkNode>(); + output_node_->Connect(gain_node_); + // Register the source parameters. + system_settings_.GetSourceParametersManager()->Register(kSourceId); + } + + // Helper method to create a new input buffer. + // + // @return Mono audio buffer filled with test data. + std::unique_ptr<AudioBuffer> CreateInputBuffer() { + std::unique_ptr<AudioBuffer> buffer( + new AudioBuffer(kNumMonoChannels, kNumFrames)); + (*buffer)[0] = input_data_; + return buffer; + } + + // Helper method that generates a node graph and returns the processed output. + // + // @param input_gain Input gain value to be processed. + // @return Processed output buffer. + + const AudioBuffer* ProcessGainNode(float input_gain) { + // Create a new audio buffer. + auto input = CreateInputBuffer(); + // Update the input gain parameter. + auto parameters = + system_settings_.GetSourceParametersManager()->GetMutableParameters( + kSourceId); + parameters->attenuations[AttenuationType::kInput] = input_gain; + // Process the buffer. + AudioBuffer* const input_node_buffer = + input_buffer_node_->GetMutableAudioBufferAndSetNewBufferFlag(); + *input_node_buffer = *input; + + const std::vector<const AudioBuffer*>& outputs = output_node_->ReadInputs(); + if (!outputs.empty()) { + DCHECK_EQ(outputs.size(), 1U); + return outputs.front(); + } + return nullptr; + } + + private: + std::vector<float> input_data_; + + std::shared_ptr<GainNode> gain_node_; + std::shared_ptr<BufferedSourceNode> input_buffer_node_; + std::shared_ptr<SinkNode> output_node_; + + SystemSettings system_settings_; +}; + +// Tests that the gain node returns the expected output buffers with different +// gain values. +TEST_F(GainNodeTest, GainTest) { + // nullptr should be returned when the gain value is zero from the start. + auto output = ProcessGainNode(0.0f); + + EXPECT_TRUE(output == nullptr); + + // A valid output buffer should be returned when the gain value is non-zero. + output = ProcessGainNode(0.5f); + + EXPECT_FALSE(output == nullptr); + + // A valid output buffer should be returned even when the gain value is zero + // while gain processor interpolation. + output = ProcessGainNode(0.0f); + + EXPECT_FALSE(output == nullptr); + + // nullptr should be returned after the interpolation is completed. + for (size_t i = 0; i < kUnitRampLength; ++i) { + output = ProcessGainNode(0.0f); + } + + EXPECT_TRUE(output == nullptr); +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/graph_manager.cc b/src/3rdparty/resonance-audio/resonance_audio/graph/graph_manager.cc new file mode 100644 index 000000000..cb5b85590 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/graph_manager.cc @@ -0,0 +1,290 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "graph/graph_manager.h" + +#include <functional> + +#include "ambisonics/utils.h" +#include "base/constants_and_types.h" +#include "base/logging.h" +#include "graph/foa_rotator_node.h" +#include "graph/gain_node.h" +#include "graph/hoa_rotator_node.h" +#include "graph/mono_from_soundfield_node.h" +#include "graph/near_field_effect_node.h" +#include "graph/occlusion_node.h" + +namespace vraudio { + +GraphManager::GraphManager(const SystemSettings& system_settings) + : + room_effects_enabled_(true), + config_(GlobalConfig()), + system_settings_(system_settings), + fft_manager_(system_settings.GetFramesPerBuffer()), + output_node_(std::make_shared<SinkNode>()) { + CHECK_LE(system_settings.GetFramesPerBuffer(), kMaxSupportedNumFrames); + + stereo_mixer_node_ = + std::make_shared<MixerNode>(system_settings_, kNumStereoChannels); + output_node_->Connect(stereo_mixer_node_); + + /// Initialize the Ambisonic Lookup Table. + lookup_table_.reset(new AmbisonicLookupTable(config_.max_ambisonic_order)); + // Initialize the Ambisonic Renderer subgraphs. + for (const auto& sh_hrir_filename_itr : config_.sh_hrir_filenames) { + const int ambisonic_order = sh_hrir_filename_itr.first; + const auto& sh_hrir_filename = sh_hrir_filename_itr.second; + InitializeAmbisonicRendererGraph(ambisonic_order, sh_hrir_filename); + // Initialize the Ambisonic Mixing Encoders for HRTF sound object rendering. + ambisonic_mixing_encoder_nodes_[ambisonic_order] = + std::make_shared<AmbisonicMixingEncoderNode>( + system_settings_, *lookup_table_, ambisonic_order); + ambisonic_mixer_nodes_[ambisonic_order]->Connect( + ambisonic_mixing_encoder_nodes_[ambisonic_order]); + } + + // Stereo mixing panner node used in non-HRTF sound object rendering. + stereo_mixing_panner_node_ = + std::make_shared<StereoMixingPannerNode>(system_settings_); + stereo_mixer_node_->Connect(stereo_mixing_panner_node_); + + // Initialize room effects graphs. + InitializeReflectionsGraph(); + InitializeReverbGraph(); + // Initialize ambisonic output mixer. + ambisonic_output_mixer_.reset( + new Mixer(GetNumPeriphonicComponents(config_.max_ambisonic_order), + system_settings.GetFramesPerBuffer())); +} + +void GraphManager::CreateAmbisonicSource(SourceId ambisonic_source_id, + size_t num_channels) { + DCHECK(source_nodes_.find(ambisonic_source_id) == source_nodes_.end()); + // Create a new |ambisonic_source_node| and register to |source_nodes_|. + auto ambisonic_source_node = std::make_shared<BufferedSourceNode>( + ambisonic_source_id, num_channels, system_settings_.GetFramesPerBuffer()); + source_nodes_[ambisonic_source_id] = ambisonic_source_node; + + // Connect |ambisonic_source_node| to the ambisonic decoding pipeline. + const int ambisonic_order = GetPeriphonicAmbisonicOrder(num_channels); + auto direct_attenuation_node = + std::make_shared<GainNode>(ambisonic_source_id, num_channels, + AttenuationType::kDirect, system_settings_); + direct_attenuation_node->Connect(ambisonic_source_node); + if (ambisonic_order == 1) { + // First order case. + auto foa_rotator_node = + std::make_shared<FoaRotatorNode>(ambisonic_source_id, system_settings_); + foa_rotator_node->Connect(direct_attenuation_node); + ambisonic_mixer_nodes_[ambisonic_order]->Connect(foa_rotator_node); + } else { + // Higher orders case. + auto hoa_rotator_node = std::make_shared<HoaRotatorNode>( + ambisonic_source_id, system_settings_, ambisonic_order); + hoa_rotator_node->Connect(direct_attenuation_node); + ambisonic_mixer_nodes_[ambisonic_order]->Connect(hoa_rotator_node); + } + // Connect to room effects rendering pipeline. + auto mono_from_soundfield_node = std::make_shared<MonoFromSoundfieldNode>( + ambisonic_source_id, system_settings_); + mono_from_soundfield_node->Connect(ambisonic_source_node); + reflections_gain_mixer_node_->Connect(mono_from_soundfield_node); + reverb_gain_mixer_node_->Connect(mono_from_soundfield_node); +} + +void GraphManager::CreateSoundObjectSource(SourceId sound_object_source_id, + int ambisonic_order, + bool enable_hrtf, + bool enable_direct_rendering) { + DCHECK(source_nodes_.find(sound_object_source_id) == source_nodes_.end()); + // Create a new |sound_object_source_node| and register to |source_nodes_|. + auto sound_object_source_node = std::make_shared<BufferedSourceNode>( + sound_object_source_id, kNumMonoChannels, + system_settings_.GetFramesPerBuffer()); + source_nodes_[sound_object_source_id] = sound_object_source_node; + + // Create direct rendering pipeline. + if (enable_direct_rendering) { + auto direct_attenuation_node = + std::make_shared<GainNode>(sound_object_source_id, kNumMonoChannels, + AttenuationType::kDirect, system_settings_); + direct_attenuation_node->Connect(sound_object_source_node); + auto occlusion_node = std::make_shared<OcclusionNode>( + sound_object_source_id, system_settings_); + occlusion_node->Connect(direct_attenuation_node); + auto near_field_effect_node = std::make_shared<NearFieldEffectNode>( + sound_object_source_id, system_settings_); + + if (enable_hrtf) { + ambisonic_mixing_encoder_nodes_[ambisonic_order]->Connect(occlusion_node); + } else { + stereo_mixing_panner_node_->Connect(occlusion_node); + } + + near_field_effect_node->Connect(occlusion_node); + stereo_mixer_node_->Connect(near_field_effect_node); + } + + // Connect to room effects rendering pipeline. + reflections_gain_mixer_node_->Connect(sound_object_source_node); + reverb_gain_mixer_node_->Connect(sound_object_source_node); +} + +void GraphManager::EnableRoomEffects(bool enable) { + room_effects_enabled_ = enable; + reflections_gain_mixer_node_->SetMute(!room_effects_enabled_); + reverb_gain_mixer_node_->SetMute(!room_effects_enabled_); +} + +const AudioBuffer* GraphManager::GetAmbisonicBuffer() const { + ambisonic_output_mixer_->Reset(); + for (const auto& ambisonic_mixer_node_itr : ambisonic_mixer_nodes_) { + const auto* ambisonic_buffer = + ambisonic_mixer_node_itr.second->GetOutputBuffer(); + if (ambisonic_buffer != nullptr) { + ambisonic_output_mixer_->AddInput(*ambisonic_buffer); + } + } + return ambisonic_output_mixer_->GetOutput(); +} + +const AudioBuffer* GraphManager::GetStereoBuffer() const { + return stereo_mixer_node_->GetOutputBuffer(); +} + +const AudioBuffer *GraphManager::GetReverbBuffer() const +{ + return reverb_node_->GetOutputBuffer(); +} + +size_t GraphManager::GetNumMaxAmbisonicChannels() const { + return GetNumPeriphonicComponents(config_.max_ambisonic_order); +} + +bool GraphManager::GetRoomEffectsEnabled() const { + return room_effects_enabled_; +} + +void GraphManager::UpdateRoomReflections() { reflections_node_->Update(); } + +void GraphManager::UpdateRoomReverb() { reverb_node_->Update(); } + +void GraphManager::InitializeReverbGraph() { + reverb_gain_mixer_node_ = std::make_shared<GainMixerNode>( + AttenuationType::kReverb, system_settings_, kNumMonoChannels); + reverb_node_ = std::make_shared<ReverbNode>(system_settings_, &fft_manager_); + reverb_node_->Connect(reverb_gain_mixer_node_); + stereo_mixer_node_->Connect(reverb_node_); +} + +void GraphManager::InitializeReflectionsGraph() { + reflections_gain_mixer_node_ = std::make_shared<GainMixerNode>( + AttenuationType::kReflections, system_settings_, kNumMonoChannels); + reflections_node_ = std::make_shared<ReflectionsNode>(system_settings_); + reflections_node_->Connect(reflections_gain_mixer_node_); + // Reflections are limited to First Order Ambisonics to reduce complexity. + const int kAmbisonicOrder1 = 1; + ambisonic_mixer_nodes_[kAmbisonicOrder1]->Connect(reflections_node_); +} + +void GraphManager::CreateAmbisonicPannerSource(SourceId sound_object_source_id, + bool enable_hrtf) { + DCHECK(source_nodes_.find(sound_object_source_id) == source_nodes_.end()); + // Create a new |sound_object_source_node| and register to |source_nodes_|. + auto sound_object_source_node = std::make_shared<BufferedSourceNode>( + sound_object_source_id, kNumMonoChannels, + system_settings_.GetFramesPerBuffer()); + source_nodes_[sound_object_source_id] = sound_object_source_node; + + if (enable_hrtf) { + ambisonic_mixing_encoder_nodes_[config_.max_ambisonic_order]->Connect( + sound_object_source_node); + } else { + stereo_mixing_panner_node_->Connect(sound_object_source_node); + } +} + +void GraphManager::CreateStereoSource(SourceId stereo_source_id) { + DCHECK(source_nodes_.find(stereo_source_id) == source_nodes_.end()); + // Create a new |stereo_source_node| and register to |source_nodes_|. + auto stereo_source_node = std::make_shared<BufferedSourceNode>( + stereo_source_id, kNumStereoChannels, + system_settings_.GetFramesPerBuffer()); + source_nodes_[stereo_source_id] = stereo_source_node; + + // Connect |stereo_source_node| to the stereo rendering pipeline. + auto gain_node = + std::make_shared<GainNode>(stereo_source_id, kNumStereoChannels, + AttenuationType::kInput, system_settings_); + gain_node->Connect(stereo_source_node); + stereo_mixer_node_->Connect(gain_node); +} + +void GraphManager::DestroySource(SourceId source_id) { + auto source_node = LookupSourceNode(source_id); + if (source_node != nullptr) { + // Disconnect the source from the graph. + source_node->MarkEndOfStream(); + output_node_->CleanUp(); + // Unregister the source from |source_nodes_|. + source_nodes_.erase(source_id); + } +} + +std::shared_ptr<SinkNode> GraphManager::GetSinkNode() { return output_node_; } + +void GraphManager::Process() { + + output_node_->ReadInputs(); +} + +AudioBuffer* GraphManager::GetMutableAudioBuffer(SourceId source_id) { + auto source_node = LookupSourceNode(source_id); + if (source_node == nullptr) { + return nullptr; + } + return source_node->GetMutableAudioBufferAndSetNewBufferFlag(); +} + +void GraphManager::InitializeAmbisonicRendererGraph( + int ambisonic_order, const std::string& sh_hrir_filename) { + CHECK_LE(ambisonic_order, config_.max_ambisonic_order); + const size_t num_channels = GetNumPeriphonicComponents(ambisonic_order); + // Create binaural decoder pipeline. + ambisonic_mixer_nodes_[ambisonic_order] = + std::make_shared<MixerNode>(system_settings_, num_channels); + auto ambisonic_binaural_decoder_node = + std::make_shared<AmbisonicBinauralDecoderNode>( + system_settings_, ambisonic_order, sh_hrir_filename, &fft_manager_, + &resampler_); + ambisonic_binaural_decoder_node->Connect( + ambisonic_mixer_nodes_[ambisonic_order]); + stereo_mixer_node_->Connect(ambisonic_binaural_decoder_node); +} + +std::shared_ptr<BufferedSourceNode> GraphManager::LookupSourceNode( + SourceId source_id) { + auto source_node_itr = source_nodes_.find(source_id); + if (source_node_itr == source_nodes_.end()) { + LOG(WARNING) << "Source node " << source_id << " not found"; + return nullptr; + } + return source_node_itr->second; +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/graph_manager.h b/src/3rdparty/resonance-audio/resonance_audio/graph/graph_manager.h new file mode 100644 index 000000000..818b6fa01 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/graph_manager.h @@ -0,0 +1,404 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_GRAPH_GRAPH_MANAGER_H_ +#define RESONANCE_AUDIO_GRAPH_GRAPH_MANAGER_H_ + +#include <memory> +#include <string> +#include <unordered_map> + +#include "ambisonics/ambisonic_lookup_table.h" +#include "base/audio_buffer.h" +#include "base/constants_and_types.h" +#include "config/global_config.h" +#include "dsp/fft_manager.h" +#include "dsp/resampler.h" +#include "graph/ambisonic_binaural_decoder_node.h" +#include "graph/ambisonic_mixing_encoder_node.h" +#include "graph/buffered_source_node.h" +#include "graph/gain_mixer_node.h" +#include "graph/mixer_node.h" +#include "graph/reflections_node.h" +#include "graph/reverb_node.h" +#include "graph/stereo_mixing_panner_node.h" +#include "graph/system_settings.h" +#include "node/sink_node.h" + +namespace vraudio { + +// The GraphManager class manages the construction and lifetime of the audio +// processing graph. It owns the output node that connects the audio processing +// graph to the audio hardware. +class GraphManager { + public: + // Initializes GraphManager class. + // + // @param system_settings Global system configuration. + explicit GraphManager(const SystemSettings& system_settings); + + // Returns the sink node the audio graph is connected to. + // + // @return Shared pointer of the sink node. + std::shared_ptr<SinkNode> GetSinkNode(); + + // Triggers processing of the audio graph for all the connected nodes. + void Process(); + + // Returns a mutable pointer to the |AudioBuffer| of an audio source with + // given |source_id|. Calls to this method must be synchronized with the audio + // graph processing. + // + // @param source_id Source id. + // @return Mutable audio buffer pointer. Nullptr if source_id not found. + AudioBuffer* GetMutableAudioBuffer(SourceId source_id); + + // Creates an ambisonic panner source with given |sound_object_source_id|. + // + // Processing graph: + // + // +-------------------+ + // | | + // +------------+ SoundObjectSource +----------+ + // | | | | + // | +---------+---------+ | + // | | + // +-----------v-----------+ +---------v----------+ + // | | | | + // | AmbisonicMixingPanner | | StereoMixingPanner | + // | | | | + // +-----------+-----------+ +---------+----------+ + // | | + // +-----------v-----------+ +---------v----------+ + // | | | | + // | AmbisonicMixer | | StereoMixer | + // | | | | + // +-----------+-----------+ +--------------------+ + // + // + // @param sound_object_source_id Id of sound object source. + // @param enable_hrtf Flag to enable HRTF-based spatialization. + void CreateAmbisonicPannerSource(SourceId sound_object_source_id, + bool enable_hrtf); + + // Creates a new stereo non-spatialized source with given |stereo_source_id|. + // + // Processing graph: + // + // +--------------+ + // | | + // | StereoSource | + // | | + // +-------+------+ + // | + // +-------v------+ + // | | + // | Gain | + // | | + // +-------+------+ + // | + // +-------V------+ + // | | + // | StereoMixer | + // | | + // +--------------+ + // + // @param stereo_source_id Id of new stereo source. + void CreateStereoSource(SourceId stereo_source_id); + + // Destroys source with given |source_id|. Note that this call only sets a + // flag to indicate that this source can be removed. The actual disconnect + // happens from the audio processing thread the next time the processing graph + // is triggered. + // + // @param source_id Id of source to be destroyed. + void DestroySource(SourceId source_id); + + // Creates a new ambisonic source subgraph with given |ambisonic_source_id|. + // Note: Ambisonic source subgraph is only created if the rendering mode is + // HRTF. + // + // Processing graph (all the graphs created using http://asciiflow.com/): + // + // +-----------------+ + // | | + // +-------+ AmbisonicSource +-------+ + // | | | | + // | +-----------------+ | + // | | + // +----v---+ +----------v---------+ + // | | | | + // | Gain | +--+ MonoFromSoundfield +--+ + // | | | | | | + // +----+---+ | +--------------------+ | + // | | | + // | | | + // +--------v-------+ +--------v---------+ +------v------+ + // | | | | | | + // | Foa/HoaRotator | | ReflectionsMixer | | ReverbMixer | + // | | | | | | + // +--------+-------+ +--------+---------+ +------+------+ + // | + // +--------v-------+ + // | | + // | AmbisonicMixer | + // | | + // +--------+-------+ + // + // @param ambisonic_source_id Id of new ambisonic source. + // @param num_channels Number of input channels of ambisonic source node. + void CreateAmbisonicSource(SourceId ambisonic_source_id, size_t num_channels); + + // Creates a new sound object source with given |sound_object_source_id|. + // + // Processing graph: + // + // +-------------------+ + // | | + // +-------------+ SoundObjectSource +----------+ + // | | | | + // | +---------+---------+ | + // | | | + // +----------v-----------+ +---------v---------+ +--------v--------+ + // | | | | | | + // | ReflectionsGainMixer | | DirectAttenuation | | ReverbGainMixer | + // | | | | | | + // +----------+-----------+ +---------+---------+ +--------+--------+ + // | + // +---------v---------+ + // HRTF | | Stereo Panning + // +------------+ Occlusion +----------+ + // | | | | + // | +---------+---------+ | + // | | | + // +-----------v-----------+ +--------v--------+ +---------v----------+ + // | | | | | | + // | AmbisonicMixingPanner | | NearFieldEffect | | StereoMixingPanner | + // | | | | | | + // +-----------+-----------+ +--------+--------+ +---------+----------+ + // | | | + // +-----------v-----------+ +--------v--------+ | + // | | | | | + // | AmbisonicMixer | | StereoMixer <-----------+ + // | | | | + // +-----------+-----------+ +-----------------+ + // + // + // @param sound_object_source_id Id of sound object source. + // @param ambisonic_order Ambisonic order to encode the sound object source. + // @param enable_hrtf Flag to enable HRTF-based rendering. + // @param enable_direct_rendering Flag to enable direct source rendering. + void CreateSoundObjectSource(SourceId sound_object_source_id, + int ambisonic_order, bool enable_hrtf, + bool enable_direct_rendering); + + // Mutes on/off the room effects mixers. + // + // @param Whether to enable room effects. + void EnableRoomEffects(bool enable); + + // Returns the last processed output audio buffer of the ambisonic mix with + // the highest possible ambisonic channel configuration. Note that, this + // method will *not* trigger the processing of the audio graph. + // |GraphManager::Process| must be called prior to this method call to ensure + // that the output buffer is up-to-date. + // + // @return Output audio buffer of the ambisonic mix, or nullptr if no output. + const AudioBuffer* GetAmbisonicBuffer() const; + + // Returns the last processed output audio buffer of the stereo (binaural) + // mix. Note that, this method will *not* trigger the processing of the audio + // graph. |GraphManager::Process| must be called prior to this method call to + // ensure that the output buffer is up-to-date. + // + // @return Output audio buffer of the stereo mix, or nullptr if no output. + const AudioBuffer* GetStereoBuffer() const; + + // Returns the last processed buffer containing the reverb data for the room. + // The buffer contains stereo data. + // Note that, this method will *not* trigger the processing of the audio + // graph. |GraphManager::Process| must be called prior to this method call to + // ensure that the buffer is up-to-date. + // + // @return Room reverb audio buffer, or nullptr if no output. + const AudioBuffer* GetReverbBuffer() const; + + // Returns the maximum allowed number of ambisonic channels. + // + // @return Number of channels based on Ambisonic order in the global config. + size_t GetNumMaxAmbisonicChannels() const; + + // Returns whether the room effects graph is enabled. + // + // @return True if room effects are enabled. + bool GetRoomEffectsEnabled() const; + + // Updates the room reflections with the current properties for room effects + // processing. + void UpdateRoomReflections(); + + // Updates the room reverb. + void UpdateRoomReverb(); + + private: + // Initializes the Ambisonic renderer subgraph for the speficied Ambisonic + // order and connects it to the |StereoMixerNode|. + // + // Processing graph: + // + // +------------------+ + // | | + // | AmbisonicMixer | + // | | + // +--------+---------+ + // | + // | + // +------------v-------------+ + // | | + // | AmbisonicBinauralDecoder | + // | | + // +------------+-------------+ + // | + // | + // +-----------v------------+ + // | | + // | StereoMixer | + // | | + // +------------------------+ + // + // @param ambisonic_order Ambisonic order. + // @param sh_hrir_filename Filename to load the HRIR data from. + void InitializeAmbisonicRendererGraph(int ambisonic_order, + const std::string& sh_hrir_filename); + + // Helper method to lookup a source node with given |source_id|. + // + // @param source_id Source id. + // @returns Shared pointer to source node instance, nullptr if not found. + std::shared_ptr<BufferedSourceNode> LookupSourceNode(SourceId source_id); + + // Creates an audio subgraph that renders early reflections based on a room + // model on a single mix. + // + // Processing graph: + // + // +---------------------------+ + // | | + // | ReflectionsGainMixer | + // | | + // +-------------+-------------+ + // | + // +----------v----------+ + // | | + // | Reflections | + // | | + // +----------+----------+ + // | + // +----------v----------+ + // | | + // | AmbisonicMixer | + // | | + // +----------+----------+ + // + void InitializeReflectionsGraph(); + + // Creates an audio subgraph that renders a reverb from a mono mix of all the + // sound objects based on a room model. + // + // Processing graph: + // + // +-----------------+ + // | | + // | ReverbGainMixer | + // | | + // +--------+--------+ + // | + // +--------v--------+ + // | | + // | Reverb | + // | | + // +--------+--------+ + // | + // +--------v--------+ + // | | + // | StereoMixer | + // | | + // +-----------------+ + // + void InitializeReverbGraph(); + + // Flag indicating if room effects are enabled. + bool room_effects_enabled_; + + // Mono mixer to accumulate all reverb sources. + std::shared_ptr<GainMixerNode> reverb_gain_mixer_node_; + + // Reflections node. + std::shared_ptr<ReflectionsNode> reflections_node_; + + // Mono mixer node to accumulate the early reflection sources. + std::shared_ptr<GainMixerNode> reflections_gain_mixer_node_; + + // Reverb node. + std::shared_ptr<ReverbNode> reverb_node_; + + // Ambisonic output mixer to accumulate incoming ambisonic inputs into a + // single ambisonic output buffer. + std::unique_ptr<Mixer> ambisonic_output_mixer_; + + // Global config passed in during construction. + const GraphManagerConfig config_; + + // Manages system wide settings. + const SystemSettings& system_settings_; + + // Provides Ambisonic encoding coefficients. + std::unique_ptr<AmbisonicLookupTable> lookup_table_; + + // |FftManager| to be used in nodes that require FFT transformations. + FftManager fft_manager_; + + // |Resampler| to be used to convert HRIRs to the system sample rate. + Resampler resampler_; + + // Ambisonic mixer nodes per each ambisonic order to accumulate the + // ambisonic sources for the corresponding binaural Ambisonic decoders. + std::unordered_map<int, std::shared_ptr<MixerNode>> ambisonic_mixer_nodes_; + + // Stereo mixer to combine all the stereo and binaural output. + std::shared_ptr<MixerNode> stereo_mixer_node_; + + // Ambisonic mixing encoder node to apply encoding coefficients and accumulate + // the Ambisonic buffers. + std::unordered_map<int, std::shared_ptr<AmbisonicMixingEncoderNode>> + ambisonic_mixing_encoder_nodes_; + + // Stereo mixing panner node to apply stereo panning gains and accumulate the + // buffers. + std::shared_ptr<StereoMixingPannerNode> stereo_mixing_panner_node_; + + // Output node that enables audio playback of a single audio stream. + std::shared_ptr<SinkNode> output_node_; + + // Holds all registered source nodes (independently of their type) and + // allows look up by id. + std::unordered_map<SourceId, std::shared_ptr<BufferedSourceNode>> + source_nodes_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_GRAPH_GRAPH_MANAGER_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/graph_manager_config.h b/src/3rdparty/resonance-audio/resonance_audio/graph/graph_manager_config.h new file mode 100644 index 000000000..f3c310a06 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/graph_manager_config.h @@ -0,0 +1,40 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_GRAPH_GRAPH_MANAGER_CONFIG_H_ +#define RESONANCE_AUDIO_GRAPH_GRAPH_MANAGER_CONFIG_H_ + +#include <string> +#include <utility> +#include <vector> + +namespace vraudio { + +// Configuration of the GraphManager and the nodes it is instantiating. +struct GraphManagerConfig { + // Configuration name. + std::string configuration_name; + + // Maximum ambisonic order allowed. + int max_ambisonic_order = 1; + + // HRIR filenames (second element) per ambisonic order (first element). + std::vector<std::pair<int, std::string>> sh_hrir_filenames = {}; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_GRAPH_GRAPH_MANAGER_CONFIG_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/hoa_rotator_node.cc b/src/3rdparty/resonance-audio/resonance_audio/graph/hoa_rotator_node.cc new file mode 100644 index 000000000..4ffae89c3 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/hoa_rotator_node.cc @@ -0,0 +1,69 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "graph/hoa_rotator_node.h" + +#include "ambisonics/utils.h" +#include "base/logging.h" + + +namespace vraudio { + +HoaRotatorNode::HoaRotatorNode(SourceId source_id, + const SystemSettings& system_settings, + int ambisonic_order) + : system_settings_(system_settings), + hoa_rotator_(ambisonic_order), + output_buffer_(GetNumPeriphonicComponents(ambisonic_order), + system_settings.GetFramesPerBuffer()) { + output_buffer_.Clear(); + output_buffer_.set_source_id(source_id); +} + +const AudioBuffer* HoaRotatorNode::AudioProcess(const NodeInput& input) { + + + const AudioBuffer* input_buffer = input.GetSingleInput(); + DCHECK(input_buffer); + DCHECK_GT(input_buffer->num_frames(), 0U); + DCHECK_GE(input_buffer->num_channels(), 4U); + DCHECK_EQ(input_buffer->source_id(), output_buffer_.source_id()); + + // Rotate soundfield buffer by the inverse head orientation. + const auto source_parameters = + system_settings_.GetSourceParameters(input_buffer->source_id()); + if (source_parameters == nullptr) { + LOG(WARNING) << "Could not find source parameters"; + return nullptr; + } + + const WorldRotation& source_rotation = + source_parameters->object_transform.rotation; + const WorldRotation inverse_head_rotation = + system_settings_.GetHeadRotation().conjugate(); + const WorldRotation rotation = inverse_head_rotation * source_rotation; + const bool rotation_applied = + hoa_rotator_.Process(rotation, *input_buffer, &output_buffer_); + + if (!rotation_applied) { + return input_buffer; + } + + // Copy buffer parameters. + return &output_buffer_; +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/hoa_rotator_node.h b/src/3rdparty/resonance-audio/resonance_audio/graph/hoa_rotator_node.h new file mode 100644 index 000000000..1ad3442be --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/hoa_rotator_node.h @@ -0,0 +1,55 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_GRAPH_HOA_ROTATOR_NODE_H_ +#define RESONANCE_AUDIO_GRAPH_HOA_ROTATOR_NODE_H_ + +#include <memory> + +#include "ambisonics/hoa_rotator.h" +#include "base/audio_buffer.h" +#include "base/constants_and_types.h" +#include "graph/system_settings.h" +#include "node/processing_node.h" + +namespace vraudio { + +// Node that accepts a single PeriphonicSoundfieldBuffer as input and outputs a +// rotated PeriphonicSoundfieldBuffer of the corresponding soundfield input +// using head rotation information from the system settings. +class HoaRotatorNode : public ProcessingNode { + public: + HoaRotatorNode(SourceId source_id, const SystemSettings& system_settings, + int ambisonic_order); + + protected: + // Implements ProcessingNode. Returns a null pointer if we are in stereo + // loudspeaker or stereo pan mode. + const AudioBuffer* AudioProcess(const NodeInput& input) override; + + private: + const SystemSettings& system_settings_; + + // Soundfield rotator used to rotate higher order soundfields. + HoaRotator hoa_rotator_; + + // Output buffer. + AudioBuffer output_buffer_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_GRAPH_HOA_ROTATOR_NODE_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/mixer_node.cc b/src/3rdparty/resonance-audio/resonance_audio/graph/mixer_node.cc new file mode 100644 index 000000000..8bb91c767 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/mixer_node.cc @@ -0,0 +1,57 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "graph/mixer_node.h" + + + +namespace vraudio { + +MixerNode::MixerNode(const SystemSettings& system_settings, size_t num_channels) + : num_channels_(num_channels), + mixer_(num_channels_, system_settings.GetFramesPerBuffer()) { + DCHECK_NE(num_channels_, 0U); + EnableProcessOnEmptyInput(true); +} + +const AudioBuffer* MixerNode::GetOutputBuffer() const { + return mixer_.GetOutput(); +} + +bool MixerNode::CleanUp() { + CallCleanUpOnInputNodes(); + // Prevent node from being disconnected when all sources are removed. + return false; +} + +const AudioBuffer* MixerNode::AudioProcess(const NodeInput& input) { + + + mixer_.Reset(); + + const auto& input_buffers = input.GetInputBuffers(); + if (input_buffers.empty()) { + return nullptr; + } + + for (auto input_buffer : input_buffers) { + DCHECK_EQ(input_buffer->num_channels(), num_channels_); + mixer_.AddInput(*input_buffer); + } + return mixer_.GetOutput(); +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/mixer_node.h b/src/3rdparty/resonance-audio/resonance_audio/graph/mixer_node.h new file mode 100644 index 000000000..0fe88f98b --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/mixer_node.h @@ -0,0 +1,54 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_GRAPH_MIXER_NODE_H_ +#define RESONANCE_AUDIO_GRAPH_MIXER_NODE_H_ + +#include "base/audio_buffer.h" +#include "dsp/mixer.h" +#include "graph/system_settings.h" +#include "node/processing_node.h" + +namespace vraudio { + +// Accepts multiple input buffers and outputs a downmix to a single output +// buffer. All input buffers must have the same number of channels and the same +// number of frames per buffer. +class MixerNode : public ProcessingNode { + public: + MixerNode(const SystemSettings& system_settings, size_t num_channels); + + // Returns the current output buffer of the mixer. + // + // @return Output audio buffer. + const AudioBuffer* GetOutputBuffer() const; + + // Node implementation. + bool CleanUp() final; + + protected: + // Implements ProcessingNode. + const AudioBuffer* AudioProcess(const NodeInput& input) override; + + private: + const size_t num_channels_; + + Mixer mixer_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_GRAPH_MIXER_NODE_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/mixer_node_test.cc b/src/3rdparty/resonance-audio/resonance_audio/graph/mixer_node_test.cc new file mode 100644 index 000000000..db9e72e17 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/mixer_node_test.cc @@ -0,0 +1,112 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "graph/mixer_node.h" + +#include <algorithm> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/audio_buffer.h" +#include "base/logging.h" +#include "graph/system_settings.h" +#include "node/sink_node.h" +#include "node/source_node.h" + +namespace vraudio { + +namespace { + +// Helper class to detect destruction. +class DeletionDetector { + public: + explicit DeletionDetector(bool* node_deletion_flag) + : node_deletion_flag_(node_deletion_flag) {} + ~DeletionDetector() { + if (node_deletion_flag_ != nullptr) { + *node_deletion_flag_ = true; + } + } + + private: + bool* node_deletion_flag_; +}; + +// Wraps |SourceNode| to detect its deletion. +class MySourceNode : public SourceNode, DeletionDetector { + public: + explicit MySourceNode(bool* node_deletion_flag) + : SourceNode(), DeletionDetector(node_deletion_flag) {} + + protected: + const AudioBuffer* AudioProcess() final { return nullptr; } +}; + +// Wraps |MixerNode| to detect its deletion. +class MyAudioMixerNode : public MixerNode, DeletionDetector { + public: + explicit MyAudioMixerNode(bool* node_deletion_flag, + const SystemSettings& system_settings) + : MixerNode(system_settings, kNumMonoChannels), + DeletionDetector(node_deletion_flag) {} +}; + +// Wraps |SinkNode| to detect its deletion. +class MySinkNode : public SinkNode, DeletionDetector { + public: + explicit MySinkNode(bool* node_deletion_flag) + : SinkNode(), DeletionDetector(node_deletion_flag) {} +}; + +// Tests that the |MixerNode| keeps connected at the moment all of its sources +// are removed. +TEST(AudioNodesTest, cleanUpOnEmptyInputTest) { + SystemSettings system_settings_(kNumMonoChannels, 128 /* frames_per_buffer */, + 48000 /* sample_rate_hz */); + + bool source_node_deleted = false; + bool mixer_node_deleted = false; + bool sink_node_deleted = false; + + auto sink_node = std::make_shared<MySinkNode>(&sink_node_deleted); + + { + // Create a source and mixer node and connect it to sink node. + auto source_node = std::make_shared<MySourceNode>(&source_node_deleted); + auto mixer_node = std::make_shared<MyAudioMixerNode>(&mixer_node_deleted, + system_settings_); + + // Connect nodes. + sink_node->Connect(mixer_node); + mixer_node->Connect(source_node); + + // End of stream is marked in source node. Do not expect any data anymore. + source_node->MarkEndOfStream(); + } + + EXPECT_FALSE(source_node_deleted); + EXPECT_FALSE(mixer_node_deleted); + EXPECT_FALSE(sink_node_deleted); + + sink_node->CleanUp(); + + EXPECT_TRUE(source_node_deleted); + EXPECT_FALSE(mixer_node_deleted); + EXPECT_FALSE(sink_node_deleted); +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/mono_from_soundfield_node.cc b/src/3rdparty/resonance-audio/resonance_audio/graph/mono_from_soundfield_node.cc new file mode 100644 index 000000000..0519be5ef --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/mono_from_soundfield_node.cc @@ -0,0 +1,45 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "graph/mono_from_soundfield_node.h" + +#include "base/constants_and_types.h" + +namespace vraudio { + +MonoFromSoundfieldNode::MonoFromSoundfieldNode( + SourceId source_id, const SystemSettings& system_settings) + : output_buffer_(kNumMonoChannels, system_settings.GetFramesPerBuffer()) { + output_buffer_.set_source_id(source_id); + output_buffer_.Clear(); +} + +const AudioBuffer* MonoFromSoundfieldNode::AudioProcess( + const NodeInput& input) { + + + const AudioBuffer* input_buffer = input.GetSingleInput(); + DCHECK(input_buffer); + DCHECK_EQ(input_buffer->source_id(), output_buffer_.source_id()); + DCHECK_NE(input_buffer->num_channels(), 0U); + DCHECK_EQ(input_buffer->num_frames(), output_buffer_.num_frames()); + // Get W channel of the ambisonic input. + output_buffer_[0] = (*input_buffer)[0]; + + return &output_buffer_; +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/mono_from_soundfield_node.h b/src/3rdparty/resonance-audio/resonance_audio/graph/mono_from_soundfield_node.h new file mode 100644 index 000000000..6e8525e17 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/mono_from_soundfield_node.h @@ -0,0 +1,44 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_GRAPH_MONO_FROM_SOUNDFIELD_NODE_H_ +#define RESONANCE_AUDIO_GRAPH_MONO_FROM_SOUNDFIELD_NODE_H_ + +#include "base/audio_buffer.h" +#include "graph/system_settings.h" +#include "node/processing_node.h" + +namespace vraudio { + +// Node that accepts an ambisonic buffer as input and extracts its W channel +// onto a mono output buffer. +class MonoFromSoundfieldNode : public ProcessingNode { + public: + MonoFromSoundfieldNode(SourceId source_id, + const SystemSettings& system_settings); + + protected: + // Implements |ProcessingNode|. + const AudioBuffer* AudioProcess(const NodeInput& input) override; + + private: + // Mono audio buffer to store output data. + AudioBuffer output_buffer_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_GRAPH_MONO_FROM_SOUNDFIELD_NODE_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/near_field_effect_node.cc b/src/3rdparty/resonance-audio/resonance_audio/graph/near_field_effect_node.cc new file mode 100644 index 000000000..2b75ae6e9 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/near_field_effect_node.cc @@ -0,0 +1,117 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "graph/near_field_effect_node.h" + +#include <algorithm> +#include <cmath> + +#include "base/logging.h" +#include "base/spherical_angle.h" + +#include "dsp/distance_attenuation.h" +#include "dsp/gain.h" +#include "dsp/stereo_panner.h" + +namespace vraudio { + +NearFieldEffectNode::NearFieldEffectNode(SourceId source_id, + const SystemSettings& system_settings) + : pan_gains_({0.0f, 0.0f}), + near_field_processor_(system_settings.GetSampleRateHz(), + system_settings.GetFramesPerBuffer()), + system_settings_(system_settings), + output_buffer_(kNumStereoChannels, system_settings.GetFramesPerBuffer()) { + output_buffer_.set_source_id(source_id); +} + +const AudioBuffer* NearFieldEffectNode::AudioProcess(const NodeInput& input) { + + + const AudioBuffer* input_buffer = input.GetSingleInput(); + DCHECK(input_buffer); + DCHECK_EQ(input_buffer->num_channels(), 1U); + DCHECK_EQ(input_buffer->source_id(), output_buffer_.source_id()); + + const auto source_parameters = + system_settings_.GetSourceParameters(input_buffer->source_id()); + if (source_parameters == nullptr) { + LOG(WARNING) << "Could not find source parameters"; + return nullptr; + } + + + DCHECK_EQ(pan_gains_.size(), kNumStereoChannels); + const float near_field_gain = source_parameters->near_field_gain; + if (near_field_gain > 0.0f) { + const auto& listener_position = system_settings_.GetHeadPosition(); + const auto& listener_rotation = system_settings_.GetHeadRotation(); + const auto& source_transform = source_parameters->object_transform; + // Compute the relative source direction in spherical angles to calculate + // the left and right panner gains. + WorldPosition relative_direction; + GetRelativeDirection(listener_position, listener_rotation, + source_transform.position, &relative_direction); + const auto source_direction = + SphericalAngle::FromWorldPosition(relative_direction); + CalculateStereoPanGains(source_direction, &pan_gains_); + // Combine pan gains with per-source near field gain. + const float total_near_field_gain = + ComputeNearFieldEffectGain(listener_position, + source_transform.position) * + near_field_gain / kMaxNearFieldEffectGain; + for (size_t i = 0; i < pan_gains_.size(); ++i) { + pan_gains_[i] *= total_near_field_gain; + } + } else { + // Disable near field effect if |near_field_gain| is zero. + std::fill(pan_gains_.begin(), pan_gains_.end(), 0.0f); + } + + const float left_current_gain = left_panner_.GetGain(); + const float right_current_gain = right_panner_.GetGain(); + const float left_target_gain = pan_gains_[0]; + const float right_target_gain = pan_gains_[1]; + const bool is_left_zero_gain = + IsGainNearZero(left_current_gain) && IsGainNearZero(left_target_gain); + const bool is_right_zero_gain = + IsGainNearZero(right_current_gain) && IsGainNearZero(right_target_gain); + + if (is_left_zero_gain && is_right_zero_gain) { + // Make sure gain processors are initialized. + left_panner_.Reset(0.0f); + right_panner_.Reset(0.0f); + // Both channels go to zero, there is no need for further processing. + return nullptr; + } + + const auto& input_channel = (*input_buffer)[0]; + auto* left_output_channel = &output_buffer_[0]; + auto* right_output_channel = &output_buffer_[1]; + // Apply bass boost and delay compensation (if necessary) to the input signal + // and place it temporarily in the right output channel. This way we avoid + // allocating a temporary buffer. + near_field_processor_.Process(input_channel, right_output_channel, + source_parameters->enable_hrtf); + left_panner_.ApplyGain(left_target_gain, *right_output_channel, + left_output_channel, /*accumulate_output=*/false); + right_panner_.ApplyGain(right_target_gain, *right_output_channel, + right_output_channel, /*accumulate_output=*/false); + + return &output_buffer_; +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/near_field_effect_node.h b/src/3rdparty/resonance-audio/resonance_audio/graph/near_field_effect_node.h new file mode 100644 index 000000000..83b8c6d69 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/near_field_effect_node.h @@ -0,0 +1,69 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_GRAPH_NEAR_FIELD_EFFECT_NODE_H_ +#define RESONANCE_AUDIO_GRAPH_NEAR_FIELD_EFFECT_NODE_H_ + +#include <vector> + +#include "base/audio_buffer.h" +#include "base/constants_and_types.h" +#include "dsp/gain_processor.h" +#include "dsp/near_field_processor.h" +#include "graph/system_settings.h" +#include "node/processing_node.h" + +namespace vraudio { + +// Node that accepts a single mono audio buffer as input, applies an appoximate +// near field effect and outputs a processed stereo audio buffer. The stereo +// output buffer can then be combined with a binaural output in order to +// simulate a sound source which is close (<1m) to the listener's head. +class NearFieldEffectNode : public ProcessingNode { + public: + // Constructor. + // + // @param source_id Output buffer source id. + // @param system_settings Global system settings. + NearFieldEffectNode(SourceId source_id, + const SystemSettings& system_settings); + + protected: + // Implements ProcessingNode. + const AudioBuffer* AudioProcess(const NodeInput& input) override; + + private: + // Left and right processors apply both near field gain and panner gains. + GainProcessor left_panner_; + GainProcessor right_panner_; + + // Left and right gains for near field and panning combined. + std::vector<float> pan_gains_; + + // Near field processor used to apply approximate near field effect to the + // mono source signal. + NearFieldProcessor near_field_processor_; + + // Used to obtain head rotation. + const SystemSettings& system_settings_; + + // Output buffer. + AudioBuffer output_buffer_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_GRAPH_NEAR_FIELD_EFFECT_NODE_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/near_field_effect_node_test.cc b/src/3rdparty/resonance-audio/resonance_audio/graph/near_field_effect_node_test.cc new file mode 100644 index 000000000..1117ccd21 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/near_field_effect_node_test.cc @@ -0,0 +1,127 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "graph/near_field_effect_node.h" + +#include <memory> +#include <vector> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/constants_and_types.h" +#include "dsp/distance_attenuation.h" +#include "dsp/stereo_panner.h" +#include "graph/buffered_source_node.h" +#include "node/sink_node.h" +#include "node/source_node.h" +#include "utils/test_util.h" + +namespace vraudio { + +namespace { + +// Source Id for use with |BufferedSourceNode|. +const SourceId kSourceId = 0; + +// Number of frames per buffer. +const size_t kFramesPerBuffer = kUnitRampLength; + +// Sampling rate. +const int kSampleRate = 48000; + +// Source distances. +const size_t kNumDistances = 5; +const float kDistances[kNumDistances] = {0.0f, 0.25f, 0.5f, 0.75f, 10.0f}; + +// Maximum expected gain change determines the number of buffers we need to wait +// for the output sample values to settle. +const size_t kMaxExpectedGainChange = 9; + +// Expected offset due to near field processor delay compensation at 48000kHz. +const size_t kExpectedDelay = 31; + +// Expected Dirac pulse attenuation due to shelf-filtering at 48000kHz. +const float KExpectedPeakReduction = 0.87319273f; + +} // namespace + +TEST(NearFieldEffectNodeTest, VariousDistanceTest) { + const size_t kDiracOffset = kFramesPerBuffer / 2; + // We add one extra buffer since the Dirac is in the middle of the buffer. + const size_t kBuffersToSettle = kMaxExpectedGainChange + 1; + const auto kIdentityRotation = WorldRotation(); + SystemSettings system_settings(kNumStereoChannels, kFramesPerBuffer, + kSampleRate); + + // Create the simple audio graph. + auto near_field_effect_node = + std::make_shared<NearFieldEffectNode>(kSourceId, system_settings); + auto input_node = std::make_shared<BufferedSourceNode>( + kSourceId, kNumMonoChannels, kFramesPerBuffer); + auto output_node = std::make_shared<SinkNode>(); + near_field_effect_node->Connect(input_node); + output_node->Connect(near_field_effect_node); + auto parameters_manager = system_settings.GetSourceParametersManager(); + parameters_manager->Register(kSourceId); + + const AudioBuffer* output_buffer; + for (size_t i = 0; i < kNumDistances; ++i) { + const WorldPosition input_position(kDistances[i], 0.0f, 0.0f); + // Loop till gain processors have reached steady state. + for (size_t settle = 0; settle < kBuffersToSettle; ++settle) { + AudioBuffer* const input_node_buffer = + input_node->GetMutableAudioBufferAndSetNewBufferFlag(); + GenerateDiracImpulseFilter(kDiracOffset, &(*input_node_buffer)[0]); + + auto source_parameters = + parameters_manager->GetMutableParameters(kSourceId); + source_parameters->object_transform.position = input_position; + source_parameters->near_field_gain = kMaxNearFieldEffectGain; + // Retrieve the output. + const auto& buffer_vector = output_node->ReadInputs(); + if (!buffer_vector.empty()) { + EXPECT_EQ(buffer_vector.size(), 1U); + output_buffer = buffer_vector.front(); + } else { + output_buffer = nullptr; + } + } + + std::vector<float> stereo_pan_gains(kNumStereoChannels, 0.0f); + // These methods are tested elsewhere. Their output will be used to + // determine if the output from the |NearfieldEffectNode| is correct. + WorldPosition relative_direction; + GetRelativeDirection(system_settings.GetHeadPosition(), kIdentityRotation, + input_position, &relative_direction); + const SphericalAngle source_direction = + SphericalAngle::FromWorldPosition(relative_direction); + CalculateStereoPanGains(source_direction, &stereo_pan_gains); + const float near_field_gain = ComputeNearFieldEffectGain( + system_settings.GetHeadPosition(), input_position); + if (i < kNumDistances - 1) { + EXPECT_FALSE(output_buffer == nullptr); + EXPECT_NEAR( + near_field_gain * stereo_pan_gains[0] * KExpectedPeakReduction, + (*output_buffer)[0][kDiracOffset + kExpectedDelay], kEpsilonFloat); + EXPECT_NEAR( + near_field_gain * stereo_pan_gains[1] * KExpectedPeakReduction, + (*output_buffer)[1][kDiracOffset + kExpectedDelay], kEpsilonFloat); + } else { + EXPECT_TRUE(output_buffer == nullptr); + } + } +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/occlusion_node.cc b/src/3rdparty/resonance-audio/resonance_audio/graph/occlusion_node.cc new file mode 100644 index 000000000..3805836f5 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/occlusion_node.cc @@ -0,0 +1,101 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "graph/occlusion_node.h" + +#include <cmath> + +#include "base/logging.h" +#include "base/spherical_angle.h" + +#include "dsp/occlusion_calculator.h" + +namespace vraudio { + +namespace { + +// Low pass filter coefficient for smoothing the applied occlusion. This avoids +// sudden unrealistic changes in the volume of a sound object. Range [0, 1]. +// The value below has been calculated empirically. +const float kOcclusionSmoothingCoefficient = 0.75f; + +// This function provides first order low-pass filtering. It is used to smooth +// the occlusion parameter. +float Interpolate(float coefficient, float previous_value, float target_value) { + return target_value + coefficient * (previous_value - target_value); +} + +} // namespace + +OcclusionNode::OcclusionNode(SourceId source_id, + const SystemSettings& system_settings) + : system_settings_(system_settings), + low_pass_filter_(0.0f), + current_occlusion_(0.0f), + output_buffer_(kNumMonoChannels, system_settings.GetFramesPerBuffer()) { + output_buffer_.Clear(); + output_buffer_.set_source_id(source_id); +} + +const AudioBuffer* OcclusionNode::AudioProcess(const NodeInput& input) { + + const AudioBuffer* input_buffer = input.GetSingleInput(); + DCHECK(input_buffer); + DCHECK_EQ(input_buffer->source_id(), output_buffer_.source_id()); + + const auto source_parameters = + system_settings_.GetSourceParameters(input_buffer->source_id()); + if (source_parameters == nullptr) { + LOG(WARNING) << "Could not find source parameters"; + return nullptr; + } + + const WorldPosition& listener_position = system_settings_.GetHeadPosition(); + const WorldRotation& listener_rotation = system_settings_.GetHeadRotation(); + const ObjectTransform& source_transform = source_parameters->object_transform; + // Compute the relative listener/source direction in spherical angles. + WorldPosition relative_direction; + GetRelativeDirection(listener_position, listener_rotation, + source_transform.position, &relative_direction); + const SphericalAngle listener_direction = + SphericalAngle::FromWorldPosition(relative_direction); + + GetRelativeDirection(source_transform.position, source_transform.rotation, + listener_position, &relative_direction); + const SphericalAngle source_direction = + SphericalAngle::FromWorldPosition(relative_direction); + // Calculate low-pass filter coefficient based on listener/source directivity + // and occlusion values. + const float listener_directivity = CalculateDirectivity( + source_parameters->listener_directivity_alpha, + source_parameters->listener_directivity_order, listener_direction); + const float source_directivity = CalculateDirectivity( + source_parameters->directivity_alpha, + source_parameters->directivity_order, source_direction); + current_occlusion_ = + Interpolate(kOcclusionSmoothingCoefficient, current_occlusion_, + source_parameters->occlusion_intensity); + const float filter_coefficient = CalculateOcclusionFilterCoefficient( + listener_directivity * source_directivity, current_occlusion_); + low_pass_filter_.SetCoefficient(filter_coefficient); + if (!low_pass_filter_.Filter((*input_buffer)[0], &output_buffer_[0])) { + return input_buffer; + } + // Copy buffer parameters. + return &output_buffer_; +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/occlusion_node.h b/src/3rdparty/resonance-audio/resonance_audio/graph/occlusion_node.h new file mode 100644 index 000000000..316788751 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/occlusion_node.h @@ -0,0 +1,59 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_GRAPH_OCCLUSION_NODE_H_ +#define RESONANCE_AUDIO_GRAPH_OCCLUSION_NODE_H_ + +#include "base/audio_buffer.h" +#include "dsp/mono_pole_filter.h" +#include "graph/system_settings.h" +#include "node/processing_node.h" + +namespace vraudio { + +// Node that accepts a single audio buffer as input and outputs the input buffer +// with its cuttoff frequency scaled by listener/source directivity and +// occlusion intensity. +class OcclusionNode : public ProcessingNode { + public: + // Constructor. + // + // @param source_id Output buffer source id. + // @param system_settings Global system settings. + OcclusionNode(SourceId source_id, const SystemSettings& system_settings); + + protected: + // Implements ProcessingNode. + const AudioBuffer* AudioProcess(const NodeInput& input) override; + + private: + friend class OcclusionNodeTest; + + const SystemSettings& system_settings_; + + // Used to low-pass input audio when a source is occluded or self-occluded. + MonoPoleFilter low_pass_filter_; + + // Occlusion intensity value for the current input buffer. + float current_occlusion_; + + // Output buffer. + AudioBuffer output_buffer_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_GRAPH_OCCLUSION_NODE_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/occlusion_node_test.cc b/src/3rdparty/resonance-audio/resonance_audio/graph/occlusion_node_test.cc new file mode 100644 index 000000000..3dac808c3 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/occlusion_node_test.cc @@ -0,0 +1,186 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "graph/occlusion_node.h" + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/constants_and_types.h" +#include "utils/test_util.h" + +namespace vraudio { + +namespace { + +// Number of frames per buffer. +const size_t kFramesPerBuffer = 256; + +// Sampling rate. +const int kSampleRate = 48000; + +// Generated sawtooth length for test buffers. +const size_t kSawtoothLength = 16; + +// Source id. +const SourceId kSourceId = 1; + +} // namespace + +class OcclusionNodeTest : public ::testing::Test { + protected: + OcclusionNodeTest() + : system_settings_(kNumStereoChannels, kFramesPerBuffer, kSampleRate) {} + + void SetUp() override { + system_settings_.GetSourceParametersManager()->Register(kSourceId); + } + + // Function which wraps the buffer passed in a vector for processing and + // returns the output of the occlusion nodes AudioProcess method. + const AudioBuffer* GetProcessedData(const AudioBuffer* input_buffer, + OcclusionNode* occlusion_node) { + std::vector<const AudioBuffer*> input_buffers; + input_buffers.push_back(input_buffer); + return occlusion_node->AudioProcess( + ProcessingNode::NodeInput(input_buffers)); + } + + // Returns a pointer to the parameters of the source. + SourceParameters* GetParameters() { + return system_settings_.GetSourceParametersManager()->GetMutableParameters( + kSourceId); + } + + // System settings. + SystemSettings system_settings_; +}; + +// Test to ensure that no effect is made on a buffer of input if both occlusion +// and self-occlusion are absent. +TEST_F(OcclusionNodeTest, NoOcclusionTest) { + OcclusionNode occlusion_processor(kSourceId, system_settings_); + + AudioBuffer input(1, kFramesPerBuffer); + input.set_source_id(kSourceId); + GenerateSawToothSignal(kSawtoothLength, &input[0]); + + const AudioBuffer* output = GetProcessedData(&input, &occlusion_processor); + const bool buffers_identical = + CompareAudioBuffers((*output)[0], input[0], kEpsilonFloat); + EXPECT_TRUE(buffers_identical); +} + +// Test to ensure that a more heavily occluded object shows a lower energy +// output. +TEST_F(OcclusionNodeTest, OcclusionTest) { + OcclusionNode occlusion_processor_1(kSourceId, system_settings_); + OcclusionNode occlusion_processor_2(kSourceId, system_settings_); + + AudioBuffer input(kNumMonoChannels, kFramesPerBuffer); + GenerateSawToothSignal(kSawtoothLength, &input[0]); + AudioBuffer input_1; + input_1 = input; + input_1.set_source_id(kSourceId); + AudioBuffer input_2; + input_2 = input; + input_2.set_source_id(kSourceId); + + SourceParameters* parameters = GetParameters(); + + parameters->occlusion_intensity = 0.5f; + const AudioBuffer* output_1 = + GetProcessedData(&input_1, &occlusion_processor_1); + parameters->occlusion_intensity = 1.0f; + const AudioBuffer* output_2 = + GetProcessedData(&input_2, &occlusion_processor_2); + const double output_1_energy = CalculateSignalRms((*output_1)[0]); + const double output_2_energy = CalculateSignalRms((*output_2)[0]); + const double input_energy = CalculateSignalRms(input[0]); + + EXPECT_LT(output_1_energy, input_energy); + EXPECT_LT(output_2_energy, output_1_energy); +} + +// Test to ensure that setting a non-omnidirectional listener directivity +// pattern shows a lower energy output when the listener orientation is pointing +// away from a source. +TEST_F(OcclusionNodeTest, ListenerDirectivityTest) { + OcclusionNode occlusion_processor(kSourceId, system_settings_); + + AudioBuffer input(kNumMonoChannels, kFramesPerBuffer); + input.set_source_id(kSourceId); + GenerateSawToothSignal(kSawtoothLength, &input[0]); + + // Set a hyper-cardioid shaped listener directivity for the source input. + SourceParameters* parameters = GetParameters(); + parameters->listener_directivity_alpha = 0.5f; + parameters->listener_directivity_order = 2.0f; + // Set listener position to one meter away from the origin in Z axis. This is + // required for the listener directivity properties to take effect. + system_settings_.SetHeadPosition(WorldPosition(0.0f, 0.0f, 1.0f)); + + const double input_energy = CalculateSignalRms(input[0]); + // Process input with identity listener orientation. + const AudioBuffer* output_default = + GetProcessedData(&input, &occlusion_processor); + const double output_default_energy = CalculateSignalRms((*output_default)[0]); + // Process input with 90 degrees rotated listener orientation about Y axis. + system_settings_.SetHeadRotation( + WorldRotation(0.0f, kInverseSqrtTwo, 0.0f, kInverseSqrtTwo)); + const AudioBuffer* output_rotated = + GetProcessedData(&input, &occlusion_processor); + const double output_rotated_energy = CalculateSignalRms((*output_rotated)[0]); + + // Test if the output energy is lower when the listener was rotated away from + // the source. + EXPECT_NEAR(output_default_energy, input_energy, kEpsilonFloat); + EXPECT_LT(output_rotated_energy, output_default_energy); +} + +// Test to ensure that a more heavily self occluded source shows a lower energy +// output. +TEST_F(OcclusionNodeTest, SourceDirectivityTest) { + OcclusionNode occlusion_processor_1(kSourceId, system_settings_); + OcclusionNode occlusion_processor_2(kSourceId, system_settings_); + + AudioBuffer input(kNumMonoChannels, kFramesPerBuffer); + input.set_source_id(kSourceId); + GenerateSawToothSignal(kSawtoothLength, &input[0]); + AudioBuffer input_1; + input_1 = input; + AudioBuffer input_2; + input_2 = input; + + SourceParameters* parameters = GetParameters(); + + parameters->directivity_alpha = 0.25f; + parameters->directivity_order = 2.0f; + const AudioBuffer* output_1 = + GetProcessedData(&input_1, &occlusion_processor_1); + + parameters->directivity_order = 4.0f; + parameters->directivity_alpha = 0.25f; + const AudioBuffer* output_2 = + GetProcessedData(&input_2, &occlusion_processor_2); + + const double output_1_energy = CalculateSignalRms((*output_1)[0]); + const double output_2_energy = CalculateSignalRms((*output_2)[0]); + const double input_energy = CalculateSignalRms(input[0]); + + EXPECT_LT(output_1_energy, input_energy); + EXPECT_LT(output_2_energy, output_1_energy); +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/reflections_node.cc b/src/3rdparty/resonance-audio/resonance_audio/graph/reflections_node.cc new file mode 100644 index 000000000..1223fe7e8 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/reflections_node.cc @@ -0,0 +1,113 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "graph/reflections_node.h" + +#include "base/constants_and_types.h" +#include "base/logging.h" +#include "base/misc_math.h" + + +namespace vraudio { + +ReflectionsNode::ReflectionsNode(const SystemSettings& system_settings) + : system_settings_(system_settings), + reflections_processor_(system_settings_.GetSampleRateHz(), + system_settings_.GetFramesPerBuffer()), + num_frames_processed_on_empty_input_( + system_settings_.GetFramesPerBuffer()), + output_buffer_(kNumFirstOrderAmbisonicChannels, + system_settings_.GetFramesPerBuffer()), + silence_mono_buffer_(kNumMonoChannels, + system_settings_.GetFramesPerBuffer()) { + silence_mono_buffer_.Clear(); + EnableProcessOnEmptyInput(true); +} + +void ReflectionsNode::Update() { + + const auto& current_reflection_properties = reflection_properties_; + const auto& new_reflection_properties = + system_settings_.GetReflectionProperties(); + const bool room_position_changed = + !EqualSafe(std::begin(current_reflection_properties.room_position), + std::end(current_reflection_properties.room_position), + std::begin(new_reflection_properties.room_position), + std::end(new_reflection_properties.room_position)); + const bool room_rotation_changed = + !EqualSafe(std::begin(current_reflection_properties.room_rotation), + std::end(current_reflection_properties.room_rotation), + std::begin(new_reflection_properties.room_rotation), + std::end(new_reflection_properties.room_rotation)); + const bool room_dimensions_changed = + !EqualSafe(std::begin(current_reflection_properties.room_dimensions), + std::end(current_reflection_properties.room_dimensions), + std::begin(new_reflection_properties.room_dimensions), + std::end(new_reflection_properties.room_dimensions)); + const bool cutoff_frequency_changed = + current_reflection_properties.cutoff_frequency != + new_reflection_properties.cutoff_frequency; + const bool coefficients_changed = + !EqualSafe(std::begin(current_reflection_properties.coefficients), + std::end(current_reflection_properties.coefficients), + std::begin(new_reflection_properties.coefficients), + std::end(new_reflection_properties.coefficients)); + const auto& current_listener_position = listener_position_; + const auto& new_listener_position = system_settings_.GetHeadPosition(); + const bool listener_position_changed = + current_listener_position != new_listener_position; + if (room_position_changed || room_rotation_changed || + room_dimensions_changed || cutoff_frequency_changed || + coefficients_changed || listener_position_changed) { + // Update reflections processor if necessary. + reflection_properties_ = new_reflection_properties; + listener_position_ = new_listener_position; + reflections_processor_.Update(reflection_properties_, listener_position_); + } +} + +const AudioBuffer* ReflectionsNode::AudioProcess(const NodeInput& input) { + + const AudioBuffer* input_buffer = input.GetSingleInput(); + const size_t num_frames = system_settings_.GetFramesPerBuffer(); + if (input_buffer == nullptr) { + // If we have no input, generate a silent input buffer until the node states + // are cleared. + if (num_frames_processed_on_empty_input_ < + reflections_processor_.num_frames_to_process_on_empty_input()) { + num_frames_processed_on_empty_input_ += num_frames; + input_buffer = &silence_mono_buffer_; + } else { + // Skip processing entirely when the states are fully cleared. + return nullptr; + } + } else { + num_frames_processed_on_empty_input_ = 0; + DCHECK_EQ(input_buffer->num_channels(), kNumMonoChannels); + } + output_buffer_.Clear(); + reflections_processor_.Process(*input_buffer, &output_buffer_); + + // Rotate the reflections with respect to listener's orientation. + const WorldRotation inverse_head_rotation = + system_settings_.GetHeadRotation().conjugate(); + foa_rotator_.Process(inverse_head_rotation, output_buffer_, &output_buffer_); + + // Copy buffer parameters. + return &output_buffer_; +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/reflections_node.h b/src/3rdparty/resonance-audio/resonance_audio/graph/reflections_node.h new file mode 100644 index 000000000..8685759b9 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/reflections_node.h @@ -0,0 +1,78 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_GRAPH_REFLECTIONS_NODE_H_ +#define RESONANCE_AUDIO_GRAPH_REFLECTIONS_NODE_H_ + +#include <vector> + +#include "ambisonics/foa_rotator.h" +#include "api/resonance_audio_api.h" +#include "base/audio_buffer.h" +#include "base/misc_math.h" +#include "dsp/reflections_processor.h" +#include "graph/system_settings.h" +#include "node/processing_node.h" + +namespace vraudio { + +// Node that accepts a single mono buffer as input and outputs an ambisonically +// encoded sound field buffer of the mix of all the early room reflections. +class ReflectionsNode : public ProcessingNode { + public: + // Initializes |ReflectionsNode| class. + // + // @param system_settings Global system configuration. + explicit ReflectionsNode(const SystemSettings& system_settings); + + // Updates the reflections. Depending on whether to use RT60s for reverb + // according to the global system settings, the reflections are calculated + // either by the current room properties or the proxy room properties. + void Update(); + + protected: + // Implements ProcessingNode. + const AudioBuffer* AudioProcess(const NodeInput& input) override; + + private: + const SystemSettings& system_settings_; + + // First-order-ambisonics rotator to be used to rotate the reflections with + // respect to the listener's orientation. + FoaRotator foa_rotator_; + + // Processes and encodes reflections into an ambisonic buffer. + ReflectionsProcessor reflections_processor_; + + // Most recently updated reflection properties. + ReflectionProperties reflection_properties_; + + // Most recently updated listener position. + WorldPosition listener_position_; + + size_t num_frames_processed_on_empty_input_; + + // Ambisonic output buffer. + AudioBuffer output_buffer_; + + // Silence mono buffer to render reflection tails during the absence of input + // buffers. + AudioBuffer silence_mono_buffer_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_GRAPH_REFLECTIONS_NODE_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/resonance_audio_api_impl.cc b/src/3rdparty/resonance-audio/resonance_audio/graph/resonance_audio_api_impl.cc new file mode 100644 index 000000000..f8cc6731b --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/resonance_audio_api_impl.cc @@ -0,0 +1,586 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "graph/resonance_audio_api_impl.h" + +#include <algorithm> +#include <numeric> + +#include "ambisonics/utils.h" +#include "base/constants_and_types.h" +#include "base/logging.h" +#include "base/misc_math.h" +#include "base/source_parameters.h" + +#include "base/unique_ptr_wrapper.h" +#include "config/source_config.h" +#include "dsp/channel_converter.h" +#include "dsp/distance_attenuation.h" +#include "graph/source_parameters_manager.h" +#include "utils/planar_interleaved_conversion.h" +#include "utils/sample_type_conversion.h" + +namespace vraudio { + +namespace { + +// Support 50 setter calls for 512 sources. +const size_t kMaxNumTasksOnTaskQueue = 50 * 512; + +// User warning/notification messages. +static const char* kBadInputPointerMessage = "Ignoring nullptr buffer"; +static const char* kBufferSizeMustMatchNumFramesMessage = + "Number of frames must match the frames per buffer specified during " + "construction - ignoring buffer"; + +// Helper method to fetch |SourceGraphConfig| from |RenderingMode|. +SourceGraphConfig GetSourceGraphConfigFromRenderingMode( + RenderingMode rendering_mode) { + switch (rendering_mode) { + case RenderingMode::kStereoPanning: + return StereoPanningConfig(); + case RenderingMode::kBinauralLowQuality: + return BinauralLowQualityConfig(); + case RenderingMode::kBinauralMediumQuality: + return BinauralMediumQualityConfig(); + case RenderingMode::kBinauralHighQuality: + return BinauralHighQualityConfig(); + case RenderingMode::kRoomEffectsOnly: + return RoomEffectsOnlyConfig(); + default: + LOG(FATAL) << "Unknown rendering mode"; + break; + } + return BinauralHighQualityConfig(); +} + +} // namespace + +ResonanceAudioApiImpl::ResonanceAudioApiImpl(size_t num_channels, + size_t frames_per_buffer, + int sample_rate_hz) + : system_settings_(num_channels, frames_per_buffer, sample_rate_hz), + task_queue_(kMaxNumTasksOnTaskQueue), + source_id_counter_(0) { + if (num_channels != kNumStereoChannels) { + LOG(FATAL) << "Only stereo output is supported"; + return; + } + + if (frames_per_buffer > kMaxSupportedNumFrames) { + LOG(FATAL) << "Only frame lengths up to " << kMaxSupportedNumFrames + << " are supported."; + return; + } + + // The pffft library requires a minimum buffer size of 32 samples. + if (frames_per_buffer < FftManager::kMinFftSize) { + LOG(FATAL) << "The minimum number of frames per buffer is " + << FftManager::kMinFftSize << " samples"; + return; + } + graph_manager_.reset(new GraphManager(system_settings_)); +} + +ResonanceAudioApiImpl::~ResonanceAudioApiImpl() { + // Clear task queue before shutting down. + task_queue_.Execute(); +} + +bool ResonanceAudioApiImpl::FillInterleavedOutputBuffer(size_t num_channels, + size_t num_frames, + float* buffer_ptr) { + DCHECK(buffer_ptr); + return FillOutputBuffer<float*>(num_channels, num_frames, buffer_ptr); +} + +bool ResonanceAudioApiImpl::FillInterleavedOutputBuffer(size_t num_channels, + size_t num_frames, + int16* buffer_ptr) { + DCHECK(buffer_ptr); + return FillOutputBuffer<int16*>(num_channels, num_frames, buffer_ptr); +} + +bool ResonanceAudioApiImpl::FillPlanarOutputBuffer(size_t num_channels, + size_t num_frames, + float* const* buffer_ptr) { + DCHECK(buffer_ptr); + return FillOutputBuffer<float* const*>(num_channels, num_frames, buffer_ptr); +} + +bool ResonanceAudioApiImpl::FillPlanarOutputBuffer(size_t num_channels, + size_t num_frames, + int16* const* buffer_ptr) { + DCHECK(buffer_ptr); + return FillOutputBuffer<int16* const*>(num_channels, num_frames, buffer_ptr); +} + +void ResonanceAudioApiImpl::SetHeadPosition(float x, float y, float z) { + auto task = [this, x, y, z]() { + const WorldPosition head_position(x, y, z); + system_settings_.SetHeadPosition(head_position); + }; + task_queue_.Post(task); +} + +void ResonanceAudioApiImpl::SetHeadRotation(float x, float y, float z, + float w) { + auto task = [this, w, x, y, z]() { + const WorldRotation head_rotation(w, x, y, z); + system_settings_.SetHeadRotation(head_rotation); + }; + task_queue_.Post(task); +} + +void ResonanceAudioApiImpl::SetMasterVolume(float volume) { + auto task = [this, volume]() { system_settings_.SetMasterGain(volume); }; + task_queue_.Post(task); +} + +int ResonanceAudioApiImpl::CreateAmbisonicSource(size_t num_channels) { + if (num_channels < kNumFirstOrderAmbisonicChannels || + !IsValidAmbisonicOrder(num_channels)) { + // Invalid number of input channels, don't create the ambisonic source. + LOG(ERROR) << "Invalid number of channels for the ambisonic source: " + << num_channels; + return kInvalidSourceId; + } + + const int ambisonic_source_id = source_id_counter_.fetch_add(1); + + const size_t num_valid_channels = + std::min(num_channels, graph_manager_->GetNumMaxAmbisonicChannels()); + if (num_valid_channels < num_channels) { + LOG(WARNING) << "Number of ambisonic channels will be diminished to " + << num_valid_channels; + } + + auto task = [this, ambisonic_source_id, num_valid_channels]() { + graph_manager_->CreateAmbisonicSource(ambisonic_source_id, + num_valid_channels); + system_settings_.GetSourceParametersManager()->Register( + ambisonic_source_id); + // Overwrite default source parameters for ambisonic source. + auto source_parameters = + system_settings_.GetSourceParametersManager()->GetMutableParameters( + ambisonic_source_id); + source_parameters->room_effects_gain = 0.0f; + source_parameters->distance_rolloff_model = DistanceRolloffModel::kNone; + source_parameters->distance_attenuation = 1.0f; + }; + task_queue_.Post(task); + return ambisonic_source_id; +} + +int ResonanceAudioApiImpl::CreateStereoSource(size_t num_channels) { + if (num_channels > kNumStereoChannels) { + LOG(ERROR) << "Unsupported number of input channels"; + return kInvalidSourceId; + } + const int stereo_source_id = source_id_counter_.fetch_add(1); + + auto task = [this, stereo_source_id]() { + graph_manager_->CreateStereoSource(stereo_source_id); + system_settings_.GetSourceParametersManager()->Register(stereo_source_id); + auto source_parameters = + system_settings_.GetSourceParametersManager()->GetMutableParameters( + stereo_source_id); + source_parameters->enable_hrtf = false; + }; + task_queue_.Post(task); + return stereo_source_id; +} + +int ResonanceAudioApiImpl::CreateSoundObjectSource( + RenderingMode rendering_mode) { + const int sound_object_source_id = source_id_counter_.fetch_add(1); + + const auto config = GetSourceGraphConfigFromRenderingMode(rendering_mode); + auto task = [this, sound_object_source_id, config]() { + graph_manager_->CreateSoundObjectSource( + sound_object_source_id, config.ambisonic_order, config.enable_hrtf, + config.enable_direct_rendering); + system_settings_.GetSourceParametersManager()->Register( + sound_object_source_id); + auto source_parameters = + system_settings_.GetSourceParametersManager()->GetMutableParameters( + sound_object_source_id); + source_parameters->enable_hrtf = config.enable_hrtf; + }; + task_queue_.Post(task); + return sound_object_source_id; +} + +void ResonanceAudioApiImpl::DestroySource(SourceId source_id) { + auto task = [this, source_id]() { + graph_manager_->DestroySource(source_id); + system_settings_.GetSourceParametersManager()->Unregister(source_id); + }; + task_queue_.Post(task); +} + +void ResonanceAudioApiImpl::SetInterleavedBuffer(SourceId source_id, + const float* audio_buffer_ptr, + size_t num_channels, + size_t num_frames) { + SetSourceBuffer<const float*>(source_id, audio_buffer_ptr, num_channels, + num_frames); +} + +void ResonanceAudioApiImpl::SetInterleavedBuffer(SourceId source_id, + const int16* audio_buffer_ptr, + size_t num_channels, + size_t num_frames) { + SetSourceBuffer<const int16*>(source_id, audio_buffer_ptr, num_channels, + num_frames); +} + +void ResonanceAudioApiImpl::SetPlanarBuffer( + SourceId source_id, const float* const* audio_buffer_ptr, + size_t num_channels, size_t num_frames) { + SetSourceBuffer<const float* const*>(source_id, audio_buffer_ptr, + num_channels, num_frames); +} + +void ResonanceAudioApiImpl::SetPlanarBuffer( + SourceId source_id, const int16* const* audio_buffer_ptr, + size_t num_channels, size_t num_frames) { + SetSourceBuffer<const int16* const*>(source_id, audio_buffer_ptr, + num_channels, num_frames); +} + +void ResonanceAudioApiImpl::SetSourceDistanceAttenuation( + SourceId source_id, float distance_attenuation) { + auto task = [this, source_id, distance_attenuation]() { + auto source_parameters = + system_settings_.GetSourceParametersManager()->GetMutableParameters( + source_id); + if (source_parameters != nullptr) { + const auto& rolloff_model = source_parameters->distance_rolloff_model; + DCHECK_EQ(rolloff_model, DistanceRolloffModel::kNone); + if (rolloff_model != DistanceRolloffModel::kNone) { + LOG(WARNING) << "Implicit distance rolloff model is set. The value " + "will be overwritten."; + } + source_parameters->distance_attenuation = distance_attenuation; + } + }; + task_queue_.Post(task); +} + +void ResonanceAudioApiImpl::SetSourceDistanceModel(SourceId source_id, + DistanceRolloffModel rolloff, + float min_distance, + float max_distance) { + if (max_distance < min_distance && rolloff != DistanceRolloffModel::kNone) { + LOG(WARNING) << "max_distance must be larger than min_distance"; + return; + } + auto task = [this, source_id, rolloff, min_distance, max_distance]() { + auto source_parameters = + system_settings_.GetSourceParametersManager()->GetMutableParameters( + source_id); + if (source_parameters != nullptr) { + source_parameters->distance_rolloff_model = rolloff; + source_parameters->minimum_distance = min_distance; + source_parameters->maximum_distance = max_distance; + } + }; + task_queue_.Post(task); +} + +void ResonanceAudioApiImpl::SetSourcePosition(SourceId source_id, float x, + float y, float z) { + const WorldPosition position(x, y, z); + auto task = [this, source_id, position]() { + auto source_parameters = + system_settings_.GetSourceParametersManager()->GetMutableParameters( + source_id); + if (source_parameters != nullptr) { + source_parameters->object_transform.position = position; + } + }; + task_queue_.Post(task); +} + +void ResonanceAudioApiImpl::SetSourceRoomEffectsGain(SourceId source_id, + float room_effects_gain) { + auto task = [this, source_id, room_effects_gain]() { + auto source_parameters = + system_settings_.GetSourceParametersManager()->GetMutableParameters( + source_id); + if (source_parameters != nullptr) { + source_parameters->room_effects_gain = room_effects_gain; + } + }; + task_queue_.Post(task); +} + +void ResonanceAudioApiImpl::SetSourceRotation(SourceId source_id, float x, + float y, float z, float w) { + const WorldRotation rotation(w, x, y, z); + auto task = [this, source_id, rotation]() { + auto source_parameters = + system_settings_.GetSourceParametersManager()->GetMutableParameters( + source_id); + if (source_parameters != nullptr) { + source_parameters->object_transform.rotation = rotation; + } + }; + task_queue_.Post(task); +} + +void ResonanceAudioApiImpl::SetSourceVolume(SourceId source_id, float volume) { + auto task = [this, source_id, volume]() { + auto source_parameters = + system_settings_.GetSourceParametersManager()->GetMutableParameters( + source_id); + if (source_parameters != nullptr) { + source_parameters->gain = volume; + } + }; + task_queue_.Post(task); +} + +void ResonanceAudioApiImpl::SetSoundObjectDirectivity( + SourceId sound_object_source_id, float alpha, float order) { + auto task = [this, sound_object_source_id, alpha, order]() { + auto source_parameters = + system_settings_.GetSourceParametersManager()->GetMutableParameters( + sound_object_source_id); + if (source_parameters != nullptr) { + source_parameters->directivity_alpha = alpha; + source_parameters->directivity_order = order; + } + }; + task_queue_.Post(task); +} + +void ResonanceAudioApiImpl::SetSoundObjectListenerDirectivity( + SourceId sound_object_source_id, float alpha, float order) { + auto task = [this, sound_object_source_id, alpha, order]() { + auto source_parameters = + system_settings_.GetSourceParametersManager()->GetMutableParameters( + sound_object_source_id); + if (source_parameters != nullptr) { + source_parameters->listener_directivity_alpha = alpha; + source_parameters->listener_directivity_order = order; + } + }; + task_queue_.Post(task); +} + +void ResonanceAudioApiImpl::SetSoundObjectNearFieldEffectGain( + SourceId sound_object_source_id, float gain) { + auto task = [this, sound_object_source_id, gain]() { + auto source_parameters = + system_settings_.GetSourceParametersManager()->GetMutableParameters( + sound_object_source_id); + if (source_parameters != nullptr) { + source_parameters->near_field_gain = gain; + } + }; + task_queue_.Post(task); +} + +void ResonanceAudioApiImpl::SetSoundObjectOcclusionIntensity( + SourceId sound_object_source_id, float intensity) { + auto task = [this, sound_object_source_id, intensity]() { + auto source_parameters = + system_settings_.GetSourceParametersManager()->GetMutableParameters( + sound_object_source_id); + if (source_parameters != nullptr) { + source_parameters->occlusion_intensity = intensity; + } + }; + task_queue_.Post(task); +} + +void ResonanceAudioApiImpl::SetSoundObjectSpread( + SourceId sound_object_source_id, float spread_deg) { + auto task = [this, sound_object_source_id, spread_deg]() { + auto source_parameters = + system_settings_.GetSourceParametersManager()->GetMutableParameters( + sound_object_source_id); + if (source_parameters != nullptr) { + source_parameters->spread_deg = spread_deg; + } + }; + task_queue_.Post(task); +} + +void ResonanceAudioApiImpl::EnableRoomEffects(bool enable) { + auto task = [this, enable]() { graph_manager_->EnableRoomEffects(enable); }; + task_queue_.Post(task); +} + +void ResonanceAudioApiImpl::SetReflectionProperties( + const ReflectionProperties& reflection_properties) { + auto task = [this, reflection_properties]() { + system_settings_.SetReflectionProperties(reflection_properties); + }; + task_queue_.Post(task); +} + +void ResonanceAudioApiImpl::SetReverbProperties( + const ReverbProperties& reverb_properties) { + auto task = [this, reverb_properties]() { + system_settings_.SetReverbProperties(reverb_properties); + }; + task_queue_.Post(task); +} + +const AudioBuffer* ResonanceAudioApiImpl::GetAmbisonicOutputBuffer() const { + return graph_manager_->GetAmbisonicBuffer(); +} + +const AudioBuffer* ResonanceAudioApiImpl::GetStereoOutputBuffer() const { + return graph_manager_->GetStereoBuffer(); +} + +const AudioBuffer *ResonanceAudioApiImpl::GetReverbBuffer() const { + return graph_manager_->GetReverbBuffer(); +} + +void ResonanceAudioApiImpl::ProcessNextBuffer() { +#if defined(ENABLE_TRACING) && !ION_PRODUCTION + // This enables tracing on the audio thread. + auto task = []() { ENABLE_TRACING_ON_CURRENT_THREAD("AudioThread"); }; + task_queue_.Post(task); +#endif // defined(ENABLE_TRACING) && !ION_PRODUCTION + + + task_queue_.Execute(); + + // Update room effects only if the pipeline is initialized. + if (graph_manager_->GetRoomEffectsEnabled()) { + graph_manager_->UpdateRoomReflections(); + graph_manager_->UpdateRoomReverb(); + } + // Update source attenuation parameters. + const auto process = [this](SourceParameters* parameters) { + const float master_gain = system_settings_.GetMasterGain(); + const auto& listener_position = system_settings_.GetHeadPosition(); + const auto& reflection_properties = + system_settings_.GetReflectionProperties(); + const auto& reverb_properties = system_settings_.GetReverbProperties(); + UpdateAttenuationParameters(master_gain, reflection_properties.gain, + reverb_properties.gain, listener_position, + parameters); + }; + system_settings_.GetSourceParametersManager()->ProcessAllParameters(process); + + graph_manager_->Process(); +} + +void ResonanceAudioApiImpl::SetStereoSpeakerMode(bool enabled) { + auto task = [this, enabled]() { + system_settings_.SetStereoSpeakerMode(enabled); + }; + task_queue_.Post(task); +} + +template <typename OutputType> +bool ResonanceAudioApiImpl::FillOutputBuffer(size_t num_channels, + size_t num_frames, + OutputType buffer_ptr) { + + + if (buffer_ptr == nullptr) { + LOG(WARNING) << kBadInputPointerMessage; + return false; + } + if (num_channels != kNumStereoChannels) { + LOG(WARNING) << "Output buffer must be stereo"; + return false; + } + const size_t num_input_samples = num_frames * num_channels; + const size_t num_expected_output_samples = + system_settings_.GetFramesPerBuffer() * system_settings_.GetNumChannels(); + if (num_input_samples != num_expected_output_samples) { + LOG(WARNING) << "Output buffer size must be " << num_expected_output_samples + << " samples"; + return false; + } + + // Get the processed output buffer. + ProcessNextBuffer(); + const AudioBuffer* output_buffer = GetStereoOutputBuffer(); + if (output_buffer == nullptr) { + // This indicates that the graph processing is triggered without having any + // connected sources. + return false; + } + + FillExternalBuffer(*output_buffer, buffer_ptr, num_frames, num_channels); + return true; +} + +template <typename SampleType> +void ResonanceAudioApiImpl::SetSourceBuffer(SourceId source_id, + SampleType audio_buffer_ptr, + size_t num_input_channels, + size_t num_frames) { + // Execute task queue to ensure newly created sound sources are initialized. + task_queue_.Execute(); + + if (audio_buffer_ptr == nullptr) { + LOG(WARNING) << kBadInputPointerMessage; + return; + } + if (num_frames != system_settings_.GetFramesPerBuffer()) { + LOG(WARNING) << kBufferSizeMustMatchNumFramesMessage; + return; + } + + AudioBuffer* const output_buffer = + graph_manager_->GetMutableAudioBuffer(source_id); + if (output_buffer == nullptr) { + LOG(WARNING) << "Source audio buffer not found"; + return; + } + const size_t num_output_channels = output_buffer->num_channels(); + + if (num_input_channels == num_output_channels) { + FillAudioBuffer(audio_buffer_ptr, num_frames, num_input_channels, + output_buffer); + + return; + } + + if ((num_input_channels == kNumMonoChannels) && + (num_output_channels == kNumStereoChannels)) { + FillAudioBufferWithChannelRemapping( + audio_buffer_ptr, num_frames, num_input_channels, + {0, 0} /* channel_map */, output_buffer); + return; + } + + if (num_input_channels > num_output_channels) { + std::vector<size_t> channel_map(num_output_channels); + // Fill channel map with increasing indices. + std::iota(std::begin(channel_map), std::end(channel_map), 0); + FillAudioBufferWithChannelRemapping(audio_buffer_ptr, num_frames, + num_input_channels, channel_map, + output_buffer); + return; + } + + LOG(WARNING) << "Number of input channels does not match the number of " + "output channels"; +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/resonance_audio_api_impl.h b/src/3rdparty/resonance-audio/resonance_audio/graph/resonance_audio_api_impl.h new file mode 100644 index 000000000..0adbc1c67 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/resonance_audio_api_impl.h @@ -0,0 +1,175 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_GRAPH_RESONANCE_AUDIO_API_IMPL_H_ +#define RESONANCE_AUDIO_GRAPH_RESONANCE_AUDIO_API_IMPL_H_ + +#include <atomic> +#include <memory> +#include <vector> + +#include "base/integral_types.h" +#include "api/resonance_audio_api.h" +#include "base/audio_buffer.h" +#include "graph/graph_manager.h" +#include "graph/system_settings.h" +#include "utils/lockless_task_queue.h" + +namespace vraudio { + +// Implementation of ResonanceAudioApi interface. +class ResonanceAudioApiImpl : public ResonanceAudioApi { + public: + // Constructor that initializes |ResonanceAudioApi| with system configuration. + // + // @param num_channels Number of channels of audio output. + // @param frames_per_buffer Number of frames per buffer. + // @param sample_rate_hz System sample rate. + ResonanceAudioApiImpl(size_t num_channels, size_t frames_per_buffer, + int sample_rate_hz); + + ~ResonanceAudioApiImpl() override; + + ////////////////////////////////// + // ResonanceAudioApi implementation. + ////////////////////////////////// + + // Obtain processed output buffers. + bool FillInterleavedOutputBuffer(size_t num_channels, size_t num_frames, + float* buffer_ptr) override; + bool FillInterleavedOutputBuffer(size_t num_channels, size_t num_frames, + int16* buffer_ptr) override; + bool FillPlanarOutputBuffer(size_t num_channels, size_t num_frames, + float* const* buffer_ptr) override; + bool FillPlanarOutputBuffer(size_t num_channels, size_t num_frames, + int16* const* buffer_ptr) override; + + // Listener configuration. + void SetHeadPosition(float x, float y, float z) override; + void SetHeadRotation(float x, float y, float z, float w) override; + void SetMasterVolume(float volume) override; + void SetStereoSpeakerMode(bool enabled) override; + + // Create and destroy sources. + SourceId CreateAmbisonicSource(size_t num_channels) override; + SourceId CreateStereoSource(size_t num_channels) override; + SourceId CreateSoundObjectSource(RenderingMode rendering_mode) override; + void DestroySource(SourceId source_id) override; + + // Set source data. + void SetInterleavedBuffer(SourceId source_id, const float* audio_buffer_ptr, + size_t num_channels, size_t num_frames) override; + void SetInterleavedBuffer(SourceId source_id, const int16* audio_buffer_ptr, + size_t num_channels, size_t num_frames) override; + void SetPlanarBuffer(SourceId source_id, const float* const* audio_buffer_ptr, + size_t num_channels, size_t num_frames) override; + void SetPlanarBuffer(SourceId source_id, const int16* const* audio_buffer_ptr, + size_t num_channels, size_t num_frames) override; + + // Source configuration. + void SetSourceDistanceAttenuation(SourceId source_id, + float distance_attenuation) override; + void SetSourceDistanceModel(SourceId source_id, DistanceRolloffModel rolloff, + float min_distance, float max_distance) override; + void SetSourcePosition(SourceId source_id, float x, float y, + float z) override; + void SetSourceRoomEffectsGain(SourceId source_id, + float room_effects_gain) override; + void SetSourceRotation(SourceId source_id, float x, float y, float z, + float w) override; + void SetSourceVolume(SourceId source_id, float volume) override; + + // Sound object configuration. + void SetSoundObjectDirectivity(SourceId sound_object_source_id, float alpha, + float order) override; + void SetSoundObjectListenerDirectivity(SourceId sound_object_source_id, + float alpha, float order) override; + void SetSoundObjectNearFieldEffectGain(SourceId sound_object_source_id, + float gain) override; + void SetSoundObjectOcclusionIntensity(SourceId sound_object_source_id, + float intensity) override; + void SetSoundObjectSpread(SourceId sound_object_source_id, + float spread_deg) override; + + // Room effects configuration. + void EnableRoomEffects(bool enable) override; + void SetReflectionProperties( + const ReflectionProperties& reflection_properties) override; + void SetReverbProperties(const ReverbProperties& reverb_properties) override; + + ////////////////////////////////// + // Internal API methods. + ////////////////////////////////// + + // Returns the last processed output buffer of the ambisonic mix. + // + // @return Pointer to ambisonic output buffer. + const AudioBuffer* GetAmbisonicOutputBuffer() const; + + // Returns the last processed output buffer of the stereo (binaural) mix. + // + // @return Pointer to stereo output buffer. + const AudioBuffer* GetStereoOutputBuffer() const; + + // Returns the last processed buffer containing stereo data for the room reverb + // + // @return Pointer to room reverb stereo buffer. + const AudioBuffer* GetReverbBuffer() const; + + // Triggers processing of the audio graph with the updated system properties. + void ProcessNextBuffer(); + + private: + // This method triggers the processing of the audio graph and outputs a + // binaural stereo output buffer. + // + // @tparam OutputType Output sample format, only float and int16 are + // supported. + // @param num_channels Number of channels in output buffer. + // @param num_frames Size of buffer in frames. + // @param buffer_ptr Raw pointer to audio buffer. + // @return True if a valid output was successfully rendered, false otherwise. + template <typename OutputType> + bool FillOutputBuffer(size_t num_channels, size_t num_frames, + OutputType buffer_ptr); + + // Sets the next audio buffer to a sound source. + // + // @param source_id Id of sound source. + // @param audio_buffer_ptr Pointer to planar or interleaved audio buffer. + // @param num_input_channels Number of input channels. + // @param num_frames Number of frames per channel / audio buffer. + template <typename SampleType> + void SetSourceBuffer(SourceId source_id, SampleType audio_buffer_ptr, + size_t num_input_channels, size_t num_frames); + + // Graph manager used to create and destroy sound objects. + std::unique_ptr<GraphManager> graph_manager_; + + // Manages system wide settings. + SystemSettings system_settings_; + + // Task queue to cache manipulation of all the entities in the system. All + // tasks are executed from the audio thread. + LocklessTaskQueue task_queue_; + + // Incremental source id counter. + std::atomic<int> source_id_counter_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_GRAPH_RESONANCE_AUDIO_API_IMPL_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/reverb_node.cc b/src/3rdparty/resonance-audio/resonance_audio/graph/reverb_node.cc new file mode 100644 index 000000000..6a9999881 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/reverb_node.cc @@ -0,0 +1,162 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "graph/reverb_node.h" + +#include <algorithm> +#include <cmath> + +#include "base/constants_and_types.h" +#include "base/logging.h" + + +namespace vraudio { + +namespace { + +// Default time in seconds to update the rt60s over. +const float kUpdateTimeSeconds = 1.0f; + +// Interpolates between the current and target values in steps of |update_step|, +// will set |current| to |target| when the diff between them is less than +// |update_step|. +inline void InterpolateFloatParam(float update_step, float target, + float* current) { + if (std::abs(target - *current) <= std::abs(update_step)) { + *current = target; + } else { + *current += update_step; + } +} + +} // namespace + +ReverbNode::ReverbNode(const SystemSettings& system_settings, + FftManager* fft_manager) + : system_settings_(system_settings), + rt60_band_update_steps_(kNumReverbOctaveBands, 0.0f), + gain_update_step_(0.0f), + rt60_updating_(false), + gain_updating_(false), + buffers_to_update_( + static_cast<float>(system_settings_.GetSampleRateHz()) * + kUpdateTimeSeconds / + static_cast<float>(system_settings_.GetFramesPerBuffer())), + spectral_reverb_(system_settings_.GetSampleRateHz(), + system_settings_.GetFramesPerBuffer()), + onset_compensator_(system_settings_.GetSampleRateHz(), + system_settings_.GetFramesPerBuffer(), fft_manager), + num_frames_processed_on_empty_input_(0), + reverb_length_frames_(0), + output_buffer_(kNumStereoChannels, system_settings_.GetFramesPerBuffer()), + compensator_output_buffer_(kNumStereoChannels, + system_settings_.GetFramesPerBuffer()), + silence_mono_buffer_(kNumMonoChannels, + system_settings_.GetFramesPerBuffer()) { + EnableProcessOnEmptyInput(true); + output_buffer_.Clear(); + silence_mono_buffer_.Clear(); + Update(); +} + +void ReverbNode::Update() { + new_reverb_properties_ = system_settings_.GetReverbProperties(); + + rt60_updating_ = !EqualSafe(std::begin(reverb_properties_.rt60_values), + std::end(reverb_properties_.rt60_values), + std::begin(new_reverb_properties_.rt60_values), + std::end(new_reverb_properties_.rt60_values)); + if (rt60_updating_) { + for (size_t i = 0; i < kNumReverbOctaveBands; ++i) { + rt60_band_update_steps_[i] = (new_reverb_properties_.rt60_values[i] - + reverb_properties_.rt60_values[i]) / + buffers_to_update_; + } + } + // Update the reverb gain if necessary. + gain_updating_ = reverb_properties_.gain != new_reverb_properties_.gain; + if (gain_updating_) { + gain_update_step_ = + (new_reverb_properties_.gain - reverb_properties_.gain) / + buffers_to_update_; + } +} + +const AudioBuffer* ReverbNode::GetOutputBuffer() const +{ + return &output_buffer_; +} + +const AudioBuffer* ReverbNode::AudioProcess(const NodeInput& input) { + if (rt60_updating_) { + for (size_t i = 0; i < kNumReverbOctaveBands; ++i) { + InterpolateFloatParam(rt60_band_update_steps_[i], + new_reverb_properties_.rt60_values[i], + &reverb_properties_.rt60_values[i]); + } + spectral_reverb_.SetRt60PerOctaveBand(reverb_properties_.rt60_values); + const auto max_rt_it = + std::max_element(std::begin(reverb_properties_.rt60_values), + std::end(reverb_properties_.rt60_values)); + reverb_length_frames_ = static_cast<size_t>( + *max_rt_it * static_cast<float>(system_settings_.GetSampleRateHz())); + onset_compensator_.Update(reverb_properties_.rt60_values, + reverb_properties_.gain); + // |InterpolateFloatParam| will set the two values below to be equal on + // completion of interpolation. + rt60_updating_ = !EqualSafe(std::begin(reverb_properties_.rt60_values), + std::end(reverb_properties_.rt60_values), + std::begin(new_reverb_properties_.rt60_values), + std::end(new_reverb_properties_.rt60_values)); + } + + if (gain_updating_) { + InterpolateFloatParam(gain_update_step_, new_reverb_properties_.gain, + &reverb_properties_.gain); + spectral_reverb_.SetGain(reverb_properties_.gain); + onset_compensator_.Update(reverb_properties_.rt60_values, + reverb_properties_.gain); + // |InterpolateFloatParam| will set the two values below to be equal on + // completion of interpolation. + gain_updating_ = reverb_properties_.gain != new_reverb_properties_.gain; + } + + const AudioBuffer* input_buffer = input.GetSingleInput(); + if (input_buffer == nullptr) { + // If we have no input, generate a silent input buffer until the node states + // are cleared. + if (num_frames_processed_on_empty_input_ < reverb_length_frames_) { + const size_t num_frames = system_settings_.GetFramesPerBuffer(); + num_frames_processed_on_empty_input_ += num_frames; + spectral_reverb_.Process(silence_mono_buffer_[0], &output_buffer_[0], + &output_buffer_[1]); + return &output_buffer_; + } else { + // Skip processing entirely when the states are fully cleared. + return nullptr; + } + } + DCHECK_EQ(input_buffer->num_channels(), kNumMonoChannels); + num_frames_processed_on_empty_input_ = 0; + spectral_reverb_.Process((*input_buffer)[0], &output_buffer_[0], + &output_buffer_[1]); + onset_compensator_.Process(*input_buffer, &compensator_output_buffer_); + output_buffer_[0] += compensator_output_buffer_[0]; + output_buffer_[1] += compensator_output_buffer_[1]; + return &output_buffer_; +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/reverb_node.h b/src/3rdparty/resonance-audio/resonance_audio/graph/reverb_node.h new file mode 100644 index 000000000..f9921fa66 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/reverb_node.h @@ -0,0 +1,99 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_GRAPH_REVERB_NODE_H_ +#define RESONANCE_AUDIO_GRAPH_REVERB_NODE_H_ + +#include "api/resonance_audio_api.h" +#include "base/audio_buffer.h" +#include "dsp/fft_manager.h" +#include "dsp/reverb_onset_compensator.h" +#include "dsp/spectral_reverb.h" +#include "graph/system_settings.h" +#include "node/processing_node.h" + +namespace vraudio { + +// Implements a spectral reverb producing a decorrelated stereo output with +// onset compensated by a pair of convolution filters. +class ReverbNode : public ProcessingNode { + public: + // Constructs a |ReverbNode|. + // + // @param system_settings Global system configuration. + // @param fft_manager Pointer to a manager to perform FFT transformations. + ReverbNode(const SystemSettings& system_settings, FftManager* fft_manager); + + // Updates the |SpectralReverb| using the current room properties or RT60 + // values depending on the system settings. + void Update(); + + const AudioBuffer *GetOutputBuffer() const; + + protected: + // Implements ProcessingNode. + const AudioBuffer* AudioProcess(const NodeInput& input) override; + + private: + // Global system configuration. + const SystemSettings& system_settings_; + + // Current reverb properties. + ReverbProperties reverb_properties_; + + // New reverb properties. + ReverbProperties new_reverb_properties_; + + // Per band reverb time update step sizes. + std::vector<float> rt60_band_update_steps_; + + // Update step size for the gain parameter. + float gain_update_step_; + + // Denotes whether the rt60s are currently being updated. + bool rt60_updating_; + + // Denotes whether the gain is currently being updated. + bool gain_updating_; + + // Number of buffers to updae rt60s over. + float buffers_to_update_; + + // DSP class to perform filtering associated with the reverb. + SpectralReverb spectral_reverb_; + + // DSP class to perform spectral reverb onset compensation. + ReverbOnsetCompensator onset_compensator_; + + // Number of frames of zeroed out data to be processed by the node to ensure + // the entire tail is rendered after input has ceased. + size_t num_frames_processed_on_empty_input_; + + // Longest current reverb time, across all bands, in frames. + size_t reverb_length_frames_; + + // Output buffers for mixing spectral reverb and compensator output. + AudioBuffer output_buffer_; + AudioBuffer compensator_output_buffer_; + + // Silence mono buffer to render reverb tails during the absence of input + // buffers. + AudioBuffer silence_mono_buffer_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_GRAPH_REVERB_NODE_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/source_graph_config.h b/src/3rdparty/resonance-audio/resonance_audio/graph/source_graph_config.h new file mode 100644 index 000000000..513f9b087 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/source_graph_config.h @@ -0,0 +1,43 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_GRAPH_SOURCE_GRAPH_CONFIG_H_ +#define RESONANCE_AUDIO_GRAPH_SOURCE_GRAPH_CONFIG_H_ + +#include <string> +#include <utility> +#include <vector> + +namespace vraudio { + +// Configuration of a source and the nodes it is instantiating. +struct SourceGraphConfig { + // Configuration name. + std::string configuration_name; + + // Ambisonic order to encode to/decode from source. + int ambisonic_order = 1; + + // Flag to enable HRTF-based rendering of source. + bool enable_hrtf = true; + + // Flag to enable direct rendering of source. + bool enable_direct_rendering = true; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_GRAPH_SOURCE_GRAPH_CONFIG_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/source_parameters_manager.cc b/src/3rdparty/resonance-audio/resonance_audio/graph/source_parameters_manager.cc new file mode 100644 index 000000000..edc903cc5 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/source_parameters_manager.cc @@ -0,0 +1,58 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "graph/source_parameters_manager.h" + +#include "base/logging.h" + +namespace vraudio { + +void SourceParametersManager::Register(SourceId source_id) { + DCHECK(parameters_.find(source_id) == parameters_.end()); + parameters_[source_id] = SourceParameters(); +} + +void SourceParametersManager::Unregister(SourceId source_id) { + parameters_.erase(source_id); +} + +const SourceParameters* SourceParametersManager::GetParameters( + SourceId source_id) const { + const auto source_parameters_itr = parameters_.find(source_id); + if (source_parameters_itr == parameters_.end()) { + LOG(ERROR) << "Source " << source_id << " not found"; + return nullptr; + } + return &source_parameters_itr->second; +} + +SourceParameters* SourceParametersManager::GetMutableParameters( + SourceId source_id) { + auto source_parameters_itr = parameters_.find(source_id); + if (source_parameters_itr == parameters_.end()) { + LOG(ERROR) << "Source " << source_id << " not found"; + return nullptr; + } + return &source_parameters_itr->second; +} + +void SourceParametersManager::ProcessAllParameters(const Process& process) { + for (auto& source_parameters_itr : parameters_) { + process(&source_parameters_itr.second); + } +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/source_parameters_manager.h b/src/3rdparty/resonance-audio/resonance_audio/graph/source_parameters_manager.h new file mode 100644 index 000000000..a6b394fd7 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/source_parameters_manager.h @@ -0,0 +1,68 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_GRAPH_SOURCE_PARAMETERS_MANAGER_H_ +#define RESONANCE_AUDIO_GRAPH_SOURCE_PARAMETERS_MANAGER_H_ + +#include <functional> +#include <unordered_map> + +#include "base/constants_and_types.h" +#include "base/source_parameters.h" + +namespace vraudio { + +// Class that manages the corresponding parameters of each registered source. +class SourceParametersManager { + public: + // Alias for the parameters process closure type. + using Process = std::function<void(SourceParameters*)>; + + // Registers new source parameters for given |source_id|. + // + // @param source_id Source id. + void Register(SourceId source_id); + + // Unregisters the source parameters for given |source_id|. + // + // @param source_id Source id. + void Unregister(SourceId source_id); + + // Returns read-only source parameters for given |source_id|. + // + // @param source_id Source id. + // @return Read-only source parameters, nullptr if |source_id| not found. + const SourceParameters* GetParameters(SourceId source_id) const; + + // Returns mutable source parameters for given |source_id|. + // + // @param source_id Source id. + // @return Mutable source parameters, nullptr if |source_id| not found. + SourceParameters* GetMutableParameters(SourceId source_id); + + // Executes given |process| for the parameters of each registered source. + // + // @param process Parameters processing method. + void ProcessAllParameters(const Process& process); + + private: + // Registered source parameters. + std::unordered_map<SourceId, SourceParameters> parameters_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_GRAPH_SOURCE_PARAMETERS_MANAGER_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/source_parameters_manager_test.cc b/src/3rdparty/resonance-audio/resonance_audio/graph/source_parameters_manager_test.cc new file mode 100644 index 000000000..59c69eaed --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/source_parameters_manager_test.cc @@ -0,0 +1,91 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "graph/source_parameters_manager.h" + +#include "third_party/googletest/googletest/include/gtest/gtest.h" + +namespace vraudio { + +namespace { + +// Tests that the manager registers/unregisters source parameters as expected +// for given arbitrary source ids. +TEST(SourceParametersManagerTest, RegisterUnregisterTest) { + const SourceId kSourceIds[] = {0, 1, 5, 10}; + + // Initialize a new |SourceParametersManager|. + SourceParametersManager source_parameters_manager; + for (const auto source_id : kSourceIds) { + // Verify that no parameters are registered for given |source_id|. + EXPECT_TRUE(source_parameters_manager.GetParameters(source_id) == nullptr); + // Verify that the parameters are initialized after |Register|. + source_parameters_manager.Register(source_id); + EXPECT_FALSE(source_parameters_manager.GetParameters(source_id) == nullptr); + // Verify that the parameters are destroyed after |Unregister|. + source_parameters_manager.Unregister(source_id); + EXPECT_TRUE(source_parameters_manager.GetParameters(source_id) == nullptr); + } +} + +// Tests that the manager correctly applies and returns parameter values of a +// source for a given arbitrary modifier. +TEST(SourceParametersManagerTest, ParametersAccessTest) { + const SourceId kSourceId = 1; + const float kSourceGain = 0.25f; + + // Initialize a new |SourceParametersManager| and register the source. + SourceParametersManager source_parameters_manager; + source_parameters_manager.Register(kSourceId); + // Modify the gain parameter. + auto mutable_parameters = + source_parameters_manager.GetMutableParameters(kSourceId); + EXPECT_TRUE(mutable_parameters != nullptr); + mutable_parameters->gain = kSourceGain; + // Access the parameters to verify the gain value was applied correctly. + const auto parameters = source_parameters_manager.GetParameters(kSourceId); + EXPECT_TRUE(parameters != nullptr); + EXPECT_EQ(kSourceGain, parameters->gain); +} + +// Tests that the manager correctly executes a given arbitrary call to process +// all parameters for all the sources contained within. +TEST(SourceParametersManagerTest, ProcessAllParametersTest) { + const SourceId kSourceIds[] = {0, 1, 2, 3, 4, 5}; + const float kDistanceAttenuation = 0.75f; + const auto kProcess = [kDistanceAttenuation](SourceParameters* parameters) { + parameters->distance_attenuation = kDistanceAttenuation; + }; + + // Initialize a new |SourceParametersManager| and register all the sources. + SourceParametersManager source_parameters_manager; + for (const auto source_id : kSourceIds) { + source_parameters_manager.Register(source_id); + } + // Process all parameters to apply the distance attenuation. + source_parameters_manager.ProcessAllParameters(kProcess); + // Verify that the distance attenuation value was applied correctly to all the + // sources. + for (const auto source_id : kSourceIds) { + const auto parameters = source_parameters_manager.GetParameters(source_id); + EXPECT_TRUE(parameters != nullptr); + EXPECT_EQ(kDistanceAttenuation, parameters->distance_attenuation); + } +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/stereo_mixing_panner_node.cc b/src/3rdparty/resonance-audio/resonance_audio/graph/stereo_mixing_panner_node.cc new file mode 100644 index 000000000..d919e94c1 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/stereo_mixing_panner_node.cc @@ -0,0 +1,65 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "graph/stereo_mixing_panner_node.h" + +#include "base/constants_and_types.h" +#include "base/logging.h" +#include "base/spherical_angle.h" + +#include "dsp/stereo_panner.h" + +namespace vraudio { + +StereoMixingPannerNode::StereoMixingPannerNode( + const SystemSettings& system_settings) + : system_settings_(system_settings), + gain_mixer_(kNumStereoChannels, system_settings_.GetFramesPerBuffer()), + coefficients_(kNumStereoChannels) {} + +const AudioBuffer* StereoMixingPannerNode::AudioProcess( + const NodeInput& input) { + + + const WorldPosition& listener_position = system_settings_.GetHeadPosition(); + const WorldRotation& listener_rotation = system_settings_.GetHeadRotation(); + + gain_mixer_.Reset(); + for (auto& input_buffer : input.GetInputBuffers()) { + const int source_id = input_buffer->source_id(); + const auto source_parameters = + system_settings_.GetSourceParameters(source_id); + DCHECK_NE(source_id, kInvalidSourceId); + DCHECK_EQ(input_buffer->num_channels(), 1U); + + // Compute the relative source direction in spherical angles. + const ObjectTransform& source_transform = + source_parameters->object_transform; + WorldPosition relative_direction; + GetRelativeDirection(listener_position, listener_rotation, + source_transform.position, &relative_direction); + const SphericalAngle source_direction = + SphericalAngle::FromWorldPosition(relative_direction); + + + CalculateStereoPanGains(source_direction, &coefficients_); + + gain_mixer_.AddInputChannel((*input_buffer)[0], source_id, coefficients_); + } + return gain_mixer_.GetOutput(); +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/stereo_mixing_panner_node.h b/src/3rdparty/resonance-audio/resonance_audio/graph/stereo_mixing_panner_node.h new file mode 100644 index 000000000..b465917bd --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/stereo_mixing_panner_node.h @@ -0,0 +1,61 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_GRAPH_STEREO_MIXING_PANNER_NODE_H_ +#define RESONANCE_AUDIO_GRAPH_STEREO_MIXING_PANNER_NODE_H_ + +#include <vector> + +#include "base/audio_buffer.h" +#include "dsp/gain_mixer.h" +#include "graph/system_settings.h" +#include "node/processing_node.h" + +namespace vraudio { + +// Node that accepts single mono sound object buffer as input and pans into a +// stereo panorama. +class StereoMixingPannerNode : public ProcessingNode { + public: + // Initializes StereoMixingPannerNode class. + // + // @param system_settings Global system configuration. + explicit StereoMixingPannerNode(const SystemSettings& system_settings); + + // Node implementation. + bool CleanUp() final { + CallCleanUpOnInputNodes(); + // Prevent node from being disconnected when all sources are removed. + return false; + } + + protected: + // Implements ProcessingNode. + const AudioBuffer* AudioProcess(const NodeInput& input) override; + + private: + const SystemSettings& system_settings_; + + // |GainMixer| instance. + GainMixer gain_mixer_; + + // Panning coefficients to be applied the input. + std::vector<float> coefficients_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_GRAPH_STEREO_MIXING_PANNER_NODE_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/graph/system_settings.h b/src/3rdparty/resonance-audio/resonance_audio/graph/system_settings.h new file mode 100644 index 000000000..48573be54 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/graph/system_settings.h @@ -0,0 +1,189 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_GRAPH_SYSTEM_SETTINGS_H_ +#define RESONANCE_AUDIO_GRAPH_SYSTEM_SETTINGS_H_ + +#include "api/resonance_audio_api.h" +#include "base/constants_and_types.h" +#include "base/misc_math.h" +#include "graph/source_parameters_manager.h" + +namespace vraudio { + +// Contains system-wide settings and parameters. Note that this class is not +// thread-safe. Updating system parameters must be avoided during the audio +// graph processing. +class SystemSettings { + public: + // Constructor initializes the system configuration. + // + // @param num_output_channels Number of output channels. + // @param frames_per_buffer Buffer size in frames. + // @param sample_rate_hz Sample rate. + SystemSettings(size_t num_output_channels, size_t frames_per_buffer, + int sample_rate_hz) + : sample_rate_hz_(sample_rate_hz), + frames_per_buffer_(frames_per_buffer), + num_channels_(num_output_channels), + head_rotation_(WorldRotation::Identity()), + head_position_(WorldPosition::Zero()), + master_gain_(1.0f), + stereo_speaker_mode_(false) {} + + // Sets the listener head orientation. + // + // @param head_rotation Listener head orientation. + void SetHeadRotation(const WorldRotation& head_rotation) { + head_rotation_ = head_rotation; + } + + // Sets the listener head position. + // + // @param head_position Listener head position. + void SetHeadPosition(const WorldPosition& head_position) { + head_position_ = head_position; + } + + // Sets the global stereo speaker mode flag. This flag enforces stereo panning + // and disables HRTF-based binauralization. The stereo speaker mode is + // disabled by default. + // + // @param enabled Defines the stereo speaker mode state. + void SetStereoSpeakerMode(bool enabled) { stereo_speaker_mode_ = enabled; } + + // Returns the source parameters manager. + // + // @return Mutable source parameters manager. + SourceParametersManager* GetSourceParametersManager() { + return &source_parameters_manager_; + } + + // Returns the parameters of source with given |source_id|. + // + // @param source_id Source id. + // @return Pointer to source parameters, nullptr if |source_id| not found. + const SourceParameters* GetSourceParameters(SourceId source_id) const { + return source_parameters_manager_.GetParameters(source_id); + } + + // Returns the sample rate. + // + // @return Sample rate in Hertz. + int GetSampleRateHz() const { return sample_rate_hz_; } + + // Returns the frames per buffer. + // + // @return Buffer size in frames. + size_t GetFramesPerBuffer() const { return frames_per_buffer_; } + + // Returns the number of output channels. + // + // @return Number of output channels. + size_t GetNumChannels() const { return num_channels_; } + + // Returns the head rotation. + // + // @return Head orientation. + const WorldRotation& GetHeadRotation() const { return head_rotation_; } + + // Returns the head position. + // + // @return Head position. + const WorldPosition& GetHeadPosition() const { return head_position_; } + + // Returns the stereo speaker mode state. + // + // @return Current stereo speaker mode state. + bool IsStereoSpeakerModeEnabled() const { return stereo_speaker_mode_; } + + // Sets the master gain. + // + // @param master_gain Master output gain. + void SetMasterGain(float master_gain) { master_gain_ = master_gain; } + + // Sets current reflection properties. + // + // @param reflection_properties Reflection properties. + void SetReflectionProperties( + const ReflectionProperties& reflection_properties) { + reflection_properties_ = reflection_properties; + } + + // Sets current reverb properties. + // + // @param reverb_properties Reflection properties. + void SetReverbProperties(const ReverbProperties& reverb_properties) { + reverb_properties_ = reverb_properties; + } + + // Returns the master gain. + // + // @return Master output gain. + float GetMasterGain() const { return master_gain_; } + + // Returns the current reflection properties of the environment. + // + // @return Current reflection properties. + const ReflectionProperties& GetReflectionProperties() const { + return reflection_properties_; + } + + // Returns the current reverb properties of the environment. + // + // @return Current reverb properties. + const ReverbProperties& GetReverbProperties() const { + return reverb_properties_; + } + + // Disable copy and assignment operator. Since |SystemSettings| serves as a + // global parameter storage, it should never be copied. + SystemSettings& operator=(const SystemSettings&) = delete; + SystemSettings(const SystemSettings&) = delete; + + private: + // Sampling rate. + const int sample_rate_hz_; + + // Frames per buffer. + const size_t frames_per_buffer_; + + // Number of channels per buffer. + const size_t num_channels_; + + // The most recently updated head rotation and position. + WorldRotation head_rotation_; + WorldPosition head_position_; + + // Source parameters manager. + SourceParametersManager source_parameters_manager_; + + // Master gain in amplitude. + float master_gain_; + + // Current reflection properties of the environment. + ReflectionProperties reflection_properties_; + + // Current reverb properties of the environment. + ReverbProperties reverb_properties_; + + // Defines the state of the global speaker mode. + bool stereo_speaker_mode_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_GRAPH_SYSTEM_SETTINGS_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/node/audio_nodes_test.cc b/src/3rdparty/resonance-audio/resonance_audio/node/audio_nodes_test.cc new file mode 100644 index 000000000..a623f5d1c --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/node/audio_nodes_test.cc @@ -0,0 +1,407 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include <algorithm> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/audio_buffer.h" +#include "base/logging.h" +#include "node/processing_node.h" +#include "node/sink_node.h" +#include "node/source_node.h" + +namespace vraudio { + +namespace { + +// Number of channels in test buffers. +static const size_t kNumChannels = 5; + +// Number of frames in test buffers. +static const size_t kNumFrames = 7; + +// Helper method to compare two audio buffers. +bool CompareAudioBuffer(const AudioBuffer& buffer_a, + const AudioBuffer& buffer_b) { + if (buffer_a.num_channels() != buffer_b.num_channels() || + buffer_a.num_frames() != buffer_b.num_frames()) { + return false; + } + for (size_t channel = 0; channel < buffer_a.num_channels(); ++channel) { + const AudioBuffer::Channel& channel_a = buffer_a[channel]; + const AudioBuffer::Channel& channel_b = buffer_b[channel]; + for (size_t frame = 0; frame < buffer_a.num_frames(); ++frame) { + if (channel_a[frame] != channel_b[frame]) { + return false; + } + } + } + return true; +} + +// Helper method to generate a test AudioBuffer. +std::unique_ptr<AudioBuffer> GenerateTestAudioBuffer(float factor) { + std::unique_ptr<AudioBuffer> new_buffer( + new AudioBuffer(kNumChannels, kNumFrames)); + for (size_t channel = 0; channel < kNumChannels; ++channel) { + std::fill((*new_buffer)[channel].begin(), (*new_buffer)[channel].end(), + static_cast<float>(channel) * factor); + } + return new_buffer; +} + +// Simple audio source node that generates AudioData buffers. +class MySourceNode : public SourceNode { + public: + MySourceNode(bool output_empty_buffer, bool* node_deletion_flag) + : output_empty_buffer_(output_empty_buffer), + node_deletion_flag_(node_deletion_flag), + audio_buffer_(GenerateTestAudioBuffer(1.0f)) {} + ~MySourceNode() final { + if (node_deletion_flag_ != nullptr) { + *node_deletion_flag_ = true; + } + } + + protected: + // AudioProcess methods outputs kTestAudioData data. + const AudioBuffer* AudioProcess() override { + if (output_empty_buffer_) { + return nullptr; + } + return audio_buffer_.get(); + } + + private: + const bool output_empty_buffer_; + bool* node_deletion_flag_; + + std::unique_ptr<AudioBuffer> audio_buffer_; +}; + +// Simple audio processing node that reads from a single input and passes the +// data to the output. +class MyProcessingNode : public ProcessingNode { + public: + MyProcessingNode(bool process_on_empty_input, bool* audio_process_called_flag, + bool* node_deletion_flag) + : process_on_empty_input_(process_on_empty_input), + audio_process_called_flag_(audio_process_called_flag), + node_deletion_flag_(node_deletion_flag) { + EnableProcessOnEmptyInput(process_on_empty_input_); + } + + ~MyProcessingNode() final { + if (node_deletion_flag_ != nullptr) { + *node_deletion_flag_ = true; + } + } + + protected: + const AudioBuffer* AudioProcess(const NodeInput& input) override { + if (audio_process_called_flag_ != nullptr) { + *audio_process_called_flag_ = true; + } + + if (!process_on_empty_input_) { + EXPECT_GT(input.GetInputBuffers().size(), 0U); + } else { + if (input.GetInputBuffers().empty()) { + return nullptr; + } + } + + return input.GetInputBuffers()[0]; + } + + private: + const bool process_on_empty_input_; + bool* const audio_process_called_flag_; + bool* const node_deletion_flag_; +}; + +// Simple audio mixer node that connects to multiple nodes and outputs the sum +// of all inputs. +class MyAudioMixerNode : public ProcessingNode { + public: + explicit MyAudioMixerNode(bool* node_deletion_flag) + : node_deletion_flag_(node_deletion_flag) {} + ~MyAudioMixerNode() final { + if (node_deletion_flag_ != nullptr) { + *node_deletion_flag_ = true; + } + } + + protected: + // AudioProcess performs an element-wise sum from all inputs and outputs a new + // AudioData buffer. + const AudioBuffer* AudioProcess(const NodeInput& input) override { + const auto& input_buffers = input.GetInputBuffers(); + output_data_ = *input_buffers[0]; + + // Iterate over all inputs and add its data to |output_data|. + for (size_t buffer = 1; buffer < input_buffers.size(); ++buffer) { + const AudioBuffer* accumulate_data = input_buffers[buffer]; + for (size_t channel = 0; channel < accumulate_data->num_channels(); + ++channel) { + const AudioBuffer::Channel& accumulate_channel = + (*accumulate_data)[channel]; + AudioBuffer::Channel* output_channel = &output_data_[channel]; + EXPECT_EQ(accumulate_channel.size(), output_channel->size()); + for (size_t frame = 0; frame < accumulate_channel.size(); ++frame) { + (*output_channel)[frame] += accumulate_channel[frame]; + } + } + } + return &output_data_; + } + + private: + bool* node_deletion_flag_; + AudioBuffer output_data_; +}; + +// Simple audio sink node that expects a single input. +class MySinkNode : public SinkNode { + public: + explicit MySinkNode(bool* node_deletion_flag) + : node_deletion_flag_(node_deletion_flag) {} + + ~MySinkNode() final { + if (node_deletion_flag_ != nullptr) { + *node_deletion_flag_ = true; + } + } + + private: + bool* node_deletion_flag_; +}; + +// Tests a chain of an |SinkNode|, |ProcessingNode| and +// |SourceNode|. +TEST(AudioNodesTest, SourceProcessingSinkConnectionTest) { + static const bool kOutputEmptyBuffer = false; + static const bool kEnableProcessOnEmptyInput = true; + + auto source_node = + std::make_shared<MySourceNode>(kOutputEmptyBuffer, nullptr); + auto processing_node = std::make_shared<MyProcessingNode>( + kEnableProcessOnEmptyInput, nullptr, nullptr); + auto sink_node = std::make_shared<MySinkNode>(nullptr); + + // Create chain of nodes. + sink_node->Connect(processing_node); + processing_node->Connect(source_node); + + // Test output data. + const auto& data = sink_node->ReadInputs(); + EXPECT_GT(data.size(), 0U); + EXPECT_TRUE(CompareAudioBuffer(*data[0], *GenerateTestAudioBuffer(1.0f))); +} + +// Tests a chain of an |SinkNode| and |AudioMixerNode| connected to two +// |SourceNodes|. +TEST(AudioNodesTest, MixerProcessingConnectionTest) { + static const bool kOutputEmptyBuffer = false; + auto source_node_a = + std::make_shared<MySourceNode>(kOutputEmptyBuffer, nullptr); + auto source_node_b = + std::make_shared<MySourceNode>(kOutputEmptyBuffer, nullptr); + auto mixer_node = std::make_shared<MyAudioMixerNode>(nullptr); + auto sink_node = std::make_shared<MySinkNode>(nullptr); + + // Create chain of nodes. + sink_node->Connect(mixer_node); + mixer_node->Connect(source_node_a); + mixer_node->Connect(source_node_b); + + // Test output data. + const auto& data = sink_node->ReadInputs(); + EXPECT_GT(data.size(), 0U); + EXPECT_TRUE(CompareAudioBuffer(*data[0], *GenerateTestAudioBuffer(2.0f))); +} + +// Tests if ProcessingNode::AudioProcess() calls are skipped in case of +// empty input buffers. +TEST(AudioNodesTest, SkipProcessingOnEmptyInputTest) { + static const bool kEnableProcessOnEmptyInput = true; + + bool audio_process_called = false; + + // Tests that ProcessingNode::AudioProcess() is called in case source + // nodes do generate output. + { + static const bool kOutputEmptyBuffer = false; + auto source_node_a = + std::make_shared<MySourceNode>(kOutputEmptyBuffer, nullptr); + auto source_node_b = + std::make_shared<MySourceNode>(kOutputEmptyBuffer, nullptr); + auto processing_node = std::make_shared<MyProcessingNode>( + kEnableProcessOnEmptyInput, &audio_process_called, nullptr); + auto sink_node = std::make_shared<MySinkNode>(nullptr); + + // Create chain of nodes. + sink_node->Connect(processing_node); + processing_node->Connect(source_node_a); + processing_node->Connect(source_node_b); + + EXPECT_FALSE(audio_process_called); + const auto& data = sink_node->ReadInputs(); + EXPECT_TRUE(audio_process_called); + EXPECT_GT(data.size(), 0U); + } + + audio_process_called = false; + + // Tests that ProcessingNode::AudioProcess() is *not* called in case + // source nodes do *not* generate output. + { + static const bool kOutputEmptyBuffer = true; + auto source_node_a = + std::make_shared<MySourceNode>(kOutputEmptyBuffer, nullptr); + auto source_node_b = + std::make_shared<MySourceNode>(kOutputEmptyBuffer, nullptr); + auto processing_node = std::make_shared<MyProcessingNode>( + false, &audio_process_called, nullptr); + auto sink_node = std::make_shared<MySinkNode>(nullptr); + + // Create chain of nodes. + sink_node->Connect(processing_node); + processing_node->Connect(source_node_a); + processing_node->Connect(source_node_b); + + EXPECT_FALSE(audio_process_called); + const auto& data = sink_node->ReadInputs(); + EXPECT_FALSE(audio_process_called); + EXPECT_EQ(data.size(), 0U); + } +} + +// Tests a chain of an |SinkNode|, |ProcessingNode| and +// |SourceNode| and runs the node clean-up procedure with sources being +// *not* marked with end-of-stream. +TEST(AudioNodesTest, NodeCleanUpWithoutMarkEndOfStreamCallTest) { + static const bool kEnableProcessOnEmptyInput = true; + + bool source_node_deleted = false; + bool processing_node_deleted = false; + bool sink_node_deleted = false; + + auto sink_node = std::make_shared<MySinkNode>(&sink_node_deleted); + + { + // Create a source and processing node and connect it to sink node. + auto source_node = std::make_shared<MySourceNode>( + /*output_empty_buffer=*/false, &source_node_deleted); + auto processing_node = std::make_shared<MyProcessingNode>( + kEnableProcessOnEmptyInput, nullptr, &processing_node_deleted); + + // Connect nodes. + sink_node->Connect(processing_node); + processing_node->Connect(source_node); + + // End-of-stream is not marked in source node. + // source_node->MarkEndOfStream(); + } + + EXPECT_FALSE(source_node_deleted); + EXPECT_FALSE(processing_node_deleted); + EXPECT_FALSE(sink_node_deleted); + + sink_node->CleanUp(); + + EXPECT_FALSE(source_node_deleted); + EXPECT_FALSE(processing_node_deleted); + EXPECT_FALSE(sink_node_deleted); +} + +// Tests a chain of an SinkNode, ProcessingNode and SourceNode +// and runs the node clean-up procedure with sources being marked with +// end-of-stream. +TEST(AudioNodesTest, NodeCleanUpTest) { + static const bool kEnableProcessOnEmptyInput = true; + + bool source_node_deleted = false; + bool processing_node_deleted = false; + bool sink_node_deleted = false; + + auto sink_node = std::make_shared<MySinkNode>(&sink_node_deleted); + + { + // Create a source and processing node and connect it to sink node. + auto source_node = std::make_shared<MySourceNode>( + /*output_empty_buffer=*/false, &source_node_deleted); + auto processing_node = std::make_shared<MyProcessingNode>( + kEnableProcessOnEmptyInput, nullptr, &processing_node_deleted); + + // Connect nodes. + sink_node->Connect(processing_node); + processing_node->Connect(source_node); + + // End of stream is marked in source node. Do not expect any data anymore. + source_node->MarkEndOfStream(); + } + + EXPECT_FALSE(source_node_deleted); + EXPECT_FALSE(processing_node_deleted); + EXPECT_FALSE(sink_node_deleted); + + sink_node->CleanUp(); + + EXPECT_TRUE(source_node_deleted); + EXPECT_TRUE(processing_node_deleted); + EXPECT_FALSE(sink_node_deleted); +} + +// Tests ProcessingNode::EnableProcessOnEmptyInput(). +TEST(AudioNodesTest, ProcessOnEmptyInputFlagTest) { + bool audio_process_called = false; + + static const bool kEnableProcessOnEmptyInput = true; + static const bool kOutputEmptyBuffer = true; + + auto source_node = + std::make_shared<MySourceNode>(kOutputEmptyBuffer, nullptr); + auto processing_node = std::make_shared<MyProcessingNode>( + kEnableProcessOnEmptyInput, &audio_process_called, nullptr); + auto sink_node = std::make_shared<MySinkNode>(nullptr); + + // Create chain of nodes. + sink_node->Connect(processing_node); + processing_node->Connect(source_node); + + EXPECT_FALSE(audio_process_called); + const auto& data = sink_node->ReadInputs(); + EXPECT_TRUE(audio_process_called); + EXPECT_EQ(data.size(), 0U); +} + +// Tests a chain of an |SinkNode| and |AudioMixerNode| without a |SourceNode|. +TEST(AudioNodesTest, MissingSourceConnectionTest) { + auto mixer_node = std::make_shared<MyAudioMixerNode>(nullptr); + auto sink_node = std::make_shared<MySinkNode>(nullptr); + + // Create chain of nodes. + sink_node->Connect(mixer_node); + + // Test output data. + const auto& data = sink_node->ReadInputs(); + EXPECT_EQ(data.size(), 0U); +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/node/node.h b/src/3rdparty/resonance-audio/resonance_audio/node/node.h new file mode 100644 index 000000000..e6e10e39d --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/node/node.h @@ -0,0 +1,258 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_NODE_NODE_H_ +#define RESONANCE_AUDIO_NODE_NODE_H_ + +#include <memory> +#include <set> +#include <unordered_map> +#include <utility> +#include <vector> + +#include "base/logging.h" + +namespace vraudio { + +// Implements a processing node in a synchronous processing graph. +// This processing graph is expected to be directed, acyclic, and +// accessed from a single thread. +// +// Subclasses are expected to implement Process(), which will read +// from all of the instance's inputs, process the data as necessary, +// and then write to all of its outputs. +// +// Data is passed through unique_ptrs, so nodes are expected to +// modify data in place whenever it suits their purposes. If an +// outputs is connected to more than one input, copies will be made for +// each input. +// +// Graphs are managed through shared_ptrs. Orphaned nodes are kept +// alive as long as they output to a living input. Ownership is +// unidirectional -- from input to output -- in order to avoid +// circular dependencies. +class Node : public std::enable_shared_from_this<Node> { + public: + virtual ~Node() {} + virtual void Process() = 0; + + // Disconnects from input nodes that are marked to be at the end of data + // stream. + // + // @return True if node is does not have any inputs and can be removed, false + // otherwise. + virtual bool CleanUp() = 0; + + template <class T> + class Output; + + // An endpoint for a node, this object consumes data from any connected + // outputs. Because an input may be connected to more than one output, it + // returns a vector of read data. All outputs must be of the same type. + template <typename T> + class Input { + public: + // Unordered map that stores pairs of input Node instances and their + // |Output| member. + typedef std::unordered_map<Output<T>*, std::shared_ptr<Node>> OutputNodeMap; + + Input() {} + ~Input(); + + // Returns a vector of computed data, one for each connected output. + const std::vector<T>& Read(); + + // Connects this input to the specified output. + // + // @param node The parent of the output. + // @param output The output to connect to. + void Connect(const std::shared_ptr<Node>& node, Output<T>* output); + + // Disconnects this input from the specified output. + // + // @param output The output to be disconnected. + void Disconnect(Output<T>* output); + + // Returns the number of connected outputs. + // + // @return Number of connected outputs. + size_t GetNumConnections() const; + + // Returns reference to OutputNodeMap map to obtain all connected nodes and + // their outputs. + const OutputNodeMap& GetConnectedNodeOutputPairs(); + + // Disable copy constructor. + Input(const Input& that) = delete; + + private: + friend class Node::Output<T>; + + void AddOutput(const std::shared_ptr<Node>& node, Output<T>* output); + void RemoveOutput(Output<T>* output); + + OutputNodeMap outputs_; + std::vector<T> read_data_; + }; + + // An endpoint for a node, this object produces data for any connected inputs. + // Because an output may have more than one input, this object will duplicate + // any computed data, once for each connected input. All inputs must be of the + // same type. + // + // If an output does not have any data to deliver, it will ask its parent node + // to process more data. It is assumed that after processing, some new data + // will be written to this output. + template <typename T> + class Output { + public: + explicit Output(Node* node) : parent_(node) {} + + // Parent nodes should call this function to push new data to any connected + // inputs. This data will be copied once for each connected input. + // + // @param data New data to pass to all connected inputs. + void Write(T data); + + // Disable copy constructor. + Output(const Output& that) = delete; + + private: + friend class Node::Input<T>; + + // Signature of copy operator. + typedef T (*CopyOperator)(const T&); + + // Returns a single piece of stored processed data. If no data exists, + // the parent node is processed to produce more data. + T PullData(); + + void AddInput(Input<T>* input); + bool RemoveInput(Input<T>* input); + + std::set<Input<T>*> inputs_; + std::vector<T> written_data_; + Node* parent_; + }; +}; + +template <class T> +Node::Input<T>::~Input() { + for (auto& o : outputs_) { + CHECK(o.first->RemoveInput(this)); + } +} + +template <class T> +const std::vector<T>& Node::Input<T>::Read() { + read_data_.clear(); + + for (auto& o : outputs_) { + // Obtain processed data. + T processed_data = o.first->PullData(); + if (processed_data != nullptr) { + read_data_.emplace_back(std::move(processed_data)); + } + } + + return read_data_; +} + +template <class T> +void Node::Input<T>::Connect(const std::shared_ptr<Node>& node, + Output<T>* output) { + output->AddInput(this); + AddOutput(node, output); +} + +// RemoveOutput(output) may trigger *output be destructed, +// so we need to call output->RemoveInput(this) first. +template <class T> +void Node::Input<T>::Disconnect(Output<T>* output) { + output->RemoveInput(this); + RemoveOutput(output); +} + +template <class T> +size_t Node::Input<T>::GetNumConnections() const { + return outputs_.size(); +} + +template <class T> +const typename Node::Input<T>::OutputNodeMap& +Node::Input<T>::GetConnectedNodeOutputPairs() { + return outputs_; +} + +template <class T> +void Node::Input<T>::AddOutput(const std::shared_ptr<Node>& node, + Output<T>* output) { + outputs_[output] = node; + + DCHECK(outputs_.find(output) != outputs_.end()); +} + +template <class T> +void Node::Input<T>::RemoveOutput(Output<T>* output) { + outputs_.erase(output); +} + +template <class T> +T Node::Output<T>::PullData() { + if (written_data_.empty()) { + parent_->Process(); + } + + DCHECK(!written_data_.empty()); + + T return_value = std::move(written_data_.back()); + written_data_.pop_back(); + return return_value; +} + +template <class T> +void Node::Output<T>::Write(T data) { + DCHECK(written_data_.empty()); + written_data_.clear(); + written_data_.emplace_back(std::move(data)); + + // If we have more than one connected input, copy the data for each input. + for (size_t i = 1; i < inputs_.size(); i++) { + written_data_.push_back(written_data_[0]); + } + + DCHECK(written_data_.size() == inputs_.size()); +} + +template <class T> +void Node::Output<T>::AddInput(Input<T>* input) { + inputs_.insert(input); +} + +template <class T> +bool Node::Output<T>::RemoveInput(Input<T>* input) { + auto it = inputs_.find(input); + if (it == inputs_.end()) { + return false; + } + + inputs_.erase(it); + return true; +} + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_NODE_NODE_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/node/node_test.cc b/src/3rdparty/resonance-audio/resonance_audio/node/node_test.cc new file mode 100644 index 000000000..e95a68a1c --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/node/node_test.cc @@ -0,0 +1,330 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "node/node.h" + +#include "third_party/googletest/googletest/include/gtest/gtest.h" + +namespace vraudio { + +namespace { + +class SourceNode : public Node { + public: + explicit SourceNode(bool output_nullptr) + : output_nullptr_(output_nullptr), output_(this), next_value_(0) {} + + void Process() final { + if (output_nullptr_) { + // Output nullptr. + output_.Write(nullptr); + } else { + output_.Write(&next_value_); + next_value_++; + } + } + + bool CleanUp() final { return false; } + + const bool output_nullptr_; + Node::Output<int*> output_; + + private: + int next_value_; +}; + +class PassThrough : public Node { + public: + PassThrough() : output_(this) {} + + void Process() final { output_.Write(input_.Read()[0]); } + + bool CleanUp() final { return false; } + + Node::Input<int*> input_; + Node::Output<int*> output_; +}; + +class IncNode : public Node { + public: + IncNode() : output_(this), inc_value_(0) {} + + void Process() final { + inc_value_ = *input_.Read()[0]; + ++inc_value_; + output_.Write(&inc_value_); + } + + bool CleanUp() final { return false; } + + Node::Input<int*> input_; + Node::Output<int*> output_; + + private: + int inc_value_; +}; + +class SinkNode : public Node { + public: + void Process() final {} + bool CleanUp() final { return false; } + + Node::Input<int*> input_; +}; + +// Class for testing multiple subscriber streams. +class NodeTest : public ::testing::Test { + public: + void SetUp() override {} +}; + +TEST_F(NodeTest, SourceSink) { + static const bool kOutputNullptr = false; + auto source_node = std::make_shared<SourceNode>(kOutputNullptr); + auto sink_node = std::make_shared<SinkNode>(); + sink_node->input_.Connect(source_node, &source_node->output_); + + auto& data0 = sink_node->input_.Read(); + EXPECT_EQ(data0.size(), 1U); + EXPECT_EQ(*data0[0], 1); + + auto& data1 = sink_node->input_.Read(); + EXPECT_EQ(data1.size(), 1U); + EXPECT_EQ(*data1[0], 2); +} + +TEST_F(NodeTest, SourcePassThroughSink) { + static const bool kOutputNullptr = false; + auto source_node = std::make_shared<SourceNode>(kOutputNullptr); + auto copy_node = std::make_shared<PassThrough>(); + auto sink_node = std::make_shared<SinkNode>(); + sink_node->input_.Connect(copy_node, ©_node->output_); + copy_node->input_.Connect(source_node, &source_node->output_); + + auto& data0 = sink_node->input_.Read(); + EXPECT_EQ(data0.size(), 1U); + EXPECT_EQ(*data0[0], 1); + + auto& data1 = sink_node->input_.Read(); + EXPECT_EQ(data1.size(), 1U); + EXPECT_EQ(*data1[0], 2); +} + +TEST_F(NodeTest, TwoSources) { + static const bool kOutputNullptr = false; + auto source_node_a = std::make_shared<SourceNode>(kOutputNullptr); + auto source_node_b = std::make_shared<SourceNode>(kOutputNullptr); + auto sink_node = std::make_shared<SinkNode>(); + + sink_node->input_.Connect(source_node_a, &source_node_a->output_); + sink_node->input_.Connect(source_node_b, &source_node_b->output_); + EXPECT_EQ(source_node_a.use_count(), 2); + EXPECT_EQ(source_node_b.use_count(), 2); + EXPECT_EQ(sink_node.use_count(), 1); + + auto& data0 = sink_node->input_.Read(); + EXPECT_EQ(data0.size(), 2U); + EXPECT_EQ(*data0[0], 1); + EXPECT_EQ(*data0[1], 1); + + auto& data1 = sink_node->input_.Read(); + EXPECT_EQ(data1.size(), 2U); + EXPECT_EQ(*data1[0], 2); + EXPECT_EQ(*data1[1], 2); + + sink_node.reset(); + EXPECT_EQ(source_node_a.use_count(), 1); + EXPECT_EQ(source_node_b.use_count(), 1); +} + +TEST_F(NodeTest, DoubleSink) { + static const bool kOutputNullptr = false; + auto source_node = std::make_shared<SourceNode>(kOutputNullptr); + auto sink_node_a = std::make_shared<SinkNode>(); + auto sink_node_b = std::make_shared<SinkNode>(); + + sink_node_a->input_.Connect(source_node, &source_node->output_); + sink_node_b->input_.Connect(source_node, &source_node->output_); + EXPECT_EQ(sink_node_a.use_count(), 1); + EXPECT_EQ(sink_node_b.use_count(), 1); + EXPECT_EQ(source_node.use_count(), 3); + + auto& dataA0 = sink_node_a->input_.Read(); + auto& dataB0 = sink_node_b->input_.Read(); + EXPECT_EQ(dataA0.size(), 1U); + EXPECT_EQ(dataB0.size(), 1U); + EXPECT_EQ(*dataA0[0], 1); + EXPECT_EQ(*dataB0[0], 1); + + auto& dataA1 = sink_node_a->input_.Read(); + auto& dataB1 = sink_node_b->input_.Read(); + EXPECT_EQ(dataA1.size(), 1U); + EXPECT_EQ(dataB1.size(), 1U); + EXPECT_EQ(*dataA1[0], 2); + EXPECT_EQ(*dataB1[0], 2); + + sink_node_a.reset(); + EXPECT_EQ(source_node.use_count(), 2); + sink_node_b.reset(); + EXPECT_EQ(source_node.use_count(), 1); +} + +TEST_F(NodeTest, DoubleSinkWithIncrement) { + static const bool kOutputNullptr = false; + auto source_node = std::make_shared<SourceNode>(kOutputNullptr); + auto inc_node = std::make_shared<IncNode>(); + auto sink_node_a = std::make_shared<SinkNode>(); + auto sink_node_b = std::make_shared<SinkNode>(); + + sink_node_a->input_.Connect(source_node, &source_node->output_); + sink_node_b->input_.Connect(inc_node, &inc_node->output_); + inc_node->input_.Connect(source_node, &source_node->output_); + + auto& dataA0 = sink_node_a->input_.Read(); + auto& dataB0 = sink_node_b->input_.Read(); + EXPECT_EQ(dataA0.size(), 1U); + EXPECT_EQ(dataB0.size(), 1U); + EXPECT_EQ(*dataA0[0], 1); + EXPECT_EQ(*dataB0[0], 2); + + auto& dataA1 = sink_node_a->input_.Read(); + auto& dataB1 = sink_node_b->input_.Read(); + EXPECT_EQ(dataA1.size(), 1U); + EXPECT_EQ(dataB1.size(), 1U); + EXPECT_EQ(*dataA1[0], 2); + EXPECT_EQ(*dataB1[0], 3); +} + +TEST_F(NodeTest, DisconnectSingleLink) { + static const bool kOutputNullptr = false; + auto source_node = std::make_shared<SourceNode>(kOutputNullptr); + auto sink_node = std::make_shared<SinkNode>(); + + sink_node->input_.Connect(source_node, &source_node->output_); + EXPECT_EQ(sink_node.use_count(), 1); + EXPECT_EQ(source_node.use_count(), 2); + + auto& data0 = sink_node->input_.Read(); + EXPECT_EQ(data0.size(), 1U); + EXPECT_EQ(*data0[0], 1); + + sink_node->input_.Disconnect(&source_node->output_); + EXPECT_EQ(sink_node.use_count(), 1); + EXPECT_EQ(source_node.use_count(), 1); + + auto& data1 = sink_node->input_.Read(); + EXPECT_EQ(data1.size(), 0U); +} + +TEST_F(NodeTest, DisconnectIntermediate) { + static const bool kOutputNullptr = false; + auto source_node = std::make_shared<SourceNode>(kOutputNullptr); + auto inc_node = std::make_shared<IncNode>(); + auto sink_node = std::make_shared<SinkNode>(); + + sink_node->input_.Connect(inc_node, &inc_node->output_); + inc_node->input_.Connect(source_node, &source_node->output_); + + inc_node->input_.Disconnect(&source_node->output_); + inc_node.reset(); + EXPECT_EQ(sink_node.use_count(), 1); + EXPECT_EQ(source_node.use_count(), 1); +} + +TEST_F(NodeTest, DisconnectMultiLink) { + static const bool kOutputNullptr = false; + auto source_node = std::make_shared<SourceNode>(kOutputNullptr); + auto inc_node = std::make_shared<IncNode>(); + auto sink_node_a = std::make_shared<SinkNode>(); + auto sink_node_b = std::make_shared<SinkNode>(); + auto sink_node_c = std::make_shared<SinkNode>(); + + sink_node_a->input_.Connect(source_node, &source_node->output_); + sink_node_b->input_.Connect(inc_node, &inc_node->output_); + sink_node_c->input_.Connect(inc_node, &inc_node->output_); + inc_node->input_.Connect(source_node, &source_node->output_); + + sink_node_a->input_.Disconnect(&source_node->output_); + EXPECT_EQ(sink_node_a.use_count(), 1); + EXPECT_EQ(sink_node_b.use_count(), 1); + EXPECT_EQ(sink_node_c.use_count(), 1); + EXPECT_EQ(inc_node.use_count(), 3); + EXPECT_EQ(source_node.use_count(), 2); + + auto& dataA0 = sink_node_a->input_.Read(); + auto& dataB0 = sink_node_b->input_.Read(); + auto& dataC0 = sink_node_c->input_.Read(); + EXPECT_EQ(dataA0.size(), 0U); + EXPECT_EQ(dataB0.size(), 1U); + EXPECT_EQ(*dataB0[0], 2); + EXPECT_EQ(dataC0.size(), 1U); + EXPECT_EQ(*dataC0[0], 2); + + sink_node_b->input_.Disconnect(&inc_node->output_); + EXPECT_EQ(sink_node_a.use_count(), 1); + EXPECT_EQ(sink_node_b.use_count(), 1); + EXPECT_EQ(sink_node_c.use_count(), 1); + EXPECT_EQ(inc_node.use_count(), 2); + EXPECT_EQ(source_node.use_count(), 2); + + auto& dataA1 = sink_node_a->input_.Read(); + auto& dataB1 = sink_node_b->input_.Read(); + auto& dataC1 = sink_node_c->input_.Read(); + EXPECT_EQ(dataA1.size(), 0U); + EXPECT_EQ(dataB1.size(), 0U); + EXPECT_EQ(dataC1.size(), 1U); + EXPECT_EQ(*dataC1[0], 3); + + sink_node_c->input_.Disconnect(&inc_node->output_); + EXPECT_EQ(sink_node_a.use_count(), 1); + EXPECT_EQ(sink_node_b.use_count(), 1); + EXPECT_EQ(sink_node_c.use_count(), 1); + EXPECT_EQ(inc_node.use_count(), 1); + EXPECT_EQ(source_node.use_count(), 2); + + auto& dataA2 = sink_node_a->input_.Read(); + auto& dataB2 = sink_node_b->input_.Read(); + auto& dataC2 = sink_node_c->input_.Read(); + EXPECT_EQ(dataA2.size(), 0U); + EXPECT_EQ(dataB2.size(), 0U); + EXPECT_EQ(dataC2.size(), 0U); + + inc_node->input_.Disconnect(&source_node->output_); + EXPECT_EQ(sink_node_a.use_count(), 1); + EXPECT_EQ(sink_node_b.use_count(), 1); + EXPECT_EQ(inc_node.use_count(), 1); + EXPECT_EQ(source_node.use_count(), 1); +} + +TEST_F(NodeTest, NullPtrSourceMultipleClients) { + static const bool kOutputNullptr = true; + auto source_node = std::make_shared<SourceNode>(kOutputNullptr); + auto sink_node_a = std::make_shared<SinkNode>(); + auto sink_node_b = std::make_shared<SinkNode>(); + + sink_node_a->input_.Connect(source_node, &source_node->output_); + sink_node_b->input_.Connect(source_node, &source_node->output_); + + auto& dataA = sink_node_a->input_.Read(); + auto& dataB = sink_node_b->input_.Read(); + + EXPECT_TRUE(dataA.empty()); + EXPECT_TRUE(dataB.empty()); +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/node/processing_node.cc b/src/3rdparty/resonance-audio/resonance_audio/node/processing_node.cc new file mode 100644 index 000000000..3053e8e6b --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/node/processing_node.cc @@ -0,0 +1,89 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "node/processing_node.h" + +namespace vraudio { + +ProcessingNode::NodeInput::NodeInput( + const std::vector<const AudioBuffer*>& input_vector) + : input_vector_(input_vector) {} + +const AudioBuffer* ProcessingNode::NodeInput::GetSingleInput() const { + if (input_vector_.size() == 1) { + return input_vector_[0]; + } + if (input_vector_.size() > 1) { + LOG(WARNING) << "GetSingleInput() called on multi buffer input"; + } + return nullptr; +} + +const std::vector<const AudioBuffer*>& +ProcessingNode::NodeInput::GetInputBuffers() const { + return input_vector_; +} + +ProcessingNode::ProcessingNode() + : Node(), output_stream_(this), process_on_no_input_(false) {} + +void ProcessingNode::Connect( + const std::shared_ptr<PublisherNodeType>& publisher_node) { + input_stream_.Connect(publisher_node->GetSharedNodePtr(), + publisher_node->GetOutput()); +} + +void ProcessingNode::Process() { + NodeInput input(input_stream_.Read()); + const AudioBuffer* output = nullptr; + // Only call AudioProcess if input data is available. + if (process_on_no_input_ || !input.GetInputBuffers().empty()) { + output = AudioProcess(input); + } + output_stream_.Write(output); +} + +bool ProcessingNode::CleanUp() { + CallCleanUpOnInputNodes(); + return (input_stream_.GetNumConnections() == 0); +} + +void ProcessingNode::EnableProcessOnEmptyInput(bool enable) { + process_on_no_input_ = enable; +} + +void ProcessingNode::CallCleanUpOnInputNodes() { + // We need to make a copy of the OutputNodeMap map since it changes due to + // Disconnect() calls. + const auto connected_nodes = input_stream_.GetConnectedNodeOutputPairs(); + for (const auto& input_node : connected_nodes) { + Output<const AudioBuffer*>* output = input_node.first; + std::shared_ptr<Node> node = input_node.second; + const bool is_ready_to_be_disconnected = node->CleanUp(); + if (is_ready_to_be_disconnected) { + input_stream_.Disconnect(output); + } + } +} + +std::shared_ptr<Node> ProcessingNode::GetSharedNodePtr() { + return shared_from_this(); +} +Node::Output<const AudioBuffer*>* ProcessingNode::GetOutput() { + return &output_stream_; +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/node/processing_node.h b/src/3rdparty/resonance-audio/resonance_audio/node/processing_node.h new file mode 100644 index 000000000..6142c794c --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/node/processing_node.h @@ -0,0 +1,115 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_NODE_PROCESSING_NODE_H_ +#define RESONANCE_AUDIO_NODE_PROCESSING_NODE_H_ + +#include <memory> +#include <vector> + +#include "base/audio_buffer.h" +#include "node/publisher_node.h" +#include "node/subscriber_node.h" + +namespace vraudio { + +// Audio processing node that reads from multiple inputs, processes the +// received data and outputs its result. +class ProcessingNode : public Node, + public SubscriberNode<const AudioBuffer*>, + public PublisherNode<const AudioBuffer*> { + public: + typedef SubscriberNode<const AudioBuffer*> SubscriberNodeType; + typedef PublisherNode<const AudioBuffer*> PublisherNodeType; + + // Helper class to manage incoming |AudioBuffer|s. + class NodeInput { + public: + // Constructor. + // + // @param input_vector Vector containing pointers to incoming + // |AudioBuffer|s. + explicit NodeInput(const std::vector<const AudioBuffer*>& input_vector); + + // Returns a nullptr if zero or more than one input buffers are available. + // Otherwise a pointer to the single input |AudioBuffer| is returned. This + // method should be used if only a single input |AudioBuffer| is expected. + // + // @return Pointer to single input |AudioBuffer|. + const AudioBuffer* GetSingleInput() const; + + // Returns vector with input |AudioBuffer|s. + // + // @return Pointer to single input |AudioBuffer|. + const std::vector<const AudioBuffer*>& GetInputBuffers() const; + + // Delete copy constructor. + NodeInput(const NodeInput& that) = delete; + + private: + // Const reference to vector of input |AudioBuffer|s. + const std::vector<const AudioBuffer*>& input_vector_; + }; + + ProcessingNode(); + + // SubscriberNode<InputType> implementation. + void Connect( + const std::shared_ptr<PublisherNodeType>& publisher_node) override; + + // Node implementation. + void Process() final; + bool CleanUp() override; + + // By default, calls to AudioProcess() are skipped in case of empty input + // buffers. This enables this node to process audio buffers in the absence of + // input data (which is needed for instance for a reverberation effect). + void EnableProcessOnEmptyInput(bool enable); + + // Disable copy constructor. + ProcessingNode(const ProcessingNode& that) = delete; + + protected: + // Calls |CleanUp| on all connected input nodes. + void CallCleanUpOnInputNodes(); + + // Pure virtual method to implement the audio processing method. This method + // receives a vector of all input arguments to be processed and requires to + // output a single output buffer. + // + // @param input Input instance to receive pointers to input |AudioBuffer|s. + // @return Returns output data. + virtual const AudioBuffer* AudioProcess(const NodeInput& input) = 0; + + private: + // PublisherNode<OutputType> implementation. + std::shared_ptr<Node> GetSharedNodePtr() final; + Node::Output<const AudioBuffer*>* GetOutput() final; + + // Input stream to poll for incoming data. + Node::Input<const AudioBuffer*> input_stream_; + + // Output stream to write processed data to. + Node::Output<const AudioBuffer*> output_stream_; + + // Flag that indicates if |AudioProcess| should be called in case no input + // data is available. + bool process_on_no_input_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_NODE_PROCESSING_NODE_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/node/publisher_node.h b/src/3rdparty/resonance-audio/resonance_audio/node/publisher_node.h new file mode 100644 index 000000000..a6935da75 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/node/publisher_node.h @@ -0,0 +1,51 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_NODE_PUBLISHER_NODE_H_ +#define RESONANCE_AUDIO_NODE_PUBLISHER_NODE_H_ + +#include <memory> + +#include "base/logging.h" +#include "node/node.h" + +namespace vraudio { + +// Interface for publisher nodes that declares helper methods required to +// connect to a publisher node. All publishing nodes need to implement this +// interface. +// +// @tparam OutputType Type of the output container being streamed. +// @interface +template <typename OutputType> +class PublisherNode { + public: + virtual ~PublisherNode() {} + + // Creates a shared pointer of the Node instance. + // + // @return Returns a shared pointer the of Node instance. + virtual std::shared_ptr<Node> GetSharedNodePtr() = 0; + + // Get internal Node::Output instance. + // + // @return Returns a pointer to the internal Node::Output instance. + virtual Node::Output<OutputType>* GetOutput() = 0; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_NODE_PUBLISHER_NODE_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/node/sink_node.cc b/src/3rdparty/resonance-audio/resonance_audio/node/sink_node.cc new file mode 100644 index 000000000..2d003009c --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/node/sink_node.cc @@ -0,0 +1,54 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "node/sink_node.h" + +#include "base/logging.h" + +namespace vraudio { + +SinkNode::SinkNode() : Node() {} + +const std::vector<const AudioBuffer*>& SinkNode::ReadInputs() { + return input_stream_.Read(); +} + +void SinkNode::Connect( + const std::shared_ptr<PublisherNodeType>& publisher_node) { + input_stream_.Connect(publisher_node->GetSharedNodePtr(), + publisher_node->GetOutput()); +} + +void SinkNode::Process() { + LOG(FATAL) << "Process should not be called on audio sink node."; +} + +bool SinkNode::CleanUp() { + // We need to make a copy of the OutputNodeMap map since it might change due + // to Disconnect() calls. + const auto connected_nodes = input_stream_.GetConnectedNodeOutputPairs(); + for (const auto& input_node : connected_nodes) { + Output<const AudioBuffer*>* output = input_node.first; + std::shared_ptr<Node> node = input_node.second; + const bool is_orphaned = node->CleanUp(); + if (is_orphaned) { + input_stream_.Disconnect(output); + } + } + return false; +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/node/sink_node.h b/src/3rdparty/resonance-audio/resonance_audio/node/sink_node.h new file mode 100644 index 000000000..4bb3ade06 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/node/sink_node.h @@ -0,0 +1,59 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_NODE_SINK_NODE_H_ +#define RESONANCE_AUDIO_NODE_SINK_NODE_H_ + +#include <memory> +#include <vector> + +#include "base/audio_buffer.h" +#include "node/publisher_node.h" +#include "node/subscriber_node.h" + +namespace vraudio { + +// Audio sink node that reads from multiple inputs. +class SinkNode : public Node, public SubscriberNode<const AudioBuffer*> { + public: + typedef PublisherNode<const AudioBuffer*> PublisherNodeType; + + SinkNode(); + + // Polls for data on all inputs of the sink node. + // + // @return A vector of input data. + const std::vector<const AudioBuffer*>& ReadInputs(); + + // SubscriberNode<AudioBuffer> implementation. + void Connect( + const std::shared_ptr<PublisherNodeType>& publisher_node) override; + + // Node implementation. + void Process() final; + bool CleanUp() final; + + // Disable copy constructor. + SinkNode(const SinkNode& that) = delete; + + private: + // Input stream to poll for incoming data. + Node::Input<const AudioBuffer*> input_stream_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_NODE_SINK_NODE_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/node/source_node.cc b/src/3rdparty/resonance-audio/resonance_audio/node/source_node.cc new file mode 100644 index 000000000..8567df6c6 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/node/source_node.cc @@ -0,0 +1,41 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "node/source_node.h" + +namespace vraudio { + +SourceNode::SourceNode() + : Node(), output_stream_(this), end_of_stream_(false) {} + +void SourceNode::Process() { + const AudioBuffer* output = AudioProcess(); + output_stream_.Write(output); +} + +bool SourceNode::CleanUp() { return end_of_stream_; } + +std::shared_ptr<Node> SourceNode::GetSharedNodePtr() { + return shared_from_this(); +} + +Node::Output<const AudioBuffer*>* SourceNode::GetOutput() { + return &output_stream_; +} + +void SourceNode::MarkEndOfStream() { end_of_stream_ = true; } + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/node/source_node.h b/src/3rdparty/resonance-audio/resonance_audio/node/source_node.h new file mode 100644 index 000000000..73a598637 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/node/source_node.h @@ -0,0 +1,68 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_NODE_SOURCE_NODE_H_ +#define RESONANCE_AUDIO_NODE_SOURCE_NODE_H_ + +#include <atomic> +#include <memory> + +#include "base/audio_buffer.h" +#include "node/publisher_node.h" + +namespace vraudio { + +// Audio source node that outputs data via the AudioProcess() method. +class SourceNode : public Node, public PublisherNode<const AudioBuffer*> { + public: + typedef PublisherNode<const AudioBuffer*> PublisherNodeType; + + SourceNode(); + + // Node implementation. + void Process() final; + bool CleanUp() final; + + // PublisherNode<OutputType> implementation. + std::shared_ptr<Node> GetSharedNodePtr() final; + Node::Output<const AudioBuffer*>* GetOutput() final; + + // Marks this node as being out of data and to be removed during the next + // clean-up cycle. + void MarkEndOfStream(); + + // Disable copy constructor. + SourceNode(const SourceNode& that) = delete; + + protected: + // Pure virtual method to implement the audio processing method. This method + // requires to return a single output buffer that can be processed by the node + // subscribers. + // + // @return Returns output data. + virtual const AudioBuffer* AudioProcess() = 0; + + private: + // Output stream to write processed data to. + Node::Output<const AudioBuffer*> output_stream_; + + // Flag indicating if this source node can be removed. + std::atomic<bool> end_of_stream_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_NODE_SOURCE_NODE_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/node/subscriber_node.h b/src/3rdparty/resonance-audio/resonance_audio/node/subscriber_node.h new file mode 100644 index 000000000..f297ce9e4 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/node/subscriber_node.h @@ -0,0 +1,48 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_NODE_SUBSCRIBER_NODE_H_ +#define RESONANCE_AUDIO_NODE_SUBSCRIBER_NODE_H_ + +#include <memory> + +#include "base/logging.h" +#include "node/publisher_node.h" + +namespace vraudio { + +// Interface for subscriber nodes that declares the connection and +// disconnection methods. All subscribing nodes need to implement this +// interface. +// +// @tparam InputType Input data type, i. e., the output data type of nodes to +// connect to. +// @interface +template <typename InputType> +class SubscriberNode { + public: + virtual ~SubscriberNode() {} + + // Connects this node to |publisher_node|. + // + // @param publisher_node Publisher node to connect to. + virtual void Connect( + const std::shared_ptr<PublisherNode<InputType>>& publisher_node) = 0; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_NODE_SUBSCRIBER_NODE_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/buffer_crossfader.cc b/src/3rdparty/resonance-audio/resonance_audio/utils/buffer_crossfader.cc new file mode 100644 index 000000000..01aa0f473 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/buffer_crossfader.cc @@ -0,0 +1,66 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "utils/buffer_crossfader.h" + +#include "base/constants_and_types.h" +#include "base/simd_utils.h" + +namespace vraudio { + +BufferCrossfader::BufferCrossfader(size_t num_frames) + : crossfade_buffer_(kNumStereoChannels, num_frames) { + DCHECK_NE(num_frames, 0); + // Initialize the |crossfade_buffer_|. + auto* fade_in_channel = &crossfade_buffer_[0]; + auto* fade_out_channel = &crossfade_buffer_[1]; + for (size_t frame = 0; frame < num_frames; ++frame) { + const float crossfade_factor = + static_cast<float>(frame) / static_cast<float>(num_frames); + (*fade_in_channel)[frame] = crossfade_factor; + (*fade_out_channel)[frame] = 1.0f - crossfade_factor; + } +} + +void BufferCrossfader::ApplyLinearCrossfade(const AudioBuffer& input_fade_in, + const AudioBuffer& input_fade_out, + AudioBuffer* output) const { + DCHECK(output); + DCHECK_NE(output, &input_fade_in); + DCHECK_NE(output, &input_fade_out); + + const size_t num_channels = input_fade_in.num_channels(); + const size_t num_frames = input_fade_in.num_frames(); + DCHECK_EQ(num_channels, input_fade_out.num_channels()); + DCHECK_EQ(num_channels, output->num_channels()); + DCHECK_EQ(num_frames, input_fade_out.num_frames()); + DCHECK_EQ(num_frames, output->num_frames()); + DCHECK_EQ(num_frames, crossfade_buffer_.num_frames()); + + const auto* gain_fade_in_channel = crossfade_buffer_[0].begin(); + const auto* gain_fade_out_channel = crossfade_buffer_[1].begin(); + for (size_t channel = 0; channel < num_channels; ++channel) { + const auto* input_fade_in_channel = input_fade_in[channel].begin(); + const auto* input_fade_out_channel = input_fade_out[channel].begin(); + auto* output_channel = ((*output)[channel]).begin(); + MultiplyPointwise(num_frames, gain_fade_in_channel, input_fade_in_channel, + output_channel); + MultiplyAndAccumulatePointwise(num_frames, gain_fade_out_channel, + input_fade_out_channel, output_channel); + } +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/buffer_crossfader.h b/src/3rdparty/resonance-audio/resonance_audio/utils/buffer_crossfader.h new file mode 100644 index 000000000..25502df3a --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/buffer_crossfader.h @@ -0,0 +1,48 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_UTILS_BUFFER_CROSSFADER_H_ +#define RESONANCE_AUDIO_UTILS_BUFFER_CROSSFADER_H_ + +#include "base/audio_buffer.h" + +namespace vraudio { + +// Class that takes two input buffers and produces an output buffer by applying +// linear crossfade between the inputs. +class BufferCrossfader { + public: + explicit BufferCrossfader(size_t num_frames); + + // Applies linear crossfade for given input buffers and stores the result in + // |output| buffer. Note that, in-place processing is *not* supported by this + // method, the output buffer must be different than the input buffers. + // + // @param input_fade_in Input buffer to fade-in to. + // @param input_fade_out Input buffer to fade-out from. + // @param output Output buffer to store the crossfaded result. + void ApplyLinearCrossfade(const AudioBuffer& input_fade_in, + const AudioBuffer& input_fade_out, + AudioBuffer* output) const; + + private: + // Stereo audio buffer to store crossfade decay and growth multipliers. + AudioBuffer crossfade_buffer_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_UTILS_BUFFER_CROSSFADER_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/buffer_crossfader_test.cc b/src/3rdparty/resonance-audio/resonance_audio/utils/buffer_crossfader_test.cc new file mode 100644 index 000000000..c14a0e950 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/buffer_crossfader_test.cc @@ -0,0 +1,53 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "utils/buffer_crossfader.h" + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/audio_buffer.h" +#include "base/constants_and_types.h" +#include "base/logging.h" + +namespace vraudio { + +namespace { + +// Tests that the linear crossfade is correctly applied to an input pair. +TEST(BufferCrossfaderTest, ApplyLinearCrossfadeTest) { + const size_t kNumFrames = 5; + const size_t kTestValue = 1.0f; + // Initialize input buffers. + AudioBuffer input_fade_in(kNumMonoChannels, kNumFrames); + AudioBuffer input_fade_out(kNumMonoChannels, kNumFrames); + for (size_t i = 0; i < kNumFrames; ++i) { + input_fade_in[0][i] = kTestValue; + input_fade_out[0][i] = kTestValue; + } + // Initialize output buffer. + AudioBuffer output(kNumMonoChannels, kNumFrames); + output.Clear(); + // Initialize a new crossfader and apply linear crossfade. + BufferCrossfader crossfader(kNumFrames); + crossfader.ApplyLinearCrossfade(input_fade_in, input_fade_out, &output); + // Verify that the output buffer is correctly filled in as expected. + for (size_t i = 0; i < kNumFrames; ++i) { + EXPECT_FLOAT_EQ(kTestValue, output[0][i]); + } +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/buffer_partitioner.cc b/src/3rdparty/resonance-audio/resonance_audio/utils/buffer_partitioner.cc new file mode 100644 index 000000000..91023fed5 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/buffer_partitioner.cc @@ -0,0 +1,160 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Prevent Visual Studio from complaining about std::copy_n. +#if defined(_WIN32) +#define _SCL_SECURE_NO_WARNINGS +#endif + +#include "utils/buffer_partitioner.h" + +#include "utils/planar_interleaved_conversion.h" + +namespace vraudio { + +BufferPartitioner::BufferPartitioner(size_t num_channels, + size_t frames_per_buffer, + NewBufferCallback buffer_callback) + : num_channels_(num_channels), + frames_per_buffer_(frames_per_buffer), + buffer_callback_(std::move(buffer_callback)), + current_buffer_ptr_(nullptr), + current_buffer_write_position_frames_(0), + planar_channel_ptrs_(num_channels) {} + +size_t BufferPartitioner::GetNumGeneratedBuffersForNumInputFrames( + size_t num_input_frames) const { + return (current_buffer_write_position_frames_ + num_input_frames) / + frames_per_buffer_; +} + +void BufferPartitioner::AddBuffer(const int16* interleaved_buffer, + size_t num_channels, size_t num_frames) { + AddBufferTemplated<const int16*>(interleaved_buffer, num_channels, + num_frames); +} + +void BufferPartitioner::AddBuffer(const float* interleaved_buffer, + size_t num_channels, size_t num_frames) { + AddBufferTemplated<const float*>(interleaved_buffer, num_channels, + num_frames); +} + +void BufferPartitioner::AddBuffer(const float* const* planar_buffer, + size_t num_channels, size_t num_frames) { + AddBufferTemplated<const float* const*>(planar_buffer, num_channels, + num_frames); +} + +void BufferPartitioner::AddBuffer(const int16* const* planar_buffer, + size_t num_channels, size_t num_frames) { + AddBufferTemplated<const int16* const*>(planar_buffer, num_channels, + num_frames); +} + +void BufferPartitioner::AddBuffer(const AudioBuffer& audio_buffer) { + AddBuffer(audio_buffer.num_frames(), audio_buffer); +} + +void BufferPartitioner::AddBuffer(size_t num_valid_frames, + const AudioBuffer& audio_buffer) { + DCHECK_EQ(audio_buffer.num_channels(), num_channels_); + DCHECK_LE(num_valid_frames, audio_buffer.num_frames()); + for (size_t channel = 0; channel < num_channels_; ++channel) { + planar_channel_ptrs_[channel] = &audio_buffer[channel][0]; + } + AddBuffer(planar_channel_ptrs_.data(), audio_buffer.num_channels(), + num_valid_frames); +} + +size_t BufferPartitioner::GetNumBufferedFrames() const { + return current_buffer_write_position_frames_; +} + +size_t BufferPartitioner::Flush() { + if (current_buffer_write_position_frames_ == 0 || + current_buffer_ptr_ == nullptr) { + return 0; + } + DCHECK_LE(current_buffer_write_position_frames_, + current_buffer_ptr_->num_frames()); + const size_t num_zeropadded_frames = + current_buffer_ptr_->num_frames() - current_buffer_write_position_frames_; + for (AudioBuffer::Channel& channel : *current_buffer_ptr_) { + DCHECK_LE(current_buffer_write_position_frames_, channel.size()); + std::fill(channel.begin() + current_buffer_write_position_frames_, + channel.end(), 0.0f); + } + current_buffer_ptr_ = buffer_callback_(current_buffer_ptr_); + current_buffer_write_position_frames_ = 0; + return num_zeropadded_frames; +} + +void BufferPartitioner::Clear() { + current_buffer_ptr_ = nullptr; + current_buffer_write_position_frames_ = 0; +} + +template <typename BufferType> +void BufferPartitioner::AddBufferTemplated(BufferType buffer, + size_t num_channels, + size_t num_frames) { + DCHECK_EQ(num_channels, num_channels_); + + size_t input_read_frame = 0; + while (input_read_frame < num_frames) { + if (current_buffer_ptr_ == nullptr) { + current_buffer_ptr_ = buffer_callback_(nullptr); + if (current_buffer_ptr_ == nullptr) { + LOG(WARNING) << "No input buffer received"; + return; + } + DCHECK_EQ(current_buffer_ptr_->num_frames(), frames_per_buffer_); + DCHECK_EQ(current_buffer_ptr_->num_channels(), num_channels_); + current_buffer_write_position_frames_ = 0; + } + DCHECK_GT(frames_per_buffer_, current_buffer_write_position_frames_); + const size_t remaining_frames_in_temp_buffer = + frames_per_buffer_ - current_buffer_write_position_frames_; + const size_t remaining_frames_in_input_buffer = + num_frames - input_read_frame; + const size_t num_frames_to_process = std::min( + remaining_frames_in_temp_buffer, remaining_frames_in_input_buffer); + + FillAudioBufferWithOffset(buffer, num_frames, num_channels_, + input_read_frame, + current_buffer_write_position_frames_, + num_frames_to_process, current_buffer_ptr_); + + input_read_frame += num_frames_to_process; + + // Update write pointers in temporary buffer. + current_buffer_write_position_frames_ += num_frames_to_process; + if (current_buffer_write_position_frames_ == frames_per_buffer_) { + // Current buffer is filled with data -> pass it to callback. + current_buffer_ptr_ = buffer_callback_(current_buffer_ptr_); + current_buffer_write_position_frames_ = 0; + if (current_buffer_ptr_ == nullptr) { + LOG(WARNING) << "No input buffer received"; + return; + } + DCHECK_EQ(current_buffer_ptr_->num_frames(), frames_per_buffer_); + DCHECK_EQ(current_buffer_ptr_->num_channels(), num_channels_); + } + } +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/buffer_partitioner.h b/src/3rdparty/resonance-audio/resonance_audio/utils/buffer_partitioner.h new file mode 100644 index 000000000..208ad886c --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/buffer_partitioner.h @@ -0,0 +1,152 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_UTILS_BUFFER_PARTITIONER_H_ +#define RESONANCE_AUDIO_UTILS_BUFFER_PARTITIONER_H_ + +#include <functional> +#include <memory> + +#include "base/audio_buffer.h" +#include "base/logging.h" + +namespace vraudio { + +// Packages input buffers of arbitrary sizes into fixed size |AudioBuffer|s. +class BufferPartitioner { + public: + // Callback to receive processed and assign empty |AudioBuffer|s while + // processing input buffers. + // + // @param output Pointer to partitioned |AudioBuffer| with input audio data. + // @return Pointer to the next |AudioBuffer| to be filled. + typedef std::function<AudioBuffer*(AudioBuffer* output)> NewBufferCallback; + + // Constructor. + // + // @param num_channels Number of audio channels in input and output buffers. + // @param frames_per_buffer Number of frames in output |AudioBuffer|s. + // @param buffer_callback Callback to receive output |AudioBuffer|s. + BufferPartitioner(size_t num_channels, size_t frames_per_buffer, + NewBufferCallback buffer_callback); + + // Predicts the number of generated buffers for a given number of input frames + // and based on the current fill state of the internal |temp_buffer_|. + // + // @param num_input_frames Number of input frames. + // @return Number of generated output buffers. + size_t GetNumGeneratedBuffersForNumInputFrames(size_t num_input_frames) const; + + // Returns the number of buffered frames in internal |temp_buffer_|. + size_t GetNumBufferedFrames() const; + + // Adds an interleaved int16 input buffer. This method triggers + // |NewBufferCallback| whenever a new |AudioBuffer| has been generated. + // + // @param interleaved_buffer Interleaved input buffer. + // @param num_channels Number of channels in input buffer. + // @param num_frames Number of frames in input buffer. + void AddBuffer(const int16* interleaved_buffer, size_t num_channels, + size_t num_frames); + + // Adds an interleaved float input buffer. This method triggers + // |NewBufferCallback| whenever a new |AudioBuffer| has been generated. + // + // @param interleaved_buffer Interleaved input buffer. + // @param num_channels Number of channels in input buffer. + // @param num_frames Number of frames in input buffer. + void AddBuffer(const float* interleaved_buffer, size_t num_channels, + size_t num_frames); + + // Adds a planar float input buffer. This method triggers + // |NewBufferCallback| whenever a new |AudioBuffer| has been generated. + // + // @param planar_buffer Pointer to array of pointers for each audio channel. + // @param num_channels Number of channels in input buffer. + // @param num_frames Number of frames in input buffer. + void AddBuffer(const float* const* planar_buffer, size_t num_channels, + size_t num_frames); + + // Adds a planar int16 input buffer. This method triggers + // |NewBufferCallback| whenever a new |AudioBuffer| has been generated. + // + // @param planar_buffer Pointer to array of pointers for each audio channel. + // @param num_channels Number of channels in input buffer. + // @param num_frames Number of frames in input buffer. + void AddBuffer(const int16* const* planar_buffer, size_t num_channels, + size_t num_frames); + + // Adds an |AudioBuffer|. This method triggers |NewBufferCallback| whenever a + // new |AudioBuffer| has been generated. + // + // @param audio_buffer Planar |AudioBuffer| input. + void AddBuffer(const AudioBuffer& audio_buffer); + + // Adds an |AudioBuffer| and specifies how many of that audio buffers frames + // are valid. This method triggers |NewBufferCallback| whenever a new + // |AudioBuffer| has been generated. + // + // @param num_valid_frames Indicates the number of frames which are actually + // valid in the audio buffer passed. Frames after this will be ignored. + // @param audio_buffer Planar |AudioBuffer| input. + void AddBuffer(size_t num_valid_frames, const AudioBuffer& audio_buffer); + + // Flushes the internal temporary |AudioBuffer| by filling the remaining + // audio frames with silence. + // + // @return Number of zero padded audio frames. Zero if the internal buffer + // is empty. + size_t Flush(); + + // Clears internal temporary buffer that holds remaining audio frames. + void Clear(); + + private: + // Adds an interleaved and planar float and int16 input buffer as well as + // planar |AudioBuffer| input. This method triggers |NewBufferCallback| + // whenever a new |AudioBuffer| has been generated. + // + // @tparam BufferType Input buffer type. + // @param buffer Input buffer. + // @param num_channels Number of channels in input buffer. + // @param num_frames Number of frames in input buffer. + template <typename BufferType> + void AddBufferTemplated(BufferType buffer, size_t num_channels, + size_t num_frames); + + // Number of channels in input buffers. + const size_t num_channels_; + + // Number of frames per buffer in output buffers. + const size_t frames_per_buffer_; + + // Callback to output generated |AudioBuffer|s. + const NewBufferCallback buffer_callback_; + + // Temporary buffer to remaining samples from input buffers. + AudioBuffer* current_buffer_ptr_; // Not owned. + + // Current write position in frames in temporary buffer. + size_t current_buffer_write_position_frames_; + + // Helper vector to obtain an array of planar channel pointers from an + // |AudioBuffer|. + std::vector<const float*> planar_channel_ptrs_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_UTILS_BUFFER_PARTITIONER_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/buffer_partitioner_test.cc b/src/3rdparty/resonance-audio/resonance_audio/utils/buffer_partitioner_test.cc new file mode 100644 index 000000000..a486b1f23 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/buffer_partitioner_test.cc @@ -0,0 +1,241 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "utils/buffer_partitioner.h" + +#include <memory> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/audio_buffer.h" +#include "base/constants_and_types.h" +#include "base/misc_math.h" +#include "utils/planar_interleaved_conversion.h" +#include "utils/test_util.h" + +namespace vraudio { + +const size_t kNumFramesPerBuffer = 64; + +using vraudio::AudioBuffer; + +class BufferPartitionerTest : public ::testing::Test { + public: + // Public validation method. This is called for every generated |AudioBuffer|. + AudioBuffer* ValidateAudioBufferCallback(AudioBuffer* audio_buffer) { + if (audio_buffer != nullptr) { + std::vector<float> received_buffer_interleaved; + FillExternalBuffer(*audio_buffer, &received_buffer_interleaved); + total_received_buffer_interleaved_.insert( + total_received_buffer_interleaved_.end(), + received_buffer_interleaved.begin(), + received_buffer_interleaved.end()); + + ++num_total_buffers_received_; + num_total_frames_received_ += audio_buffer->num_frames(); + } + ++num_total_callbacks_triggered_; + return &partitioned_buffer_; + } + + protected: + BufferPartitionerTest() + : partitioned_buffer_(kNumMonoChannels, kNumFramesPerBuffer), + partitioner_( + kNumMonoChannels, kNumFramesPerBuffer, + std::bind(&BufferPartitionerTest::ValidateAudioBufferCallback, this, + std::placeholders::_1)), + num_total_frames_received_(0), + num_total_buffers_received_(0), + num_total_callbacks_triggered_(0) {} + ~BufferPartitionerTest() override {} + + // Returns a section of an |AudioBuffer| in interleaved format. + template <typename T> + std::vector<T> GetInterleavedVectorFromAudioBufferSection( + const AudioBuffer& planar_buffer, size_t frame_offset, + size_t num_frames) { + CHECK_GE(planar_buffer.num_frames(), frame_offset + num_frames); + std::vector<T> return_vector(planar_buffer.num_channels() * num_frames); + + FillExternalBufferWithOffset( + planar_buffer, frame_offset, return_vector.data(), num_frames, + planar_buffer.num_channels(), 0 /* output_offset_frames */, num_frames); + + return return_vector; + } + + template <typename T> + void SmallBufferInputTest(float buffer_epsilon) { + ResetTest(); + + const size_t kNumInputBuffers = 8; + const size_t kNumFramesPerTestBuffer = + kNumFramesPerBuffer / kNumInputBuffers; + + AudioBuffer test_buffer(vraudio::kNumMonoChannels, kNumFramesPerBuffer); + GenerateSawToothSignal(kNumFramesPerBuffer, &test_buffer[0]); + + for (size_t i = 0; i < kNumInputBuffers; ++i) { + const std::vector<T> input_interleaved = + GetInterleavedVectorFromAudioBufferSection<T>( + test_buffer, kNumFramesPerTestBuffer * i, + kNumFramesPerTestBuffer); + + partitioner_.AddBuffer(input_interleaved.data(), kNumMonoChannels, + kNumFramesPerTestBuffer); + } + EXPECT_EQ(num_total_buffers_received_, 1U); + EXPECT_EQ(num_total_callbacks_triggered_, num_total_buffers_received_ + 1); + EXPECT_EQ(num_total_frames_received_, kNumFramesPerBuffer); + + AudioBuffer reconstructed_buffer(kNumMonoChannels, kNumFramesPerBuffer); + FillAudioBuffer(total_received_buffer_interleaved_, kNumMonoChannels, + &reconstructed_buffer); + + EXPECT_TRUE(CompareAudioBuffers(test_buffer[0], reconstructed_buffer[0], + buffer_epsilon)); + } + + template <typename T> + void LargeBufferTest(float buffer_epsilon) { + ResetTest(); + const size_t kNumOutputBuffers = 8; + const size_t kNumFramesPerTestBuffer = + kNumFramesPerBuffer * kNumOutputBuffers; + + AudioBuffer test_buffer(vraudio::kNumMonoChannels, kNumFramesPerTestBuffer); + GenerateSawToothSignal(kNumFramesPerTestBuffer, &test_buffer[0]); + + const std::vector<T> input_interleaved = + GetInterleavedVectorFromAudioBufferSection<T>(test_buffer, 0, + kNumFramesPerTestBuffer); + + partitioner_.AddBuffer(input_interleaved.data(), kNumMonoChannels, + kNumFramesPerTestBuffer); + + EXPECT_EQ(num_total_buffers_received_, kNumOutputBuffers); + EXPECT_EQ(num_total_callbacks_triggered_, num_total_buffers_received_ + 1); + EXPECT_EQ(num_total_frames_received_, kNumFramesPerTestBuffer); + + AudioBuffer reconstructed_buffer(kNumMonoChannels, kNumFramesPerTestBuffer); + FillAudioBuffer(total_received_buffer_interleaved_, kNumMonoChannels, + &reconstructed_buffer); + + EXPECT_TRUE(CompareAudioBuffers(test_buffer[0], reconstructed_buffer[0], + buffer_epsilon)); + } + + void ResetTest() { + num_total_frames_received_ = 0; + num_total_buffers_received_ = 0; + num_total_callbacks_triggered_ = 0; + total_received_buffer_interleaved_.clear(); + + partitioner_.Clear(); + } + + AudioBuffer partitioned_buffer_; + std::vector<float> total_received_buffer_interleaved_; + + BufferPartitioner partitioner_; + + // Stores the total number of frames received in + // |ValidateAudioBufferCallback|. + size_t num_total_frames_received_; + + // Stores the total number of buffers received in + // |ValidateAudioBufferCallback|. + size_t num_total_buffers_received_; + + // Stores the total number of callbacks being triggered in + // |ValidateAudioBufferCallback|. + size_t num_total_callbacks_triggered_; +}; + +// Tests the concatenation of multiple small buffers into a single +// |AudioBuffer|. +TEST_F(BufferPartitionerTest, TestSmallBufferInput) { + // int16 to float conversions introduce rounding errors. + SmallBufferInputTest<int16>(1e-4f /* buffer_epsilon */); + SmallBufferInputTest<float>(1e-6f /* buffer_epsilon */); +} + +// Tests the splitting of a large input buffer into multiple |AudioBuffer|s. +TEST_F(BufferPartitionerTest, TestLargeBufferInput) { + // int16 to float conversions introduce rounding errors. + LargeBufferTest<int16>(1e-4f /* buffer_epsilon */); + LargeBufferTest<float>(1e-6f /* buffer_epsilon */); +} + +// Tests the GetNumBufferedFramesTest() and +// GetNumGeneratedBuffersForNumInputFrames() methods. +TEST_F(BufferPartitionerTest, GetNumBufferedFramesTest) { + const std::vector<float> input_interleaved(1, 1.0f); + + EXPECT_EQ(partitioner_.GetNumGeneratedBuffersForNumInputFrames( + kNumFramesPerBuffer - 1), + 0U); + EXPECT_EQ( + partitioner_.GetNumGeneratedBuffersForNumInputFrames(kNumFramesPerBuffer), + 1U); + + for (size_t i = 0; i < kNumFramesPerBuffer * 2; ++i) { + EXPECT_EQ(partitioner_.GetNumBufferedFrames(), i % kNumFramesPerBuffer); + + const size_t large_random_frame_number = + i * 7 * kNumFramesPerBuffer + 13 * i; + // Expected number of generated buffers is based on + // |large_random_frame_number| and the internally stored remainder |i % + // kNumFramesPerBuffer|. + const size_t expected_num_generated_buffers = + (large_random_frame_number + partitioner_.GetNumBufferedFrames()) / + kNumFramesPerBuffer; + + EXPECT_EQ(partitioner_.GetNumGeneratedBuffersForNumInputFrames( + large_random_frame_number), + expected_num_generated_buffers); + + // Add single frame to |partinioner_|. + partitioner_.AddBuffer(input_interleaved.data(), kNumMonoChannels, 1); + } +} + +// Tests the Flush() method. +TEST_F(BufferPartitionerTest, FlushTest) { + AudioBuffer test_buffer(vraudio::kNumMonoChannels, kNumFramesPerBuffer); + GenerateDiracImpulseFilter(0, &test_buffer[0]); + + const std::vector<float> input_interleaved = + GetInterleavedVectorFromAudioBufferSection<float>( + test_buffer, 0 /* frame_offset */, kNumFramesPerBuffer); + + // Add only the first sample that contains the dirac impulse. + partitioner_.AddBuffer(input_interleaved.data(), kNumMonoChannels, 1); + partitioner_.Flush(); + + EXPECT_EQ(num_total_buffers_received_, 1U); + EXPECT_EQ(num_total_callbacks_triggered_, num_total_buffers_received_ + 1); + EXPECT_EQ(num_total_frames_received_, kNumFramesPerBuffer); + + AudioBuffer reconstructed_buffer(kNumMonoChannels, kNumFramesPerBuffer); + FillAudioBuffer(total_received_buffer_interleaved_, kNumMonoChannels, + &reconstructed_buffer); + + EXPECT_TRUE(CompareAudioBuffers(test_buffer[0], reconstructed_buffer[0], + kEpsilonFloat)); +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/buffer_unpartitioner.cc b/src/3rdparty/resonance-audio/resonance_audio/utils/buffer_unpartitioner.cc new file mode 100644 index 000000000..fb4f51b36 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/buffer_unpartitioner.cc @@ -0,0 +1,132 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Prevent Visual Studio from complaining about std::copy_n. +#if defined(_WIN32) +#define _SCL_SECURE_NO_WARNINGS +#endif + +#include "utils/buffer_unpartitioner.h" + +#include "utils/planar_interleaved_conversion.h" +#include "utils/sample_type_conversion.h" + +namespace vraudio { + +BufferUnpartitioner::BufferUnpartitioner(size_t num_channels, + size_t frames_per_buffer, + GetBufferCallback buffer_callback) + : num_channels_(num_channels), + frames_per_buffer_(frames_per_buffer), + buffer_callback_(std::move(buffer_callback)), + current_input_buffer_(nullptr), + current_buffer_read_offset_frames_(0) { + DCHECK_GT(frames_per_buffer_, 0); + DCHECK_GT(num_channels, 0); +} + +size_t BufferUnpartitioner::GetNumBuffersRequestedForNumInputFrames( + size_t num_output_frames) const { + if (num_output_frames == 0) { + return 0; + } + return (num_output_frames - GetNumFramesAvailableInBuffer() + + frames_per_buffer_ - 1) / + frames_per_buffer_; +} + +size_t BufferUnpartitioner::GetBuffer(int16* output_buffer, size_t num_channels, + size_t num_frames) { + return GetBufferTemplated<int16*>(output_buffer, num_channels, num_frames); +} + +size_t BufferUnpartitioner::GetBuffer(float* output_buffer, size_t num_channels, + size_t num_frames) { + return GetBufferTemplated<float*>(output_buffer, num_channels, num_frames); +} + +size_t BufferUnpartitioner::GetBuffer(int16** output_buffer, + size_t num_channels, size_t num_frames) { + return GetBufferTemplated<int16**>(output_buffer, num_channels, num_frames); +} + +size_t BufferUnpartitioner::GetBuffer(float** output_buffer, + size_t num_channels, size_t num_frames) { + return GetBufferTemplated<float**>(output_buffer, num_channels, num_frames); +} + +size_t BufferUnpartitioner::GetNumBufferedFrames() const { + return current_buffer_read_offset_frames_; +} + +size_t BufferUnpartitioner::GetNumFramesAvailableInBuffer() const { + if (current_input_buffer_ == nullptr) { + return 0; + } + DCHECK_GE(current_input_buffer_->num_frames(), + current_buffer_read_offset_frames_); + return current_input_buffer_->num_frames() - + current_buffer_read_offset_frames_; +} + +void BufferUnpartitioner::Clear() { + current_input_buffer_ = nullptr; + current_buffer_read_offset_frames_ = 0; +} + +template <typename BufferType> +size_t BufferUnpartitioner::GetBufferTemplated(BufferType buffer, + size_t num_channels, + size_t num_frames) { + DCHECK_EQ(num_channels, num_channels_); + + size_t num_copied_frames = 0; + while (num_copied_frames < num_frames) { + if (current_input_buffer_ == nullptr) { + current_input_buffer_ = buffer_callback_(); + if (current_input_buffer_ == nullptr) { + // No more input |AudioBuffer|s are available. + return num_copied_frames; + } + DCHECK_EQ(frames_per_buffer_, current_input_buffer_->num_frames()); + current_buffer_read_offset_frames_ = 0; + } + DCHECK_GE(frames_per_buffer_, current_buffer_read_offset_frames_); + const size_t remaining_frames_in_input_buffer = + num_frames - num_copied_frames; + DCHECK_GE(current_input_buffer_->num_frames(), + current_buffer_read_offset_frames_); + const size_t num_frames_to_process = + std::min(current_input_buffer_->num_frames() - + current_buffer_read_offset_frames_, + remaining_frames_in_input_buffer); + + FillExternalBufferWithOffset( + *current_input_buffer_, current_buffer_read_offset_frames_, buffer, + num_frames, num_channels, num_copied_frames, num_frames_to_process); + + num_copied_frames += num_frames_to_process; + + current_buffer_read_offset_frames_ += num_frames_to_process; + if (current_buffer_read_offset_frames_ == + current_input_buffer_->num_frames()) { + current_input_buffer_ = nullptr; + } + } + return num_copied_frames; +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/buffer_unpartitioner.h b/src/3rdparty/resonance-audio/resonance_audio/utils/buffer_unpartitioner.h new file mode 100644 index 000000000..51a5793d3 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/buffer_unpartitioner.h @@ -0,0 +1,135 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_UTILS_BUFFER_UNPARTITIONER_H_ +#define RESONANCE_AUDIO_UTILS_BUFFER_UNPARTITIONER_H_ + +#include <functional> +#include <memory> + +#include "base/audio_buffer.h" +#include "base/logging.h" + +namespace vraudio { + +// Unpackages |AudioBuffer| into output buffers of arbitrary sizes. +class BufferUnpartitioner { + public: + // Callback to obtain input |AudioBuffer|s. Returns a nullptr if no buffers + // are available. + typedef std::function<const AudioBuffer*()> GetBufferCallback; + + // Constructor. + // + // @param num_channels Number of audio channels in input and output buffers. + // @param frames_per_buffer Number of frames per input buffer. + // @param buffer_callback Callback to receive output |AudioBuffer|s. + BufferUnpartitioner(size_t num_channels, size_t frames_per_buffer, + GetBufferCallback buffer_callback); + + // Returns the number of input buffers required for a given number of output + // frames and based on the current fill state of the internal |temp_buffer_|. + // + // @param num_output_frames Number of output frames. + // @return Number of required input |AudioBuffer|s. + size_t GetNumBuffersRequestedForNumInputFrames( + size_t num_output_frames) const; + + // Returns the number of buffered frames in internal |temp_buffer_|. + size_t GetNumBufferedFrames() const; + + // Requests an interleaved int16 output buffer. This method triggers + // |GetBufferCallback|. + // + // @param output_buffer Interleaved output buffer to write to. + // @param num_channels Number of channels in output buffer. + // @param num_frames Number of frames in output buffer. + // @return Number of frames actually written. + size_t GetBuffer(int16* output_buffer, size_t num_channels, + size_t num_frames); + + // Requests an interleaved float output buffer. This method triggers + // |GetBufferCallback|. + // + // @param output_buffer Interleaved output buffer to write to. + // @param num_channels Number of channels in output buffer. + // @param num_frames Number of frames in output buffer. + // @return Number of frames actually written. + size_t GetBuffer(float* output_buffer, size_t num_channels, + size_t num_frames); + + // Requests a planar int16 output buffer. This method triggers + // |GetBufferCallback|. + // + // @param output_buffer Planar output buffer to write to. + // @param num_channels Number of channels in output buffer. + // @param num_frames Number of frames in output buffer. + // @return Number of frames actually written. + size_t GetBuffer(int16** output_buffer, size_t num_channels, + size_t num_frames); + + // Requests a planar float output buffer. This method triggers + // |GetBufferCallback|. + // + // @param output_buffer Planar output buffer to write to. + // @param num_channels Number of channels in output buffer. + // @param num_frames Number of frames in output buffer. + // @return Number of frames actually written. + size_t GetBuffer(float** output_buffer, size_t num_channels, + size_t num_frames); + + // Clears internal temporary buffer that holds remaining audio frames. + void Clear(); + + private: + // Returns the number of frames that are buffered in |current_input_buffer_|. + // + // @return Number of frames in |current_input_buffer_|. If + // |current_input_buffer_| is undefined, it returns 0. + size_t GetNumFramesAvailableInBuffer() const; + + // Writes output buffer into target buffer. Supported buffer types are planar + // and interleaved floating point abd interleaved int16 output. This method + // triggers |GetBufferCallback|. + // + // @tparam BufferType Output buffer type. + // @param buffer Output buffer to write to. + // @param num_channels Number of channels in output buffer. + // @param num_frames Number of frames in output buffer. + // @return Number of frames actually written. + template <typename BufferType> + size_t GetBufferTemplated(BufferType buffer, size_t num_channels, + size_t num_frames); + + // Number of channels in output buffers. + const size_t num_channels_; + + // Number of frames per buffer in output buffers. + const size_t frames_per_buffer_; + + // Callback to request input |AudioBuffer|s. + const GetBufferCallback buffer_callback_; + + // Temporary buffer containing remaining audio frames. + const AudioBuffer* current_input_buffer_; // Not owned. + + // Current read position in |current_input_buffer_|. + size_t current_buffer_read_offset_frames_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_UTILS_BUFFER_UNPARTITIONER_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/buffer_unpartitioner_test.cc b/src/3rdparty/resonance-audio/resonance_audio/utils/buffer_unpartitioner_test.cc new file mode 100644 index 000000000..48f5701fa --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/buffer_unpartitioner_test.cc @@ -0,0 +1,294 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "utils/buffer_unpartitioner.h" + +#include <memory> +#include <numeric> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/audio_buffer.h" +#include "base/constants_and_types.h" +#include "base/misc_math.h" +#include "utils/planar_interleaved_conversion.h" +#include "utils/sample_type_conversion.h" +#include "utils/test_util.h" + +namespace vraudio { + +// Number of channels in test audio buffers. +const size_t kNumChannels = vraudio::kNumStereoChannels; + +// Number of frames per test audio buffer. +const size_t kNumFramesPerBuffer = 64; + +using vraudio::AudioBuffer; + +class BufferUnpartitionerTest : public ::testing::Test { + public: + // Generates a vector of int16 values with arbitrary data (vec[i] = i). + std::vector<int16> GenerateTestData(size_t num_channels, size_t num_frames) { + std::vector<int16> test_data(num_channels * num_frames); + std::iota(std::begin(test_data), std::end(test_data), 0); + return test_data; + } + + // Generates an AudioBuffer from a segment of an interleaved std::vector. + AudioBuffer GetAudioBufferFromTestData(const std::vector<int16>& test_data, + size_t num_channels, + size_t num_frame_offset, + size_t num_audio_buffer_frames) { + const size_t num_input_frames = test_data.size() / num_channels; + AudioBuffer test_audio_buffer(num_channels, num_audio_buffer_frames); + FillAudioBufferWithOffset(&test_data[0], num_input_frames, num_channels, + num_frame_offset, 0 /* output_frame_offset */, + num_audio_buffer_frames, &test_audio_buffer); + return test_audio_buffer; + } + + protected: + BufferUnpartitionerTest() {} + ~BufferUnpartitionerTest() override {} + + const AudioBuffer* PassNextAudioBufferToBufferUnpartitioner( + size_t num_input_buffer_size_frames) { + input_audio_buffer_ = GetAudioBufferFromTestData( + input_audio_vector_, kNumChannels, + num_input_buffer_size_frames * num_callback_calls_, + num_input_buffer_size_frames); + ++num_callback_calls_; + return &input_audio_buffer_; + } + + void InitBufferUnpartitioner(size_t num_total_frames_to_test, + size_t num_input_buffer_size_frames) { + input_audio_vector_ = + GenerateTestData(kNumChannels, num_total_frames_to_test); + + num_callback_calls_ = 0; + unpartitioner_.reset(new BufferUnpartitioner( + kNumChannels, num_input_buffer_size_frames, + std::bind( + &BufferUnpartitionerTest::PassNextAudioBufferToBufferUnpartitioner, + this, num_input_buffer_size_frames))); + } + + // Returns the number of triggered callback calls. + template <typename T> + size_t TestInterleavedBufferOutputTest(size_t num_input_buffer_size_frames, + size_t num_output_buffer_size_frames, + size_t num_total_frames_to_test, + float buffer_epsilon) { + InitBufferUnpartitioner(num_total_frames_to_test, + num_input_buffer_size_frames); + + std::vector<T> output_vector(input_audio_vector_.size(), static_cast<T>(0)); + for (size_t b = 0; + b < num_total_frames_to_test / num_output_buffer_size_frames; ++b) { + EXPECT_EQ( + num_output_buffer_size_frames, + unpartitioner_->GetBuffer( + &output_vector[b * num_output_buffer_size_frames * kNumChannels], + kNumChannels, num_output_buffer_size_frames)); + } + + AudioBuffer input(kNumChannels, num_total_frames_to_test); + FillAudioBuffer(input_audio_vector_, kNumChannels, &input); + + AudioBuffer output(kNumChannels, num_total_frames_to_test); + FillAudioBuffer(output_vector, kNumChannels, &output); + + for (size_t channel = 0; channel < kNumChannels; ++channel) { + EXPECT_TRUE( + CompareAudioBuffers(input[channel], output[channel], buffer_epsilon)); + } + + EXPECT_EQ(unpartitioner_->GetNumBuffersRequestedForNumInputFrames( + num_total_frames_to_test), + num_callback_calls_); + return num_callback_calls_; + } + + // Returns the number of triggered callback calls. + template <typename T> + size_t TestPlanarBufferOutputTest(size_t input_buffer_size_frames, + size_t num_output_buffer_size_frames, + size_t num_total_frames_to_test, + float buffer_epsilon) { + InitBufferUnpartitioner(num_total_frames_to_test, input_buffer_size_frames); + + std::vector<std::vector<T>> planar_output_vector( + kNumChannels, + std::vector<T>(num_total_frames_to_test, static_cast<T>(0))); + std::vector<T*> planar_output_vector_ptrs(kNumChannels); + for (size_t channel = 0; channel < kNumChannels; ++channel) { + planar_output_vector_ptrs[channel] = &planar_output_vector[channel][0]; + } + + const size_t num_total_buffers = + num_total_frames_to_test / num_output_buffer_size_frames; + for (size_t buffer = 0; buffer < num_total_buffers; ++buffer) { + EXPECT_EQ(num_output_buffer_size_frames, + unpartitioner_->GetBuffer(planar_output_vector_ptrs.data(), + kNumChannels, + num_output_buffer_size_frames)); + for (T*& planar_output_vector_ptr : planar_output_vector_ptrs) { + planar_output_vector_ptr += num_output_buffer_size_frames; + } + } + AudioBuffer input(kNumChannels, num_total_frames_to_test); + FillAudioBuffer(input_audio_vector_, kNumChannels, &input); + + AudioBuffer output(kNumChannels, num_total_frames_to_test); + for (size_t channel = 0; channel < kNumChannels; ++channel) { + ConvertPlanarSamples(num_total_frames_to_test, + &planar_output_vector[channel][0], + &output[channel][0]); + } + + for (size_t channel = 0; channel < kNumChannels; ++channel) { + EXPECT_TRUE( + CompareAudioBuffers(input[channel], output[channel], buffer_epsilon)); + } + + EXPECT_EQ(unpartitioner_->GetNumBuffersRequestedForNumInputFrames( + num_total_frames_to_test), + num_callback_calls_); + return num_callback_calls_; + } + + size_t num_callback_calls_; + AudioBuffer input_audio_buffer_; + std::vector<int16> input_audio_vector_; + + std::unique_ptr<BufferUnpartitioner> unpartitioner_; +}; + +TEST_F(BufferUnpartitionerTest, TestInterleavedBufferOutputTest) { + const size_t kNumInputBuffers = 8; + EXPECT_EQ(kNumInputBuffers, + TestInterleavedBufferOutputTest<int16>( + kNumFramesPerBuffer / kNumInputBuffers /* input_buffer_size */, + kNumFramesPerBuffer /* output_buffer_size */, + kNumFramesPerBuffer /* num_frames_to_test */, + 1e-4f /* buffer_epsilon */)); + + EXPECT_EQ(kNumInputBuffers, + TestInterleavedBufferOutputTest<float>( + kNumFramesPerBuffer / kNumInputBuffers /* input_buffer_size */, + kNumFramesPerBuffer /* output_buffer_size */, + kNumFramesPerBuffer /* num_frames_to_test */, + 1e-6f /* buffer_epsilon */)); + + EXPECT_EQ(1U, // Single callback expected. + TestInterleavedBufferOutputTest<int16>( + kNumFramesPerBuffer /* input_buffer_size */, + kNumFramesPerBuffer / kNumInputBuffers /* output_buffer_size */, + kNumFramesPerBuffer /* num_frames_to_test */, + 1e-4f /* buffer_epsilon */)); + + EXPECT_EQ(1U, // Single callback expected. + TestInterleavedBufferOutputTest<float>( + kNumFramesPerBuffer /* input_buffer_size */, + kNumFramesPerBuffer / kNumInputBuffers /* output_buffer_size */, + kNumFramesPerBuffer /* num_frames_to_test */, + 1e-6f /* buffer_epsilon */)); +} + +TEST_F(BufferUnpartitionerTest, TestPlanarBufferOutputTest) { + const size_t kNumInputBuffers = 8; + EXPECT_EQ(kNumInputBuffers, + TestPlanarBufferOutputTest<int16>( + kNumFramesPerBuffer / kNumInputBuffers /* input_buffer_size */, + kNumFramesPerBuffer /* output_buffer_size */, + kNumFramesPerBuffer /* num_frames_to_test */, + 5e-3f /* buffer_epsilon */)); + + EXPECT_EQ(kNumInputBuffers, + TestPlanarBufferOutputTest<float>( + kNumFramesPerBuffer / kNumInputBuffers /* input_buffer_size */, + kNumFramesPerBuffer /* output_buffer_size */, + kNumFramesPerBuffer /* num_frames_to_test */, + 1e-6f /* buffer_epsilon */)); + + EXPECT_EQ(1U, // Single callback expected. + TestPlanarBufferOutputTest<int16>( + kNumFramesPerBuffer /* input_buffer_size */, + kNumFramesPerBuffer / kNumInputBuffers /* output_buffer_size */, + kNumFramesPerBuffer /* num_frames_to_test */, + 5e-3f /* buffer_epsilon */)); + + EXPECT_EQ(1U, // Single callback expected. + TestPlanarBufferOutputTest<float>( + kNumFramesPerBuffer /* input_buffer_size */, + kNumFramesPerBuffer / kNumInputBuffers /* output_buffer_size */, + kNumFramesPerBuffer /* num_frames_to_test */, + 1e-6f /* buffer_epsilon */)); +} + +TEST_F(BufferUnpartitionerTest, GetNumBuffersRequestedForNumInputFramesTest) { + AudioBuffer input_audio_buffer(kNumChannels, kNumFramesPerBuffer); + size_t num_callback_calls = 0; + const auto input_callback = [this, &input_audio_buffer, + &num_callback_calls]() -> AudioBuffer* { + ++num_callback_calls; + return &input_audio_buffer; + }; + unpartitioner_.reset(new BufferUnpartitioner( + kNumChannels, kNumFramesPerBuffer, input_callback)); + + EXPECT_EQ(unpartitioner_->GetNumBuffersRequestedForNumInputFrames(0), 0U); + EXPECT_EQ(unpartitioner_->GetNumBuffersRequestedForNumInputFrames(1), 1U); + EXPECT_EQ(unpartitioner_->GetNumBuffersRequestedForNumInputFrames( + kNumFramesPerBuffer - 1), + 1U); + EXPECT_EQ(unpartitioner_->GetNumBuffersRequestedForNumInputFrames( + kNumFramesPerBuffer), + 1U); + EXPECT_EQ(unpartitioner_->GetNumBuffersRequestedForNumInputFrames( + kNumFramesPerBuffer + 1), + 2U); + + const auto GetLargeRandomFrameNumber = [](size_t i) -> size_t { + return i * 7 * kNumFramesPerBuffer + 13 * i; + }; + + const size_t kMaximumInputSize = + kNumChannels * GetLargeRandomFrameNumber(kNumFramesPerBuffer * 2); + std::vector<float> input(kMaximumInputSize); + + for (size_t i = 0; i < kNumFramesPerBuffer * 2; ++i) { + // Reset unpartitioner. + unpartitioner_->Clear(); + + // Simulate initial read of |i| frames. + EXPECT_EQ(i, unpartitioner_->GetBuffer(&input[0], kNumChannels, i)); + + const size_t large_random_frame_number = GetLargeRandomFrameNumber(i); + const size_t expected_num_buffer_requests = + unpartitioner_->GetNumBuffersRequestedForNumInputFrames( + large_random_frame_number); + + num_callback_calls = 0; + EXPECT_EQ(large_random_frame_number, + unpartitioner_->GetBuffer(&input[0], kNumChannels, + large_random_frame_number)); + + EXPECT_EQ(expected_num_buffer_requests, num_callback_calls); + } +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/lockless_task_queue.cc b/src/3rdparty/resonance-audio/resonance_audio/utils/lockless_task_queue.cc new file mode 100644 index 000000000..3edb75dc3 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/lockless_task_queue.cc @@ -0,0 +1,156 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "utils/lockless_task_queue.h" + +#include <limits> + +#include "base/logging.h" + +namespace vraudio { + +namespace { + +// Reserved index representing an invalid list index. +constexpr uint64_t kInvalidIndex = std::numeric_limits<uint32_t>::max(); + +// Maximum number of producers. +constexpr uint64_t kMaxProducers = kInvalidIndex - 1; + +} // namespace + +LocklessTaskQueue::LocklessTaskQueue(size_t max_tasks) { + CHECK_GT(max_tasks, 0U); + CHECK_LE(max_tasks, kMaxProducers); + Init(max_tasks); +} + +LocklessTaskQueue::~LocklessTaskQueue() { Clear(); } + +void LocklessTaskQueue::Post(Task&& task) { + const TagAndIndex free_node_idx = PopNodeFromList(&free_list_head_idx_); + if (GetIndex(free_node_idx) == kInvalidIndex) { + LOG(WARNING) << "Queue capacity reached - dropping task"; + return; + } + nodes_[GetIndex(free_node_idx)].task = std::move(task); + PushNodeToList(&task_list_head_idx_, free_node_idx); +} + +void LocklessTaskQueue::Execute() { + const TagAndIndex old_flag_with_invalid_index = + (GetFlag(task_list_head_idx_) << 32) + kInvalidIndex; + const TagAndIndex old_task_list_head_idx = + task_list_head_idx_.exchange(old_flag_with_invalid_index); + ProcessTaskList(old_task_list_head_idx, true /*execute_tasks*/); +} + +void LocklessTaskQueue::Clear() { + const TagAndIndex old_flag_with_invalid_index = + (GetFlag(task_list_head_idx_) << 32) + kInvalidIndex; + const TagAndIndex old_task_list_head_idx = + task_list_head_idx_.exchange(old_flag_with_invalid_index); + ProcessTaskList(old_task_list_head_idx, false /*execute_tasks*/); +} + +LocklessTaskQueue::TagAndIndex LocklessTaskQueue::IncreaseTag( + TagAndIndex tag_and_index) { + // The most significant 32 bits a reserved for tagging. Overflows are + // acceptable. + return tag_and_index + (static_cast<uint64_t>(1) << 32); +} + +LocklessTaskQueue::TagAndIndex LocklessTaskQueue::GetIndex( + TagAndIndex tag_and_index) { + // The least significant 32 bits a reserved for the index. + return tag_and_index & std::numeric_limits<uint32_t>::max(); +} + +// Extracts the flag in the most significant 32 bits from a TagAndIndex; +LocklessTaskQueue::TagAndIndex LocklessTaskQueue::GetFlag( + TagAndIndex tag_and_index) { + // The most significant 32 bits a reserved for the flag. + return tag_and_index >> 32; +} + +void LocklessTaskQueue::PushNodeToList( + std::atomic<TagAndIndex>* list_head_idx_ptr, TagAndIndex node_idx) { + DCHECK(list_head_idx_ptr); + TagAndIndex list_head_idx; + do { + list_head_idx = list_head_idx_ptr->load(); + nodes_[GetIndex(node_idx)].next = list_head_idx; + } while (!std::atomic_compare_exchange_strong(list_head_idx_ptr, + &list_head_idx, node_idx)); +} + +LocklessTaskQueue::TagAndIndex LocklessTaskQueue::PopNodeFromList( + std::atomic<TagAndIndex>* list_head_idx_ptr) { + DCHECK(list_head_idx_ptr); + TagAndIndex list_head_idx; + TagAndIndex list_head_next; + do { + list_head_idx = list_head_idx_ptr->load(); + if (GetIndex(list_head_idx) == kInvalidIndex) { + // End of list reached. + return kInvalidIndex; + } + list_head_next = nodes_[GetIndex(list_head_idx)].next; + } while (!std::atomic_compare_exchange_strong( + list_head_idx_ptr, &list_head_idx, list_head_next)); + return IncreaseTag(list_head_idx); +} + +void LocklessTaskQueue::ProcessTaskList(TagAndIndex list_head_idx, + bool execute) { + TagAndIndex node_itr = list_head_idx; + while (GetIndex(node_itr) != kInvalidIndex) { + Node* node = &nodes_[GetIndex(node_itr)]; + TagAndIndex next_node = node->next; + temp_tasks_.emplace_back(std::move(node->task)); + node->task = nullptr; + PushNodeToList(&free_list_head_idx_, node_itr); + node_itr = next_node; + } + + if (execute) { + // Execute tasks in reverse order. + for (std::vector<Task>::reverse_iterator task_itr = temp_tasks_.rbegin(); + task_itr != temp_tasks_.rend(); ++task_itr) { + if (*task_itr != nullptr) { + (*task_itr)(); + } + } + } + temp_tasks_.clear(); +} + +void LocklessTaskQueue::Init(size_t num_nodes) { + nodes_.resize(num_nodes); + temp_tasks_.reserve(num_nodes); + + // Initialize free list. + free_list_head_idx_ = 0; + for (size_t i = 0; i < num_nodes - 1; ++i) { + nodes_[i].next = i + 1; + } + nodes_[num_nodes - 1].next = kInvalidIndex; + + // Initialize task list. + task_list_head_idx_ = kInvalidIndex; +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/lockless_task_queue.h b/src/3rdparty/resonance-audio/resonance_audio/utils/lockless_task_queue.h new file mode 100644 index 000000000..e041c1fa4 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/lockless_task_queue.h @@ -0,0 +1,124 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_UTILS_LOCKLESS_TASK_QUEUE_H_ +#define RESONANCE_AUDIO_UTILS_LOCKLESS_TASK_QUEUE_H_ + +#include <atomic> +#include <cstddef> +#include <cstdint> +#include <functional> +#include <vector> + +namespace vraudio { + +// Lock-less task queue which is thread safe for concurrent task producers and +// single task consumers. +class LocklessTaskQueue { + public: + // Alias for the task closure type. + typedef std::function<void()> Task; + + // Constructor. Preallocates nodes on the task queue list. + // + // @param max_tasks Maximum number of tasks on the task queue. + explicit LocklessTaskQueue(size_t max_tasks); + + ~LocklessTaskQueue(); + + // Posts a new task to task queue. + // + // @param task Task to process. + void Post(Task&& task); + + // Executes all tasks on the task queue. + void Execute(); + + // Removes all tasks on the task queue. + void Clear(); + + private: + // To prevent ABA problems during thread synchronization, the most significant + // 32 bits of this index type are reserved for a continuously increasing + // tag counter. This prevents cases where nodes on the head appears to be + // untouched during the preparation of a push operation but instead they have + // been popped and pushed back during a context switch. + typedef uint64_t TagAndIndex; + + // Node to model a single-linked list. + struct Node { + Node() = default; + + // Dummy copy constructor to enable vector::resize allocation. + Node(const Node& node) : next() {} + + // User task. + LocklessTaskQueue::Task task; + + // Index to next node. + std::atomic<TagAndIndex> next; + }; + + // Returned a TagAndIndex with increased tag. + TagAndIndex IncreaseTag(TagAndIndex tag_and_index); + + // Extracts the index in the least significant 32 bits from a TagAndIndex. + TagAndIndex GetIndex(TagAndIndex tag_and_index); + + // Extracts the flag in the most significant 32 bits from a TagAndIndex. + TagAndIndex GetFlag(TagAndIndex tag_and_index); + + // Pushes a node to the front of a list. + // + // @param list_head Index to list head. + // @param node Index of node to be pushed to the front of the list. + void PushNodeToList(std::atomic<TagAndIndex>* list_head, TagAndIndex node); + + // Pops a node from the front of a list. + // + // @param list_head Index to list head. + // @return Index of front node, kInvalidIndex if list is empty. + TagAndIndex PopNodeFromList(std::atomic<TagAndIndex>* list_head); + + // Iterates over list and moves all tasks to |temp_tasks_| to be executed in + // FIFO order. All processed nodes are pushed back to the free list. + // + // @param list_head Index of head node of list to be processed. + // @param execute If true, tasks on task list are executed. + void ProcessTaskList(TagAndIndex list_head, bool execute); + + // Initializes task queue structures and preallocates task queue nodes. + // + // @param num_nodes Number of nodes to be initialized on free list. + void Init(size_t num_nodes); + + // Index to head node of free list. + std::atomic<TagAndIndex> free_list_head_idx_; + + // Index to head node of task list. + std::atomic<TagAndIndex> task_list_head_idx_; + + // Holds preallocated nodes. + std::vector<Node> nodes_; + + // Temporary vector to hold |Task|s in order to execute them in reverse order + // (FIFO, instead of LIFO). + std::vector<Task> temp_tasks_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_UTILS_LOCKLESS_TASK_QUEUE_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/lockless_task_queue_test.cc b/src/3rdparty/resonance-audio/resonance_audio/utils/lockless_task_queue_test.cc new file mode 100644 index 000000000..3168a92a1 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/lockless_task_queue_test.cc @@ -0,0 +1,215 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "utils/lockless_task_queue.h" + +#include <atomic> +#include <condition_variable> +#include <memory> +#include <mutex> +#include <thread> +#include <vector> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/logging.h" + +namespace vraudio { + +namespace { +// Number of task producer threads. +static const size_t kNumTaskProducers = 5; + +// Atomic thread counter used to trigger a simultaneous execution of all +// threads. +static std::atomic<size_t> s_thread_count(0); + +// Waits until all threads are initialized. +static void WaitForProducerThreads() { + static std::mutex mutex; + static std::condition_variable cond_var; + std::unique_lock<std::mutex> lock(mutex); + + if (++s_thread_count < kNumTaskProducers) { + cond_var.wait(lock); + } else { + cond_var.notify_all(); + } +} + +static void IncVectorAtIndex(std::vector<size_t>* work_vector_ptr, + size_t index) { + ++(*work_vector_ptr)[index]; +} + +class TaskProducer { + public: + TaskProducer(LocklessTaskQueue* queue, std::vector<size_t>* work_vector_ptr, + int delay_ms) + : producer_thread_(new std::thread(std::bind( + &TaskProducer::Produce, this, queue, work_vector_ptr, delay_ms))) { + } + + TaskProducer(TaskProducer&& task) + : producer_thread_(std::move(task.producer_thread_)) {} + + void Join() { + if (producer_thread_->joinable()) { + producer_thread_->join(); + } + } + + private: + void Produce(LocklessTaskQueue* queue, std::vector<size_t>* work_vector_ptr, + int delay_ms) { + WaitForProducerThreads(); + + for (size_t i = 0; i < work_vector_ptr->size(); ++i) { + queue->Post(std::bind(IncVectorAtIndex, work_vector_ptr, i)); + if (delay_ms > 0) { + std::this_thread::sleep_for(std::chrono::milliseconds(delay_ms)); + } + } + } + std::unique_ptr<std::thread> producer_thread_; +}; + +} // namespace + +class LocklessTaskQueueTest : public ::testing::Test { + protected: + // Virtual methods from ::testing::Test + ~LocklessTaskQueueTest() override {} + void SetUp() override {} + void TearDown() override {} + + // Helper method to initialize and run the concurrency test with multiple + // task producers and a single task executor. + void ConcurrentThreadsMultipleTaskProducerSingleTaskExecutorTest( + int producer_delay_ms) { + s_thread_count = 0; + const size_t kTasksPerProducer = 50; + work_vector_.resize(kNumTaskProducers); + std::fill(work_vector_.begin(), work_vector_.end(), 0); + + LocklessTaskQueue task_queue(kNumTaskProducers * kTasksPerProducer); + + std::vector<TaskProducer> task_producer_tasks; + for (size_t i = 0; i < kNumTaskProducers; ++i) { + task_producer_tasks.emplace_back(&task_queue, &work_vector_, + producer_delay_ms); + } + WaitForProducerThreads(); + task_queue.Execute(); + + for (auto& producer : task_producer_tasks) { + producer.Join(); + } + task_queue.Execute(); + + for (size_t worker_count : work_vector_) { + EXPECT_EQ(worker_count, kNumTaskProducers); + } + } + + std::vector<size_t> work_vector_; +}; + +TEST_F(LocklessTaskQueueTest, MaxTasks) { + LocklessTaskQueue task_queue(1); + + work_vector_.resize(1, 0); + + task_queue.Execute(); + + task_queue.Post(std::bind(IncVectorAtIndex, &work_vector_, 0)); + // Second task should be dropped since queue is initialized with size 1. + task_queue.Post(std::bind(IncVectorAtIndex, &work_vector_, 0)); + task_queue.Execute(); + + EXPECT_EQ(work_vector_[0], 1U); + + task_queue.Post(std::bind(IncVectorAtIndex, &work_vector_, 0)); + // Second task should be dropped since queue is initialized with size 1. + task_queue.Post(std::bind(IncVectorAtIndex, &work_vector_, 0)); + task_queue.Execute(); + + EXPECT_EQ(work_vector_[0], 2U); +} + +TEST_F(LocklessTaskQueueTest, Clear) { + LocklessTaskQueue task_queue(1); + + work_vector_.resize(1, 0); + + task_queue.Post(std::bind(IncVectorAtIndex, &work_vector_, 0)); + task_queue.Clear(); + task_queue.Execute(); + + EXPECT_EQ(work_vector_[0], 0U); +} + +TEST_F(LocklessTaskQueueTest, SynchronousTaskExecution) { + const size_t kNumRounds = 5; + const size_t kNumTasksPerRound = 20; + + LocklessTaskQueue task_queue(kNumTasksPerRound); + + work_vector_.resize(kNumTasksPerRound, 0); + + for (size_t r = 0; r < kNumRounds; ++r) { + for (size_t t = 0; t < kNumTasksPerRound; ++t) { + task_queue.Post(std::bind(IncVectorAtIndex, &work_vector_, t)); + } + task_queue.Execute(); + } + + for (size_t t = 0; t < kNumTasksPerRound; ++t) { + EXPECT_EQ(work_vector_[t], kNumRounds); + } +} + +TEST_F(LocklessTaskQueueTest, SynchronousInOrderTaskExecution) { + const size_t kNumTasksPerRound = 20; + + LocklessTaskQueue task_queue(kNumTasksPerRound); + + work_vector_.resize(kNumTasksPerRound, 0); + work_vector_[0] = 1; + + const auto accumulate_from_lower_index_task = [this](size_t index) { + work_vector_[index] += work_vector_[index - 1]; + }; + for (size_t t = 1; t < kNumTasksPerRound; ++t) { + task_queue.Post(std::bind(accumulate_from_lower_index_task, t)); + } + task_queue.Execute(); + + for (size_t t = 0; t < kNumTasksPerRound; ++t) { + EXPECT_EQ(work_vector_[t], 1U); + } +} + +// Tests concurrency of multiple producers and a single executor. +TEST_F(LocklessTaskQueueTest, + ConcurrentThreadsMultipleFastProducersSingleExecutor) { + // Test fast producers and a fast consumer. + ConcurrentThreadsMultipleTaskProducerSingleTaskExecutorTest( + 0 /* producer delay in ms */); + ConcurrentThreadsMultipleTaskProducerSingleTaskExecutorTest( + 1 /* producer delay in ms */); +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/ogg_vorbis_recorder.cc b/src/3rdparty/resonance-audio/resonance_audio/utils/ogg_vorbis_recorder.cc new file mode 100644 index 000000000..26596abab --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/ogg_vorbis_recorder.cc @@ -0,0 +1,107 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "utils/ogg_vorbis_recorder.h" + +#include "base/logging.h" +#include "utils/planar_interleaved_conversion.h" + +namespace vraudio { + +OggVorbisRecorder::OggVorbisRecorder(int sample_rate_hz, size_t num_channels, + size_t num_frames, size_t max_num_buffers) + : sample_rate_hz_(sample_rate_hz), + num_channels_(num_channels), + num_frames_(num_frames), + max_num_buffers_(max_num_buffers), + temp_data_channel_ptrs_(num_channels), + crossfader_(num_frames_), + crossfade_buffer_(num_channels_, num_frames_) { + DCHECK_GT(sample_rate_hz_, 0); + DCHECK_NE(num_channels_, 0U); + DCHECK_NE(num_frames_, 0U); + CHECK_NE(max_num_buffers_, 0U); +} + +void OggVorbisRecorder::AddInput(std::unique_ptr<AudioBuffer> input_buffer) { + DCHECK(input_buffer); + DCHECK_EQ(input_buffer->num_channels(), num_channels_); + DCHECK_EQ(input_buffer->num_frames(), num_frames_); + + if (data_.size() == max_num_buffers_) { + LOG(WARNING) << "Maximum input buffer limit reached, overwriting data"; + data_.erase(data_.begin()); + } + data_.push_back(std::move(input_buffer)); +} + +void OggVorbisRecorder::Reset() { data_.clear(); } + +bool OggVorbisRecorder::WriteToFile(const std::string& file_path, float quality, + bool seamless) { + if (data_.empty()) { + LOG(WARNING) << "No recorded data"; + return false; + } + + if (seamless) { + MakeSeamless(); + } + + if (!encoder_.InitializeForFile( + file_path, num_channels_, sample_rate_hz_, + VorbisStreamEncoder::EncodingMode::kVariableBitRate, 0 /* bitrate */, + quality)) { + LOG(WARNING) << "Cannot initialize file to record: " << file_path; + Reset(); + return false; + } + for (const auto& audio_buffer : data_) { + GetRawChannelDataPointersFromAudioBuffer(*audio_buffer, + &temp_data_channel_ptrs_); + if (!encoder_.AddPlanarBuffer(temp_data_channel_ptrs_.data(), num_channels_, + num_frames_)) { + LOG(WARNING) << "Failed to write buffer into file: " << file_path; + Reset(); + return false; + } + } + if (!encoder_.FlushAndClose()) { + LOG(WARNING) << "Failed to safely close file: " << file_path; + Reset(); + return false; + } + + Reset(); + return true; +} + +void OggVorbisRecorder::MakeSeamless() { + if (data_.size() == 1) { + LOG(WARNING) << "Not enough data to make seamless file"; + return; + } + // Apply crossfade between the beginning and the end buffers of |data_|. + auto* front_buffer = data_.front().get(); + const auto& back_buffer = *data_.back(); + crossfader_.ApplyLinearCrossfade(*front_buffer, back_buffer, + &crossfade_buffer_); + + *front_buffer = crossfade_buffer_; + data_.pop_back(); +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/ogg_vorbis_recorder.h b/src/3rdparty/resonance-audio/resonance_audio/utils/ogg_vorbis_recorder.h new file mode 100644 index 000000000..2e47947ff --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/ogg_vorbis_recorder.h @@ -0,0 +1,94 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_UTILS_OGG_VORBIS_RECORDER_H_ +#define RESONANCE_AUDIO_UTILS_OGG_VORBIS_RECORDER_H_ + +#include <memory> +#include <vector> + +#include "base/audio_buffer.h" +#include "utils/buffer_crossfader.h" +#include "utils/vorbis_stream_encoder.h" + +namespace vraudio { + +// Class that takes audio buffers as input and writes them into a compressed OGG +// Vorbis file. + +class OggVorbisRecorder { + public: + // Constructs a new recorder with given sampling rate and number of channels. + + OggVorbisRecorder(int sample_rate_hz, size_t num_channels, size_t num_frames, + size_t max_num_buffers); + + // Adds next input buffer at the end of the record data. + // + // @param input_buffer Next audio buffer to be recorded. + void AddInput(std::unique_ptr<AudioBuffer> input_buffer); + + // Flushes the record data. + void Reset(); + + // Writes the current record data into a file and resets the recorder. + // + // @param file_path Full path of the file to be recorded into. + // @param quality Compression quality of the record. The usable range is from + // -0.1 (lowest quality, smallest file) to 1.0 (highest quality, largest + // file). + // @param seamless True to record seamlessly for looping. Note that this + // option will truncate the record length by |num_frames_| samples. + // @return False if fails to successfully write data into |file_path|. + bool WriteToFile(const std::string& file_path, float quality, bool seamless); + + private: + // Helper method to make record data seamless. + void MakeSeamless(); + + // Record sampling rate. + const int sample_rate_hz_; + + // Record number of channels. + const size_t num_channels_; + + // Record number of frames per buffer. + const size_t num_frames_; + + // Maximum number of input buffers allowed to record. + const size_t max_num_buffers_; + + // Record data that is stored as a list of planar audio buffers. + std::vector<std::unique_ptr<AudioBuffer>> data_; + + // Temporary vector to extract pointers to planar channels in an + // |AudioBuffer|. + std::vector<const float*> temp_data_channel_ptrs_; + + // Buffer crossfader that is used to create seamless loop. + BufferCrossfader crossfader_; + + // Temporary buffer to store the crossfaded output. + + AudioBuffer crossfade_buffer_; + + // OGG Vorbis encoder to write record data into file in compressed format. + VorbisStreamEncoder encoder_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_UTILS_OGG_VORBIS_RECORDER_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/planar_interleaved_conversion.cc b/src/3rdparty/resonance-audio/resonance_audio/utils/planar_interleaved_conversion.cc new file mode 100644 index 000000000..be50b23f7 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/planar_interleaved_conversion.cc @@ -0,0 +1,505 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Prevent Visual Studio from complaining about std::copy_n. +#if defined(_WIN32) +#define _SCL_SECURE_NO_WARNINGS +#endif + +#include "utils/planar_interleaved_conversion.h" + +#include "base/constants_and_types.h" +#include "base/logging.h" +#include "base/simd_utils.h" + +#include "utils/sample_type_conversion.h" + +namespace vraudio { + +namespace { + +template <typename InputType, typename OutputType> +void ConvertInterleavedToPlanarTemplated( + InputType interleaved_buffer, size_t num_input_frames, + size_t num_input_channels, size_t input_offset_frames, + const std::vector<size_t>* channel_map, OutputType output_buffer, + size_t num_output_frames, size_t num_output_channels, + size_t output_offset_frames, size_t num_frames_to_copy) { + DCHECK_GE(num_input_frames, input_offset_frames); + const size_t max_num_input_frames = num_input_frames - input_offset_frames; + DCHECK_GE(num_output_frames, output_offset_frames); + const size_t max_num_output_frames = num_output_frames - output_offset_frames; + DCHECK_GE(max_num_input_frames, num_frames_to_copy); + DCHECK_GE(max_num_output_frames, num_frames_to_copy); + + if (channel_map == nullptr) { + DCHECK_EQ(num_input_channels, num_output_channels); + } else { + DCHECK_GE(channel_map->size(), num_output_channels); + } + + InputType interleaved_buffer_with_offset = + interleaved_buffer + input_offset_frames * num_input_channels; + + if (num_input_channels == kNumStereoChannels && + num_output_channels == kNumStereoChannels) { + if (channel_map == nullptr) { + DeinterleaveStereo(num_frames_to_copy, interleaved_buffer_with_offset, + &output_buffer[0][output_offset_frames], + &output_buffer[1][output_offset_frames]); + } else { + DCHECK_LT((*channel_map)[0], kNumStereoChannels); + DCHECK_LT((*channel_map)[1], kNumStereoChannels); + DeinterleaveStereo( + num_input_frames, interleaved_buffer_with_offset, + &output_buffer[(*channel_map)[0]][output_offset_frames], + &output_buffer[(*channel_map)[1]][output_offset_frames]); + } + } else { + for (size_t channel_idx = 0; channel_idx < num_output_channels; + ++channel_idx) { + const size_t input_channel = + channel_map != nullptr ? (*channel_map)[channel_idx] : channel_idx; + DCHECK_LT(input_channel, num_input_channels); + InputType input_ptr = &interleaved_buffer_with_offset[input_channel]; + float* output_ptr = &output_buffer[channel_idx][output_offset_frames]; + for (size_t frame = 0; frame < num_frames_to_copy; ++frame) { + ConvertSampleToFloatFormat(*input_ptr, output_ptr); + input_ptr += num_input_channels; + ++output_ptr; + } + } + } +} + +template <typename PlanarInputType, typename PlanarOutputType> +void ConvertPlanarToPlanarTemplated( + PlanarInputType input, size_t num_input_frames, size_t num_input_channels, + size_t input_offset_frames, const std::vector<size_t>* channel_map, + PlanarOutputType planar_output_ptrs, size_t num_output_frames, + size_t num_output_channels, size_t output_offset_frames, + size_t num_frames_convert_and_copy) { + DCHECK_GE(num_input_frames, input_offset_frames); + const size_t max_num_input_frames = num_input_frames - input_offset_frames; + DCHECK_GE(num_output_frames, output_offset_frames); + const size_t max_num_output_frames = num_output_frames - output_offset_frames; + DCHECK_GE(max_num_input_frames, num_frames_convert_and_copy); + DCHECK_GE(max_num_output_frames, num_frames_convert_and_copy); + + if (channel_map == nullptr) { + DCHECK_EQ(num_input_channels, num_output_channels); + } else { + DCHECK_GE(channel_map->size(), num_output_channels); + } + + for (size_t channel = 0; channel < num_output_channels; ++channel) { + const size_t input_channel = + channel_map != nullptr ? (*channel_map)[channel] : channel; + DCHECK_LT(input_channel, num_input_channels); + ConvertPlanarSamples(num_frames_convert_and_copy, + &input[input_channel][input_offset_frames], + &planar_output_ptrs[channel][output_offset_frames]); + } +} + +template <typename PlanarInputType, typename InterleavedOutputType> +void ConvertPlanarToInterleavedTemplated( + PlanarInputType input, size_t num_input_frames, size_t num_input_channels, + size_t input_offset_frames, InterleavedOutputType interleaved_output_ptr, + size_t num_output_frames, size_t num_output_channels, + size_t output_offset_frames, size_t num_frames_convert_and_copy) { + DCHECK(interleaved_output_ptr); + DCHECK_GE(num_input_frames, input_offset_frames); + const size_t max_num_input_frames = num_input_frames - input_offset_frames; + DCHECK_GE(num_output_frames, output_offset_frames); + const size_t max_num_output_frames = num_output_frames - output_offset_frames; + DCHECK_GE(max_num_input_frames, num_frames_convert_and_copy); + DCHECK_GE(max_num_output_frames, num_frames_convert_and_copy); + DCHECK_EQ(num_input_channels, num_output_channels); + + InterleavedOutputType interleaved_output_ptr_with_offset = + interleaved_output_ptr + output_offset_frames * num_output_channels; + + if (num_input_channels == kNumStereoChannels && + num_output_channels == kNumStereoChannels) { + const float* left_ptr = &input[0][input_offset_frames]; + const float* right_ptr = &input[1][input_offset_frames]; + InterleaveStereo(num_frames_convert_and_copy, left_ptr, right_ptr, + interleaved_output_ptr_with_offset); + } else { + for (size_t channel = 0; channel < num_output_channels; ++channel) { + const float* input_channel_ptr = &input[channel][input_offset_frames]; + size_t interleaved_index = channel; + for (size_t frame = 0; frame < num_frames_convert_and_copy; ++frame) { + ConvertSampleFromFloatFormat( + input_channel_ptr[frame], + &interleaved_output_ptr_with_offset[interleaved_index]); + interleaved_index += num_output_channels; + } + } + } +} + +} // namespace + +void PlanarFromInterleaved(const float* interleaved_buffer, + size_t num_input_frames, size_t num_input_channels, + const std::vector<float*>& planar_buffer_ptr, + size_t num_output_frames) { + DCHECK(interleaved_buffer); + DCHECK_GT(planar_buffer_ptr.size(), 0); + + const size_t num_frames_to_copy = + std::min(num_input_frames, num_output_frames); + ConvertInterleavedToPlanarTemplated<const float*, float* const*>( + interleaved_buffer, num_input_frames, num_input_channels, + 0 /* input_offset_frames */, nullptr /* channel_map*/, + planar_buffer_ptr.data(), num_output_frames, planar_buffer_ptr.size(), + 0 /* output_offset_frames */, num_frames_to_copy); +} + +void PlanarFromInterleaved(const int16* interleaved_buffer, + size_t num_input_frames, size_t num_input_channels, + const std::vector<float*>& planar_buffer_ptr, + size_t num_output_frames) { + DCHECK(interleaved_buffer); + DCHECK_GT(planar_buffer_ptr.size(), 0); + + const size_t num_frames_to_copy = + std::min(num_input_frames, num_output_frames); + ConvertInterleavedToPlanarTemplated<const int16*, float* const*>( + interleaved_buffer, num_input_frames, num_input_channels, + 0 /* input_offset_frames */, nullptr /* channel_map*/, + planar_buffer_ptr.data(), num_output_frames, planar_buffer_ptr.size(), + 0 /* output_offset_frames */, num_frames_to_copy); +} + +void FillAudioBuffer(const float* interleaved_buffer, size_t num_input_frames, + size_t num_input_channels, AudioBuffer* output) { + DCHECK(interleaved_buffer); + DCHECK(output); + const size_t num_frames_to_copy = + std::min(num_input_frames, output->num_frames()); + ConvertInterleavedToPlanarTemplated<const float*, AudioBuffer&>( + interleaved_buffer, num_input_frames, num_input_channels, + 0 /* input_offset_frames */, nullptr /* channel_map*/, *output, + output->num_frames(), output->num_channels(), + 0 /* output_offset_frames */, num_frames_to_copy); +} + +void FillAudioBuffer(const int16* interleaved_buffer, size_t num_input_frames, + size_t num_input_channels, AudioBuffer* output) { + DCHECK(interleaved_buffer); + DCHECK(output); + const size_t num_frames_to_copy = + std::min(num_input_frames, output->num_frames()); + + ConvertInterleavedToPlanarTemplated<const int16*, AudioBuffer&>( + interleaved_buffer, num_input_frames, num_input_channels, + 0 /* input_offset_frames */, nullptr /* channel_map*/, *output, + output->num_frames(), output->num_channels(), + 0 /* output_offset_frames */, num_frames_to_copy); +} + +void FillAudioBuffer(const std::vector<float>& interleaved_buffer, + size_t num_input_channels, AudioBuffer* output) { + DCHECK(output); + DCHECK_EQ(interleaved_buffer.size() % num_input_channels, 0); + const size_t num_frames_to_copy = std::min( + interleaved_buffer.size() / num_input_channels, output->num_frames()); + FillAudioBuffer(&interleaved_buffer[0], num_frames_to_copy, + num_input_channels, output); +} + +void FillAudioBuffer(const std::vector<int16>& interleaved_buffer, + size_t num_input_channels, AudioBuffer* output) { + DCHECK(output); + DCHECK_EQ(interleaved_buffer.size() % num_input_channels, 0); + const size_t num_frames_to_copy = std::min( + interleaved_buffer.size() / num_input_channels, output->num_frames()); + FillAudioBuffer(&interleaved_buffer[0], num_frames_to_copy, + num_input_channels, output); +} + +void FillAudioBuffer(const float* const* planar_ptrs, size_t num_input_frames, + size_t num_input_channels, AudioBuffer* output) { + DCHECK(planar_ptrs); + DCHECK(output); + const size_t num_frames_to_copy = + std::min(num_input_frames, output->num_frames()); + ConvertPlanarToPlanarTemplated<const float* const*, AudioBuffer&>( + planar_ptrs, num_input_frames, num_input_channels, + 0 /* input_offset_frames */, nullptr /* channel_map*/, *output, + output->num_frames(), output->num_channels(), + 0 /* output_offset_frames */, num_frames_to_copy); +} + +void FillAudioBuffer(const int16* const* planar_ptrs, size_t num_input_frames, + size_t num_input_channels, AudioBuffer* output) { + DCHECK(planar_ptrs); + DCHECK(output); + const size_t num_frames_to_copy = + std::min(num_input_frames, output->num_frames()); + ConvertPlanarToPlanarTemplated<const int16* const*, AudioBuffer&>( + planar_ptrs, num_input_frames, num_input_channels, + 0 /* input_offset_frames */, nullptr /* channel_map*/, *output, + output->num_frames(), output->num_channels(), + 0 /* output_offset_frames */, num_frames_to_copy); +} + +void FillAudioBufferWithOffset(const float* interleaved_buffer, + size_t num_input_frames, + size_t num_input_channels, + size_t input_frame_offset, + size_t output_frame_offset, + size_t num_frames_to_copy, AudioBuffer* output) { + DCHECK(interleaved_buffer); + DCHECK(output); + ConvertInterleavedToPlanarTemplated<const float*, AudioBuffer&>( + interleaved_buffer, num_input_frames, num_input_channels, + input_frame_offset, nullptr /* channel_map*/, *output, + output->num_frames(), output->num_channels(), output_frame_offset, + num_frames_to_copy); +} + +void FillAudioBufferWithOffset(const int16* interleaved_buffer, + size_t num_input_frames, + size_t num_input_channels, + size_t input_frame_offset, + size_t output_frame_offset, + size_t num_frames_to_copy, AudioBuffer* output) { + DCHECK(interleaved_buffer); + DCHECK(output); + ConvertInterleavedToPlanarTemplated<const int16*, AudioBuffer&>( + interleaved_buffer, num_input_frames, num_input_channels, + input_frame_offset, nullptr /* channel_map*/, *output, + output->num_frames(), output->num_channels(), output_frame_offset, + num_frames_to_copy); +} + +void FillAudioBufferWithOffset(const float* const* planar_ptrs, + size_t num_input_frames, + size_t num_input_channels, + size_t input_frame_offset, + size_t output_frame_offset, + size_t num_frames_to_copy, AudioBuffer* output) { + DCHECK(planar_ptrs); + DCHECK(output); + ConvertPlanarToPlanarTemplated<const float* const*, AudioBuffer&>( + planar_ptrs, num_input_frames, num_input_channels, input_frame_offset, + nullptr /* channel_map*/, *output, output->num_frames(), + output->num_channels(), output_frame_offset, num_frames_to_copy); +} + +void FillAudioBufferWithOffset(const int16* const* planar_ptrs, + size_t num_input_frames, + size_t num_input_channels, + size_t input_frame_offset, + size_t output_frame_offset, + size_t num_frames_to_copy, AudioBuffer* output) { + DCHECK(planar_ptrs); + DCHECK(output); + ConvertPlanarToPlanarTemplated<const int16* const*, AudioBuffer&>( + planar_ptrs, num_input_frames, num_input_channels, input_frame_offset, + nullptr /* channel_map*/, *output, output->num_frames(), + output->num_channels(), output_frame_offset, num_frames_to_copy); +} + +void FillAudioBufferWithChannelRemapping(const int16* interleaved_buffer, + size_t num_input_frames, + size_t num_input_channels, + const std::vector<size_t>& channel_map, + AudioBuffer* output) { + DCHECK(interleaved_buffer); + DCHECK(output); + const size_t num_frames_to_copy = + std::min(num_input_frames, output->num_frames()); + ConvertInterleavedToPlanarTemplated<const int16*, AudioBuffer&>( + interleaved_buffer, num_input_frames, num_input_channels, + 0 /*input_frame_offset*/, &channel_map, *output, output->num_frames(), + output->num_channels(), 0 /*output_frame_offset*/, num_frames_to_copy); +} + +void FillAudioBufferWithChannelRemapping(const float* interleaved_buffer, + size_t num_input_frames, + size_t num_input_channels, + const std::vector<size_t>& channel_map, + AudioBuffer* output) { + DCHECK(interleaved_buffer); + DCHECK(output); + const size_t num_frames_to_copy = + std::min(num_input_frames, output->num_frames()); + ConvertInterleavedToPlanarTemplated<const float*, AudioBuffer&>( + interleaved_buffer, num_input_frames, num_input_channels, + 0 /*input_frame_offset*/, &channel_map, *output, output->num_frames(), + output->num_channels(), 0 /*output_frame_offset*/, num_frames_to_copy); +} + +void FillAudioBufferWithChannelRemapping(const float* const* planar_ptrs, + size_t num_input_frames, + size_t num_input_channels, + const std::vector<size_t>& channel_map, + AudioBuffer* output) { + DCHECK(planar_ptrs); + DCHECK(output); + const size_t num_frames_to_copy = + std::min(num_input_frames, output->num_frames()); + ConvertPlanarToPlanarTemplated<const float* const*, AudioBuffer&>( + planar_ptrs, num_input_frames, num_input_channels, + 0 /*input_offset_frames*/, &channel_map, *output, output->num_frames(), + output->num_channels(), 0 /* output_offset_frames*/, num_frames_to_copy); +} + +void FillAudioBufferWithChannelRemapping(const int16* const* planar_ptr, + size_t num_input_frames, + size_t num_input_channels, + const std::vector<size_t>& channel_map, + AudioBuffer* output) { + DCHECK(planar_ptr); + DCHECK(output); + const size_t num_frames_to_copy = + std::min(num_input_frames, output->num_frames()); + ConvertPlanarToPlanarTemplated<const int16* const*, AudioBuffer&>( + planar_ptr, num_input_frames, num_input_channels, + 0 /*input_offset_frames*/, &channel_map, *output, output->num_frames(), + output->num_channels(), 0 /* output_offset_frames*/, num_frames_to_copy); +} + +void FillExternalBuffer(const AudioBuffer& input, std::vector<float>* output) { + DCHECK(output); + output->resize(input.num_frames() * input.num_channels()); + FillExternalBuffer(input, output->data(), input.num_frames(), + input.num_channels()); +} + +void FillExternalBuffer(const AudioBuffer& input, std::vector<int16>* output) { + DCHECK(output); + output->resize(input.num_frames() * input.num_channels()); + FillExternalBuffer(input, output->data(), input.num_frames(), + input.num_channels()); +} + +void FillExternalBuffer(const AudioBuffer& input, + int16* const* planar_output_ptrs, + size_t num_output_frames, size_t num_output_channels) { + ConvertPlanarToPlanarTemplated<const AudioBuffer&, int16* const*>( + input, input.num_frames(), input.num_channels(), + 0 /*input_offset_frames*/, nullptr /* channel_map*/, planar_output_ptrs, + num_output_frames, num_output_channels, 0 /* output_offset_frames*/, + num_output_frames); +} + +void FillExternalBuffer(const AudioBuffer& input, + float* const* planar_output_ptrs, + size_t num_output_frames, size_t num_output_channels) { + ConvertPlanarToPlanarTemplated<const AudioBuffer&, float* const*>( + input, input.num_frames(), input.num_channels(), + 0 /*input_offset_frames*/, nullptr /* channel_map*/, planar_output_ptrs, + num_output_frames, num_output_channels, 0 /* output_offset_frames*/, + num_output_frames); +} + +void FillExternalBuffer(const AudioBuffer& input, + int16* interleaved_output_buffer, + size_t num_output_frames, size_t num_output_channels) { + ConvertPlanarToInterleavedTemplated<const AudioBuffer&, int16*>( + input, input.num_frames(), input.num_channels(), + 0 /*input_offset_frames*/, interleaved_output_buffer, num_output_frames, + num_output_channels, 0 /* output_offset_frames*/, num_output_frames); +} + +void FillExternalBuffer(const AudioBuffer& input, + float* interleaved_output_buffer, + size_t num_output_frames, size_t num_output_channels) { + ConvertPlanarToInterleavedTemplated<const AudioBuffer&, float*>( + input, input.num_frames(), input.num_channels(), + 0 /*input_offset_frames*/, interleaved_output_buffer, num_output_frames, + num_output_channels, 0 /* output_offset_frames*/, num_output_frames); +} + +void FillExternalBufferWithOffset(const AudioBuffer& input, + size_t input_offset_frames, + int16* const* planar_output_ptrs, + size_t num_output_frames, + size_t num_output_channels, + size_t output_offset_frames, + size_t num_frames_convert_and_copy) { + ConvertPlanarToPlanarTemplated<const AudioBuffer&, int16* const*>( + input, input.num_frames(), input.num_channels(), input_offset_frames, + nullptr /* channel_map */, planar_output_ptrs, num_output_frames, + num_output_channels, output_offset_frames, num_frames_convert_and_copy); +} + +void FillExternalBufferWithOffset(const AudioBuffer& input, + size_t input_offset_frames, + float* const* planar_output_ptrs, + size_t num_output_frames, + size_t num_output_channels, + size_t output_offset_frames, + size_t num_frames_convert_and_copy) { + ConvertPlanarToPlanarTemplated<const AudioBuffer&, float* const*>( + input, input.num_frames(), input.num_channels(), input_offset_frames, + nullptr /* channel_map */, planar_output_ptrs, num_output_frames, + num_output_channels, output_offset_frames, num_frames_convert_and_copy); +} + +void FillExternalBufferWithOffset(const AudioBuffer& input, + size_t input_offset_frames, + int16* interleaved_output_buffer, + size_t num_output_frames, + size_t num_output_channels, + size_t output_offset_frames, + size_t num_frames_convert_and_copy) { + ConvertPlanarToInterleavedTemplated<const AudioBuffer&, int16*>( + input, input.num_frames(), input.num_channels(), input_offset_frames, + interleaved_output_buffer, num_output_frames, num_output_channels, + output_offset_frames, num_frames_convert_and_copy); +} + +void FillExternalBufferWithOffset(const AudioBuffer& input, + size_t input_offset_frames, + float* interleaved_output_buffer, + size_t num_output_frames, + size_t num_output_channels, + size_t output_offset_frames, + size_t num_frames_convert_and_copy) { + ConvertPlanarToInterleavedTemplated<const AudioBuffer&, float*>( + input, input.num_frames(), input.num_channels(), input_offset_frames, + interleaved_output_buffer, num_output_frames, num_output_channels, + output_offset_frames, num_frames_convert_and_copy); +} + +void GetRawChannelDataPointersFromAudioBuffer( + AudioBuffer* audio_buffer, std::vector<float*>* channel_ptr_vector) { + DCHECK(audio_buffer); + DCHECK(channel_ptr_vector); + DCHECK_EQ(audio_buffer->num_channels(), channel_ptr_vector->size()); + for (size_t i = 0; i < audio_buffer->num_channels(); ++i) { + (*channel_ptr_vector)[i] = &(*audio_buffer)[i][0]; + } +} + +void GetRawChannelDataPointersFromAudioBuffer( + const AudioBuffer& audio_buffer, + std::vector<const float*>* channel_ptr_vector) { + DCHECK(channel_ptr_vector); + DCHECK_EQ(audio_buffer.num_channels(), channel_ptr_vector->size()); + for (size_t i = 0; i < audio_buffer.num_channels(); ++i) { + (*channel_ptr_vector)[i] = &audio_buffer[i][0]; + } +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/planar_interleaved_conversion.h b/src/3rdparty/resonance-audio/resonance_audio/utils/planar_interleaved_conversion.h new file mode 100644 index 000000000..ed30be30e --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/planar_interleaved_conversion.h @@ -0,0 +1,421 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_UTILS_PLANAR_INTERLEAVED_CONVERSION_H_ +#define RESONANCE_AUDIO_UTILS_PLANAR_INTERLEAVED_CONVERSION_H_ + +#include <vector> + +#include "base/integral_types.h" +#include "base/audio_buffer.h" +#include "base/logging.h" + +namespace vraudio { + +// Copies interleaved audio data from a raw float pointer into separate channel +// buffers specified by a vector of raw float pointers. +// +// @param interleaved_buffer Raw float pointer to interleaved audio data. +// @param num_input_frames Size of |interleaved_buffer| in frames. +// @param num_input_channels Number of channels in interleaved audio data. +// @param planar_buffer_ptr Raw float pointers to each planar channel buffer. +// @param num_output_frames Number of frames per channel in output buffer. +void PlanarFromInterleaved(const float* interleaved_buffer, + size_t num_input_frames, size_t num_input_channels, + const std::vector<float*>& planar_buffer_ptr, + size_t num_output_frames); + +// Copies interleaved audio data from a raw int16 pointer into separate channel +// buffers specified by a vector of raw float pointers. +// +// @param interleaved_buffer Raw int16 pointer to interleaved audio data. +// @param num_input_frames Size of |interleaved_buffer| in frames. +// @param num_input_channels Number of channels in interleaved audio data. +// @param planar_buffer_ptr Raw float pointers to each planar channel buffer. +// @param num_output_frames Number of frames per channel in output buffer. +void PlanarFromInterleaved(const int16* interleaved_buffer, + size_t num_input_frames, size_t num_input_channels, + const std::vector<float*>& planar_buffer_ptr, + size_t num_output_frames); + +// Copies interleaved audio data from a raw float pointer into a planar +// |AudioBuffer|. Note that the number of output channels and frames is defined +// by the target |AudioBuffer| instance. +// +// @param interleaved_buffer Raw float pointer to interleaved audio data. +// @param num_input_frames Size of interleaved_buffer in frames. +// @param num_input_channels Number of channels in interleaved audio data. +// @param output Target output buffer. +void FillAudioBuffer(const float* interleaved_buffer, size_t num_input_frames, + size_t num_input_channels, AudioBuffer* output); + +// Copies interleaved audio data from a raw int16 pointer into a planar +// |AudioBuffer|. Note that the number of output channels and frames is defined +// by the target |AudioBuffer| instance. +// +// @param interleaved_buffer Raw int16 pointer to interleaved audio data. +// @param num_input_frames Size of interleaved_buffer in frames. +// @param num_input_channels Number of channels in interleaved audio data. +// @param output Target output buffer. +void FillAudioBuffer(const int16* interleaved_buffer, size_t num_input_frames, + size_t num_input_channels, AudioBuffer* output); + +// Copies interleaved audio data from a float vector into a planar +// |AudioBuffer|. Note that the number of output channels and frames is defined +// by the target |AudioBuffer| instance. +// +// @param interleaved_buffer Interleaved audio data. +// @param num_input_channels Number of channels in interleaved audio data. +// @param output Target output buffer. +void FillAudioBuffer(const std::vector<float>& interleaved_buffer, + size_t num_input_channels, AudioBuffer* output); + +// Copies interleaved audio data from a int16 vector into a planar +// |AudioBuffer|. Note that the number of output channels and frames is defined +// by the target |AudioBuffer| instance. +// +// @param interleaved_buffer Interleaved audio data. +// @param num_input_channels Number of channels in interleaved audio data. +// @param output Target output buffer. +void FillAudioBuffer(const std::vector<int16>& interleaved_buffer, + size_t num_input_channels, AudioBuffer* output); + +// Copies raw planar float audio data into a planar |AudioBuffer|. Note that the +// number of output channels and frames is defined by the target |AudioBuffer| +// instance. +// +// @param planar_ptrs Pointer to an array of pointers to raw float channel data. +// @param num_input_frames Size of planar input in frames. +// @param num_input_channels Number of channels in planar input buffer. +// @param output Target output buffer. +void FillAudioBuffer(const float* const* planar_ptrs, size_t num_input_frames, + size_t num_input_channels, AudioBuffer* output); + +// Copies raw planar int16 audio data into a planar |AudioBuffer|. Note that the +// number of output channels and frames is defined by the target |AudioBuffer| +// instance. +// +// @param planar_ptrs Pointer to an array of pointers to raw int16 channel data. +// @param num_input_frames Size of planar input in frames. +// @param num_input_channels Number of channels in planar input buffer. +// @param output Target output buffer. +void FillAudioBuffer(const int16* const* planar_ptrs, size_t num_input_frames, + size_t num_input_channels, AudioBuffer* output); + +// Copies interleaved audio data from a raw float pointer into a planar +// |AudioBuffer| with a specified output frame offset. Note that the +// number of output channels is defined by the target |AudioBuffer| instance. +// +// @param interleaved_buffer Raw float pointer to interleaved audio data. +// @param num_input_frames Size of |interleaved_buffer| in frames. +// @param num_input_channels Number of channels in interleaved audio data. +// @param input_frame_offset First source frame position in input. +// @param output_frame_offset First frame destination in output. +// @param num_frames_to_copy Number of frames per copy. +// @param output Target output buffer. +void FillAudioBufferWithOffset(const float* interleaved_buffer, + size_t num_input_frames, + size_t num_input_channels, + size_t input_frame_offset, + size_t output_frame_offset, + size_t num_frames_to_copy, AudioBuffer* output); + +// Copies interleaved audio data from a raw int16 pointer into a planar +// |AudioBuffer| with a specified output frame offset. Note that the +// number of output channels is defined by the target |AudioBuffer| instance. +// +// @param interleaved_buffer Raw int16 pointer to interleaved audio data. +// @param num_input_frames Size of |interleaved_buffer| in frames. +// @param num_input_channels Number of channels in interleaved audio data. +// @param input_frame_offset First source frame position in input. +// @param output_frame_offset First frame destination in output. +// @param num_frames_to_copy Number of frames per copy. +// @param output Target output buffer. +void FillAudioBufferWithOffset(const int16* interleaved_buffer, + size_t num_input_frames, + size_t num_input_channels, + size_t input_frame_offset, + size_t output_frame_offset, + size_t num_frames_to_copy, AudioBuffer* output); + +// Copies raw planar float audio data into a planar |AudioBuffer| with a +// specified output frame offset. Note that the number of output channels is +// defined by the target |AudioBuffer| instance. +// +// @param planar_ptrs Pointer to an array of pointers to raw float channel data. +// @param num_input_frames Size of |interleaved_buffer| in frames. +// @param num_input_channels Number of channels in interleaved audio data. +// @param input_frame_offset First source frame position in input. +// @param output_frame_offset First frame destination in output. +// @param num_frames_to_copy Number of frames per copy. +// @param output Target output buffer. +void FillAudioBufferWithOffset(const float* const* planar_ptrs, + size_t num_input_frames, + size_t num_input_channels, + size_t input_frame_offset, + size_t output_frame_offset, + size_t num_frames_to_copy, AudioBuffer* output); + +// Copies raw planar int16 audio data into a planar |AudioBuffer| with a +// specified output frame offset. Note that the number of output channels is +// defined by the target |AudioBuffer| instance. +// +// @param planar_ptrs Pointer to an array of pointers to raw int16 channel data. +// @param num_input_frames Size of |interleaved_buffer| in frames. +// @param num_input_channels Number of channels in interleaved audio data. +// @param input_frame_offset First source frame position in input. +// @param output_frame_offset First frame destination in output. +// @param num_frames_to_copy Number of frames per copy. +// @param output Target output buffer. +void FillAudioBufferWithOffset(const int16* const* planar_ptrs, + size_t num_input_frames, + size_t num_input_channels, + size_t input_frame_offset, + size_t output_frame_offset, + size_t num_frames_to_copy, AudioBuffer* output); + +// Copies interleaved audio data from a raw int16 pointer into a planar +// |AudioBuffer|. The |channel_map| argument allows to reorder the channel +// mapping between the interleaved input and output buffer. The i'th channel in +// the output buffer corresponds to the |channel_map[i]|'th input channel. Note +// that the number of output channels and frames is derived from the target +// |AudioBuffer| instance. +// +// @param interleaved_buffer Raw int16 pointer to interleaved audio data. +// @param num_input_frames Size of interleaved_buffer in frames. +// @param num_input_channels Number of input channels. +// @param channel_map Mapping that maps output channels to input channels +// @param output Target output buffer. +void FillAudioBufferWithChannelRemapping(const int16* interleaved_buffer, + size_t num_input_frames, + size_t num_input_channels, + const std::vector<size_t>& channel_map, + AudioBuffer* output); + +// Copies interleaved audio data from a raw float pointer into a planar +// |AudioBuffer|. The |channel_map| argument allows to reorder the channel +// mapping between the interleaved input and output buffer. The i'th channel in +// the output buffer corresponds to the |channel_map[i]| input channel. Note +// that the number of output channels and frames is derived from the target +// |AudioBuffer| instance. +// +// @param interleaved_buffer Raw float pointer to interleaved audio data. +// @param num_input_frames Size of interleaved_buffer in frames. +// @param num_input_channels Number of input channels. +// @param channel_map Mapping that maps output channels to input channels +// @param output Target output buffer. +void FillAudioBufferWithChannelRemapping(const float* interleaved_buffer, + size_t num_input_frames, + size_t num_input_channels, + const std::vector<size_t>& channel_map, + AudioBuffer* output); + +// Copies raw planar float audio data into a planar |AudioBuffer|. The +// |channel_map| argument allows to reorder the channel mapping between the +// planar input and output buffer. The i'th channel in the output buffer +// corresponds to the |channel_map[i]| input channel. Note that the number of +// output channels and frames is derived from the target |AudioBuffer| instance. +// +// @param planar_ptrs Pointer to an array of pointers to raw float channel data. +// @param num_input_frames Size of interleaved_buffer in frames. +// @param num_input_channels Number of input channels. +// @param channel_map Mapping that maps output channels to input channels +// @param output Target output buffer. +void FillAudioBufferWithChannelRemapping(const float* const* planar_ptr, + size_t num_input_frames, + size_t num_input_channels, + const std::vector<size_t>& channel_map, + AudioBuffer* output); + +// Copies raw planar int16 audio data into a planar |AudioBuffer|. The +// |channel_map| argument allows to reorder the channel mapping between the +// planar input and output buffer. The i'th channel in the output buffer +// corresponds to the |channel_map[i]| input channel. Note that the number of +// output channels and frames is derived from the target |AudioBuffer| instance. +// +// @param planar_ptrs Pointer to an array of pointers to raw int16 channel data. +// @param num_input_frames Size of interleaved_buffer in frames. +// @param num_input_channels Number of input channels. +// @param channel_map Mapping that maps output channels to input channels +// @param output Target output buffer. +void FillAudioBufferWithChannelRemapping(const int16* const* planar_ptr, + size_t num_input_frames, + size_t num_input_channels, + const std::vector<size_t>& channel_map, + AudioBuffer* output); + +// Copies planar audio data from an |AudioBuffer| to an external interleaved +// float vector. Note this method resizes the target vector to match number of +// input samples. +// +// @param input Input audio buffer. +// @param output interleaved output vector. +void FillExternalBuffer(const AudioBuffer& input, std::vector<float>* output); + +// Copies and converts planar audio data from an |AudioBuffer| to an external +// interleaved int16 vector. Note this method resizes the target vector to match +// number of input samples. +// +// @param input Input audio buffer. +// @param output interleaved output vector. +void FillExternalBuffer(const AudioBuffer& input, std::vector<int16>* output); + +// Copies planar audio data from an |AudioBuffer| to an external planar raw +// float buffer. Note that the input and output buffer must match in terms of +// number of channels and frames. +// +// @param input Input audio buffer. +// @param planar_output_ptrs Planar output vector. +// @param num_output_frames Number of frames in output buffer. +// @param num_output_channels Number of channels in output buffer. +void FillExternalBuffer(const AudioBuffer& input, + float* const* planar_output_ptrs, + size_t num_output_frames, size_t num_output_channels); + +// Copies and converts audio data from an |AudioBuffer| to an external planar +// int16 buffer. Note that the input and output buffer must match in terms of +// number of channels and frames. +// +// @param input Input audio buffer. +// @param planar_output_ptrs Planar output vector. +// @param num_output_frames Number of frames in output buffer. +// @param num_output_channels Number of channels in output buffer. +void FillExternalBuffer(const AudioBuffer& input, + int16* const* planar_output_ptrs, + size_t num_output_frames, size_t num_output_channels); + +// Copies and converts planar audio data from an |AudioBuffer| to an external +// interleaved raw int16 buffer. Note that the input and output buffer must +// match in terms of number of channels and frames. +// +// @param input Input audio buffer. +// @param interleaved_output_buffer Interleaved output vector. +// @param num_output_frames Number of frames in output buffer. +// @param num_output_channels Number of channels in output buffer. +void FillExternalBuffer(const AudioBuffer& input, + int16* interleaved_output_buffer, + size_t num_output_frames, size_t num_output_channels); + +// Copies planar audio data from an |AudioBuffer| to an external interleaved +// raw float buffer. Note that the input and output buffer must match in terms +// of number of channels and frames. +// +// @param input Input audio buffer. +// @param interleaved_output_buffer Interleaved output vector. +// @param num_output_frames Number of frames in output buffer. +// @param num_output_channels Number of channels in output buffer. +void FillExternalBuffer(const AudioBuffer& input, + float* interleaved_output_buffer, + size_t num_output_frames, size_t num_output_channels); + +// Copies and converts audio data from an |AudioBuffer| to an external +// planar raw int16 buffer with the ability to specify an offset into the +// input and output buffer. +// +// @param input Input audio buffer. +// @param input_offset_frames Offset into input buffer in frames. +// @param planar_output_ptrs Planar output vector. +// @param num_output_frames Number of frames in output buffer. +// @param num_output_channels Number of channels in output buffer. +// @param output_offset_frames Offset into the output buffer in frames. +// @param num_frames_convert_and_copy Number of frames to be processed. +void FillExternalBufferWithOffset(const AudioBuffer& input, + size_t input_offset_frames, + int16* const* planar_output_ptrs, + size_t num_output_frames, + size_t num_output_channels, + size_t output_offset_frames, + size_t num_frames_convert_and_copy); + +// Copies audio data from an |AudioBuffer| to an external planar raw float +// buffer with the ability to specify an offset into the input and output +// buffer. +// +// @param input Input audio buffer. +// @param input_offset_frames Offset into input buffer in frames. +// @param planar_output_ptrs Planar output vector. +// @param num_output_frames Number of frames in output buffer. +// @param num_output_channels Number of channels in output buffer. +// @param output_offset_frames Offset into the output buffer in frames. +// @param num_frames_convert_and_copy Number of frames to be processed. +void FillExternalBufferWithOffset(const AudioBuffer& input, + size_t input_offset_frames, + float* const* planar_output_ptrs, + size_t num_output_frames, + size_t num_output_channels, + size_t output_offset_frames, + size_t num_frames_convert_and_copy); + +// Copies and converts audio data from an |AudioBuffer| to an external +// interleaved raw int16 buffer with the ability to specify an offset into the +// input and output buffer. +// +// @param input Input audio buffer. +// @param input_offset_frames Offset into input buffer in frames. +// @param interleaved_output_buffer Interleaved output vector. +// @param num_output_frames Number of frames in output buffer. +// @param num_output_channels Number of channels in output buffer. +// @param output_offset_frames Offset into the output buffer in frames. +// @param num_frames_convert_and_copy Number of frames to be processed. +void FillExternalBufferWithOffset(const AudioBuffer& input, + size_t input_offset_frames, + int16* interleaved_output_buffer, + size_t num_output_frames, + size_t num_output_channels, + size_t output_offset_frames, + size_t num_frames_convert_and_copy); + +// Copies and audio data from an |AudioBuffer| to an external interleaved raw +// float buffer with the ability to specify an offset into the input and output +// buffer. +// +// @param input Input audio buffer. +// @param input_offset_frames Offset into input buffer in frames. +// @param interleaved_output_buffer Interleaved output vector. +// @param num_output_frames Number of frames in output buffer. +// @param num_output_channels Number of channels in output buffer. +// @param output_offset_frames Offset into the output buffer in frames. +// @param num_frames_convert_and_copy Number of frames to be processed. +void FillExternalBufferWithOffset(const AudioBuffer& input, + size_t input_offset_frames, + float* interleaved_output_buffer, + size_t num_output_frames, + size_t num_output_channels, + size_t output_offset_frames, + size_t num_frames_convert_and_copy); + +// Generates a vector of mutable float pointers to the beginning of each channel +// buffer in an |AudioBuffer|. The size of the |channel_ptr_vector| output +// vector must match the number of channels in |audio_buffer|. +// +// @param audio_buffer Audio buffer. +// @param channel_ptr_vector Output std::vector<float*> vector. +void GetRawChannelDataPointersFromAudioBuffer( + AudioBuffer* audio_buffer, std::vector<float*>* channel_ptr_vector); + +// Generates a vector of immutable float pointers to the beginning of each +// channel buffer in an |AudioBuffer|. The size of the |channel_ptr_vector| +// output vector must match the number of channels in |audio_buffer|. +// +// @param audio_buffer Audio buffer. +// @param channel_ptr_vector Output std::vector<const float*> vector. +void GetRawChannelDataPointersFromAudioBuffer( + const AudioBuffer& audio_buffer, + std::vector<const float*>* channel_ptr_vector); + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_UTILS_PLANAR_INTERLEAVED_CONVERSION_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/planar_interleaved_conversion_test.cc b/src/3rdparty/resonance-audio/resonance_audio/utils/planar_interleaved_conversion_test.cc new file mode 100644 index 000000000..abfd31b82 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/planar_interleaved_conversion_test.cc @@ -0,0 +1,875 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "utils/planar_interleaved_conversion.h" + +#include <algorithm> + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/audio_buffer.h" +#include "base/constants_and_types.h" +#include "base/misc_math.h" + +namespace vraudio { + +namespace { + +// Epsilon for conversion from int16_t back to float. +const float kFloatEpsilon = 1e-4f; +const int16_t kIntEpsilon = 1; + +const int kMemoryAlignmentBytesInt = static_cast<int>(kMemoryAlignmentBytes); + +const size_t kMaxNumFrames = 16; +const size_t kMaxNumChannels = 8; + +int16_t SingleFloatToInt16(float value) { + const float kInt16Min = static_cast<float>(-0x7FFF); + const float kInt16Max = static_cast<float>(0x7FFF); + const float scaled_value = value * kInt16Max; + const float clamped_value = + std::min(kInt16Max, std::max(kInt16Min, scaled_value)); + return static_cast<int16_t>(clamped_value); +} + +// Creates a trivial channel map, i.e. no mapping. +std::vector<size_t> GetTrivialChannelMap(size_t size) { + std::vector<size_t> channel_map(size); + for (size_t i = 0; i < size; ++i) { + channel_map[i] = i; + } + return channel_map; +} + +// Fills an interleaved buffer with the channel number / 10, in each frame +// (converted to integer format). +void FillInterleaved(size_t num_channels, size_t num_frames, int16_t* buffer) { + for (size_t f = 0; f < num_frames; ++f) { + for (size_t c = 0; c < num_channels; ++c) { + buffer[f * num_channels + c] = + SingleFloatToInt16(static_cast<float>(c) * 0.1f); + } + } +} + +// Fills an interleaved buffer with the channel number / 10, in each frame. +void FillInterleaved(size_t num_channels, size_t num_frames, float* buffer) { + for (size_t f = 0; f < num_frames; ++f) { + for (size_t c = 0; c < num_channels; ++c) { + buffer[f * num_channels + c] = static_cast<float>(c) * 0.1f; + } + } +} + +// Fills a planar buffer with the channel number / 10, in each frame. +void FillPlanar(AudioBuffer* buffer) { + for (size_t c = 0; c < buffer->num_channels(); ++c) { + std::fill_n(&(*buffer)[c][0], buffer->num_frames(), + static_cast<float>(c) * 0.1f); + } +} + +// Fills a planar buffer with the channel number / 10, in each frame. +void FillPlanar(size_t num_frames, std::vector<float*>* buffer) { + for (size_t c = 0; c < buffer->size(); ++c) { + std::fill_n((*buffer)[c], num_frames, static_cast<float>(c) * 0.1f); + } +} + +// Fills a planar buffer with the channel number / 10, in each frame (converted +// to integer format). +void FillPlanar(size_t num_frames, int16_t** buffer, size_t num_channels) { + for (size_t c = 0; c < num_channels; ++c) { + std::fill_n(buffer[c], num_frames, + SingleFloatToInt16(static_cast<float>(c) * 0.1f)); + } +} + +// Fills a planar buffer with the channel number / 10, in each frame (converted +// to integer format). +void FillPlanar(size_t num_frames, std::vector<int16_t*>* buffer) { + FillPlanar(num_frames, buffer->data(), buffer->size()); +} + +// Verifies that the output and expected output match. +void VerifyOutput(size_t num_channels, size_t num_frames, + const std::vector<int16_t*>& expected_output, + const std::vector<int16_t*>& output) { + for (size_t c = 0; c < num_channels; ++c) { + for (size_t f = 0; f < num_frames; ++f) { + EXPECT_NEAR(expected_output[c][f], output[c][f], kIntEpsilon); + } + } +} + +// Verifies that the output and expected output match. +template <typename InputType, typename OutputType> +void VerifyOutputFloatTemplated(size_t num_channels, size_t num_frames, + const InputType& expected_output, + const OutputType& output, + const std::vector<size_t>& channel_map, + size_t output_offset) { + for (size_t c = 0; c < num_channels; ++c) { + for (size_t f = output_offset; f < output_offset + num_frames; ++f) { + EXPECT_NEAR(expected_output[channel_map[c]][f], output[c][f], + kFloatEpsilon); + } + } +} + +// Verifies that the output and expected output match. +void VerifyOutput(size_t num_channels, size_t num_frames, + const AudioBuffer& expected_output, + const AudioBuffer& output) { + VerifyOutputFloatTemplated<AudioBuffer, AudioBuffer>( + num_channels, num_frames, expected_output, output, + GetTrivialChannelMap(num_channels), 0); +} + +// Verifies that the output and expected output match. +void VerifyOutput(size_t num_channels, size_t num_frames, + const std::vector<float*>& expected_output, + const AudioBuffer& output) { + VerifyOutputFloatTemplated<std::vector<float*>, AudioBuffer>( + num_channels, num_frames, expected_output, output, + GetTrivialChannelMap(num_channels), 0); +} + +// Verifies that the output and expected output match. +void VerifyOutput(size_t num_channels, size_t num_frames, + const AudioBuffer& expected_output, + const std::vector<float*>& output) { + VerifyOutputFloatTemplated<AudioBuffer, std::vector<float*>>( + num_channels, num_frames, expected_output, output, + GetTrivialChannelMap(num_channels), 0); +} + +// Verifies that the output and expected output match. +void VerifyOutput(size_t num_channels, size_t num_frames, + const std::vector<float*>& expected_output, + const std::vector<float*>& output) { + VerifyOutputFloatTemplated<std::vector<float*>, std::vector<float*>>( + num_channels, num_frames, expected_output, output, + GetTrivialChannelMap(num_channels), 0); +} + +// Verifies that the output and expected output match. +void VerifyOutput(size_t num_channels, size_t num_frames, + const std::vector<const float*>& expected_output, + const std::vector<const float*>& output) { + VerifyOutputFloatTemplated<std::vector<const float*>, + std::vector<const float*>>( + num_channels, num_frames, expected_output, output, + GetTrivialChannelMap(num_channels), 0); +} + +// Verifies that the output and expected output match. +void VerifyOutput(size_t num_channels, size_t num_frames, + const AudioBuffer& expected_output, const AudioBuffer& output, + const std::vector<size_t>& channel_map) { + VerifyOutputFloatTemplated<AudioBuffer, AudioBuffer>( + num_channels, num_frames, expected_output, output, channel_map, 0); +} + +// Verifies that the output and expected output match. +void VerifyOutput(size_t num_channels, size_t num_frames, size_t output_offset, + const AudioBuffer& expected_output, + const AudioBuffer& output) { + VerifyOutputFloatTemplated<AudioBuffer, AudioBuffer>( + num_channels, num_frames, expected_output, output, + GetTrivialChannelMap(num_channels), output_offset); +} + +// Verifies that the output and expected output match. +void VerifyOutput(size_t length, const std::vector<float>& expected_output, + int16_t* output) { + for (size_t f = 0; f < length; ++f) { + EXPECT_NEAR(SingleFloatToInt16(expected_output[f]), output[f], kIntEpsilon); + } +} + +// Verifies that the output and expected output match. +void VerifyOutput(size_t length, const std::vector<float>& expected_output, + const std::vector<int16_t>& output) { + for (size_t f = 0; f < length; ++f) { + EXPECT_NEAR(SingleFloatToInt16(expected_output[f]), output[f], kIntEpsilon); + } +} + +// Verifies that the output and expected output match. +void VerifyOutput(size_t length, const std::vector<float>& expected_output, + float* output) { + for (size_t f = 0; f < length; ++f) { + EXPECT_NEAR(expected_output[f], output[f], kIntEpsilon); + } +} + +// Verifies that the output and expected output match. +void VerifyOutput(size_t length, const std::vector<float>& expected_output, + const std::vector<float>& output) { + for (size_t f = 0; f < length; ++f) { + EXPECT_NEAR(expected_output[f], output[f], kIntEpsilon); + } +} + +typedef std::tuple<size_t, size_t> TestParams; +class PlanarInterleavedConverterTest + : public ::testing::Test, + public ::testing::WithParamInterface<TestParams> { + protected: + PlanarInterleavedConverterTest() {} + + // Virtual methods from ::testing::Test + ~PlanarInterleavedConverterTest() override {} + + void SetUp() override {} + + void TearDown() override {} +}; + +// Tests that interleaved (float/int16_t) data can be correctly written into +// vectors of float pointers. +TEST_P(PlanarInterleavedConverterTest, TestInterleavedIntoVectorFloatPtr) { + const size_t num_channels = ::testing::get<0>(GetParam()); + const size_t num_frames = ::testing::get<1>(GetParam()); + const size_t num_interleaved_samples = num_frames * num_channels; + + AudioBuffer expected_output(kMaxNumChannels, num_frames); + FillPlanar(&expected_output); + + // Create output buffers memory. + AudioBuffer aligned_output_buffer(kMaxNumChannels, num_frames); + std::vector<float> unaligned_output_memory(num_interleaved_samples); + + // Create output planar buffers. + std::vector<float*> aligned_output(num_channels); + std::vector<float*> unaligned_output(num_channels); + for (size_t c = 0; c < num_channels; ++c) { + aligned_output[c] = &aligned_output_buffer[c][0]; + unaligned_output[c] = &unaligned_output_memory[num_frames * c]; + } + + // Integer. + AudioBuffer::AlignedInt16Vector interleaved_aligned_int( + num_interleaved_samples); + std::vector<int16_t> interleaved_unaligned_int(num_interleaved_samples); + FillInterleaved(num_channels, num_frames, interleaved_aligned_int.data()); + FillInterleaved(num_channels, num_frames, &interleaved_unaligned_int[0]); + + // Aligned Input, Aligned Output. + aligned_output_buffer.Clear(); + LOG(INFO) << "aligned_output.size()" << aligned_output.size(); + LOG(INFO) << "num_frames" << num_frames; + PlanarFromInterleaved(interleaved_aligned_int.data(), num_frames, + num_channels, aligned_output, num_frames); + VerifyOutput(num_channels, std::min(num_frames, num_frames), expected_output, + aligned_output); + + // Unaligned Input, Aligned Output. + aligned_output_buffer.Clear(); + PlanarFromInterleaved(interleaved_unaligned_int.data(), num_frames, + num_channels, aligned_output, num_frames); + VerifyOutput(num_channels, std::min(num_frames, num_frames), expected_output, + aligned_output); + + // Aligned Input. Unaligned Output. + std::fill(unaligned_output_memory.begin(), unaligned_output_memory.end(), + 0.0f); + PlanarFromInterleaved(interleaved_aligned_int.data(), num_frames, + num_channels, unaligned_output, num_frames); + VerifyOutput(num_channels, std::min(num_frames, num_frames), expected_output, + unaligned_output); + // Unligned Input. Unaligned Output. + std::fill(unaligned_output_memory.begin(), unaligned_output_memory.end(), + 0.0f); + PlanarFromInterleaved(&interleaved_unaligned_int[0], num_frames, num_channels, + unaligned_output, num_frames); + VerifyOutput(num_channels, std::min(num_frames, num_frames), expected_output, + unaligned_output); + + // Floating point. + AudioBuffer::AlignedFloatVector interleaved_aligned_float( + num_interleaved_samples); + std::vector<float> interleaved_plane_float(num_interleaved_samples); + FillInterleaved(num_channels, num_frames, interleaved_aligned_float.data()); + FillInterleaved(num_channels, num_frames, &interleaved_plane_float[0]); + // Aligned Input, Aligned Output. + aligned_output_buffer.Clear(); + PlanarFromInterleaved(interleaved_aligned_float.data(), num_frames, + num_channels, aligned_output, num_frames); + VerifyOutput(num_channels, std::min(num_frames, num_frames), expected_output, + aligned_output); + // Unaligned Input, Aligned Output. + aligned_output_buffer.Clear(); + PlanarFromInterleaved(&interleaved_plane_float[0], num_frames, num_channels, + aligned_output, num_frames); + VerifyOutput(num_channels, std::min(num_frames, num_frames), expected_output, + aligned_output); + // Aligned Input. Unaligned Output. + std::fill(unaligned_output_memory.begin(), unaligned_output_memory.end(), + 0.0f); + PlanarFromInterleaved(interleaved_aligned_float.data(), num_frames, + num_channels, unaligned_output, num_frames); + VerifyOutput(num_channels, std::min(num_frames, num_frames), expected_output, + unaligned_output); + // Unligned Input. Unaligned Output. + std::fill(unaligned_output_memory.begin(), unaligned_output_memory.end(), + 0.0f); + PlanarFromInterleaved(&interleaved_plane_float[0], num_frames, num_channels, + unaligned_output, num_frames); + VerifyOutput(num_channels, std::min(num_frames, num_frames), expected_output, + unaligned_output); +} + +// Tests that interleaved (float/int16_t) data can be correctly written into +// |AudioBuffer|s. +TEST_P(PlanarInterleavedConverterTest, TestInterleavedIntoAudioBuffer) { + const size_t num_channels = ::testing::get<0>(GetParam()); + const size_t num_frames = ::testing::get<1>(GetParam()); + const size_t num_interleaved_samples = num_frames * num_channels; + + AudioBuffer expected_output(num_channels, num_frames); + FillPlanar(&expected_output); + + // Create output buffers memory. + AudioBuffer output_buffer(num_channels, num_frames); + + // Integer. + AudioBuffer::AlignedInt16Vector interleaved_aligned_int( + num_interleaved_samples); + std::vector<int16_t> interleaved_unaligned_int(num_interleaved_samples); + FillInterleaved(num_channels, num_frames, interleaved_aligned_int.data()); + FillInterleaved(num_channels, num_frames, &interleaved_unaligned_int[0]); + // Aligned Input, Aligned Output. + output_buffer.Clear(); + FillAudioBuffer(interleaved_aligned_int.data(), num_frames, num_channels, + &output_buffer); + VerifyOutput(num_channels, std::min(num_frames, num_frames), expected_output, + output_buffer); + // Unaligned Input, Aligned Output. + output_buffer.Clear(); + FillAudioBuffer(&interleaved_unaligned_int[0], num_frames, num_channels, + &output_buffer); + VerifyOutput(num_channels, std::min(num_frames, num_frames), expected_output, + output_buffer); + + // Floating point. + AudioBuffer::AlignedFloatVector interleaved_aligned_float( + num_interleaved_samples); + std::vector<float> interleaved_plane_float(num_interleaved_samples); + FillInterleaved(num_channels, num_frames, interleaved_aligned_float.data()); + FillInterleaved(num_channels, num_frames, &interleaved_plane_float[0]); + // Aligned Input, Aligned Output. + output_buffer.Clear(); + FillAudioBuffer(interleaved_aligned_float.data(), num_frames, num_channels, + &output_buffer); + VerifyOutput(num_channels, std::min(num_frames, num_frames), expected_output, + output_buffer); + // Unaligned Input, Aligned Output. + output_buffer.Clear(); + FillAudioBuffer(&interleaved_plane_float[0], num_frames, num_channels, + &output_buffer); + VerifyOutput(num_channels, std::min(num_frames, num_frames), expected_output, + output_buffer); +} + +// Tests that interleaved (float/int16_t) vectors can be correctly written into +// |AudioBuffer|s. +TEST_P(PlanarInterleavedConverterTest, TestInterleavedVectorIntoAudioBuffer) { + const size_t num_channels = ::testing::get<0>(GetParam()); + const size_t num_frames = ::testing::get<1>(GetParam()); + const size_t num_interleaved_samples = num_frames * num_channels; + + AudioBuffer expected_output(num_channels, num_frames); + FillPlanar(&expected_output); + + // Create output buffers memory. + AudioBuffer output_buffer(num_channels, num_frames); + + // Integer. + std::vector<int16_t> interleaved_unaligned_int(num_interleaved_samples); + FillInterleaved(num_channels, num_frames, &interleaved_unaligned_int[0]); + // Unaligned Input, Aligned Output. + output_buffer.Clear(); + FillAudioBuffer(&interleaved_unaligned_int[0], num_frames, num_channels, + &output_buffer); + VerifyOutput(num_channels, std::min(num_frames, num_frames), expected_output, + output_buffer); + + // Floating point. + std::vector<float> interleaved_plane_float(num_interleaved_samples); + FillInterleaved(num_channels, num_frames, &interleaved_plane_float[0]); + // Unaligned Input, Aligned Output. + output_buffer.Clear(); + FillAudioBuffer(&interleaved_plane_float[0], num_frames, num_channels, + &output_buffer); + VerifyOutput(num_channels, std::min(num_frames, num_frames), expected_output, + output_buffer); +} + +// Tests that vectors of planar (float/int16_t) pointers can be correctly +// written into |AudioBuffer|s. +TEST_P(PlanarInterleavedConverterTest, TestPlanarPtrsIntoAudioBuffer) { + const size_t num_channels = ::testing::get<0>(GetParam()); + const size_t num_frames = ::testing::get<1>(GetParam()); + + AudioBuffer expected_output(kMaxNumChannels, num_frames); + FillPlanar(&expected_output); + + // Create output buffers memory. + AudioBuffer output_buffer(num_channels, num_frames); + + // Integer. + std::vector<AudioBuffer::AlignedInt16Vector> planar_aligned_channels_int_buf( + num_channels, AudioBuffer::AlignedInt16Vector(num_frames)); + std::vector<std::vector<int16_t>> planar_unaligned_channels_int_buffers( + num_channels, std::vector<int16_t>(num_frames)); + std::vector<int16_t*> planar_aligned_channels_int(num_channels); + std::vector<int16_t*> planar_unaligned_channels_int(num_channels); + for (size_t c = 0; c < num_channels; ++c) { + planar_aligned_channels_int[c] = planar_aligned_channels_int_buf[c].data(); + planar_unaligned_channels_int[c] = + planar_unaligned_channels_int_buffers[c].data(); + } + FillPlanar(num_frames, &planar_aligned_channels_int); + FillPlanar(num_frames, &planar_unaligned_channels_int); + // Aligned Input, Aligned Output. + output_buffer.Clear(); + FillAudioBuffer(&planar_aligned_channels_int[0], num_frames, num_channels, + &output_buffer); + VerifyOutput(num_channels, std::min(num_frames, num_frames), expected_output, + output_buffer); + // Unaligned Input, Aligned Output. + output_buffer.Clear(); + FillAudioBuffer(&planar_unaligned_channels_int[0], num_frames, num_channels, + &output_buffer); + VerifyOutput(num_channels, std::min(num_frames, num_frames), expected_output, + output_buffer); + + // Floating point. + std::vector<AudioBuffer::AlignedFloatVector> + planar_aligned_channels_float_buf( + num_channels, AudioBuffer::AlignedFloatVector(num_frames)); + std::vector<std::vector<float>> planar_unaligned_channels_float_buffers( + num_channels, std::vector<float>(num_frames)); + std::vector<float*> planar_aligned_channels_float(num_channels); + std::vector<float*> planar_unaligned_channels_float(num_channels); + for (size_t c = 0; c < num_channels; ++c) { + planar_aligned_channels_float[c] = + planar_aligned_channels_float_buf[c].data(); + planar_unaligned_channels_float[c] = + planar_unaligned_channels_float_buffers[c].data(); + } + FillPlanar(num_frames, &planar_aligned_channels_float); + FillPlanar(num_frames, &planar_unaligned_channels_float); + // Aligned Input, Aligned Output. + output_buffer.Clear(); + FillAudioBuffer(&planar_aligned_channels_float[0], num_frames, num_channels, + &output_buffer); + VerifyOutput(num_channels, std::min(num_frames, num_frames), expected_output, + output_buffer); + // Unaligned Input, Aligned Output. + output_buffer.Clear(); + FillAudioBuffer(&planar_unaligned_channels_float[0], num_frames, num_channels, + &output_buffer); + VerifyOutput(num_channels, std::min(num_frames, num_frames), expected_output, + output_buffer); +} + +// Tests that interleaved (float/int16_t) data can be correctly written into +// |AudioBuffer|s, with offsets into both the input and output data. +TEST_P(PlanarInterleavedConverterTest, + TestInterleavedIntoAudioBufferWithOffset) { + const size_t num_channels = ::testing::get<0>(GetParam()); + const size_t num_frames = ::testing::get<1>(GetParam()); + + AudioBuffer expected_output(kMaxNumChannels, num_frames); + FillPlanar(&expected_output); + + const size_t num_interleaved_samples = num_frames * num_channels; + // Create output buffers memory. + AudioBuffer output_buffer(num_channels, num_frames); + + AudioBuffer::AlignedInt16Vector interleaved_aligned_int( + num_interleaved_samples); + std::vector<int16_t> interleaved_unaligned_int(num_interleaved_samples); + FillInterleaved(num_channels, num_frames, interleaved_aligned_int.data()); + FillInterleaved(num_channels, num_frames, &interleaved_unaligned_int[0]); + + AudioBuffer::AlignedFloatVector interleaved_aligned_float( + num_interleaved_samples); + std::vector<float> interleaved_plane_float(num_interleaved_samples); + FillInterleaved(num_channels, num_frames, interleaved_aligned_float.data()); + FillInterleaved(num_channels, num_frames, &interleaved_plane_float[0]); + + for (size_t output_offset = 1; output_offset <= 4; ++output_offset) { + for (size_t input_offset = 1; input_offset <= 4; ++input_offset) { + const size_t num_frames_to_copy = + std::min(num_frames - input_offset, num_frames - output_offset); + // Integer. + // Aligned Input, Aligned Output. + output_buffer.Clear(); + FillAudioBufferWithOffset(interleaved_aligned_int.data(), num_frames, + num_channels, input_offset, output_offset, + num_frames_to_copy, &output_buffer); + VerifyOutput(num_channels, num_frames_to_copy, output_offset, + expected_output, output_buffer); + // Unaligned Input, Aligned Output. + output_buffer.Clear(); + FillAudioBufferWithOffset(&interleaved_unaligned_int[0], num_frames, + num_channels, input_offset, output_offset, + num_frames_to_copy, &output_buffer); + VerifyOutput(num_channels, num_frames_to_copy, output_offset, + expected_output, output_buffer); + + // Floating point. + // Aligned Input, Aligned Output. + output_buffer.Clear(); + FillAudioBufferWithOffset(interleaved_aligned_float.data(), num_frames, + num_channels, input_offset, output_offset, + num_frames_to_copy, &output_buffer); + VerifyOutput(num_channels, num_frames_to_copy, output_offset, + expected_output, output_buffer); + // Unaligned Input, Aligned Output. + output_buffer.Clear(); + FillAudioBufferWithOffset(&interleaved_plane_float[0], num_frames, + num_channels, input_offset, output_offset, + num_frames_to_copy, &output_buffer); + VerifyOutput(num_channels, num_frames_to_copy, output_offset, + expected_output, output_buffer); + } + } +} + +// Tests that planar (float/int16_t) data can be correctly written into +// |AudioBuffer|s, with offsets into both the input and output data. +TEST_P(PlanarInterleavedConverterTest, + TestPlanarPtrsIntoAudioBufferWithOffset) { + const size_t num_channels = ::testing::get<0>(GetParam()); + const size_t num_frames = ::testing::get<1>(GetParam()); + + AudioBuffer expected_output(num_channels, num_frames); + FillPlanar(&expected_output); + + // Create output buffers memory. + AudioBuffer output_buffer(num_channels, num_frames); + + std::vector<AudioBuffer::AlignedInt16Vector> + planar_aligned_channels_int_buffers( + num_channels, AudioBuffer::AlignedInt16Vector(num_frames)); + std::vector<std::vector<int16_t>> planar_unaligned_channels_int_buffers( + num_channels, std::vector<int16_t>(num_frames)); + + std::vector<int16_t*> planar_aligned_channels_int(num_channels); + std::vector<int16_t*> planar_unaligned_channels_int(num_channels); + for (size_t c = 0; c < num_channels; ++c) { + planar_aligned_channels_int[c] = + planar_aligned_channels_int_buffers[c].data(); + planar_unaligned_channels_int[c] = + planar_unaligned_channels_int_buffers[c].data(); + } + FillPlanar(num_frames, &planar_aligned_channels_int); + FillPlanar(num_frames, &planar_unaligned_channels_int); + + std::vector<AudioBuffer::AlignedFloatVector> + planar_aligned_channels_float_buffer( + num_channels, AudioBuffer::AlignedFloatVector(num_frames)); + std::vector<std::vector<float>> planar_unaligned_channels_float_buffers( + num_channels, std::vector<float>(num_frames)); + + std::vector<float*> planar_aligned_channels_float(num_channels); + std::vector<float*> planar_unaligned_channels_float(num_channels); + + for (size_t c = 0; c < num_channels; ++c) { + planar_aligned_channels_float[c] = + planar_aligned_channels_float_buffer[c].data(); + planar_unaligned_channels_float[c] = + planar_unaligned_channels_float_buffers[c].data(); + } + FillPlanar(num_frames, &planar_aligned_channels_float); + FillPlanar(num_frames, &planar_unaligned_channels_float); + + for (size_t output_offset = 1; output_offset <= 4; ++output_offset) { + for (size_t input_offset = 1; input_offset <= 4; ++input_offset) { + const size_t num_frames_to_copy = + std::min(num_frames - input_offset, num_frames - output_offset); + // Integer. + // Aligned Input, Aligned Output. + output_buffer.Clear(); + FillAudioBufferWithOffset(&planar_aligned_channels_int[0], num_frames, + num_channels, input_offset, output_offset, + num_frames_to_copy, &output_buffer); + VerifyOutput(num_channels, num_frames_to_copy, output_offset, + expected_output, output_buffer); + // Unaligned Input, Aligned Output. + output_buffer.Clear(); + FillAudioBufferWithOffset(&planar_unaligned_channels_int[0], num_frames, + num_channels, input_offset, output_offset, + num_frames_to_copy, &output_buffer); + VerifyOutput(num_channels, num_frames_to_copy, output_offset, + expected_output, output_buffer); + + // Floating point. + // Aligned Input, Aligned Output. + output_buffer.Clear(); + FillAudioBufferWithOffset(&planar_aligned_channels_float[0], num_frames, + num_channels, input_offset, output_offset, + num_frames_to_copy, &output_buffer); + VerifyOutput(num_channels, num_frames_to_copy, output_offset, + expected_output, output_buffer); + // Unaligned Input, Aligned Output. + output_buffer.Clear(); + FillAudioBufferWithOffset(&planar_unaligned_channels_float[0], num_frames, + num_channels, input_offset, output_offset, + num_frames_to_copy, &output_buffer); + VerifyOutput(num_channels, num_frames_to_copy, output_offset, + expected_output, output_buffer); + } + } +} + +// Tests that interleaved (float/int16_t) data can be correctly written into +// |AudioBuffer|s, with remapping of channels. +TEST_P(PlanarInterleavedConverterTest, + TestInterleavedIntoAudioBufferRemapping) { + const size_t num_channels = ::testing::get<0>(GetParam()); + const size_t num_frames = ::testing::get<1>(GetParam()); + + AudioBuffer expected_output(num_channels, num_frames); + FillPlanar(&expected_output); + + std::vector<size_t> channel_map(num_channels); + for (size_t c = 0; c < num_channels; ++c) { + channel_map[c] = c; + } + + // Permute the channel map. + std::next_permutation(channel_map.begin(), channel_map.end()); + const size_t num_interleaved_samples = num_frames * num_channels; + + // Create output buffers memory. + AudioBuffer output_buffer(num_channels, num_frames); + + // Integer. + AudioBuffer::AlignedInt16Vector interleaved_aligned_int( + num_interleaved_samples); + std::vector<int16_t> interleaved_unaligned_int(num_interleaved_samples); + FillInterleaved(num_channels, num_frames, interleaved_aligned_int.data()); + FillInterleaved(num_channels, num_frames, &interleaved_unaligned_int[0]); + // Aligned Input, Aligned Output. + output_buffer.Clear(); + FillAudioBufferWithChannelRemapping(interleaved_aligned_int.data(), + num_frames, num_channels, channel_map, + &output_buffer); + VerifyOutput(num_channels, num_frames, expected_output, output_buffer, + channel_map); + // Unaligned Input, Aligned Output. + output_buffer.Clear(); + FillAudioBufferWithChannelRemapping(&interleaved_unaligned_int[0], num_frames, + num_channels, channel_map, + &output_buffer); + VerifyOutput(num_channels, num_frames, expected_output, output_buffer, + channel_map); + + // Floating point. + AudioBuffer::AlignedFloatVector interleaved_aligned_float( + num_interleaved_samples); + std::vector<float> interleaved_plane_float(num_interleaved_samples); + FillInterleaved(num_channels, num_frames, interleaved_aligned_float.data()); + FillInterleaved(num_channels, num_frames, &interleaved_plane_float[0]); + // Aligned Input, Aligned Output. + output_buffer.Clear(); + FillAudioBufferWithChannelRemapping(interleaved_aligned_float.data(), + num_frames, num_channels, channel_map, + &output_buffer); + VerifyOutput(num_channels, num_frames, expected_output, output_buffer, + channel_map); + // Unaligned Input, Aligned Output. + output_buffer.Clear(); + FillAudioBufferWithChannelRemapping(&interleaved_plane_float[0], num_frames, + num_channels, channel_map, + &output_buffer); + VerifyOutput(num_channels, num_frames, expected_output, output_buffer, + channel_map); +} + +// Tests that planar (float/int16_t) data can be correctly written into +// |AudioBuffer|s, with remapping of channels. +TEST_P(PlanarInterleavedConverterTest, TestPlanarPtrsIntoAudioBufferRemapping) { + const size_t num_channels = ::testing::get<0>(GetParam()); + const size_t num_frames = ::testing::get<1>(GetParam()); + + AudioBuffer expected_output(num_channels, num_frames); + FillPlanar(&expected_output); + + std::vector<size_t> channel_map(num_channels); + for (size_t c = 0; c < num_channels; ++c) { + channel_map[c] = c; + } + // Permute the channel map. + std::next_permutation(channel_map.begin(), channel_map.end()); + // Create output buffers memory. + AudioBuffer output_buffer(num_channels, num_frames); + + // Integer. + std::vector<AudioBuffer::AlignedInt16Vector> + planar_aligned_channels_int_bufferss( + num_channels, AudioBuffer::AlignedInt16Vector(num_frames)); + std::vector<std::vector<int16_t>> planar_unaligned_channels_int_buffers( + num_channels, std::vector<int16_t>(num_frames)); + + std::vector<int16_t*> planar_aligned_channels_int(num_channels); + std::vector<int16_t*> planar_unaligned_channels_int(num_channels); + for (size_t c = 0; c < num_channels; ++c) { + planar_aligned_channels_int[c] = + planar_aligned_channels_int_bufferss[c].data(); + planar_unaligned_channels_int[c] = + planar_unaligned_channels_int_buffers[c].data(); + } + + FillPlanar(num_frames, &planar_aligned_channels_int); + FillPlanar(num_frames, &planar_unaligned_channels_int); + // Aligned Input, Aligned Output. + output_buffer.Clear(); + FillAudioBufferWithChannelRemapping(&planar_aligned_channels_int[0], + num_frames, num_channels, channel_map, + &output_buffer); + VerifyOutput(num_channels, num_frames, expected_output, output_buffer, + channel_map); + // Unaligned Input, Aligned Output. + output_buffer.Clear(); + FillAudioBufferWithChannelRemapping(&planar_unaligned_channels_int[0], + num_frames, num_channels, channel_map, + &output_buffer); + VerifyOutput(num_channels, num_frames, expected_output, output_buffer, + channel_map); + + // Floating point. + std::vector<AudioBuffer::AlignedFloatVector> + planar_aligned_channels_float_buffers( + num_channels, AudioBuffer::AlignedFloatVector(num_frames)); + std::vector<std::vector<float>> planar_unaligned_channels_float_buffers( + num_channels, std::vector<float>(num_frames)); + + std::vector<float*> planar_aligned_channels_float(num_channels); + std::vector<float*> planar_unaligned_channels_float(num_channels); + for (size_t c = 0; c < num_channels; ++c) { + planar_aligned_channels_float[c] = + planar_aligned_channels_float_buffers[c].data(); + planar_unaligned_channels_float[c] = + planar_unaligned_channels_float_buffers[c].data(); + } + FillPlanar(num_frames, &planar_aligned_channels_float); + FillPlanar(num_frames, &planar_unaligned_channels_float); + // Aligned Input, Aligned Output. + output_buffer.Clear(); + FillAudioBufferWithChannelRemapping(&planar_aligned_channels_float[0], + num_frames, num_channels, channel_map, + &output_buffer); + VerifyOutput(num_channels, num_frames, expected_output, output_buffer, + channel_map); + // Unaligned Input, Aligned Output. + output_buffer.Clear(); + FillAudioBufferWithChannelRemapping(&planar_unaligned_channels_float[0], + num_frames, num_channels, channel_map, + &output_buffer); + VerifyOutput(num_channels, num_frames, expected_output, output_buffer, + channel_map); +} + +// Tests that an |AudioBuffer| can be correctly written into an interleaved +// vector of (float/int16_t) data. +TEST_P(PlanarInterleavedConverterTest, TestAudioBufferIntoInterleavedVector) { + const size_t num_channels = ::testing::get<0>(GetParam()); + const size_t num_frames = ::testing::get<1>(GetParam()); + const size_t num_interleaved_samples = num_frames * num_channels; + std::vector<float> expected_output(num_interleaved_samples); + FillInterleaved(num_channels, num_frames, &expected_output[0]); + + // Floating point. + AudioBuffer planar_input(num_channels, num_frames); + FillPlanar(&planar_input); + // Aligned Input, Unaligned Output. + std::vector<float> interleaved_output; + FillExternalBuffer(planar_input, &interleaved_output); + VerifyOutput(num_frames * num_channels, expected_output, interleaved_output); +} + +// Tests that an |AudioBuffer| can be correctly written into an interleaved +// (float/int16_t) array. +TEST_P(PlanarInterleavedConverterTest, TestAudioBufferIntoInterleavedPtr) { + const size_t num_channels = ::testing::get<0>(GetParam()); + const size_t num_frames = ::testing::get<1>(GetParam()); + const size_t num_interleaved_samples = num_frames * num_channels; + + std::vector<float> expected_output(num_interleaved_samples); + FillInterleaved(num_channels, num_frames, &expected_output[0]); + + AudioBuffer planar_input(num_channels, num_frames); + FillPlanar(&planar_input); + + // Integer. + AudioBuffer::AlignedInt16Vector interleaved_aligned_int( + num_interleaved_samples); + std::vector<int16_t> interleaved_unaligned_int(num_interleaved_samples); + + // Aligned Input, Aligned Output. + DCHECK_EQ(interleaved_aligned_int.size(), + planar_input.num_frames() * planar_input.num_channels()); + FillExternalBuffer(planar_input, interleaved_aligned_int.data(), + planar_input.num_frames(), planar_input.num_channels()); + VerifyOutput(num_frames * num_channels, expected_output, + interleaved_aligned_int.data()); + // Aligned Input, Unaligned Output. + DCHECK_EQ(interleaved_unaligned_int.size(), + planar_input.num_frames() * planar_input.num_channels()); + FillExternalBuffer(planar_input, interleaved_unaligned_int.data(), + planar_input.num_frames(), planar_input.num_channels()); + VerifyOutput(num_frames * num_channels, expected_output, + interleaved_unaligned_int); + + // Floating point. + AudioBuffer::AlignedFloatVector interleaved_aligned_float( + num_interleaved_samples); + std::vector<float> interleaved_plane_float(num_interleaved_samples); + + // Aligned Input, Aligned Output. + DCHECK_EQ(interleaved_aligned_float.size(), + planar_input.num_frames() * planar_input.num_channels()); + FillExternalBuffer(planar_input, interleaved_aligned_float.data(), + planar_input.num_frames(), planar_input.num_channels()); + VerifyOutput(num_frames * num_channels, expected_output, + interleaved_aligned_float.data()); + // Aligned Input, Unaligned Output. + FillExternalBuffer(planar_input, &interleaved_plane_float[0], + planar_input.num_frames(), planar_input.num_channels()); + VerifyOutput(num_frames * num_channels, expected_output, + interleaved_plane_float); +} + +// Test Params define: channels, frames +INSTANTIATE_TEST_CASE_P( + TestParameters, PlanarInterleavedConverterTest, + testing::Values( + TestParams(2, 8), TestParams(2, 13), TestParams(2, kMaxNumFrames), + TestParams(4, 8), TestParams(4, 13), TestParams(4, kMaxNumFrames), + TestParams(5, 8), TestParams(5, 13), TestParams(5, kMaxNumFrames), + TestParams(kMaxNumChannels, 8), TestParams(kMaxNumChannels, 13), + TestParams(kMaxNumChannels, kMaxNumFrames))); + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/pseudoinverse.h b/src/3rdparty/resonance-audio/resonance_audio/utils/pseudoinverse.h new file mode 100644 index 000000000..17b4b2934 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/pseudoinverse.h @@ -0,0 +1,45 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_UTILS_PSEUDOINVERSE_H_ +#define RESONANCE_AUDIO_UTILS_PSEUDOINVERSE_H_ + +#include "Eigen/Dense" + +namespace vraudio { + +// Computes the Moore-Penrose pseudoinverse of |matrix|. +// +// @tparam MatrixType The type of the input matrix (an Eigen::Matrix). +// @param matrix The input matrix to compute the pseudoinverse of. +// @return The Moore-Penrose pseudoinverse of |matrix|. +template <typename MatrixType> +Eigen::Matrix<typename MatrixType::Scalar, MatrixType::ColsAtCompileTime, + MatrixType::RowsAtCompileTime> +Pseudoinverse(const MatrixType& matrix) { + Eigen::JacobiSVD<Eigen::Matrix<typename MatrixType::Scalar, Eigen::Dynamic, + Eigen::Dynamic>> svd(matrix, + Eigen::ComputeThinU | + Eigen::ComputeThinV); + return svd.solve( + Eigen::Matrix<typename MatrixType::Scalar, MatrixType::RowsAtCompileTime, + MatrixType::RowsAtCompileTime>::Identity(matrix.rows(), + matrix.rows())); +} + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_UTILS_PSEUDOINVERSE_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/pseudoinverse_test.cc b/src/3rdparty/resonance-audio/resonance_audio/utils/pseudoinverse_test.cc new file mode 100644 index 000000000..c71e97595 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/pseudoinverse_test.cc @@ -0,0 +1,87 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "utils/pseudoinverse.h" + +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/constants_and_types.h" + +namespace vraudio { + +namespace { + +// Tests that the pseudoinverse of an invertible square matrix is equal to the +// inverse of that matrix. +TEST(PseudoinverseTest, SquareInverse) { + Eigen::Matrix<double, 5, 5> invertible_matrix; + invertible_matrix << 0.8478, 0.1676, 0.1961, 0.2654, 0.7662, 0.0279, 0.5309, + 0.2043, 0.4947, 0.0918, 0.1367, 0.4714, 0.7113, 0.246, 0.8048, 0.9617, + 0.4378, 0.0259, 0.536, 0.9565, 0.1541, 0.6275, 0.8471, 0.1133, 0.8074; + ASSERT_NE(0.0, invertible_matrix.determinant()); + + auto pseudoinverse = Pseudoinverse(invertible_matrix); + auto inverse = invertible_matrix.inverse(); + + EXPECT_TRUE(pseudoinverse.isApprox(inverse, kEpsilonDouble)) + << "Pseudoinverse: \n" + << pseudoinverse << " should be within " << kEpsilonDouble + << " of inverse: \n" + << inverse; +} + +// Tests that the pseudoinverse of a full-rank matrix with more rows than +// columns works successfully. +TEST(PseudoinverseTest, PseudoinverseMoreRows) { + Eigen::Matrix<double, 5, 4> invertible_matrix; + invertible_matrix << 0.8478, 0.1676, 0.1961, 0.2654, 0.0279, 0.5309, 0.2043, + 0.4947, 0.1367, 0.4714, 0.7113, 0.246, 0.9617, 0.4378, 0.0259, 0.536, + 0.1541, 0.6275, 0.8471, 0.1133; + + auto pseudoinverse = Pseudoinverse(invertible_matrix); + auto should_be_identity = pseudoinverse * invertible_matrix; + + EXPECT_TRUE(should_be_identity.isApprox( + decltype(should_be_identity)::Identity(should_be_identity.rows(), + should_be_identity.cols()), + kEpsilonDouble)) + << "Matrix should be within " << kEpsilonDouble + << " of an identity matrix: \n" + << should_be_identity; +} + +// Tests that the pseudoinverse of a full-rank matrix with more columns than +// rows works successfully. +TEST(PseudoinverseTest, PseudoinverseMoreColumns) { + Eigen::Matrix<double, 4, 5> invertible_matrix; + invertible_matrix << 0.8478, 0.1676, 0.1961, 0.2654, 0.7662, 0.0279, 0.5309, + 0.2043, 0.4947, 0.0918, 0.1367, 0.4714, 0.7113, 0.246, 0.8048, 0.9617, + 0.4378, 0.0259, 0.536, 0.9565; + + auto pseudoinverse = Pseudoinverse(invertible_matrix); + auto should_be_identity = invertible_matrix * pseudoinverse; + + EXPECT_TRUE(should_be_identity.isApprox( + decltype(should_be_identity)::Identity(should_be_identity.rows(), + should_be_identity.cols()), + kEpsilonDouble)) + << "Matrix should be within " << kEpsilonDouble + << " of an identity matrix: \n" + << should_be_identity; +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/sample_type_conversion.cc b/src/3rdparty/resonance-audio/resonance_audio/utils/sample_type_conversion.cc new file mode 100644 index 000000000..fdd010cb9 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/sample_type_conversion.cc @@ -0,0 +1,40 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Prevent Visual Studio from complaining about std::copy_n. +#if defined(_WIN32) +#define _SCL_SECURE_NO_WARNINGS +#endif + +#include "utils/sample_type_conversion.h" + +#include "base/simd_utils.h" + +namespace vraudio { + +void ConvertPlanarSamples(size_t length, const int16* input, float* output) { + FloatFromInt16(length, input, output); +} + +void ConvertPlanarSamples(size_t length, const float* input, float* output) { + std::copy_n(input, length, output); +} + +void ConvertPlanarSamples(size_t length, const float* input, int16* output) { + Int16FromFloat(length, input, output); +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/sample_type_conversion.h b/src/3rdparty/resonance-audio/resonance_audio/utils/sample_type_conversion.h new file mode 100644 index 000000000..5fcb1a33c --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/sample_type_conversion.h @@ -0,0 +1,79 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_UTILS_SAMPLE_TYPE_CONVERSION_H_ +#define RESONANCE_AUDIO_UTILS_SAMPLE_TYPE_CONVERSION_H_ + +#include <algorithm> + +#include "base/integral_types.h" +#include "base/logging.h" + +namespace vraudio { + +// Convert the given int16 to a float in the range [-1.0f, 1.0f]. +inline void ConvertSampleToFloatFormat(int16 input, float* output) { + DCHECK(output); + static const float kInt16Max = static_cast<float>(0x7FFF); + static const float kInt16ToFloat = 1.0f / kInt16Max; + *output = input * kInt16ToFloat; +} + +// Overloaded input argument to support sample type templated methods. +inline void ConvertSampleToFloatFormat(float input, float* output) { + DCHECK(output); + *output = input; +} + +// Saturating if the float is not in [-1.0f, 1.0f]. +inline void ConvertSampleFromFloatFormat(float input, int16* output) { + DCHECK(output); + // Convert the given float to an int16 in the range + // [-32767 (0x7FFF), 32767 (0x7FFF)], + static const float kInt16Min = static_cast<float>(-0x7FFF); + static const float kInt16Max = static_cast<float>(0x7FFF); + static const float kFloatToInt16 = kInt16Max; + const float scaled_value = input * kFloatToInt16; + const float clamped_value = + std::min(kInt16Max, std::max(kInt16Min, scaled_value)); + *output = static_cast<int16>(clamped_value); +} + +// Overloaded output argument to support sample type templated methods. +inline void ConvertSampleFromFloatFormat(float input, float* output) { + DCHECK(output); + *output = input; +} + +// Convert a vector of int16 samples to float format in the range [-1.0f, 1.0f]. +void ConvertPlanarSamples(size_t length, const int16* input, + float* output); + +// Overloaded input argument to support sample type templated methods. +void ConvertPlanarSamples(size_t length, const float* input, + float* output); + +// Overloaded method to support methods templated against the input sample type. +void ConvertPlanarSamples(size_t length, const float* input, + int16* output); + +// Overloaded output argument to support sample type templated methods. +void ConvertPlanarSamples(size_t length, const float* input, + float* output); + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_UTILS_SAMPLE_TYPE_CONVERSION_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/sample_type_conversion_test.cc b/src/3rdparty/resonance-audio/resonance_audio/utils/sample_type_conversion_test.cc new file mode 100644 index 000000000..8d4e84243 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/sample_type_conversion_test.cc @@ -0,0 +1,80 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "utils/sample_type_conversion.h" + +#include "third_party/googletest/googletest/include/gtest/gtest.h" + +namespace vraudio { + +namespace { + +TEST(MiscMath, Int16ToFloatTest) { + static const int16 kMinInt16 = -0x7FFF; + static const int16 kMaxInt16 = 0x7FFF; + static const float kMinFloat = -1.0f; + static const float kMaxFloat = 1.0f; + + static const float kFloatRange = kMaxFloat - kMinFloat; + static const uint16 kInt16Range = kMaxInt16 - kMinInt16; + + for (int16 i = kMinInt16; i < kMaxInt16; i = static_cast<int16>(i + 0xFF)) { + const float mapped_float = + static_cast<float>(i) / static_cast<float>(kInt16Range) * kFloatRange; + float float_result = 0.0f; + ConvertSampleToFloatFormat(i, &float_result); + EXPECT_FLOAT_EQ(mapped_float, float_result); + } +} + +TEST(MiscMath, FloatToInt16Test) { + static const int16 kMinInt16 = -0x7FFF; + static const int16 kMaxInt16 = 0x7FFF; + static const float kMinFloat = -1.0f; + static const float kMaxFloat = 1.0f; + // NOTE: Int16 maximum is 0x7FFF, NOT 0x8000; see scheme 2) in + // http://goo.gl/NTRQ1a for background. + static const float kFloatRange = kMaxFloat - kMinFloat; + static const uint16 kInt16Range = kMaxInt16 - kMinInt16; + + for (float i = kMinFloat; i < kMaxFloat; i += 0.005f) { + const int16 mapped_int = static_cast<int16>(i * kInt16Range / kFloatRange); + int16 int16_result = 0; + ConvertSampleFromFloatFormat(i, &int16_result); + EXPECT_EQ(mapped_int, int16_result); + } +} + +TEST(MiscMath, FloatToInt16TestPositiveSaturate) { + // Maximum positive value is 2^15 - 1 + static const int16 kMaxInt16 = 0x7FFF; + static const float kMaxFloat = 1.0f; + int16 int16_result = 0; + ConvertSampleFromFloatFormat(2 * kMaxFloat, &int16_result); + EXPECT_EQ(kMaxInt16, int16_result); +} + +TEST(MiscMath, FloatToInt16TestNegativeSaturate) { + static const int16 kMinInt16 = -0x7FFF; + static const float kMinFloat = -1.0f; + int16 int16_result = 0; + ConvertSampleFromFloatFormat(2 * kMinFloat, &int16_result); + EXPECT_EQ(kMinInt16, int16_result); +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/semi_lockless_fifo.h b/src/3rdparty/resonance-audio/resonance_audio/utils/semi_lockless_fifo.h new file mode 100644 index 000000000..81950036b --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/semi_lockless_fifo.h @@ -0,0 +1,232 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_UTILS_SEMI_LOCKLESS_FIFO_H_ +#define RESONANCE_AUDIO_UTILS_SEMI_LOCKLESS_FIFO_H_ + +#include <atomic> +#include <chrono> +#include <condition_variable> +#include <mutex> +#include <vector> + +#include "base/logging.h" + +namespace vraudio { + +// Thread-safe multiple producer - single consumer FIFO queue to share data +// between threads. The FIFO takes over ownership of the queue elements. Note +// that |PushBack| calls are synchronized with a mutex and may block. Calls to +// |PopFront| are lockless and never block. +// +// @tparam DataType Object type that the FIFO handles. +template <typename DataType> +class SemiLocklessFifo { + public: + typedef std::chrono::steady_clock::duration ClockDuration; + + SemiLocklessFifo(); + + ~SemiLocklessFifo(); + + // Takes over ownership of |input| and pushes it to the FIFO queue back. + // + // @param input Input element to be added to the FIFO queue. + void PushBack(DataType&& input); + + // Pops element from FIFO queue front. + // + // @return Element from FIFO queue front. Must not be called if the queue is + // empty. + DataType PopFront(); + + // Returns true if FIFO queue is empty, false otherwise. This method is *not* + // thread-safe and should only be called from the consumer thread. + bool Empty() const; + + // Clears the FIFO queue and deletes all its elements. This method is *not* + // thread-safe and should only be called from the consumer thread. + void Clear(); + + // Sleeps until the number of elements in the FIFO queue drop below a target + // threshold. This method can be used to synchronize the producer and the + // consumer. Sleeping is enabled by default and can be disabled via + // |EnableBlockingSleepUntilMethods|. + // + // @param target_size Target size of FIFO queue. + // @param max_wait Maximum waiting period. + // @return True if number of FIFO elements is below target size. + bool SleepUntilBelowSizeTarget(size_t target_size, + const ClockDuration& max_wait); + + // Sleeps until the number of elements in the FIFO queue is greater or equal a + // target threshold. This method can be used to synchronize the producer and + // the consumer. Sleeping is enabled by default and can be disabled via + // |EnableBlockingSleepUntilMethods|. + // + // @param target_size Target size of FIFO queue. + // @param max_wait Maximum waiting period. + // @return True if number of FIFO elements is greater or equal the target + // size. + bool SleepUntilNumElementsInQueue(size_t target_size, + const ClockDuration& max_wait); + + // Allows for unblocking |SleepUntil[BelowSizeTarget|NumElementsInQueue]| + // method. + void EnableBlockingSleepUntilMethods(bool enable); + + private: + // Node in single-linked list. + struct Node { + Node() : next(nullptr) {} + std::atomic<Node*> next; + DataType data; + }; + + // Head of linked list. + Node* head_; + + // Tail of linked list. + Node* tail_; + + // Number of elements. + std::atomic<size_t> fifo_size_; + + // Mutex to synchronize |PushBack| calls from multiple threads. + std::mutex push_mutex_; + + // Conditional to signal consumption. + std::condition_variable pop_conditional_; + + // Mutex to block on until signal consumption occurs. + std::mutex pop_conditional_mutex_; + + // Conditional to signal new elements on the FIFO. + std::condition_variable push_conditional_; + + // Mutex to block on until new elements have been added to the FIFO. + std::mutex push_conditional_mutex_; + + // Flag to enable and disable blocking sleeping calls. + std::atomic<bool> enable_sleeping_; +}; + +template <typename DataType> +SemiLocklessFifo<DataType>::SemiLocklessFifo() + : fifo_size_(0), enable_sleeping_(true) { + head_ = tail_ = new Node(); +} + +template <typename DataType> +SemiLocklessFifo<DataType>::~SemiLocklessFifo() { + Clear(); + DCHECK_EQ(head_, tail_); + DCHECK(head_->next.load() == nullptr); + delete head_; +} + +template <typename DataType> +void SemiLocklessFifo<DataType>::PushBack(DataType&& input) { + std::lock_guard<std::mutex> lock(push_mutex_); + tail_->data = std::move(input); + Node* const new_node = new Node(); + DCHECK(tail_->next.load() == nullptr); + tail_->next = new_node; + tail_ = new_node; + ++fifo_size_; + + { + // Taking the lock and dropping it immediately assure that the notify + // cannot happen between the check of the predicate and wait of the + // |push_conditional_|. + std::lock_guard<std::mutex> lock(push_conditional_mutex_); + } + push_conditional_.notify_all(); +} + +template <typename DataType> +DataType SemiLocklessFifo<DataType>::PopFront() { + DCHECK(!Empty()); + + Node* const front_node = head_; + head_ = front_node->next; + + DataType output = std::move(front_node->data); + delete front_node; + + DCHECK_GT(fifo_size_.load(), 0u); + --fifo_size_; + + { + // Taking the lock and dropping it immediately assure that the notify + // cannot happen between the check of the predicate and wait of the + // |pop_conditional_|. + std::lock_guard<std::mutex> lock(pop_conditional_mutex_); + } + pop_conditional_.notify_one(); + return output; +} + +template <typename DataType> +bool SemiLocklessFifo<DataType>::Empty() const { + return fifo_size_.load() == 0; +} + +template <typename DataType> +void SemiLocklessFifo<DataType>::Clear() { + while (!Empty()) { + PopFront(); + } + DCHECK_EQ(fifo_size_, 0u); +} + +template <typename DataType> +bool SemiLocklessFifo<DataType>::SleepUntilBelowSizeTarget( + size_t target_size, const ClockDuration& max_wait) { + DCHECK_GT(target_size, 0); + std::unique_lock<std::mutex> lock(pop_conditional_mutex_); + pop_conditional_.wait_for(lock, max_wait, [this, target_size]() { + return fifo_size_ < target_size || !enable_sleeping_.load(); + }); + return fifo_size_ < target_size; +} + +template <typename DataType> +bool SemiLocklessFifo<DataType>::SleepUntilNumElementsInQueue( + size_t target_size, const ClockDuration& max_wait) { + DCHECK_GT(target_size, 0u); + std::unique_lock<std::mutex> lock(push_conditional_mutex_); + push_conditional_.wait_for(lock, max_wait, [this, target_size]() { + return fifo_size_ >= target_size || !enable_sleeping_.load(); + }); + return fifo_size_ >= target_size; +} + +template <typename DataType> +void SemiLocklessFifo<DataType>::EnableBlockingSleepUntilMethods(bool enable) { + enable_sleeping_ = enable; + // Taking the lock and dropping it immediately assure that the notify + // cannot happen between the check of the predicate and wait of the + // |pop_conditional_| and |push_conditional_|. + { std::lock_guard<std::mutex> lock(pop_conditional_mutex_); } + { std::lock_guard<std::mutex> lock(push_conditional_mutex_); } + pop_conditional_.notify_one(); + push_conditional_.notify_one(); +} + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_UTILS_SEMI_LOCKLESS_FIFO_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/sum_and_difference_processor.cc b/src/3rdparty/resonance-audio/resonance_audio/utils/sum_and_difference_processor.cc new file mode 100644 index 000000000..e6d9fe862 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/sum_and_difference_processor.cc @@ -0,0 +1,40 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "utils/sum_and_difference_processor.h" + +#include "base/constants_and_types.h" +#include "base/logging.h" + + +namespace vraudio { + +SumAndDifferenceProcessor::SumAndDifferenceProcessor(size_t num_frames) + : temp_buffer_(kNumMonoChannels, num_frames) {} + +void SumAndDifferenceProcessor::Process(AudioBuffer* stereo_buffer) { + + DCHECK_EQ(stereo_buffer->num_channels(), kNumStereoChannels); + AudioBuffer::Channel* temp_channel = &temp_buffer_[0]; + // channel_1' = channel_1 + channel_2; + // channel_2' = channel_1 - channel_2; + *temp_channel = (*stereo_buffer)[0]; + *temp_channel -= (*stereo_buffer)[1]; + (*stereo_buffer)[0] += (*stereo_buffer)[1]; + (*stereo_buffer)[1] = *temp_channel; +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/sum_and_difference_processor.h b/src/3rdparty/resonance-audio/resonance_audio/utils/sum_and_difference_processor.h new file mode 100644 index 000000000..76804a9c7 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/sum_and_difference_processor.h @@ -0,0 +1,44 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_UTILS_SUM_AND_DIFFERENCE_PROCESSOR_H_ +#define RESONANCE_AUDIO_UTILS_SUM_AND_DIFFERENCE_PROCESSOR_H_ + +#include "base/audio_buffer.h" + +namespace vraudio { + +// Class which converts a 2-channel input audio buffer into its sum and +// difference signals and stores them in the left and right channel +// respectively. +class SumAndDifferenceProcessor { + public: + // Constructs a stereo sum and difference processor. + // + // @param num_frames Number of frames in the stereo input audio buffer. + explicit SumAndDifferenceProcessor(size_t num_frames); + + // Converts a 2-channel buffer signals into their sum and difference. + void Process(AudioBuffer* stereo_buffer); + + private: + // Temporary audio buffer to store left channel input data during conversion. + AudioBuffer temp_buffer_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_UTILS_SUM_AND_DIFFERENCE_PROCESSOR_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/sum_and_difference_processor_test.cc b/src/3rdparty/resonance-audio/resonance_audio/utils/sum_and_difference_processor_test.cc new file mode 100644 index 000000000..e80acbdb5 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/sum_and_difference_processor_test.cc @@ -0,0 +1,42 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "utils/sum_and_difference_processor.h" + +#include "third_party/googletest/googletest/include/gtest/gtest.h" + +namespace vraudio { + +// Tests Process method. +TEST(SumAndDifferenceProcessor, TestProcessMethod) { + static const std::vector<std::vector<float>> kTestVector = { + {0.0f, 1.0f, 2.0f}, {3.0f, 4.0f, 5.0f}}; + + AudioBuffer audio_buffer(kTestVector.size(), kTestVector[0].size()); + audio_buffer = kTestVector; + + SumAndDifferenceProcessor processor(audio_buffer.num_frames()); + processor.Process(&audio_buffer); + + for (size_t frame = 0; frame < kTestVector[0].size(); ++frame) { + EXPECT_EQ(kTestVector[0][frame] + kTestVector[1][frame], + audio_buffer[0][frame]); + EXPECT_EQ(kTestVector[0][frame] - kTestVector[1][frame], + audio_buffer[1][frame]); + } +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/task_thread_pool.cc b/src/3rdparty/resonance-audio/resonance_audio/utils/task_thread_pool.cc new file mode 100644 index 000000000..485e834df --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/task_thread_pool.cc @@ -0,0 +1,249 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "utils/task_thread_pool.h" + +#include <thread> + +#include "base/integral_types.h" +#include "base/logging.h" + +namespace vraudio { + +// A simple worker thread wrapper, to be used by TaskThreadPool. +class TaskThreadPool::WorkerThread { + public: + // Constructor. + // + // @param parent_pool The |TaskThreadPool| which owns and manages this + // |WorkerThread| instance. + WorkerThread() + : parent_pool_(), task_closure_(), task_loop_triggered_(false) {} + + // Copy constructor. Necessary for allowing this class to be storied in + // std::vector<WorkerThread> container class. + // + // @param other |WorkerThread| instance this instance will be copied from. + WorkerThread(const TaskThreadPool::WorkerThread& other) + : parent_pool_(), task_closure_(), task_loop_triggered_(false) {} + + // Destructor. + ~WorkerThread(); + + // Sets the parent pool for this |WorkerThread|. + // + // @param parent_pool The |TaskThreadPool| which owns this |WorkerThread|. + // @return True indicates that the thread could be started. + bool SetParentAndStart(TaskThreadPool* parent_pool); + + // Indicates that the worker thread should run the |TaskClosure| task. + // + // @param task_closure The task function which |TaskThreadPool| assigns to + // this |WorkerThread|. + void Run(TaskClosure task_closure); + + // Waits for the |WorkerThread| task loop to exit, for shutdown. + void Join(); + + // Checks whether this |WorkerThread| is available for a task assignment. + bool IsAvailable() const; + + private: + // The task loop for this |WorkerThread|. This function will wait for an + // assigned task, execute the task, and then reset itself to wait for the next + // task. + void TaskLoop(); + + // This |WorkerThread|'s parent |TaskThreadPool|. + TaskThreadPool* parent_pool_; + + // Condition allowing the |TaskThreadPool| to trigger this worker thread to + // continue once it has been given a work assignment. + std::condition_variable execute_task_condition_; + + // Mutex for receiving |execute_task_condition_| notification. + std::mutex execute_task_mutex_; + + // The current task binding which the Worker Thread has been asked to + // execute. + TaskClosure task_closure_; + + // An atomic boolean to indicate that a task loop trigger has occurred. This + // may happen even when a task has not been assigned during shutdown. + std::atomic<bool> task_loop_triggered_; + + // The worker thread. + std::thread task_thread_; +}; + +TaskThreadPool::TaskThreadPool() + : num_worker_threads_available_(0), + is_pool_running_(false) {} + +TaskThreadPool::~TaskThreadPool() { StopThreadPool(); } + +bool TaskThreadPool::StartThreadPool(size_t num_worker_threads) { + if (is_pool_running_) { + return true; + } + is_pool_running_ = true; + worker_threads_.resize(num_worker_threads); + + // Start all worker threads. + for (auto& worker_thread : worker_threads_) { + bool thread_started = worker_thread.SetParentAndStart(this); + if (!thread_started) { + StopThreadPool(); + return false; + } + } + + // Wait for all worker threads to be launched. + std::unique_lock<std::mutex> worker_lock(worker_available_mutex_); + worker_available_condition_.wait(worker_lock, [this, num_worker_threads]() { + return !is_pool_running_.load() || num_worker_threads_available_.load() == + static_cast<int>(num_worker_threads); + }); + + return true; +} + +void TaskThreadPool::StopThreadPool() { + if (!is_pool_running_) { + return; + } + // Shut down all active worker threads. + { + std::lock_guard<std::mutex> worker_lock(worker_available_mutex_); + is_pool_running_ = false; + } + worker_available_condition_.notify_one(); + + // Join and destruct workers. + worker_threads_.resize(0); +} + +bool TaskThreadPool::WaitUntilWorkerBecomesAvailable() { + if (!is_pool_running_.load()) { + return false; + } + if (num_worker_threads_available_.load() > 0) { + return true; + } + std::unique_lock<std::mutex> worker_lock(worker_available_mutex_); + worker_available_condition_.wait(worker_lock, [this]() { + return (num_worker_threads_available_.load() > 0) || + !is_pool_running_.load(); + }); + return num_worker_threads_available_.load() > 0 && is_pool_running_.load(); +} + +bool TaskThreadPool::RunOnWorkerThread(TaskThreadPool::TaskClosure closure) { + if (!is_pool_running_.load() || num_worker_threads_available_.load() == 0) { + return false; + } + // Find the first available worker thread. + WorkerThread* available_worker_thread = nullptr; + for (auto& worker_thread : worker_threads_) { + if (worker_thread.IsAvailable()) { + available_worker_thread = &worker_thread; + break; + } + } + DCHECK(available_worker_thread); + { + std::lock_guard<std::mutex> lock(worker_available_mutex_); + --num_worker_threads_available_; + } + available_worker_thread->Run(std::move(closure)); + return true; +} + +size_t TaskThreadPool::GetAvailableTaskThreadCount() const { + return num_worker_threads_available_.load(); +} + +bool TaskThreadPool::IsPoolRunning() { return is_pool_running_.load(); } + +void TaskThreadPool::SignalWorkerAvailable() { + { + std::lock_guard<std::mutex> lock(worker_available_mutex_); + ++num_worker_threads_available_; + } + worker_available_condition_.notify_one(); +} + +TaskThreadPool::WorkerThread::~WorkerThread() { Join(); } + +bool TaskThreadPool::WorkerThread::SetParentAndStart( + TaskThreadPool* parent_pool) { + parent_pool_ = parent_pool; + + // Start the worker thread. + task_thread_ = std::thread(std::bind(&WorkerThread::TaskLoop, this)); + return true; +} + +void TaskThreadPool::WorkerThread::Run(TaskThreadPool::TaskClosure closure) { + if (closure) { + task_closure_ = std::move(closure); + { + std::lock_guard<std::mutex> lock(execute_task_mutex_); + task_loop_triggered_ = true; + } + execute_task_condition_.notify_one(); + } +} + +void TaskThreadPool::WorkerThread::Join() { + DCHECK(!parent_pool_->IsPoolRunning()); + // Aquire and release lock to assure that the notify cannot happen between the + // check of the predicate and wait of the |push_conditional_|. + { std::lock_guard<std::mutex> lock(execute_task_mutex_); } + execute_task_condition_.notify_one(); + if (task_thread_.joinable()) { + task_thread_.join(); + } +} + +bool TaskThreadPool::WorkerThread::IsAvailable() const { + return !task_loop_triggered_.load(); +} + +void TaskThreadPool::WorkerThread::TaskLoop() { + // Signal back to the parent thread pool that this thread has started and is + // ready for use. + task_loop_triggered_ = false; + + while (parent_pool_->IsPoolRunning() || task_closure_ != nullptr) { + parent_pool_->SignalWorkerAvailable(); + std::unique_lock<std::mutex> task_lock(execute_task_mutex_); + execute_task_condition_.wait(task_lock, [this]() { + return task_loop_triggered_.load() || !parent_pool_->IsPoolRunning(); + }); + + // Execute the assigned task. + if (task_closure_ != nullptr) { + task_closure_(); + + // Clear the assigned task and return to ready state. + task_closure_ = nullptr; + } + task_loop_triggered_ = false; + } +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/task_thread_pool.h b/src/3rdparty/resonance-audio/resonance_audio/utils/task_thread_pool.h new file mode 100644 index 000000000..e9db083f9 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/task_thread_pool.h @@ -0,0 +1,121 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_UTILS_TASK_THREAD_POOL_H_ +#define RESONANCE_AUDIO_UTILS_TASK_THREAD_POOL_H_ + +#include <atomic> +#include <chrono> +#include <condition_variable> +#include <functional> +#include <mutex> +#include <vector> + +namespace vraudio { + +class TaskThreadPool; + +// A very basic thread pool for launching discrete encapsulated task functions +// on a configurable number of task threads. Note that this pool expects tasks +// to complete their work with no management by the pool itself. Any means of +// managing or terminating tasks must be designed into the task contexts and +// managed externally in a thread-safe way. +class TaskThreadPool { + friend class WorkerThread; + + public: + // Type definition for task function which may be assigned to a |WorkerThread| + // in this pool. Note that tasks should be self-contained and not require + // communication with other tasks. + typedef std::function<void()> TaskClosure; + + // Constructor. + // + TaskThreadPool(); + + ~TaskThreadPool(); + + // Creates and initializes thread pool. This method blocks until all threads + // are loaded and initialized. + // + // @param num_worker_threads The number of worker threads to make available in + // the pool. + // @return true on success or if thread pool has been already started. + bool StartThreadPool(size_t num_worker_threads); + + // Signals all |WorkerThread|s to stop and waits for completion. + void StopThreadPool(); + + // Waits until a |WorkerThread| becomes available. It is assumed that only a + // single thread will dispatch threads using this function, and therefore this + // function should not itself be considered thread safe. + // + // @return True if a |WorkerThread| is available. + bool WaitUntilWorkerBecomesAvailable(); + + // Executes a |TaskClosure| on a |WorkerThread|. It is assumed that only a + // signal thread will dispatch threads using this function, and therefore this + // function should not itself be considered thread safe. + // + // @param closure The client task which will begin execution if and when this + // function returns True. + // @return True if a |WorkerThread| is allocated to execute the closure + // function, false if no |WorkerThread| is available. + bool RunOnWorkerThread(TaskClosure closure); + + // Query the number of |WorkerThread|s current available to do work. + size_t GetAvailableTaskThreadCount() const; + + private: + // Forward declaration of |WorkerThread| class. See implementation file for + // class details. + class WorkerThread; + + // Query whether the |TaskThreadPool| is active. + // + // @return True if the pool is still running. + bool IsPoolRunning(); + + // Signals to thread pool that a worker thread has become available for + // task assignment. + void SignalWorkerAvailable(); + + // Closure reusable task loop to be executed by each |WorkerThread|. + void WorkerLoopFunction(); + + // Task Loop executed by each worker thread. + void WorkerThreadLoop(); + + // Number of worker threads currently available to execute tasks. + std::atomic<int> num_worker_threads_available_; + + // Control of all worker thread loops. + std::atomic<bool> is_pool_running_; + + // Container of available worker threads, waiting to be used. + + std::vector<WorkerThread> worker_threads_; + + // Condition to indicate that a worker thread has become available. + std::condition_variable worker_available_condition_; + + // Mutex for the worker thread available condition notification receiver. + std::mutex worker_available_mutex_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_UTILS_TASK_THREAD_POOL_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/task_thread_pool_test.cc b/src/3rdparty/resonance-audio/resonance_audio/utils/task_thread_pool_test.cc new file mode 100644 index 000000000..61e83f2d9 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/task_thread_pool_test.cc @@ -0,0 +1,185 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include <atomic> +#include <chrono> +#include <mutex> +#include <thread> +#include <vector> + +#include "base/integral_types.h" +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/logging.h" +#include "utils/task_thread_pool.h" + +namespace vraudio { + +namespace { + +// The number of simultaneous worker threads to run for these tests. +const size_t kNumberOfThreads = 5; + +// An extremely large number of worker threads to use when attempting to shut +// down the |TaskThreadPool| while it is still being initialized. +const size_t kHugeThreadCount = 64; + +// An arbitrary numeric value to set when testing worker threads. +const int kModifiedValue = 113; + +// A limited number of iterations to perform when testing worker threads which +// do continuous work over time. +const size_t kNumberOfIncrementOperations = 50; + +// The number of times to repeat test loops in functions to insure reuse of +// worker threads. +const size_t kNumTestLoops = 3; + +class TaskThreadPoolTest : public ::testing::Test { + protected: + TaskThreadPoolTest() {} + ~TaskThreadPoolTest() override {} + + void SetUp() override { modified_values_.resize(kNumberOfThreads, 0); } + + public: + // Helper worker task to asynchronously set values in worker threads. + void ModifyValue(int* value_to_set) { + std::lock_guard<std::mutex> worker_lock(modification_mutex_); + EXPECT_NE(value_to_set, nullptr); + *value_to_set = kModifiedValue; + } + + // Helper worker task to asynchronously increment modified values over time + // with enough interior delay to measure results. + void IncrementValue(int* value_to_increment) { + EXPECT_NE(value_to_increment, nullptr); + for (size_t i = 0; i < kNumberOfIncrementOperations; ++i) { + // Sleep briefly so this doesn't finish too quickly. + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + std::lock_guard<std::mutex> worker_lock(modification_mutex_); + *value_to_increment += 1; + } + } + + protected: + // Vector of numbers used for testing asynchronous threading results. + std::vector<int> modified_values_; + + // A mutex for protecting modified_values_ from tsan (Thread Sanitizer) + // failures. + std::mutex modification_mutex_; +}; + +// This test verifies that TaskThreadPool actually executes tasks. +TEST_F(TaskThreadPoolTest, SetValuesInWorkerThreads) { + TaskThreadPool thread_pool; + EXPECT_TRUE(thread_pool.StartThreadPool(kNumberOfThreads)); + + // Run this several times to insure that worker threads can be reused. + for (size_t loop = 0; loop < kNumTestLoops; ++loop) { + // Verify that all worker threads are available again. + EXPECT_EQ(thread_pool.GetAvailableTaskThreadCount(), kNumberOfThreads); + + for (size_t i = 0; i < kNumberOfThreads; ++i) { + modified_values_[i] = 0; + const bool task_available = thread_pool.WaitUntilWorkerBecomesAvailable(); + EXPECT_TRUE(task_available); + const bool task_submitted = thread_pool.RunOnWorkerThread( + std::bind(&vraudio::TaskThreadPoolTest::ModifyValue, this, + &modified_values_[i])); + EXPECT_TRUE(task_submitted); + } + + // Wait until all threads become available again (trying to time their + // completion seems to cause flakey tests). + while (thread_pool.GetAvailableTaskThreadCount() < kNumberOfThreads) { + // Wait briefly to allow tasks to execute. + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } + + // Check results. + for (size_t i = 0; i < kNumberOfThreads; ++i) { + std::lock_guard<std::mutex> worker_lock(modification_mutex_); + EXPECT_EQ(modified_values_[i], kModifiedValue); + } + } +} + +// This test verifies that the |TaskThreadPool| cannot be shut down until all of +// its worker threads are brought to a ready state. +TEST_F(TaskThreadPoolTest, VerifyRapidShutdownWithLargeThreadCount) { + TaskThreadPool thread_pool; + EXPECT_TRUE(thread_pool.StartThreadPool(kHugeThreadCount)); +} + +// This test verifies the timeout features of assigning worker threads, as well +// as the continuous asynchronous operation of threads.. +TEST_F(TaskThreadPoolTest, VerifyTimeoutsAndContinuousOperation) { + // Preset |modified_values_| to known state. + for (size_t i = 0; i < kNumberOfThreads; ++i) { + modified_values_[i] = 0; + } + + { + TaskThreadPool thread_pool; + EXPECT_TRUE(thread_pool.StartThreadPool(kNumberOfThreads)); + + // Verify that all worker threads are available again. + EXPECT_EQ(thread_pool.GetAvailableTaskThreadCount(), kNumberOfThreads); + + for (size_t i = 0; i < kNumberOfThreads; ++i) { + modified_values_[i] = 0; + const bool task_available = thread_pool.WaitUntilWorkerBecomesAvailable(); + EXPECT_TRUE(task_available); + const bool task_submitted = thread_pool.RunOnWorkerThread( + std::bind(&vraudio::TaskThreadPoolTest::IncrementValue, this, + &modified_values_[i])); + EXPECT_TRUE(task_submitted); + } + + // Verify that all worker threads are available again. + EXPECT_EQ(thread_pool.GetAvailableTaskThreadCount(), 0U); + + // Trying to add one more task should fail. + int extra_modified; + EXPECT_FALSE(thread_pool.RunOnWorkerThread(std::bind( + &vraudio::TaskThreadPoolTest::IncrementValue, this, &extra_modified))); + + // Wait briefly to allow tasks to execute. + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + // Verify that all of the tasks are still doing some work. + for (size_t i = 0; i < kNumberOfThreads; ++i) { + std::lock_guard<std::mutex> worker_lock(modification_mutex_); + EXPECT_GT(modified_values_[i], 0); + } + } + // To verify that all threads are shut down correctly, record the + // |modified_values_|, wait briefly, and then make sure theyΩ have not + // changed. + std::vector<int> copy_of_modified_values = modified_values_; + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + for (size_t i = 0; i < kNumberOfThreads; ++i) { + // NOTE: modification_mutex_ is intentionally not used for this block so + // that any race conditions might be caught if for some reason the worker + // threads have not yet shut down. + EXPECT_EQ(copy_of_modified_values[i], modified_values_[i]); + } +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/test_util.cc b/src/3rdparty/resonance-audio/resonance_audio/utils/test_util.cc new file mode 100644 index 000000000..4912ab8cb --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/test_util.cc @@ -0,0 +1,190 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "utils/test_util.h" + +#include <algorithm> +#include <cmath> +#include <string> + +#include "third_party/googletest/googlemock/include/gmock/gmock.h" +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/audio_buffer.h" +#include "base/constants_and_types.h" +#include "base/logging.h" +#include "base/misc_math.h" + +namespace vraudio { + +namespace { + +using ::testing::NotNull; + +} // namespace + +void GenerateSilence(AudioBuffer::Channel* output) { + ASSERT_THAT(output, ::testing::NotNull()); + output->Clear(); +} + +void GenerateSineWave(float frequency_hz, int sample_rate, + AudioBuffer::Channel* output) { + ASSERT_GE(frequency_hz, 0.0f); + ASSERT_GT(sample_rate, 0); + ASSERT_THAT(output, ::testing::NotNull()); + + for (size_t i = 0; i < output->size(); ++i) { + const float phase = static_cast<float>(i) * kTwoPi / + static_cast<float>(sample_rate) * frequency_hz; + (*output)[i] = std::sin(phase); + } +} + +void GenerateSawToothSignal(size_t tooth_length_samples, + AudioBuffer::Channel* output) { + ASSERT_GT(tooth_length_samples, 0U); + ASSERT_THAT(output, ::testing::NotNull()); + for (size_t i = 0; i < output->size(); ++i) { + (*output)[i] = static_cast<float>(i % tooth_length_samples) / + static_cast<float>(tooth_length_samples) * 2.0f - + 1.0f; + } +} + +void GenerateDiracImpulseFilter(size_t delay_samples, + AudioBuffer::Channel* output) { + ASSERT_THAT(output, ::testing::NotNull()); + ASSERT_LT(delay_samples, output->size()); + ASSERT_THAT(output, ::testing::NotNull()); + output->Clear(); + (*output)[delay_samples] = 1.0f; +} + +void GenerateIncreasingSignal(AudioBuffer::Channel* output) { + ASSERT_THAT(output, ::testing::NotNull()); + for (size_t i = 0; i < output->size(); ++i) { + (*output)[i] = + static_cast<float>(i) / static_cast<float>(output->size()) * 2.0f - + 1.0f; + } +} + +size_t ZeroCompare(const AudioBuffer::Channel& signal, float epsilon) { + for (size_t i = 0; i < signal.size(); ++i) { + if (std::abs(signal[i]) > epsilon) { + return i; + } + } + return signal.size(); +} + +bool CompareAudioBuffers(const AudioBuffer::Channel& buffer_a, + const AudioBuffer::Channel& buffer_b, float epsilon) { + if (buffer_a.size() != buffer_b.size()) { + return false; + } + for (size_t i = 0; i < buffer_a.size(); ++i) { + if (std::abs(buffer_a[i] - buffer_b[i]) > epsilon) { + return false; + } + } + return true; +} + +size_t DelayCompare(const AudioBuffer::Channel& original_signal, + const AudioBuffer::Channel& delayed_signal, size_t delay, + float epsilon) { + if (delay > delayed_signal.size() || + (delayed_signal.size() > original_signal.size() + delay)) { + return 0; + } + for (size_t i = delay; i < delayed_signal.size(); ++i) { + const size_t original_index = i - delay; + const float difference = + std::abs(delayed_signal[i] - original_signal[original_index]); + if (difference > epsilon) { + return i; + } + } + return delayed_signal.size(); +} + +bool TestZeroPaddedDelay(const AudioBuffer::Channel& original_signal, + const AudioBuffer::Channel& delayed_signal, + size_t delay_samples, float epsilon) { + size_t temp = ZeroCompare(delayed_signal, epsilon); + if (delay_samples != temp) { + return false; + } + temp = DelayCompare(original_signal, delayed_signal, delay_samples, epsilon); + if (original_signal.size() != temp) { + return false; + } + return true; +} + +double CalculateSignalPeak(const AudioBuffer::Channel& channel) { + double peak = 0.0; + for (const float& sample : channel) { + if (std::abs(sample) > peak) peak = std::abs(sample); + } + + DCHECK_GT(channel.size(), 0); + return peak; +} + +double CalculateSignalEnergy(const AudioBuffer::Channel& channel) { + double energy = 0.0; + for (const float& sample : channel) { + energy += sample * sample; + } + return energy; +} + +double CalculateSignalRms(const AudioBuffer::Channel& channel) { + const double energy = CalculateSignalEnergy(channel); + DCHECK_GT(channel.size(), 0); + return std::sqrt(energy / static_cast<double>(channel.size())); +} + +double DbFromMagnitude(double magnitude) { + DCHECK_GT(magnitude, 0.0); + const double decibel = 20.0 * std::log10(magnitude); + return decibel; +} + +double DbFromPower(double power) { + DCHECK_GT(power, 0.0); + const double decibel = 10.0 * std::log10(power); + return decibel; +} + +float MaxCrossCorrelation(const AudioBuffer::Channel& signal_a, + const AudioBuffer::Channel& signal_b) { + CHECK_EQ(signal_a.size(), signal_b.size()); + float output = 0.0f; + const size_t length = signal_a.size(); + for (size_t i = 0; i < length; ++i) { + float current = 0.0f; + for (size_t j = 0; j < length - i - 1; ++j) { + current += signal_a[j + i] * signal_b[j]; + } + output = std::max(output, current); + } + return output; +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/test_util.h b/src/3rdparty/resonance-audio/resonance_audio/utils/test_util.h new file mode 100644 index 000000000..3ce9db5d2 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/test_util.h @@ -0,0 +1,91 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_UTILS_TEST_UTIL_H_ +#define RESONANCE_AUDIO_UTILS_TEST_UTIL_H_ + +#include <cstddef> +#include <vector> + +#include "base/integral_types.h" +#include "base/audio_buffer.h" + +namespace vraudio { + +// Silences an audio channel. +void GenerateSilence(AudioBuffer::Channel* output); + +// Generates a sine wave at the specified frequency in hertz at the given +// sampling rate in hertz. +void GenerateSineWave(float frequency_hz, int sample_rate, + AudioBuffer::Channel* output); + +// Generates a saw tooth signal between -1 and 1 for the given wave form length. +void GenerateSawToothSignal(size_t tooth_length_samples, + AudioBuffer::Channel* output); + +// Generates a Dirac impulse filter kernel, which delays filtered signals by +// the given delay. +void GenerateDiracImpulseFilter(size_t delay_samples, + AudioBuffer::Channel* output); + +// Generates a linear ramp signal between -1 and 1. +void GenerateIncreasingSignal(AudioBuffer::Channel* output); + +// Returns the index of the first non-zero element. +size_t ZeroCompare(const AudioBuffer::Channel& signal, float epsilon); + +// Compares the content of two audio channels. Returns true if the absolute +// difference between all samples is below epsion. +bool CompareAudioBuffers(const AudioBuffer::Channel& buffer_a, + const AudioBuffer::Channel& buffer_b, float epsilon); + +// Returns delayed_signal.size() in output if delayed_signal is approximately +// equal to original_signal delayed by the given amount; otherwise, returns the +// index of the first unequal element in delayed_signal. +size_t DelayCompare(const AudioBuffer::Channel& original_signal, + const AudioBuffer::Channel& delayed_signal, size_t delay, + float epsilon); + +// Test if two signals are shifted by a fixed number of samples with zero +// padding. +bool TestZeroPaddedDelay(const AudioBuffer::Channel& original_signal, + const AudioBuffer::Channel& delayed_signal, + size_t delay_samples, float epsilon); + +// Returns absolute peak amplitude of a signal. +double CalculateSignalPeak(const AudioBuffer::Channel& channel); + +// Returns energy of a signal. +double CalculateSignalEnergy(const AudioBuffer::Channel& channel); + +// Returns Root Mean Square (RMS) of a signal. +double CalculateSignalRms(const AudioBuffer::Channel& channel); + +// Expresses a magnitude measurement in dB. +double DbFromMagnitude(double magnitude); + +// Expresses a power measurement in dB. +double DbFromPower(double power); + +// Returns the maximum cross correlation value between two signals. +// To be used only with signals of the same length. +float MaxCrossCorrelation(const AudioBuffer::Channel& signal_a, + const AudioBuffer::Channel& signal_b); + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_UTILS_TEST_UTIL_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/test_util_test.cc b/src/3rdparty/resonance-audio/resonance_audio/utils/test_util_test.cc new file mode 100644 index 000000000..e92f55632 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/test_util_test.cc @@ -0,0 +1,249 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "utils/test_util.h" + +#include <cmath> +#include <string> + +#include "third_party/googletest/googlemock/include/gmock/gmock.h" +#include "third_party/googletest/googletest/include/gtest/gtest.h" +#include "base/audio_buffer.h" +#include "base/constants_and_types.h" +#include "base/misc_math.h" + +namespace vraudio { + +namespace { + +TEST(TestUtilTest, GenerateSineWave_SuccessfulGeneration) { + const size_t kLengthStart = 1; + const size_t kLengthStop = 200; + const size_t kLengthStep = 10; + for (size_t length = kLengthStart; length <= kLengthStop; + length += kLengthStep) { + const float kFrequencyStart = 0.0f; + const float kFrequencyStop = 2000.0f; + const float kFrequencyStep = 100.0f; + for (float frequency = kFrequencyStart; frequency <= kFrequencyStop; + frequency += kFrequencyStep) { + const int kSampleRate = 2000; + AudioBuffer expected_signal(1, length); + AudioBuffer::Channel& expected_signal_view = expected_signal[0]; + for (size_t i = 0; i < length; i++) { + const float phase = static_cast<float>(i) * 2.0f * + static_cast<float>(M_PI) / kSampleRate * frequency; + const float expected_value_float = std::sin(phase); + expected_signal_view[i] = expected_value_float; + } + + AudioBuffer sine_wave(1U, length); + GenerateSineWave(frequency, kSampleRate, &sine_wave[0]); + EXPECT_TRUE(CompareAudioBuffers(sine_wave[0], expected_signal_view, + kEpsilonFloat)); + } + } +} + +TEST(TestUtilTest, GenerateSawToothSignal_SuccessfulGeneration) { + const size_t kLengthStart = 1; + const size_t kLengthStop = 200; + const size_t kLengthStep = 10; + for (size_t length = kLengthStart; length <= kLengthStop; + length += kLengthStep) { + const size_t kToothLengthStart = 1; + const size_t kToothLengthStop = 20; + const size_t kToothLengthStep = 3; + for (size_t tooth_length = kToothLengthStart; + tooth_length <= kToothLengthStop; tooth_length += kToothLengthStep) { + AudioBuffer expected_signal(1, length); + AudioBuffer::Channel& expected_signal_view = expected_signal[0]; + for (size_t i = 0; i < length; i++) { + const float expected_value = static_cast<float>(i % tooth_length) / + static_cast<float>(tooth_length) * + 2.0f - + 1.0f; + expected_signal_view[i] = expected_value; + } + + AudioBuffer signal(1, length); + GenerateSawToothSignal(tooth_length, &signal[0]); + EXPECT_TRUE( + CompareAudioBuffers(signal[0], expected_signal_view, kEpsilonFloat)); + } + } +} + +TEST(TestUtilTest, GenerateDiracImpulseFilterFloat_SuccessfulGeneration) { + const size_t kLengthStart = 1; + const size_t kLengthStop = 100; + const size_t kLengthStep = 10; + for (size_t length = kLengthStart; length <= kLengthStop; + length += kLengthStep) { + const size_t kDelayStart = 1; + const size_t kDelayStop = length - 1; + const size_t kDelayStep = 3; + for (size_t delay = kDelayStart; delay <= kDelayStop; delay += kDelayStep) { + AudioBuffer expected_signal(1, length); + GenerateSilence(&expected_signal[0]); + expected_signal[0][delay] = 1.0f; + + AudioBuffer dirac_buffer(1, length); + GenerateDiracImpulseFilter(delay, &dirac_buffer[0]); + EXPECT_TRUE(CompareAudioBuffers(dirac_buffer[0], expected_signal[0], + kEpsilonFloat)); + } + } +} + +TEST(TestUtilTest, GenerateIncreasingSignal_SuccessfulGeneration) { + const size_t kLengthStart = 1; + const size_t kLengthStop = 200; + const size_t kLengthStep = 10; + for (size_t length = kLengthStart; length <= kLengthStop; + length += kLengthStep) { + AudioBuffer expected_signal(1, length); + AudioBuffer::Channel& expected_signal_view = expected_signal[0]; + for (size_t i = 0; i < length; i++) { + const float expected_value = + static_cast<float>(i) / static_cast<float>(length) * 2.0f - 1.0f; + expected_signal_view[i] = expected_value; + } + + AudioBuffer signal(1, length); + GenerateIncreasingSignal(&signal[0]); + EXPECT_TRUE( + CompareAudioBuffers(signal[0], expected_signal[0], kEpsilonFloat)); + } +} + +TEST(TestUtilTest, ZeroCompare_SuccessfulZeroSignal) { + const size_t kLengthStart = 1; + const size_t kLengthStop = 100; + const size_t kLengthStep = 10; + for (size_t length = kLengthStart; length <= kLengthStop; + length += kLengthStep) { + const size_t kZeroLengthStart = 0; + const size_t kZeroLengthStop = length - 1; + const size_t kZeroLengthStep = 3; + for (size_t zero_length = kZeroLengthStart; zero_length <= kZeroLengthStop; + zero_length += kZeroLengthStep) { + AudioBuffer signal(1, length); + AudioBuffer::Channel& signal_view = signal[0]; + GenerateSilence(&signal_view); + for (size_t i = zero_length; i < length; i++) { + signal_view[i] = 123.0f * static_cast<float>(i) + 1.0f; + } + const size_t result = ZeroCompare(signal_view, kEpsilonFloat); + EXPECT_EQ(zero_length, result); + } + } +} + +TEST(TestUtilTest, ZeroCompare_SuccessfulNonzeroSignal) { + const size_t kLengthStart = 1; + const size_t kLengthStop = 100; + const size_t kLengthStep = 10; + for (size_t length = kLengthStart; length <= kLengthStop; + length += kLengthStep) { + const size_t kZeroLengthStart = 1; + const size_t kZeroLengthStop = length - 1; + const size_t kZeroLengthStep = 3; + for (size_t zero_length = kZeroLengthStart; zero_length <= kZeroLengthStop; + zero_length += kZeroLengthStep) { + AudioBuffer signal(1, length); + AudioBuffer::Channel& signal_view = signal[0]; + std::fill(signal_view.begin(), signal_view.end(), 100.0f); + for (size_t i = 0; i < zero_length - 1; i++) { + signal_view[i] = 0.0f; + const size_t result = ZeroCompare(signal_view, kEpsilonFloat); + EXPECT_NE(zero_length, result); + } + } + } +} + +TEST(TestUtilTest, DelayCompare_SuccessfulEqualDelay) { + const size_t kLengthStart = 1; + const size_t kLengthStop = 100; + const size_t kLengthStep = 10; + for (size_t length = kLengthStart; length <= kLengthStop; + length += kLengthStep) { + AudioBuffer original_signal(1, length); + AudioBuffer::Channel& original_signal_view = original_signal[0]; + + GenerateIncreasingSignal(&original_signal_view); + + const size_t kDelayStart = 0; + const size_t kDelayStop = length - 1; + const size_t kDelayStep = 3; + for (size_t delay = kDelayStart; delay <= kDelayStop; delay += kDelayStep) { + AudioBuffer delayed_signal(1, length + delay); + AudioBuffer::Channel& delayed_signal_view = delayed_signal[0]; + GenerateSilence(&delayed_signal_view); + std::copy(original_signal_view.begin(), original_signal_view.end(), + delayed_signal_view.begin() + delay); + const size_t result = DelayCompare( + original_signal_view, delayed_signal_view, delay, kEpsilonFloat); + EXPECT_EQ(delayed_signal_view.size(), result); + } + } +} + +TEST(TestUtilTest, DelayCompare_SuccessfulNotEqualDelay) { + const size_t kLengthStart = 1; + const size_t kLengthStop = 20; + const size_t kLengthStep = 10; + for (size_t length = kLengthStart; length <= kLengthStop; + length += kLengthStep) { + AudioBuffer original_signal(1, length); + AudioBuffer::Channel& original_signal_view = original_signal[0]; + + GenerateIncreasingSignal(&original_signal_view); + + const size_t kDelayStart = 1; + const size_t kDelayStop = length - 1; + const size_t kDelayStep = 3; + for (size_t delay = kDelayStart; delay <= kDelayStop; delay += kDelayStep) { + // Test altering first delayed element. + { + AudioBuffer delayed_signal(1, length + delay); + AudioBuffer::Channel& delayed_signal_view = delayed_signal[0]; + std::copy(original_signal_view.begin(), original_signal_view.end(), + delayed_signal_view.begin() + delay); + delayed_signal_view[delay] = -100.0f; + const size_t result = DelayCompare( + original_signal_view, delayed_signal_view, delay, kEpsilonFloat); + EXPECT_NE(delayed_signal_view.size(), result); + } + // Test altering last delayed element. + { + AudioBuffer delayed_signal(1, length + delay); + AudioBuffer::Channel& delayed_signal_view = delayed_signal[0]; + std::copy(original_signal_view.begin(), original_signal_view.end(), + delayed_signal_view.begin() + delay); + delayed_signal_view[delayed_signal_view.size() - 1] = -100.0f; + const size_t result = DelayCompare( + original_signal_view, delayed_signal_view, delay, kEpsilonFloat); + EXPECT_NE(delayed_signal_view.size(), result); + } + } + } +} + +} // namespace + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/threadsafe_fifo.h b/src/3rdparty/resonance-audio/resonance_audio/utils/threadsafe_fifo.h new file mode 100644 index 000000000..a8c863714 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/threadsafe_fifo.h @@ -0,0 +1,258 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_UTILS_THREADSAFE_FIFO_H_ +#define RESONANCE_AUDIO_UTILS_THREADSAFE_FIFO_H_ + +#include <atomic> +#include <chrono> +#include <condition_variable> +#include <memory> +#include <mutex> +#include <thread> +#include <vector> + +#include "base/logging.h" + +namespace vraudio { + +// Container to share preallocated data between threads. It is thread-safe for +// single producer - single consumer FIFO usage. +// +// @tparam T Object type that the FIFO handles. +template <typename T> +class ThreadsafeFifo { + public: + // Constructor preallocates the maximum number of objects in the FIFO queue + // and defines the maximum waiting period before triggering a buffer underflow + // or overflow event. Sleeping is enabled by default and can be disabled via + // |EnableBlockingSleepUntilMethods|. + // + // @param max_objects Maximum number of objects in FIFO queue. + explicit ThreadsafeFifo(size_t max_objects); + + // Constructor preallocates the maximum number of objects in the FIFO queue. + // Sleeping is enabled by default and can be disabled via + // |EnableBlockingSleepUntilMethods|. + // + // @param max_objects Maximum number of objects in FIFO queue. + // @param init Initializer to be assigned to allocated objects. + ThreadsafeFifo(size_t max_objects, const T& init); + + // Returns a pointer to an available input object T. If the queue is full, a + // nullptr is returned. + // + // @return Pointer to an available input object. Nullptr if no input object is + // available. + T* AcquireInputObject(); + + // Releases a previously acquired input object to be pushed onto the FIFO + // front. + void ReleaseInputObject(const T* object); + + // Returns a pointer to an output object T. If the queue is empty, a nullptr + // is returned. + // + // @return Pointer to the output object. Nullptr on empty queue. + T* AcquireOutputObject(); + + // Releases a previously acquired output object back to the FIFO. + void ReleaseOutputObject(const T* object); + + // Blocks until the FIFO queue has an input object available or + // |EnableBlockingSleepUntilMethods(false)| is called. + // + // Returns true if free slot is available. + bool SleepUntilInputObjectIsAvailable() const; + + // Blocks until the FIFO queue has an output object available or + // |EnableBlockingSleepUntilMethods(false)| is called. + // + // Returns true if an object is available. + bool SleepUntilOutputObjectIsAvailable() const; + + // Allows for unblocking |SleepUntil[Input|Output]ObjectIsAvailable| + // method. + void EnableBlockingSleepUntilMethods(bool enable); + + // Returns the number of objects in the FIFO queue. + size_t Size() const; + + // Returns true if FIFO queue is empty, false otherwise. + bool Empty() const; + + // Returns true if FIFO queue is full, false otherwise. + bool Full() const; + + // Clears the FIFO queue. This call is only thread-safe if called by the + // consumer. + void Clear(); + + private: + // Conditional to signal empty/full queue events. + mutable std::mutex fifo_empty_mutex_; + mutable std::condition_variable fifo_empty_conditional_; + + mutable std::mutex fifo_full_mutex_; + mutable std::condition_variable fifo_full_conditional_; + + // Vector that stores all objects. + std::vector<T> fifo_; + size_t read_pos_; + size_t write_pos_; + + // Atomic counter that reflects the size of |fifo_|. + std::atomic<size_t> fifo_size_; + + std::atomic<bool> enable_sleeping_; +}; + +template <typename T> +ThreadsafeFifo<T>::ThreadsafeFifo(size_t max_objects) + : fifo_(max_objects), + read_pos_(0), + write_pos_(0), + fifo_size_(0), + enable_sleeping_(true) { + CHECK_GT(max_objects, 0) << "FIFO size must be greater than zero"; +} + +template <typename T> +ThreadsafeFifo<T>::ThreadsafeFifo(size_t max_objects, const T& init) + : ThreadsafeFifo(max_objects) { + for (auto& object : fifo_) { + object = init; + } +} + +template <typename T> +T* ThreadsafeFifo<T>::AcquireInputObject() { + if (Full()) { + return nullptr; + } + CHECK_LT(fifo_size_, fifo_.size()); + + // Add object to FIFO queue. + return &fifo_[write_pos_]; +} + +template <typename T> +void ThreadsafeFifo<T>::ReleaseInputObject(const T* object) { + DCHECK_EQ(object, &fifo_[write_pos_]); + + ++write_pos_; + write_pos_ = write_pos_ % fifo_.size(); + if (fifo_size_.fetch_add(1) == 0) { + { + // Taking the lock and dropping it immediately assure that the notify + // cannot happen between the check of the predicate and wait of the + // |fifo_empty_conditional_|. + std::lock_guard<std::mutex> lock(fifo_empty_mutex_); + } + // In case of an empty queue, notify reader. + fifo_empty_conditional_.notify_one(); + } +} + +template <typename T> +T* ThreadsafeFifo<T>::AcquireOutputObject() { + if (Empty()) { + return nullptr; + } + CHECK_GT(fifo_size_, 0); + return &fifo_[read_pos_]; +} + +template <typename T> +void ThreadsafeFifo<T>::ReleaseOutputObject(const T* object) { + DCHECK_EQ(object, &fifo_[read_pos_]); + + ++read_pos_; + read_pos_ = read_pos_ % fifo_.size(); + + if (fifo_size_.fetch_sub(1) == fifo_.size()) { + { + // Taking the lock and dropping it immediately assure that the notify + // cannot happen between the check of the predicate and wait of the + // |fifo_full_conditional_|. + std::lock_guard<std::mutex> lock(fifo_full_mutex_); + } + // In case of a previously full queue, notify writer. + fifo_full_conditional_.notify_one(); + } +} + +template <typename T> +bool ThreadsafeFifo<T>::SleepUntilInputObjectIsAvailable() const { + // In case of a full queue, wait to allow objects to be popped from the + // FIFO queue. + std::unique_lock<std::mutex> lock(fifo_full_mutex_); + fifo_full_conditional_.wait(lock, [this]() { + return fifo_size_.load() < fifo_.size() || !enable_sleeping_.load(); + }); + return fifo_size_.load() < fifo_.size(); +} + +template <typename T> +bool ThreadsafeFifo<T>::SleepUntilOutputObjectIsAvailable() const { + // In case of an empty queue, wait for new objects to be added. + std::unique_lock<std::mutex> lock(fifo_empty_mutex_); + fifo_empty_conditional_.wait(lock, [this]() { + return fifo_size_.load() > 0 || !enable_sleeping_.load(); + }); + return fifo_size_.load() > 0; +} + +template <typename T> +void ThreadsafeFifo<T>::EnableBlockingSleepUntilMethods(bool enable) { + enable_sleeping_ = enable; + // Taking the lock and dropping it immediately assure that the notify + // cannot happen between the check of the predicate and wait of the + // |fifo_empty_conditional_| and |fifo_full_conditional_|. + { std::lock_guard<std::mutex> lock(fifo_empty_mutex_); } + { std::lock_guard<std::mutex> lock(fifo_full_mutex_); } + fifo_empty_conditional_.notify_one(); + fifo_full_conditional_.notify_one(); +} + +template <typename T> +size_t ThreadsafeFifo<T>::Size() const { + return fifo_size_.load(); +} + +template <typename T> +bool ThreadsafeFifo<T>::Empty() const { + return fifo_size_.load() == 0; +} + +template <typename T> +bool ThreadsafeFifo<T>::Full() const { + return fifo_size_.load() == fifo_.size(); +} + +template <typename T> +void ThreadsafeFifo<T>::Clear() { + while (!Empty()) { + T* output = AcquireOutputObject(); + if (output != nullptr) { + ReleaseOutputObject(output); + } + } +} + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_UTILS_THREADSAFE_FIFO_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/vorbis_stream_encoder.cc b/src/3rdparty/resonance-audio/resonance_audio/utils/vorbis_stream_encoder.cc new file mode 100644 index 000000000..f8b2f2287 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/vorbis_stream_encoder.cc @@ -0,0 +1,174 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Prevent Visual Studio from complaining about std::copy_n. +#if defined(_WIN32) +#define _SCL_SECURE_NO_WARNINGS +#endif + +#include "utils/vorbis_stream_encoder.h" + +#include "vorbis/vorbisenc.h" +#include "base/audio_buffer.h" +#include "base/logging.h" + +namespace vraudio { + +VorbisStreamEncoder::VorbisStreamEncoder() : init_(false) {} + +bool VorbisStreamEncoder::InitializeForFile(const std::string& filename, + size_t num_channels, + int sample_rate, + EncodingMode encoding_mode, + int bitrate, float quality) { + output_file_stream_.open(filename, + std::ios::out | std::ios::trunc | std::ios::binary); + if (!output_file_stream_.good()) { + LOG(ERROR) << "Could not open output file: " << filename; + return false; + } + + int return_value = 1; + vorbis_info_init(&vorbis_info_); + + switch (encoding_mode) { + case EncodingMode::kVariableBitRate: + return_value = vorbis_encode_init_vbr( + &vorbis_info_, static_cast<long>(num_channels), + sample_rate, quality); + break; + case EncodingMode::kAverageBitRate: + return_value = vorbis_encode_init( + &vorbis_info_, static_cast<long>(num_channels), + sample_rate, -1 /* max_bitrate */, bitrate, -1 /* quality */); + break; + case EncodingMode::kUndefined: + default: + break; + } + + if (return_value != 0) { + return false; + } + + vorbis_comment_init(&vorbis_comment_); + vorbis_comment_add_tag(&vorbis_comment_, "ENCODER", "VrAudio"); + vorbis_analysis_init(&vorbis_state_, &vorbis_info_); + vorbis_block_init(&vorbis_state_, &vorbis_block_); + ogg_stream_init(&stream_state_, 1 /* serial_number */); + + // Generate Ogg header + ogg_packet header; + ogg_packet header_comments; + ogg_packet header_code; + + vorbis_analysis_headerout(&vorbis_state_, &vorbis_comment_, &header, + &header_comments, &header_code); + ogg_stream_packetin(&stream_state_, &header); + ogg_stream_packetin(&stream_state_, &header_comments); + ogg_stream_packetin(&stream_state_, &header_code); + + while (true) { + return_value = ogg_stream_flush(&stream_state_, &ogg_page_); + if (return_value == 0) { + break; + } + if (!WriteOggPage()) { + return false; + } + } + init_ = true; + return true; +} + +bool VorbisStreamEncoder::AddPlanarBuffer(const float* const* input_ptrs, + size_t num_channels, + size_t num_frames) { + CHECK(init_); + PrepareVorbisBuffer(input_ptrs, num_channels, num_frames); + return PerformEncoding(); +} + +bool VorbisStreamEncoder::FlushAndClose() { + // Signal end of stream. + vorbis_analysis_wrote(&vorbis_state_, 0); + if (!PerformEncoding()) { + return false; + } + + output_file_stream_.close(); + + vorbis_comment_clear(&vorbis_comment_); + vorbis_dsp_clear(&vorbis_state_); + vorbis_block_clear(&vorbis_block_); + ogg_stream_clear(&stream_state_); + vorbis_info_clear(&vorbis_info_); + + init_ = false; + return true; +} + +void VorbisStreamEncoder::PrepareVorbisBuffer(const float* const* input_ptrs, + size_t num_channels, + size_t num_frames) { + float** buffer = vorbis_analysis_buffer( + &vorbis_state_, static_cast<int>(num_channels * num_frames)); + for (size_t channel = 0; channel < num_channels; ++channel) { + std::copy_n(input_ptrs[channel], num_frames, buffer[channel]); + } + + vorbis_analysis_wrote(&vorbis_state_, static_cast<int>(num_frames)); +} + +bool VorbisStreamEncoder::PerformEncoding() { + CHECK(init_); + while (vorbis_analysis_blockout(&vorbis_state_, &vorbis_block_) == 1) { + vorbis_analysis(&vorbis_block_, nullptr); + vorbis_bitrate_addblock(&vorbis_block_); + + while (vorbis_bitrate_flushpacket(&vorbis_state_, &ogg_packet_)) { + ogg_stream_packetin(&stream_state_, &ogg_packet_); + + bool end_of_stream = false; + while (!end_of_stream) { + int result = ogg_stream_pageout(&stream_state_, &ogg_page_); + if (result == 0) { + break; + } + if (!WriteOggPage()) { + return false; + } + if (ogg_page_eos(&ogg_page_)) { + end_of_stream = true; + } + } + } + } + return true; +} + +bool VorbisStreamEncoder::WriteOggPage() { + output_file_stream_.write(reinterpret_cast<char*>(ogg_page_.header), + ogg_page_.header_len); + output_file_stream_.write(reinterpret_cast<char*>(ogg_page_.body), + ogg_page_.body_len); + if (!output_file_stream_.good()) { + return false; + } + return true; +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/vorbis_stream_encoder.h b/src/3rdparty/resonance-audio/resonance_audio/utils/vorbis_stream_encoder.h new file mode 100644 index 000000000..cf66d2ad2 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/vorbis_stream_encoder.h @@ -0,0 +1,114 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_UTILS_VORBIS_STREAM_ENCODER_H_ +#define RESONANCE_AUDIO_UTILS_VORBIS_STREAM_ENCODER_H_ + +#include <fstream> +#include <iostream> + +#include "base/integral_types.h" + +#include "ogg/ogg.h" +#include "vorbis/codec.h" + +namespace vraudio { + +class VorbisStreamEncoder { + public: + // Supported encoding modes. + enum class EncodingMode { + kUndefined, + // Variable bit rate mode (VBR). + kVariableBitRate, + // Average bit rate mode (ABR). + kAverageBitRate, + }; + + VorbisStreamEncoder(); + + // Initializes Vorbis encoding. + // + // @param filename Ogg vorbis output file. If it doesn't exist, it will be + // created. Existing files will be overwritten. + // @param num_channels Number of input channels. + // @param sample_rate Sample rate of input audio buffers. + // @param encoding_mode Selects variable (VBR) or average encoding (ABR). + // @param bitrate Target bitrate (only used when selecting ABR encoding). + // @param quality Target quality (only used when selecting VBR encoding). The + // usable range is -.1 (lowest quality, smallest file) to 1. (highest + // quality, largest file). + // @return False in case of file I/O errors or libvorbis initialization + // failures like non-supported channel/sample rate configuration. + bool InitializeForFile(const std::string& filename, size_t num_channels, + int sample_rate, EncodingMode encoding_mode, + int bitrate, float quality); + + // Feeds input audio data into libvorbis encoder and triggers encoding. + // + // @param Array of pointers to planar channel data. + // @param num_channels Number of input channels. + // @param num_frames Number of input frames. + // @return False in case of file I/O errors or missing encoder initialization. + bool AddPlanarBuffer(const float* const* input_ptrs, size_t num_channels, + size_t num_frames); + + // Flushes the remaining audio buffers and closes the output file. + // + // @return False in case of file I/O errors or missing encoder initialization. + bool FlushAndClose(); + + private: + // Copies input audio data into libvorbis encoder buffer. + // + // @param Array of pointers to planar channel data. + // @param num_channels Number of input channels. + // @param num_frames Number of input frames. + void PrepareVorbisBuffer(const float* const* input_ptrs, size_t num_channels, + size_t num_frames); + + // Performs encoding of audio data prepared via |PrepareVorbisBuffer| or when + // the end of stream has been signaled. + // + // @return False in case of file I/O errors or missing encoder initialization. + bool PerformEncoding(); + + // Dumps data from |ogg_page_| struct to |output_file_stream_|. + // + // @return False in case of file I/O errors or missing encoder initialization. + bool WriteOggPage(); + + // Flag indicating if encoder has been successfully initialized. + bool init_; + + // Output file stream. + std::ofstream output_file_stream_; + + // libogg structs. + ogg_stream_state stream_state_; + ogg_page ogg_page_; + ogg_packet ogg_packet_; + + // libvorbis structs. + vorbis_info vorbis_info_; + vorbis_comment vorbis_comment_; + vorbis_dsp_state vorbis_state_; + vorbis_block vorbis_block_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_UTILS_VORBIS_STREAM_ENCODER_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/wav.cc b/src/3rdparty/resonance-audio/resonance_audio/utils/wav.cc new file mode 100644 index 000000000..5edbbc7bd --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/wav.cc @@ -0,0 +1,53 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "utils/wav.h" + +#include <cerrno> +#include <fstream> +#include <string> + +#include "base/integral_types.h" +#include "base/logging.h" +#include "utils/wav_reader.h" + +namespace vraudio { + +Wav::Wav(size_t num_channels, int sample_rate, + std::vector<int16_t>&& interleaved_samples) + : num_channels_(num_channels), + sample_rate_(sample_rate), + interleaved_samples_(interleaved_samples) {} + +Wav::~Wav() {} + +std::unique_ptr<const Wav> Wav::CreateOrNull(std::istream* binary_stream) { + WavReader wav_reader(binary_stream); + const size_t num_total_samples = wav_reader.GetNumTotalSamples(); + if (!wav_reader.IsHeaderValid() || num_total_samples == 0) { + return nullptr; + } + std::vector<int16> interleaved_samples(num_total_samples); + if (wav_reader.ReadSamples(num_total_samples, &interleaved_samples[0]) != + num_total_samples) { + return nullptr; + } + return std::unique_ptr<Wav>(new Wav(wav_reader.GetNumChannels(), + wav_reader.GetSampleRateHz(), + std::move(interleaved_samples))); +} + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/wav.h b/src/3rdparty/resonance-audio/resonance_audio/utils/wav.h new file mode 100644 index 000000000..c76bd8f3a --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/wav.h @@ -0,0 +1,66 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_UTILS_WAV_H_ +#define RESONANCE_AUDIO_UTILS_WAV_H_ + +#include <cstdint> +#include <memory> +#include <vector> + +namespace vraudio { + +// Wraps WavReader class to decode a wav file into memory. +class Wav { + public: + ~Wav(); + + // Reads a RIFF WAVE from an opened binary stream. + static std::unique_ptr<const Wav> CreateOrNull(std::istream* binary_stream); + + // Returns reference to interleaved samples. + const std::vector<int16_t>& interleaved_samples() const { + return interleaved_samples_; + } + + // Returns number of channels. + size_t GetNumChannels() const { return num_channels_; } + + // Returns sample rate. + int GetSampleRateHz() const { return sample_rate_; } + + private: + // Private constructor used by static factory methods. + // + // @param num_channels Number of audio channels. + // @param sample_rate Sample rate. + // @param interleaved_samples Decoded interleaved samples. + Wav(size_t num_channels, int sample_rate, + std::vector<int16_t>&& interleaved_samples); + + // Number of channels. + size_t num_channels_; + + // Sample rate. + int sample_rate_; + + // Interleaved samples. + std::vector<int16_t> interleaved_samples_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_UTILS_WAV_H_ diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/wav_reader.cc b/src/3rdparty/resonance-audio/resonance_audio/utils/wav_reader.cc new file mode 100644 index 000000000..ca1078467 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/wav_reader.cc @@ -0,0 +1,214 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#include "utils/wav_reader.h" + +#include <algorithm> +#include <string> + +#include "base/integral_types.h" +#include "base/logging.h" + +namespace vraudio { + +namespace { + +struct ChunkHeader { + char id[4]; + uint32 size; +}; + +struct WavFormat { + ChunkHeader header; + uint16 format_tag; // Integer identifier of the format + uint16 num_channels; // Number of audio channels + uint32 samples_rate; // Audio sample rate + uint32 average_bytes_per_second; // Bytes per second (possibly approximate) + uint16 block_align; // Size in bytes of a sample block (all channels) + uint16 bits_per_sample; // Size in bits of a single per-channel sample +}; +static_assert(sizeof(WavFormat) == 24, "Padding in WavFormat struct detected"); + +struct WavHeader { + struct { + ChunkHeader header; + char format[4]; + } riff; + WavFormat format; + struct { + ChunkHeader header; + } data; +}; + +// Size of WAV header. +const size_t kWavHeaderSize = 44; + +static_assert(sizeof(WavHeader) == kWavHeaderSize, + "Padding in WavHeader struct detected"); + +// Supported WAV encoding formats. +static const uint16 kExtensibleWavFormat = 0xfffe; +static const uint16 kPcmFormat = 0x1; +} // namespace + +WavReader::WavReader(std::istream* binary_stream) + : binary_stream_(CHECK_NOTNULL(binary_stream)), + num_channels_(0), + sample_rate_hz_(-1), + num_total_samples_(0), + num_remaining_samples_(0), + pcm_offset_bytes_(0) { + init_ = ParseHeader(); +} + +size_t WavReader::ReadBinaryDataFromStream(void* target_ptr, size_t size) { + if (!binary_stream_->good()) { + return 0; + } + binary_stream_->read(static_cast<char*>(target_ptr), size); + return static_cast<size_t>(binary_stream_->gcount()); +} + +bool WavReader::ParseHeader() { + WavHeader header; + // Exclude data field to be able to optionally parse the two-byte extension + // field. + if (ReadBinaryDataFromStream(&header, kWavHeaderSize - sizeof(header.data)) != + kWavHeaderSize - sizeof(header.data)) + return false; + const uint32 format_size = header.format.header.size; + // Size of |WavFormat| without |ChunkHeader|. + static const uint32 kFormatSubChunkHeader = + sizeof(WavFormat) - sizeof(ChunkHeader); + if (format_size < kFormatSubChunkHeader) { + return false; + } + if (format_size != kFormatSubChunkHeader) { + // Parse optional extension fields. + int16 extension_size; + if (ReadBinaryDataFromStream(&extension_size, sizeof(extension_size)) != + sizeof(extension_size)) + return false; + int8 extension_data; + for (size_t i = 0; i < static_cast<size_t>(extension_size); ++i) { + if (ReadBinaryDataFromStream(&extension_data, sizeof(extension_data)) != + sizeof(extension_data)) + return false; + } + } + if (header.format.format_tag == kExtensibleWavFormat) { + // Skip extensible wav format "fact" header. + ChunkHeader fact_header; + if (ReadBinaryDataFromStream(&fact_header, sizeof(fact_header)) != + sizeof(fact_header)) { + return false; + } + if (std::string(fact_header.id, 4) != "fact") { + return false; + } + int8 fact_data; + for (size_t i = 0; i < static_cast<size_t>(fact_header.size); ++i) { + if (ReadBinaryDataFromStream(&fact_data, sizeof(fact_data)) != + sizeof(fact_data)) + return false; + } + } + + // Read the "data" header. + if (ReadBinaryDataFromStream(&header.data, sizeof(header.data)) != + sizeof(header.data)) { + return false; + } + + num_channels_ = header.format.num_channels; + sample_rate_hz_ = header.format.samples_rate; + + bytes_per_sample_ = header.format.bits_per_sample / 8; + if (bytes_per_sample_ == 0 || bytes_per_sample_ != sizeof(int16)) { + return false; + } + const size_t bytes_in_payload = header.data.header.size; + num_total_samples_ = bytes_in_payload / bytes_per_sample_; + num_remaining_samples_ = num_total_samples_; + + if (header.format.num_channels == 0 || num_total_samples_ == 0 || + bytes_in_payload % bytes_per_sample_ != 0 || + (header.format.format_tag != kPcmFormat && + header.format.format_tag != kExtensibleWavFormat) || + (std::string(header.riff.header.id, 4) != "RIFF") || + (std::string(header.riff.format, 4) != "WAVE") || + (std::string(header.format.header.id, 4) != "fmt ") || + (std::string(header.data.header.id, 4) != "data")) { + return false; + } + + int64 current_position = binary_stream_->tellg(); + if (current_position < 0) { + return false; + } + pcm_offset_bytes_ = static_cast<uint64>(current_position); + return true; +} + +size_t WavReader::ReadSamples(size_t num_samples, int16* target_buffer) { + const size_t num_samples_to_read = + std::min(num_remaining_samples_, num_samples); + if (num_samples_to_read == 0) { + return 0; + } + const size_t num_bytes_read = + ReadBinaryDataFromStream(target_buffer, num_samples * sizeof(int16)); + const size_t num_samples_read = num_bytes_read / bytes_per_sample_; + + num_remaining_samples_ -= num_samples_read; + return num_samples_read; +} + +int64 WavReader::SeekToFrame(const uint64 frame_position) { + DCHECK_GT(num_channels_, 0U); + if (frame_position <= (num_total_samples_ / num_channels_)) { + const uint64 seek_position_byte = + pcm_offset_bytes_ + frame_position * num_channels_ * bytes_per_sample_; + binary_stream_->seekg(seek_position_byte, binary_stream_->beg); + } + + int64 binary_stream_position_byte = + static_cast<int64>(binary_stream_->tellg()); + if (binary_stream_position_byte < 0) { + // Return an error status if the actual bitstream position could not be + // queried. + return binary_stream_position_byte; + } + + if (static_cast<uint64>(binary_stream_position_byte) > pcm_offset_bytes_) { + DCHECK_GT(bytes_per_sample_, 0U); + return (static_cast<uint64>(binary_stream_position_byte) - + pcm_offset_bytes_) / + (bytes_per_sample_ * num_channels_); + } + + return 0; +} + +size_t WavReader::GetNumTotalSamples() const { return num_total_samples_; } + +size_t WavReader::GetNumChannels() const { return num_channels_; } + +int WavReader::GetSampleRateHz() const { return sample_rate_hz_; } + +bool WavReader::IsHeaderValid() const { return init_; } + +} // namespace vraudio diff --git a/src/3rdparty/resonance-audio/resonance_audio/utils/wav_reader.h b/src/3rdparty/resonance-audio/resonance_audio/utils/wav_reader.h new file mode 100644 index 000000000..513853c30 --- /dev/null +++ b/src/3rdparty/resonance-audio/resonance_audio/utils/wav_reader.h @@ -0,0 +1,103 @@ +/* +Copyright 2018 Google Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS-IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef RESONANCE_AUDIO_UTILS_WAV_READER_H_ +#define RESONANCE_AUDIO_UTILS_WAV_READER_H_ + +#include <cstdint> +#include <istream> + +#include "base/integral_types.h" + +namespace vraudio { + +// Basic RIFF WAVE decoder that supports multichannel 16-bit PCM. +class WavReader { + public: + // Constructor decodes WAV header. + // + // @param binary_stream Binary input stream to read from. + explicit WavReader(std::istream* binary_stream); + + // True if WAV header was successfully parsed. + bool IsHeaderValid() const; + + // Returns the total number of samples defined in the WAV header. Note that + // the actual number of samples in the file can differ. + size_t GetNumTotalSamples() const; + + // Returns number of channels. + size_t GetNumChannels() const; + + // Returns sample rate in Hertz. + int GetSampleRateHz() const; + + // Seek to a specific frame position within the wave file. If frame_position + // is not a valid address, then the internal read position remains unchanged. + // + // @param frame_position Destination frame position for play cursor. + // @return Actual frame position of cursor after this seek operation. A + // negative return value indicates a stream failure. + int64 SeekToFrame(const uint64 frame_position); + + // Reads samples from WAV file into target buffer. + // + // @param num_samples Number of samples to read. + // @param target_buffer Target buffer to write to. + // @return Number of decoded samples. + size_t ReadSamples(size_t num_samples, int16_t* target_buffer); + + private: + // Parses WAV header. + // + // @return True on success. + bool ParseHeader(); + + // Helper method to read binary data from input stream. + // + // @param size Number of bytes to read. + // @param target_ptr Target buffer to write to. + // @return Number of bytes read. + size_t ReadBinaryDataFromStream(void* target_ptr, size_t size); + + // Binary input stream. + std::istream* binary_stream_; + + // Flag indicating if the WAV header was parsed successfully. + bool init_; + + // Number of audio channels. + size_t num_channels_; + + // Sample rate in Hertz. + int sample_rate_hz_; + + // Total number of samples. + size_t num_total_samples_; + + // Number of remaining samples in WAV file. + size_t num_remaining_samples_; + + // Bytes per sample as defined in the WAV header. + size_t bytes_per_sample_; + + // Offset into data stream where PCM data begins. + uint64 pcm_offset_bytes_; +}; + +} // namespace vraudio + +#endif // RESONANCE_AUDIO_UTILS_WAV_READER_H_ |