From 42ff9460075775d684ffe90481cc9adfed78ea0d Mon Sep 17 00:00:00 2001 From: YangZhou <56786796+SmileGoat@users.noreply.github.com> Date: Thu, 8 Dec 2022 19:55:53 +0800 Subject: [PATCH] [audio] mv paddlespeech/audio to paddleaudio (#2706) * split paddlespeech/audio to paddleaudio. * add sox io ,sox effect, kaldi native fbank to paddleaudio. --- .gitignore | 4 + .pre-commit-config.yaml | 27 +- .readthedocs.yml | 2 +- audio/CMakeLists.txt | 70 + audio/README.md | 35 + audio/cmake/FindGFortranLibs.cmake | 153 ++ audio/cmake/external/openblas.cmake | 119 + audio/cmake/pybind.cmake | 42 + audio/cmake/summary.cmake | 45 + audio/paddleaudio/CMakeLists.txt | 19 + .../paddleaudio}/__init__.py | 15 +- audio/paddleaudio/_extension.py | 167 ++ audio/paddleaudio/_internal/__init__.py | 0 audio/paddleaudio/_internal/module_utils.py | 151 ++ .../paddleaudio}/backends/__init__.py | 12 + audio/paddleaudio/backends/common.py | 55 + audio/paddleaudio/backends/no_backend.py | 32 + .../paddleaudio/backends/soundfile_backend.py | 677 +++++ audio/paddleaudio/backends/sox_io_backend.py | 106 + audio/paddleaudio/backends/utils.py | 83 + .../paddleaudio}/compliance/__init__.py | 0 .../paddleaudio}/compliance/kaldi.py | 0 .../paddleaudio}/compliance/librosa.py | 0 .../paddleaudio}/datasets/__init__.py | 0 .../paddleaudio}/datasets/dataset.py | 2 +- .../paddleaudio}/datasets/esc50.py | 2 +- .../paddleaudio}/datasets/gtzan.py | 2 +- .../paddleaudio}/datasets/hey_snips.py | 0 .../paddleaudio}/datasets/rirs_noises.py | 4 +- .../paddleaudio}/datasets/tess.py | 2 +- .../paddleaudio}/datasets/urban_sound.py | 2 +- .../paddleaudio}/datasets/voxceleb.py | 2 +- .../paddleaudio}/features/__init__.py | 0 .../paddleaudio}/features/layers.py | 0 .../paddleaudio}/functional/__init__.py | 0 .../paddleaudio}/functional/functional.py | 0 .../paddleaudio}/functional/window.py | 222 +- .../paddleaudio/kaldi}/__init__.py | 2 + audio/paddleaudio/kaldi/kaldi.py | 132 + .../paddleaudio}/metric/__init__.py | 0 .../audio => audio/paddleaudio}/metric/eer.py | 0 audio/paddleaudio/sox_effects/__init__.py | 21 + audio/paddleaudio/sox_effects/sox_effects.py | 241 ++ audio/paddleaudio/src/CMakeLists.txt | 217 ++ audio/paddleaudio/src/optional/COPYING | 121 + audio/paddleaudio/src/optional/optional.hpp | 2182 +++++++++++++++++ .../src/pybind/kaldi/feature_common.h | 49 + .../src/pybind/kaldi/feature_common_inl.h | 93 + .../src/pybind/kaldi/kaldi_feature.cc | 75 + .../src/pybind/kaldi/kaldi_feature.h | 64 + .../src/pybind/kaldi/kaldi_feature_wrapper.cc | 51 + .../src/pybind/kaldi/kaldi_feature_wrapper.h | 40 + audio/paddleaudio/src/pybind/pybind.cpp | 148 ++ audio/paddleaudio/src/pybind/sox/effects.cpp | 259 ++ audio/paddleaudio/src/pybind/sox/effects.h | 37 + .../src/pybind/sox/effects_chain.cpp | 597 +++++ .../src/pybind/sox/effects_chain.h | 78 + audio/paddleaudio/src/pybind/sox/io.cpp | 279 +++ audio/paddleaudio/src/pybind/sox/io.h | 61 + audio/paddleaudio/src/pybind/sox/types.cpp | 143 ++ audio/paddleaudio/src/pybind/sox/types.h | 58 + audio/paddleaudio/src/pybind/sox/utils.cpp | 550 +++++ audio/paddleaudio/src/pybind/sox/utils.h | 114 + audio/paddleaudio/src/utils.cpp | 35 + audio/paddleaudio/third_party/.gitignore | 2 + audio/paddleaudio/third_party/CMakeLists.txt | 15 + .../third_party/kaldi/CMakeLists.txt | 111 + .../third_party/patches/config.guess | 1754 +++++++++++++ .../third_party/patches/config.sub | 1890 ++++++++++++++ .../third_party/patches/libmad.patch | 86 + .../paddleaudio/third_party/patches/sox.patch | 16 + .../third_party/sox/CMakeLists.txt | 254 ++ audio/paddleaudio/utils/__init__.py | 27 + audio/paddleaudio/utils/download.py | 64 + audio/paddleaudio/utils/env.py | 60 + .../paddleaudio/utils/error.py | 7 + audio/paddleaudio/utils/log.py | 139 ++ audio/paddleaudio/utils/numeric.py | 107 + audio/paddleaudio/utils/sox_utils.py | 103 + audio/paddleaudio/utils/tensor_utils.py | 192 ++ audio/paddleaudio/utils/time.py | 72 + audio/setup.py | 293 +++ .../audio => audio/tests}/backends/base.py | 0 audio/tests/backends/common.py | 32 + audio/tests/backends/soundfile/base.py | 34 + audio/tests/backends/soundfile/common.py | 89 + audio/tests/backends/soundfile/common_utils | 1 + audio/tests/backends/soundfile/info_test.py | 199 ++ audio/tests/backends/soundfile/load_test.py | 363 +++ audio/tests/backends/soundfile/save_test.py | 323 +++ .../tests}/backends/soundfile/test_io.py | 13 +- audio/tests/backends/sox_io/common.py | 89 + audio/tests/backends/sox_io/common_utils | 1 + audio/tests/backends/sox_io/info_test.py | 322 +++ audio/tests/backends/sox_io/load_test.py | 56 + audio/tests/backends/sox_io/save_test.py | 188 ++ audio/tests/backends/sox_io/smoke_test.py | 189 ++ .../tests/backends/sox_io/sox_effect_test.py | 364 +++ .../sox_io/sox_effect_test_args.jsonl | 77 + .../audio => audio/tests/benchmark}/README.md | 1 + .../tests/benchmark}/log_melspectrogram.py | 20 +- .../tests/benchmark}/melspectrogram.py | 20 +- .../audio => audio/tests/benchmark}/mfcc.py | 20 +- audio/tests/common_utils/__init__.py | 15 + audio/tests/common_utils/case_utils.py | 50 + audio/tests/common_utils/data_utils.py | 135 + .../tests/common_utils/parameterized_utils.py | 44 + audio/tests/common_utils/sox_utils.py | 116 + audio/tests/common_utils/wav_utils.py | 102 + .../tests/features}/__init__.py | 0 .../audio => audio/tests}/features/base.py | 3 +- .../tests}/features/test_istft.py | 2 +- .../tests}/features/test_kaldi.py | 14 +- audio/tests/features/test_kaldi_feat.py | 55 + .../tests}/features/test_librosa.py | 34 +- .../features/test_log_melspectrogram.py | 4 +- .../tests}/features/test_spectrogram.py | 4 +- .../tests}/features/test_stft.py | 2 +- audio/tests/features/testdata/fbank_feat.ark | Bin 0 -> 86596 bytes .../features/testdata/fbank_feat_txt.ark | 942 +++++++ audio/tests/features/testdata/pitch_feat.ark | Bin 0 -> 7552 bytes .../features/testdata/pitch_feat_txt.ark | 942 +++++++ audio/tests/features/testdata/test.wav | Bin 0 -> 301838 bytes audio/tools/setup_helpers/__init__.py | 1 + audio/tools/setup_helpers/extension.py | 152 ++ .../api/paddlespeech.audio.backends.rst | 16 - ...peech.audio.backends.soundfile_backend.rst | 7 - ...addlespeech.audio.backends.sox_backend.rst | 7 - .../paddlespeech.audio.compliance.kaldi.rst | 7 - .../api/paddlespeech.audio.compliance.rst | 16 - .../paddlespeech.audio.datasets.dataset.rst | 7 - .../paddlespeech.audio.datasets.hey_snips.rst | 7 - ...addlespeech.audio.datasets.rirs_noises.rst | 7 - .../api/paddlespeech.audio.datasets.rst | 22 - .../api/paddlespeech.audio.datasets.tess.rst | 7 - ...addlespeech.audio.datasets.urban_sound.rst | 7 - .../paddlespeech.audio.datasets.voxceleb.rst | 7 - ...ddlespeech.audio.functional.functional.rst | 7 - .../api/paddlespeech.audio.functional.rst | 16 - .../paddlespeech.audio.functional.window.rst | 7 - docs/source/api/paddlespeech.audio.metric.rst | 15 - docs/source/api/paddlespeech.audio.rst | 4 +- docs/source/api/paddlespeech.audio.utils.rst | 1 + .../api/paddlespeech.cls.exps.panns.rst | 1 - ...dlespeech.kws.exps.mdtc.plot_det_curve.rst | 7 + docs/source/api/paddlespeech.rst | 8 + docs/source/api/paddlespeech.t2s.exps.rst | 1 + .../paddlespeech.t2s.exps.stream_play_tts.rst | 7 + ...h.t2s.models.vits.monotonic_align.core.rst | 7 + ...speech.t2s.models.vits.monotonic_align.rst | 16 + ....t2s.models.vits.monotonic_align.setup.rst | 7 + docs/source/api/paddlespeech.version.rst | 7 + docs/source/audio_api/modules.rst | 7 + .../audio_api/paddleaudio.backends.common.rst | 7 + .../paddleaudio.backends.no_backend.rst} | 4 +- .../source/audio_api/paddleaudio.backends.rst | 19 + ...paddleaudio.backends.soundfile_backend.rst | 7 + .../paddleaudio.backends.sox_io_backend.rst} | 4 +- .../audio_api/paddleaudio.backends.utils.rst | 7 + .../paddleaudio.compliance.kaldi.rst | 7 + .../paddleaudio.compliance.librosa.rst | 7 + .../audio_api/paddleaudio.compliance.rst | 16 + .../paddleaudio.datasets.dataset.rst | 7 + .../audio_api/paddleaudio.datasets.esc50.rst | 7 + .../audio_api/paddleaudio.datasets.gtzan.rst | 7 + .../paddleaudio.datasets.hey_snips.rst | 7 + .../paddleaudio.datasets.rirs_noises.rst} | 4 +- .../source/audio_api/paddleaudio.datasets.rst | 22 + .../audio_api/paddleaudio.datasets.tess.rst | 7 + .../paddleaudio.datasets.urban_sound.rst} | 4 +- .../paddleaudio.datasets.voxceleb.rst} | 4 +- .../audio_api/paddleaudio.features.layers.rst | 7 + .../source/audio_api/paddleaudio.features.rst | 15 + .../paddleaudio.functional.functional.rst | 7 + .../audio_api/paddleaudio.functional.rst | 16 + .../paddleaudio.functional.window.rst | 7 + .../audio_api/paddleaudio.kaldi.kaldi.rst | 7 + docs/source/audio_api/paddleaudio.kaldi.rst | 15 + .../audio_api/paddleaudio.metric.eer.rst | 7 + docs/source/audio_api/paddleaudio.metric.rst | 15 + docs/source/audio_api/paddleaudio.rst | 23 + .../audio_api/paddleaudio.sox_effects.rst | 15 + .../paddleaudio.sox_effects.sox_effects.rst | 7 + .../audio_api/paddleaudio.utils.download.rst | 7 + .../audio_api/paddleaudio.utils.env.rst | 7 + .../audio_api/paddleaudio.utils.error.rst | 7 + .../audio_api/paddleaudio.utils.log.rst | 7 + .../audio_api/paddleaudio.utils.numeric.rst | 7 + docs/source/audio_api/paddleaudio.utils.rst | 22 + .../audio_api/paddleaudio.utils.sox_utils.rst | 7 + .../paddleaudio.utils.tensor_utils.rst | 7 + .../audio_api/paddleaudio.utils.time.rst | 7 + docs/source/cls/custom_dataset.md | 4 +- docs/source/conf.py | 2 + docs/source/index.rst | 1 + docs/source/install.md | 14 +- examples/esc50/cls0/conf/panns.yaml | 4 +- examples/hey_snips/kws0/conf/mdtc.yaml | 2 +- examples/tess/README.md | 34 + .../cls0/conf/panns_logmelspectrogram.yaml | 32 + .../tess/cls0/conf/panns_melspectrogram.yaml | 32 + examples/tess/cls0/conf/panns_mfcc.yaml | 33 + .../tess/cls0/conf/panns_spectrogram.yaml | 28 + examples/tess/cls0/local/train.py | 191 ++ examples/tess/cls0/local/train.sh | 12 + examples/tess/cls0/path.sh | 13 + examples/tess/cls0/run.sh | 35 + examples/voxceleb/sv0/local/data_prepare.py | 2 +- .../make_rirs_noise_csv_dataset_from_json.py | 2 +- .../local/make_vox_csv_dataset_from_json.py | 2 +- paddlespeech/__init__.py | 1 - paddlespeech/audio/.gitignore | 1 + paddlespeech/audio/__init__.py | 9 - .../audio/backends/soundfile_backend.py | 325 --- paddlespeech/audio/backends/sox_backend.py | 13 - paddlespeech/audio/streamdata/autodecode.py | 6 +- paddlespeech/audio/streamdata/filters.py | 4 +- paddlespeech/audio/streamdata/tariterators.py | 6 +- paddlespeech/audio/transform/spectrogram.py | 3 +- paddlespeech/audio/utils/__init__.py | 2 + paddlespeech/audio/utils/numeric.py | 77 + paddlespeech/cli/cls/infer.py | 4 +- paddlespeech/cli/kws/infer.py | 6 +- paddlespeech/cli/vector/infer.py | 4 +- paddlespeech/cls/exps/panns/deploy/predict.py | 11 +- paddlespeech/cls/exps/panns/export_model.py | 2 +- paddlespeech/cls/exps/panns/predict.py | 7 +- paddlespeech/cls/exps/panns/train.py | 6 +- paddlespeech/cls/models/panns/panns.py | 2 +- paddlespeech/kws/exps/mdtc/train.py | 4 +- .../frontend/featurizer/audio_featurizer.py | 3 +- paddlespeech/s2t/models/u2_st/u2_st.py | 4 +- paddlespeech/s2t/modules/fbank.py | 2 +- .../engine/vector/python/vector_engine.py | 4 +- paddlespeech/server/util.py | 4 +- .../vector/exps/ecapa_tdnn/extract_emb.py | 4 +- paddlespeech/vector/exps/ecapa_tdnn/test.py | 2 +- paddlespeech/vector/exps/ecapa_tdnn/train.py | 2 +- .../exps/ge2e/speaker_verification_dataset.py | 2 +- paddlespeech/vector/io/dataset.py | 4 +- paddlespeech/vector/io/dataset_from_json.py | 7 +- setup.py | 1 + speechx/speechx/kaldi/base/kaldi-types.h | 7 +- speechx/speechx/kaldi/feat/feature-plp.h | 2 +- .../speechx/kaldi/feat/online-feature-itf.h | 125 + speechx/speechx/kaldi/feat/online-feature.h | 2 +- speechx/speechx/kaldi/feat/pitch-functions.h | 2 +- speechx/speechx/kaldi/matrix/kaldi-blas.h | 6 + tests/unit/audio/features/__init__.py | 13 - tools/extras/install_openblas.sh | 2 +- 250 files changed, 18847 insertions(+), 797 deletions(-) create mode 100644 audio/CMakeLists.txt create mode 100644 audio/README.md create mode 100644 audio/cmake/FindGFortranLibs.cmake create mode 100644 audio/cmake/external/openblas.cmake create mode 100644 audio/cmake/pybind.cmake create mode 100644 audio/cmake/summary.cmake create mode 100644 audio/paddleaudio/CMakeLists.txt rename {paddlespeech/audio/backends => audio/paddleaudio}/__init__.py (72%) create mode 100644 audio/paddleaudio/_extension.py create mode 100644 audio/paddleaudio/_internal/__init__.py create mode 100644 audio/paddleaudio/_internal/module_utils.py rename {tests/unit/audio => audio/paddleaudio}/backends/__init__.py (59%) create mode 100644 audio/paddleaudio/backends/common.py create mode 100644 audio/paddleaudio/backends/no_backend.py create mode 100644 audio/paddleaudio/backends/soundfile_backend.py create mode 100644 audio/paddleaudio/backends/sox_io_backend.py create mode 100644 audio/paddleaudio/backends/utils.py rename {paddlespeech/audio => audio/paddleaudio}/compliance/__init__.py (100%) rename {paddlespeech/audio => audio/paddleaudio}/compliance/kaldi.py (100%) rename {paddlespeech/audio => audio/paddleaudio}/compliance/librosa.py (100%) rename {paddlespeech/audio => audio/paddleaudio}/datasets/__init__.py (100%) rename {paddlespeech/audio => audio/paddleaudio}/datasets/dataset.py (97%) rename {paddlespeech/audio => audio/paddleaudio}/datasets/esc50.py (99%) rename {paddlespeech/audio => audio/paddleaudio}/datasets/gtzan.py (99%) rename {paddlespeech/audio => audio/paddleaudio}/datasets/hey_snips.py (100%) rename {paddlespeech/audio => audio/paddleaudio}/datasets/rirs_noises.py (98%) rename {paddlespeech/audio => audio/paddleaudio}/datasets/tess.py (99%) rename {paddlespeech/audio => audio/paddleaudio}/datasets/urban_sound.py (99%) rename {paddlespeech/audio => audio/paddleaudio}/datasets/voxceleb.py (99%) rename {paddlespeech/audio => audio/paddleaudio}/features/__init__.py (100%) rename {paddlespeech/audio => audio/paddleaudio}/features/layers.py (100%) rename {paddlespeech/audio => audio/paddleaudio}/functional/__init__.py (100%) rename {paddlespeech/audio => audio/paddleaudio}/functional/functional.py (100%) rename {paddlespeech/audio => audio/paddleaudio}/functional/window.py (60%) rename {tests/unit/audio/backends/soundfile => audio/paddleaudio/kaldi}/__init__.py (92%) create mode 100644 audio/paddleaudio/kaldi/kaldi.py rename {paddlespeech/audio => audio/paddleaudio}/metric/__init__.py (100%) rename {paddlespeech/audio => audio/paddleaudio}/metric/eer.py (100%) create mode 100644 audio/paddleaudio/sox_effects/__init__.py create mode 100644 audio/paddleaudio/sox_effects/sox_effects.py create mode 100644 audio/paddleaudio/src/CMakeLists.txt create mode 100644 audio/paddleaudio/src/optional/COPYING create mode 100644 audio/paddleaudio/src/optional/optional.hpp create mode 100644 audio/paddleaudio/src/pybind/kaldi/feature_common.h create mode 100644 audio/paddleaudio/src/pybind/kaldi/feature_common_inl.h create mode 100644 audio/paddleaudio/src/pybind/kaldi/kaldi_feature.cc create mode 100644 audio/paddleaudio/src/pybind/kaldi/kaldi_feature.h create mode 100644 audio/paddleaudio/src/pybind/kaldi/kaldi_feature_wrapper.cc create mode 100644 audio/paddleaudio/src/pybind/kaldi/kaldi_feature_wrapper.h create mode 100644 audio/paddleaudio/src/pybind/pybind.cpp create mode 100644 audio/paddleaudio/src/pybind/sox/effects.cpp create mode 100644 audio/paddleaudio/src/pybind/sox/effects.h create mode 100644 audio/paddleaudio/src/pybind/sox/effects_chain.cpp create mode 100644 audio/paddleaudio/src/pybind/sox/effects_chain.h create mode 100644 audio/paddleaudio/src/pybind/sox/io.cpp create mode 100644 audio/paddleaudio/src/pybind/sox/io.h create mode 100644 audio/paddleaudio/src/pybind/sox/types.cpp create mode 100644 audio/paddleaudio/src/pybind/sox/types.h create mode 100644 audio/paddleaudio/src/pybind/sox/utils.cpp create mode 100644 audio/paddleaudio/src/pybind/sox/utils.h create mode 100644 audio/paddleaudio/src/utils.cpp create mode 100644 audio/paddleaudio/third_party/.gitignore create mode 100644 audio/paddleaudio/third_party/CMakeLists.txt create mode 100644 audio/paddleaudio/third_party/kaldi/CMakeLists.txt create mode 100644 audio/paddleaudio/third_party/patches/config.guess create mode 100644 audio/paddleaudio/third_party/patches/config.sub create mode 100644 audio/paddleaudio/third_party/patches/libmad.patch create mode 100644 audio/paddleaudio/third_party/patches/sox.patch create mode 100644 audio/paddleaudio/third_party/sox/CMakeLists.txt create mode 100644 audio/paddleaudio/utils/__init__.py create mode 100644 audio/paddleaudio/utils/download.py create mode 100644 audio/paddleaudio/utils/env.py rename paddlespeech/audio/io/__init__.py => audio/paddleaudio/utils/error.py (83%) create mode 100644 audio/paddleaudio/utils/log.py create mode 100644 audio/paddleaudio/utils/numeric.py create mode 100644 audio/paddleaudio/utils/sox_utils.py create mode 100644 audio/paddleaudio/utils/tensor_utils.py create mode 100644 audio/paddleaudio/utils/time.py create mode 100644 audio/setup.py rename {tests/unit/audio => audio/tests}/backends/base.py (100%) create mode 100644 audio/tests/backends/common.py create mode 100644 audio/tests/backends/soundfile/base.py create mode 100644 audio/tests/backends/soundfile/common.py create mode 120000 audio/tests/backends/soundfile/common_utils create mode 100644 audio/tests/backends/soundfile/info_test.py create mode 100644 audio/tests/backends/soundfile/load_test.py create mode 100644 audio/tests/backends/soundfile/save_test.py rename {tests/unit/audio => audio/tests}/backends/soundfile/test_io.py (88%) create mode 100644 audio/tests/backends/sox_io/common.py create mode 120000 audio/tests/backends/sox_io/common_utils create mode 100644 audio/tests/backends/sox_io/info_test.py create mode 100644 audio/tests/backends/sox_io/load_test.py create mode 100644 audio/tests/backends/sox_io/save_test.py create mode 100644 audio/tests/backends/sox_io/smoke_test.py create mode 100644 audio/tests/backends/sox_io/sox_effect_test.py create mode 100644 audio/tests/backends/sox_io/sox_effect_test_args.jsonl rename {tests/benchmark/audio => audio/tests/benchmark}/README.md (97%) rename {tests/benchmark/audio => audio/tests/benchmark}/log_melspectrogram.py (87%) rename {tests/benchmark/audio => audio/tests/benchmark}/melspectrogram.py (85%) rename {tests/benchmark/audio => audio/tests/benchmark}/mfcc.py (86%) create mode 100644 audio/tests/common_utils/__init__.py create mode 100644 audio/tests/common_utils/case_utils.py create mode 100644 audio/tests/common_utils/data_utils.py create mode 100644 audio/tests/common_utils/parameterized_utils.py create mode 100644 audio/tests/common_utils/sox_utils.py create mode 100644 audio/tests/common_utils/wav_utils.py rename {paddlespeech/audio/sox_effects => audio/tests/features}/__init__.py (100%) rename {tests/unit/audio => audio/tests}/features/base.py (96%) rename {tests/unit/audio => audio/tests}/features/test_istft.py (96%) rename {tests/unit/audio => audio/tests}/features/test_kaldi.py (86%) create mode 100644 audio/tests/features/test_kaldi_feat.py rename {tests/unit/audio => audio/tests}/features/test_librosa.py (89%) rename {tests/unit/audio => audio/tests}/features/test_log_melspectrogram.py (94%) rename {tests/unit/audio => audio/tests}/features/test_spectrogram.py (93%) rename {tests/unit/audio => audio/tests}/features/test_stft.py (95%) create mode 100644 audio/tests/features/testdata/fbank_feat.ark create mode 100644 audio/tests/features/testdata/fbank_feat_txt.ark create mode 100644 audio/tests/features/testdata/pitch_feat.ark create mode 100644 audio/tests/features/testdata/pitch_feat_txt.ark create mode 100644 audio/tests/features/testdata/test.wav create mode 100644 audio/tools/setup_helpers/__init__.py create mode 100644 audio/tools/setup_helpers/extension.py delete mode 100644 docs/source/api/paddlespeech.audio.backends.rst delete mode 100644 docs/source/api/paddlespeech.audio.backends.soundfile_backend.rst delete mode 100644 docs/source/api/paddlespeech.audio.backends.sox_backend.rst delete mode 100644 docs/source/api/paddlespeech.audio.compliance.kaldi.rst delete mode 100644 docs/source/api/paddlespeech.audio.compliance.rst delete mode 100644 docs/source/api/paddlespeech.audio.datasets.dataset.rst delete mode 100644 docs/source/api/paddlespeech.audio.datasets.hey_snips.rst delete mode 100644 docs/source/api/paddlespeech.audio.datasets.rirs_noises.rst delete mode 100644 docs/source/api/paddlespeech.audio.datasets.rst delete mode 100644 docs/source/api/paddlespeech.audio.datasets.tess.rst delete mode 100644 docs/source/api/paddlespeech.audio.datasets.urban_sound.rst delete mode 100644 docs/source/api/paddlespeech.audio.datasets.voxceleb.rst delete mode 100644 docs/source/api/paddlespeech.audio.functional.functional.rst delete mode 100644 docs/source/api/paddlespeech.audio.functional.rst delete mode 100644 docs/source/api/paddlespeech.audio.functional.window.rst delete mode 100644 docs/source/api/paddlespeech.audio.metric.rst create mode 100644 docs/source/api/paddlespeech.kws.exps.mdtc.plot_det_curve.rst create mode 100644 docs/source/api/paddlespeech.t2s.exps.stream_play_tts.rst create mode 100644 docs/source/api/paddlespeech.t2s.models.vits.monotonic_align.core.rst create mode 100644 docs/source/api/paddlespeech.t2s.models.vits.monotonic_align.rst create mode 100644 docs/source/api/paddlespeech.t2s.models.vits.monotonic_align.setup.rst create mode 100644 docs/source/api/paddlespeech.version.rst create mode 100644 docs/source/audio_api/modules.rst create mode 100644 docs/source/audio_api/paddleaudio.backends.common.rst rename docs/source/{api/paddlespeech.audio.sox_effects.rst => audio_api/paddleaudio.backends.no_backend.rst} (51%) create mode 100644 docs/source/audio_api/paddleaudio.backends.rst create mode 100644 docs/source/audio_api/paddleaudio.backends.soundfile_backend.rst rename docs/source/{api/paddlespeech.audio.compliance.librosa.rst => audio_api/paddleaudio.backends.sox_io_backend.rst} (50%) create mode 100644 docs/source/audio_api/paddleaudio.backends.utils.rst create mode 100644 docs/source/audio_api/paddleaudio.compliance.kaldi.rst create mode 100644 docs/source/audio_api/paddleaudio.compliance.librosa.rst create mode 100644 docs/source/audio_api/paddleaudio.compliance.rst create mode 100644 docs/source/audio_api/paddleaudio.datasets.dataset.rst create mode 100644 docs/source/audio_api/paddleaudio.datasets.esc50.rst create mode 100644 docs/source/audio_api/paddleaudio.datasets.gtzan.rst create mode 100644 docs/source/audio_api/paddleaudio.datasets.hey_snips.rst rename docs/source/{api/paddlespeech.audio.datasets.gtzan.rst => audio_api/paddleaudio.datasets.rirs_noises.rst} (51%) create mode 100644 docs/source/audio_api/paddleaudio.datasets.rst create mode 100644 docs/source/audio_api/paddleaudio.datasets.tess.rst rename docs/source/{api/paddlespeech.audio.datasets.esc50.rst => audio_api/paddleaudio.datasets.urban_sound.rst} (51%) rename docs/source/{api/paddlespeech.audio.metric.eer.rst => audio_api/paddleaudio.datasets.voxceleb.rst} (52%) create mode 100644 docs/source/audio_api/paddleaudio.features.layers.rst create mode 100644 docs/source/audio_api/paddleaudio.features.rst create mode 100644 docs/source/audio_api/paddleaudio.functional.functional.rst create mode 100644 docs/source/audio_api/paddleaudio.functional.rst create mode 100644 docs/source/audio_api/paddleaudio.functional.window.rst create mode 100644 docs/source/audio_api/paddleaudio.kaldi.kaldi.rst create mode 100644 docs/source/audio_api/paddleaudio.kaldi.rst create mode 100644 docs/source/audio_api/paddleaudio.metric.eer.rst create mode 100644 docs/source/audio_api/paddleaudio.metric.rst create mode 100644 docs/source/audio_api/paddleaudio.rst create mode 100644 docs/source/audio_api/paddleaudio.sox_effects.rst create mode 100644 docs/source/audio_api/paddleaudio.sox_effects.sox_effects.rst create mode 100644 docs/source/audio_api/paddleaudio.utils.download.rst create mode 100644 docs/source/audio_api/paddleaudio.utils.env.rst create mode 100644 docs/source/audio_api/paddleaudio.utils.error.rst create mode 100644 docs/source/audio_api/paddleaudio.utils.log.rst create mode 100644 docs/source/audio_api/paddleaudio.utils.numeric.rst create mode 100644 docs/source/audio_api/paddleaudio.utils.rst create mode 100644 docs/source/audio_api/paddleaudio.utils.sox_utils.rst create mode 100644 docs/source/audio_api/paddleaudio.utils.tensor_utils.rst create mode 100644 docs/source/audio_api/paddleaudio.utils.time.rst create mode 100644 examples/tess/README.md create mode 100644 examples/tess/cls0/conf/panns_logmelspectrogram.yaml create mode 100644 examples/tess/cls0/conf/panns_melspectrogram.yaml create mode 100644 examples/tess/cls0/conf/panns_mfcc.yaml create mode 100644 examples/tess/cls0/conf/panns_spectrogram.yaml create mode 100644 examples/tess/cls0/local/train.py create mode 100755 examples/tess/cls0/local/train.sh create mode 100644 examples/tess/cls0/path.sh create mode 100755 examples/tess/cls0/run.sh create mode 100644 paddlespeech/audio/.gitignore delete mode 100644 paddlespeech/audio/backends/soundfile_backend.py delete mode 100644 paddlespeech/audio/backends/sox_backend.py create mode 100644 speechx/speechx/kaldi/feat/online-feature-itf.h delete mode 100644 tests/unit/audio/features/__init__.py diff --git a/.gitignore b/.gitignore index 1ed76375..75f56b60 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,9 @@ build *output/ +audio/dist/ +audio/fc_patch/ + docs/build/ docs/topic/ctc/warp-ctc/ @@ -42,6 +45,7 @@ tools/python-soundfile/ tools/onnx tools/onnxruntime tools/Paddle2ONNX +tools/onnx-simplifier/ speechx/fc_patch/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0435cfbe..15b842d5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,8 +3,13 @@ repos: rev: v0.16.0 hooks: - id: yapf - files: \.py$ - exclude: (?=third_party).*(\.py)$ + name: yapf + language: python + entry: yapf + args: [-i, -vv] + types: [python] + exclude: (?=speechx/speechx/kaldi|audio/paddleaudio/src|third_party).*(\.cpp|\.cc|\.h\.hpp|\.py)$ + - repo: https://github.com/pre-commit/pre-commit-hooks rev: a11d9314b22d8f8c7556443875b731ef05965464 hooks: @@ -30,7 +35,8 @@ repos: - --ignore=E501,E228,E226,E261,E266,E128,E402,W503 - --builtins=G,request - --jobs=1 - exclude: (?=third_party).*(\.py)$ + exclude: (?=speechx/speechx/kaldi|audio/paddleaudio/src|third_party).*(\.cpp|\.cc|\.h\.hpp|\.py)$ + - repo : https://github.com/Lucas-C/pre-commit-hooks rev: v1.0.1 hooks: @@ -42,6 +48,7 @@ repos: files: \.md$ - id: remove-tabs files: \.md$ + - repo: local hooks: - id: clang-format @@ -49,23 +56,17 @@ repos: description: Format files with ClangFormat entry: bash .pre-commit-hooks/clang-format.hook -i language: system - files: \.(c|cc|cxx|cpp|cu|h|hpp|hxx|cuh|proto)$ - exclude: (?=speechx/speechx/kaldi|speechx/patch|speechx/tools/fstbin|speechx/tools/lmbin|third_party/ctc_decoders).*(\.cpp|\.cc|\.h|\.py)$ - #- id: copyright_checker - # name: copyright_checker - # entry: python .pre-commit-hooks/copyright-check.hook - # language: system - # files: \.(c|cc|cxx|cpp|cu|h|hpp|hxx|proto|py)$ - # exclude: (?=third_party|pypinyin|speechx/speechx/kaldi|speechx/patch|speechx/tools/fstbin|speechx/tools/lmbin).*(\.cpp|\.cc|\.h|\.py)$ + files: \.(h\+\+|h|hh|hxx|hpp|cuh|c|cc|cpp|cu|c\+\+|cxx|tpp|txx)$ + exclude: (?=speechx/speechx/kaldi|audio/paddleaudio/src|speechx/patch|speechx/tools/fstbin|speechx/tools/lmbin|third_party/ctc_decoders).*(\.cpp|\.cc|\.h|\.hpp|\.py)$ - id: cpplint name: cpplint description: Static code analysis of C/C++ files language: python files: \.(h\+\+|h|hh|hxx|hpp|cuh|c|cc|cpp|cu|c\+\+|cxx|tpp|txx)$ - exclude: (?=speechx/speechx/kaldi|speechx/patch|speechx/tools/fstbin|speechx/tools/lmbin|third_party/ctc_decoders).*(\.cpp|\.cc|\.h|\.py)$ + exclude: (?=speechx/speechx/kaldi|audio/paddleaudio/src|speechx/patch|speechx/tools/fstbin|speechx/tools/lmbin|third_party/ctc_decoders).*(\.cpp|\.cc|\.h|\.hpp|\.py)$ entry: cpplint --filter=-build,-whitespace,+whitespace/comma,-whitespace/indent - repo: https://github.com/asottile/reorder_python_imports rev: v2.4.0 hooks: - id: reorder-python-imports - exclude: (?=third_party).*(\.py)$ + exclude: (?=speechx/speechx/kaldi|audio/paddleaudio/src|speechx/patch|speechx/tools/fstbin|speechx/tools/lmbin|third_party/ctc_decoders).*(\.cpp|\.cc|\.h\.hpp|\.py)$ diff --git a/.readthedocs.yml b/.readthedocs.yml index e922891e..dafc6bf3 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -23,4 +23,4 @@ python: - requirements: docs/requirements.txt - method: setuptools path: . - system_packages: true \ No newline at end of file + system_packages: true diff --git a/audio/CMakeLists.txt b/audio/CMakeLists.txt new file mode 100644 index 00000000..d9ae63cd --- /dev/null +++ b/audio/CMakeLists.txt @@ -0,0 +1,70 @@ +cmake_minimum_required(VERSION 3.16 FATAL_ERROR) + +# Use compiler ID "AppleClang" instead of "Clang" for XCode. +# Not setting this sometimes makes XCode C compiler gets detected as "Clang", +# even when the C++ one is detected as "AppleClang". +cmake_policy(SET CMP0010 NEW) +cmake_policy(SET CMP0025 NEW) + +# Suppress warning flags in default MSVC configuration. It's not +# mandatory that we do this (and we don't if cmake is old), but it's +# nice when it's possible, and it's possible on our Windows configs. +if(NOT CMAKE_VERSION VERSION_LESS 3.15.0) + cmake_policy(SET CMP0092 NEW) +endif() + +project(paddleaudio) + +# check and set CMAKE_CXX_STANDARD +string(FIND "${CMAKE_CXX_FLAGS}" "-std=c++" env_cxx_standard) +if(env_cxx_standard GREATER -1) + message( + WARNING "C++ standard version definition detected in environment variable." + "paddleaudio requires -std=c++14. Please remove -std=c++ settings in your environment.") +endif() + + +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_C_STANDARD 11) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) +set(CMAKE_VERBOSE_MAKEFILE ON) + +# Options +option(BUILD_SOX "Build libsox statically" ON) +option(BUILD_MAD "Enable libmad" ON) +option(BUILD_KALDI "Build kaldi statically" ON) +option(BUILD_PADDLEAUDIO_PYTHON_EXTENSION "Build Python extension" ON) + + +# cmake +set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};${PROJECT_SOURCE_DIR}/cmake;${PROJECT_SOURCE_DIR}/cmake/external") + +if (NOT MSVC) + find_package(GFortranLibs REQUIRED) + include(FortranCInterface) + include(FindGFortranLibs REQUIRED) +endif() + +# fc_patch dir +set(FETCHCONTENT_QUIET off) +get_filename_component(fc_patch "fc_patch" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") +set(FETCHCONTENT_BASE_DIR ${fc_patch}) +set(THIRD_PARTY_PATH ${fc_patch}) + +include(openblas) + +set(PYBIND11_PYTHON_VERSION ${PY_VERSION}) +include(cmake/pybind.cmake) +include_directories(${PYTHON_INCLUDE_DIR}) + +# packages +find_package(Python3 COMPONENTS Interpreter Development) + +# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread -O0 -Wall -g") +add_subdirectory(paddleaudio) + +# Summary +include(cmake/summary.cmake) +onnx_print_configuration_summary() diff --git a/audio/README.md b/audio/README.md new file mode 100644 index 00000000..f336ac9a --- /dev/null +++ b/audio/README.md @@ -0,0 +1,35 @@ +# PaddleAudio + +安装方式: pip install paddleaudio + +目前支持的平台:Linux: + +## Environment + +## Build wheel + +Linux test build whl environment: +* docker - `registry.baidubce.com/paddlepaddle/paddle:2.2.2` +* os - Ubuntu 16.04.7 LTS +* gcc/g++/gfortran - 8.2.0 +* cmake - 3.18.0 (need install) + +* [How to Install Docker](https://docs.docker.com/engine/install/) +* [A Docker Tutorial for Beginners](https://docker-curriculum.com/) + +1. First to launch docker container. + +``` +docker run --privileged --net=host --ipc=host -it --rm -v $PWD:/workspace --name=dev registry.baidubce.com/paddlepaddle/paddle:2.2.2 /bin/bash +``` +2. python setup.py bdist_wheel + +MAC:test build whl envrioment: +* os +* gcc/g++/gfortran 12.2.0 +* cpu Intel Xeon E5 x86_64 + + +Windows: +not support: paddleaudio C++ extension lib (sox io, kaldi native fbank) +python setup.py bdist_wheel \ No newline at end of file diff --git a/audio/cmake/FindGFortranLibs.cmake b/audio/cmake/FindGFortranLibs.cmake new file mode 100644 index 00000000..0ac35662 --- /dev/null +++ b/audio/cmake/FindGFortranLibs.cmake @@ -0,0 +1,153 @@ +#.rst: +# FindGFortranLibs +# -------- +# https://github.com/Argonne-National-Laboratory/PIPS/blob/master/cmake/Modules/FindGFortranLibs.cmake +# https://enccs.github.io/cmake-workshop/cxx-fortran/ +# +# Find gcc Fortran compiler & library paths +# +# The module defines the following variables: +# +# :: +# +# +# GFORTRANLIBS_FOUND - true if system has gfortran +# LIBGFORTRAN_LIBRARIES - path to libgfortran +# LIBQUADMATH_LIBRARIES - path to libquadmath +# GFORTRAN_LIBARIES_DIR - directory containing libgfortran, libquadmath +# GFORTRAN_INCLUDE_DIR - directory containing gfortran/gcc headers +# LIBGOMP_LIBRARIES - path to libgomp +# LIBGOMP_INCLUDE_DIR - directory containing omp.h header +# GFORTRAN_VERSION_STRING - version of gfortran found +# +set(CMAKE_REQUIRED_QUIET ${LIBIOMP_FIND_QUIETLY}) + +if(NOT CMAKE_REQUIRED_QUIET) + message(STATUS "Looking for gfortran related libraries...") +endif() + +enable_language(Fortran) +if(CMAKE_Fortran_COMPILER_ID MATCHES "GNU") + + # Basically, call "gfortran -v" to dump compiler info to the string + # GFORTRAN_VERBOSE_STR, which will be used to get necessary paths + message(STATUS "Extracting library and header information by calling 'gfortran -v'...") + execute_process(COMMAND "${CMAKE_Fortran_COMPILER}" "-v" ERROR_VARIABLE + GFORTRAN_VERBOSE_STR RESULT_VARIABLE FLAG) + + # For debugging + message(STATUS "'gfortran -v' returned:") + message(STATUS "${GFORTRAN_VERBOSE_STR}") + + # Detect gfortran version + string(REGEX MATCH "gcc version [^\t\n ]+" GFORTRAN_VER_STR "${GFORTRAN_VERBOSE_STR}") + string(REGEX REPLACE "gcc version ([^\t\n ]+)" "\\1" GFORTRAN_VERSION_STRING "${GFORTRAN_VER_STR}") + message(STATUS "Detected gfortran version ${GFORTRAN_VERSION_STRING}") + unset(GFORTRAN_VER_STR) + + set(MATCH_REGEX "[^\t\n ]+[\t\n ]+") + set(REPLACE_REGEX "([^\t\n ]+)") + + # Find architecture for compiler + string(REGEX MATCH "Target: [^\t\n ]+" + GFORTRAN_ARCH_STR "${GFORTRAN_VERBOSE_STR}") + message(STATUS "Architecture string: ${GFORTRAN_ARCH_STR}") + string(REGEX REPLACE "Target: ([^\t\n ]+)" "\\1" + GFORTRAN_ARCH "${GFORTRAN_ARCH_STR}") + message(STATUS "Detected gfortran architecture: ${GFORTRAN_ARCH}") + unset(GFORTRAN_ARCH_STR) + + # Find install prefix, if it exists; if not, use default + string(REGEX MATCH "--prefix=[^\t\n ]+[\t\n ]+" + GFORTRAN_PREFIX_STR "${GFORTRAN_VERBOSE_STR}") + if(NOT GFORTRAN_PREFIX_STR) + message(STATUS "Detected default gfortran prefix") + set(GFORTRAN_PREFIX_DIR "/usr/local") # default prefix for gcc install + else() + string(REGEX REPLACE "--prefix=([^\t\n ]+)" "\\1" + GFORTRAN_PREFIX_DIR "${GFORTRAN_PREFIX_STR}") + endif() + message(STATUS "Detected gfortran prefix: ${GFORTRAN_PREFIX_DIR}") + unset(GFORTRAN_PREFIX_STR) + + # Find install exec-prefix, if it exists; if not, use default + string(REGEX MATCH "--exec-prefix=[^\t\n ]+[\t\n ]+" "\\1" + GFORTRAN_EXEC_PREFIX_STR "${GFORTRAN_VERBOSE_STR}") + if(NOT GFORTRAN_EXEC_PREFIX_STR) + message(STATUS "Detected default gfortran exec-prefix") + set(GFORTRAN_EXEC_PREFIX_DIR "${GFORTRAN_PREFIX_DIR}") + else() + string(REGEX REPLACE "--exec-prefix=([^\t\n ]+)" "\\1" + GFORTRAN_EXEC_PREFIX_DIR "${GFORTRAN_EXEC_PREFIX_STR}") + endif() + message(STATUS "Detected gfortran exec-prefix: ${GFORTRAN_EXEC_PREFIX_DIR}") + UNSET(GFORTRAN_EXEC_PREFIX_STR) + + # Find library directory and include directory, if library directory specified + string(REGEX MATCH "--libdir=[^\t\n ]+" + GFORTRAN_LIB_DIR_STR "${GFORTRAN_VERBOSE_STR}") + if(NOT GFORTRAN_LIB_DIR_STR) + message(STATUS "Found --libdir flag -- not found") + message(STATUS "Using default gfortran library & include directory paths") + string(STRIP ${GFORTRAN_PREFIX_DIR} TMPLIBDIR) + set(GFORTRAN_LIBRARIES_DIR "${TMPLIBDIR}/lib64") + set(GFORTRAN_INCLUDE_DIR "${TMPLIBDIR}/include") + else() + message(STATUS "Found --libdir flag -- yes") + string(REGEX REPLACE "--libdir=([^\t\n ]+)" "\\1" + GFORTRAN_LIBRARIES_DIR "${GFORTRAN_LIB_DIR_STR}") + string(CONCAT GFORTRAN_INCLUDE_DIR "${GFORTRAN_LIBRARIES_DIR}" "/gcc/" "${GFORTRAN_ARCH}" "/" "${GFORTRAN_VERSION_STRING}" "/include") + endif() + message(STATUS "gfortran libraries path: ${GFORTRAN_LIBRARIES_DIR}") + message(STATUS "gfortran include path dir: ${GFORTRAN_INCLUDE_DIR}") + unset(GFORTRAN_LIB_DIR_STR) + + # There are lots of other build options for gcc & gfortran. For now, the + # options implemented above should cover a lot of common use cases. + + # Clean up be deleting the output string from "gfortran -v" + unset(GFORTRAN_VERBOSE_STR) + + # Find paths for libgfortran, libquadmath, libgomp + # libgomp needed for OpenMP support without Clang + find_library(LIBGFORTRAN_LIBRARIES NAMES gfortran libgfortran + HINTS ${GFORTRAN_LIBRARIES_DIR}) + find_library(LIBQUADMATH_LIBRARIES NAMES quadmath libquadmath + HINTS ${GFORTRAN_LIBRARIES_DIR}) + find_library(LIBGOMP_LIBRARIES NAMES gomp libgomp + HINTS ${GFORTRAN_LIBRARIES_DIR}) + + # Find OpenMP headers + find_path(LIBGOMP_INCLUDE_DIR NAMES omp.h HINTS ${GFORTRAN_INCLUDE_DIR}) + +else() + message(STATUS "CMAKE_Fortran_COMPILER_ID does not match 'GNU'!") +endif() + +include(FindPackageHandleStandardArgs) + +# Required: libgfortran, libquadmath, path for gfortran libraries +# Optional: libgomp, path for OpenMP headers, path for gcc/gfortran headers +find_package_handle_standard_args(GFortranLibs + REQUIRED_VARS LIBGFORTRAN_LIBRARIES LIBQUADMATH_LIBRARIES GFORTRAN_LIBRARIES_DIR + VERSION_VAR GFORTRAN_VERSION_STRING) + +if(GFORTRANLIBS_FOUND) + message(STATUS "Looking for gfortran libraries -- found") + message(STATUS "gfortran version: ${GFORTRAN_VERSION_STRING}") +else() + message(STATUS "Looking for gfortran libraries -- not found") +endif() + +mark_as_advanced(LIBGFORTRAN_LIBRARIES LIBQUADMATH_LIBRARIES + LIBGOMP_LIBRARIES LIBGOMP_INCLUDE_DIR + GFORTRAN_LIBRARIES_DIR GFORTRAN_INCLUDE_DIR) +# FindGFortranLIBS.cmake ends here + + +message(STATUS LIBGFORTRAN_LIBRARIES= ${LIBGFORTRAN_LIBRARIES}) +message(STATUS LIBQUADMATH_LIBRARIES= ${LIBQUADMATH_LIBRARIES}) +message(STATUS LIBGOMP_LIBRARIES= ${LIBGOMP_LIBRARIES}) +message(STATUS LIBGOMP_INCLUDE_DIR= ${LIBGOMP_INCLUDE_DIR}) +message(STATUS GFORTRAN_LIBRARIES_DIR= ${GFORTRAN_LIBRARIES_DIR}) +message(STATUS GFORTRAN_INCLUDE_DIR= ${GFORTRAN_INCLUDE_DIR}) diff --git a/audio/cmake/external/openblas.cmake b/audio/cmake/external/openblas.cmake new file mode 100644 index 00000000..9fee0063 --- /dev/null +++ b/audio/cmake/external/openblas.cmake @@ -0,0 +1,119 @@ +# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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(ExternalProject) + +set(CBLAS_PREFIX_DIR ${THIRD_PARTY_PATH}/openblas) +set(CBLAS_INSTALL_DIR ${THIRD_PARTY_PATH}/install/openblas) +set(CBLAS_REPOSITORY https://github.com/xianyi/OpenBLAS.git) +set(CBLAS_TAG v0.3.10) + +if(NOT WIN32) + set(CBLAS_LIBRARIES + "${CBLAS_INSTALL_DIR}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}openblas${CMAKE_STATIC_LIBRARY_SUFFIX}" + CACHE FILEPATH "openblas library." FORCE) + set(CBLAS_INC_DIR + "${CBLAS_INSTALL_DIR}/include" + CACHE PATH "openblas include directory." FORCE) + set(OPENBLAS_CC + "${CMAKE_C_COMPILER} -Wno-unused-but-set-variable -Wno-unused-variable") + + if(APPLE) + set(OPENBLAS_CC "${CMAKE_C_COMPILER} -isysroot ${CMAKE_OSX_SYSROOT}") + endif() + set(OPTIONAL_ARGS "") + set(COMMON_ARGS "") + + if(APPLE) + if(CMAKE_SYSTEM_PROCESSOR MATCHES "^x86(_64)?$") + set(OPTIONAL_ARGS DYNAMIC_ARCH=1 NUM_THREADS=64) + endif() + set(COMMON_ARGS CC=${OPENBLAS_CC} NO_SHARED=1) + endif() + + ExternalProject_Add( + OPENBLAS + URL "https://paddleaudio.bj.bcebos.com/build/OpenBLAS-0.3.10.zip" + GIT_SHALLOW YES + DOWNLOAD_DIR ${CBLAS_PREFIX_DIR} + SOURCE_DIR ${CBLAS_PREFIX_DIR} + INSTALL_DIR ${CBLAS_INSTALL_DIR} + BUILD_IN_SOURCE 1 + BUILD_COMMAND make -j${NPROC} ${COMMON_ARGS} ${OPTIONAL_ARGS} + INSTALL_COMMAND make install PREFIX= + UPDATE_COMMAND "" + CONFIGURE_COMMAND "" + BUILD_BYPRODUCTS ${CBLAS_LIBRARIES}) + + ExternalProject_Get_Property(OPENBLAS INSTALL_DIR) + set(OpenBLAS_INSTALL_PREFIX ${INSTALL_DIR}) + add_library(openblas STATIC IMPORTED) + add_dependencies(openblas OPENBLAS) + set_target_properties(openblas PROPERTIES IMPORTED_LINK_INTERFACE_LANGUAGES Fortran) + set_target_properties(openblas PROPERTIES IMPORTED_LOCATION ${OpenBLAS_INSTALL_PREFIX}/lib/libopenblas.a) + + link_directories(${OpenBLAS_INSTALL_PREFIX}/lib) + include_directories(${OpenBLAS_INSTALL_PREFIX}/include) + + set(OPENBLAS_LIBRARIES + ${OpenBLAS_INSTALL_PREFIX}/lib/libopenblas.a + ) + + add_library(libopenblas INTERFACE) + add_dependencies(libopenblas openblas) + target_include_directories(libopenblas INTERFACE ${OpenBLAS_INSTALL_PREFIX}/include/openblas) + target_link_libraries(libopenblas INTERFACE ${OPENBLAS_LIBRARIES}) +else() + set(CBLAS_LIBRARIES + "${CBLAS_INSTALL_DIR}/lib/openblas${CMAKE_STATIC_LIBRARY_SUFFIX}" + CACHE FILEPATH "openblas library." FORCE) + set(CBLAS_INC_DIR + "${CBLAS_INSTALL_DIR}/include/openblas" + CACHE PATH "openblas include directory." FORCE) + ExternalProject_Add( + extern_openblas + ${EXTERNAL_PROJECT_LOG_ARGS} + GIT_REPOSITORY ${CBLAS_REPOSITORY} + GIT_TAG ${CBLAS_TAG} + PREFIX ${CBLAS_PREFIX_DIR} + INSTALL_DIR ${CBLAS_INSTALL_DIR} + BUILD_IN_SOURCE 0 + UPDATE_COMMAND "" + CMAKE_ARGS -DCMAKE_C_COMPILER=clang-cl + -DCMAKE_CXX_COMPILER=clang-cl + -DCMAKE_C_FLAGS=${CMAKE_C_FLAGS} + -DCMAKE_INSTALL_PREFIX=${CBLAS_INSTALL_DIR} + -DCMAKE_BUILD_TYPE=Release #${THIRD_PARTY_BUILD_TYPE} + -DCMAKE_MT=mt + -DUSE_THREAD=OFF + -DBUILD_WITHOUT_LAPACK=NO + -DCMAKE_Fortran_COMPILER=flang + -DNOFORTRAN=0 + -DDYNAMIC_ARCH=ON + #${EXTERNAL_OPTIONAL_ARGS} + CMAKE_CACHE_ARGS + -DCMAKE_INSTALL_PREFIX:PATH=${CBLAS_INSTALL_DIR} + -DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON + -DCMAKE_BUILD_TYPE:STRING=Release #${THIRD_PARTY_BUILD_TYPE} + # ninja need to know where openblas.lib comes from + BUILD_BYPRODUCTS ${CBLAS_LIBRARIES}) + set(OPENBLAS_SHARED_LIB + ${CBLAS_INSTALL_DIR}/bin/openblas${CMAKE_SHARED_LIBRARY_SUFFIX}) + + add_library(openblas INTERFACE) + add_dependencies(openblas extern_openblas) + include_directories(${CBLAS_INC_DIR}) + link_libraries(${CBLAS_LIBRARIES}) +endif() + diff --git a/audio/cmake/pybind.cmake b/audio/cmake/pybind.cmake new file mode 100644 index 00000000..0ce1f57f --- /dev/null +++ b/audio/cmake/pybind.cmake @@ -0,0 +1,42 @@ +#the pybind11 is from:https://github.com/pybind/pybind11 +# Copyright (c) 2016 Wenzel Jakob , All rights reserved. + +SET(PYBIND_ZIP "v2.10.0.zip") +SET(LOCAL_PYBIND_ZIP ${FETCHCONTENT_BASE_DIR}/${PYBIND_ZIP}) +SET(PYBIND_SRC ${FETCHCONTENT_BASE_DIR}/pybind11) +SET(DOWNLOAD_URL "https://paddleaudio.bj.bcebos.com/build/v2.10.0.zip") +SET(PYBIND_TIMEOUT 600 CACHE STRING "Timeout in seconds when downloading pybind.") + +IF(NOT EXISTS ${LOCAL_PYBIND_ZIP}) + FILE(DOWNLOAD ${DOWNLOAD_URL} + ${LOCAL_PYBIND_ZIP} + TIMEOUT ${PYBIND_TIMEOUT} + STATUS ERR + SHOW_PROGRESS + ) + + IF(ERR EQUAL 0) + MESSAGE(STATUS "download pybind success") + ELSE() + MESSAGE(FATAL_ERROR "download pybind fail") + ENDIF() +ENDIF() + +IF(NOT EXISTS ${PYBIND_SRC}) + EXECUTE_PROCESS( + COMMAND ${CMAKE_COMMAND} -E tar xfz ${LOCAL_PYBIND_ZIP} + WORKING_DIRECTORY ${FETCHCONTENT_BASE_DIR} + RESULT_VARIABLE tar_result + ) + + file(RENAME ${FETCHCONTENT_BASE_DIR}/pybind11-2.10.0 ${PYBIND_SRC}) + + IF (tar_result MATCHES 0) + MESSAGE(STATUS "unzip pybind success") + ELSE() + MESSAGE(FATAL_ERROR "unzip pybind fail") + ENDIF() + +ENDIF() + +include_directories(${PYBIND_SRC}/include) diff --git a/audio/cmake/summary.cmake b/audio/cmake/summary.cmake new file mode 100644 index 00000000..f04d4469 --- /dev/null +++ b/audio/cmake/summary.cmake @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: Apache-2.0 + +# Prints accumulated ONNX configuration summary +function (onnx_print_configuration_summary) + message(STATUS "") + message(STATUS "******** Summary ********") + message(STATUS " CMake version : ${CMAKE_VERSION}") + message(STATUS " CMake command : ${CMAKE_COMMAND}") + message(STATUS " System : ${CMAKE_SYSTEM_NAME}") + message(STATUS " C++ compiler : ${CMAKE_CXX_COMPILER}") + message(STATUS " C++ compiler version : ${CMAKE_CXX_COMPILER_VERSION}") + message(STATUS " CXX flags : ${CMAKE_CXX_FLAGS}") + message(STATUS " Build type : ${CMAKE_BUILD_TYPE}") + get_directory_property(tmp DIRECTORY ${PROJECT_SOURCE_DIR} COMPILE_DEFINITIONS) + message(STATUS " Compile definitions : ${tmp}") + message(STATUS " CMAKE_PREFIX_PATH : ${CMAKE_PREFIX_PATH}") + message(STATUS " CMAKE_INSTALL_PREFIX : ${CMAKE_INSTALL_PREFIX}") + message(STATUS " CMAKE_MODULE_PATH : ${CMAKE_MODULE_PATH}") + message(STATUS "") + message(STATUS " ONNX version : ${ONNX_VERSION}") + message(STATUS " ONNX NAMESPACE : ${ONNX_NAMESPACE}") + message(STATUS " ONNX_USE_LITE_PROTO : ${ONNX_USE_LITE_PROTO}") + message(STATUS " USE_PROTOBUF_SHARED_LIBS : ${ONNX_USE_PROTOBUF_SHARED_LIBS}") + message(STATUS " Protobuf_USE_STATIC_LIBS : ${Protobuf_USE_STATIC_LIBS}") + message(STATUS " ONNX_DISABLE_EXCEPTIONS : ${ONNX_DISABLE_EXCEPTIONS}") + message(STATUS " ONNX_WERROR : ${ONNX_WERROR}") + message(STATUS " ONNX_BUILD_TESTS : ${ONNX_BUILD_TESTS}") + message(STATUS " ONNX_BUILD_BENCHMARKS : ${ONNX_BUILD_BENCHMARKS}") + message(STATUS " ONNXIFI_DUMMY_BACKEND : ${ONNXIFI_DUMMY_BACKEND}") + message(STATUS " ONNXIFI_ENABLE_EXT : ${ONNXIFI_ENABLE_EXT}") + message(STATUS "") + message(STATUS " Protobuf compiler : ${PROTOBUF_PROTOC_EXECUTABLE}") + message(STATUS " Protobuf includes : ${PROTOBUF_INCLUDE_DIRS}") + message(STATUS " Protobuf libraries : ${PROTOBUF_LIBRARIES}") + message(STATUS " BUILD_ONNX_PYTHON : ${BUILD_ONNX_PYTHON}") + message(STATUS " Python version : ${Python_VERSION}") + message(STATUS " Python executable : ${Python_EXECUTABLE}") + message(STATUS " Python includes : ${Python_INCLUDE_DIR}") + message(STATUS " Python libraries : ${Python_LIBRARY}") + message(STATUS " PYBIND11 : ${pybind11_FOUND}") + message(STATUS " Pybind11 version : ${pybind11_VERSION}") + message(STATUS " Pybind11 include : ${pybind11_INCLUDE_DIR}") + message(STATUS " Pybind11 includes : ${pybind11_INCLUDE_DIRS}") + message(STATUS " Pybind11 libraries : ${pybind11_LIBRARIES}") +endfunction() \ No newline at end of file diff --git a/audio/paddleaudio/CMakeLists.txt b/audio/paddleaudio/CMakeLists.txt new file mode 100644 index 00000000..dbf2bd3e --- /dev/null +++ b/audio/paddleaudio/CMakeLists.txt @@ -0,0 +1,19 @@ + +add_subdirectory(third_party) +add_subdirectory(src) + +if (APPLE) + file(COPY ${GFORTRAN_LIBRARIES_DIR}/libgcc_s.1.1.dylib + DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/lib) +endif(APPLE) + +if (UNIX AND NOT APPLE) + file(COPY ${GFORTRAN_LIBRARIES_DIR}/libgfortran.so.5 + DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/lib FOLLOW_SYMLINK_CHAIN) + + file(COPY ${GFORTRAN_LIBRARIES_DIR}/libquadmath.so.0 + DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/lib FOLLOW_SYMLINK_CHAIN) + + file(COPY ${GFORTRAN_LIBRARIES_DIR}/libgcc_s.so.1 + DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/lib FOLLOW_SYMLINK_CHAIN) +endif() diff --git a/paddlespeech/audio/backends/__init__.py b/audio/paddleaudio/__init__.py similarity index 72% rename from paddlespeech/audio/backends/__init__.py rename to audio/paddleaudio/__init__.py index 8eae07e8..3388b816 100644 --- a/paddlespeech/audio/backends/__init__.py +++ b/audio/paddleaudio/__init__.py @@ -11,9 +11,12 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from .soundfile_backend import depth_convert -from .soundfile_backend import load -from .soundfile_backend import normalize -from .soundfile_backend import resample -from .soundfile_backend import save -from .soundfile_backend import to_mono +from . import _extension +from . import backends +from . import compliance +from . import datasets +from . import features +from . import functional +from . import metric +from . import sox_effects +from . import utils diff --git a/audio/paddleaudio/_extension.py b/audio/paddleaudio/_extension.py new file mode 100644 index 00000000..13f4aabf --- /dev/null +++ b/audio/paddleaudio/_extension.py @@ -0,0 +1,167 @@ +import contextlib +import ctypes +import os +import sys +import types +import warnings +from pathlib import Path + +from ._internal import module_utils as _mod_utils # noqa: F401 + +# Query `hasattr` only once. +_SET_GLOBAL_FLAGS = hasattr(sys, 'getdlopenflags') and hasattr(sys, + 'setdlopenflags') + + +@contextlib.contextmanager +def dl_open_guard(): + """ + # https://manpages.debian.org/bullseye/manpages-dev/dlopen.3.en.html + Context manager to set the RTLD_GLOBAL dynamic linker flag while we open a + shared library to load custom operators. + """ + if _SET_GLOBAL_FLAGS: + old_flags = sys.getdlopenflags() + sys.setdlopenflags(old_flags | ctypes.RTLD_GLOBAL) + yield + if _SET_GLOBAL_FLAGS: + sys.setdlopenflags(old_flags) + + +def resolve_library_path(path: str) -> str: + return os.path.realpath(path) + + +class _Ops(types.ModuleType): + #__file__ = '_ops.py' + + def __init__(self): + super(_Ops, self).__init__('paddleaudio.ops') + self.loaded_libraries = set() + + def load_library(self, path): + """ + Loads a shared library from the given path into the current process. + This allows dynamically loading custom operators. For this, + you should compile your operator and + the static registration code into a shared library object, and then + call ``paddleaudio.ops.load_library('path/to/libcustom.so')`` to load the + shared object. + After the library is loaded, it is added to the + ``paddleaudio.ops.loaded_libraries`` attribute, a set that may be inspected + for the paths of all libraries loaded using this function. + Args: + path (str): A path to a shared library to load. + """ + path = resolve_library_path(path) + with dl_open_guard(): + # https://docs.python.org/3/library/ctypes.html?highlight=ctypes#loading-shared-libraries + # Import the shared library into the process, thus running its + # static (global) initialization code in order to register custom + # operators with the JIT. + ctypes.CDLL(path) + self.loaded_libraries.add(path) + + +_LIB_DIR = Path(__file__).parent / "lib" + + +def _get_lib_path(lib: str): + suffix = "pyd" if os.name == "nt" else "so" + path = _LIB_DIR / f"{lib}.{suffix}" + return path + + +def _load_lib(lib: str) -> bool: + """Load extension module + Note: + In case `paddleaudio` is deployed with `pex` format, the library file + is not in a standard location. + In this case, we expect that `libpaddlleaudio` is available somewhere + in the search path of dynamic loading mechanism, so that importing + `_paddlleaudio` will have library loader find and load `libpaddlleaudio`. + This is the reason why the function should not raising an error when the library + file is not found. + Returns: + bool: + True if the library file is found AND the library loaded without failure. + False if the library file is not found (like in the case where paddlleaudio + is deployed with pex format, thus the shared library file is + in a non-standard location.). + If the library file is found but there is an issue loading the library, + (such as missing dependency) then this function raises the exception as-is. + Raises: + Exception: + If the library file is found, but there is an issue loading the library file, + (when underlying `ctype.DLL` throws an exception), this function will pass + the exception as-is, instead of catching it and returning bool. + The expected case is `OSError` thrown by `ctype.DLL` when a dynamic dependency + is not found. + This behavior was chosen because the expected failure case is not recoverable. + If a dependency is missing, then users have to install it. + """ + path = _get_lib_path(lib) + if not path.exists(): + warnings.warn("lib path is not exists:" + str(path)) + return False + ops.load_library(path) + return True + + +_FFMPEG_INITIALIZED = False + + +def _init_ffmpeg(): + global _FFMPEG_INITIALIZED + if _FFMPEG_INITIALIZED: + return + + if not paddleaudio._paddlleaudio.is_ffmpeg_available(): + raise RuntimeError( + "paddlleaudio is not compiled with FFmpeg integration. Please set USE_FFMPEG=1 when compiling paddlleaudio." + ) + + try: + _load_lib("libpaddlleaudio_ffmpeg") + except OSError as err: + raise ImportError( + "FFmpeg libraries are not found. Please install FFmpeg.") from err + + import paddllespeech.audio._paddlleaudio_ffmpeg # noqa + + paddleaudio._paddlleaudio.ffmpeg_init() + if paddleaudio._paddlleaudio.ffmpeg_get_log_level() > 8: + paddleaudio._paddlleaudio.ffmpeg_set_log_level(8) + + _FFMPEG_INITIALIZED = True + + +def _init_extension(): + if not _mod_utils.is_module_available("paddleaudio._paddleaudio"): + warnings.warn( + "paddleaudio C++ extension is not available. sox_io, sox_effect, kaldi raw feature is not supported!!!") + return + + _load_lib("libpaddleaudio") + # This import is for initializing the methods registered via PyBind11 + # This has to happen after the base library is loaded + try: + from paddleaudio import _paddleaudio # noqa + except Exception: + warnings.warn( + "paddleaudio C++ extension is not available. sox_io, sox_effect, kaldi raw feature is not supported!!!") + return + + # Because this part is executed as part of `import torchaudio`, we ignore the + # initialization failure. + # If the FFmpeg integration is not properly initialized, then detailed error + # will be raised when client code attempts to import the dedicated feature. + try: + _init_ffmpeg() + except Exception: + pass + + +ops = _Ops() + +_init_extension() diff --git a/audio/paddleaudio/_internal/__init__.py b/audio/paddleaudio/_internal/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/audio/paddleaudio/_internal/module_utils.py b/audio/paddleaudio/_internal/module_utils.py new file mode 100644 index 00000000..7b3230de --- /dev/null +++ b/audio/paddleaudio/_internal/module_utils.py @@ -0,0 +1,151 @@ +import importlib.util +import platform +import warnings +from functools import wraps +from typing import Optional + +#code is from https://github.com/pytorch/audio/blob/main/torchaudio/_internal/module_utils.py with modification. + + +def is_module_available(*modules: str) -> bool: + r"""Returns if a top-level module with :attr:`name` exists *without** + importing it. This is generally safer than try-catch block around a + `import X`. It avoids third party libraries breaking assumptions of some of + our tests, e.g., setting multiprocessing start method when imported + (see librosa/#747, torchvision/#544). + """ + return all(importlib.util.find_spec(m) is not None for m in modules) + + +def requires_module(*modules: str): + """Decorate function to give error message if invoked without required optional modules. + This decorator is to give better error message to users rather + than raising ``NameError: name 'module' is not defined`` at random places. + """ + missing = [m for m in modules if not is_module_available(m)] + + if not missing: + # fall through. If all the modules are available, no need to decorate + def decorator(func): + return func + + else: + req = f"module: {missing[0]}" if len( + missing) == 1 else f"modules: {missing}" + + def decorator(func): + @wraps(func) + def wrapped(*args, **kwargs): + raise RuntimeError( + f"{func.__module__}.{func.__name__} requires {req}") + + return wrapped + + return decorator + + +def deprecated(direction: str, version: Optional[str]=None): + """Decorator to add deprecation message + Args: + direction (str): Migration steps to be given to users. + version (str or int): The version when the object will be removed + """ + + def decorator(func): + @wraps(func) + def wrapped(*args, **kwargs): + message = ( + f"{func.__module__}.{func.__name__} has been deprecated " + f'and will be removed from {"future" if version is None else version} release. ' + f"{direction}") + warnings.warn(message, stacklevel=2) + return func(*args, **kwargs) + + return wrapped + + return decorator + + +def is_kaldi_available(): + return is_module_available("paddleaudio._paddleaudio") + + +def requires_kaldi(): + if is_kaldi_available(): + + def decorator(func): + return func + + else: + + def decorator(func): + @wraps(func) + def wrapped(*args, **kwargs): + raise RuntimeError( + f"{func.__module__}.{func.__name__} requires libpaddleaudio build with kaldi") + + return wrapped + + return decorator + + +def _check_soundfile_importable(): + if not is_module_available("soundfile"): + return False + try: + import soundfile # noqa: F401 + + return True + except Exception: + warnings.warn( + "Failed to import soundfile. 'soundfile' backend is not available.") + return False + + +_is_soundfile_importable = _check_soundfile_importable() + + +def is_soundfile_available(): + return _is_soundfile_importable + + +def requires_soundfile(): + if is_soundfile_available(): + + def decorator(func): + return func + else: + + def decorator(func): + @wraps(func) + def wrapped(*args, **kwargs): + raise RuntimeError( + f"{func.__module__}.{func.__name__} requires soundfile") + + return wrapped + + return decorator + + +def is_sox_available(): + if platform.system() == "Windows": # not support sox in windows + return False + return is_module_available("paddleaudio._paddleaudio") + + +def requires_sox(): + if is_sox_available(): + + def decorator(func): + return func + else: + + def decorator(func): + @wraps(func) + def wrapped(*args, **kwargs): + raise RuntimeError( + f"{func.__module__}.{func.__name__} requires libpaddleaudio build with sox") + + return wrapped + + return decorator diff --git a/tests/unit/audio/backends/__init__.py b/audio/paddleaudio/backends/__init__.py similarity index 59% rename from tests/unit/audio/backends/__init__.py rename to audio/paddleaudio/backends/__init__.py index 97043fd7..e0250956 100644 --- a/tests/unit/audio/backends/__init__.py +++ b/audio/paddleaudio/backends/__init__.py @@ -11,3 +11,15 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +from . import utils +from .soundfile_backend import depth_convert +from .soundfile_backend import normalize +from .soundfile_backend import resample +from .soundfile_backend import soundfile_load +from .soundfile_backend import soundfile_save +from .soundfile_backend import to_mono +from .utils import get_audio_backend +from .utils import list_audio_backends +from .utils import set_audio_backend + +utils._init_audio_backend() diff --git a/audio/paddleaudio/backends/common.py b/audio/paddleaudio/backends/common.py new file mode 100644 index 00000000..9d3edf81 --- /dev/null +++ b/audio/paddleaudio/backends/common.py @@ -0,0 +1,55 @@ +# Token form https://github.com/pytorch/audio/blob/main/torchaudio/backend/common.py with modification. + +class AudioInfo: + """return of info function. + + This class is used by :ref:`"sox_io" backend` and + :ref:`"soundfile" backend with the new interface`. + + :ivar int sample_rate: Sample rate + :ivar int num_frames: The number of frames + :ivar int num_channels: The number of channels + :ivar int bits_per_sample: The number of bits per sample. This is 0 for lossy formats, + or when it cannot be accurately inferred. + :ivar str encoding: Audio encoding + The values encoding can take are one of the following: + + * ``PCM_S``: Signed integer linear PCM + * ``PCM_U``: Unsigned integer linear PCM + * ``PCM_F``: Floating point linear PCM + * ``FLAC``: Flac, Free Lossless Audio Codec + * ``ULAW``: Mu-law + * ``ALAW``: A-law + * ``MP3`` : MP3, MPEG-1 Audio Layer III + * ``VORBIS``: OGG Vorbis + * ``AMR_WB``: Adaptive Multi-Rate + * ``AMR_NB``: Adaptive Multi-Rate Wideband + * ``OPUS``: Opus + * ``HTK``: Single channel 16-bit PCM + * ``UNKNOWN`` : None of above + """ + + def __init__( + self, + sample_rate: int, + num_frames: int, + num_channels: int, + bits_per_sample: int, + encoding: str, + ): + self.sample_rate = sample_rate + self.num_frames = num_frames + self.num_channels = num_channels + self.bits_per_sample = bits_per_sample + self.encoding = encoding + + def __str__(self): + return ( + f"AudioMetaData(" + f"sample_rate={self.sample_rate}, " + f"num_frames={self.num_frames}, " + f"num_channels={self.num_channels}, " + f"bits_per_sample={self.bits_per_sample}, " + f"encoding={self.encoding}" + f")" + ) diff --git a/audio/paddleaudio/backends/no_backend.py b/audio/paddleaudio/backends/no_backend.py new file mode 100644 index 00000000..157536f4 --- /dev/null +++ b/audio/paddleaudio/backends/no_backend.py @@ -0,0 +1,32 @@ +from pathlib import Path +from typing import Callable +from typing import Optional +from typing import Tuple +from typing import Union + +from paddle import Tensor + +#code is from: https://github.com/pytorch/audio/blob/main/torchaudio/backend/no_backend.py + + +def load( + filepath: Union[str, Path], + out: Optional[Tensor]=None, + normalization: Union[bool, float, Callable]=True, + channels_first: bool=True, + num_frames: int=0, + offset: int=0, + filetype: Optional[str]=None, ) -> Tuple[Tensor, int]: + raise RuntimeError("No audio I/O backend is available.") + + +def save(filepath: str, + src: Tensor, + sample_rate: int, + precision: int=16, + channels_first: bool=True) -> None: + raise RuntimeError("No audio I/O backend is available.") + + +def info(filepath: str) -> None: + raise RuntimeError("No audio I/O backend is available.") diff --git a/audio/paddleaudio/backends/soundfile_backend.py b/audio/paddleaudio/backends/soundfile_backend.py new file mode 100644 index 00000000..ae7b5b52 --- /dev/null +++ b/audio/paddleaudio/backends/soundfile_backend.py @@ -0,0 +1,677 @@ +# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import os +import warnings +from typing import Optional +from typing import Tuple + +import numpy as np +import paddle +import resampy +import soundfile +from scipy.io import wavfile + +from ..utils import depth_convert +from ..utils import ParameterError +from .common import AudioInfo + +__all__ = [ + 'resample', + 'to_mono', + 'normalize', + 'save', + 'soundfile_save', + 'load', + 'soundfile_load', + 'info', +] +NORMALMIZE_TYPES = ['linear', 'gaussian'] +MERGE_TYPES = ['ch0', 'ch1', 'random', 'average'] +RESAMPLE_MODES = ['kaiser_best', 'kaiser_fast'] +EPS = 1e-8 + + +def resample(y: np.ndarray, + src_sr: int, + target_sr: int, + mode: str='kaiser_fast') -> np.ndarray: + """Audio resampling. + + Args: + y (np.ndarray): Input waveform array in 1D or 2D. + src_sr (int): Source sample rate. + target_sr (int): Target sample rate. + mode (str, optional): The resampling filter to use. Defaults to 'kaiser_fast'. + + Returns: + np.ndarray: `y` resampled to `target_sr` + """ + + if mode == 'kaiser_best': + warnings.warn( + f'Using resampy in kaiser_best to {src_sr}=>{target_sr}. This function is pretty slow, \ + we recommend the mode kaiser_fast in large scale audio trainning') + + if not isinstance(y, np.ndarray): + raise ParameterError( + 'Only support numpy np.ndarray, but received y in {type(y)}') + + if mode not in RESAMPLE_MODES: + raise ParameterError(f'resample mode must in {RESAMPLE_MODES}') + + return resampy.resample(y, src_sr, target_sr, filter=mode) + + +def to_mono(y: np.ndarray, merge_type: str='average') -> np.ndarray: + """Convert sterior audio to mono. + + Args: + y (np.ndarray): Input waveform array in 1D or 2D. + merge_type (str, optional): Merge type to generate mono waveform. Defaults to 'average'. + + Returns: + np.ndarray: `y` with mono channel. + """ + + if merge_type not in MERGE_TYPES: + raise ParameterError( + f'Unsupported merge type {merge_type}, available types are {MERGE_TYPES}' + ) + if y.ndim > 2: + raise ParameterError( + f'Unsupported audio array, y.ndim > 2, the shape is {y.shape}') + if y.ndim == 1: # nothing to merge + return y + + if merge_type == 'ch0': + return y[0] + if merge_type == 'ch1': + return y[1] + if merge_type == 'random': + return y[np.random.randint(0, 2)] + + # need to do averaging according to dtype + + if y.dtype == 'float32': + y_out = (y[0] + y[1]) * 0.5 + elif y.dtype == 'int16': + y_out = y.astype('int32') + y_out = (y_out[0] + y_out[1]) // 2 + y_out = np.clip(y_out, np.iinfo(y.dtype).min, + np.iinfo(y.dtype).max).astype(y.dtype) + + elif y.dtype == 'int8': + y_out = y.astype('int16') + y_out = (y_out[0] + y_out[1]) // 2 + y_out = np.clip(y_out, np.iinfo(y.dtype).min, + np.iinfo(y.dtype).max).astype(y.dtype) + else: + raise ParameterError(f'Unsupported dtype: {y.dtype}') + return y_out + + +def soundfile_load_(file: os.PathLike, + offset: Optional[float]=None, + dtype: str='int16', + duration: Optional[int]=None) -> Tuple[np.ndarray, int]: + """Load audio using soundfile library. This function load audio file using libsndfile. + + Args: + file (os.PathLike): File of waveform. + offset (Optional[float], optional): Offset to the start of waveform. Defaults to None. + dtype (str, optional): Data type of waveform. Defaults to 'int16'. + duration (Optional[int], optional): Duration of waveform to read. Defaults to None. + + Returns: + Tuple[np.ndarray, int]: Waveform in ndarray and its samplerate. + """ + with soundfile.SoundFile(file) as sf_desc: + sr_native = sf_desc.samplerate + if offset: + sf_desc.seek(int(offset * sr_native)) + if duration is not None: + frame_duration = int(duration * sr_native) + else: + frame_duration = -1 + y = sf_desc.read(frames=frame_duration, dtype=dtype, always_2d=False).T + + return y, sf_desc.samplerate + + +def normalize(y: np.ndarray, norm_type: str='linear', + mul_factor: float=1.0) -> np.ndarray: + """Normalize an input audio with additional multiplier. + + Args: + y (np.ndarray): Input waveform array in 1D or 2D. + norm_type (str, optional): Type of normalization. Defaults to 'linear'. + mul_factor (float, optional): Scaling factor. Defaults to 1.0. + + Returns: + np.ndarray: `y` after normalization. + """ + + if norm_type == 'linear': + amax = np.max(np.abs(y)) + factor = 1.0 / (amax + EPS) + y = y * factor * mul_factor + elif norm_type == 'gaussian': + amean = np.mean(y) + astd = np.std(y) + astd = max(astd, EPS) + y = mul_factor * (y - amean) / astd + else: + raise NotImplementedError(f'norm_type should be in {NORMALMIZE_TYPES}') + + return y + + +def soundfile_save(y: np.ndarray, sr: int, file: os.PathLike) -> None: + """Save audio file to disk. This function saves audio to disk using scipy.io.wavfile, with additional step to convert input waveform to int16. + + Args: + y (np.ndarray): Input waveform array in 1D or 2D. + sr (int): Sample rate. + file (os.PathLike): Path of auido file to save. + """ + if not file.endswith('.wav'): + raise ParameterError( + f'only .wav file supported, but dst file name is: {file}') + + if sr <= 0: + raise ParameterError( + f'Sample rate should be larger than 0, recieved sr = {sr}') + + if y.dtype not in ['int16', 'int8']: + warnings.warn( + f'input data type is {y.dtype}, will convert data to int16 format before saving' + ) + y_out = depth_convert(y, 'int16') + else: + y_out = y + + wavfile.write(file, sr, y_out) + + +def soundfile_load( + file: os.PathLike, + sr: Optional[int]=None, + mono: bool=True, + merge_type: str='average', # ch0,ch1,random,average + normal: bool=True, + norm_type: str='linear', + norm_mul_factor: float=1.0, + offset: float=0.0, + duration: Optional[int]=None, + dtype: str='float32', + resample_mode: str='kaiser_fast') -> Tuple[np.ndarray, int]: + """Load audio file from disk. This function loads audio from disk using using audio beackend. + + Args: + file (os.PathLike): Path of auido file to load. + sr (Optional[int], optional): Sample rate of loaded waveform. Defaults to None. + mono (bool, optional): Return waveform with mono channel. Defaults to True. + merge_type (str, optional): Merge type of multi-channels waveform. Defaults to 'average'. + normal (bool, optional): Waveform normalization. Defaults to True. + norm_type (str, optional): Type of normalization. Defaults to 'linear'. + norm_mul_factor (float, optional): Scaling factor. Defaults to 1.0. + offset (float, optional): Offset to the start of waveform. Defaults to 0.0. + duration (Optional[int], optional): Duration of waveform to read. Defaults to None. + dtype (str, optional): Data type of waveform. Defaults to 'float32'. + resample_mode (str, optional): The resampling filter to use. Defaults to 'kaiser_fast'. + + Returns: + Tuple[np.ndarray, int]: Waveform in ndarray and its samplerate. + """ + + y, r = soundfile_load_(file, offset=offset, dtype=dtype, duration=duration) + + if not ((y.ndim == 1 and len(y) > 0) or (y.ndim == 2 and len(y[0]) > 0)): + raise ParameterError(f'audio file {file} looks empty') + + if mono: + y = to_mono(y, merge_type) + + if sr is not None and sr != r: + y = resample(y, r, sr, mode=resample_mode) + r = sr + + if normal: + y = normalize(y, norm_type, norm_mul_factor) + elif dtype in ['int8', 'int16']: + # still need to do normalization, before depth convertion + y = normalize(y, 'linear', 1.0) + + y = depth_convert(y, dtype) + return y, r + + +#the code below token form: https://github.com/pytorch/audio/blob/main/torchaudio/backend/soundfile_backend.py with modificaion. + + +def _get_subtype_for_wav(dtype: paddle.dtype, + encoding: str, + bits_per_sample: int): + if not encoding: + if not bits_per_sample: + subtype = { + paddle.uint8: "PCM_U8", + paddle.int16: "PCM_16", + paddle.int32: "PCM_32", + paddle.float32: "FLOAT", + paddle.float64: "DOUBLE", + }.get(dtype) + if not subtype: + raise ValueError(f"Unsupported dtype for wav: {dtype}") + return subtype + if bits_per_sample == 8: + return "PCM_U8" + return f"PCM_{bits_per_sample}" + if encoding == "PCM_S": + if not bits_per_sample: + return "PCM_32" + if bits_per_sample == 8: + raise ValueError("wav does not support 8-bit signed PCM encoding.") + return f"PCM_{bits_per_sample}" + if encoding == "PCM_U": + if bits_per_sample in (None, 8): + return "PCM_U8" + raise ValueError("wav only supports 8-bit unsigned PCM encoding.") + if encoding == "PCM_F": + if bits_per_sample in (None, 32): + return "FLOAT" + if bits_per_sample == 64: + return "DOUBLE" + raise ValueError("wav only supports 32/64-bit float PCM encoding.") + if encoding == "ULAW": + if bits_per_sample in (None, 8): + return "ULAW" + raise ValueError("wav only supports 8-bit mu-law encoding.") + if encoding == "ALAW": + if bits_per_sample in (None, 8): + return "ALAW" + raise ValueError("wav only supports 8-bit a-law encoding.") + raise ValueError(f"wav does not support {encoding}.") + + +def _get_subtype_for_sphere(encoding: str, bits_per_sample: int): + if encoding in (None, "PCM_S"): + return f"PCM_{bits_per_sample}" if bits_per_sample else "PCM_32" + if encoding in ("PCM_U", "PCM_F"): + raise ValueError(f"sph does not support {encoding} encoding.") + if encoding == "ULAW": + if bits_per_sample in (None, 8): + return "ULAW" + raise ValueError("sph only supports 8-bit for mu-law encoding.") + if encoding == "ALAW": + return "ALAW" + raise ValueError(f"sph does not support {encoding}.") + + +def _get_subtype(dtype: paddle.dtype, + format: str, + encoding: str, + bits_per_sample: int): + if format == "wav": + return _get_subtype_for_wav(dtype, encoding, bits_per_sample) + if format == "flac": + if encoding: + raise ValueError("flac does not support encoding.") + if not bits_per_sample: + return "PCM_16" + if bits_per_sample > 24: + raise ValueError("flac does not support bits_per_sample > 24.") + return "PCM_S8" if bits_per_sample == 8 else f"PCM_{bits_per_sample}" + if format in ("ogg", "vorbis"): + if encoding or bits_per_sample: + raise ValueError( + "ogg/vorbis does not support encoding/bits_per_sample.") + return "VORBIS" + if format == "sph": + return _get_subtype_for_sphere(encoding, bits_per_sample) + if format in ("nis", "nist"): + return "PCM_16" + raise ValueError(f"Unsupported format: {format}") + + +def save( + filepath: str, + src: paddle.Tensor, + sample_rate: int, + channels_first: bool=True, + compression: Optional[float]=None, + format: Optional[str]=None, + encoding: Optional[str]=None, + bits_per_sample: Optional[int]=None, ): + """Save audio data to file. + + Note: + The formats this function can handle depend on the soundfile installation. + This function is tested on the following formats; + + * WAV + + * 32-bit floating-point + * 32-bit signed integer + * 16-bit signed integer + * 8-bit unsigned integer + + * FLAC + * OGG/VORBIS + * SPHERE + + Note: + ``filepath`` argument is intentionally annotated as ``str`` only, even though it accepts + ``pathlib.Path`` object as well. This is for the consistency with ``"sox_io"`` backend, + + Args: + filepath (str or pathlib.Path): Path to audio file. + src (paddle.Tensor): Audio data to save. must be 2D tensor. + sample_rate (int): sampling rate + channels_first (bool, optional): If ``True``, the given tensor is interpreted as `[channel, time]`, + otherwise `[time, channel]`. + compression (float of None, optional): Not used. + It is here only for interface compatibility reson with "sox_io" backend. + format (str or None, optional): Override the audio format. + When ``filepath`` argument is path-like object, audio format is + inferred from file extension. If the file extension is missing or + different, you can specify the correct format with this argument. + + When ``filepath`` argument is file-like object, + this argument is required. + + Valid values are ``"wav"``, ``"ogg"``, ``"vorbis"``, + ``"flac"`` and ``"sph"``. + encoding (str or None, optional): Changes the encoding for supported formats. + This argument is effective only for supported formats, sush as + ``"wav"``, ``""flac"`` and ``"sph"``. Valid values are; + + - ``"PCM_S"`` (signed integer Linear PCM) + - ``"PCM_U"`` (unsigned integer Linear PCM) + - ``"PCM_F"`` (floating point PCM) + - ``"ULAW"`` (mu-law) + - ``"ALAW"`` (a-law) + + bits_per_sample (int or None, optional): Changes the bit depth for the + supported formats. + When ``format`` is one of ``"wav"``, ``"flac"`` or ``"sph"``, + you can change the bit depth. + Valid values are ``8``, ``16``, ``24``, ``32`` and ``64``. + + Supported formats/encodings/bit depth/compression are: + + ``"wav"`` + - 32-bit floating-point PCM + - 32-bit signed integer PCM + - 24-bit signed integer PCM + - 16-bit signed integer PCM + - 8-bit unsigned integer PCM + - 8-bit mu-law + - 8-bit a-law + + Note: + Default encoding/bit depth is determined by the dtype of + the input Tensor. + + ``"flac"`` + - 8-bit + - 16-bit (default) + - 24-bit + + ``"ogg"``, ``"vorbis"`` + - Doesn't accept changing configuration. + + ``"sph"`` + - 8-bit signed integer PCM + - 16-bit signed integer PCM + - 24-bit signed integer PCM + - 32-bit signed integer PCM (default) + - 8-bit mu-law + - 8-bit a-law + - 16-bit a-law + - 24-bit a-law + - 32-bit a-law + + """ + if src.ndim != 2: + raise ValueError(f"Expected 2D Tensor, got {src.ndim}D.") + if compression is not None: + warnings.warn( + '`save` function of "soundfile" backend does not support "compression" parameter. ' + "The argument is silently ignored.") + if hasattr(filepath, "write"): + if format is None: + raise RuntimeError( + "`format` is required when saving to file object.") + ext = format.lower() + else: + ext = str(filepath).split(".")[-1].lower() + + if bits_per_sample not in (None, 8, 16, 24, 32, 64): + raise ValueError("Invalid bits_per_sample.") + if bits_per_sample == 24: + warnings.warn( + "Saving audio with 24 bits per sample might warp samples near -1. " + "Using 16 bits per sample might be able to avoid this.") + subtype = _get_subtype(src.dtype, ext, encoding, bits_per_sample) + + # sph is a extension used in TED-LIUM but soundfile does not recognize it as NIST format, + # so we extend the extensions manually here + if ext in ["nis", "nist", "sph"] and format is None: + format = "NIST" + + if channels_first: + src = src.t() + + soundfile.write( + file=filepath, + data=src, + samplerate=sample_rate, + subtype=subtype, + format=format) + + +_SUBTYPE2DTYPE = { + "PCM_S8": "int8", + "PCM_U8": "uint8", + "PCM_16": "int16", + "PCM_32": "int32", + "FLOAT": "float32", + "DOUBLE": "float64", +} + + +def load( + filepath: str, + frame_offset: int=0, + num_frames: int=-1, + normalize: bool=True, + channels_first: bool=True, + format: Optional[str]=None, ) -> Tuple[paddle.Tensor, int]: + """Load audio data from file. + + Note: + The formats this function can handle depend on the soundfile installation. + This function is tested on the following formats; + + * WAV + + * 32-bit floating-point + * 32-bit signed integer + * 16-bit signed integer + * 8-bit unsigned integer + + * FLAC + * OGG/VORBIS + * SPHERE + + By default (``normalize=True``, ``channels_first=True``), this function returns Tensor with + ``float32`` dtype and the shape of `[channel, time]`. + The samples are normalized to fit in the range of ``[-1.0, 1.0]``. + + When the input format is WAV with integer type, such as 32-bit signed integer, 16-bit + signed integer and 8-bit unsigned integer (24-bit signed integer is not supported), + by providing ``normalize=False``, this function can return integer Tensor, where the samples + are expressed within the whole range of the corresponding dtype, that is, ``int32`` tensor + for 32-bit signed PCM, ``int16`` for 16-bit signed PCM and ``uint8`` for 8-bit unsigned PCM. + + ``normalize`` parameter has no effect on 32-bit floating-point WAV and other formats, such as + ``flac`` and ``mp3``. + For these formats, this function always returns ``float32`` Tensor with values normalized to + ``[-1.0, 1.0]``. + + Note: + ``filepath`` argument is intentionally annotated as ``str`` only, even though it accepts + ``pathlib.Path`` object as well. This is for the consistency with ``"sox_io"`` backend. + + Args: + filepath (path-like object or file-like object): + Source of audio data. + frame_offset (int, optional): + Number of frames to skip before start reading data. + num_frames (int, optional): + Maximum number of frames to read. ``-1`` reads all the remaining samples, + starting from ``frame_offset``. + This function may return the less number of frames if there is not enough + frames in the given file. + normalize (bool, optional): + When ``True``, this function always return ``float32``, and sample values are + normalized to ``[-1.0, 1.0]``. + If input file is integer WAV, giving ``False`` will change the resulting Tensor type to + integer type. + This argument has no effect for formats other than integer WAV type. + channels_first (bool, optional): + When True, the returned Tensor has dimension `[channel, time]`. + Otherwise, the returned Tensor's dimension is `[time, channel]`. + format (str or None, optional): + Not used. PySoundFile does not accept format hint. + + Returns: + (paddle.Tensor, int): Resulting Tensor and sample rate. + If the input file has integer wav format and normalization is off, then it has + integer type, else ``float32`` type. If ``channels_first=True``, it has + `[channel, time]` else `[time, channel]`. + """ + with soundfile.SoundFile(filepath, "r") as file_: + if file_.format != "WAV" or normalize: + dtype = "float32" + elif file_.subtype not in _SUBTYPE2DTYPE: + raise ValueError(f"Unsupported subtype: {file_.subtype}") + else: + dtype = _SUBTYPE2DTYPE[file_.subtype] + + frames = file_._prepare_read(frame_offset, None, num_frames) + waveform = file_.read(frames, dtype, always_2d=True) + sample_rate = file_.samplerate + + waveform = paddle.to_tensor(waveform) + if channels_first: + waveform = paddle.transpose(waveform, perm=[1, 0]) + return waveform, sample_rate + + +# Mapping from soundfile subtype to number of bits per sample. +# This is mostly heuristical and the value is set to 0 when it is irrelevant +# (lossy formats) or when it can't be inferred. +# For ADPCM (and G72X) subtypes, it's hard to infer the bit depth because it's not part of the standard: +# According to https://en.wikipedia.org/wiki/Adaptive_differential_pulse-code_modulation#In_telephony, +# the default seems to be 8 bits but it can be compressed further to 4 bits. +# The dict is inspired from +# https://github.com/bastibe/python-soundfile/blob/744efb4b01abc72498a96b09115b42a4cabd85e4/soundfile.py#L66-L94 +_SUBTYPE_TO_BITS_PER_SAMPLE = { + "PCM_S8": 8, # Signed 8 bit data + "PCM_16": 16, # Signed 16 bit data + "PCM_24": 24, # Signed 24 bit data + "PCM_32": 32, # Signed 32 bit data + "PCM_U8": 8, # Unsigned 8 bit data (WAV and RAW only) + "FLOAT": 32, # 32 bit float data + "DOUBLE": 64, # 64 bit float data + "ULAW": 8, # U-Law encoded. See https://en.wikipedia.org/wiki/G.711#Types + "ALAW": 8, # A-Law encoded. See https://en.wikipedia.org/wiki/G.711#Types + "IMA_ADPCM": 0, # IMA ADPCM. + "MS_ADPCM": 0, # Microsoft ADPCM. + "GSM610": + 0, # GSM 6.10 encoding. (Wikipedia says 1.625 bit depth?? https://en.wikipedia.org/wiki/Full_Rate) + "VOX_ADPCM": 0, # OKI / Dialogix ADPCM + "G721_32": 0, # 32kbs G721 ADPCM encoding. + "G723_24": 0, # 24kbs G723 ADPCM encoding. + "G723_40": 0, # 40kbs G723 ADPCM encoding. + "DWVW_12": 12, # 12 bit Delta Width Variable Word encoding. + "DWVW_16": 16, # 16 bit Delta Width Variable Word encoding. + "DWVW_24": 24, # 24 bit Delta Width Variable Word encoding. + "DWVW_N": 0, # N bit Delta Width Variable Word encoding. + "DPCM_8": 8, # 8 bit differential PCM (XI only) + "DPCM_16": 16, # 16 bit differential PCM (XI only) + "VORBIS": 0, # Xiph Vorbis encoding. (lossy) + "ALAC_16": 16, # Apple Lossless Audio Codec (16 bit). + "ALAC_20": 20, # Apple Lossless Audio Codec (20 bit). + "ALAC_24": 24, # Apple Lossless Audio Codec (24 bit). + "ALAC_32": 32, # Apple Lossless Audio Codec (32 bit). +} + + +def _get_bit_depth(subtype): + if subtype not in _SUBTYPE_TO_BITS_PER_SAMPLE: + warnings.warn( + f"The {subtype} subtype is unknown to PaddleAudio. As a result, the bits_per_sample " + "attribute will be set to 0. If you are seeing this warning, please " + "report by opening an issue on github (after checking for existing/closed ones). " + "You may otherwise ignore this warning.") + return _SUBTYPE_TO_BITS_PER_SAMPLE.get(subtype, 0) + + +_SUBTYPE_TO_ENCODING = { + "PCM_S8": "PCM_S", + "PCM_16": "PCM_S", + "PCM_24": "PCM_S", + "PCM_32": "PCM_S", + "PCM_U8": "PCM_U", + "FLOAT": "PCM_F", + "DOUBLE": "PCM_F", + "ULAW": "ULAW", + "ALAW": "ALAW", + "VORBIS": "VORBIS", +} + + +def _get_encoding(format: str, subtype: str): + if format == "FLAC": + return "FLAC" + return _SUBTYPE_TO_ENCODING.get(subtype, "UNKNOWN") + + +def info(filepath: str, format: Optional[str]=None) -> AudioInfo: + """Get signal information of an audio file. + + Note: + ``filepath`` argument is intentionally annotated as ``str`` only, even though it accepts + ``pathlib.Path`` object as well. This is for the consistency with ``"sox_io"`` backend, + + Args: + filepath (path-like object or file-like object): + Source of audio data. + format (str or None, optional): + Not used. PySoundFile does not accept format hint. + + Returns: + AudioInfo: meta data of the given audio. + + """ + sinfo = soundfile.info(filepath) + return AudioInfo( + sinfo.samplerate, + sinfo.frames, + sinfo.channels, + bits_per_sample=_get_bit_depth(sinfo.subtype), + encoding=_get_encoding(sinfo.format, sinfo.subtype), ) diff --git a/audio/paddleaudio/backends/sox_io_backend.py b/audio/paddleaudio/backends/sox_io_backend.py new file mode 100644 index 00000000..1c2d5f65 --- /dev/null +++ b/audio/paddleaudio/backends/sox_io_backend.py @@ -0,0 +1,106 @@ +import os +from typing import Optional +from typing import Tuple + +import paddle +import paddleaudio +from paddle import Tensor +from paddleaudio._internal import module_utils as _mod_utils + +from .common import AudioInfo + +#https://github.com/pytorch/audio/blob/main/torchaudio/backend/sox_io_backend.py + + +def _fail_info(filepath: str, format: Optional[str]) -> AudioInfo: + raise RuntimeError("Failed to fetch metadata from {}".format(filepath)) + + +def _fail_info_fileobj(fileobj, format: Optional[str]) -> AudioInfo: + raise RuntimeError("Failed to fetch metadata from {}".format(fileobj)) + + +# Note: need to comply TorchScript syntax -- need annotation and no f-string +def _fail_load( + filepath: str, + frame_offset: int=0, + num_frames: int=-1, + normalize: bool=True, + channels_first: bool=True, + format: Optional[str]=None, ) -> Tuple[Tensor, int]: + raise RuntimeError("Failed to load audio from {}".format(filepath)) + + +def _fail_load_fileobj(fileobj, *args, **kwargs): + raise RuntimeError(f"Failed to load audio from {fileobj}") + + +_fallback_info = _fail_info +_fallback_info_fileobj = _fail_info_fileobj +_fallback_load = _fail_load +_fallback_load_filebj = _fail_load_fileobj + + +@_mod_utils.requires_sox() +def load( + filepath: str, + frame_offset: int=0, + num_frames: int=-1, + normalize: bool=True, + channels_first: bool=True, + format: Optional[str]=None, ) -> Tuple[Tensor, int]: + if hasattr(filepath, "read"): + ret = paddleaudio._paddleaudio.load_audio_fileobj( + filepath, frame_offset, num_frames, normalize, channels_first, + format) + if ret is not None: + audio_tensor = paddle.to_tensor(ret[0]) + return (audio_tensor, ret[1]) + return _fallback_load_fileobj(filepath, frame_offset, num_frames, + normalize, channels_first, format) + filepath = os.fspath(filepath) + ret = paddleaudio._paddleaudio.sox_io_load_audio_file( + filepath, frame_offset, num_frames, normalize, channels_first, format) + if ret is not None: + audio_tensor = paddle.to_tensor(ret[0]) + return (audio_tensor, ret[1]) + return _fallback_load(filepath, frame_offset, num_frames, normalize, + channels_first, format) + + +@_mod_utils.requires_sox() +def save( + filepath: str, + src: Tensor, + sample_rate: int, + channels_first: bool=True, + compression: Optional[float]=None, + format: Optional[str]=None, + encoding: Optional[str]=None, + bits_per_sample: Optional[int]=None, ): + src_arr = src.numpy() + if hasattr(filepath, "write"): + paddleaudio._paddleaudio.save_audio_fileobj( + filepath, src_arr, sample_rate, channels_first, compression, format, + encoding, bits_per_sample) + return + filepath = os.fspath(filepath) + paddleaudio._paddleaudio.sox_io_save_audio_file( + filepath, src_arr, sample_rate, channels_first, compression, format, + encoding, bits_per_sample) + + +@_mod_utils.requires_sox() +def info( + filepath: str, + format: Optional[str]=None, ) -> AudioInfo: + if hasattr(filepath, "read"): + sinfo = paddleaudio._paddleaudio.get_info_fileobj(filepath, format) + if sinfo is not None: + return AudioInfo(*sinfo) + return _fallback_info_fileobj(filepath, format) + filepath = os.fspath(filepath) + sinfo = paddleaudio._paddleaudio.get_info_file(filepath, format) + if sinfo is not None: + return AudioInfo(*sinfo) + return _fallback_info(filepath, format) diff --git a/audio/paddleaudio/backends/utils.py b/audio/paddleaudio/backends/utils.py new file mode 100644 index 00000000..83c1a71c --- /dev/null +++ b/audio/paddleaudio/backends/utils.py @@ -0,0 +1,83 @@ +"""Defines utilities for switching audio backends""" +#code is from: https://github.com/pytorch/audio/blob/main/torchaudio/backend/utils.py +import warnings +from typing import List +from typing import Optional + +import paddleaudio +from paddleaudio._internal import module_utils as _mod_utils + +from . import no_backend +from . import soundfile_backend +from . import sox_io_backend + +__all__ = [ + "list_audio_backends", + "get_audio_backend", + "set_audio_backend", +] + + +def list_audio_backends() -> List[str]: + """List available backends + + Returns: + List[str]: The list of available backends. + """ + backends = [] + if _mod_utils.is_module_available("soundfile"): + backends.append("soundfile") + if _mod_utils.is_sox_available(): + backends.append("sox_io") + return backends + + +def set_audio_backend(backend: Optional[str]): + """Set the backend for I/O operation + + Args: + backend (str or None): Name of the backend. + One of ``"sox_io"`` or ``"soundfile"`` based on availability + of the system. If ``None`` is provided the current backend is unassigned. + """ + if backend is not None and backend not in list_audio_backends(): + raise RuntimeError(f'Backend "{backend}" is not one of ' + f"available backends: {list_audio_backends()}.") + + if backend is None: + module = no_backend + elif backend == "sox_io": + module = sox_io_backend + elif backend == "soundfile": + module = soundfile_backend + else: + raise NotImplementedError(f'Unexpected backend "{backend}"') + + for func in ["save", "load", "info"]: + setattr(paddleaudio, func, getattr(module, func)) + + +def _init_audio_backend(): + backends = list_audio_backends() + if "soundfile" in backends: + set_audio_backend("soundfile") + elif "sox_io" in backends: + set_audio_backend("sox_io") + else: + warnings.warn("No audio backend is available.") + set_audio_backend(None) + + +def get_audio_backend() -> Optional[str]: + """Get the name of the current backend + + Returns: + Optional[str]: The name of the current backend or ``None`` if no backend is assigned. + """ + if paddleaudio.load == no_backend.load: + return None + if paddleaudio.load == sox_io_backend.load: + return "sox_io" + if paddleaudio.load == soundfile_backend.load: + return "soundfile" + raise ValueError("Unknown backend.") diff --git a/paddlespeech/audio/compliance/__init__.py b/audio/paddleaudio/compliance/__init__.py similarity index 100% rename from paddlespeech/audio/compliance/__init__.py rename to audio/paddleaudio/compliance/__init__.py diff --git a/paddlespeech/audio/compliance/kaldi.py b/audio/paddleaudio/compliance/kaldi.py similarity index 100% rename from paddlespeech/audio/compliance/kaldi.py rename to audio/paddleaudio/compliance/kaldi.py diff --git a/paddlespeech/audio/compliance/librosa.py b/audio/paddleaudio/compliance/librosa.py similarity index 100% rename from paddlespeech/audio/compliance/librosa.py rename to audio/paddleaudio/compliance/librosa.py diff --git a/paddlespeech/audio/datasets/__init__.py b/audio/paddleaudio/datasets/__init__.py similarity index 100% rename from paddlespeech/audio/datasets/__init__.py rename to audio/paddleaudio/datasets/__init__.py diff --git a/paddlespeech/audio/datasets/dataset.py b/audio/paddleaudio/datasets/dataset.py similarity index 97% rename from paddlespeech/audio/datasets/dataset.py rename to audio/paddleaudio/datasets/dataset.py index 488187a6..f1dfc1ea 100644 --- a/paddlespeech/audio/datasets/dataset.py +++ b/audio/paddleaudio/datasets/dataset.py @@ -16,7 +16,7 @@ from typing import List import numpy as np import paddle -from ..backends import load as load_audio +from ..backends.soundfile_backend import soundfile_load as load_audio from ..compliance.kaldi import fbank as kaldi_fbank from ..compliance.kaldi import mfcc as kaldi_mfcc from ..compliance.librosa import melspectrogram diff --git a/paddlespeech/audio/datasets/esc50.py b/audio/paddleaudio/datasets/esc50.py similarity index 99% rename from paddlespeech/audio/datasets/esc50.py rename to audio/paddleaudio/datasets/esc50.py index f5c7050f..e7477d40 100644 --- a/paddlespeech/audio/datasets/esc50.py +++ b/audio/paddleaudio/datasets/esc50.py @@ -16,8 +16,8 @@ import os from typing import List from typing import Tuple -from ..utils import DATA_HOME from ..utils.download import download_and_decompress +from ..utils.env import DATA_HOME from .dataset import AudioClassificationDataset __all__ = ['ESC50'] diff --git a/paddlespeech/audio/datasets/gtzan.py b/audio/paddleaudio/datasets/gtzan.py similarity index 99% rename from paddlespeech/audio/datasets/gtzan.py rename to audio/paddleaudio/datasets/gtzan.py index 1f6835a5..cfea6f37 100644 --- a/paddlespeech/audio/datasets/gtzan.py +++ b/audio/paddleaudio/datasets/gtzan.py @@ -17,8 +17,8 @@ import random from typing import List from typing import Tuple -from ..utils import DATA_HOME from ..utils.download import download_and_decompress +from ..utils.env import DATA_HOME from .dataset import AudioClassificationDataset __all__ = ['GTZAN'] diff --git a/paddlespeech/audio/datasets/hey_snips.py b/audio/paddleaudio/datasets/hey_snips.py similarity index 100% rename from paddlespeech/audio/datasets/hey_snips.py rename to audio/paddleaudio/datasets/hey_snips.py diff --git a/paddlespeech/audio/datasets/rirs_noises.py b/audio/paddleaudio/datasets/rirs_noises.py similarity index 98% rename from paddlespeech/audio/datasets/rirs_noises.py rename to audio/paddleaudio/datasets/rirs_noises.py index 68639a60..74418daa 100644 --- a/paddlespeech/audio/datasets/rirs_noises.py +++ b/audio/paddleaudio/datasets/rirs_noises.py @@ -20,8 +20,8 @@ from typing import List from paddle.io import Dataset from tqdm import tqdm -from ..backends import load as load_audio -from ..backends import save as save_wav +from ..backends.soundfile_backend import soundfile_load as load_audio +from ..backends.soundfile_backend import soundfile_save as save_wav from ..utils import DATA_HOME from ..utils.download import download_and_decompress from .dataset import feat_funcs diff --git a/paddlespeech/audio/datasets/tess.py b/audio/paddleaudio/datasets/tess.py similarity index 99% rename from paddlespeech/audio/datasets/tess.py rename to audio/paddleaudio/datasets/tess.py index 1469fa5e..8faab9c3 100644 --- a/paddlespeech/audio/datasets/tess.py +++ b/audio/paddleaudio/datasets/tess.py @@ -17,8 +17,8 @@ import random from typing import List from typing import Tuple -from ..utils import DATA_HOME from ..utils.download import download_and_decompress +from ..utils.env import DATA_HOME from .dataset import AudioClassificationDataset __all__ = ['TESS'] diff --git a/paddlespeech/audio/datasets/urban_sound.py b/audio/paddleaudio/datasets/urban_sound.py similarity index 99% rename from paddlespeech/audio/datasets/urban_sound.py rename to audio/paddleaudio/datasets/urban_sound.py index 0389cd5f..d97c4d1d 100644 --- a/paddlespeech/audio/datasets/urban_sound.py +++ b/audio/paddleaudio/datasets/urban_sound.py @@ -16,8 +16,8 @@ import os from typing import List from typing import Tuple -from ..utils import DATA_HOME from ..utils.download import download_and_decompress +from ..utils.env import DATA_HOME from .dataset import AudioClassificationDataset __all__ = ['UrbanSound8K'] diff --git a/paddlespeech/audio/datasets/voxceleb.py b/audio/paddleaudio/datasets/voxceleb.py similarity index 99% rename from paddlespeech/audio/datasets/voxceleb.py rename to audio/paddleaudio/datasets/voxceleb.py index 07f44e0c..b7160b24 100644 --- a/paddlespeech/audio/datasets/voxceleb.py +++ b/audio/paddleaudio/datasets/voxceleb.py @@ -23,7 +23,7 @@ from paddle.io import Dataset from pathos.multiprocessing import Pool from tqdm import tqdm -from ..backends import load as load_audio +from ..backends.soundfile_backend import soundfile_load as load_audio from ..utils import DATA_HOME from ..utils import decompress from ..utils.download import download_and_decompress diff --git a/paddlespeech/audio/features/__init__.py b/audio/paddleaudio/features/__init__.py similarity index 100% rename from paddlespeech/audio/features/__init__.py rename to audio/paddleaudio/features/__init__.py diff --git a/paddlespeech/audio/features/layers.py b/audio/paddleaudio/features/layers.py similarity index 100% rename from paddlespeech/audio/features/layers.py rename to audio/paddleaudio/features/layers.py diff --git a/paddlespeech/audio/functional/__init__.py b/audio/paddleaudio/functional/__init__.py similarity index 100% rename from paddlespeech/audio/functional/__init__.py rename to audio/paddleaudio/functional/__init__.py diff --git a/paddlespeech/audio/functional/functional.py b/audio/paddleaudio/functional/functional.py similarity index 100% rename from paddlespeech/audio/functional/functional.py rename to audio/paddleaudio/functional/functional.py diff --git a/paddlespeech/audio/functional/window.py b/audio/paddleaudio/functional/window.py similarity index 60% rename from paddlespeech/audio/functional/window.py rename to audio/paddleaudio/functional/window.py index c99d5046..ebbbe46c 100644 --- a/paddlespeech/audio/functional/window.py +++ b/audio/paddleaudio/functional/window.py @@ -1,4 +1,4 @@ -# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved +# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,127 +18,156 @@ from typing import Union import paddle from paddle import Tensor -__all__ = [ - 'get_window', -] +class WindowFunctionRegister(object): + def __init__(self): + self._functions_dict = dict() + def register(self): + def add_subfunction(func): + name = func.__name__ + self._functions_dict[name] = func + return func + + return add_subfunction + + def get(self, name): + return self._functions_dict[name] + + +window_function_register = WindowFunctionRegister() + + +@window_function_register.register() def _cat(x: List[Tensor], data_type: str) -> Tensor: l = [paddle.to_tensor(_, data_type) for _ in x] return paddle.concat(l) +@window_function_register.register() def _acosh(x: Union[Tensor, float]) -> Tensor: if isinstance(x, float): return math.log(x + math.sqrt(x**2 - 1)) return paddle.log(x + paddle.sqrt(paddle.square(x) - 1)) +@window_function_register.register() def _extend(M: int, sym: bool) -> bool: - """Extend window by 1 sample if needed for DFT-even symmetry. """ + """Extend window by 1 sample if needed for DFT-even symmetry.""" if not sym: return M + 1, True else: return M, False +@window_function_register.register() def _len_guards(M: int) -> bool: - """Handle small or incorrect window lengths. """ + """Handle small or incorrect window lengths.""" if int(M) != M or M < 0: raise ValueError('Window length M must be a non-negative integer') return M <= 1 +@window_function_register.register() def _truncate(w: Tensor, needed: bool) -> Tensor: - """Truncate window by 1 sample if needed for DFT-even symmetry. """ + """Truncate window by 1 sample if needed for DFT-even symmetry.""" if needed: return w[:-1] else: return w -def _general_gaussian(M: int, p, sig, sym: bool=True, - dtype: str='float64') -> Tensor: +@window_function_register.register() +def _general_gaussian( + M: int, p, sig, sym: bool = True, dtype: str = 'float64' +) -> Tensor: """Compute a window with a generalized Gaussian shape. This function is consistent with scipy.signal.windows.general_gaussian(). """ if _len_guards(M): - return paddle.ones((M, ), dtype=dtype) + return paddle.ones((M,), dtype=dtype) M, needs_trunc = _extend(M, sym) n = paddle.arange(0, M, dtype=dtype) - (M - 1.0) / 2.0 - w = paddle.exp(-0.5 * paddle.abs(n / sig)**(2 * p)) + w = paddle.exp(-0.5 * paddle.abs(n / sig) ** (2 * p)) return _truncate(w, needs_trunc) -def _general_cosine(M: int, a: float, sym: bool=True, - dtype: str='float64') -> Tensor: +@window_function_register.register() +def _general_cosine( + M: int, a: float, sym: bool = True, dtype: str = 'float64' +) -> Tensor: """Compute a generic weighted sum of cosine terms window. This function is consistent with scipy.signal.windows.general_cosine(). """ if _len_guards(M): - return paddle.ones((M, ), dtype=dtype) + return paddle.ones((M,), dtype=dtype) M, needs_trunc = _extend(M, sym) fac = paddle.linspace(-math.pi, math.pi, M, dtype=dtype) - w = paddle.zeros((M, ), dtype=dtype) + w = paddle.zeros((M,), dtype=dtype) for k in range(len(a)): w += a[k] * paddle.cos(k * fac) return _truncate(w, needs_trunc) -def _general_hamming(M: int, alpha: float, sym: bool=True, - dtype: str='float64') -> Tensor: +@window_function_register.register() +def _general_hamming( + M: int, alpha: float, sym: bool = True, dtype: str = 'float64' +) -> Tensor: """Compute a generalized Hamming window. This function is consistent with scipy.signal.windows.general_hamming() """ - return _general_cosine(M, [alpha, 1. - alpha], sym, dtype=dtype) + return _general_cosine(M, [alpha, 1.0 - alpha], sym, dtype=dtype) -def _taylor(M: int, - nbar=4, - sll=30, - norm=True, - sym: bool=True, - dtype: str='float64') -> Tensor: +@window_function_register.register() +def _taylor( + M: int, nbar=4, sll=30, norm=True, sym: bool = True, dtype: str = 'float64' +) -> Tensor: """Compute a Taylor window. The Taylor window taper function approximates the Dolph-Chebyshev window's constant sidelobe level for a parameterized number of near-in sidelobes. """ if _len_guards(M): - return paddle.ones((M, ), dtype=dtype) + return paddle.ones((M,), dtype=dtype) M, needs_trunc = _extend(M, sym) # Original text uses a negative sidelobe level parameter and then negates # it in the calculation of B. To keep consistent with other methods we # assume the sidelobe level parameter to be positive. - B = 10**(sll / 20) + B = 10 ** (sll / 20) A = _acosh(B) / math.pi - s2 = nbar**2 / (A**2 + (nbar - 0.5)**2) + s2 = nbar**2 / (A**2 + (nbar - 0.5) ** 2) ma = paddle.arange(1, nbar, dtype=dtype) - Fm = paddle.empty((nbar - 1, ), dtype=dtype) + Fm = paddle.empty((nbar - 1,), dtype=dtype) signs = paddle.empty_like(ma) signs[::2] = 1 signs[1::2] = -1 m2 = ma * ma for mi in range(len(ma)): - numer = signs[mi] * paddle.prod(1 - m2[mi] / s2 / (A**2 + (ma - 0.5)**2 - )) + numer = signs[mi] * paddle.prod( + 1 - m2[mi] / s2 / (A**2 + (ma - 0.5) ** 2) + ) if mi == 0: - denom = 2 * paddle.prod(1 - m2[mi] / m2[mi + 1:]) + denom = 2 * paddle.prod(1 - m2[mi] / m2[mi + 1 :]) elif mi == len(ma) - 1: denom = 2 * paddle.prod(1 - m2[mi] / m2[:mi]) else: - denom = 2 * paddle.prod(1 - m2[mi] / m2[:mi]) * paddle.prod(1 - m2[ - mi] / m2[mi + 1:]) + denom = ( + 2 + * paddle.prod(1 - m2[mi] / m2[:mi]) + * paddle.prod(1 - m2[mi] / m2[mi + 1 :]) + ) Fm[mi] = numer / denom def W(n): return 1 + 2 * paddle.matmul( Fm.unsqueeze(0), - paddle.cos(2 * math.pi * ma.unsqueeze(1) * (n - M / 2. + 0.5) / M)) + paddle.cos(2 * math.pi * ma.unsqueeze(1) * (n - M / 2.0 + 0.5) / M), + ) w = W(paddle.arange(0, M, dtype=dtype)) @@ -150,7 +179,8 @@ def _taylor(M: int, return _truncate(w, needs_trunc) -def _hamming(M: int, sym: bool=True, dtype: str='float64') -> Tensor: +@window_function_register.register() +def _hamming(M: int, sym: bool = True, dtype: str = 'float64') -> Tensor: """Compute a Hamming window. The Hamming window is a taper formed by using a raised cosine with non-zero endpoints, optimized to minimize the nearest side lobe. @@ -158,7 +188,8 @@ def _hamming(M: int, sym: bool=True, dtype: str='float64') -> Tensor: return _general_hamming(M, 0.54, sym, dtype=dtype) -def _hann(M: int, sym: bool=True, dtype: str='float64') -> Tensor: +@window_function_register.register() +def _hann(M: int, sym: bool = True, dtype: str = 'float64') -> Tensor: """Compute a Hann window. The Hann window is a taper formed by using a raised cosine or sine-squared with ends that touch zero. @@ -166,15 +197,18 @@ def _hann(M: int, sym: bool=True, dtype: str='float64') -> Tensor: return _general_hamming(M, 0.5, sym, dtype=dtype) -def _tukey(M: int, alpha=0.5, sym: bool=True, dtype: str='float64') -> Tensor: +@window_function_register.register() +def _tukey( + M: int, alpha=0.5, sym: bool = True, dtype: str = 'float64' +) -> Tensor: """Compute a Tukey window. The Tukey window is also known as a tapered cosine window. """ if _len_guards(M): - return paddle.ones((M, ), dtype=dtype) + return paddle.ones((M,), dtype=dtype) if alpha <= 0: - return paddle.ones((M, ), dtype=dtype) + return paddle.ones((M,), dtype=dtype) elif alpha >= 1.0: return hann(M, sym=sym) @@ -182,53 +216,48 @@ def _tukey(M: int, alpha=0.5, sym: bool=True, dtype: str='float64') -> Tensor: n = paddle.arange(0, M, dtype=dtype) width = int(alpha * (M - 1) / 2.0) - n1 = n[0:width + 1] - n2 = n[width + 1:M - width - 1] - n3 = n[M - width - 1:] + n1 = n[0 : width + 1] + n2 = n[width + 1 : M - width - 1] + n3 = n[M - width - 1 :] w1 = 0.5 * (1 + paddle.cos(math.pi * (-1 + 2.0 * n1 / alpha / (M - 1)))) w2 = paddle.ones(n2.shape, dtype=dtype) - w3 = 0.5 * (1 + paddle.cos(math.pi * (-2.0 / alpha + 1 + 2.0 * n3 / alpha / - (M - 1)))) + w3 = 0.5 * ( + 1 + + paddle.cos(math.pi * (-2.0 / alpha + 1 + 2.0 * n3 / alpha / (M - 1))) + ) w = paddle.concat([w1, w2, w3]) return _truncate(w, needs_trunc) -def _kaiser(M: int, beta: float, sym: bool=True, - dtype: str='float64') -> Tensor: - """Compute a Kaiser window. - The Kaiser window is a taper formed by using a Bessel function. - """ - raise NotImplementedError() - - -def _gaussian(M: int, std: float, sym: bool=True, - dtype: str='float64') -> Tensor: +@window_function_register.register() +def _gaussian( + M: int, std: float, sym: bool = True, dtype: str = 'float64' +) -> Tensor: """Compute a Gaussian window. The Gaussian widows has a Gaussian shape defined by the standard deviation(std). """ if _len_guards(M): - return paddle.ones((M, ), dtype=dtype) + return paddle.ones((M,), dtype=dtype) M, needs_trunc = _extend(M, sym) n = paddle.arange(0, M, dtype=dtype) - (M - 1.0) / 2.0 sig2 = 2 * std * std - w = paddle.exp(-n**2 / sig2) + w = paddle.exp(-(n**2) / sig2) return _truncate(w, needs_trunc) -def _exponential(M: int, - center=None, - tau=1., - sym: bool=True, - dtype: str='float64') -> Tensor: - """Compute an exponential (or Poisson) window. """ +@window_function_register.register() +def _exponential( + M: int, center=None, tau=1.0, sym: bool = True, dtype: str = 'float64' +) -> Tensor: + """Compute an exponential (or Poisson) window.""" if sym and center is not None: raise ValueError("If sym==True, center must be None.") if _len_guards(M): - return paddle.ones((M, ), dtype=dtype) + return paddle.ones((M,), dtype=dtype) M, needs_trunc = _extend(M, sym) if center is None: @@ -240,11 +269,11 @@ def _exponential(M: int, return _truncate(w, needs_trunc) -def _triang(M: int, sym: bool=True, dtype: str='float64') -> Tensor: - """Compute a triangular window. - """ +@window_function_register.register() +def _triang(M: int, sym: bool = True, dtype: str = 'float64') -> Tensor: + """Compute a triangular window.""" if _len_guards(M): - return paddle.ones((M, ), dtype=dtype) + return paddle.ones((M,), dtype=dtype) M, needs_trunc = _extend(M, sym) n = paddle.arange(1, (M + 1) // 2 + 1, dtype=dtype) @@ -258,23 +287,26 @@ def _triang(M: int, sym: bool=True, dtype: str='float64') -> Tensor: return _truncate(w, needs_trunc) -def _bohman(M: int, sym: bool=True, dtype: str='float64') -> Tensor: +@window_function_register.register() +def _bohman(M: int, sym: bool = True, dtype: str = 'float64') -> Tensor: """Compute a Bohman window. The Bohman window is the autocorrelation of a cosine window. """ if _len_guards(M): - return paddle.ones((M, ), dtype=dtype) + return paddle.ones((M,), dtype=dtype) M, needs_trunc = _extend(M, sym) fac = paddle.abs(paddle.linspace(-1, 1, M, dtype=dtype)[1:-1]) w = (1 - fac) * paddle.cos(math.pi * fac) + 1.0 / math.pi * paddle.sin( - math.pi * fac) + math.pi * fac + ) w = _cat([0, w, 0], dtype) return _truncate(w, needs_trunc) -def _blackman(M: int, sym: bool=True, dtype: str='float64') -> Tensor: +@window_function_register.register() +def _blackman(M: int, sym: bool = True, dtype: str = 'float64') -> Tensor: """Compute a Blackman window. The Blackman window is a taper formed by using the first three terms of a summation of cosines. It was designed to have close to the minimal @@ -284,31 +316,44 @@ def _blackman(M: int, sym: bool=True, dtype: str='float64') -> Tensor: return _general_cosine(M, [0.42, 0.50, 0.08], sym, dtype=dtype) -def _cosine(M: int, sym: bool=True, dtype: str='float64') -> Tensor: - """Compute a window with a simple cosine shape. - """ +@window_function_register.register() +def _cosine(M: int, sym: bool = True, dtype: str = 'float64') -> Tensor: + """Compute a window with a simple cosine shape.""" if _len_guards(M): - return paddle.ones((M, ), dtype=dtype) + return paddle.ones((M,), dtype=dtype) M, needs_trunc = _extend(M, sym) - w = paddle.sin(math.pi / M * (paddle.arange(0, M, dtype=dtype) + .5)) + w = paddle.sin(math.pi / M * (paddle.arange(0, M, dtype=dtype) + 0.5)) return _truncate(w, needs_trunc) -def get_window(window: Union[str, Tuple[str, float]], - win_length: int, - fftbins: bool=True, - dtype: str='float64') -> Tensor: +def get_window( + window: Union[str, Tuple[str, float]], + win_length: int, + fftbins: bool = True, + dtype: str = 'float64', +) -> Tensor: """Return a window of a given length and type. Args: - window (Union[str, Tuple[str, float]]): The window function applied to the signal before the Fourier transform. Supported window functions: 'hamming', 'hann', 'kaiser', 'gaussian', 'exponential', 'triang', 'bohman', 'blackman', 'cosine', 'tukey', 'taylor'. + window (Union[str, Tuple[str, float]]): The window function applied to the signal before the Fourier transform. Supported window functions: 'hamming', 'hann', 'gaussian', 'general_gaussian', 'exponential', 'triang', 'bohman', 'blackman', 'cosine', 'tukey', 'taylor'. win_length (int): Number of samples. fftbins (bool, optional): If True, create a "periodic" window. Otherwise, create a "symmetric" window, for use in filter design. Defaults to True. dtype (str, optional): The data type of the return window. Defaults to 'float64'. Returns: Tensor: The window represented as a tensor. + + Examples: + .. code-block:: python + + import paddle + + n_fft = 512 + cosine_window = paddle.audio.functional.get_window('cosine', n_fft) + + std = 7 + gaussian_window = paddle.audio.functional.get_window(('gaussian',std), n_fft) """ sym = not fftbins @@ -319,19 +364,22 @@ def get_window(window: Union[str, Tuple[str, float]], args = window[1:] elif isinstance(window, str): if window in ['gaussian', 'exponential']: - raise ValueError("The '" + window + "' window needs one or " - "more parameters -- pass a tuple.") + raise ValueError( + "The '" + window + "' window needs one or " + "more parameters -- pass a tuple." + ) else: winstr = window else: - raise ValueError("%s as window type is not supported." % - str(type(window))) + raise ValueError( + "%s as window type is not supported." % str(type(window)) + ) try: - winfunc = eval('_' + winstr) + winfunc = window_function_register.get('_' + winstr) except KeyError as e: raise ValueError("Unknown window type.") from e - params = (win_length, ) + args + params = (win_length,) + args kwargs = {'sym': sym} return winfunc(*params, dtype=dtype, **kwargs) diff --git a/tests/unit/audio/backends/soundfile/__init__.py b/audio/paddleaudio/kaldi/__init__.py similarity index 92% rename from tests/unit/audio/backends/soundfile/__init__.py rename to audio/paddleaudio/kaldi/__init__.py index 97043fd7..f951e280 100644 --- a/tests/unit/audio/backends/soundfile/__init__.py +++ b/audio/paddleaudio/kaldi/__init__.py @@ -11,3 +11,5 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +from .kaldi import fbank +from .kaldi import pitch diff --git a/audio/paddleaudio/kaldi/kaldi.py b/audio/paddleaudio/kaldi/kaldi.py new file mode 100644 index 00000000..16969d77 --- /dev/null +++ b/audio/paddleaudio/kaldi/kaldi.py @@ -0,0 +1,132 @@ +# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import paddleaudio +from paddleaudio._internal import module_utils + +__all__ = [ + 'fbank', + 'pitch', +] + + +@module_utils.requires_kaldi() +def fbank( + wav, + samp_freq: int=16000, + frame_shift_ms: float=10.0, + frame_length_ms: float=25.0, + dither: float=0.0, + preemph_coeff: float=0.97, + remove_dc_offset: bool=True, + window_type: str='povey', + round_to_power_of_two: bool=True, + blackman_coeff: float=0.42, + snip_edges: bool=True, + allow_downsample: bool=False, + allow_upsample: bool=False, + max_feature_vectors: int=-1, + num_bins: int=23, + low_freq: float=20, + high_freq: float=0, + vtln_low: float=100, + vtln_high: float=-500, + debug_mel: bool=False, + htk_mode: bool=False, + use_energy: bool=False, # fbank opts + energy_floor: float=0.0, + raw_energy: bool=True, + htk_compat: bool=False, + use_log_fbank: bool=True, + use_power: bool=True): + frame_opts = paddleaudio._paddleaudio.FrameExtractionOptions() + mel_opts = paddleaudio._paddleaudio.MelBanksOptions() + fbank_opts = paddleaudio._paddleaudio.FbankOptions() + frame_opts.samp_freq = samp_freq + frame_opts.frame_shift_ms = frame_shift_ms + frame_opts.frame_length_ms = frame_length_ms + frame_opts.dither = dither + frame_opts.preemph_coeff = preemph_coeff + frame_opts.remove_dc_offset = remove_dc_offset + frame_opts.window_type = window_type + frame_opts.round_to_power_of_two = round_to_power_of_two + frame_opts.blackman_coeff = blackman_coeff + frame_opts.snip_edges = snip_edges + frame_opts.allow_downsample = allow_downsample + frame_opts.allow_upsample = allow_upsample + frame_opts.max_feature_vectors = max_feature_vectors + + mel_opts.num_bins = num_bins + mel_opts.low_freq = low_freq + mel_opts.high_freq = high_freq + mel_opts.vtln_low = vtln_low + mel_opts.vtln_high = vtln_high + mel_opts.debug_mel = debug_mel + mel_opts.htk_mode = htk_mode + + fbank_opts.use_energy = use_energy + fbank_opts.energy_floor = energy_floor + fbank_opts.raw_energy = raw_energy + fbank_opts.htk_compat = htk_compat + fbank_opts.use_log_fbank = use_log_fbank + fbank_opts.use_power = use_power + feat = paddleaudio._paddleaudio.ComputeFbank(frame_opts, mel_opts, + fbank_opts, wav) + return feat + + +@module_utils.requires_kaldi() +def pitch(wav, + samp_freq: int=16000, + frame_shift_ms: float=10.0, + frame_length_ms: float=25.0, + preemph_coeff: float=0.0, + min_f0: int=50, + max_f0: int=400, + soft_min_f0: float=10.0, + penalty_factor: float=0.1, + lowpass_cutoff: int=1000, + resample_freq: int=4000, + delta_pitch: float=0.005, + nccf_ballast: int=7000, + lowpass_filter_width: int=1, + upsample_filter_width: int=5, + max_frames_latency: int=0, + frames_per_chunk: int=0, + simulate_first_pass_online: bool=False, + recompute_frame: int=500, + nccf_ballast_online: bool=False, + snip_edges: bool=True): + pitch_opts = paddleaudio._paddleaudio.PitchExtractionOptions() + pitch_opts.samp_freq = samp_freq + pitch_opts.frame_shift_ms = frame_shift_ms + pitch_opts.frame_length_ms = frame_length_ms + pitch_opts.preemph_coeff = preemph_coeff + pitch_opts.min_f0 = min_f0 + pitch_opts.max_f0 = max_f0 + pitch_opts.soft_min_f0 = soft_min_f0 + pitch_opts.penalty_factor = penalty_factor + pitch_opts.lowpass_cutoff = lowpass_cutoff + pitch_opts.resample_freq = resample_freq + pitch_opts.delta_pitch = delta_pitch + pitch_opts.nccf_ballast = nccf_ballast + pitch_opts.lowpass_filter_width = lowpass_filter_width + pitch_opts.upsample_filter_width = upsample_filter_width + pitch_opts.max_frames_latency = max_frames_latency + pitch_opts.frames_per_chunk = frames_per_chunk + pitch_opts.simulate_first_pass_online = simulate_first_pass_online + pitch_opts.recompute_frame = recompute_frame + pitch_opts.nccf_ballast_online = nccf_ballast_online + pitch_opts.snip_edges = snip_edges + pitch = paddleaudio._paddleaudio.ComputeKaldiPitch(pitch_opts, wav) + return pitch diff --git a/paddlespeech/audio/metric/__init__.py b/audio/paddleaudio/metric/__init__.py similarity index 100% rename from paddlespeech/audio/metric/__init__.py rename to audio/paddleaudio/metric/__init__.py diff --git a/paddlespeech/audio/metric/eer.py b/audio/paddleaudio/metric/eer.py similarity index 100% rename from paddlespeech/audio/metric/eer.py rename to audio/paddleaudio/metric/eer.py diff --git a/audio/paddleaudio/sox_effects/__init__.py b/audio/paddleaudio/sox_effects/__init__.py new file mode 100644 index 00000000..6ca26ec5 --- /dev/null +++ b/audio/paddleaudio/sox_effects/__init__.py @@ -0,0 +1,21 @@ +from paddleaudio._internal import module_utils as _mod_utils + +from .sox_effects import apply_effects_file +from .sox_effects import apply_effects_tensor +from .sox_effects import effect_names +from .sox_effects import init_sox_effects +from .sox_effects import shutdown_sox_effects + +if _mod_utils.is_sox_available(): + import atexit + + init_sox_effects() + atexit.register(shutdown_sox_effects) + +__all__ = [ + "init_sox_effects", + "shutdown_sox_effects", + "effect_names", + "apply_effects_tensor", + "apply_effects_file", +] diff --git a/audio/paddleaudio/sox_effects/sox_effects.py b/audio/paddleaudio/sox_effects/sox_effects.py new file mode 100644 index 00000000..cb7e1b0b --- /dev/null +++ b/audio/paddleaudio/sox_effects/sox_effects.py @@ -0,0 +1,241 @@ +import os +from typing import List +from typing import Optional +from typing import Tuple + +import paddle +import paddleaudio +from paddleaudio._internal import module_utils as _mod_utils +from paddleaudio.utils.sox_utils import list_effects + +#code is from: https://github.com/pytorch/audio/blob/main/torchaudio/sox_effects/sox_effects.py + + +@_mod_utils.requires_sox() +def init_sox_effects(): + """Initialize resources required to use sox effects. + + Note: + You do not need to call this function manually. It is called automatically. + + Once initialized, you do not need to call this function again across the multiple uses of + sox effects though it is safe to do so as long as :func:`shutdown_sox_effects` is not called yet. + Once :func:`shutdown_sox_effects` is called, you can no longer use SoX effects and initializing + again will result in error. + """ + paddleaudio._paddleaudio.sox_effects_initialize_sox_effects() + + +@_mod_utils.requires_sox() +def shutdown_sox_effects(): + """Clean up resources required to use sox effects. + + Note: + You do not need to call this function manually. It is called automatically. + + It is safe to call this function multiple times. + Once :py:func:`shutdown_sox_effects` is called, you can no longer use SoX effects and + initializing again will result in error. + """ + paddleaudio._paddleaudio.sox_effects_shutdown_sox_effects() + + +@_mod_utils.requires_sox() +def effect_names() -> List[str]: + """Gets list of valid sox effect names + + Returns: + List[str]: list of available effect names. + + Example + >>> paddleaudio.sox_effects.effect_names() + ['allpass', 'band', 'bandpass', ... ] + """ + return list(list_effects().keys()) + + +@_mod_utils.requires_sox() +def apply_effects_tensor( + tensor: paddle.Tensor, + sample_rate: int, + effects: List[List[str]], + channels_first: bool=True, ) -> Tuple[paddle.Tensor, int]: + """Apply sox effects to given Tensor + + .. devices:: CPU + + Note: + This function only works on CPU Tensors. + This function works in the way very similar to ``sox`` command, however there are slight + differences. For example, ``sox`` command adds certain effects automatically (such as + ``rate`` effect after ``speed`` and ``pitch`` and other effects), but this function does + only applies the given effects. (Therefore, to actually apply ``speed`` effect, you also + need to give ``rate`` effect with desired sampling rate.). + + Args: + tensor (paddle.Tensor): Input 2D CPU Tensor. + sample_rate (int): Sample rate + effects (List[List[str]]): List of effects. + channels_first (bool, optional): Indicates if the input Tensor's dimension is + `[channels, time]` or `[time, channels]` + + Returns: + (Tensor, int): Resulting Tensor and sample rate. + The resulting Tensor has the same ``dtype`` as the input Tensor, and + the same channels order. The shape of the Tensor can be different based on the + effects applied. Sample rate can also be different based on the effects applied. + + Example - Basic usage + >>> + >>> # Defines the effects to apply + >>> effects = [ + ... ['gain', '-n'], # normalises to 0dB + ... ['pitch', '5'], # 5 cent pitch shift + ... ['rate', '8000'], # resample to 8000 Hz + ... ] + >>> + >>> # Generate pseudo wave: + >>> # normalized, channels first, 2ch, sampling rate 16000, 1 second + >>> sample_rate = 16000 + >>> waveform = 2 * paddle.rand([2, sample_rate * 1]) - 1 + >>> waveform.shape + paddle.Size([2, 16000]) + >>> waveform + tensor([[ 0.3138, 0.7620, -0.9019, ..., -0.7495, -0.4935, 0.5442], + [-0.0832, 0.0061, 0.8233, ..., -0.5176, -0.9140, -0.2434]]) + >>> + >>> # Apply effects + >>> waveform, sample_rate = apply_effects_tensor( + ... wave_form, sample_rate, effects, channels_first=True) + >>> + >>> # Check the result + >>> # The new waveform is sampling rate 8000, 1 second. + >>> # normalization and channel order are preserved + >>> waveform.shape + paddle.Size([2, 8000]) + >>> waveform + tensor([[ 0.5054, -0.5518, -0.4800, ..., -0.0076, 0.0096, -0.0110], + [ 0.1331, 0.0436, -0.3783, ..., -0.0035, 0.0012, 0.0008]]) + >>> sample_rate + 8000 + + """ + tensor_np = tensor.numpy() + ret = paddleaudio._paddleaudio.sox_effects_apply_effects_tensor(tensor_np, sample_rate, + effects, channels_first) + if ret is not None: + return (paddle.to_tensor(ret[0]), ret[1]) + raise RuntimeError("Failed to apply sox effect") + + +@_mod_utils.requires_sox() +def apply_effects_file( + path: str, + effects: List[List[str]], + normalize: bool=True, + channels_first: bool=True, + format: Optional[str]=None, ) -> Tuple[paddle.Tensor, int]: + """Apply sox effects to the audio file and load the resulting data as Tensor + + Note: + This function works in the way very similar to ``sox`` command, however there are slight + differences. For example, ``sox`` commnad adds certain effects automatically (such as + ``rate`` effect after ``speed``, ``pitch`` etc), but this function only applies the given + effects. Therefore, to actually apply ``speed`` effect, you also need to give ``rate`` + effect with desired sampling rate, because internally, ``speed`` effects only alter sampling + rate and leave samples untouched. + + Args: + path (path-like object or file-like object): + effects (List[List[str]]): List of effects. + normalize (bool, optional): + When ``True``, this function always return ``float32``, and sample values are + normalized to ``[-1.0, 1.0]``. + If input file is integer WAV, giving ``False`` will change the resulting Tensor type to + integer type. This argument has no effect for formats other + than integer WAV type. + channels_first (bool, optional): When True, the returned Tensor has dimension `[channel, time]`. + Otherwise, the returned Tensor's dimension is `[time, channel]`. + format (str or None, optional): + Override the format detection with the given format. + Providing the argument might help when libsox can not infer the format + from header or extension, + + Returns: + (Tensor, int): Resulting Tensor and sample rate. + If ``normalize=True``, the resulting Tensor is always ``float32`` type. + If ``normalize=False`` and the input audio file is of integer WAV file, then the + resulting Tensor has corresponding integer type. (Note 24 bit integer type is not supported) + If ``channels_first=True``, the resulting Tensor has dimension `[channel, time]`, + otherwise `[time, channel]`. + + Example - Basic usage + >>> + >>> # Defines the effects to apply + >>> effects = [ + ... ['gain', '-n'], # normalises to 0dB + ... ['pitch', '5'], # 5 cent pitch shift + ... ['rate', '8000'], # resample to 8000 Hz + ... ] + >>> + >>> # Apply effects and load data with channels_first=True + >>> waveform, sample_rate = apply_effects_file("data.wav", effects, channels_first=True) + >>> + >>> # Check the result + >>> waveform.shape + paddle.Size([2, 8000]) + >>> waveform + tensor([[ 5.1151e-03, 1.8073e-02, 2.2188e-02, ..., 1.0431e-07, + -1.4761e-07, 1.8114e-07], + [-2.6924e-03, 2.1860e-03, 1.0650e-02, ..., 6.4122e-07, + -5.6159e-07, 4.8103e-07]]) + >>> sample_rate + 8000 + + Example - Apply random speed perturbation to dataset + >>> + >>> # Load data from file, apply random speed perturbation + >>> class RandomPerturbationFile(paddle.utils.data.Dataset): + ... \"\"\"Given flist, apply random speed perturbation + ... + ... Suppose all the input files are at least one second long. + ... \"\"\" + ... def __init__(self, flist: List[str], sample_rate: int): + ... super().__init__() + ... self.flist = flist + ... self.sample_rate = sample_rate + ... + ... def __getitem__(self, index): + ... speed = 0.5 + 1.5 * random.randn() + ... effects = [ + ... ['gain', '-n', '-10'], # apply 10 db attenuation + ... ['remix', '-'], # merge all the channels + ... ['speed', f'{speed:.5f}'], # duration is now 0.5 ~ 2.0 seconds. + ... ['rate', f'{self.sample_rate}'], + ... ['pad', '0', '1.5'], # add 1.5 seconds silence at the end + ... ['trim', '0', '2'], # get the first 2 seconds + ... ] + ... waveform, _ = paddleaudio.sox_effects.apply_effects_file( + ... self.flist[index], effects) + ... return waveform + ... + ... def __len__(self): + ... return len(self.flist) + ... + >>> dataset = RandomPerturbationFile(file_list, sample_rate=8000) + >>> loader = paddle.utils.data.DataLoader(dataset, batch_size=32) + >>> for batch in loader: + >>> pass + """ + if hasattr(path, "read"): + ret = paddleaudio._paddleaudio.apply_effects_fileobj(path, effects, normalize, + channels_first, format) + if ret is None: + raise RuntimeError("Failed to load audio from {}".format(path)) + return (paddle.to_tensor(ret[0]), ret[1]) + path = os.fspath(path) + ret = paddleaudio._paddleaudio.sox_effects_apply_effects_file(path, effects, normalize, + channels_first, format) + if ret is not None: + return (paddle.to_tensor(ret[0]), ret[1]) + raise RuntimeError("Failed to load audio from {}".format(path)) diff --git a/audio/paddleaudio/src/CMakeLists.txt b/audio/paddleaudio/src/CMakeLists.txt new file mode 100644 index 00000000..fb6f3209 --- /dev/null +++ b/audio/paddleaudio/src/CMakeLists.txt @@ -0,0 +1,217 @@ +if (MSVC) + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) +endif() + +if(APPLE) +set(CMAKE_SHARED_LIBRARY_SUFFIX ".so") +endif(APPLE) + +################################################################################ +# libpaddleaudio +################################################################################ +set( + LIBPADDLEAUDIO_SOURCES + utils.cpp + ) + +set( + LIBPADDLEAUDIO_INCLUDE_DIRS + ${PROJECT_SOURCE_DIR} + ) + +set( + LIBPADDLEAUDIO_LINK_LIBRARIES + ) + +set( + LIBPADDLEAUDIO_COMPILE_DEFINITIONS) + +#------------------------------------------------------------------------------# +# START OF CUSTOMIZATION LOGICS +#------------------------------------------------------------------------------# + +if(BUILD_SOX) + list( + APPEND + LIBPADDLEAUDIO_LINK_LIBRARIES + libsox + ) + list( + APPEND + LIBPADDLEAUDIO_SOURCES + ) + list( + APPEND + LIBPADDLEAUDIO_COMPILE_DEFINITIONS + INCLUDE_SOX + ) +endif() + + +if(BUILD_KALDI) + list( + APPEND + LIBPADDLEAUDIO_LINK_LIBRARIES + libkaldi + ) + list( + APPEND + LIBPADDLEAUDIO_COMPILE_DEFINITIONS + INCLUDE_KALDI + COMPILE_WITHOUT_OPENFST + ) +endif() + +#------------------------------------------------------------------------------# +# END OF CUSTOMIZATION LOGICS +#------------------------------------------------------------------------------# + +function (define_library name source include_dirs link_libraries compile_defs) + add_library(${name} SHARED ${source}) + target_include_directories(${name} PRIVATE ${include_dirs}) + target_link_libraries(${name} ${link_libraries}) + target_compile_definitions(${name} PRIVATE ${compile_defs}) + set_target_properties(${name} PROPERTIES PREFIX "") + if (MSVC) + set_target_properties(${name} PROPERTIES SUFFIX ".pyd") + endif(MSVC) + + install( + TARGETS ${name} + LIBRARY DESTINATION lib + RUNTIME DESTINATION lib # For Windows + ) +endfunction() + + +define_library( + libpaddleaudio + "${LIBPADDLEAUDIO_SOURCES}" + "${LIBPADDLEAUDIO_INCLUDE_DIRS}" + "${LIBPADDLEAUDIO_LINK_LIBRARIES}" + "${LIBPADDLEAUDIO_COMPILE_DEFINITIONS}" +) + +if (APPLE) + add_custom_command(TARGET libpaddleaudio POST_BUILD COMMAND install_name_tool -change "${GFORTRAN_LIBRARIES_DIR}/libgcc_s.1.1.dylib" "@loader_path/libgcc_s.1.1.dylib" libpaddleaudio.so) +endif(APPLE) + +if (UNIX AND NOT APPLE) + set_target_properties(libpaddleaudio PROPERTIES INSTALL_RPATH "$ORIGIN") +endif() + +if (APPLE) + set(AUDIO_LIBRARY libpaddleaudio CACHE INTERNAL "") +else() + set(AUDIO_LIBRARY -Wl,--no-as-needed libpaddleaudio -Wl,--as-needed CACHE INTERNAL "") +endif() + + ################################################################################ +# _paddleaudio.so +################################################################################ +if (BUILD_PADDLEAUDIO_PYTHON_EXTENSION) +if (WIN32) + find_package(Python3 ${PYTHON_VERSION} EXACT COMPONENTS Development) + set(ADDITIONAL_ITEMS Python3::Python) +endif() +function(define_extension name sources include_dirs libraries definitions) + add_library(${name} SHARED ${sources}) + target_compile_definitions(${name} PRIVATE "${definitions}") + target_include_directories( + ${name} PRIVATE ${PROJECT_SOURCE_DIR} ${Python_INCLUDE_DIR} ${pybind11_INCLUDE_DIR} ${include_dirs}) + target_link_libraries( + ${name} + ${libraries} + ${PYTHON_LIBRARY} + ${ADDITIONAL_ITEMS} + ) + set_target_properties(${name} PROPERTIES PREFIX "") + if (MSVC) + set_target_properties(${name} PROPERTIES SUFFIX ".pyd") + endif(MSVC) + if (APPLE) + # https://github.com/facebookarchive/caffe2/issues/854#issuecomment-364538485 + # https://github.com/pytorch/pytorch/commit/73f6715f4725a0723d8171d3131e09ac7abf0666 + set_target_properties(${name} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup") + endif() + install( + TARGETS ${name} + LIBRARY DESTINATION . + RUNTIME DESTINATION . # For Windows + ) +endfunction() + +set( + EXTENSION_SOURCES + pybind/pybind.cpp + ) +#----------------------------------------------------------------------------# +# START OF CUSTOMIZATION LOGICS +#----------------------------------------------------------------------------# +if(BUILD_SOX) + list( + APPEND + EXTENSION_SOURCES + pybind/sox/effects.cpp + pybind/sox/effects_chain.cpp + pybind/sox/io.cpp + pybind/sox/types.cpp + pybind/sox/utils.cpp + ) +endif() + +if(BUILD_KALDI) + list( + APPEND + EXTENSION_SOURCES + pybind/kaldi/kaldi_feature_wrapper.cc + pybind/kaldi/kaldi_feature.cc + ) +endif() +#----------------------------------------------------------------------------# +# END OF CUSTOMIZATION LOGICS +#----------------------------------------------------------------------------# +define_extension( + _paddleaudio + "${EXTENSION_SOURCES}" + "" + libpaddleaudio + "${LIBPADDLEAUDIO_COMPILE_DEFINITIONS}" + ) +# if(BUILD_CTC_DECODER) +# set( +# DECODER_EXTENSION_SOURCES +# decoder/bindings/pybind.cpp +# ) +# define_extension( +# _paddleaudio_decoder +# "${DECODER_EXTENSION_SOURCES}" +# "" +# "libpaddleaudio_decoder" +# "${LIBPADDLEAUDIO_DECODER_DEFINITIONS}" +# ) +# endif() +# if(USE_FFMPEG) +# set( +# FFMPEG_EXTENSION_SOURCES +# ffmpeg/pybind/typedefs.cpp +# ffmpeg/pybind/pybind.cpp +# ffmpeg/pybind/stream_reader.cpp +# ) +# define_extension( +# _paddleaudio_ffmpeg +# "${FFMPEG_EXTENSION_SOURCES}" +# "${FFMPEG_INCLUDE_DIRS}" +# "libpaddleaudio_ffmpeg" +# "${LIBPADDLEAUDIO_DECODER_DEFINITIONS}" +# ) +# endif() +endif() + +if (APPLE) + add_custom_command(TARGET _paddleaudio POST_BUILD COMMAND install_name_tool -change "${GFORTRAN_LIBRARIES_DIR}/libgcc_s.1.1.dylib" "@loader_path/lib/libgcc_s.1.1.dylib" _paddleaudio.so) +endif(APPLE) + +if (UNIX AND NOT APPLE) + set_target_properties(_paddleaudio PROPERTIES INSTALL_RPATH "$ORIGIN/lib") +endif() diff --git a/audio/paddleaudio/src/optional/COPYING b/audio/paddleaudio/src/optional/COPYING new file mode 100644 index 00000000..0e259d42 --- /dev/null +++ b/audio/paddleaudio/src/optional/COPYING @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/audio/paddleaudio/src/optional/optional.hpp b/audio/paddleaudio/src/optional/optional.hpp new file mode 100644 index 00000000..bceb4113 --- /dev/null +++ b/audio/paddleaudio/src/optional/optional.hpp @@ -0,0 +1,2182 @@ + +/// +// optional - An implementation of std::optional with extensions +// Written in 2017 by Sy Brand (tartanllama@gmail.com, @TartanLlama) +// +// Documentation available at https://tl.tartanllama.xyz/ +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to the +// public domain worldwide. This software is distributed without any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. If not, see +// . +// https://github.com/TartanLlama/optional +/// + +#ifndef TL_OPTIONAL_HPP +#define TL_OPTIONAL_HPP + +#define TL_OPTIONAL_VERSION_MAJOR 1 +#define TL_OPTIONAL_VERSION_MINOR 0 +#define TL_OPTIONAL_VERSION_PATCH 0 + +#include +#include +#include +#include +#include + +#if (defined(_MSC_VER) && _MSC_VER == 1900) +#define TL_OPTIONAL_MSVC2015 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \ + !defined(__clang__)) +#define TL_OPTIONAL_GCC49 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 4 && \ + !defined(__clang__)) +#define TL_OPTIONAL_GCC54 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 5 && \ + !defined(__clang__)) +#define TL_OPTIONAL_GCC55 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \ + !defined(__clang__)) +// GCC < 5 doesn't support overloading on const&& for member functions +#define TL_OPTIONAL_NO_CONSTRR + +// GCC < 5 doesn't support some standard C++11 type traits +#define TL_OPTIONAL_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ + std::has_trivial_copy_constructor::value +#define TL_OPTIONAL_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ + std::has_trivial_copy_assign::value + +// This one will be different for GCC 5.7 if it's ever supported +#define TL_OPTIONAL_IS_TRIVIALLY_DESTRUCTIBLE(T) \ + std::is_trivially_destructible::value + +// GCC 5 < v < 8 has a bug in is_trivially_copy_constructible which breaks +// std::vector +// for non-copyable types +#elif (defined(__GNUC__) && __GNUC__ < 8 && !defined(__clang__)) +#ifndef TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX +#define TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX +namespace tl { +namespace detail { +template +struct is_trivially_copy_constructible + : std::is_trivially_copy_constructible {}; +#ifdef _GLIBCXX_VECTOR +template +struct is_trivially_copy_constructible> + : std::is_trivially_copy_constructible {}; +#endif +} +} +#endif + +#define TL_OPTIONAL_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ + tl::detail::is_trivially_copy_constructible::value +#define TL_OPTIONAL_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ + std::is_trivially_copy_assignable::value +#define TL_OPTIONAL_IS_TRIVIALLY_DESTRUCTIBLE(T) \ + std::is_trivially_destructible::value +#else +#define TL_OPTIONAL_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ + std::is_trivially_copy_constructible::value +#define TL_OPTIONAL_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ + std::is_trivially_copy_assignable::value +#define TL_OPTIONAL_IS_TRIVIALLY_DESTRUCTIBLE(T) \ + std::is_trivially_destructible::value +#endif + +#if __cplusplus > 201103L +#define TL_OPTIONAL_CXX14 +#endif + +// constexpr implies const in C++11, not C++14 +#if (__cplusplus == 201103L || defined(TL_OPTIONAL_MSVC2015) || \ + defined(TL_OPTIONAL_GCC49)) +#define TL_OPTIONAL_11_CONSTEXPR +#else +#define TL_OPTIONAL_11_CONSTEXPR constexpr +#endif + +namespace tl { +#ifndef TL_MONOSTATE_INPLACE_MUTEX +#define TL_MONOSTATE_INPLACE_MUTEX +/// Used to represent an optional with no data; essentially a bool +class monostate {}; + +/// A tag type to tell optional to construct its value in-place +struct in_place_t { + explicit in_place_t() = default; +}; +/// A tag to tell optional to construct its value in-place +static constexpr in_place_t in_place{}; +#endif + +template +class optional; + +namespace detail { +#ifndef TL_TRAITS_MUTEX +#define TL_TRAITS_MUTEX +// C++14-style aliases for brevity +template +using remove_const_t = typename std::remove_const::type; +template +using remove_reference_t = typename std::remove_reference::type; +template +using decay_t = typename std::decay::type; +template +using enable_if_t = typename std::enable_if::type; +template +using conditional_t = typename std::conditional::type; + +// std::conjunction from C++17 +template +struct conjunction : std::true_type {}; +template +struct conjunction : B {}; +template +struct conjunction + : std::conditional, B>::type {}; + +#if defined(_LIBCPP_VERSION) && __cplusplus == 201103L +#define TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND +#endif + +// In C++11 mode, there's an issue in libc++'s std::mem_fn +// which results in a hard-error when using it in a noexcept expression +// in some cases. This is a check to workaround the common failing case. +#ifdef TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND +template +struct is_pointer_to_non_const_member_func : std::false_type {}; +template +struct is_pointer_to_non_const_member_func + : std::true_type {}; +template +struct is_pointer_to_non_const_member_func + : std::true_type {}; +template +struct is_pointer_to_non_const_member_func + : std::true_type {}; +template +struct is_pointer_to_non_const_member_func + : std::true_type {}; +template +struct is_pointer_to_non_const_member_func + : std::true_type {}; +template +struct is_pointer_to_non_const_member_func + : std::true_type {}; + +template +struct is_const_or_const_ref : std::false_type {}; +template +struct is_const_or_const_ref : std::true_type {}; +template +struct is_const_or_const_ref : std::true_type {}; +#endif + +// std::invoke from C++17 +// https://stackoverflow.com/questions/38288042/c11-14-invoke-workaround +template < + typename Fn, + typename... Args, +#ifdef TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND + typename = enable_if_t::value && + is_const_or_const_ref::value)>, +#endif + typename = enable_if_t>::value>, + int = 0> +constexpr auto invoke(Fn &&f, Args &&... args) noexcept( + noexcept(std::mem_fn(f)(std::forward(args)...))) + -> decltype(std::mem_fn(f)(std::forward(args)...)) { + return std::mem_fn(f)(std::forward(args)...); +} + +template >::value>> +constexpr auto invoke(Fn &&f, Args &&... args) noexcept( + noexcept(std::forward(f)(std::forward(args)...))) + -> decltype(std::forward(f)(std::forward(args)...)) { + return std::forward(f)(std::forward(args)...); +} + +// std::invoke_result from C++17 +template +struct invoke_result_impl; + +template +struct invoke_result_impl< + F, + decltype(detail::invoke(std::declval(), std::declval()...), void()), + Us...> { + using type = + decltype(detail::invoke(std::declval(), std::declval()...)); +}; + +template +using invoke_result = invoke_result_impl; + +template +using invoke_result_t = typename invoke_result::type; + +#if defined(_MSC_VER) && _MSC_VER <= 1900 +// TODO make a version which works with MSVC 2015 +template +struct is_swappable : std::true_type {}; + +template +struct is_nothrow_swappable : std::true_type {}; +#else +// https://stackoverflow.com/questions/26744589/what-is-a-proper-way-to-implement-is-swappable-to-test-for-the-swappable-concept +namespace swap_adl_tests { +// if swap ADL finds this then it would call std::swap otherwise (same +// signature) +struct tag {}; + +template +tag swap(T &, T &); +template +tag swap(T (&a)[N], T (&b)[N]); + +// helper functions to test if an unqualified swap is possible, and if it +// becomes std::swap +template +std::false_type can_swap(...) noexcept(false); +template (), std::declval()))> +std::true_type can_swap(int) noexcept(noexcept(swap(std::declval(), + std::declval()))); + +template +std::false_type uses_std(...); +template +std::is_same(), std::declval())), tag> +uses_std(int); + +template +struct is_std_swap_noexcept + : std::integral_constant::value && + std::is_nothrow_move_assignable::value> {}; + +template +struct is_std_swap_noexcept : is_std_swap_noexcept {}; + +template +struct is_adl_swap_noexcept + : std::integral_constant(0))> {}; +} // namespace swap_adl_tests + +template +struct is_swappable + : std::integral_constant< + bool, + decltype(detail::swap_adl_tests::can_swap(0))::value && + (!decltype(detail::swap_adl_tests::uses_std(0))::value || + (std::is_move_assignable::value && + std::is_move_constructible::value))> {}; + +template +struct is_swappable + : std::integral_constant< + bool, + decltype(detail::swap_adl_tests::can_swap(0))::value && + (!decltype( + detail::swap_adl_tests::uses_std(0))::value || + is_swappable::value)> {}; + +template +struct is_nothrow_swappable + : std::integral_constant< + bool, + is_swappable::value && + ((decltype(detail::swap_adl_tests::uses_std(0))::value + &&detail::swap_adl_tests::is_std_swap_noexcept::value) || + (!decltype(detail::swap_adl_tests::uses_std(0))::value && + detail::swap_adl_tests::is_adl_swap_noexcept::value))> { +}; +#endif +#endif + +// std::void_t from C++17 +template +struct voider { + using type = void; +}; +template +using void_t = typename voider::type; + +// Trait for checking if a type is a tl::optional +template +struct is_optional_impl : std::false_type {}; +template +struct is_optional_impl> : std::true_type {}; +template +using is_optional = is_optional_impl>; + +// Change void to tl::monostate +template +using fixup_void = conditional_t::value, monostate, U>; + +template > +using get_map_return = optional>>; + +// Check if invoking F for some Us returns void +template +struct returns_void_impl; +template +struct returns_void_impl>, U...> + : std::is_void> {}; +template +using returns_void = returns_void_impl; + +template +using enable_if_ret_void = enable_if_t::value>; + +template +using disable_if_ret_void = enable_if_t::value>; + +template +using enable_forward_value = + detail::enable_if_t::value && + !std::is_same, in_place_t>::value && + !std::is_same, detail::decay_t>::value>; + +template +using enable_from_other = detail::enable_if_t< + std::is_constructible::value && + !std::is_constructible &>::value && + !std::is_constructible &&>::value && + !std::is_constructible &>::value && + !std::is_constructible &&>::value && + !std::is_convertible &, T>::value && + !std::is_convertible &&, T>::value && + !std::is_convertible &, T>::value && + !std::is_convertible &&, T>::value>; + +template +using enable_assign_forward = detail::enable_if_t< + !std::is_same, detail::decay_t>::value && + !detail::conjunction, + std::is_same>>::value && + std::is_constructible::value && std::is_assignable::value>; + +template +using enable_assign_from_other = detail::enable_if_t< + std::is_constructible::value && + std::is_assignable::value && + !std::is_constructible &>::value && + !std::is_constructible &&>::value && + !std::is_constructible &>::value && + !std::is_constructible &&>::value && + !std::is_convertible &, T>::value && + !std::is_convertible &&, T>::value && + !std::is_convertible &, T>::value && + !std::is_convertible &&, T>::value && + !std::is_assignable &>::value && + !std::is_assignable &&>::value && + !std::is_assignable &>::value && + !std::is_assignable &&>::value>; + +// The storage base manages the actual storage, and correctly propagates +// trivial destruction from T. This case is for when T is not trivially +// destructible. +template ::value> +struct optional_storage_base { + TL_OPTIONAL_11_CONSTEXPR optional_storage_base() noexcept + : m_dummy(), + m_has_value(false) {} + + template + TL_OPTIONAL_11_CONSTEXPR optional_storage_base(in_place_t, U &&... u) + : m_value(std::forward(u)...), m_has_value(true) {} + + ~optional_storage_base() { + if (m_has_value) { + m_value.~T(); + m_has_value = false; + } + } + + struct dummy {}; + union { + dummy m_dummy; + T m_value; + }; + + bool m_has_value; +}; + +// This case is for when T is trivially destructible. +template +struct optional_storage_base { + TL_OPTIONAL_11_CONSTEXPR optional_storage_base() noexcept + : m_dummy(), + m_has_value(false) {} + + template + TL_OPTIONAL_11_CONSTEXPR optional_storage_base(in_place_t, U &&... u) + : m_value(std::forward(u)...), m_has_value(true) {} + + // No destructor, so this class is trivially destructible + + struct dummy {}; + union { + dummy m_dummy; + T m_value; + }; + + bool m_has_value = false; +}; + +// This base class provides some handy member functions which can be used in +// further derived classes +template +struct optional_operations_base : optional_storage_base { + using optional_storage_base::optional_storage_base; + + void hard_reset() noexcept { + get().~T(); + this->m_has_value = false; + } + + template + void construct(Args &&... args) noexcept { + new (std::addressof(this->m_value)) T(std::forward(args)...); + this->m_has_value = true; + } + + template + void assign(Opt &&rhs) { + if (this->has_value()) { + if (rhs.has_value()) { + this->m_value = std::forward(rhs).get(); + } else { + this->m_value.~T(); + this->m_has_value = false; + } + } + + else if (rhs.has_value()) { + construct(std::forward(rhs).get()); + } + } + + bool has_value() const { return this->m_has_value; } + + TL_OPTIONAL_11_CONSTEXPR T &get() & { return this->m_value; } + TL_OPTIONAL_11_CONSTEXPR const T &get() const & { return this->m_value; } + TL_OPTIONAL_11_CONSTEXPR T &&get() && { return std::move(this->m_value); } +#ifndef TL_OPTIONAL_NO_CONSTRR + constexpr const T &&get() const && { return std::move(this->m_value); } +#endif +}; + +// This class manages conditionally having a trivial copy constructor +// This specialization is for when T is trivially copy constructible +template +struct optional_copy_base : optional_operations_base { + using optional_operations_base::optional_operations_base; +}; + +// This specialization is for when T is not trivially copy constructible +template +struct optional_copy_base : optional_operations_base { + using optional_operations_base::optional_operations_base; + + optional_copy_base() = default; + optional_copy_base(const optional_copy_base &rhs) + : optional_operations_base() { + if (rhs.has_value()) { + this->construct(rhs.get()); + } else { + this->m_has_value = false; + } + } + + optional_copy_base(optional_copy_base &&rhs) = default; + optional_copy_base &operator=(const optional_copy_base &rhs) = default; + optional_copy_base &operator=(optional_copy_base &&rhs) = default; +}; + +// This class manages conditionally having a trivial move constructor +// Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it +// doesn't implement an analogue to std::is_trivially_move_constructible. We +// have to make do with a non-trivial move constructor even if T is trivially +// move constructible +#ifndef TL_OPTIONAL_GCC49 +template ::value> +struct optional_move_base : optional_copy_base { + using optional_copy_base::optional_copy_base; +}; +#else +template +struct optional_move_base; +#endif +template +struct optional_move_base : optional_copy_base { + using optional_copy_base::optional_copy_base; + + optional_move_base() = default; + optional_move_base(const optional_move_base &rhs) = default; + + optional_move_base(optional_move_base &&rhs) noexcept( + std::is_nothrow_move_constructible::value) { + if (rhs.has_value()) { + this->construct(std::move(rhs.get())); + } else { + this->m_has_value = false; + } + } + optional_move_base &operator=(const optional_move_base &rhs) = default; + optional_move_base &operator=(optional_move_base &&rhs) = default; +}; + +// This class manages conditionally having a trivial copy assignment operator +template +struct optional_copy_assign_base : optional_move_base { + using optional_move_base::optional_move_base; +}; + +template +struct optional_copy_assign_base : optional_move_base { + using optional_move_base::optional_move_base; + + optional_copy_assign_base() = default; + optional_copy_assign_base(const optional_copy_assign_base &rhs) = default; + + optional_copy_assign_base(optional_copy_assign_base &&rhs) = default; + optional_copy_assign_base &operator=(const optional_copy_assign_base &rhs) { + this->assign(rhs); + return *this; + } + optional_copy_assign_base &operator=(optional_copy_assign_base &&rhs) = + default; +}; + +// This class manages conditionally having a trivial move assignment operator +// Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it +// doesn't implement an analogue to std::is_trivially_move_assignable. We have +// to make do with a non-trivial move assignment operator even if T is trivially +// move assignable +#ifndef TL_OPTIONAL_GCC49 +template ::value + &&std::is_trivially_move_constructible::value + &&std::is_trivially_move_assignable::value> +struct optional_move_assign_base : optional_copy_assign_base { + using optional_copy_assign_base::optional_copy_assign_base; +}; +#else +template +struct optional_move_assign_base; +#endif + +template +struct optional_move_assign_base : optional_copy_assign_base { + using optional_copy_assign_base::optional_copy_assign_base; + + optional_move_assign_base() = default; + optional_move_assign_base(const optional_move_assign_base &rhs) = default; + + optional_move_assign_base(optional_move_assign_base &&rhs) = default; + + optional_move_assign_base &operator=(const optional_move_assign_base &rhs) = + default; + + optional_move_assign_base & + operator=(optional_move_assign_base &&rhs) noexcept( + std::is_nothrow_move_constructible::value + &&std::is_nothrow_move_assignable::value) { + this->assign(std::move(rhs)); + return *this; + } +}; + +// optional_delete_ctor_base will conditionally delete copy and move +// constructors depending on whether T is copy/move constructible +template ::value, + bool EnableMove = std::is_move_constructible::value> +struct optional_delete_ctor_base { + optional_delete_ctor_base() = default; + optional_delete_ctor_base(const optional_delete_ctor_base &) = default; + optional_delete_ctor_base(optional_delete_ctor_base &&) noexcept = default; + optional_delete_ctor_base &operator=(const optional_delete_ctor_base &) = + default; + optional_delete_ctor_base &operator=( + optional_delete_ctor_base &&) noexcept = default; +}; + +template +struct optional_delete_ctor_base { + optional_delete_ctor_base() = default; + optional_delete_ctor_base(const optional_delete_ctor_base &) = default; + optional_delete_ctor_base(optional_delete_ctor_base &&) noexcept = delete; + optional_delete_ctor_base &operator=(const optional_delete_ctor_base &) = + default; + optional_delete_ctor_base &operator=( + optional_delete_ctor_base &&) noexcept = default; +}; + +template +struct optional_delete_ctor_base { + optional_delete_ctor_base() = default; + optional_delete_ctor_base(const optional_delete_ctor_base &) = delete; + optional_delete_ctor_base(optional_delete_ctor_base &&) noexcept = default; + optional_delete_ctor_base &operator=(const optional_delete_ctor_base &) = + default; + optional_delete_ctor_base &operator=( + optional_delete_ctor_base &&) noexcept = default; +}; + +template +struct optional_delete_ctor_base { + optional_delete_ctor_base() = default; + optional_delete_ctor_base(const optional_delete_ctor_base &) = delete; + optional_delete_ctor_base(optional_delete_ctor_base &&) noexcept = delete; + optional_delete_ctor_base &operator=(const optional_delete_ctor_base &) = + default; + optional_delete_ctor_base &operator=( + optional_delete_ctor_base &&) noexcept = default; +}; + +// optional_delete_assign_base will conditionally delete copy and move +// constructors depending on whether T is copy/move constructible + assignable +template ::value && + std::is_copy_assignable::value), + bool EnableMove = (std::is_move_constructible::value && + std::is_move_assignable::value)> +struct optional_delete_assign_base { + optional_delete_assign_base() = default; + optional_delete_assign_base(const optional_delete_assign_base &) = default; + optional_delete_assign_base(optional_delete_assign_base &&) noexcept = + default; + optional_delete_assign_base &operator=( + const optional_delete_assign_base &) = default; + optional_delete_assign_base &operator=( + optional_delete_assign_base &&) noexcept = default; +}; + +template +struct optional_delete_assign_base { + optional_delete_assign_base() = default; + optional_delete_assign_base(const optional_delete_assign_base &) = default; + optional_delete_assign_base(optional_delete_assign_base &&) noexcept = + default; + optional_delete_assign_base &operator=( + const optional_delete_assign_base &) = default; + optional_delete_assign_base &operator=( + optional_delete_assign_base &&) noexcept = delete; +}; + +template +struct optional_delete_assign_base { + optional_delete_assign_base() = default; + optional_delete_assign_base(const optional_delete_assign_base &) = default; + optional_delete_assign_base(optional_delete_assign_base &&) noexcept = + default; + optional_delete_assign_base &operator=( + const optional_delete_assign_base &) = delete; + optional_delete_assign_base &operator=( + optional_delete_assign_base &&) noexcept = default; +}; + +template +struct optional_delete_assign_base { + optional_delete_assign_base() = default; + optional_delete_assign_base(const optional_delete_assign_base &) = default; + optional_delete_assign_base(optional_delete_assign_base &&) noexcept = + default; + optional_delete_assign_base &operator=( + const optional_delete_assign_base &) = delete; + optional_delete_assign_base &operator=( + optional_delete_assign_base &&) noexcept = delete; +}; + +} // namespace detail + +/// A tag type to represent an empty optional +struct nullopt_t { + struct do_not_use {}; + constexpr explicit nullopt_t(do_not_use, do_not_use) noexcept {} +}; +/// Represents an empty optional +static constexpr nullopt_t nullopt{nullopt_t::do_not_use{}, + nullopt_t::do_not_use{}}; + +class bad_optional_access : public std::exception { + public: + bad_optional_access() = default; + const char *what() const noexcept { return "Optional has no value"; } +}; + +/// An optional object is an object that contains the storage for another +/// object and manages the lifetime of this contained object, if any. The +/// contained object may be initialized after the optional object has been +/// initialized, and may be destroyed before the optional object has been +/// destroyed. The initialization state of the contained object is tracked by +/// the optional object. +template +class optional : private detail::optional_move_assign_base, + private detail::optional_delete_ctor_base, + private detail::optional_delete_assign_base { + using base = detail::optional_move_assign_base; + + static_assert(!std::is_same::value, + "instantiation of optional with in_place_t is ill-formed"); + static_assert(!std::is_same, nullopt_t>::value, + "instantiation of optional with nullopt_t is ill-formed"); + + public: +// The different versions for C++14 and 11 are needed because deduced return +// types are not SFINAE-safe. This provides better support for things like +// generic lambdas. C.f. +// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0826r0.html +#if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ + !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) + /// Carries out some operation which returns an optional on the stored + /// object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR auto and_then(F &&f) & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + + template + TL_OPTIONAL_11_CONSTEXPR auto and_then(F &&f) && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() + ? detail::invoke(std::forward(f), std::move(**this)) + : result(nullopt); + } + + template + constexpr auto and_then(F &&f) const & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + constexpr auto and_then(F &&f) const && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() + ? detail::invoke(std::forward(f), std::move(**this)) + : result(nullopt); + } +#endif +#else + /// Carries out some operation which returns an optional on the stored + /// object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR detail::invoke_result_t and_then(F &&f) & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + + template + TL_OPTIONAL_11_CONSTEXPR detail::invoke_result_t and_then( + F &&f) && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() + ? detail::invoke(std::forward(f), std::move(**this)) + : result(nullopt); + } + + template + constexpr detail::invoke_result_t and_then(F &&f) const & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + constexpr detail::invoke_result_t and_then(F &&f) const && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() + ? detail::invoke(std::forward(f), std::move(**this)) + : result(nullopt); + } +#endif +#endif + +#if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ + !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) + /// Carries out some operation on the stored object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR auto map(F &&f) & { + return optional_map_impl(*this, std::forward(f)); + } + + template + TL_OPTIONAL_11_CONSTEXPR auto map(F &&f) && { + return optional_map_impl(std::move(*this), std::forward(f)); + } + + template + constexpr auto map(F &&f) const & { + return optional_map_impl(*this, std::forward(f)); + } + + template + constexpr auto map(F &&f) const && { + return optional_map_impl(std::move(*this), std::forward(f)); + } +#else + /// Carries out some operation on the stored object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR decltype( + optional_map_impl(std::declval(), std::declval())) + map(F &&f) & { + return optional_map_impl(*this, std::forward(f)); + } + + template + TL_OPTIONAL_11_CONSTEXPR decltype( + optional_map_impl(std::declval(), std::declval())) + map(F &&f) && { + return optional_map_impl(std::move(*this), std::forward(f)); + } + + template + constexpr decltype(optional_map_impl(std::declval(), + std::declval())) + map(F &&f) const & { + return optional_map_impl(*this, std::forward(f)); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + constexpr decltype(optional_map_impl(std::declval(), + std::declval())) + map(F &&f) const && { + return optional_map_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + +#if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ + !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) + /// Carries out some operation on the stored object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR auto transform(F &&f) & { + return optional_map_impl(*this, std::forward(f)); + } + + template + TL_OPTIONAL_11_CONSTEXPR auto transform(F &&f) && { + return optional_map_impl(std::move(*this), std::forward(f)); + } + + template + constexpr auto transform(F &&f) const & { + return optional_map_impl(*this, std::forward(f)); + } + + template + constexpr auto transform(F &&f) const && { + return optional_map_impl(std::move(*this), std::forward(f)); + } +#else + /// Carries out some operation on the stored object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR decltype( + optional_map_impl(std::declval(), std::declval())) + transform(F &&f) & { + return optional_map_impl(*this, std::forward(f)); + } + + template + TL_OPTIONAL_11_CONSTEXPR decltype( + optional_map_impl(std::declval(), std::declval())) + transform(F &&f) && { + return optional_map_impl(std::move(*this), std::forward(f)); + } + + template + constexpr decltype(optional_map_impl(std::declval(), + std::declval())) + transform(F &&f) const & { + return optional_map_impl(*this, std::forward(f)); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + constexpr decltype(optional_map_impl(std::declval(), + std::declval())) + transform(F &&f) const && { + return optional_map_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + + /// Calls `f` if the optional is empty + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) & { + if (has_value()) return *this; + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) & { + return has_value() ? *this : std::forward(f)(); + } + + template * = nullptr> + optional or_else(F &&f) && { + if (has_value()) return std::move(*this); + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) && { + return has_value() ? std::move(*this) : std::forward(f)(); + } + + template * = nullptr> + optional or_else(F &&f) const & { + if (has_value()) return *this; + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) const & { + return has_value() ? *this : std::forward(f)(); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template * = nullptr> + optional or_else(F &&f) const && { + if (has_value()) return std::move(*this); + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional or_else(F &&f) const && { + return has_value() ? std::move(*this) : std::forward(f)(); + } +#endif + + /// Maps the stored value with `f` if there is one, otherwise returns `u`. + template + U map_or(F &&f, U &&u) & { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u); + } + + template + U map_or(F &&f, U &&u) && { + return has_value() + ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u); + } + + template + U map_or(F &&f, U &&u) const & { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + U map_or(F &&f, U &&u) const && { + return has_value() + ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u); + } +#endif + + /// Maps the stored value with `f` if there is one, otherwise calls + /// `u` and returns the result. + template + detail::invoke_result_t map_or_else(F &&f, U &&u) & { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u)(); + } + + template + detail::invoke_result_t map_or_else(F &&f, U &&u) && { + return has_value() + ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u)(); + } + + template + detail::invoke_result_t map_or_else(F &&f, U &&u) const & { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u)(); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + detail::invoke_result_t map_or_else(F &&f, U &&u) const && { + return has_value() + ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u)(); + } +#endif + + /// Returns `u` if `*this` has a value, otherwise an empty optional. + template + constexpr optional::type> conjunction(U &&u) const { + using result = optional>; + return has_value() ? result{u} : result{nullopt}; + } + + /// Returns `rhs` if `*this` is empty, otherwise the current value. + TL_OPTIONAL_11_CONSTEXPR optional disjunction(const optional &rhs) & { + return has_value() ? *this : rhs; + } + + constexpr optional disjunction(const optional &rhs) const & { + return has_value() ? *this : rhs; + } + + TL_OPTIONAL_11_CONSTEXPR optional disjunction(const optional &rhs) && { + return has_value() ? std::move(*this) : rhs; + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + constexpr optional disjunction(const optional &rhs) const && { + return has_value() ? std::move(*this) : rhs; + } +#endif + + TL_OPTIONAL_11_CONSTEXPR optional disjunction(optional &&rhs) & { + return has_value() ? *this : std::move(rhs); + } + + constexpr optional disjunction(optional &&rhs) const & { + return has_value() ? *this : std::move(rhs); + } + + TL_OPTIONAL_11_CONSTEXPR optional disjunction(optional &&rhs) && { + return has_value() ? std::move(*this) : std::move(rhs); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + constexpr optional disjunction(optional &&rhs) const && { + return has_value() ? std::move(*this) : std::move(rhs); + } +#endif + + /// Takes the value out of the optional, leaving it empty + optional take() { + optional ret = std::move(*this); + reset(); + return ret; + } + + using value_type = T; + + /// Constructs an optional that does not contain a value. + constexpr optional() noexcept = default; + + constexpr optional(nullopt_t) noexcept {} + + /// Copy constructor + /// + /// If `rhs` contains a value, the stored value is direct-initialized with + /// it. Otherwise, the constructed optional is empty. + TL_OPTIONAL_11_CONSTEXPR optional(const optional &rhs) = default; + + /// Move constructor + /// + /// If `rhs` contains a value, the stored value is direct-initialized with + /// it. Otherwise, the constructed optional is empty. + TL_OPTIONAL_11_CONSTEXPR optional(optional &&rhs) = default; + + /// Constructs the stored value in-place using the given arguments. + template + constexpr explicit optional( + detail::enable_if_t::value, + in_place_t>, + Args &&... args) + : base(in_place, std::forward(args)...) {} + + template + TL_OPTIONAL_11_CONSTEXPR explicit optional( + detail::enable_if_t &, + Args &&...>::value, + in_place_t>, + std::initializer_list il, + Args &&... args) { + this->construct(il, std::forward(args)...); + } + + /// Constructs the stored value with `u`. + template < + class U = T, + detail::enable_if_t::value> * = nullptr, + detail::enable_forward_value * = nullptr> + constexpr optional(U &&u) : base(in_place, std::forward(u)) {} + + template < + class U = T, + detail::enable_if_t::value> * = nullptr, + detail::enable_forward_value * = nullptr> + constexpr explicit optional(U &&u) : base(in_place, std::forward(u)) {} + + /// Converting copy constructor. + template * = nullptr, + detail::enable_if_t::value> * = + nullptr> + optional(const optional &rhs) { + if (rhs.has_value()) { + this->construct(*rhs); + } + } + + template * = nullptr, + detail::enable_if_t::value> * = + nullptr> + explicit optional(const optional &rhs) { + if (rhs.has_value()) { + this->construct(*rhs); + } + } + + /// Converting move constructor. + template < + class U, + detail::enable_from_other * = nullptr, + detail::enable_if_t::value> * = nullptr> + optional(optional &&rhs) { + if (rhs.has_value()) { + this->construct(std::move(*rhs)); + } + } + + template < + class U, + detail::enable_from_other * = nullptr, + detail::enable_if_t::value> * = nullptr> + explicit optional(optional &&rhs) { + if (rhs.has_value()) { + this->construct(std::move(*rhs)); + } + } + + /// Destroys the stored value if there is one. + ~optional() = default; + + /// Assignment to empty. + /// + /// Destroys the current value if there is one. + optional &operator=(nullopt_t) noexcept { + if (has_value()) { + this->m_value.~T(); + this->m_has_value = false; + } + + return *this; + } + + /// Copy assignment. + /// + /// Copies the value from `rhs` if there is one. Otherwise resets the stored + /// value in `*this`. + optional &operator=(const optional &rhs) = default; + + /// Move assignment. + /// + /// Moves the value from `rhs` if there is one. Otherwise resets the stored + /// value in `*this`. + optional &operator=(optional &&rhs) = default; + + /// Assigns the stored value from `u`, destroying the old value if there was + /// one. + template * = nullptr> + optional &operator=(U &&u) { + if (has_value()) { + this->m_value = std::forward(u); + } else { + this->construct(std::forward(u)); + } + + return *this; + } + + /// Converting copy assignment operator. + /// + /// Copies the value from `rhs` if there is one. Otherwise resets the stored + /// value in `*this`. + template * = nullptr> + optional &operator=(const optional &rhs) { + if (has_value()) { + if (rhs.has_value()) { + this->m_value = *rhs; + } else { + this->hard_reset(); + } + } + + if (rhs.has_value()) { + this->construct(*rhs); + } + + return *this; + } + + // TODO check exception guarantee + /// Converting move assignment operator. + /// + /// Moves the value from `rhs` if there is one. Otherwise resets the stored + /// value in `*this`. + template * = nullptr> + optional &operator=(optional &&rhs) { + if (has_value()) { + if (rhs.has_value()) { + this->m_value = std::move(*rhs); + } else { + this->hard_reset(); + } + } + + if (rhs.has_value()) { + this->construct(std::move(*rhs)); + } + + return *this; + } + + /// Constructs the value in-place, destroying the current one if there is + /// one. + template + T &emplace(Args &&... args) { + static_assert(std::is_constructible::value, + "T must be constructible with Args"); + + *this = nullopt; + this->construct(std::forward(args)...); + return value(); + } + + template + detail::enable_if_t< + std::is_constructible &, Args &&...>::value, + T &> + emplace(std::initializer_list il, Args &&... args) { + *this = nullopt; + this->construct(il, std::forward(args)...); + return value(); + } + + /// Swaps this optional with the other. + /// + /// If neither optionals have a value, nothing happens. + /// If both have a value, the values are swapped. + /// If one has a value, it is moved to the other and the movee is left + /// valueless. + void swap(optional &rhs) noexcept( + std::is_nothrow_move_constructible::value + &&detail::is_nothrow_swappable::value) { + using std::swap; + if (has_value()) { + if (rhs.has_value()) { + swap(**this, *rhs); + } else { + new (std::addressof(rhs.m_value)) T(std::move(this->m_value)); + this->m_value.T::~T(); + } + } else if (rhs.has_value()) { + new (std::addressof(this->m_value)) T(std::move(rhs.m_value)); + rhs.m_value.T::~T(); + } + swap(this->m_has_value, rhs.m_has_value); + } + + /// Returns a pointer to the stored value + constexpr const T *operator->() const { + return std::addressof(this->m_value); + } + + TL_OPTIONAL_11_CONSTEXPR T *operator->() { + return std::addressof(this->m_value); + } + + /// Returns the stored value + TL_OPTIONAL_11_CONSTEXPR T &operator*() & { return this->m_value; } + + constexpr const T &operator*() const & { return this->m_value; } + + TL_OPTIONAL_11_CONSTEXPR T &&operator*() && { + return std::move(this->m_value); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + constexpr const T &&operator*() const && { + return std::move(this->m_value); + } +#endif + + /// Returns whether or not the optional has a value + constexpr bool has_value() const noexcept { return this->m_has_value; } + + constexpr explicit operator bool() const noexcept { + return this->m_has_value; + } + + /// Returns the contained value if there is one, otherwise throws + /// bad_optional_access + TL_OPTIONAL_11_CONSTEXPR T &value() & { + if (has_value()) return this->m_value; + throw bad_optional_access(); + } + TL_OPTIONAL_11_CONSTEXPR const T &value() const & { + if (has_value()) return this->m_value; + throw bad_optional_access(); + } + TL_OPTIONAL_11_CONSTEXPR T &&value() && { + if (has_value()) return std::move(this->m_value); + throw bad_optional_access(); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + TL_OPTIONAL_11_CONSTEXPR const T &&value() const && { + if (has_value()) return std::move(this->m_value); + throw bad_optional_access(); + } +#endif + + /// Returns the stored value if there is one, otherwise returns `u` + template + constexpr T value_or(U &&u) const & { + static_assert(std::is_copy_constructible::value && + std::is_convertible::value, + "T must be copy constructible and convertible from U"); + return has_value() ? **this : static_cast(std::forward(u)); + } + + template + TL_OPTIONAL_11_CONSTEXPR T value_or(U &&u) && { + static_assert(std::is_move_constructible::value && + std::is_convertible::value, + "T must be move constructible and convertible from U"); + return has_value() ? **this : static_cast(std::forward(u)); + } + + /// Destroys the stored value if one exists, making the optional empty + void reset() noexcept { + if (has_value()) { + this->m_value.~T(); + this->m_has_value = false; + } + } +}; // namespace tl + +/// Compares two optional objects +template +inline constexpr bool operator==(const optional &lhs, + const optional &rhs) { + return lhs.has_value() == rhs.has_value() && + (!lhs.has_value() || *lhs == *rhs); +} +template +inline constexpr bool operator!=(const optional &lhs, + const optional &rhs) { + return lhs.has_value() != rhs.has_value() || + (lhs.has_value() && *lhs != *rhs); +} +template +inline constexpr bool operator<(const optional &lhs, + const optional &rhs) { + return rhs.has_value() && (!lhs.has_value() || *lhs < *rhs); +} +template +inline constexpr bool operator>(const optional &lhs, + const optional &rhs) { + return lhs.has_value() && (!rhs.has_value() || *lhs > *rhs); +} +template +inline constexpr bool operator<=(const optional &lhs, + const optional &rhs) { + return !lhs.has_value() || (rhs.has_value() && *lhs <= *rhs); +} +template +inline constexpr bool operator>=(const optional &lhs, + const optional &rhs) { + return !rhs.has_value() || (lhs.has_value() && *lhs >= *rhs); +} + +/// Compares an optional to a `nullopt` +template +inline constexpr bool operator==(const optional &lhs, nullopt_t) noexcept { + return !lhs.has_value(); +} +template +inline constexpr bool operator==(nullopt_t, const optional &rhs) noexcept { + return !rhs.has_value(); +} +template +inline constexpr bool operator!=(const optional &lhs, nullopt_t) noexcept { + return lhs.has_value(); +} +template +inline constexpr bool operator!=(nullopt_t, const optional &rhs) noexcept { + return rhs.has_value(); +} +template +inline constexpr bool operator<(const optional &, nullopt_t) noexcept { + return false; +} +template +inline constexpr bool operator<(nullopt_t, const optional &rhs) noexcept { + return rhs.has_value(); +} +template +inline constexpr bool operator<=(const optional &lhs, nullopt_t) noexcept { + return !lhs.has_value(); +} +template +inline constexpr bool operator<=(nullopt_t, const optional &) noexcept { + return true; +} +template +inline constexpr bool operator>(const optional &lhs, nullopt_t) noexcept { + return lhs.has_value(); +} +template +inline constexpr bool operator>(nullopt_t, const optional &) noexcept { + return false; +} +template +inline constexpr bool operator>=(const optional &, nullopt_t) noexcept { + return true; +} +template +inline constexpr bool operator>=(nullopt_t, const optional &rhs) noexcept { + return !rhs.has_value(); +} + +/// Compares the optional with a value. +template +inline constexpr bool operator==(const optional &lhs, const U &rhs) { + return lhs.has_value() ? *lhs == rhs : false; +} +template +inline constexpr bool operator==(const U &lhs, const optional &rhs) { + return rhs.has_value() ? lhs == *rhs : false; +} +template +inline constexpr bool operator!=(const optional &lhs, const U &rhs) { + return lhs.has_value() ? *lhs != rhs : true; +} +template +inline constexpr bool operator!=(const U &lhs, const optional &rhs) { + return rhs.has_value() ? lhs != *rhs : true; +} +template +inline constexpr bool operator<(const optional &lhs, const U &rhs) { + return lhs.has_value() ? *lhs < rhs : true; +} +template +inline constexpr bool operator<(const U &lhs, const optional &rhs) { + return rhs.has_value() ? lhs < *rhs : false; +} +template +inline constexpr bool operator<=(const optional &lhs, const U &rhs) { + return lhs.has_value() ? *lhs <= rhs : true; +} +template +inline constexpr bool operator<=(const U &lhs, const optional &rhs) { + return rhs.has_value() ? lhs <= *rhs : false; +} +template +inline constexpr bool operator>(const optional &lhs, const U &rhs) { + return lhs.has_value() ? *lhs > rhs : false; +} +template +inline constexpr bool operator>(const U &lhs, const optional &rhs) { + return rhs.has_value() ? lhs > *rhs : true; +} +template +inline constexpr bool operator>=(const optional &lhs, const U &rhs) { + return lhs.has_value() ? *lhs >= rhs : false; +} +template +inline constexpr bool operator>=(const U &lhs, const optional &rhs) { + return rhs.has_value() ? lhs >= *rhs : true; +} + +template ::value> * = nullptr, + detail::enable_if_t::value> * = nullptr> +void swap(optional &lhs, + optional &rhs) noexcept(noexcept(lhs.swap(rhs))) { + return lhs.swap(rhs); +} + +namespace detail { +struct i_am_secret {}; +} // namespace detail + +template ::value, + detail::decay_t, + T>> +inline constexpr optional make_optional(U &&v) { + return optional(std::forward(v)); +} + +template +inline constexpr optional make_optional(Args &&... args) { + return optional(in_place, std::forward(args)...); +} +template +inline constexpr optional make_optional(std::initializer_list il, + Args &&... args) { + return optional(in_place, il, std::forward(args)...); +} + +#if __cplusplus >= 201703L +template +optional(T)->optional; +#endif + +/// \exclude +namespace detail { +#ifdef TL_OPTIONAL_CXX14 +template (), + *std::declval())), + detail::enable_if_t::value> * = nullptr> +constexpr auto optional_map_impl(Opt &&opt, F &&f) { + return opt.has_value() + ? detail::invoke(std::forward(f), *std::forward(opt)) + : optional(nullopt); +} + +template (), + *std::declval())), + detail::enable_if_t::value> * = nullptr> +auto optional_map_impl(Opt &&opt, F &&f) { + if (opt.has_value()) { + detail::invoke(std::forward(f), *std::forward(opt)); + return make_optional(monostate{}); + } + + return optional(nullopt); +} +#else +template (), + *std::declval())), + detail::enable_if_t::value> * = nullptr> + +constexpr auto optional_map_impl(Opt &&opt, F &&f) -> optional { + return opt.has_value() + ? detail::invoke(std::forward(f), *std::forward(opt)) + : optional(nullopt); +} + +template (), + *std::declval())), + detail::enable_if_t::value> * = nullptr> + +auto optional_map_impl(Opt &&opt, F &&f) -> optional { + if (opt.has_value()) { + detail::invoke(std::forward(f), *std::forward(opt)); + return monostate{}; + } + + return nullopt; +} +#endif +} // namespace detail + +/// Specialization for when `T` is a reference. `optional` acts similarly +/// to a `T*`, but provides more operations and shows intent more clearly. +template +class optional { + public: +// The different versions for C++14 and 11 are needed because deduced return +// types are not SFINAE-safe. This provides better support for things like +// generic lambdas. C.f. +// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0826r0.html +#if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ + !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) + + /// Carries out some operation which returns an optional on the stored + /// object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR auto and_then(F &&f) & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + + template + TL_OPTIONAL_11_CONSTEXPR auto and_then(F &&f) && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + + template + constexpr auto and_then(F &&f) const & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + constexpr auto and_then(F &&f) const && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } +#endif +#else + /// Carries out some operation which returns an optional on the stored + /// object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR detail::invoke_result_t and_then(F &&f) & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + + template + TL_OPTIONAL_11_CONSTEXPR detail::invoke_result_t and_then( + F &&f) && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + + template + constexpr detail::invoke_result_t and_then(F &&f) const & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + constexpr detail::invoke_result_t and_then(F &&f) const && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } +#endif +#endif + +#if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ + !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) + /// Carries out some operation on the stored object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR auto map(F &&f) & { + return detail::optional_map_impl(*this, std::forward(f)); + } + + template + TL_OPTIONAL_11_CONSTEXPR auto map(F &&f) && { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } + + template + constexpr auto map(F &&f) const & { + return detail::optional_map_impl(*this, std::forward(f)); + } + + template + constexpr auto map(F &&f) const && { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } +#else + /// Carries out some operation on the stored object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR decltype(detail::optional_map_impl( + std::declval(), std::declval())) + map(F &&f) & { + return detail::optional_map_impl(*this, std::forward(f)); + } + + template + TL_OPTIONAL_11_CONSTEXPR decltype(detail::optional_map_impl( + std::declval(), std::declval())) + map(F &&f) && { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } + + template + constexpr decltype(detail::optional_map_impl( + std::declval(), std::declval())) + map(F &&f) const & { + return detail::optional_map_impl(*this, std::forward(f)); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + constexpr decltype(detail::optional_map_impl( + std::declval(), std::declval())) + map(F &&f) const && { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + +#if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ + !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) + /// Carries out some operation on the stored object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR auto transform(F &&f) & { + return detail::optional_map_impl(*this, std::forward(f)); + } + + template + TL_OPTIONAL_11_CONSTEXPR auto transform(F &&f) && { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } + + template + constexpr auto transform(F &&f) const & { + return detail::optional_map_impl(*this, std::forward(f)); + } + + template + constexpr auto transform(F &&f) const && { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } +#else + /// Carries out some operation on the stored object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR decltype(detail::optional_map_impl( + std::declval(), std::declval())) + transform(F &&f) & { + return detail::optional_map_impl(*this, std::forward(f)); + } + + /// \group map + /// \synopsis template auto transform(F &&f) &&; + template + TL_OPTIONAL_11_CONSTEXPR decltype(detail::optional_map_impl( + std::declval(), std::declval())) + transform(F &&f) && { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } + + template + constexpr decltype(detail::optional_map_impl( + std::declval(), std::declval())) + transform(F &&f) const & { + return detail::optional_map_impl(*this, std::forward(f)); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + constexpr decltype(detail::optional_map_impl( + std::declval(), std::declval())) + transform(F &&f) const && { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + + /// Calls `f` if the optional is empty + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) & { + if (has_value()) return *this; + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) & { + return has_value() ? *this : std::forward(f)(); + } + + template * = nullptr> + optional or_else(F &&f) && { + if (has_value()) return std::move(*this); + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) && { + return has_value() ? std::move(*this) : std::forward(f)(); + } + + template * = nullptr> + optional or_else(F &&f) const & { + if (has_value()) return *this; + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) const & { + return has_value() ? *this : std::forward(f)(); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template * = nullptr> + optional or_else(F &&f) const && { + if (has_value()) return std::move(*this); + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional or_else(F &&f) const && { + return has_value() ? std::move(*this) : std::forward(f)(); + } +#endif + + /// Maps the stored value with `f` if there is one, otherwise returns `u` + template + U map_or(F &&f, U &&u) & { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u); + } + + template + U map_or(F &&f, U &&u) && { + return has_value() + ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u); + } + + template + U map_or(F &&f, U &&u) const & { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + U map_or(F &&f, U &&u) const && { + return has_value() + ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u); + } +#endif + + /// Maps the stored value with `f` if there is one, otherwise calls + /// `u` and returns the result. + template + detail::invoke_result_t map_or_else(F &&f, U &&u) & { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u)(); + } + + template + detail::invoke_result_t map_or_else(F &&f, U &&u) && { + return has_value() + ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u)(); + } + + template + detail::invoke_result_t map_or_else(F &&f, U &&u) const & { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u)(); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + detail::invoke_result_t map_or_else(F &&f, U &&u) const && { + return has_value() + ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u)(); + } +#endif + + /// Returns `u` if `*this` has a value, otherwise an empty optional. + template + constexpr optional::type> conjunction(U &&u) const { + using result = optional>; + return has_value() ? result{u} : result{nullopt}; + } + + /// Returns `rhs` if `*this` is empty, otherwise the current value. + TL_OPTIONAL_11_CONSTEXPR optional disjunction(const optional &rhs) & { + return has_value() ? *this : rhs; + } + + constexpr optional disjunction(const optional &rhs) const & { + return has_value() ? *this : rhs; + } + + TL_OPTIONAL_11_CONSTEXPR optional disjunction(const optional &rhs) && { + return has_value() ? std::move(*this) : rhs; + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + constexpr optional disjunction(const optional &rhs) const && { + return has_value() ? std::move(*this) : rhs; + } +#endif + + TL_OPTIONAL_11_CONSTEXPR optional disjunction(optional &&rhs) & { + return has_value() ? *this : std::move(rhs); + } + + constexpr optional disjunction(optional &&rhs) const & { + return has_value() ? *this : std::move(rhs); + } + + TL_OPTIONAL_11_CONSTEXPR optional disjunction(optional &&rhs) && { + return has_value() ? std::move(*this) : std::move(rhs); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + constexpr optional disjunction(optional &&rhs) const && { + return has_value() ? std::move(*this) : std::move(rhs); + } +#endif + + /// Takes the value out of the optional, leaving it empty + optional take() { + optional ret = std::move(*this); + reset(); + return ret; + } + + using value_type = T &; + + /// Constructs an optional that does not contain a value. + constexpr optional() noexcept : m_value(nullptr) {} + + constexpr optional(nullopt_t) noexcept : m_value(nullptr) {} + + /// Copy constructor + /// + /// If `rhs` contains a value, the stored value is direct-initialized with + /// it. Otherwise, the constructed optional is empty. + TL_OPTIONAL_11_CONSTEXPR optional(const optional &rhs) noexcept = default; + + /// Move constructor + /// + /// If `rhs` contains a value, the stored value is direct-initialized with + /// it. Otherwise, the constructed optional is empty. + TL_OPTIONAL_11_CONSTEXPR optional(optional &&rhs) = default; + + /// Constructs the stored value with `u`. + template >::value> * = nullptr> + constexpr optional(U &&u) noexcept : m_value(std::addressof(u)) { + static_assert(std::is_lvalue_reference::value, + "U must be an lvalue"); + } + + template + constexpr explicit optional(const optional &rhs) noexcept + : optional(*rhs) {} + + /// No-op + ~optional() = default; + + /// Assignment to empty. + /// + /// Destroys the current value if there is one. + optional &operator=(nullopt_t) noexcept { + m_value = nullptr; + return *this; + } + + /// Copy assignment. + /// + /// Rebinds this optional to the referee of `rhs` if there is one. Otherwise + /// resets the stored value in `*this`. + optional &operator=(const optional &rhs) = default; + + /// Rebinds this optional to `u`. + template >::value> * = nullptr> + optional &operator=(U &&u) { + static_assert(std::is_lvalue_reference::value, + "U must be an lvalue"); + m_value = std::addressof(u); + return *this; + } + + /// Converting copy assignment operator. + /// + /// Rebinds this optional to the referee of `rhs` if there is one. Otherwise + /// resets the stored value in `*this`. + template + optional &operator=(const optional &rhs) noexcept { + m_value = std::addressof(rhs.value()); + return *this; + } + + /// Rebinds this optional to `u`. + template >::value> * = nullptr> + optional &emplace(U &&u) noexcept { + return *this = std::forward(u); + } + + void swap(optional &rhs) noexcept { std::swap(m_value, rhs.m_value); } + + /// Returns a pointer to the stored value + constexpr const T *operator->() const noexcept { return m_value; } + + TL_OPTIONAL_11_CONSTEXPR T *operator->() noexcept { return m_value; } + + /// Returns the stored value + TL_OPTIONAL_11_CONSTEXPR T &operator*() noexcept { return *m_value; } + + constexpr const T &operator*() const noexcept { return *m_value; } + + constexpr bool has_value() const noexcept { return m_value != nullptr; } + + constexpr explicit operator bool() const noexcept { + return m_value != nullptr; + } + + /// Returns the contained value if there is one, otherwise throws + /// bad_optional_access + TL_OPTIONAL_11_CONSTEXPR T &value() { + if (has_value()) return *m_value; + throw bad_optional_access(); + } + TL_OPTIONAL_11_CONSTEXPR const T &value() const { + if (has_value()) return *m_value; + throw bad_optional_access(); + } + + /// Returns the stored value if there is one, otherwise returns `u` + template + constexpr T value_or(U &&u) const &noexcept { + static_assert(std::is_copy_constructible::value && + std::is_convertible::value, + "T must be copy constructible and convertible from U"); + return has_value() ? **this : static_cast(std::forward(u)); + } + + /// \group value_or + template + TL_OPTIONAL_11_CONSTEXPR T value_or(U &&u) && noexcept { + static_assert(std::is_move_constructible::value && + std::is_convertible::value, + "T must be move constructible and convertible from U"); + return has_value() ? **this : static_cast(std::forward(u)); + } + + /// Destroys the stored value if one exists, making the optional empty + void reset() noexcept { m_value = nullptr; } + + private: + T *m_value; +}; // namespace tl + + +} // namespace tl + +namespace std { +// TODO SFINAE +template +struct hash> { + ::std::size_t operator()(const tl::optional &o) const { + if (!o.has_value()) return 0; + + return std::hash>()(*o); + } +}; +} // namespace std + +#endif diff --git a/audio/paddleaudio/src/pybind/kaldi/feature_common.h b/audio/paddleaudio/src/pybind/kaldi/feature_common.h new file mode 100644 index 00000000..05522bb7 --- /dev/null +++ b/audio/paddleaudio/src/pybind/kaldi/feature_common.h @@ -0,0 +1,49 @@ +// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "pybind11/pybind11.h" +#include "pybind11/numpy.h" +#include "feat/feature-window.h" + +namespace paddleaudio { +namespace kaldi { + +namespace py = pybind11; + +template +class StreamingFeatureTpl { + public: + typedef typename F::Options Options; + StreamingFeatureTpl(const Options& opts); + bool ComputeFeature(const ::kaldi::VectorBase<::kaldi::BaseFloat>& wav, + ::kaldi::Vector<::kaldi::BaseFloat>* feats); + void Reset() { remained_wav_.Resize(0); } + + int Dim() { return computer_.Dim(); } + + private: + bool Compute(const ::kaldi::Vector<::kaldi::BaseFloat>& waves, + ::kaldi::Vector<::kaldi::BaseFloat>* feats); + Options opts_; + ::kaldi::FeatureWindowFunction window_function_; + ::kaldi::Vector<::kaldi::BaseFloat> remained_wav_; + F computer_; +}; + +} // namespace kaldi +} // namespace ppspeech + +#include "feature_common_inl.h" diff --git a/audio/paddleaudio/src/pybind/kaldi/feature_common_inl.h b/audio/paddleaudio/src/pybind/kaldi/feature_common_inl.h new file mode 100644 index 00000000..c894b977 --- /dev/null +++ b/audio/paddleaudio/src/pybind/kaldi/feature_common_inl.h @@ -0,0 +1,93 @@ +// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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/kaldi-common.h" + +namespace paddleaudio { +namespace kaldi { + +template +StreamingFeatureTpl::StreamingFeatureTpl(const Options& opts) + : opts_(opts), computer_(opts), window_function_(opts.frame_opts) { + // window_function_(computer_.GetFrameOptions()) { the opt set to zero +} + +template +bool StreamingFeatureTpl::ComputeFeature( + const ::kaldi::VectorBase<::kaldi::BaseFloat>& wav, + ::kaldi::Vector<::kaldi::BaseFloat>* feats) { + // append remaned waves + ::kaldi::int32 wav_len = wav.Dim(); + if (wav_len == 0) return false; + ::kaldi::int32 left_len = remained_wav_.Dim(); + ::kaldi::Vector<::kaldi::BaseFloat> waves(left_len + wav_len); + waves.Range(0, left_len).CopyFromVec(remained_wav_); + waves.Range(left_len, wav_len).CopyFromVec(wav); + + // cache remaned waves + ::kaldi::FrameExtractionOptions frame_opts = computer_.GetFrameOptions(); + ::kaldi::int32 num_frames = ::kaldi::NumFrames(waves.Dim(), frame_opts); + ::kaldi::int32 frame_shift = frame_opts.WindowShift(); + ::kaldi::int32 left_samples = waves.Dim() - frame_shift * num_frames; + remained_wav_.Resize(left_samples); + remained_wav_.CopyFromVec( + waves.Range(frame_shift * num_frames, left_samples)); + + // compute speech feature + Compute(waves, feats); + return true; +} + +// Compute feat +template +bool StreamingFeatureTpl::Compute( + const ::kaldi::Vector<::kaldi::BaseFloat>& waves, + ::kaldi::Vector<::kaldi::BaseFloat>* feats) { + ::kaldi::BaseFloat vtln_warp = 1.0; + const ::kaldi::FrameExtractionOptions& frame_opts = + computer_.GetFrameOptions(); + ::kaldi::int32 num_samples = waves.Dim(); + ::kaldi::int32 frame_length = frame_opts.WindowSize(); + ::kaldi::int32 sample_rate = frame_opts.samp_freq; + if (num_samples < frame_length) { + return false; + } + + ::kaldi::int32 num_frames = ::kaldi::NumFrames(num_samples, frame_opts); + feats->Resize(num_frames * Dim()); + + ::kaldi::Vector<::kaldi::BaseFloat> window; + bool need_raw_log_energy = computer_.NeedRawLogEnergy(); + for (::kaldi::int32 frame = 0; frame < num_frames; frame++) { + ::kaldi::BaseFloat raw_log_energy = 0.0; + ::kaldi::ExtractWindow(0, + waves, + frame, + frame_opts, + window_function_, + &window, + need_raw_log_energy ? &raw_log_energy : NULL); + + ::kaldi::Vector<::kaldi::BaseFloat> this_feature(computer_.Dim(), + ::kaldi::kUndefined); + computer_.Compute(raw_log_energy, vtln_warp, &window, &this_feature); + ::kaldi::SubVector<::kaldi::BaseFloat> output_row( + feats->Data() + frame * Dim(), Dim()); + output_row.CopyFromVec(this_feature); + } + return true; +} + +} // namespace kaldi +} // namespace paddleaudio diff --git a/audio/paddleaudio/src/pybind/kaldi/kaldi_feature.cc b/audio/paddleaudio/src/pybind/kaldi/kaldi_feature.cc new file mode 100644 index 00000000..40e3786e --- /dev/null +++ b/audio/paddleaudio/src/pybind/kaldi/kaldi_feature.cc @@ -0,0 +1,75 @@ +// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 "paddleaudio/src/pybind/kaldi/kaldi_feature.h" +#include "feat/pitch-functions.h" + +namespace paddleaudio { +namespace kaldi { + +bool InitFbank( + ::kaldi::FrameExtractionOptions frame_opts, + ::kaldi::MelBanksOptions mel_opts, + FbankOptions fbank_opts) { + ::kaldi::FbankOptions opts; + opts.frame_opts = frame_opts; + opts.mel_opts = mel_opts; + opts.use_energy = fbank_opts.use_energy; + opts.energy_floor = fbank_opts.energy_floor; + opts.raw_energy = fbank_opts.raw_energy; + opts.htk_compat = fbank_opts.htk_compat; + opts.use_log_fbank = fbank_opts.use_log_fbank; + opts.use_power = fbank_opts.use_power; + paddleaudio::kaldi::KaldiFeatureWrapper::GetInstance()->InitFbank(opts); + return true; +} + +py::array_t ComputeFbankStreaming(const py::array_t& wav) { + return paddleaudio::kaldi::KaldiFeatureWrapper::GetInstance()->ComputeFbank( + wav); +} + +py::array_t ComputeFbank( + ::kaldi::FrameExtractionOptions frame_opts, + ::kaldi::MelBanksOptions mel_opts, + FbankOptions fbank_opts, + const py::array_t& wav) { + InitFbank(frame_opts, mel_opts, fbank_opts); + py::array_t result = ComputeFbankStreaming(wav); + paddleaudio::kaldi::KaldiFeatureWrapper::GetInstance()->ResetFbank(); + return result; +} + +void ResetFbank() { + paddleaudio::kaldi::KaldiFeatureWrapper::GetInstance()->ResetFbank(); +} + +py::array_t ComputeKaldiPitch( + const ::kaldi::PitchExtractionOptions& opts, + const py::array_t& wav) { + py::buffer_info info = wav.request(); + ::kaldi::SubVector<::kaldi::BaseFloat> input_wav((float*)info.ptr, info.size); + + ::kaldi::Matrix<::kaldi::BaseFloat> features; + ::kaldi::ComputeKaldiPitch(opts, input_wav, &features); + auto result = py::array_t({features.NumRows(), features.NumCols()}); + for (int row_idx = 0; row_idx < features.NumRows(); ++row_idx) { + std::memcpy(result.mutable_data(row_idx), features.Row(row_idx).Data(), + sizeof(float)*features.NumCols()); + } + return result; +} + +} // namespace kaldi +} // namespace paddleaudio diff --git a/audio/paddleaudio/src/pybind/kaldi/kaldi_feature.h b/audio/paddleaudio/src/pybind/kaldi/kaldi_feature.h new file mode 100644 index 00000000..e059c52c --- /dev/null +++ b/audio/paddleaudio/src/pybind/kaldi/kaldi_feature.h @@ -0,0 +1,64 @@ +// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include +#include + +#include "paddleaudio/src/pybind/kaldi/kaldi_feature_wrapper.h" +#include "feat/pitch-functions.h" + +namespace py = pybind11; + +namespace paddleaudio { +namespace kaldi { + +struct FbankOptions{ + bool use_energy; // append an extra dimension with energy to the filter banks + float energy_floor; + bool raw_energy; // If true, compute energy before preemphasis and windowing + bool htk_compat; // If true, put energy last (if using energy) + bool use_log_fbank; // if true (default), produce log-filterbank, else linear + bool use_power; + FbankOptions(): use_energy(false), + energy_floor(0.0), + raw_energy(true), + htk_compat(false), + use_log_fbank(true), + use_power(true) {} +}; + +bool InitFbank( + ::kaldi::FrameExtractionOptions frame_opts, + ::kaldi::MelBanksOptions mel_opts, + FbankOptions fbank_opts); + +py::array_t ComputeFbank( + ::kaldi::FrameExtractionOptions frame_opts, + ::kaldi::MelBanksOptions mel_opts, + FbankOptions fbank_opts, + const py::array_t& wav); + +py::array_t ComputeFbankStreaming(const py::array_t& wav); + +void ResetFbank(); + +py::array_t ComputeKaldiPitch( + const ::kaldi::PitchExtractionOptions& opts, + const py::array_t& wav); + +} // namespace kaldi +} // namespace paddleaudio diff --git a/audio/paddleaudio/src/pybind/kaldi/kaldi_feature_wrapper.cc b/audio/paddleaudio/src/pybind/kaldi/kaldi_feature_wrapper.cc new file mode 100644 index 00000000..79558046 --- /dev/null +++ b/audio/paddleaudio/src/pybind/kaldi/kaldi_feature_wrapper.cc @@ -0,0 +1,51 @@ +// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT 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 "paddleaudio/src/pybind/kaldi/kaldi_feature_wrapper.h" + +namespace paddleaudio { +namespace kaldi { + +KaldiFeatureWrapper* KaldiFeatureWrapper::GetInstance() { + static KaldiFeatureWrapper instance; + return &instance; +} + +bool KaldiFeatureWrapper::InitFbank(::kaldi::FbankOptions opts) { + fbank_.reset(new Fbank(opts)); + return true; +} + +py::array_t KaldiFeatureWrapper::ComputeFbank( + const py::array_t wav) { + py::buffer_info info = wav.request(); + ::kaldi::SubVector<::kaldi::BaseFloat> input_wav((float*)info.ptr, info.size); + + ::kaldi::Vector<::kaldi::BaseFloat> feats; + bool flag = fbank_->ComputeFeature(input_wav, &feats); + if (flag == false || feats.Dim() == 0) return py::array_t(); + auto result = py::array_t(feats.Dim()); + py::buffer_info xs = result.request(); + std::cout << std::endl; + float* res_ptr = (float*)xs.ptr; + for (int idx = 0; idx < feats.Dim(); ++idx) { + *res_ptr = feats(idx); + res_ptr++; + } + + return result.reshape({feats.Dim() / Dim(), Dim()}); +} + +} // namesapce kaldi +} // namespace paddleaudio diff --git a/audio/paddleaudio/src/pybind/kaldi/kaldi_feature_wrapper.h b/audio/paddleaudio/src/pybind/kaldi/kaldi_feature_wrapper.h new file mode 100644 index 00000000..bee1eee0 --- /dev/null +++ b/audio/paddleaudio/src/pybind/kaldi/kaldi_feature_wrapper.h @@ -0,0 +1,40 @@ +// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "base/kaldi-common.h" +#include "feat/feature-fbank.h" + +#include "paddleaudio/src/pybind/kaldi/feature_common.h" + +namespace paddleaudio { +namespace kaldi { + +typedef StreamingFeatureTpl<::kaldi::FbankComputer> Fbank; + +class KaldiFeatureWrapper { + public: + static KaldiFeatureWrapper* GetInstance(); + bool InitFbank(::kaldi::FbankOptions opts); + py::array_t ComputeFbank(const py::array_t wav); + int Dim() { return fbank_->Dim(); } + void ResetFbank() { fbank_->Reset(); } + + private: + std::unique_ptr fbank_; +}; + +} // namespace kaldi +} // namespace paddleaudio diff --git a/audio/paddleaudio/src/pybind/pybind.cpp b/audio/paddleaudio/src/pybind/pybind.cpp new file mode 100644 index 00000000..c4dfa8d5 --- /dev/null +++ b/audio/paddleaudio/src/pybind/pybind.cpp @@ -0,0 +1,148 @@ +// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. + +#include "paddleaudio/src/pybind/kaldi/kaldi_feature.h" +#include "paddleaudio/third_party/kaldi/feat/feature-fbank.h" + +#ifdef INCLUDE_SOX +#include "paddleaudio/src/pybind/sox/io.h" +#include "paddleaudio/src/pybind/sox/effects.h" +#endif + +#include +#include + +// `tl::optional` +#ifdef INCLUDE_SOX +namespace pybind11 { namespace detail { + template + struct type_caster> : optional_caster> {}; +}} +#endif + +PYBIND11_MODULE(_paddleaudio, m) { +#ifdef INCLUDE_SOX + m.def("get_info_file", + &paddleaudio::sox_io::get_info_file, + "Get metadata of audio file."); + // support obj later + m.def("get_info_fileobj", + &paddleaudio::sox_io::get_info_fileobj, + "Get metadata of audio in file object."); + m.def("load_audio_fileobj", + &paddleaudio::sox_io::load_audio_fileobj, + "Load audio from file object."); + m.def("save_audio_fileobj", + &paddleaudio::sox_io::save_audio_fileobj, + "Save audio to file obj."); + + // sox io + m.def("sox_io_get_info", &paddleaudio::sox_io::get_info_file); + m.def( + "sox_io_load_audio_file", + &paddleaudio::sox_io::load_audio_file); + m.def( + "sox_io_save_audio_file", + &paddleaudio::sox_io::save_audio_file); + + // sox utils + m.def("sox_utils_set_seed", &paddleaudio::sox_utils::set_seed); + m.def( + "sox_utils_set_verbosity", + &paddleaudio::sox_utils::set_verbosity); + m.def( + "sox_utils_set_use_threads", + &paddleaudio::sox_utils::set_use_threads); + m.def( + "sox_utils_set_buffer_size", + &paddleaudio::sox_utils::set_buffer_size); + m.def( + "sox_utils_list_effects", + &paddleaudio::sox_utils::list_effects); + m.def( + "sox_utils_list_read_formats", + &paddleaudio::sox_utils::list_read_formats); + m.def( + "sox_utils_list_write_formats", + &paddleaudio::sox_utils::list_write_formats); + m.def( + "sox_utils_get_buffer_size", + &paddleaudio::sox_utils::get_buffer_size); + + // effect + m.def("apply_effects_fileobj", + &paddleaudio::sox_effects::apply_effects_fileobj, + "Decode audio data from file-like obj and apply effects."); + m.def("sox_effects_initialize_sox_effects", + &paddleaudio::sox_effects::initialize_sox_effects); + m.def( + "sox_effects_shutdown_sox_effects", + &paddleaudio::sox_effects::shutdown_sox_effects); + m.def( + "sox_effects_apply_effects_tensor", + &paddleaudio::sox_effects::apply_effects_tensor); + m.def( + "sox_effects_apply_effects_file", + &paddleaudio::sox_effects::apply_effects_file); +#endif + +#ifdef INCLUDE_KALDI + m.def("ComputeFbank", &paddleaudio::kaldi::ComputeFbank, "compute fbank"); + py::class_(m, "PitchExtractionOptions") + .def(py::init<>()) + .def_readwrite("samp_freq", &kaldi::PitchExtractionOptions::samp_freq) + .def_readwrite("frame_shift_ms", &kaldi::PitchExtractionOptions::frame_shift_ms) + .def_readwrite("frame_length_ms", &kaldi::PitchExtractionOptions::frame_length_ms) + .def_readwrite("preemph_coeff", &kaldi::PitchExtractionOptions::preemph_coeff) + .def_readwrite("min_f0", &kaldi::PitchExtractionOptions::min_f0) + .def_readwrite("max_f0", &kaldi::PitchExtractionOptions::max_f0) + .def_readwrite("soft_min_f0", &kaldi::PitchExtractionOptions::soft_min_f0) + .def_readwrite("penalty_factor", &kaldi::PitchExtractionOptions::penalty_factor) + .def_readwrite("lowpass_cutoff", &kaldi::PitchExtractionOptions::lowpass_cutoff) + .def_readwrite("resample_freq", &kaldi::PitchExtractionOptions::resample_freq) + .def_readwrite("delta_pitch", &kaldi::PitchExtractionOptions::delta_pitch) + .def_readwrite("nccf_ballast", &kaldi::PitchExtractionOptions::nccf_ballast) + .def_readwrite("lowpass_filter_width", &kaldi::PitchExtractionOptions::lowpass_filter_width) + .def_readwrite("upsample_filter_width", &kaldi::PitchExtractionOptions::upsample_filter_width) + .def_readwrite("max_frames_latency", &kaldi::PitchExtractionOptions::max_frames_latency) + .def_readwrite("frames_per_chunk", &kaldi::PitchExtractionOptions::frames_per_chunk) + .def_readwrite("simulate_first_pass_online", &kaldi::PitchExtractionOptions::simulate_first_pass_online) + .def_readwrite("recompute_frame", &kaldi::PitchExtractionOptions::recompute_frame) + .def_readwrite("nccf_ballast_online", &kaldi::PitchExtractionOptions::nccf_ballast_online) + .def_readwrite("snip_edges", &kaldi::PitchExtractionOptions::snip_edges); + m.def("ComputeKaldiPitch", &paddleaudio::kaldi::ComputeKaldiPitch, "compute kaldi pitch"); + py::class_(m, "FrameExtractionOptions") + .def(py::init<>()) + .def_readwrite("samp_freq", &kaldi::FrameExtractionOptions::samp_freq) + .def_readwrite("frame_shift_ms", &kaldi::FrameExtractionOptions::frame_shift_ms) + .def_readwrite("frame_length_ms", &kaldi::FrameExtractionOptions::frame_length_ms) + .def_readwrite("dither", &kaldi::FrameExtractionOptions::dither) + .def_readwrite("preemph_coeff", &kaldi::FrameExtractionOptions::preemph_coeff) + .def_readwrite("remove_dc_offset", &kaldi::FrameExtractionOptions::remove_dc_offset) + .def_readwrite("window_type", &kaldi::FrameExtractionOptions::window_type) + .def_readwrite("round_to_power_of_two", &kaldi::FrameExtractionOptions::round_to_power_of_two) + .def_readwrite("blackman_coeff", &kaldi::FrameExtractionOptions::blackman_coeff) + .def_readwrite("snip_edges", &kaldi::FrameExtractionOptions::snip_edges) + .def_readwrite("allow_downsample", &kaldi::FrameExtractionOptions::allow_downsample) + .def_readwrite("allow_upsample", &kaldi::FrameExtractionOptions::allow_upsample) + .def_readwrite("max_feature_vectors", &kaldi::FrameExtractionOptions::max_feature_vectors); + py::class_(m, "MelBanksOptions") + .def(py::init<>()) + .def_readwrite("num_bins", &kaldi::MelBanksOptions::num_bins) + .def_readwrite("low_freq", &kaldi::MelBanksOptions::low_freq) + .def_readwrite("high_freq", &kaldi::MelBanksOptions::high_freq) + .def_readwrite("vtln_low", &kaldi::MelBanksOptions::vtln_low) + .def_readwrite("vtln_high", &kaldi::MelBanksOptions::vtln_high) + .def_readwrite("debug_mel", &kaldi::MelBanksOptions::debug_mel) + .def_readwrite("htk_mode", &kaldi::MelBanksOptions::htk_mode); + + py::class_(m, "FbankOptions") + .def(py::init<>()) + .def_readwrite("use_energy", &paddleaudio::kaldi::FbankOptions::use_energy) + .def_readwrite("energy_floor", &paddleaudio::kaldi::FbankOptions::energy_floor) + .def_readwrite("raw_energy", &paddleaudio::kaldi::FbankOptions::raw_energy) + .def_readwrite("htk_compat", &paddleaudio::kaldi::FbankOptions::htk_compat) + .def_readwrite("use_log_fbank", &paddleaudio::kaldi::FbankOptions::use_log_fbank) + .def_readwrite("use_power", &paddleaudio::kaldi::FbankOptions::use_power); +#endif + +} diff --git a/audio/paddleaudio/src/pybind/sox/effects.cpp b/audio/paddleaudio/src/pybind/sox/effects.cpp new file mode 100644 index 00000000..ea77527b --- /dev/null +++ b/audio/paddleaudio/src/pybind/sox/effects.cpp @@ -0,0 +1,259 @@ +// the code is from https://github.com/pytorch/audio/blob/main/torchaudio/csrc/sox/effects.cpp with modification. + +#include +#include + +#include "paddleaudio/src/pybind/sox/effects.h" +#include "paddleaudio/src/pybind/sox/effects_chain.h" +#include "paddleaudio/src/pybind/sox/utils.h" + +using namespace paddleaudio::sox_utils; + +namespace paddleaudio::sox_effects { + +// Streaming decoding over file-like object is tricky because libsox operates on +// FILE pointer. The folloing is what `sox` and `play` commands do +// - file input -> FILE pointer +// - URL input -> call wget in suprocess and pipe the data -> FILE pointer +// - stdin -> FILE pointer +// +// We want to, instead, fetch byte strings chunk by chunk, consume them, and +// discard. +// +// Here is the approach +// 1. Initialize sox_format_t using sox_open_mem_read, providing the initial +// chunk of byte string +// This will perform header-based format detection, if necessary, then fill +// the metadata of sox_format_t. Internally, sox_open_mem_read uses fmemopen, +// which returns FILE* which points the buffer of the provided byte string. +// 2. Each time sox reads a chunk from the FILE*, we update the underlying +// buffer in a way that it +// starts with unseen data, and append the new data read from the given +// fileobj. This will trick libsox as if it keeps reading from the FILE* +// continuously. +// For Step 2. see `fileobj_input_drain` function in effects_chain.cpp +auto apply_effects_fileobj( + py::object fileobj, + const std::vector>& effects, + tl::optional normalize, + tl::optional channels_first, + tl::optional format) + -> tl::optional> { + // Prepare the buffer used throughout the lifecycle of SoxEffectChain. + // + // For certain format (such as FLAC), libsox keeps reading the content at + // the initialization unless it reaches EOF even when the header is properly + // parsed. (Making buffer size 8192, which is way bigger than the header, + // resulted in libsox consuming all the buffer content at the time it opens + // the file.) Therefore buffer has to always contain valid data, except after + // EOF. We default to `sox_get_globals()->bufsiz`* for buffer size and we + // first check if there is enough data to fill the buffer. `read_fileobj` + // repeatedly calls `read` method until it receives the requested length of + // bytes or it reaches EOF. If we get bytes shorter than requested, that means + // the whole audio data are fetched. + // + // * This can be changed with `paddleaudio.utils.sox_utils.set_buffer_size`. + const auto capacity = [&]() { + // NOTE: + // Use the abstraction provided by `libpaddleaudio` to access the global + // config defined by libsox. Directly using `sox_get_globals` function will + // end up retrieving the static variable defined in `_paddleaudio`, which is + // not correct. + const auto bufsiz = get_buffer_size(); + const int64_t kDefaultCapacityInBytes = 256; + return (bufsiz > kDefaultCapacityInBytes) ? bufsiz + : kDefaultCapacityInBytes; + }(); + std::string buffer(capacity, '\0'); + auto* in_buf = const_cast(buffer.data()); + auto num_read = read_fileobj(&fileobj, capacity, in_buf); + // If the file is shorter than 256, then libsox cannot read the header. + auto in_buffer_size = (num_read > 256) ? num_read : 256; + + // Open file (this starts reading the header) + // When opening a file there are two functions that can touches FILE*. + // * `auto_detect_format` + // https://github.com/dmkrepo/libsox/blob/b9dd1a86e71bbd62221904e3e59dfaa9e5e72046/src/formats.c#L43 + // * `startread` handler of detected format. + // https://github.com/dmkrepo/libsox/blob/b9dd1a86e71bbd62221904e3e59dfaa9e5e72046/src/formats.c#L574 + // To see the handler of a particular format, go to + // https://github.com/dmkrepo/libsox/blob/b9dd1a86e71bbd62221904e3e59dfaa9e5e72046/src/.c + // For example, voribs can be found + // https://github.com/dmkrepo/libsox/blob/b9dd1a86e71bbd62221904e3e59dfaa9e5e72046/src/vorbis.c#L97-L158 + SoxFormat sf(sox_open_mem_read( + in_buf, + in_buffer_size, + /*signal=*/nullptr, + /*encoding=*/nullptr, + /*filetype=*/format.has_value() ? format.value().c_str() : nullptr)); + + // In case of streamed data, length can be 0 + if (static_cast(sf) == nullptr || + sf->encoding.encoding == SOX_ENCODING_UNKNOWN) { + return {}; + } + + // Prepare output buffer + std::vector out_buffer; + out_buffer.reserve(sf->signal.length); + + // Create and run SoxEffectsChain + const auto dtype = get_dtype(sf->encoding.encoding, sf->signal.precision); + paddleaudio::sox_effects_chain::SoxEffectsChainPyBind chain( + /*input_encoding=*/sf->encoding, + /*output_encoding=*/get_tensor_encodinginfo(dtype)); + chain.addInputFileObj(sf, in_buf, in_buffer_size, &fileobj); + for (const auto& effect : effects) { + chain.addEffect(effect); + } + chain.addOutputBuffer(&out_buffer); + chain.run(); + + // Create tensor from buffer + bool channels_first_ = channels_first.value_or(true); + auto tensor = convert_to_tensor( + /*buffer=*/out_buffer.data(), + /*num_samples=*/out_buffer.size(), + /*num_channels=*/chain.getOutputNumChannels(), + dtype, + normalize.value_or(true), + channels_first_); + + return std::forward_as_tuple( + tensor, static_cast(chain.getOutputSampleRate())); +} + +namespace { + +enum SoxEffectsResourceState { NotInitialized, Initialized, ShutDown }; +SoxEffectsResourceState SOX_RESOURCE_STATE = NotInitialized; +std::mutex SOX_RESOUCE_STATE_MUTEX; + +} // namespace + +void initialize_sox_effects() { + const std::lock_guard lock(SOX_RESOUCE_STATE_MUTEX); + + switch (SOX_RESOURCE_STATE) { + case NotInitialized: + if (sox_init() != SOX_SUCCESS) { + throw std::runtime_error("Failed to initialize sox effects."); + }; + SOX_RESOURCE_STATE = Initialized; + break; + case Initialized: + break; + case ShutDown: + throw std::runtime_error( + "SoX Effects has been shut down. Cannot initialize again."); + } +}; + +void shutdown_sox_effects() { + const std::lock_guard lock(SOX_RESOUCE_STATE_MUTEX); + + switch (SOX_RESOURCE_STATE) { + case NotInitialized: + throw std::runtime_error( + "SoX Effects is not initialized. Cannot shutdown."); + case Initialized: + if (sox_quit() != SOX_SUCCESS) { + throw std::runtime_error("Failed to initialize sox effects."); + }; + SOX_RESOURCE_STATE = ShutDown; + break; + case ShutDown: + break; + } +} + +auto apply_effects_tensor( + py::array waveform, + int64_t sample_rate, + const std::vector>& effects, + bool channels_first) -> std::tuple { + validate_input_tensor(waveform); + + // Create SoxEffectsChain + const auto dtype = waveform.dtype(); + paddleaudio::sox_effects_chain::SoxEffectsChain chain( + /*input_encoding=*/get_tensor_encodinginfo(dtype), + /*output_encoding=*/get_tensor_encodinginfo(dtype)); + + // Prepare output buffer + std::vector out_buffer; + out_buffer.reserve(waveform.size()); + + // Build and run effects chain + chain.addInputTensor(&waveform, sample_rate, channels_first); + for (const auto& effect : effects) { + chain.addEffect(effect); + } + chain.addOutputBuffer(&out_buffer); + chain.run(); + + // Create tensor from buffer + auto out_tensor = convert_to_tensor( + /*buffer=*/out_buffer.data(), + /*num_samples=*/out_buffer.size(), + /*num_channels=*/chain.getOutputNumChannels(), + dtype, + /*normalize=*/false, + channels_first); + + return std::tuple( + out_tensor, chain.getOutputSampleRate()); +} + +auto apply_effects_file( + const std::string& path, + const std::vector>& effects, + tl::optional normalize, + tl::optional channels_first, + const tl::optional& format) + -> tl::optional> { + // Open input file + SoxFormat sf(sox_open_read( + path.c_str(), + /*signal=*/nullptr, + /*encoding=*/nullptr, + /*filetype=*/format.has_value() ? format.value().c_str() : nullptr)); + + if (static_cast(sf) == nullptr || + sf->encoding.encoding == SOX_ENCODING_UNKNOWN) { + return {}; + } + + const auto dtype = get_dtype(sf->encoding.encoding, sf->signal.precision); + + // Prepare output + std::vector out_buffer; + out_buffer.reserve(sf->signal.length); + + // Create and run SoxEffectsChain + paddleaudio::sox_effects_chain::SoxEffectsChain chain( + /*input_encoding=*/sf->encoding, + /*output_encoding=*/get_tensor_encodinginfo(dtype)); + + chain.addInputFile(sf); + for (const auto& effect : effects) { + chain.addEffect(effect); + } + chain.addOutputBuffer(&out_buffer); + chain.run(); + + // Create tensor from buffer + bool channels_first_ = channels_first.value_or(true); + auto tensor = convert_to_tensor( + /*buffer=*/out_buffer.data(), + /*num_samples=*/out_buffer.size(), + /*num_channels=*/chain.getOutputNumChannels(), + dtype, + normalize.value_or(true), + channels_first_); + + return std::tuple( + tensor, chain.getOutputSampleRate()); +} + +} // namespace paddleaudio::sox_effects diff --git a/audio/paddleaudio/src/pybind/sox/effects.h b/audio/paddleaudio/src/pybind/sox/effects.h new file mode 100644 index 00000000..5143db46 --- /dev/null +++ b/audio/paddleaudio/src/pybind/sox/effects.h @@ -0,0 +1,37 @@ +// the code is from https://github.com/pytorch/audio/blob/main/torchaudio/csrc/sox/effects.h with modification. +#include +#include + +#include "paddleaudio/src/optional/optional.hpp" + +namespace py = pybind11; + +namespace paddleaudio::sox_effects { + +auto apply_effects_fileobj( + py::object fileobj, + const std::vector>& effects, + tl::optional normalize, + tl::optional channels_first, + tl::optional format) + -> tl::optional>; + +void initialize_sox_effects(); + +void shutdown_sox_effects(); + +auto apply_effects_tensor( + py::array waveform, + int64_t sample_rate, + const std::vector>& effects, + bool channels_first) -> std::tuple; + +auto apply_effects_file( + const std::string& path, + const std::vector>& effects, + tl::optional normalize, + tl::optional channels_first, + const tl::optional& format) + -> tl::optional>; + +} // namespace paddleaudio::sox_effects diff --git a/audio/paddleaudio/src/pybind/sox/effects_chain.cpp b/audio/paddleaudio/src/pybind/sox/effects_chain.cpp new file mode 100644 index 00000000..0204fb30 --- /dev/null +++ b/audio/paddleaudio/src/pybind/sox/effects_chain.cpp @@ -0,0 +1,597 @@ +// the code is from https://github.com/pytorch/audio/blob/main/torchaudio/csrc/sox/effects_chain.cpp with modification. + +#include +#include +#include +#include "paddleaudio/src/pybind/sox/effects_chain.h" +#include "paddleaudio/src/pybind/sox/utils.h" + +using namespace paddleaudio::sox_utils; + +namespace paddleaudio::sox_effects_chain { + +namespace { + +/// helper classes for passing the location of input tensor and output buffer +/// +/// drain/flow callback functions require plaing C style function signature and +/// the way to pass extra data is to attach data to sox_effect_t::priv pointer. +/// The following structs will be assigned to sox_effect_t::priv pointer which +/// gives sox_effect_t an access to input Tensor and output buffer object. +struct TensorInputPriv { + size_t index; + py::array* waveform; + int64_t sample_rate; + bool channels_first; +}; + +struct TensorOutputPriv { + std::vector* buffer; +}; +struct FileOutputPriv { + sox_format_t* sf; +}; + +/// Callback function to feed Tensor data to SoxEffectChain. +int tensor_input_drain(sox_effect_t* effp, sox_sample_t* obuf, size_t* osamp) { + // Retrieve the input Tensor and current index + auto priv = static_cast(effp->priv); + auto index = priv->index; + auto tensor = *(priv->waveform); + auto num_channels = effp->out_signal.channels; + + // Adjust the number of samples to read + const size_t num_samples = tensor.size(); + if (index + *osamp > num_samples) { + *osamp = num_samples - index; + } + + // Ensure that it's a multiple of the number of channels + *osamp -= *osamp % num_channels; + + // Slice the input Tensor + // refacor this module, chunk + auto i_frame = index / num_channels; + auto num_frames = *osamp / num_channels; + + std::vector chunk(num_frames*num_channels); + py::buffer_info ori_info = tensor.request(); + void* ptr = ori_info.ptr; + // Convert to sox_sample_t (int32_t) + switch (tensor.dtype().num()) { + //case c10::ScalarType::Float: { + case 11: { + // Need to convert to 64-bit precision so that + // values around INT32_MIN/MAX are handled correctly. + for (int idx = 0; idx < chunk.size(); ++idx) { + int frame_idx = (idx + index) / num_channels; + int channels_idx = (idx + index) % num_channels; + double elem = 0; + if (priv->channels_first) { + elem = *(float*)tensor.data(channels_idx, frame_idx); + } else { + elem = *(float*)tensor.data(frame_idx, channels_idx); + } + elem = elem * 2147483648.; + // *new_ptr = std::clamp(elem, INT32_MIN, INT32_MAX); + if (elem > INT32_MAX) { + chunk[idx] = INT32_MAX; + } else if (elem < INT32_MIN) { + chunk[idx] = INT32_MIN; + } else { + chunk[idx] = elem; + } + } + break; + } + //case c10::ScalarType::Int: { + case 5: { + for (int idx = 0; idx < chunk.size(); ++idx) { + int frame_idx = (idx + index) / num_channels; + int channels_idx = (idx + index) % num_channels; + int elem = 0; + if (priv->channels_first) { + elem = *(int*)tensor.data(channels_idx, frame_idx); + } else { + elem = *(int*)tensor.data(frame_idx, channels_idx); + } + chunk[idx] = elem; + } + break; + } + // case short + case 3: { + for (int idx = 0; idx < chunk.size(); ++idx) { + int frame_idx = (idx + index) / num_channels; + int channels_idx = (idx + index) % num_channels; + int16_t elem = 0; + if (priv->channels_first) { + elem = *(int16_t*)tensor.data(channels_idx, frame_idx); + } else { + elem = *(int16_t*)tensor.data(frame_idx, channels_idx); + } + chunk[idx] = elem * 65536; + } + break; + } + // case byte + case 1: { + for (int idx = 0; idx < chunk.size(); ++idx) { + int frame_idx = (idx + index) / num_channels; + int channels_idx = (idx + index) % num_channels; + int8_t elem = 0; + if (priv->channels_first) { + elem = *(int8_t*)tensor.data(channels_idx, frame_idx); + } else { + elem = *(int8_t*)tensor.data(frame_idx, channels_idx); + } + chunk[idx] = (elem - 128) * 16777216; + } + break; + } + default: + throw std::runtime_error("Unexpected dtype."); + } + // Write to buffer + memcpy(obuf, chunk.data(), *osamp * 4); + priv->index += *osamp; + return (priv->index == num_samples) ? SOX_EOF : SOX_SUCCESS; +} + +/// Callback function to fetch data from SoxEffectChain. +int tensor_output_flow( + sox_effect_t* effp, + sox_sample_t const* ibuf, + sox_sample_t* obuf LSX_UNUSED, + size_t* isamp, + size_t* osamp) { + *osamp = 0; + // Get output buffer + auto out_buffer = static_cast(effp->priv)->buffer; + // Append at the end + out_buffer->insert(out_buffer->end(), ibuf, ibuf + *isamp); + return SOX_SUCCESS; +} + +int file_output_flow( + sox_effect_t* effp, + sox_sample_t const* ibuf, + sox_sample_t* obuf LSX_UNUSED, + size_t* isamp, + size_t* osamp) { + *osamp = 0; + if (*isamp) { + auto sf = static_cast(effp->priv)->sf; + if (sox_write(sf, ibuf, *isamp) != *isamp) { + if (sf->sox_errno) { + std::ostringstream stream; + stream << sf->sox_errstr << " " << sox_strerror(sf->sox_errno) << " " + << sf->filename; + throw std::runtime_error(stream.str()); + } + return SOX_EOF; + } + } + return SOX_SUCCESS; +} + +sox_effect_handler_t* get_tensor_input_handler() { + static sox_effect_handler_t handler{ + /*name=*/"input_tensor", + /*usage=*/NULL, + /*flags=*/SOX_EFF_MCHAN, + /*getopts=*/NULL, + /*start=*/NULL, + /*flow=*/NULL, + /*drain=*/tensor_input_drain, + /*stop=*/NULL, + /*kill=*/NULL, + /*priv_size=*/sizeof(TensorInputPriv)}; + return &handler; +} + +sox_effect_handler_t* get_tensor_output_handler() { + static sox_effect_handler_t handler{ + /*name=*/"output_tensor", + /*usage=*/NULL, + /*flags=*/SOX_EFF_MCHAN, + /*getopts=*/NULL, + /*start=*/NULL, + /*flow=*/tensor_output_flow, + /*drain=*/NULL, + /*stop=*/NULL, + /*kill=*/NULL, + /*priv_size=*/sizeof(TensorOutputPriv)}; + return &handler; +} + +sox_effect_handler_t* get_file_output_handler() { + static sox_effect_handler_t handler{ + /*name=*/"output_file", + /*usage=*/NULL, + /*flags=*/SOX_EFF_MCHAN, + /*getopts=*/NULL, + /*start=*/NULL, + /*flow=*/file_output_flow, + /*drain=*/NULL, + /*stop=*/NULL, + /*kill=*/NULL, + /*priv_size=*/sizeof(FileOutputPriv)}; + return &handler; +} + +} // namespace + +SoxEffect::SoxEffect(sox_effect_t* se) noexcept : se_(se) {} + +SoxEffect::~SoxEffect() { + if (se_ != nullptr) { + free(se_); + } +} + +SoxEffect::operator sox_effect_t*() const { + return se_; +} + +auto SoxEffect::operator->() noexcept -> sox_effect_t* { + return se_; +} + +SoxEffectsChain::SoxEffectsChain( + sox_encodinginfo_t input_encoding, + sox_encodinginfo_t output_encoding) + : in_enc_(input_encoding), + out_enc_(output_encoding), + in_sig_(), + interm_sig_(), + out_sig_(), + sec_(sox_create_effects_chain(&in_enc_, &out_enc_)) { + if (!sec_) { + throw std::runtime_error("Failed to create effect chain."); + } +} + +SoxEffectsChain::~SoxEffectsChain() { + if (sec_ != nullptr) { + sox_delete_effects_chain(sec_); + } +} + +void SoxEffectsChain::run() { + sox_flow_effects(sec_, NULL, NULL); +} + +void SoxEffectsChain::addInputTensor( + py::array* waveform, + int64_t sample_rate, + bool channels_first) { + in_sig_ = get_signalinfo(waveform, sample_rate, "wav", channels_first); + interm_sig_ = in_sig_; + SoxEffect e(sox_create_effect(get_tensor_input_handler())); + auto priv = static_cast(e->priv); + priv->index = 0; + priv->waveform = waveform; + priv->sample_rate = sample_rate; + priv->channels_first = channels_first; + if (sox_add_effect(sec_, e, &interm_sig_, &in_sig_) != SOX_SUCCESS) { + throw std::runtime_error( + "Internal Error: Failed to add effect: input_tensor"); + } +} + +void SoxEffectsChain::addOutputBuffer( + std::vector* output_buffer) { + SoxEffect e(sox_create_effect(get_tensor_output_handler())); + static_cast(e->priv)->buffer = output_buffer; + if (sox_add_effect(sec_, e, &interm_sig_, &in_sig_) != SOX_SUCCESS) { + throw std::runtime_error( + "Internal Error: Failed to add effect: output_tensor"); + } +} + +void SoxEffectsChain::addInputFile(sox_format_t* sf) { + in_sig_ = sf->signal; + interm_sig_ = in_sig_; + SoxEffect e(sox_create_effect(sox_find_effect("input"))); + char* opts[] = {(char*)sf}; + sox_effect_options(e, 1, opts); + if (sox_add_effect(sec_, e, &interm_sig_, &in_sig_) != SOX_SUCCESS) { + std::ostringstream stream; + stream << "Internal Error: Failed to add effect: input " << sf->filename; + throw std::runtime_error(stream.str()); + } +} + +void SoxEffectsChain::addOutputFile(sox_format_t* sf) { + out_sig_ = sf->signal; + SoxEffect e(sox_create_effect(get_file_output_handler())); + static_cast(e->priv)->sf = sf; + if (sox_add_effect(sec_, e, &interm_sig_, &out_sig_) != SOX_SUCCESS) { + std::ostringstream stream; + stream << "Internal Error: Failed to add effect: output " << sf->filename; + throw std::runtime_error(stream.str()); + } +} + +void SoxEffectsChain::addEffect(const std::vector effect) { + const auto num_args = effect.size(); + if (num_args == 0) { + throw std::runtime_error("Invalid argument: empty effect."); + } + const auto name = effect[0]; + if (UNSUPPORTED_EFFECTS.find(name) != UNSUPPORTED_EFFECTS.end()) { + std::ostringstream stream; + stream << "Unsupported effect: " << name; + throw std::runtime_error(stream.str()); + } + + auto returned_effect = sox_find_effect(name.c_str()); + if (!returned_effect) { + std::ostringstream stream; + stream << "Unsupported effect: " << name; + throw std::runtime_error(stream.str()); + } + SoxEffect e(sox_create_effect(returned_effect)); + const auto num_options = num_args - 1; + + std::vector opts; + for (size_t i = 1; i < num_args; ++i) { + opts.push_back((char*)effect[i].c_str()); + } + if (sox_effect_options(e, num_options, num_options ? opts.data() : nullptr) != + SOX_SUCCESS) { + std::ostringstream stream; + stream << "Invalid effect option:"; + for (const auto& v : effect) { + stream << " " << v; + } + throw std::runtime_error(stream.str()); + } + + if (sox_add_effect(sec_, e, &interm_sig_, &in_sig_) != SOX_SUCCESS) { + std::ostringstream stream; + stream << "Internal Error: Failed to add effect: \"" << name; + for (size_t i = 1; i < num_args; ++i) { + stream << " " << effect[i]; + } + stream << "\""; + throw std::runtime_error(stream.str()); + } +} + +int64_t SoxEffectsChain::getOutputNumChannels() { + return interm_sig_.channels; +} + +int64_t SoxEffectsChain::getOutputSampleRate() { + return interm_sig_.rate; +} + +namespace { + +/// helper classes for passing file-like object to SoxEffectChain +struct FileObjInputPriv { + sox_format_t* sf; + py::object* fileobj; + bool eof_reached; + char* buffer; + uint64_t buffer_size; +}; + +struct FileObjOutputPriv { + sox_format_t* sf; + py::object* fileobj; + char** buffer; + size_t* buffer_size; +}; + +/// Callback function to feed byte string +/// https://github.com/dmkrepo/libsox/blob/b9dd1a86e71bbd62221904e3e59dfaa9e5e72046/src/sox.h#L1268-L1278 +auto fileobj_input_drain(sox_effect_t* effp, sox_sample_t* obuf, size_t* osamp) + -> int { + auto priv = static_cast(effp->priv); + auto sf = priv->sf; + auto buffer = priv->buffer; + + // 1. Refresh the buffer + // + // NOTE: + // Since the underlying FILE* was opened with `fmemopen`, the only way + // libsox detect EOF is reaching the end of the buffer. (null byte won't + // help) Therefore we need to align the content at the end of buffer, + // otherwise, libsox will keep reading the content beyond intended length. + // + // Before: + // + // |<-------consumed------>|<---remaining--->| + // |***********************|-----------------| + // ^ ftell + // + // After: + // + // |<-offset->|<---remaining--->|<-new data->| + // |**********|-----------------|++++++++++++| + // ^ ftell + + // NOTE: + // Do not use `sf->tell_off` here. Presumably, `tell_off` and `fseek` are + // supposed to be in sync, but there are cases (Vorbis) they are not + // in sync and `tell_off` has seemingly uninitialized value, which + // leads num_remain to be negative and cause segmentation fault + // in `memmove`. + const auto tell = ftell((FILE*)sf->fp); + if (tell < 0) { + throw std::runtime_error("Internal Error: ftell failed."); + } + const auto num_consumed = static_cast(tell); + if (num_consumed > priv->buffer_size) { + throw std::runtime_error("Internal Error: buffer overrun."); + } + + const auto num_remain = priv->buffer_size - num_consumed; + + // 1.1. Fetch the data to see if there is data to fill the buffer + size_t num_refill = 0; + std::string chunk(num_consumed, '\0'); + if (num_consumed && !priv->eof_reached) { + num_refill = read_fileobj( + priv->fileobj, num_consumed, const_cast(chunk.data())); + if (num_refill < num_consumed) { + priv->eof_reached = true; + } + } + const auto offset = num_consumed - num_refill; + + // 1.2. Move the unconsumed data towards the beginning of buffer. + if (num_remain) { + auto src = static_cast(buffer + num_consumed); + auto dst = static_cast(buffer + offset); + memmove(dst, src, num_remain); + } + + // 1.3. Refill the remaining buffer. + if (num_refill) { + auto src = static_cast(const_cast(chunk.c_str())); + auto dst = buffer + offset + num_remain; + memcpy(dst, src, num_refill); + } + + // 1.4. Set the file pointer to the new offset + sf->tell_off = offset; + fseek((FILE*)sf->fp, offset, SEEK_SET); + + // 2. Perform decoding operation + // The following part is practically same as "input" effect + // https://github.com/dmkrepo/libsox/blob/b9dd1a86e71bbd62221904e3e59dfaa9e5e72046/src/input.c#L30-L48 + + // At this point, osamp represents the buffer size in bytes, + // but sox_read expects the maximum number of samples ready to read. + // Normally, this is fine, but in case when the samples are not 4-byte + // aligned, (e.g. sample is 24bits), the resulting signal is not correct. + // https://github.com/pytorch/audio/issues/2083 + if (sf->encoding.bits_per_sample > 0) + *osamp /= (sf->encoding.bits_per_sample / 8); + + // Ensure that it's a multiple of the number of channels + *osamp -= *osamp % effp->out_signal.channels; + + // Read up to *osamp samples into obuf; + // store the actual number read back to *osamp + *osamp = sox_read(sf, obuf, *osamp); + + // Decoding is finished when fileobject is exhausted and sox can no longer + // decode a sample. + return (priv->eof_reached && !*osamp) ? SOX_EOF : SOX_SUCCESS; +} + +auto fileobj_output_flow( + sox_effect_t* effp, + sox_sample_t const* ibuf, + sox_sample_t* obuf LSX_UNUSED, + size_t* isamp, + size_t* osamp) -> int { + *osamp = 0; + if (*isamp) { + auto priv = static_cast(effp->priv); + auto sf = priv->sf; + auto fp = static_cast(sf->fp); + auto fileobj = priv->fileobj; + auto buffer = priv->buffer; + + // Encode chunk + auto num_samples_written = sox_write(sf, ibuf, *isamp); + fflush(fp); + + // Copy the encoded chunk to python object. + fileobj->attr("write")(py::bytes(*buffer, ftell(fp))); + + // Reset FILE* + sf->tell_off = 0; + fseek(fp, 0, SEEK_SET); + + if (num_samples_written != *isamp) { + if (sf->sox_errno) { + std::ostringstream stream; + stream << sf->sox_errstr << " " << sox_strerror(sf->sox_errno) << " " + << sf->filename; + throw std::runtime_error(stream.str()); + } + return SOX_EOF; + } + } + return SOX_SUCCESS; +} + +auto get_fileobj_input_handler() -> sox_effect_handler_t* { + static sox_effect_handler_t handler{ + /*name=*/"input_fileobj_object", + /*usage=*/nullptr, + /*flags=*/SOX_EFF_MCHAN, + /*getopts=*/nullptr, + /*start=*/nullptr, + /*flow=*/nullptr, + /*drain=*/fileobj_input_drain, + /*stop=*/nullptr, + /*kill=*/nullptr, + /*priv_size=*/sizeof(FileObjInputPriv)}; + return &handler; +} + +auto get_fileobj_output_handler() -> sox_effect_handler_t* { + static sox_effect_handler_t handler{ + /*name=*/"output_fileobj_object", + /*usage=*/nullptr, + /*flags=*/SOX_EFF_MCHAN, + /*getopts=*/nullptr, + /*start=*/nullptr, + /*flow=*/fileobj_output_flow, + /*drain=*/nullptr, + /*stop=*/nullptr, + /*kill=*/nullptr, + /*priv_size=*/sizeof(FileObjOutputPriv)}; + return &handler; +} + +} // namespace + +void SoxEffectsChainPyBind::addInputFileObj( + sox_format_t* sf, + char* buffer, + uint64_t buffer_size, + py::object* fileobj) { + in_sig_ = sf->signal; + interm_sig_ = in_sig_; + + SoxEffect e(sox_create_effect(get_fileobj_input_handler())); + auto priv = static_cast(e->priv); + priv->sf = sf; + priv->fileobj = fileobj; + priv->eof_reached = false; + priv->buffer = buffer; + priv->buffer_size = buffer_size; + if (sox_add_effect(sec_, e, &interm_sig_, &in_sig_) != SOX_SUCCESS) { + throw std::runtime_error( + "Internal Error: Failed to add effect: input fileobj"); + } +} + +void SoxEffectsChainPyBind::addOutputFileObj( + sox_format_t* sf, + char** buffer, + size_t* buffer_size, + py::object* fileobj) { + out_sig_ = sf->signal; + SoxEffect e(sox_create_effect(get_fileobj_output_handler())); + auto priv = static_cast(e->priv); + priv->sf = sf; + priv->fileobj = fileobj; + priv->buffer = buffer; + priv->buffer_size = buffer_size; + if (sox_add_effect(sec_, e, &interm_sig_, &out_sig_) != SOX_SUCCESS) { + throw std::runtime_error( + "Internal Error: Failed to add effect: output fileobj"); + } +} + +} // namespace paddleaudio::sox_effects_chain diff --git a/audio/paddleaudio/src/pybind/sox/effects_chain.h b/audio/paddleaudio/src/pybind/sox/effects_chain.h new file mode 100644 index 00000000..d61de658 --- /dev/null +++ b/audio/paddleaudio/src/pybind/sox/effects_chain.h @@ -0,0 +1,78 @@ +// the code is from https://github.com/pytorch/audio/blob/main/torchaudio/csrc/sox/effects_chain.h with modification. + +#pragma once + +#include +#include "paddleaudio/src/pybind/sox/utils.h" + +namespace paddleaudio::sox_effects_chain { + +// Helper struct to safely close sox_effect_t* pointer returned by +// sox_create_effect + +struct SoxEffect { + explicit SoxEffect(sox_effect_t* se) noexcept; + SoxEffect(const SoxEffect& other) = delete; + SoxEffect(const SoxEffect&& other) = delete; + auto operator=(const SoxEffect& other) -> SoxEffect& = delete; + auto operator=(SoxEffect&& other) -> SoxEffect& = delete; + ~SoxEffect(); + operator sox_effect_t*() const; + auto operator->() noexcept -> sox_effect_t*; + + private: + sox_effect_t* se_; +}; + +// Helper struct to safely close sox_effects_chain_t with handy methods +class SoxEffectsChain { + const sox_encodinginfo_t in_enc_; + const sox_encodinginfo_t out_enc_; + + protected: + sox_signalinfo_t in_sig_; + sox_signalinfo_t interm_sig_; + sox_signalinfo_t out_sig_; + sox_effects_chain_t* sec_; + + public: + explicit SoxEffectsChain( + sox_encodinginfo_t input_encoding, + sox_encodinginfo_t output_encoding); + SoxEffectsChain(const SoxEffectsChain& other) = delete; + SoxEffectsChain(const SoxEffectsChain&& other) = delete; + SoxEffectsChain& operator=(const SoxEffectsChain& other) = delete; + SoxEffectsChain& operator=(SoxEffectsChain&& other) = delete; + ~SoxEffectsChain(); + void run(); + void addInputTensor( + py::array* waveform, + int64_t sample_rate, + bool channels_first); + void addInputFile(sox_format_t* sf); + void addOutputBuffer(std::vector* output_buffer); + void addOutputFile(sox_format_t* sf); + void addEffect(const std::vector effect); + int64_t getOutputNumChannels(); + int64_t getOutputSampleRate(); +}; + +class SoxEffectsChainPyBind : public SoxEffectsChain { + using SoxEffectsChain::SoxEffectsChain; + + public: + void addInputFileObj( + sox_format_t* sf, + char* buffer, + uint64_t buffer_size, + py::object* fileobj); + + void addOutputFileObj( + sox_format_t* sf, + char** buffer, + size_t* buffer_size, + py::object* fileobj); +}; + +} // namespace paddleaudio::sox_effects_chain + diff --git a/audio/paddleaudio/src/pybind/sox/io.cpp b/audio/paddleaudio/src/pybind/sox/io.cpp new file mode 100644 index 00000000..e0c41d5f --- /dev/null +++ b/audio/paddleaudio/src/pybind/sox/io.cpp @@ -0,0 +1,279 @@ +// the code is from https://github.com/pytorch/audio/blob/main/torchaudio/csrc/sox/io.cpp with modification. + +#include "paddleaudio/src/pybind/sox/io.h" +#include "paddleaudio/src/pybind/sox/effects.h" +#include "paddleaudio/src/pybind/sox/types.h" +#include "paddleaudio/src/pybind/sox/effects_chain.h" +#include "paddleaudio/src/pybind/sox/utils.h" +#include "paddleaudio/src/optional/optional.hpp" + +using namespace paddleaudio::sox_utils; + +namespace paddleaudio { +namespace sox_io { + +auto get_info_file(const std::string &path, + const tl::optional &format) + -> std::tuple { + SoxFormat sf( + sox_open_read(path.data(), + /*signal=*/nullptr, + /*encoding=*/nullptr, + /*filetype=*/format.has_value() ? format.value().c_str() : nullptr)); + + + validate_input_file(sf, path); + + return std::make_tuple( + static_cast(sf->signal.rate), + static_cast(sf->signal.length / sf->signal.channels), + static_cast(sf->signal.channels), + static_cast(sf->encoding.bits_per_sample), + get_encoding(sf->encoding.encoding)); +} + +std::vector> get_effects( + const tl::optional& frame_offset, + const tl::optional& num_frames) { + const auto offset = frame_offset.value_or(0); + if (offset < 0) { + throw std::runtime_error( + "Invalid argument: frame_offset must be non-negative."); + } + const auto frames = num_frames.value_or(-1); + if (frames == 0 || frames < -1) { + throw std::runtime_error( + "Invalid argument: num_frames must be -1 or greater than 0."); + } + + std::vector> effects; + if (frames != -1) { + std::ostringstream os_offset, os_frames; + os_offset << offset << "s"; + os_frames << "+" << frames << "s"; + effects.emplace_back( + std::vector{"trim", os_offset.str(), os_frames.str()}); + } else if (offset != 0) { + std::ostringstream os_offset; + os_offset << offset << "s"; + effects.emplace_back(std::vector{"trim", os_offset.str()}); + } + return effects; +} + +auto get_info_fileobj(py::object fileobj, + const tl::optional &format) + -> std::tuple { + const auto capacity = [&]() { + const auto bufsiz = get_buffer_size(); + const int64_t kDefaultCapacityInBytes = 4096; + return (bufsiz > kDefaultCapacityInBytes) ? bufsiz + : kDefaultCapacityInBytes; + }(); + std::string buffer(capacity, '\0'); + auto *buf = const_cast(buffer.data()); + auto num_read = read_fileobj(&fileobj, capacity, buf); + // If the file is shorter than 256, then libsox cannot read the header. + auto buf_size = (num_read > 256) ? num_read : 256; + + SoxFormat sf(sox_open_mem_read( + buf, + buf_size, + /*signal=*/nullptr, + /*encoding=*/nullptr, + /*filetype=*/format.has_value() ? format.value().c_str() : nullptr)); + + // In case of streamed data, length can be 0 + validate_input_memfile(sf); + + return std::make_tuple( + static_cast(sf->signal.rate), + static_cast(sf->signal.length / sf->signal.channels), + static_cast(sf->signal.channels), + static_cast(sf->encoding.bits_per_sample), + get_encoding(sf->encoding.encoding)); +} + +tl::optional> load_audio_fileobj( + py::object fileobj, + const tl::optional& frame_offset, + const tl::optional& num_frames, + tl::optional normalize, + tl::optional channels_first, + const tl::optional& format) { + auto effects = get_effects(frame_offset, num_frames); + return paddleaudio::sox_effects::apply_effects_fileobj( + std::move(fileobj), effects, normalize, channels_first, std::move(format)); +} + +tl::optional> load_audio_file( + const std::string& path, + const tl::optional& frame_offset, + const tl::optional& num_frames, + tl::optional normalize, + tl::optional channels_first, + const tl::optional& format) { + auto effects = get_effects(frame_offset, num_frames); + return paddleaudio::sox_effects::apply_effects_file( + path, effects, normalize, channels_first, format); +} + +void save_audio_file(const std::string& path, + py::array tensor, + int64_t sample_rate, + bool channels_first, + tl::optional compression, + tl::optional format, + tl::optional encoding, + tl::optional bits_per_sample) { + validate_input_tensor(tensor); + + const auto filetype = [&]() { + if (format.has_value()) return format.value(); + return get_filetype(path); + }(); + + if (filetype == "amr-nb") { + const auto num_channels = tensor.shape(channels_first ? 0 : 1); + //TORCH_CHECK(num_channels == 1, + // "amr-nb format only supports single channel audio."); + assert(num_channels == 1); + } else if (filetype == "htk") { + const auto num_channels = tensor.shape(channels_first ? 0 : 1); + // TORCH_CHECK(num_channels == 1, + // "htk format only supports single channel audio."); + assert(num_channels == 1); + } else if (filetype == "gsm") { + const auto num_channels = tensor.shape(channels_first ? 0 : 1); + assert(num_channels == 1); + assert(sample_rate == 8000); + //TORCH_CHECK(num_channels == 1, + // "gsm format only supports single channel audio."); + //TORCH_CHECK(sample_rate == 8000, + // "gsm format only supports a sampling rate of 8kHz."); + } + const auto signal_info = + get_signalinfo(&tensor, sample_rate, filetype, channels_first); + const auto encoding_info = get_encodinginfo_for_save( + filetype, tensor.dtype(), compression, encoding, bits_per_sample); + + SoxFormat sf(sox_open_write(path.c_str(), + &signal_info, + &encoding_info, + /*filetype=*/filetype.c_str(), + /*oob=*/nullptr, + /*overwrite_permitted=*/nullptr)); + + if (static_cast(sf) == nullptr) { + throw std::runtime_error( + "Error saving audio file: failed to open file " + path); + } + + paddleaudio::sox_effects_chain::SoxEffectsChain chain( + /*input_encoding=*/get_tensor_encodinginfo(tensor.dtype()), + /*output_encoding=*/sf->encoding); + chain.addInputTensor(&tensor, sample_rate, channels_first); + chain.addOutputFile(sf); + chain.run(); +} + +namespace { +// helper class to automatically release buffer, to be used by +// save_audio_fileobj +struct AutoReleaseBuffer { + char* ptr; + size_t size; + + AutoReleaseBuffer() : ptr(nullptr), size(0) {} + AutoReleaseBuffer(const AutoReleaseBuffer& other) = delete; + AutoReleaseBuffer(AutoReleaseBuffer&& other) = delete; + auto operator=(const AutoReleaseBuffer& other) -> AutoReleaseBuffer& = delete; + auto operator=(AutoReleaseBuffer&& other) -> AutoReleaseBuffer& = delete; + ~AutoReleaseBuffer() { + if (ptr) { + free(ptr); + } + } +}; + +} // namespace + +void save_audio_fileobj( + py::object fileobj, + py::array tensor, + int64_t sample_rate, + bool channels_first, + tl::optional compression, + tl::optional format, + tl::optional encoding, + tl::optional bits_per_sample) { + + if (!format.has_value()) { + throw std::runtime_error( + "`format` is required when saving to file object."); + } + const auto filetype = format.value(); + + if (filetype == "amr-nb") { + const auto num_channels = tensor.shape(channels_first ? 0 : 1); + if (num_channels != 1) { + throw std::runtime_error( + "amr-nb format only supports single channel audio."); + } + } else if (filetype == "htk") { + const auto num_channels = tensor.shape(channels_first ? 0 : 1); + if (num_channels != 1) { + throw std::runtime_error( + "htk format only supports single channel audio."); + } + } else if (filetype == "gsm") { + const auto num_channels = tensor.shape(channels_first ? 0 : 1); + if (num_channels != 1) { + throw std::runtime_error( + "gsm format only supports single channel audio."); + } + if (sample_rate != 8000) { + throw std::runtime_error( + "gsm format only supports a sampling rate of 8kHz."); + } + } + + const auto signal_info = + get_signalinfo(&tensor, sample_rate, filetype, channels_first); + const auto encoding_info = get_encodinginfo_for_save( + filetype, + tensor.dtype(), + compression, + std::move(encoding), + bits_per_sample); + + AutoReleaseBuffer buffer; + + SoxFormat sf(sox_open_memstream_write( + &buffer.ptr, + &buffer.size, + &signal_info, + &encoding_info, + filetype.c_str(), + /*oob=*/nullptr)); + + if (static_cast(sf) == nullptr) { + throw std::runtime_error( + "Error saving audio file: failed to open memory stream."); + } + + paddleaudio::sox_effects_chain::SoxEffectsChainPyBind chain( + /*input_encoding=*/get_tensor_encodinginfo(tensor.dtype()), + /*output_encoding=*/sf->encoding); + chain.addInputTensor(&tensor, sample_rate, channels_first); + chain.addOutputFileObj(sf, &buffer.ptr, &buffer.size, &fileobj); + chain.run(); + + // Closing the sox_format_t is necessary for flushing the last chunk to the + // buffer + sf.close(); + fileobj.attr("write")(py::bytes(buffer.ptr, buffer.size)); +} + +} // namespace paddleaudio +} // namespace sox_io diff --git a/audio/paddleaudio/src/pybind/sox/io.h b/audio/paddleaudio/src/pybind/sox/io.h new file mode 100644 index 00000000..24144c38 --- /dev/null +++ b/audio/paddleaudio/src/pybind/sox/io.h @@ -0,0 +1,61 @@ +// the code is from https://github.com/pytorch/audio/blob/main/torchaudio/csrc/sox/io.h with modification. +#pragma once + +#include "paddleaudio/src/pybind/sox/utils.h" + +namespace py = pybind11; + +namespace paddleaudio { +namespace sox_io { + +auto get_info_file(const std::string &path, + const tl::optional &format) + -> std::tuple; + +auto get_info_fileobj(py::object fileobj, + const tl::optional &format) + -> std::tuple; + +tl::optional> load_audio_fileobj( + py::object fileobj, + const tl::optional& frame_offset, + const tl::optional& num_frames, + tl::optional normalize, + tl::optional channels_first, + const tl::optional& format); + +void save_audio_fileobj( + py::object fileobj, + py::array tensor, + int64_t sample_rate, + bool channels_first, + tl::optional compression, + tl::optional format, + tl::optional encoding, + tl::optional bits_per_sample); + +auto get_effects(const tl::optional& frame_offset, + const tl::optional& num_frames) + -> std::vector>; + + +tl::optional> load_audio_file( + const std::string& path, + const tl::optional& frame_offset, + const tl::optional& num_frames, + tl::optional normalize, + tl::optional channels_first, + const tl::optional& format); + +void save_audio_file(const std::string& path, + py::array tensor, + int64_t sample_rate, + bool channels_first, + tl::optional compression, + tl::optional format, + tl::optional encoding, + tl::optional bits_per_sample); + + +} // namespace paddleaudio +} // namespace sox_io diff --git a/audio/paddleaudio/src/pybind/sox/types.cpp b/audio/paddleaudio/src/pybind/sox/types.cpp new file mode 100644 index 00000000..b42984e6 --- /dev/null +++ b/audio/paddleaudio/src/pybind/sox/types.cpp @@ -0,0 +1,143 @@ +//code is from: https://github.com/pytorch/audio/blob/main/torchaudio/csrc/sox/types.cpp + +#include "paddleaudio/src/pybind/sox/types.h" +#include +#include + +namespace paddleaudio { +namespace sox_utils { + +Format get_format_from_string(const std::string& format) { + if (format == "wav") + return Format::WAV; + if (format == "mp3") + return Format::MP3; + if (format == "flac") + return Format::FLAC; + if (format == "ogg" || format == "vorbis") + return Format::VORBIS; + if (format == "amr-nb") + return Format::AMR_NB; + if (format == "amr-wb") + return Format::AMR_WB; + if (format == "amb") + return Format::AMB; + if (format == "sph") + return Format::SPHERE; + if (format == "htk") + return Format::HTK; + if (format == "gsm") + return Format::GSM; + std::ostringstream stream; + stream << "Internal Error: unexpected format value: " << format; + throw std::runtime_error(stream.str()); +} + +std::string to_string(Encoding v) { + switch (v) { + case Encoding::UNKNOWN: + return "UNKNOWN"; + case Encoding::PCM_SIGNED: + return "PCM_S"; + case Encoding::PCM_UNSIGNED: + return "PCM_U"; + case Encoding::PCM_FLOAT: + return "PCM_F"; + case Encoding::FLAC: + return "FLAC"; + case Encoding::ULAW: + return "ULAW"; + case Encoding::ALAW: + return "ALAW"; + case Encoding::MP3: + return "MP3"; + case Encoding::VORBIS: + return "VORBIS"; + case Encoding::AMR_WB: + return "AMR_WB"; + case Encoding::AMR_NB: + return "AMR_NB"; + case Encoding::OPUS: + return "OPUS"; + default: + throw std::runtime_error("Internal Error: unexpected encoding."); + } +} + +Encoding get_encoding_from_option(const tl::optional encoding) { + if (!encoding.has_value()) + return Encoding::NOT_PROVIDED; + std::string v = encoding.value(); + if (v == "PCM_S") + return Encoding::PCM_SIGNED; + if (v == "PCM_U") + return Encoding::PCM_UNSIGNED; + if (v == "PCM_F") + return Encoding::PCM_FLOAT; + if (v == "ULAW") + return Encoding::ULAW; + if (v == "ALAW") + return Encoding::ALAW; + std::ostringstream stream; + stream << "Internal Error: unexpected encoding value: " << v; + throw std::runtime_error(stream.str()); +} + +BitDepth get_bit_depth_from_option(const tl::optional bit_depth) { + if (!bit_depth.has_value()) + return BitDepth::NOT_PROVIDED; + int64_t v = bit_depth.value(); + switch (v) { + case 8: + return BitDepth::B8; + case 16: + return BitDepth::B16; + case 24: + return BitDepth::B24; + case 32: + return BitDepth::B32; + case 64: + return BitDepth::B64; + default: { + std::ostringstream s; + s << "Internal Error: unexpected bit depth value: " << v; + throw std::runtime_error(s.str()); + } + } +} + +std::string get_encoding(sox_encoding_t encoding) { + switch (encoding) { + case SOX_ENCODING_UNKNOWN: + return "UNKNOWN"; + case SOX_ENCODING_SIGN2: + return "PCM_S"; + case SOX_ENCODING_UNSIGNED: + return "PCM_U"; + case SOX_ENCODING_FLOAT: + return "PCM_F"; + case SOX_ENCODING_FLAC: + return "FLAC"; + case SOX_ENCODING_ULAW: + return "ULAW"; + case SOX_ENCODING_ALAW: + return "ALAW"; + case SOX_ENCODING_MP3: + return "MP3"; + case SOX_ENCODING_VORBIS: + return "VORBIS"; + case SOX_ENCODING_AMR_WB: + return "AMR_WB"; + case SOX_ENCODING_AMR_NB: + return "AMR_NB"; + case SOX_ENCODING_OPUS: + return "OPUS"; + case SOX_ENCODING_GSM: + return "GSM"; + default: + return "UNKNOWN"; + } +} + +} // namespace sox_utils +} // namespace paddleaudio diff --git a/audio/paddleaudio/src/pybind/sox/types.h b/audio/paddleaudio/src/pybind/sox/types.h new file mode 100644 index 00000000..126e4faa --- /dev/null +++ b/audio/paddleaudio/src/pybind/sox/types.h @@ -0,0 +1,58 @@ +//code is from: https://github.com/pytorch/audio/blob/main/torchaudio/csrc/sox/types.h +#pragma once + +#include +#include "paddleaudio/src/optional/optional.hpp" + +namespace paddleaudio { +namespace sox_utils { + +enum class Format { + WAV, + MP3, + FLAC, + VORBIS, + AMR_NB, + AMR_WB, + AMB, + SPHERE, + GSM, + HTK, +}; + +Format get_format_from_string(const std::string& format); + +enum class Encoding { + NOT_PROVIDED, + UNKNOWN, + PCM_SIGNED, + PCM_UNSIGNED, + PCM_FLOAT, + FLAC, + ULAW, + ALAW, + MP3, + VORBIS, + AMR_WB, + AMR_NB, + OPUS, +}; + +std::string to_string(Encoding v); +Encoding get_encoding_from_option(const tl::optional encoding); + +enum class BitDepth : unsigned { + NOT_PROVIDED = 0, + B8 = 8, + B16 = 16, + B24 = 24, + B32 = 32, + B64 = 64, +}; + +BitDepth get_bit_depth_from_option(const tl::optional bit_depth); + +std::string get_encoding(sox_encoding_t encoding); + +} // namespace sox_utils +} // namespace paddleaudio diff --git a/audio/paddleaudio/src/pybind/sox/utils.cpp b/audio/paddleaudio/src/pybind/sox/utils.cpp new file mode 100644 index 00000000..bc32b740 --- /dev/null +++ b/audio/paddleaudio/src/pybind/sox/utils.cpp @@ -0,0 +1,550 @@ +//code is from: https://github.com/pytorch/audio/blob/main/torchaudio/csrc/sox/utils.cpp with modification. +#include + +#include "paddleaudio/src/pybind/sox/utils.h" +#include "paddleaudio/src/pybind/sox/types.h" + +#include + +namespace paddleaudio { +namespace sox_utils { + +auto read_fileobj(py::object *fileobj, const uint64_t size, char *buffer) + -> uint64_t { + uint64_t num_read = 0; + while (num_read < size) { + auto request = size - num_read; + auto chunk = static_cast( + static_cast(fileobj->attr("read")(request))); + auto chunk_len = chunk.length(); + if (chunk_len == 0) { + break; + } + if (chunk_len > request) { + std::ostringstream message; + message + << "Requested up to " << request << " bytes but, " + << "received " << chunk_len << " bytes. " + << "The given object does not confirm to read protocol of file " + "object."; + throw std::runtime_error(message.str()); + } + memcpy(buffer, chunk.data(), chunk_len); + buffer += chunk_len; + num_read += chunk_len; + } + return num_read; +} + + +void set_seed(const int64_t seed) { + sox_get_globals()->ranqd1 = static_cast(seed); +} + +void set_verbosity(const int64_t verbosity) { + sox_get_globals()->verbosity = static_cast(verbosity); +} + +void set_use_threads(const bool use_threads) { + sox_get_globals()->use_threads = static_cast(use_threads); +} + +void set_buffer_size(const int64_t buffer_size) { + sox_get_globals()->bufsiz = static_cast(buffer_size); +} + +int64_t get_buffer_size() { + return sox_get_globals()->bufsiz; +} + +std::vector> list_effects() { + std::vector> effects; + for (const sox_effect_fn_t* fns = sox_get_effect_fns(); *fns; ++fns) { + const sox_effect_handler_t* handler = (*fns)(); + if (handler && handler->name) { + if (UNSUPPORTED_EFFECTS.find(handler->name) == + UNSUPPORTED_EFFECTS.end()) { + effects.emplace_back(std::vector{ + handler->name, + handler->usage ? std::string(handler->usage) : std::string("")}); + } + } + } + return effects; +} + +std::vector list_write_formats() { + std::vector formats; + for (const sox_format_tab_t* fns = sox_get_format_fns(); fns->fn; ++fns) { + const sox_format_handler_t* handler = fns->fn(); + for (const char* const* names = handler->names; *names; ++names) { + if (!strchr(*names, '/') && handler->write) + formats.emplace_back(*names); + } + } + return formats; +} + +std::vector list_read_formats() { + std::vector formats; + for (const sox_format_tab_t* fns = sox_get_format_fns(); fns->fn; ++fns) { + const sox_format_handler_t* handler = fns->fn(); + for (const char* const* names = handler->names; *names; ++names) { + if (!strchr(*names, '/') && handler->read) + formats.emplace_back(*names); + } + } + return formats; +} + +SoxFormat::SoxFormat(sox_format_t* fd) noexcept : fd_(fd) {} +SoxFormat::~SoxFormat() { + close(); +} + +sox_format_t* SoxFormat::operator->() const noexcept { + return fd_; +} +SoxFormat::operator sox_format_t*() const noexcept { + return fd_; +} + +void SoxFormat::close() { + if (fd_ != nullptr) { + sox_close(fd_); + fd_ = nullptr; + } +} + +void validate_input_file(const SoxFormat& sf, const std::string& path) { + if (static_cast(sf) == nullptr) { + throw std::runtime_error( + "Error loading audio file: failed to open file " + path); + } + if (sf->encoding.encoding == SOX_ENCODING_UNKNOWN) { + throw std::runtime_error("Error loading audio file: unknown encoding."); + } +} + +void validate_input_memfile(const SoxFormat &sf) { + return validate_input_file(sf, ""); +} + +void validate_input_tensor(const py::array tensor) { + if (tensor.ndim() != 2) { + throw std::runtime_error("Input tensor has to be 2D."); + } + + char dtype = tensor.dtype().char_(); + bool flag = (dtype == 'f') || (dtype == 'd') || (dtype == 'l') || (dtype == 'i'); + if (flag == false) { + throw std::runtime_error( + "Input tensor has to be one of float32, int32, int16 or uint8 type."); + } +} + +py::dtype get_dtype( + const sox_encoding_t encoding, + const unsigned precision) { + switch (encoding) { + case SOX_ENCODING_UNSIGNED: // 8-bit PCM WAV + return py::dtype('u1'); + case SOX_ENCODING_SIGN2: // 16-bit, 24-bit, or 32-bit PCM WAV + switch (precision) { + case 16: + return py::dtype("i2"); + case 24: // Cast 24-bit to 32-bit. + case 32: + return py::dtype('i'); + default: + throw std::runtime_error( + "Only 16, 24, and 32 bits are supported for signed PCM."); + } + default: + // default to float32 for the other formats, including + // 32-bit flaoting-point WAV, + // MP3, + // FLAC, + // VORBIS etc... + return py::dtype("f"); + } +} + +py::array convert_to_tensor( + sox_sample_t* buffer, + const int32_t num_samples, + const int32_t num_channels, + const py::dtype dtype, + const bool normalize, + const bool channels_first) { + // todo refector later(SGoat) + py::array t; + uint64_t dummy = 0; + SOX_SAMPLE_LOCALS; + int32_t num_rows = num_samples / num_channels; + if (normalize || dtype.char_() == 'f') { + t = py::array(dtype, {num_rows, num_channels}); + auto ptr = (float*)t.mutable_data(0, 0); + for (int32_t i = 0; i < num_samples; ++i) { + ptr[i] = SOX_SAMPLE_TO_FLOAT_32BIT(buffer[i], dummy); + } + if (channels_first) { + py::array t2 = py::array(dtype, {num_channels, num_rows}); + for (int32_t row_idx = 0; row_idx < num_channels; ++row_idx) { + for (int32_t col_idx = 0; col_idx < num_rows; ++col_idx) + *(float*)t2.mutable_data(row_idx, col_idx) = *(float*)t.data(col_idx, row_idx); + } + return t2; + } + } else if (dtype.char_() == 'i') { + t = py::array(dtype, {num_rows, num_channels}); + auto ptr = (int*)t.mutable_data(0, 0); + for (int32_t i = 0; i < num_samples; ++i) { + ptr[i] = buffer[i]; + } + if (channels_first) { + py::array t2 = py::array(dtype, {num_channels, num_rows}); + for (int32_t row_idx = 0; row_idx < num_channels; ++row_idx) { + for (int32_t col_idx = 0; col_idx < num_rows; ++col_idx) + *(int*)t2.mutable_data(row_idx, col_idx) = *(int*)t.data(col_idx, row_idx); + } + return t2; + } + } else if (dtype.char_() == 'h') { // int16 + t = py::array(dtype, {num_rows, num_channels}); + auto ptr = (int16_t*)t.mutable_data(0, 0); + for (int32_t i = 0; i < num_samples; ++i) { + ptr[i] = SOX_SAMPLE_TO_SIGNED_16BIT(buffer[i], dummy); + } + if (channels_first) { + py::array t2 = py::array(dtype, {num_channels, num_rows}); + for (int32_t row_idx = 0; row_idx < num_channels; ++row_idx) { + for (int32_t col_idx = 0; col_idx < num_rows; ++col_idx) + *(int16_t*)t2.mutable_data(row_idx, col_idx) = *(int16_t*)t.data(col_idx, row_idx); + } + return t2; + } + } else if (dtype.char_() == 'b') { + //t = torch::empty({num_samples / num_channels, num_channels}, torch::kUInt8); + t = py::array(dtype, {num_rows, num_channels}); + auto ptr = (uint8_t*)t.mutable_data(0,0); + for (int32_t i = 0; i < num_samples; ++i) { + ptr[i] = SOX_SAMPLE_TO_UNSIGNED_8BIT(buffer[i], dummy); + } + if (channels_first) { + py::array t2 = py::array(dtype, {num_channels, num_rows}); + for (int32_t row_idx = 0; row_idx < num_channels; ++row_idx) { + for (int32_t col_idx = 0; col_idx < num_rows; ++col_idx) + *(uint8_t*)t2.mutable_data(row_idx, col_idx) = *(uint8_t*)t.data(col_idx, row_idx); + } + return t2; + } + } else { + throw std::runtime_error("Unsupported dtype."); + } + return t; +} + +const std::string get_filetype(const std::string path) { + std::string ext = path.substr(path.find_last_of(".") + 1); + std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower); + return ext; +} + +namespace { + +std::tuple get_save_encoding_for_wav( + const std::string format, + py::dtype dtype, + const Encoding& encoding, + const BitDepth& bits_per_sample) { + switch (encoding) { + case Encoding::NOT_PROVIDED: + switch (bits_per_sample) { + case BitDepth::NOT_PROVIDED: + switch (dtype.num()) { + case 11: // float32 numpy dtype num + return std::make_tuple<>(SOX_ENCODING_FLOAT, 32); + case 5: // int numpy dtype num + return std::make_tuple<>(SOX_ENCODING_SIGN2, 32); + case 3: // int16 numpy + return std::make_tuple<>(SOX_ENCODING_SIGN2, 16); + case 1: // byte numpy + return std::make_tuple<>(SOX_ENCODING_UNSIGNED, 8); + default: + throw std::runtime_error("Internal Error: Unexpected dtype."); + } + case BitDepth::B8: + return std::make_tuple<>(SOX_ENCODING_UNSIGNED, 8); + default: + return std::make_tuple<>( + SOX_ENCODING_SIGN2, static_cast(bits_per_sample)); + } + case Encoding::PCM_SIGNED: + switch (bits_per_sample) { + case BitDepth::NOT_PROVIDED: + return std::make_tuple<>(SOX_ENCODING_SIGN2, 32); + case BitDepth::B8: + throw std::runtime_error( + format + " does not support 8-bit signed PCM encoding."); + default: + return std::make_tuple<>( + SOX_ENCODING_SIGN2, static_cast(bits_per_sample)); + } + case Encoding::PCM_UNSIGNED: + switch (bits_per_sample) { + case BitDepth::NOT_PROVIDED: + case BitDepth::B8: + return std::make_tuple<>(SOX_ENCODING_UNSIGNED, 8); + default: + throw std::runtime_error( + format + " only supports 8-bit for unsigned PCM encoding."); + } + case Encoding::PCM_FLOAT: + switch (bits_per_sample) { + case BitDepth::NOT_PROVIDED: + case BitDepth::B32: + return std::make_tuple<>(SOX_ENCODING_FLOAT, 32); + case BitDepth::B64: + return std::make_tuple<>(SOX_ENCODING_FLOAT, 64); + default: + throw std::runtime_error( + format + + " only supports 32-bit or 64-bit for floating-point PCM encoding."); + } + case Encoding::ULAW: + switch (bits_per_sample) { + case BitDepth::NOT_PROVIDED: + case BitDepth::B8: + return std::make_tuple<>(SOX_ENCODING_ULAW, 8); + default: + throw std::runtime_error( + format + " only supports 8-bit for mu-law encoding."); + } + case Encoding::ALAW: + switch (bits_per_sample) { + case BitDepth::NOT_PROVIDED: + case BitDepth::B8: + return std::make_tuple<>(SOX_ENCODING_ALAW, 8); + default: + throw std::runtime_error( + format + " only supports 8-bit for a-law encoding."); + } + default: + throw std::runtime_error( + format + " does not support encoding: " + to_string(encoding)); + } +} + +std::tuple get_save_encoding( + const std::string& format, + const py::dtype dtype, + const tl::optional encoding, + const tl::optional bits_per_sample) { + const Format fmt = get_format_from_string(format); + const Encoding enc = get_encoding_from_option(encoding); + const BitDepth bps = get_bit_depth_from_option(bits_per_sample); + + switch (fmt) { + case Format::WAV: + case Format::AMB: + return get_save_encoding_for_wav(format, dtype, enc, bps); + case Format::MP3: + if (enc != Encoding::NOT_PROVIDED) + throw std::runtime_error("mp3 does not support `encoding` option."); + if (bps != BitDepth::NOT_PROVIDED) + throw std::runtime_error( + "mp3 does not support `bits_per_sample` option."); + return std::make_tuple<>(SOX_ENCODING_MP3, 16); + case Format::HTK: + if (enc != Encoding::NOT_PROVIDED) + throw std::runtime_error("htk does not support `encoding` option."); + if (bps != BitDepth::NOT_PROVIDED) + throw std::runtime_error( + "htk does not support `bits_per_sample` option."); + return std::make_tuple<>(SOX_ENCODING_SIGN2, 16); + case Format::VORBIS: + if (enc != Encoding::NOT_PROVIDED) + throw std::runtime_error("vorbis does not support `encoding` option."); + if (bps != BitDepth::NOT_PROVIDED) + throw std::runtime_error( + "vorbis does not support `bits_per_sample` option."); + return std::make_tuple<>(SOX_ENCODING_VORBIS, 16); + case Format::AMR_NB: + if (enc != Encoding::NOT_PROVIDED) + throw std::runtime_error("amr-nb does not support `encoding` option."); + if (bps != BitDepth::NOT_PROVIDED) + throw std::runtime_error( + "amr-nb does not support `bits_per_sample` option."); + return std::make_tuple<>(SOX_ENCODING_AMR_NB, 16); + case Format::FLAC: + if (enc != Encoding::NOT_PROVIDED) + throw std::runtime_error("flac does not support `encoding` option."); + switch (bps) { + case BitDepth::B32: + case BitDepth::B64: + throw std::runtime_error( + "flac does not support `bits_per_sample` larger than 24."); + default: + return std::make_tuple<>( + SOX_ENCODING_FLAC, static_cast(bps)); + } + case Format::SPHERE: + switch (enc) { + case Encoding::NOT_PROVIDED: + case Encoding::PCM_SIGNED: + switch (bps) { + case BitDepth::NOT_PROVIDED: + return std::make_tuple<>(SOX_ENCODING_SIGN2, 32); + default: + return std::make_tuple<>( + SOX_ENCODING_SIGN2, static_cast(bps)); + } + case Encoding::PCM_UNSIGNED: + throw std::runtime_error( + "sph does not support unsigned integer PCM."); + case Encoding::PCM_FLOAT: + throw std::runtime_error("sph does not support floating point PCM."); + case Encoding::ULAW: + switch (bps) { + case BitDepth::NOT_PROVIDED: + case BitDepth::B8: + return std::make_tuple<>(SOX_ENCODING_ULAW, 8); + default: + throw std::runtime_error( + "sph only supports 8-bit for mu-law encoding."); + } + case Encoding::ALAW: + switch (bps) { + case BitDepth::NOT_PROVIDED: + case BitDepth::B8: + return std::make_tuple<>(SOX_ENCODING_ALAW, 8); + default: + return std::make_tuple<>( + SOX_ENCODING_ALAW, static_cast(bps)); + } + default: + throw std::runtime_error( + "sph does not support encoding: " + encoding.value()); + } + case Format::GSM: + if (enc != Encoding::NOT_PROVIDED) + throw std::runtime_error("gsm does not support `encoding` option."); + if (bps != BitDepth::NOT_PROVIDED) + throw std::runtime_error( + "gsm does not support `bits_per_sample` option."); + return std::make_tuple<>(SOX_ENCODING_GSM, 16); + + default: + throw std::runtime_error("Unsupported format: " + format); + } +} + +unsigned get_precision(const std::string filetype, py::dtype dtype) { + if (filetype == "mp3") + return SOX_UNSPEC; + if (filetype == "flac") + return 24; + if (filetype == "ogg" || filetype == "vorbis") + return SOX_UNSPEC; + if (filetype == "wav" || filetype == "amb") { + switch (dtype.num()) { + case 1: // byte in numpy dype num + return 8; + case 3: // short, in numpy dtype num + return 16; + case 5: // int, numpy dtype + return 32; + case 11: // float, numpy dtype + return 32; + default: + throw std::runtime_error("Unsupported dtype."); + } + } + if (filetype == "sph") + return 32; + if (filetype == "amr-nb") { + return 16; + } + if (filetype == "gsm") { + return 16; + } + if (filetype == "htk") { + return 16; + } + throw std::runtime_error("Unsupported file type: " + filetype); +} + +} // namespace + +sox_signalinfo_t get_signalinfo( + const py::array* waveform, + const int64_t sample_rate, + const std::string filetype, + const bool channels_first) { + return sox_signalinfo_t{ + /*rate=*/static_cast(sample_rate), + /*channels=*/ + static_cast(waveform->shape(channels_first ? 0 : 1)), + /*precision=*/get_precision(filetype, waveform->dtype()), + /*length=*/static_cast(waveform->size())}; +} + +sox_encodinginfo_t get_tensor_encodinginfo(py::dtype dtype) { + sox_encoding_t encoding = [&]() { + switch (dtype.num()) { + case 1: // byte + return SOX_ENCODING_UNSIGNED; + case 3: // short + return SOX_ENCODING_SIGN2; + case 5: // int32 + return SOX_ENCODING_SIGN2; + case 11: // float + return SOX_ENCODING_FLOAT; + default: + throw std::runtime_error("Unsupported dtype."); + } + }(); + unsigned bits_per_sample = [&]() { + switch (dtype.num()) { + case 1: // byte + return 8; + case 3: //short + return 16; + case 5: // int32 + return 32; + case 11: // float + return 32; + default: + throw std::runtime_error("Unsupported dtype."); + } + }(); + return sox_encodinginfo_t{ + /*encoding=*/encoding, + /*bits_per_sample=*/bits_per_sample, + /*compression=*/HUGE_VAL, + /*reverse_bytes=*/sox_option_default, + /*reverse_nibbles=*/sox_option_default, + /*reverse_bits=*/sox_option_default, + /*opposite_endian=*/sox_false}; +} + +sox_encodinginfo_t get_encodinginfo_for_save( + const std::string& format, + const py::dtype dtype, + const tl::optional compression, + const tl::optional encoding, + const tl::optional bits_per_sample) { + auto enc = get_save_encoding(format, dtype, encoding, bits_per_sample); + return sox_encodinginfo_t{ + /*encoding=*/std::get<0>(enc), + /*bits_per_sample=*/std::get<1>(enc), + /*compression=*/compression.value_or(HUGE_VAL), + /*reverse_bytes=*/sox_option_default, + /*reverse_nibbles=*/sox_option_default, + /*reverse_bits=*/sox_option_default, + /*opposite_endian=*/sox_false}; +} + +} // namespace paddleaudio +} // namespace sox_utils diff --git a/audio/paddleaudio/src/pybind/sox/utils.h b/audio/paddleaudio/src/pybind/sox/utils.h new file mode 100644 index 00000000..6fce6671 --- /dev/null +++ b/audio/paddleaudio/src/pybind/sox/utils.h @@ -0,0 +1,114 @@ +//code is from: https://github.com/pytorch/audio/blob/main/torchaudio/csrc/sox/utils.h with modification. +#pragma once + +#include +#include +#include +#include "paddleaudio/src/optional/optional.hpp" + +namespace py = pybind11; + +namespace paddleaudio { +namespace sox_utils { + +auto read_fileobj(py::object *fileobj, uint64_t size, char *buffer) -> uint64_t; + +void set_seed(const int64_t seed); + +void set_verbosity(const int64_t verbosity); + +void set_use_threads(const bool use_threads); + +void set_buffer_size(const int64_t buffer_size); + +int64_t get_buffer_size(); + +std::vector> list_effects(); + +std::vector list_read_formats(); + +std::vector list_write_formats(); + +//////////////////////////////////////////////////////////////////////////////// +// Utilities for sox_io / sox_effects implementations +//////////////////////////////////////////////////////////////////////////////// + +const std::unordered_set UNSUPPORTED_EFFECTS = + {"input", "output", "spectrogram", "noiseprof", "noisered", "splice"}; + +/// helper class to automatically close sox_format_t* +struct SoxFormat { + explicit SoxFormat(sox_format_t* fd) noexcept; + SoxFormat(const SoxFormat& other) = delete; + SoxFormat(SoxFormat&& other) = delete; + SoxFormat& operator=(const SoxFormat& other) = delete; + SoxFormat& operator=(SoxFormat&& other) = delete; + ~SoxFormat(); + sox_format_t* operator->() const noexcept; + operator sox_format_t*() const noexcept; + + void close(); + + private: + sox_format_t* fd_; +}; + +/// +/// Verify that input Tensor is 2D, CPU and either uin8, int16, int32 or float32 +void validate_input_tensor(const py::array); + +void validate_input_file(const SoxFormat& sf, const std::string& path); + +void validate_input_memfile(const SoxFormat &sf); +/// +/// Get target dtype for the given encoding and precision. +py::dtype get_dtype( + const sox_encoding_t encoding, + const unsigned precision); + +/// +/// Convert sox_sample_t buffer to uint8/int16/int32/float32 Tensor +/// NOTE: This function might modify the values in the input buffer to +/// reduce the number of memory copy. +/// @param buffer Pointer to buffer that contains audio data. +/// @param num_samples The number of samples to read. +/// @param num_channels The number of channels. Used to reshape the resulting +/// Tensor. +/// @param dtype Target dtype. Determines the output dtype and value range in +/// conjunction with normalization. +/// @param noramlize Perform normalization. Only effective when dtype is not +/// kFloat32. When effective, the output tensor is kFloat32 type and value range +/// is [-1.0, 1.0] +/// @param channels_first When True, output Tensor has shape of [num_channels, +/// num_frames]. +py::array convert_to_tensor( + sox_sample_t* buffer, + const int32_t num_samples, + const int32_t num_channels, + const py::dtype dtype, + const bool normalize, + const bool channels_first); + +/// Extract extension from file path +const std::string get_filetype(const std::string path); + +/// Get sox_signalinfo_t for passing a py::array object. +sox_signalinfo_t get_signalinfo( + const py::array* waveform, + const int64_t sample_rate, + const std::string filetype, + const bool channels_first); + +/// Get sox_encodinginfo_t for Tensor I/O +sox_encodinginfo_t get_tensor_encodinginfo(const py::dtype dtype); + +/// Get sox_encodinginfo_t for saving to file/file object +sox_encodinginfo_t get_encodinginfo_for_save( + const std::string& format, + const py::dtype dtype, + const tl::optional compression, + const tl::optional encoding, + const tl::optional bits_per_sample); + +} // namespace paddleaudio +} // namespace sox_utils diff --git a/audio/paddleaudio/src/utils.cpp b/audio/paddleaudio/src/utils.cpp new file mode 100644 index 00000000..a1e71822 --- /dev/null +++ b/audio/paddleaudio/src/utils.cpp @@ -0,0 +1,35 @@ +// this is from: https://github.com/pytorch/audio/blob/main/torchaudio/csrc/utils.cpp with modification. + +namespace paddleaudio { + +namespace { + +bool is_sox_available() { +#ifdef INCLUDE_SOX + return true; +#else + return false; +#endif +} + +bool is_kaldi_available() { +#ifdef INCLUDE_KALDI + return true; +#else + return false; +#endif +} + +// It tells whether paddleaudio was compiled with ffmpeg +// not the runtime availability. +bool is_ffmpeg_available() { +#ifdef USE_FFMPEG + return true; +#else + return false; +#endif +} + +} // namespace + +} // namespace paddleaudio diff --git a/audio/paddleaudio/third_party/.gitignore b/audio/paddleaudio/third_party/.gitignore new file mode 100644 index 00000000..2d788f6b --- /dev/null +++ b/audio/paddleaudio/third_party/.gitignore @@ -0,0 +1,2 @@ +archives/ +install/ diff --git a/audio/paddleaudio/third_party/CMakeLists.txt b/audio/paddleaudio/third_party/CMakeLists.txt new file mode 100644 index 00000000..43288f39 --- /dev/null +++ b/audio/paddleaudio/third_party/CMakeLists.txt @@ -0,0 +1,15 @@ +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden") + +################################################################################ +# sox +################################################################################ +if (BUILD_SOX) + add_subdirectory(sox) +endif() + +################################################################################ +# kaldi +################################################################################ +if (BUILD_KALDI) + add_subdirectory(kaldi) +endif() \ No newline at end of file diff --git a/audio/paddleaudio/third_party/kaldi/CMakeLists.txt b/audio/paddleaudio/third_party/kaldi/CMakeLists.txt new file mode 100644 index 00000000..e63fb578 --- /dev/null +++ b/audio/paddleaudio/third_party/kaldi/CMakeLists.txt @@ -0,0 +1,111 @@ +# checkout the thirdparty/kaldi/base/kaldi-types.h +# compile kaldi without openfst +add_definitions("-DCOMPILE_WITHOUT_OPENFST") + +if ((NOT EXISTS ${CMAKE_CURRENT_LIST_DIR}/base)) + file(COPY ../../../../speechx/speechx/kaldi/base DESTINATION ${CMAKE_CURRENT_LIST_DIR}) + file(COPY ../../../../speechx/speechx/kaldi/feat DESTINATION ${CMAKE_CURRENT_LIST_DIR}) + file(COPY ../../../../speechx/speechx/kaldi/matrix DESTINATION ${CMAKE_CURRENT_LIST_DIR}) + file(COPY ../../../../speechx/speechx/kaldi/util DESTINATION ${CMAKE_CURRENT_LIST_DIR}) +endif() + +# kaldi-base +add_library(kaldi-base STATIC + base/io-funcs.cc + base/kaldi-error.cc + base/kaldi-math.cc + base/kaldi-utils.cc + base/timer.cc +) +target_include_directories(kaldi-base PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +# kaldi-matrix +add_library(kaldi-matrix STATIC + matrix/compressed-matrix.cc + matrix/matrix-functions.cc + matrix/kaldi-matrix.cc + matrix/kaldi-vector.cc + matrix/optimization.cc + matrix/packed-matrix.cc + matrix/qr.cc + matrix/sparse-matrix.cc + matrix/sp-matrix.cc + matrix/srfft.cc + matrix/tp-matrix.cc +) +target_include_directories(kaldi-matrix PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +if (NOT MSVC) + target_link_libraries(kaldi-matrix PUBLIC kaldi-base libopenblas) +else() + target_link_libraries(kaldi-matrix PUBLIC kaldi-base openblas) +endif() + +# kaldi-util +add_library(kaldi-util STATIC + util/kaldi-holder.cc + util/kaldi-io.cc + util/kaldi-semaphore.cc + util/kaldi-table.cc + util/kaldi-thread.cc + util/parse-options.cc + util/simple-io-funcs.cc + util/simple-options.cc + util/text-utils.cc +) +target_include_directories(kaldi-util PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(kaldi-util PUBLIC kaldi-base kaldi-matrix) + +# kaldi-feat-common +add_library(kaldi-feat-common STATIC + feat/cmvn.cc + feat/feature-functions.cc + feat/feature-window.cc + feat/mel-computations.cc + feat/pitch-functions.cc + feat/resample.cc + feat/signal.cc + feat/wave-reader.cc +) +target_include_directories(kaldi-feat-common PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(kaldi-feat-common PUBLIC kaldi-base kaldi-matrix kaldi-util) + + +# kaldi-mfcc +add_library(kaldi-mfcc STATIC + feat/feature-mfcc.cc +) +target_include_directories(kaldi-mfcc PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(kaldi-mfcc PUBLIC kaldi-feat-common) + + +# kaldi-fbank +add_library(kaldi-fbank STATIC + feat/feature-fbank.cc +) +target_include_directories(kaldi-fbank PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(kaldi-fbank PUBLIC kaldi-feat-common) + + +set(KALDI_LIBRARIES + ${CMAKE_CURRENT_BINARY_DIR}/libkaldi-base.a + ${CMAKE_CURRENT_BINARY_DIR}/libkaldi-matrix.a + ${CMAKE_CURRENT_BINARY_DIR}/libkaldi-util.a + ${CMAKE_CURRENT_BINARY_DIR}/libkaldi-feat-common.a + ${CMAKE_CURRENT_BINARY_DIR}/libkaldi-mfcc.a + ${CMAKE_CURRENT_BINARY_DIR}/libkaldi-fbank.a +) + +add_library(libkaldi INTERFACE) +add_dependencies(libkaldi kaldi-base kaldi-matrix kaldi-util kaldi-feat-common kaldi-mfcc kaldi-fbank) +target_include_directories(libkaldi INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) + +if (APPLE) + target_link_libraries(libkaldi INTERFACE ${KALDI_LIBRARIES} libopenblas ${GFORTRAN_LIBRARIES_DIR}/libgfortran.a ${GFORTRAN_LIBRARIES_DIR}/libquadmath.a ${GFORTRAN_LIBRARIES_DIR}/libgcc_s.1.1.dylib) +elseif (MSVC) + target_link_libraries(libkaldi INTERFACE kaldi-base kaldi-matrix kaldi-util kaldi-feat-common kaldi-mfcc kaldi-fbank openblas) +else() + target_link_libraries(libkaldi INTERFACE -Wl,--start-group -Wl,--whole-archive ${KALDI_LIBRARIES} libopenblas.a gfortran -Wl,--no-whole-archive -Wl,--end-group) +endif() + +target_compile_definitions(libkaldi INTERFACE "-DCOMPILE_WITHOUT_OPENFST") diff --git a/audio/paddleaudio/third_party/patches/config.guess b/audio/paddleaudio/third_party/patches/config.guess new file mode 100644 index 00000000..7f76b622 --- /dev/null +++ b/audio/paddleaudio/third_party/patches/config.guess @@ -0,0 +1,1754 @@ +#! /bin/sh +# Attempt to guess a canonical system name. +# Copyright 1992-2022 Free Software Foundation, Inc. + +# shellcheck disable=SC2006,SC2268 # see below for rationale + +timestamp='2022-01-09' + +# This file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that +# program. This Exception is an additional permission under section 7 +# of the GNU General Public License, version 3 ("GPLv3"). +# +# Originally written by Per Bothner; maintained since 2000 by Ben Elliston. +# +# You can get the latest version of this script from: +# https://git.savannah.gnu.org/cgit/config.git/plain/config.guess +# +# Please send patches to . + + +# The "shellcheck disable" line above the timestamp inhibits complaints +# about features and limitations of the classic Bourne shell that were +# superseded or lifted in POSIX. However, this script identifies a wide +# variety of pre-POSIX systems that do not have POSIX shells at all, and +# even some reasonably current systems (Solaris 10 as case-in-point) still +# have a pre-POSIX /bin/sh. + + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] + +Output the configuration name of the system \`$me' is run on. + +Options: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to ." + +version="\ +GNU config.guess ($timestamp) + +Originally written by Per Bothner. +Copyright 1992-2022 Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit ;; + --version | -v ) + echo "$version" ; exit ;; + --help | --h* | -h ) + echo "$usage"; exit ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" >&2 + exit 1 ;; + * ) + break ;; + esac +done + +if test $# != 0; then + echo "$me: too many arguments$help" >&2 + exit 1 +fi + +# Just in case it came from the environment. +GUESS= + +# CC_FOR_BUILD -- compiler used by this script. Note that the use of a +# compiler to aid in system detection is discouraged as it requires +# temporary files to be created and, as you can see below, it is a +# headache to deal with in a portable fashion. + +# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still +# use `HOST_CC' if defined, but it is deprecated. + +# Portable tmp directory creation inspired by the Autoconf team. + +tmp= +# shellcheck disable=SC2172 +trap 'test -z "$tmp" || rm -fr "$tmp"' 0 1 2 13 15 + +set_cc_for_build() { + # prevent multiple calls if $tmp is already set + test "$tmp" && return 0 + : "${TMPDIR=/tmp}" + # shellcheck disable=SC2039,SC3028 + { tmp=`(umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } || + { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir "$tmp" 2>/dev/null) ; } || + { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir "$tmp" 2>/dev/null) && echo "Warning: creating insecure temp directory" >&2 ; } || + { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } + dummy=$tmp/dummy + case ${CC_FOR_BUILD-},${HOST_CC-},${CC-} in + ,,) echo "int x;" > "$dummy.c" + for driver in cc gcc c89 c99 ; do + if ($driver -c -o "$dummy.o" "$dummy.c") >/dev/null 2>&1 ; then + CC_FOR_BUILD=$driver + break + fi + done + if test x"$CC_FOR_BUILD" = x ; then + CC_FOR_BUILD=no_compiler_found + fi + ;; + ,,*) CC_FOR_BUILD=$CC ;; + ,*,*) CC_FOR_BUILD=$HOST_CC ;; + esac +} + +# This is needed to find uname on a Pyramid OSx when run in the BSD universe. +# (ghazi@noc.rutgers.edu 1994-08-24) +if test -f /.attbin/uname ; then + PATH=$PATH:/.attbin ; export PATH +fi + +UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown +UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown +UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown +UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown + +case $UNAME_SYSTEM in +Linux|GNU|GNU/*) + LIBC=unknown + + set_cc_for_build + cat <<-EOF > "$dummy.c" + #include + #if defined(__UCLIBC__) + LIBC=uclibc + #elif defined(__dietlibc__) + LIBC=dietlibc + #elif defined(__GLIBC__) + LIBC=gnu + #else + #include + /* First heuristic to detect musl libc. */ + #ifdef __DEFINED_va_list + LIBC=musl + #endif + #endif + EOF + cc_set_libc=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^LIBC' | sed 's, ,,g'` + eval "$cc_set_libc" + + # Second heuristic to detect musl libc. + if [ "$LIBC" = unknown ] && + command -v ldd >/dev/null && + ldd --version 2>&1 | grep -q ^musl; then + LIBC=musl + fi + + # If the system lacks a compiler, then just pick glibc. + # We could probably try harder. + if [ "$LIBC" = unknown ]; then + LIBC=gnu + fi + ;; +esac + +# Note: order is significant - the case branches are not exclusive. + +case $UNAME_MACHINE:$UNAME_SYSTEM:$UNAME_RELEASE:$UNAME_VERSION in + *:NetBSD:*:*) + # NetBSD (nbsd) targets should (where applicable) match one or + # more of the tuples: *-*-netbsdelf*, *-*-netbsdaout*, + # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently + # switched to ELF, *-*-netbsd* would select the old + # object file format. This provides both forward + # compatibility and a consistent mechanism for selecting the + # object file format. + # + # Note: NetBSD doesn't particularly care about the vendor + # portion of the name. We always set it to "unknown". + UNAME_MACHINE_ARCH=`(uname -p 2>/dev/null || \ + /sbin/sysctl -n hw.machine_arch 2>/dev/null || \ + /usr/sbin/sysctl -n hw.machine_arch 2>/dev/null || \ + echo unknown)` + case $UNAME_MACHINE_ARCH in + aarch64eb) machine=aarch64_be-unknown ;; + armeb) machine=armeb-unknown ;; + arm*) machine=arm-unknown ;; + sh3el) machine=shl-unknown ;; + sh3eb) machine=sh-unknown ;; + sh5el) machine=sh5le-unknown ;; + earmv*) + arch=`echo "$UNAME_MACHINE_ARCH" | sed -e 's,^e\(armv[0-9]\).*$,\1,'` + endian=`echo "$UNAME_MACHINE_ARCH" | sed -ne 's,^.*\(eb\)$,\1,p'` + machine=${arch}${endian}-unknown + ;; + *) machine=$UNAME_MACHINE_ARCH-unknown ;; + esac + # The Operating System including object format, if it has switched + # to ELF recently (or will in the future) and ABI. + case $UNAME_MACHINE_ARCH in + earm*) + os=netbsdelf + ;; + arm*|i386|m68k|ns32k|sh3*|sparc|vax) + set_cc_for_build + if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ELF__ + then + # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). + # Return netbsd for either. FIX? + os=netbsd + else + os=netbsdelf + fi + ;; + *) + os=netbsd + ;; + esac + # Determine ABI tags. + case $UNAME_MACHINE_ARCH in + earm*) + expr='s/^earmv[0-9]/-eabi/;s/eb$//' + abi=`echo "$UNAME_MACHINE_ARCH" | sed -e "$expr"` + ;; + esac + # The OS release + # Debian GNU/NetBSD machines have a different userland, and + # thus, need a distinct triplet. However, they do not need + # kernel version information, so it can be replaced with a + # suitable tag, in the style of linux-gnu. + case $UNAME_VERSION in + Debian*) + release='-gnu' + ;; + *) + release=`echo "$UNAME_RELEASE" | sed -e 's/[-_].*//' | cut -d. -f1,2` + ;; + esac + # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: + # contains redundant information, the shorter form: + # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. + GUESS=$machine-${os}${release}${abi-} + ;; + *:Bitrig:*:*) + UNAME_MACHINE_ARCH=`arch | sed 's/Bitrig.//'` + GUESS=$UNAME_MACHINE_ARCH-unknown-bitrig$UNAME_RELEASE + ;; + *:OpenBSD:*:*) + UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'` + GUESS=$UNAME_MACHINE_ARCH-unknown-openbsd$UNAME_RELEASE + ;; + *:SecBSD:*:*) + UNAME_MACHINE_ARCH=`arch | sed 's/SecBSD.//'` + GUESS=$UNAME_MACHINE_ARCH-unknown-secbsd$UNAME_RELEASE + ;; + *:LibertyBSD:*:*) + UNAME_MACHINE_ARCH=`arch | sed 's/^.*BSD\.//'` + GUESS=$UNAME_MACHINE_ARCH-unknown-libertybsd$UNAME_RELEASE + ;; + *:MidnightBSD:*:*) + GUESS=$UNAME_MACHINE-unknown-midnightbsd$UNAME_RELEASE + ;; + *:ekkoBSD:*:*) + GUESS=$UNAME_MACHINE-unknown-ekkobsd$UNAME_RELEASE + ;; + *:SolidBSD:*:*) + GUESS=$UNAME_MACHINE-unknown-solidbsd$UNAME_RELEASE + ;; + *:OS108:*:*) + GUESS=$UNAME_MACHINE-unknown-os108_$UNAME_RELEASE + ;; + macppc:MirBSD:*:*) + GUESS=powerpc-unknown-mirbsd$UNAME_RELEASE + ;; + *:MirBSD:*:*) + GUESS=$UNAME_MACHINE-unknown-mirbsd$UNAME_RELEASE + ;; + *:Sortix:*:*) + GUESS=$UNAME_MACHINE-unknown-sortix + ;; + *:Twizzler:*:*) + GUESS=$UNAME_MACHINE-unknown-twizzler + ;; + *:Redox:*:*) + GUESS=$UNAME_MACHINE-unknown-redox + ;; + mips:OSF1:*.*) + GUESS=mips-dec-osf1 + ;; + alpha:OSF1:*:*) + # Reset EXIT trap before exiting to avoid spurious non-zero exit code. + trap '' 0 + case $UNAME_RELEASE in + *4.0) + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` + ;; + *5.*) + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'` + ;; + esac + # According to Compaq, /usr/sbin/psrinfo has been available on + # OSF/1 and Tru64 systems produced since 1995. I hope that + # covers most systems running today. This code pipes the CPU + # types through head -n 1, so we only detect the type of CPU 0. + ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1` + case $ALPHA_CPU_TYPE in + "EV4 (21064)") + UNAME_MACHINE=alpha ;; + "EV4.5 (21064)") + UNAME_MACHINE=alpha ;; + "LCA4 (21066/21068)") + UNAME_MACHINE=alpha ;; + "EV5 (21164)") + UNAME_MACHINE=alphaev5 ;; + "EV5.6 (21164A)") + UNAME_MACHINE=alphaev56 ;; + "EV5.6 (21164PC)") + UNAME_MACHINE=alphapca56 ;; + "EV5.7 (21164PC)") + UNAME_MACHINE=alphapca57 ;; + "EV6 (21264)") + UNAME_MACHINE=alphaev6 ;; + "EV6.7 (21264A)") + UNAME_MACHINE=alphaev67 ;; + "EV6.8CB (21264C)") + UNAME_MACHINE=alphaev68 ;; + "EV6.8AL (21264B)") + UNAME_MACHINE=alphaev68 ;; + "EV6.8CX (21264D)") + UNAME_MACHINE=alphaev68 ;; + "EV6.9A (21264/EV69A)") + UNAME_MACHINE=alphaev69 ;; + "EV7 (21364)") + UNAME_MACHINE=alphaev7 ;; + "EV7.9 (21364A)") + UNAME_MACHINE=alphaev79 ;; + esac + # A Pn.n version is a patched version. + # A Vn.n version is a released version. + # A Tn.n version is a released field test version. + # A Xn.n version is an unreleased experimental baselevel. + # 1.2 uses "1.2" for uname -r. + OSF_REL=`echo "$UNAME_RELEASE" | sed -e 's/^[PVTX]//' | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz` + GUESS=$UNAME_MACHINE-dec-osf$OSF_REL + ;; + Amiga*:UNIX_System_V:4.0:*) + GUESS=m68k-unknown-sysv4 + ;; + *:[Aa]miga[Oo][Ss]:*:*) + GUESS=$UNAME_MACHINE-unknown-amigaos + ;; + *:[Mm]orph[Oo][Ss]:*:*) + GUESS=$UNAME_MACHINE-unknown-morphos + ;; + *:OS/390:*:*) + GUESS=i370-ibm-openedition + ;; + *:z/VM:*:*) + GUESS=s390-ibm-zvmoe + ;; + *:OS400:*:*) + GUESS=powerpc-ibm-os400 + ;; + arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) + GUESS=arm-acorn-riscix$UNAME_RELEASE + ;; + arm*:riscos:*:*|arm*:RISCOS:*:*) + GUESS=arm-unknown-riscos + ;; + SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) + GUESS=hppa1.1-hitachi-hiuxmpp + ;; + Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) + # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. + case `(/bin/universe) 2>/dev/null` in + att) GUESS=pyramid-pyramid-sysv3 ;; + *) GUESS=pyramid-pyramid-bsd ;; + esac + ;; + NILE*:*:*:dcosx) + GUESS=pyramid-pyramid-svr4 + ;; + DRS?6000:unix:4.0:6*) + GUESS=sparc-icl-nx6 + ;; + DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*) + case `/usr/bin/uname -p` in + sparc) GUESS=sparc-icl-nx7 ;; + esac + ;; + s390x:SunOS:*:*) + SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` + GUESS=$UNAME_MACHINE-ibm-solaris2$SUN_REL + ;; + sun4H:SunOS:5.*:*) + SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` + GUESS=sparc-hal-solaris2$SUN_REL + ;; + sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) + SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` + GUESS=sparc-sun-solaris2$SUN_REL + ;; + i86pc:AuroraUX:5.*:* | i86xen:AuroraUX:5.*:*) + GUESS=i386-pc-auroraux$UNAME_RELEASE + ;; + i86pc:SunOS:5.*:* | i86xen:SunOS:5.*:*) + set_cc_for_build + SUN_ARCH=i386 + # If there is a compiler, see if it is configured for 64-bit objects. + # Note that the Sun cc does not turn __LP64__ into 1 like gcc does. + # This test works for both compilers. + if test "$CC_FOR_BUILD" != no_compiler_found; then + if (echo '#ifdef __amd64'; echo IS_64BIT_ARCH; echo '#endif') | \ + (CCOPTS="" $CC_FOR_BUILD -m64 -E - 2>/dev/null) | \ + grep IS_64BIT_ARCH >/dev/null + then + SUN_ARCH=x86_64 + fi + fi + SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` + GUESS=$SUN_ARCH-pc-solaris2$SUN_REL + ;; + sun4*:SunOS:6*:*) + # According to config.sub, this is the proper way to canonicalize + # SunOS6. Hard to guess exactly what SunOS6 will be like, but + # it's likely to be more like Solaris than SunOS4. + SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` + GUESS=sparc-sun-solaris3$SUN_REL + ;; + sun4*:SunOS:*:*) + case `/usr/bin/arch -k` in + Series*|S4*) + UNAME_RELEASE=`uname -v` + ;; + esac + # Japanese Language versions have a version number like `4.1.3-JL'. + SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/-/_/'` + GUESS=sparc-sun-sunos$SUN_REL + ;; + sun3*:SunOS:*:*) + GUESS=m68k-sun-sunos$UNAME_RELEASE + ;; + sun*:*:4.2BSD:*) + UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` + test "x$UNAME_RELEASE" = x && UNAME_RELEASE=3 + case `/bin/arch` in + sun3) + GUESS=m68k-sun-sunos$UNAME_RELEASE + ;; + sun4) + GUESS=sparc-sun-sunos$UNAME_RELEASE + ;; + esac + ;; + aushp:SunOS:*:*) + GUESS=sparc-auspex-sunos$UNAME_RELEASE + ;; + # The situation for MiNT is a little confusing. The machine name + # can be virtually everything (everything which is not + # "atarist" or "atariste" at least should have a processor + # > m68000). The system name ranges from "MiNT" over "FreeMiNT" + # to the lowercase version "mint" (or "freemint"). Finally + # the system name "TOS" denotes a system which is actually not + # MiNT. But MiNT is downward compatible to TOS, so this should + # be no problem. + atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) + GUESS=m68k-atari-mint$UNAME_RELEASE + ;; + atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) + GUESS=m68k-atari-mint$UNAME_RELEASE + ;; + *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) + GUESS=m68k-atari-mint$UNAME_RELEASE + ;; + milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) + GUESS=m68k-milan-mint$UNAME_RELEASE + ;; + hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) + GUESS=m68k-hades-mint$UNAME_RELEASE + ;; + *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) + GUESS=m68k-unknown-mint$UNAME_RELEASE + ;; + m68k:machten:*:*) + GUESS=m68k-apple-machten$UNAME_RELEASE + ;; + powerpc:machten:*:*) + GUESS=powerpc-apple-machten$UNAME_RELEASE + ;; + RISC*:Mach:*:*) + GUESS=mips-dec-mach_bsd4.3 + ;; + RISC*:ULTRIX:*:*) + GUESS=mips-dec-ultrix$UNAME_RELEASE + ;; + VAX*:ULTRIX*:*:*) + GUESS=vax-dec-ultrix$UNAME_RELEASE + ;; + 2020:CLIX:*:* | 2430:CLIX:*:*) + GUESS=clipper-intergraph-clix$UNAME_RELEASE + ;; + mips:*:*:UMIPS | mips:*:*:RISCos) + set_cc_for_build + sed 's/^ //' << EOF > "$dummy.c" +#ifdef __cplusplus +#include /* for printf() prototype */ + int main (int argc, char *argv[]) { +#else + int main (argc, argv) int argc; char *argv[]; { +#endif + #if defined (host_mips) && defined (MIPSEB) + #if defined (SYSTYPE_SYSV) + printf ("mips-mips-riscos%ssysv\\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_SVR4) + printf ("mips-mips-riscos%ssvr4\\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) + printf ("mips-mips-riscos%sbsd\\n", argv[1]); exit (0); + #endif + #endif + exit (-1); + } +EOF + $CC_FOR_BUILD -o "$dummy" "$dummy.c" && + dummyarg=`echo "$UNAME_RELEASE" | sed -n 's/\([0-9]*\).*/\1/p'` && + SYSTEM_NAME=`"$dummy" "$dummyarg"` && + { echo "$SYSTEM_NAME"; exit; } + GUESS=mips-mips-riscos$UNAME_RELEASE + ;; + Motorola:PowerMAX_OS:*:*) + GUESS=powerpc-motorola-powermax + ;; + Motorola:*:4.3:PL8-*) + GUESS=powerpc-harris-powermax + ;; + Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) + GUESS=powerpc-harris-powermax + ;; + Night_Hawk:Power_UNIX:*:*) + GUESS=powerpc-harris-powerunix + ;; + m88k:CX/UX:7*:*) + GUESS=m88k-harris-cxux7 + ;; + m88k:*:4*:R4*) + GUESS=m88k-motorola-sysv4 + ;; + m88k:*:3*:R3*) + GUESS=m88k-motorola-sysv3 + ;; + AViiON:dgux:*:*) + # DG/UX returns AViiON for all architectures + UNAME_PROCESSOR=`/usr/bin/uname -p` + if test "$UNAME_PROCESSOR" = mc88100 || test "$UNAME_PROCESSOR" = mc88110 + then + if test "$TARGET_BINARY_INTERFACE"x = m88kdguxelfx || \ + test "$TARGET_BINARY_INTERFACE"x = x + then + GUESS=m88k-dg-dgux$UNAME_RELEASE + else + GUESS=m88k-dg-dguxbcs$UNAME_RELEASE + fi + else + GUESS=i586-dg-dgux$UNAME_RELEASE + fi + ;; + M88*:DolphinOS:*:*) # DolphinOS (SVR3) + GUESS=m88k-dolphin-sysv3 + ;; + M88*:*:R3*:*) + # Delta 88k system running SVR3 + GUESS=m88k-motorola-sysv3 + ;; + XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) + GUESS=m88k-tektronix-sysv3 + ;; + Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) + GUESS=m68k-tektronix-bsd + ;; + *:IRIX*:*:*) + IRIX_REL=`echo "$UNAME_RELEASE" | sed -e 's/-/_/g'` + GUESS=mips-sgi-irix$IRIX_REL + ;; + ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. + GUESS=romp-ibm-aix # uname -m gives an 8 hex-code CPU id + ;; # Note that: echo "'`uname -s`'" gives 'AIX ' + i*86:AIX:*:*) + GUESS=i386-ibm-aix + ;; + ia64:AIX:*:*) + if test -x /usr/bin/oslevel ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV=$UNAME_VERSION.$UNAME_RELEASE + fi + GUESS=$UNAME_MACHINE-ibm-aix$IBM_REV + ;; + *:AIX:2:3) + if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then + set_cc_for_build + sed 's/^ //' << EOF > "$dummy.c" + #include + + main() + { + if (!__power_pc()) + exit(1); + puts("powerpc-ibm-aix3.2.5"); + exit(0); + } +EOF + if $CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=`"$dummy"` + then + GUESS=$SYSTEM_NAME + else + GUESS=rs6000-ibm-aix3.2.5 + fi + elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then + GUESS=rs6000-ibm-aix3.2.4 + else + GUESS=rs6000-ibm-aix3.2 + fi + ;; + *:AIX:*:[4567]) + IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'` + if /usr/sbin/lsattr -El "$IBM_CPU_ID" | grep ' POWER' >/dev/null 2>&1; then + IBM_ARCH=rs6000 + else + IBM_ARCH=powerpc + fi + if test -x /usr/bin/lslpp ; then + IBM_REV=`/usr/bin/lslpp -Lqc bos.rte.libc | \ + awk -F: '{ print $3 }' | sed s/[0-9]*$/0/` + else + IBM_REV=$UNAME_VERSION.$UNAME_RELEASE + fi + GUESS=$IBM_ARCH-ibm-aix$IBM_REV + ;; + *:AIX:*:*) + GUESS=rs6000-ibm-aix + ;; + ibmrt:4.4BSD:*|romp-ibm:4.4BSD:*) + GUESS=romp-ibm-bsd4.4 + ;; + ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and + GUESS=romp-ibm-bsd$UNAME_RELEASE # 4.3 with uname added to + ;; # report: romp-ibm BSD 4.3 + *:BOSX:*:*) + GUESS=rs6000-bull-bosx + ;; + DPX/2?00:B.O.S.:*:*) + GUESS=m68k-bull-sysv3 + ;; + 9000/[34]??:4.3bsd:1.*:*) + GUESS=m68k-hp-bsd + ;; + hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) + GUESS=m68k-hp-bsd4.4 + ;; + 9000/[34678]??:HP-UX:*:*) + HPUX_REV=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*.[0B]*//'` + case $UNAME_MACHINE in + 9000/31?) HP_ARCH=m68000 ;; + 9000/[34]??) HP_ARCH=m68k ;; + 9000/[678][0-9][0-9]) + if test -x /usr/bin/getconf; then + sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` + sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` + case $sc_cpu_version in + 523) HP_ARCH=hppa1.0 ;; # CPU_PA_RISC1_0 + 528) HP_ARCH=hppa1.1 ;; # CPU_PA_RISC1_1 + 532) # CPU_PA_RISC2_0 + case $sc_kernel_bits in + 32) HP_ARCH=hppa2.0n ;; + 64) HP_ARCH=hppa2.0w ;; + '') HP_ARCH=hppa2.0 ;; # HP-UX 10.20 + esac ;; + esac + fi + if test "$HP_ARCH" = ""; then + set_cc_for_build + sed 's/^ //' << EOF > "$dummy.c" + + #define _HPUX_SOURCE + #include + #include + + int main () + { + #if defined(_SC_KERNEL_BITS) + long bits = sysconf(_SC_KERNEL_BITS); + #endif + long cpu = sysconf (_SC_CPU_VERSION); + + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1"); break; + case CPU_PA_RISC2_0: + #if defined(_SC_KERNEL_BITS) + switch (bits) + { + case 64: puts ("hppa2.0w"); break; + case 32: puts ("hppa2.0n"); break; + default: puts ("hppa2.0"); break; + } break; + #else /* !defined(_SC_KERNEL_BITS) */ + puts ("hppa2.0"); break; + #endif + default: puts ("hppa1.0"); break; + } + exit (0); + } +EOF + (CCOPTS="" $CC_FOR_BUILD -o "$dummy" "$dummy.c" 2>/dev/null) && HP_ARCH=`"$dummy"` + test -z "$HP_ARCH" && HP_ARCH=hppa + fi ;; + esac + if test "$HP_ARCH" = hppa2.0w + then + set_cc_for_build + + # hppa2.0w-hp-hpux* has a 64-bit kernel and a compiler generating + # 32-bit code. hppa64-hp-hpux* has the same kernel and a compiler + # generating 64-bit code. GNU and HP use different nomenclature: + # + # $ CC_FOR_BUILD=cc ./config.guess + # => hppa2.0w-hp-hpux11.23 + # $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess + # => hppa64-hp-hpux11.23 + + if echo __LP64__ | (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | + grep -q __LP64__ + then + HP_ARCH=hppa2.0w + else + HP_ARCH=hppa64 + fi + fi + GUESS=$HP_ARCH-hp-hpux$HPUX_REV + ;; + ia64:HP-UX:*:*) + HPUX_REV=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*.[0B]*//'` + GUESS=ia64-hp-hpux$HPUX_REV + ;; + 3050*:HI-UX:*:*) + set_cc_for_build + sed 's/^ //' << EOF > "$dummy.c" + #include + int + main () + { + long cpu = sysconf (_SC_CPU_VERSION); + /* The order matters, because CPU_IS_HP_MC68K erroneously returns + true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct + results, however. */ + if (CPU_IS_PA_RISC (cpu)) + { + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; + case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; + default: puts ("hppa-hitachi-hiuxwe2"); break; + } + } + else if (CPU_IS_HP_MC68K (cpu)) + puts ("m68k-hitachi-hiuxwe2"); + else puts ("unknown-hitachi-hiuxwe2"); + exit (0); + } +EOF + $CC_FOR_BUILD -o "$dummy" "$dummy.c" && SYSTEM_NAME=`"$dummy"` && + { echo "$SYSTEM_NAME"; exit; } + GUESS=unknown-hitachi-hiuxwe2 + ;; + 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:*) + GUESS=hppa1.1-hp-bsd + ;; + 9000/8??:4.3bsd:*:*) + GUESS=hppa1.0-hp-bsd + ;; + *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) + GUESS=hppa1.0-hp-mpeix + ;; + hp7??:OSF1:*:* | hp8?[79]:OSF1:*:*) + GUESS=hppa1.1-hp-osf + ;; + hp8??:OSF1:*:*) + GUESS=hppa1.0-hp-osf + ;; + i*86:OSF1:*:*) + if test -x /usr/sbin/sysversion ; then + GUESS=$UNAME_MACHINE-unknown-osf1mk + else + GUESS=$UNAME_MACHINE-unknown-osf1 + fi + ;; + parisc*:Lites*:*:*) + GUESS=hppa1.1-hp-lites + ;; + C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) + GUESS=c1-convex-bsd + ;; + C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit ;; + C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) + GUESS=c34-convex-bsd + ;; + C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) + GUESS=c38-convex-bsd + ;; + C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) + GUESS=c4-convex-bsd + ;; + CRAY*Y-MP:*:*:*) + CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'` + GUESS=ymp-cray-unicos$CRAY_REL + ;; + CRAY*[A-Z]90:*:*:*) + echo "$UNAME_MACHINE"-cray-unicos"$UNAME_RELEASE" \ + | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ + -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \ + -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*TS:*:*:*) + CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'` + GUESS=t90-cray-unicos$CRAY_REL + ;; + CRAY*T3E:*:*:*) + CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'` + GUESS=alphaev5-cray-unicosmk$CRAY_REL + ;; + CRAY*SV1:*:*:*) + CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'` + GUESS=sv1-cray-unicos$CRAY_REL + ;; + *:UNICOS/mp:*:*) + CRAY_REL=`echo "$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/'` + GUESS=craynv-cray-unicosmp$CRAY_REL + ;; + F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) + FUJITSU_PROC=`uname -m | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz` + FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'` + FUJITSU_REL=`echo "$UNAME_RELEASE" | sed -e 's/ /_/'` + GUESS=${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL} + ;; + 5000:UNIX_System_V:4.*:*) + FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'` + FUJITSU_REL=`echo "$UNAME_RELEASE" | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/ /_/'` + GUESS=sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL} + ;; + i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) + GUESS=$UNAME_MACHINE-pc-bsdi$UNAME_RELEASE + ;; + sparc*:BSD/OS:*:*) + GUESS=sparc-unknown-bsdi$UNAME_RELEASE + ;; + *:BSD/OS:*:*) + GUESS=$UNAME_MACHINE-unknown-bsdi$UNAME_RELEASE + ;; + arm:FreeBSD:*:*) + UNAME_PROCESSOR=`uname -p` + set_cc_for_build + if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ARM_PCS_VFP + then + FREEBSD_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` + GUESS=$UNAME_PROCESSOR-unknown-freebsd$FREEBSD_REL-gnueabi + else + FREEBSD_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` + GUESS=$UNAME_PROCESSOR-unknown-freebsd$FREEBSD_REL-gnueabihf + fi + ;; + *:FreeBSD:*:*) + UNAME_PROCESSOR=`/usr/bin/uname -p` + case $UNAME_PROCESSOR in + amd64) + UNAME_PROCESSOR=x86_64 ;; + i386) + UNAME_PROCESSOR=i586 ;; + esac + FREEBSD_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` + GUESS=$UNAME_PROCESSOR-unknown-freebsd$FREEBSD_REL + ;; + i*:CYGWIN*:*) + GUESS=$UNAME_MACHINE-pc-cygwin + ;; + *:MINGW64*:*) + GUESS=$UNAME_MACHINE-pc-mingw64 + ;; + *:MINGW*:*) + GUESS=$UNAME_MACHINE-pc-mingw32 + ;; + *:MSYS*:*) + GUESS=$UNAME_MACHINE-pc-msys + ;; + i*:PW*:*) + GUESS=$UNAME_MACHINE-pc-pw32 + ;; + *:SerenityOS:*:*) + GUESS=$UNAME_MACHINE-pc-serenity + ;; + *:Interix*:*) + case $UNAME_MACHINE in + x86) + GUESS=i586-pc-interix$UNAME_RELEASE + ;; + authenticamd | genuineintel | EM64T) + GUESS=x86_64-unknown-interix$UNAME_RELEASE + ;; + IA64) + GUESS=ia64-unknown-interix$UNAME_RELEASE + ;; + esac ;; + i*:UWIN*:*) + GUESS=$UNAME_MACHINE-pc-uwin + ;; + amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*) + GUESS=x86_64-pc-cygwin + ;; + prep*:SunOS:5.*:*) + SUN_REL=`echo "$UNAME_RELEASE" | sed -e 's/[^.]*//'` + GUESS=powerpcle-unknown-solaris2$SUN_REL + ;; + *:GNU:*:*) + # the GNU system + GNU_ARCH=`echo "$UNAME_MACHINE" | sed -e 's,[-/].*$,,'` + GNU_REL=`echo "$UNAME_RELEASE" | sed -e 's,/.*$,,'` + GUESS=$GNU_ARCH-unknown-$LIBC$GNU_REL + ;; + *:GNU/*:*:*) + # other systems with GNU libc and userland + GNU_SYS=`echo "$UNAME_SYSTEM" | sed 's,^[^/]*/,,' | tr "[:upper:]" "[:lower:]"` + GNU_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` + GUESS=$UNAME_MACHINE-unknown-$GNU_SYS$GNU_REL-$LIBC + ;; + *:Minix:*:*) + GUESS=$UNAME_MACHINE-unknown-minix + ;; + aarch64:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + aarch64_be:Linux:*:*) + UNAME_MACHINE=aarch64_be + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + alpha:Linux:*:*) + case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' /proc/cpuinfo 2>/dev/null` in + EV5) UNAME_MACHINE=alphaev5 ;; + EV56) UNAME_MACHINE=alphaev56 ;; + PCA56) UNAME_MACHINE=alphapca56 ;; + PCA57) UNAME_MACHINE=alphapca56 ;; + EV6) UNAME_MACHINE=alphaev6 ;; + EV67) UNAME_MACHINE=alphaev67 ;; + EV68*) UNAME_MACHINE=alphaev68 ;; + esac + objdump --private-headers /bin/sh | grep -q ld.so.1 + if test "$?" = 0 ; then LIBC=gnulibc1 ; fi + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + arc:Linux:*:* | arceb:Linux:*:* | arc32:Linux:*:* | arc64:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + arm*:Linux:*:*) + set_cc_for_build + if echo __ARM_EABI__ | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ARM_EABI__ + then + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + else + if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ARM_PCS_VFP + then + GUESS=$UNAME_MACHINE-unknown-linux-${LIBC}eabi + else + GUESS=$UNAME_MACHINE-unknown-linux-${LIBC}eabihf + fi + fi + ;; + avr32*:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + cris:Linux:*:*) + GUESS=$UNAME_MACHINE-axis-linux-$LIBC + ;; + crisv32:Linux:*:*) + GUESS=$UNAME_MACHINE-axis-linux-$LIBC + ;; + e2k:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + frv:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + hexagon:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + i*86:Linux:*:*) + GUESS=$UNAME_MACHINE-pc-linux-$LIBC + ;; + ia64:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + k1om:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + loongarch32:Linux:*:* | loongarch64:Linux:*:* | loongarchx32:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + m32r*:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + m68*:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + mips:Linux:*:* | mips64:Linux:*:*) + set_cc_for_build + IS_GLIBC=0 + test x"${LIBC}" = xgnu && IS_GLIBC=1 + sed 's/^ //' << EOF > "$dummy.c" + #undef CPU + #undef mips + #undef mipsel + #undef mips64 + #undef mips64el + #if ${IS_GLIBC} && defined(_ABI64) + LIBCABI=gnuabi64 + #else + #if ${IS_GLIBC} && defined(_ABIN32) + LIBCABI=gnuabin32 + #else + LIBCABI=${LIBC} + #endif + #endif + + #if ${IS_GLIBC} && defined(__mips64) && defined(__mips_isa_rev) && __mips_isa_rev>=6 + CPU=mipsisa64r6 + #else + #if ${IS_GLIBC} && !defined(__mips64) && defined(__mips_isa_rev) && __mips_isa_rev>=6 + CPU=mipsisa32r6 + #else + #if defined(__mips64) + CPU=mips64 + #else + CPU=mips + #endif + #endif + #endif + + #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) + MIPS_ENDIAN=el + #else + #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) + MIPS_ENDIAN= + #else + MIPS_ENDIAN= + #endif + #endif +EOF + cc_set_vars=`$CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^CPU\|^MIPS_ENDIAN\|^LIBCABI'` + eval "$cc_set_vars" + test "x$CPU" != x && { echo "$CPU${MIPS_ENDIAN}-unknown-linux-$LIBCABI"; exit; } + ;; + mips64el:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + openrisc*:Linux:*:*) + GUESS=or1k-unknown-linux-$LIBC + ;; + or32:Linux:*:* | or1k*:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + padre:Linux:*:*) + GUESS=sparc-unknown-linux-$LIBC + ;; + parisc64:Linux:*:* | hppa64:Linux:*:*) + GUESS=hppa64-unknown-linux-$LIBC + ;; + parisc:Linux:*:* | hppa:Linux:*:*) + # Look for CPU level + case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in + PA7*) GUESS=hppa1.1-unknown-linux-$LIBC ;; + PA8*) GUESS=hppa2.0-unknown-linux-$LIBC ;; + *) GUESS=hppa-unknown-linux-$LIBC ;; + esac + ;; + ppc64:Linux:*:*) + GUESS=powerpc64-unknown-linux-$LIBC + ;; + ppc:Linux:*:*) + GUESS=powerpc-unknown-linux-$LIBC + ;; + ppc64le:Linux:*:*) + GUESS=powerpc64le-unknown-linux-$LIBC + ;; + ppcle:Linux:*:*) + GUESS=powerpcle-unknown-linux-$LIBC + ;; + riscv32:Linux:*:* | riscv32be:Linux:*:* | riscv64:Linux:*:* | riscv64be:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + s390:Linux:*:* | s390x:Linux:*:*) + GUESS=$UNAME_MACHINE-ibm-linux-$LIBC + ;; + sh64*:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + sh*:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + sparc:Linux:*:* | sparc64:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + tile*:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + vax:Linux:*:*) + GUESS=$UNAME_MACHINE-dec-linux-$LIBC + ;; + x86_64:Linux:*:*) + set_cc_for_build + LIBCABI=$LIBC + if test "$CC_FOR_BUILD" != no_compiler_found; then + if (echo '#ifdef __ILP32__'; echo IS_X32; echo '#endif') | \ + (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \ + grep IS_X32 >/dev/null + then + LIBCABI=${LIBC}x32 + fi + fi + GUESS=$UNAME_MACHINE-pc-linux-$LIBCABI + ;; + xtensa*:Linux:*:*) + GUESS=$UNAME_MACHINE-unknown-linux-$LIBC + ;; + i*86:DYNIX/ptx:4*:*) + # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. + # earlier versions are messed up and put the nodename in both + # sysname and nodename. + GUESS=i386-sequent-sysv4 + ;; + i*86:UNIX_SV:4.2MP:2.*) + # Unixware is an offshoot of SVR4, but it has its own version + # number series starting with 2... + # I am not positive that other SVR4 systems won't match this, + # I just have to hope. -- rms. + # Use sysv4.2uw... so that sysv4* matches it. + GUESS=$UNAME_MACHINE-pc-sysv4.2uw$UNAME_VERSION + ;; + i*86:OS/2:*:*) + # If we were able to find `uname', then EMX Unix compatibility + # is probably installed. + GUESS=$UNAME_MACHINE-pc-os2-emx + ;; + i*86:XTS-300:*:STOP) + GUESS=$UNAME_MACHINE-unknown-stop + ;; + i*86:atheos:*:*) + GUESS=$UNAME_MACHINE-unknown-atheos + ;; + i*86:syllable:*:*) + GUESS=$UNAME_MACHINE-pc-syllable + ;; + i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.[02]*:*) + GUESS=i386-unknown-lynxos$UNAME_RELEASE + ;; + i*86:*DOS:*:*) + GUESS=$UNAME_MACHINE-pc-msdosdjgpp + ;; + i*86:*:4.*:*) + UNAME_REL=`echo "$UNAME_RELEASE" | sed 's/\/MP$//'` + if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then + GUESS=$UNAME_MACHINE-univel-sysv$UNAME_REL + else + GUESS=$UNAME_MACHINE-pc-sysv$UNAME_REL + fi + ;; + i*86:*:5:[678]*) + # UnixWare 7.x, OpenUNIX and OpenServer 6. + case `/bin/uname -X | grep "^Machine"` in + *486*) UNAME_MACHINE=i486 ;; + *Pentium) UNAME_MACHINE=i586 ;; + *Pent*|*Celeron) UNAME_MACHINE=i686 ;; + esac + GUESS=$UNAME_MACHINE-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION} + ;; + i*86:*:3.2:*) + if test -f /usr/options/cb.name; then + UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then + UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')` + (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486 + (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \ + && UNAME_MACHINE=i586 + (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \ + && UNAME_MACHINE=i686 + (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \ + && UNAME_MACHINE=i686 + GUESS=$UNAME_MACHINE-pc-sco$UNAME_REL + else + GUESS=$UNAME_MACHINE-pc-sysv32 + fi + ;; + pc:*:*:*) + # Left here for compatibility: + # uname -m prints for DJGPP always 'pc', but it prints nothing about + # the processor, so we play safe by assuming i586. + # Note: whatever this is, it MUST be the same as what config.sub + # prints for the "djgpp" host, or else GDB configure will decide that + # this is a cross-build. + GUESS=i586-pc-msdosdjgpp + ;; + Intel:Mach:3*:*) + GUESS=i386-pc-mach3 + ;; + paragon:*:*:*) + GUESS=i860-intel-osf1 + ;; + i860:*:4.*:*) # i860-SVR4 + if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then + GUESS=i860-stardent-sysv$UNAME_RELEASE # Stardent Vistra i860-SVR4 + else # Add other i860-SVR4 vendors below as they are discovered. + GUESS=i860-unknown-sysv$UNAME_RELEASE # Unknown i860-SVR4 + fi + ;; + mini*:CTIX:SYS*5:*) + # "miniframe" + GUESS=m68010-convergent-sysv + ;; + mc68k:UNIX:SYSTEM5:3.51m) + GUESS=m68k-convergent-sysv + ;; + M680?0:D-NIX:5.3:*) + GUESS=m68k-diab-dnix + ;; + M68*:*:R3V[5678]*:*) + test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;; + 3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0) + OS_REL='' + test -r /etc/.relid \ + && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4.3"$OS_REL"; exit; } + /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ + && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } ;; + 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4; exit; } ;; + NCR*:*:4.2:* | MPRAS*:*:4.2:*) + OS_REL='.3' + test -r /etc/.relid \ + && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4.3"$OS_REL"; exit; } + /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ + && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } + /bin/uname -p 2>/dev/null | /bin/grep pteron >/dev/null \ + && { echo i586-ncr-sysv4.3"$OS_REL"; exit; } ;; + m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) + GUESS=m68k-unknown-lynxos$UNAME_RELEASE + ;; + mc68030:UNIX_System_V:4.*:*) + GUESS=m68k-atari-sysv4 + ;; + TSUNAMI:LynxOS:2.*:*) + GUESS=sparc-unknown-lynxos$UNAME_RELEASE + ;; + rs6000:LynxOS:2.*:*) + GUESS=rs6000-unknown-lynxos$UNAME_RELEASE + ;; + PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.[02]*:*) + GUESS=powerpc-unknown-lynxos$UNAME_RELEASE + ;; + SM[BE]S:UNIX_SV:*:*) + GUESS=mips-dde-sysv$UNAME_RELEASE + ;; + RM*:ReliantUNIX-*:*:*) + GUESS=mips-sni-sysv4 + ;; + RM*:SINIX-*:*:*) + GUESS=mips-sni-sysv4 + ;; + *:SINIX-*:*:*) + if uname -p 2>/dev/null >/dev/null ; then + UNAME_MACHINE=`(uname -p) 2>/dev/null` + GUESS=$UNAME_MACHINE-sni-sysv4 + else + GUESS=ns32k-sni-sysv + fi + ;; + PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort + # says + GUESS=i586-unisys-sysv4 + ;; + *:UNIX_System_V:4*:FTX*) + # From Gerald Hewes . + # How about differentiating between stratus architectures? -djm + GUESS=hppa1.1-stratus-sysv4 + ;; + *:*:*:FTX*) + # From seanf@swdc.stratus.com. + GUESS=i860-stratus-sysv4 + ;; + i*86:VOS:*:*) + # From Paul.Green@stratus.com. + GUESS=$UNAME_MACHINE-stratus-vos + ;; + *:VOS:*:*) + # From Paul.Green@stratus.com. + GUESS=hppa1.1-stratus-vos + ;; + mc68*:A/UX:*:*) + GUESS=m68k-apple-aux$UNAME_RELEASE + ;; + news*:NEWS-OS:6*:*) + GUESS=mips-sony-newsos6 + ;; + R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) + if test -d /usr/nec; then + GUESS=mips-nec-sysv$UNAME_RELEASE + else + GUESS=mips-unknown-sysv$UNAME_RELEASE + fi + ;; + BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. + GUESS=powerpc-be-beos + ;; + BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. + GUESS=powerpc-apple-beos + ;; + BePC:BeOS:*:*) # BeOS running on Intel PC compatible. + GUESS=i586-pc-beos + ;; + BePC:Haiku:*:*) # Haiku running on Intel PC compatible. + GUESS=i586-pc-haiku + ;; + x86_64:Haiku:*:*) + GUESS=x86_64-unknown-haiku + ;; + SX-4:SUPER-UX:*:*) + GUESS=sx4-nec-superux$UNAME_RELEASE + ;; + SX-5:SUPER-UX:*:*) + GUESS=sx5-nec-superux$UNAME_RELEASE + ;; + SX-6:SUPER-UX:*:*) + GUESS=sx6-nec-superux$UNAME_RELEASE + ;; + SX-7:SUPER-UX:*:*) + GUESS=sx7-nec-superux$UNAME_RELEASE + ;; + SX-8:SUPER-UX:*:*) + GUESS=sx8-nec-superux$UNAME_RELEASE + ;; + SX-8R:SUPER-UX:*:*) + GUESS=sx8r-nec-superux$UNAME_RELEASE + ;; + SX-ACE:SUPER-UX:*:*) + GUESS=sxace-nec-superux$UNAME_RELEASE + ;; + Power*:Rhapsody:*:*) + GUESS=powerpc-apple-rhapsody$UNAME_RELEASE + ;; + *:Rhapsody:*:*) + GUESS=$UNAME_MACHINE-apple-rhapsody$UNAME_RELEASE + ;; + arm64:Darwin:*:*) + GUESS=aarch64-apple-darwin$UNAME_RELEASE + ;; + *:Darwin:*:*) + UNAME_PROCESSOR=`uname -p` + case $UNAME_PROCESSOR in + unknown) UNAME_PROCESSOR=powerpc ;; + esac + if command -v xcode-select > /dev/null 2> /dev/null && \ + ! xcode-select --print-path > /dev/null 2> /dev/null ; then + # Avoid executing cc if there is no toolchain installed as + # cc will be a stub that puts up a graphical alert + # prompting the user to install developer tools. + CC_FOR_BUILD=no_compiler_found + else + set_cc_for_build + fi + if test "$CC_FOR_BUILD" != no_compiler_found; then + if (echo '#ifdef __LP64__'; echo IS_64BIT_ARCH; echo '#endif') | \ + (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \ + grep IS_64BIT_ARCH >/dev/null + then + case $UNAME_PROCESSOR in + i386) UNAME_PROCESSOR=x86_64 ;; + powerpc) UNAME_PROCESSOR=powerpc64 ;; + esac + fi + # On 10.4-10.6 one might compile for PowerPC via gcc -arch ppc + if (echo '#ifdef __POWERPC__'; echo IS_PPC; echo '#endif') | \ + (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \ + grep IS_PPC >/dev/null + then + UNAME_PROCESSOR=powerpc + fi + elif test "$UNAME_PROCESSOR" = i386 ; then + # uname -m returns i386 or x86_64 + UNAME_PROCESSOR=$UNAME_MACHINE + fi + GUESS=$UNAME_PROCESSOR-apple-darwin$UNAME_RELEASE + ;; + *:procnto*:*:* | *:QNX:[0123456789]*:*) + UNAME_PROCESSOR=`uname -p` + if test "$UNAME_PROCESSOR" = x86; then + UNAME_PROCESSOR=i386 + UNAME_MACHINE=pc + fi + GUESS=$UNAME_PROCESSOR-$UNAME_MACHINE-nto-qnx$UNAME_RELEASE + ;; + *:QNX:*:4*) + GUESS=i386-pc-qnx + ;; + NEO-*:NONSTOP_KERNEL:*:*) + GUESS=neo-tandem-nsk$UNAME_RELEASE + ;; + NSE-*:NONSTOP_KERNEL:*:*) + GUESS=nse-tandem-nsk$UNAME_RELEASE + ;; + NSR-*:NONSTOP_KERNEL:*:*) + GUESS=nsr-tandem-nsk$UNAME_RELEASE + ;; + NSV-*:NONSTOP_KERNEL:*:*) + GUESS=nsv-tandem-nsk$UNAME_RELEASE + ;; + NSX-*:NONSTOP_KERNEL:*:*) + GUESS=nsx-tandem-nsk$UNAME_RELEASE + ;; + *:NonStop-UX:*:*) + GUESS=mips-compaq-nonstopux + ;; + BS2000:POSIX*:*:*) + GUESS=bs2000-siemens-sysv + ;; + DS/*:UNIX_System_V:*:*) + GUESS=$UNAME_MACHINE-$UNAME_SYSTEM-$UNAME_RELEASE + ;; + *:Plan9:*:*) + # "uname -m" is not consistent, so use $cputype instead. 386 + # is converted to i386 for consistency with other x86 + # operating systems. + if test "${cputype-}" = 386; then + UNAME_MACHINE=i386 + elif test "x${cputype-}" != x; then + UNAME_MACHINE=$cputype + fi + GUESS=$UNAME_MACHINE-unknown-plan9 + ;; + *:TOPS-10:*:*) + GUESS=pdp10-unknown-tops10 + ;; + *:TENEX:*:*) + GUESS=pdp10-unknown-tenex + ;; + KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) + GUESS=pdp10-dec-tops20 + ;; + XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) + GUESS=pdp10-xkl-tops20 + ;; + *:TOPS-20:*:*) + GUESS=pdp10-unknown-tops20 + ;; + *:ITS:*:*) + GUESS=pdp10-unknown-its + ;; + SEI:*:*:SEIUX) + GUESS=mips-sei-seiux$UNAME_RELEASE + ;; + *:DragonFly:*:*) + DRAGONFLY_REL=`echo "$UNAME_RELEASE" | sed -e 's/[-(].*//'` + GUESS=$UNAME_MACHINE-unknown-dragonfly$DRAGONFLY_REL + ;; + *:*VMS:*:*) + UNAME_MACHINE=`(uname -p) 2>/dev/null` + case $UNAME_MACHINE in + A*) GUESS=alpha-dec-vms ;; + I*) GUESS=ia64-dec-vms ;; + V*) GUESS=vax-dec-vms ;; + esac ;; + *:XENIX:*:SysV) + GUESS=i386-pc-xenix + ;; + i*86:skyos:*:*) + SKYOS_REL=`echo "$UNAME_RELEASE" | sed -e 's/ .*$//'` + GUESS=$UNAME_MACHINE-pc-skyos$SKYOS_REL + ;; + i*86:rdos:*:*) + GUESS=$UNAME_MACHINE-pc-rdos + ;; + i*86:Fiwix:*:*) + GUESS=$UNAME_MACHINE-pc-fiwix + ;; + *:AROS:*:*) + GUESS=$UNAME_MACHINE-unknown-aros + ;; + x86_64:VMkernel:*:*) + GUESS=$UNAME_MACHINE-unknown-esx + ;; + amd64:Isilon\ OneFS:*:*) + GUESS=x86_64-unknown-onefs + ;; + *:Unleashed:*:*) + GUESS=$UNAME_MACHINE-unknown-unleashed$UNAME_RELEASE + ;; +esac + +# Do we have a guess based on uname results? +if test "x$GUESS" != x; then + echo "$GUESS" + exit +fi + +# No uname command or uname output not recognized. +set_cc_for_build +cat > "$dummy.c" < +#include +#endif +#if defined(ultrix) || defined(_ultrix) || defined(__ultrix) || defined(__ultrix__) +#if defined (vax) || defined (__vax) || defined (__vax__) || defined(mips) || defined(__mips) || defined(__mips__) || defined(MIPS) || defined(__MIPS__) +#include +#if defined(_SIZE_T_) || defined(SIGLOST) +#include +#endif +#endif +#endif +main () +{ +#if defined (sony) +#if defined (MIPSEB) + /* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed, + I don't know.... */ + printf ("mips-sony-bsd\n"); exit (0); +#else +#include + printf ("m68k-sony-newsos%s\n", +#ifdef NEWSOS4 + "4" +#else + "" +#endif + ); exit (0); +#endif +#endif + +#if defined (NeXT) +#if !defined (__ARCHITECTURE__) +#define __ARCHITECTURE__ "m68k" +#endif + int version; + version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`; + if (version < 4) + printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version); + else + printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version); + exit (0); +#endif + +#if defined (MULTIMAX) || defined (n16) +#if defined (UMAXV) + printf ("ns32k-encore-sysv\n"); exit (0); +#else +#if defined (CMU) + printf ("ns32k-encore-mach\n"); exit (0); +#else + printf ("ns32k-encore-bsd\n"); exit (0); +#endif +#endif +#endif + +#if defined (__386BSD__) + printf ("i386-pc-bsd\n"); exit (0); +#endif + +#if defined (sequent) +#if defined (i386) + printf ("i386-sequent-dynix\n"); exit (0); +#endif +#if defined (ns32000) + printf ("ns32k-sequent-dynix\n"); exit (0); +#endif +#endif + +#if defined (_SEQUENT_) + struct utsname un; + + uname(&un); + if (strncmp(un.version, "V2", 2) == 0) { + printf ("i386-sequent-ptx2\n"); exit (0); + } + if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */ + printf ("i386-sequent-ptx1\n"); exit (0); + } + printf ("i386-sequent-ptx\n"); exit (0); +#endif + +#if defined (vax) +#if !defined (ultrix) +#include +#if defined (BSD) +#if BSD == 43 + printf ("vax-dec-bsd4.3\n"); exit (0); +#else +#if BSD == 199006 + printf ("vax-dec-bsd4.3reno\n"); exit (0); +#else + printf ("vax-dec-bsd\n"); exit (0); +#endif +#endif +#else + printf ("vax-dec-bsd\n"); exit (0); +#endif +#else +#if defined(_SIZE_T_) || defined(SIGLOST) + struct utsname un; + uname (&un); + printf ("vax-dec-ultrix%s\n", un.release); exit (0); +#else + printf ("vax-dec-ultrix\n"); exit (0); +#endif +#endif +#endif +#if defined(ultrix) || defined(_ultrix) || defined(__ultrix) || defined(__ultrix__) +#if defined(mips) || defined(__mips) || defined(__mips__) || defined(MIPS) || defined(__MIPS__) +#if defined(_SIZE_T_) || defined(SIGLOST) + struct utsname *un; + uname (&un); + printf ("mips-dec-ultrix%s\n", un.release); exit (0); +#else + printf ("mips-dec-ultrix\n"); exit (0); +#endif +#endif +#endif + +#if defined (alliant) && defined (i860) + printf ("i860-alliant-bsd\n"); exit (0); +#endif + + exit (1); +} +EOF + +$CC_FOR_BUILD -o "$dummy" "$dummy.c" 2>/dev/null && SYSTEM_NAME=`"$dummy"` && + { echo "$SYSTEM_NAME"; exit; } + +# Apollos put the system type in the environment. +test -d /usr/apollo && { echo "$ISP-apollo-$SYSTYPE"; exit; } + +echo "$0: unable to guess system type" >&2 + +case $UNAME_MACHINE:$UNAME_SYSTEM in + mips:Linux | mips64:Linux) + # If we got here on MIPS GNU/Linux, output extra information. + cat >&2 <&2 <&2 </dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null` + +hostinfo = `(hostinfo) 2>/dev/null` +/bin/universe = `(/bin/universe) 2>/dev/null` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null` +/bin/arch = `(/bin/arch) 2>/dev/null` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null` + +UNAME_MACHINE = "$UNAME_MACHINE" +UNAME_RELEASE = "$UNAME_RELEASE" +UNAME_SYSTEM = "$UNAME_SYSTEM" +UNAME_VERSION = "$UNAME_VERSION" +EOF +fi + +exit 1 + +# Local variables: +# eval: (add-hook 'before-save-hook 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: diff --git a/audio/paddleaudio/third_party/patches/config.sub b/audio/paddleaudio/third_party/patches/config.sub new file mode 100644 index 00000000..dba16e84 --- /dev/null +++ b/audio/paddleaudio/third_party/patches/config.sub @@ -0,0 +1,1890 @@ +#! /bin/sh +# Configuration validation subroutine script. +# Copyright 1992-2022 Free Software Foundation, Inc. + +# shellcheck disable=SC2006,SC2268 # see below for rationale + +timestamp='2022-01-03' + +# This file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that +# program. This Exception is an additional permission under section 7 +# of the GNU General Public License, version 3 ("GPLv3"). + + +# Please send patches to . +# +# Configuration subroutine to validate and canonicalize a configuration type. +# Supply the specified configuration type as an argument. +# If it is invalid, we print an error message on stderr and exit with code 1. +# Otherwise, we print the canonical config type on stdout and succeed. + +# You can get the latest version of this script from: +# https://git.savannah.gnu.org/cgit/config.git/plain/config.sub + +# This file is supposed to be the same for all GNU packages +# and recognize all the CPU types, system types and aliases +# that are meaningful with *any* GNU software. +# Each package is responsible for reporting which valid configurations +# it does not support. The user should be able to distinguish +# a failure to support a valid configuration from a meaningless +# configuration. + +# The goal of this file is to map all the various variations of a given +# machine specification into a single specification in the form: +# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM +# or in some cases, the newer four-part form: +# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM +# It is wrong to echo any other type of specification. + +# The "shellcheck disable" line above the timestamp inhibits complaints +# about features and limitations of the classic Bourne shell that were +# superseded or lifted in POSIX. However, this script identifies a wide +# variety of pre-POSIX systems that do not have POSIX shells at all, and +# even some reasonably current systems (Solaris 10 as case-in-point) still +# have a pre-POSIX /bin/sh. + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] CPU-MFR-OPSYS or ALIAS + +Canonicalize a configuration name. + +Options: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to ." + +version="\ +GNU config.sub ($timestamp) + +Copyright 1992-2022 Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit ;; + --version | -v ) + echo "$version" ; exit ;; + --help | --h* | -h ) + echo "$usage"; exit ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" >&2 + exit 1 ;; + + *local*) + # First pass through any local machine types. + echo "$1" + exit ;; + + * ) + break ;; + esac +done + +case $# in + 0) echo "$me: missing argument$help" >&2 + exit 1;; + 1) ;; + *) echo "$me: too many arguments$help" >&2 + exit 1;; +esac + +# Split fields of configuration type +# shellcheck disable=SC2162 +saved_IFS=$IFS +IFS="-" read field1 field2 field3 field4 <&2 + exit 1 + ;; + *-*-*-*) + basic_machine=$field1-$field2 + basic_os=$field3-$field4 + ;; + *-*-*) + # Ambiguous whether COMPANY is present, or skipped and KERNEL-OS is two + # parts + maybe_os=$field2-$field3 + case $maybe_os in + nto-qnx* | linux-* | uclinux-uclibc* \ + | uclinux-gnu* | kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* \ + | netbsd*-eabi* | kopensolaris*-gnu* | cloudabi*-eabi* \ + | storm-chaos* | os2-emx* | rtmk-nova*) + basic_machine=$field1 + basic_os=$maybe_os + ;; + android-linux) + basic_machine=$field1-unknown + basic_os=linux-android + ;; + *) + basic_machine=$field1-$field2 + basic_os=$field3 + ;; + esac + ;; + *-*) + # A lone config we happen to match not fitting any pattern + case $field1-$field2 in + decstation-3100) + basic_machine=mips-dec + basic_os= + ;; + *-*) + # Second component is usually, but not always the OS + case $field2 in + # Prevent following clause from handling this valid os + sun*os*) + basic_machine=$field1 + basic_os=$field2 + ;; + zephyr*) + basic_machine=$field1-unknown + basic_os=$field2 + ;; + # Manufacturers + dec* | mips* | sequent* | encore* | pc533* | sgi* | sony* \ + | att* | 7300* | 3300* | delta* | motorola* | sun[234]* \ + | unicom* | ibm* | next | hp | isi* | apollo | altos* \ + | convergent* | ncr* | news | 32* | 3600* | 3100* \ + | hitachi* | c[123]* | convex* | sun | crds | omron* | dg \ + | ultra | tti* | harris | dolphin | highlevel | gould \ + | cbm | ns | masscomp | apple | axis | knuth | cray \ + | microblaze* | sim | cisco \ + | oki | wec | wrs | winbond) + basic_machine=$field1-$field2 + basic_os= + ;; + *) + basic_machine=$field1 + basic_os=$field2 + ;; + esac + ;; + esac + ;; + *) + # Convert single-component short-hands not valid as part of + # multi-component configurations. + case $field1 in + 386bsd) + basic_machine=i386-pc + basic_os=bsd + ;; + a29khif) + basic_machine=a29k-amd + basic_os=udi + ;; + adobe68k) + basic_machine=m68010-adobe + basic_os=scout + ;; + alliant) + basic_machine=fx80-alliant + basic_os= + ;; + altos | altos3068) + basic_machine=m68k-altos + basic_os= + ;; + am29k) + basic_machine=a29k-none + basic_os=bsd + ;; + amdahl) + basic_machine=580-amdahl + basic_os=sysv + ;; + amiga) + basic_machine=m68k-unknown + basic_os= + ;; + amigaos | amigados) + basic_machine=m68k-unknown + basic_os=amigaos + ;; + amigaunix | amix) + basic_machine=m68k-unknown + basic_os=sysv4 + ;; + apollo68) + basic_machine=m68k-apollo + basic_os=sysv + ;; + apollo68bsd) + basic_machine=m68k-apollo + basic_os=bsd + ;; + aros) + basic_machine=i386-pc + basic_os=aros + ;; + aux) + basic_machine=m68k-apple + basic_os=aux + ;; + balance) + basic_machine=ns32k-sequent + basic_os=dynix + ;; + blackfin) + basic_machine=bfin-unknown + basic_os=linux + ;; + cegcc) + basic_machine=arm-unknown + basic_os=cegcc + ;; + convex-c1) + basic_machine=c1-convex + basic_os=bsd + ;; + convex-c2) + basic_machine=c2-convex + basic_os=bsd + ;; + convex-c32) + basic_machine=c32-convex + basic_os=bsd + ;; + convex-c34) + basic_machine=c34-convex + basic_os=bsd + ;; + convex-c38) + basic_machine=c38-convex + basic_os=bsd + ;; + cray) + basic_machine=j90-cray + basic_os=unicos + ;; + crds | unos) + basic_machine=m68k-crds + basic_os= + ;; + da30) + basic_machine=m68k-da30 + basic_os= + ;; + decstation | pmax | pmin | dec3100 | decstatn) + basic_machine=mips-dec + basic_os= + ;; + delta88) + basic_machine=m88k-motorola + basic_os=sysv3 + ;; + dicos) + basic_machine=i686-pc + basic_os=dicos + ;; + djgpp) + basic_machine=i586-pc + basic_os=msdosdjgpp + ;; + ebmon29k) + basic_machine=a29k-amd + basic_os=ebmon + ;; + es1800 | OSE68k | ose68k | ose | OSE) + basic_machine=m68k-ericsson + basic_os=ose + ;; + gmicro) + basic_machine=tron-gmicro + basic_os=sysv + ;; + go32) + basic_machine=i386-pc + basic_os=go32 + ;; + h8300hms) + basic_machine=h8300-hitachi + basic_os=hms + ;; + h8300xray) + basic_machine=h8300-hitachi + basic_os=xray + ;; + h8500hms) + basic_machine=h8500-hitachi + basic_os=hms + ;; + harris) + basic_machine=m88k-harris + basic_os=sysv3 + ;; + hp300 | hp300hpux) + basic_machine=m68k-hp + basic_os=hpux + ;; + hp300bsd) + basic_machine=m68k-hp + basic_os=bsd + ;; + hppaosf) + basic_machine=hppa1.1-hp + basic_os=osf + ;; + hppro) + basic_machine=hppa1.1-hp + basic_os=proelf + ;; + i386mach) + basic_machine=i386-mach + basic_os=mach + ;; + isi68 | isi) + basic_machine=m68k-isi + basic_os=sysv + ;; + m68knommu) + basic_machine=m68k-unknown + basic_os=linux + ;; + magnum | m3230) + basic_machine=mips-mips + basic_os=sysv + ;; + merlin) + basic_machine=ns32k-utek + basic_os=sysv + ;; + mingw64) + basic_machine=x86_64-pc + basic_os=mingw64 + ;; + mingw32) + basic_machine=i686-pc + basic_os=mingw32 + ;; + mingw32ce) + basic_machine=arm-unknown + basic_os=mingw32ce + ;; + monitor) + basic_machine=m68k-rom68k + basic_os=coff + ;; + morphos) + basic_machine=powerpc-unknown + basic_os=morphos + ;; + moxiebox) + basic_machine=moxie-unknown + basic_os=moxiebox + ;; + msdos) + basic_machine=i386-pc + basic_os=msdos + ;; + msys) + basic_machine=i686-pc + basic_os=msys + ;; + mvs) + basic_machine=i370-ibm + basic_os=mvs + ;; + nacl) + basic_machine=le32-unknown + basic_os=nacl + ;; + ncr3000) + basic_machine=i486-ncr + basic_os=sysv4 + ;; + netbsd386) + basic_machine=i386-pc + basic_os=netbsd + ;; + netwinder) + basic_machine=armv4l-rebel + basic_os=linux + ;; + news | news700 | news800 | news900) + basic_machine=m68k-sony + basic_os=newsos + ;; + news1000) + basic_machine=m68030-sony + basic_os=newsos + ;; + necv70) + basic_machine=v70-nec + basic_os=sysv + ;; + nh3000) + basic_machine=m68k-harris + basic_os=cxux + ;; + nh[45]000) + basic_machine=m88k-harris + basic_os=cxux + ;; + nindy960) + basic_machine=i960-intel + basic_os=nindy + ;; + mon960) + basic_machine=i960-intel + basic_os=mon960 + ;; + nonstopux) + basic_machine=mips-compaq + basic_os=nonstopux + ;; + os400) + basic_machine=powerpc-ibm + basic_os=os400 + ;; + OSE68000 | ose68000) + basic_machine=m68000-ericsson + basic_os=ose + ;; + os68k) + basic_machine=m68k-none + basic_os=os68k + ;; + paragon) + basic_machine=i860-intel + basic_os=osf + ;; + parisc) + basic_machine=hppa-unknown + basic_os=linux + ;; + psp) + basic_machine=mipsallegrexel-sony + basic_os=psp + ;; + pw32) + basic_machine=i586-unknown + basic_os=pw32 + ;; + rdos | rdos64) + basic_machine=x86_64-pc + basic_os=rdos + ;; + rdos32) + basic_machine=i386-pc + basic_os=rdos + ;; + rom68k) + basic_machine=m68k-rom68k + basic_os=coff + ;; + sa29200) + basic_machine=a29k-amd + basic_os=udi + ;; + sei) + basic_machine=mips-sei + basic_os=seiux + ;; + sequent) + basic_machine=i386-sequent + basic_os= + ;; + sps7) + basic_machine=m68k-bull + basic_os=sysv2 + ;; + st2000) + basic_machine=m68k-tandem + basic_os= + ;; + stratus) + basic_machine=i860-stratus + basic_os=sysv4 + ;; + sun2) + basic_machine=m68000-sun + basic_os= + ;; + sun2os3) + basic_machine=m68000-sun + basic_os=sunos3 + ;; + sun2os4) + basic_machine=m68000-sun + basic_os=sunos4 + ;; + sun3) + basic_machine=m68k-sun + basic_os= + ;; + sun3os3) + basic_machine=m68k-sun + basic_os=sunos3 + ;; + sun3os4) + basic_machine=m68k-sun + basic_os=sunos4 + ;; + sun4) + basic_machine=sparc-sun + basic_os= + ;; + sun4os3) + basic_machine=sparc-sun + basic_os=sunos3 + ;; + sun4os4) + basic_machine=sparc-sun + basic_os=sunos4 + ;; + sun4sol2) + basic_machine=sparc-sun + basic_os=solaris2 + ;; + sun386 | sun386i | roadrunner) + basic_machine=i386-sun + basic_os= + ;; + sv1) + basic_machine=sv1-cray + basic_os=unicos + ;; + symmetry) + basic_machine=i386-sequent + basic_os=dynix + ;; + t3e) + basic_machine=alphaev5-cray + basic_os=unicos + ;; + t90) + basic_machine=t90-cray + basic_os=unicos + ;; + toad1) + basic_machine=pdp10-xkl + basic_os=tops20 + ;; + tpf) + basic_machine=s390x-ibm + basic_os=tpf + ;; + udi29k) + basic_machine=a29k-amd + basic_os=udi + ;; + ultra3) + basic_machine=a29k-nyu + basic_os=sym1 + ;; + v810 | necv810) + basic_machine=v810-nec + basic_os=none + ;; + vaxv) + basic_machine=vax-dec + basic_os=sysv + ;; + vms) + basic_machine=vax-dec + basic_os=vms + ;; + vsta) + basic_machine=i386-pc + basic_os=vsta + ;; + vxworks960) + basic_machine=i960-wrs + basic_os=vxworks + ;; + vxworks68) + basic_machine=m68k-wrs + basic_os=vxworks + ;; + vxworks29k) + basic_machine=a29k-wrs + basic_os=vxworks + ;; + xbox) + basic_machine=i686-pc + basic_os=mingw32 + ;; + ymp) + basic_machine=ymp-cray + basic_os=unicos + ;; + *) + basic_machine=$1 + basic_os= + ;; + esac + ;; +esac + +# Decode 1-component or ad-hoc basic machines +case $basic_machine in + # Here we handle the default manufacturer of certain CPU types. It is in + # some cases the only manufacturer, in others, it is the most popular. + w89k) + cpu=hppa1.1 + vendor=winbond + ;; + op50n) + cpu=hppa1.1 + vendor=oki + ;; + op60c) + cpu=hppa1.1 + vendor=oki + ;; + ibm*) + cpu=i370 + vendor=ibm + ;; + orion105) + cpu=clipper + vendor=highlevel + ;; + mac | mpw | mac-mpw) + cpu=m68k + vendor=apple + ;; + pmac | pmac-mpw) + cpu=powerpc + vendor=apple + ;; + + # Recognize the various machine names and aliases which stand + # for a CPU type and a company and sometimes even an OS. + 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc) + cpu=m68000 + vendor=att + ;; + 3b*) + cpu=we32k + vendor=att + ;; + bluegene*) + cpu=powerpc + vendor=ibm + basic_os=cnk + ;; + decsystem10* | dec10*) + cpu=pdp10 + vendor=dec + basic_os=tops10 + ;; + decsystem20* | dec20*) + cpu=pdp10 + vendor=dec + basic_os=tops20 + ;; + delta | 3300 | motorola-3300 | motorola-delta \ + | 3300-motorola | delta-motorola) + cpu=m68k + vendor=motorola + ;; + dpx2*) + cpu=m68k + vendor=bull + basic_os=sysv3 + ;; + encore | umax | mmax) + cpu=ns32k + vendor=encore + ;; + elxsi) + cpu=elxsi + vendor=elxsi + basic_os=${basic_os:-bsd} + ;; + fx2800) + cpu=i860 + vendor=alliant + ;; + genix) + cpu=ns32k + vendor=ns + ;; + h3050r* | hiux*) + cpu=hppa1.1 + vendor=hitachi + basic_os=hiuxwe2 + ;; + hp3k9[0-9][0-9] | hp9[0-9][0-9]) + cpu=hppa1.0 + vendor=hp + ;; + hp9k2[0-9][0-9] | hp9k31[0-9]) + cpu=m68000 + vendor=hp + ;; + hp9k3[2-9][0-9]) + cpu=m68k + vendor=hp + ;; + hp9k6[0-9][0-9] | hp6[0-9][0-9]) + cpu=hppa1.0 + vendor=hp + ;; + hp9k7[0-79][0-9] | hp7[0-79][0-9]) + cpu=hppa1.1 + vendor=hp + ;; + hp9k78[0-9] | hp78[0-9]) + # FIXME: really hppa2.0-hp + cpu=hppa1.1 + vendor=hp + ;; + hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893) + # FIXME: really hppa2.0-hp + cpu=hppa1.1 + vendor=hp + ;; + hp9k8[0-9][13679] | hp8[0-9][13679]) + cpu=hppa1.1 + vendor=hp + ;; + hp9k8[0-9][0-9] | hp8[0-9][0-9]) + cpu=hppa1.0 + vendor=hp + ;; + i*86v32) + cpu=`echo "$1" | sed -e 's/86.*/86/'` + vendor=pc + basic_os=sysv32 + ;; + i*86v4*) + cpu=`echo "$1" | sed -e 's/86.*/86/'` + vendor=pc + basic_os=sysv4 + ;; + i*86v) + cpu=`echo "$1" | sed -e 's/86.*/86/'` + vendor=pc + basic_os=sysv + ;; + i*86sol2) + cpu=`echo "$1" | sed -e 's/86.*/86/'` + vendor=pc + basic_os=solaris2 + ;; + j90 | j90-cray) + cpu=j90 + vendor=cray + basic_os=${basic_os:-unicos} + ;; + iris | iris4d) + cpu=mips + vendor=sgi + case $basic_os in + irix*) + ;; + *) + basic_os=irix4 + ;; + esac + ;; + miniframe) + cpu=m68000 + vendor=convergent + ;; + *mint | mint[0-9]* | *MiNT | *MiNT[0-9]*) + cpu=m68k + vendor=atari + basic_os=mint + ;; + news-3600 | risc-news) + cpu=mips + vendor=sony + basic_os=newsos + ;; + next | m*-next) + cpu=m68k + vendor=next + case $basic_os in + openstep*) + ;; + nextstep*) + ;; + ns2*) + basic_os=nextstep2 + ;; + *) + basic_os=nextstep3 + ;; + esac + ;; + np1) + cpu=np1 + vendor=gould + ;; + op50n-* | op60c-*) + cpu=hppa1.1 + vendor=oki + basic_os=proelf + ;; + pa-hitachi) + cpu=hppa1.1 + vendor=hitachi + basic_os=hiuxwe2 + ;; + pbd) + cpu=sparc + vendor=tti + ;; + pbb) + cpu=m68k + vendor=tti + ;; + pc532) + cpu=ns32k + vendor=pc532 + ;; + pn) + cpu=pn + vendor=gould + ;; + power) + cpu=power + vendor=ibm + ;; + ps2) + cpu=i386 + vendor=ibm + ;; + rm[46]00) + cpu=mips + vendor=siemens + ;; + rtpc | rtpc-*) + cpu=romp + vendor=ibm + ;; + sde) + cpu=mipsisa32 + vendor=sde + basic_os=${basic_os:-elf} + ;; + simso-wrs) + cpu=sparclite + vendor=wrs + basic_os=vxworks + ;; + tower | tower-32) + cpu=m68k + vendor=ncr + ;; + vpp*|vx|vx-*) + cpu=f301 + vendor=fujitsu + ;; + w65) + cpu=w65 + vendor=wdc + ;; + w89k-*) + cpu=hppa1.1 + vendor=winbond + basic_os=proelf + ;; + none) + cpu=none + vendor=none + ;; + leon|leon[3-9]) + cpu=sparc + vendor=$basic_machine + ;; + leon-*|leon[3-9]-*) + cpu=sparc + vendor=`echo "$basic_machine" | sed 's/-.*//'` + ;; + + *-*) + # shellcheck disable=SC2162 + saved_IFS=$IFS + IFS="-" read cpu vendor <&2 + exit 1 + ;; + esac + ;; +esac + +# Here we canonicalize certain aliases for manufacturers. +case $vendor in + digital*) + vendor=dec + ;; + commodore*) + vendor=cbm + ;; + *) + ;; +esac + +# Decode manufacturer-specific aliases for certain operating systems. + +if test x$basic_os != x +then + +# First recognize some ad-hoc cases, or perhaps split kernel-os, or else just +# set os. +case $basic_os in + gnu/linux*) + kernel=linux + os=`echo "$basic_os" | sed -e 's|gnu/linux|gnu|'` + ;; + os2-emx) + kernel=os2 + os=`echo "$basic_os" | sed -e 's|os2-emx|emx|'` + ;; + nto-qnx*) + kernel=nto + os=`echo "$basic_os" | sed -e 's|nto-qnx|qnx|'` + ;; + *-*) + # shellcheck disable=SC2162 + saved_IFS=$IFS + IFS="-" read kernel os <&2 + exit 1 + ;; +esac + +# As a final step for OS-related things, validate the OS-kernel combination +# (given a valid OS), if there is a kernel. +case $kernel-$os in + linux-gnu* | linux-dietlibc* | linux-android* | linux-newlib* \ + | linux-musl* | linux-relibc* | linux-uclibc* ) + ;; + uclinux-uclibc* ) + ;; + -dietlibc* | -newlib* | -musl* | -relibc* | -uclibc* ) + # These are just libc implementations, not actual OSes, and thus + # require a kernel. + echo "Invalid configuration \`$1': libc \`$os' needs explicit kernel." 1>&2 + exit 1 + ;; + kfreebsd*-gnu* | kopensolaris*-gnu*) + ;; + vxworks-simlinux | vxworks-simwindows | vxworks-spe) + ;; + nto-qnx*) + ;; + os2-emx) + ;; + *-eabi* | *-gnueabi*) + ;; + -*) + # Blank kernel with real OS is always fine. + ;; + *-*) + echo "Invalid configuration \`$1': Kernel \`$kernel' not known to work with OS \`$os'." 1>&2 + exit 1 + ;; +esac + +# Here we handle the case where we know the os, and the CPU type, but not the +# manufacturer. We pick the logical manufacturer. +case $vendor in + unknown) + case $cpu-$os in + *-riscix*) + vendor=acorn + ;; + *-sunos*) + vendor=sun + ;; + *-cnk* | *-aix*) + vendor=ibm + ;; + *-beos*) + vendor=be + ;; + *-hpux*) + vendor=hp + ;; + *-mpeix*) + vendor=hp + ;; + *-hiux*) + vendor=hitachi + ;; + *-unos*) + vendor=crds + ;; + *-dgux*) + vendor=dg + ;; + *-luna*) + vendor=omron + ;; + *-genix*) + vendor=ns + ;; + *-clix*) + vendor=intergraph + ;; + *-mvs* | *-opened*) + vendor=ibm + ;; + *-os400*) + vendor=ibm + ;; + s390-* | s390x-*) + vendor=ibm + ;; + *-ptx*) + vendor=sequent + ;; + *-tpf*) + vendor=ibm + ;; + *-vxsim* | *-vxworks* | *-windiss*) + vendor=wrs + ;; + *-aux*) + vendor=apple + ;; + *-hms*) + vendor=hitachi + ;; + *-mpw* | *-macos*) + vendor=apple + ;; + *-*mint | *-mint[0-9]* | *-*MiNT | *-MiNT[0-9]*) + vendor=atari + ;; + *-vos*) + vendor=stratus + ;; + esac + ;; +esac + +echo "$cpu-$vendor-${kernel:+$kernel-}$os" +exit + +# Local variables: +# eval: (add-hook 'before-save-hook 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: diff --git a/audio/paddleaudio/third_party/patches/libmad.patch b/audio/paddleaudio/third_party/patches/libmad.patch new file mode 100644 index 00000000..a8057878 --- /dev/null +++ b/audio/paddleaudio/third_party/patches/libmad.patch @@ -0,0 +1,86 @@ +See the followings for the origin of this patch +http://www.linuxfromscratch.org/blfs/view/svn/multimedia/libmad.html +http://www.linuxfromscratch.org/patches/blfs/svn/libmad-0.15.1b-fixes-1.patch +--- src/libmad/configure 2004-02-05 09:34:07.000000000 +0000 ++++ src/libmad/configure.new 2020-06-30 21:10:28.528018931 +0000 +@@ -19083,71 +19083,7 @@ + + if test "$GCC" = yes + then +- if test -z "$arch" +- then +- case "$host" in +- i386-*) ;; +- i?86-*) arch="-march=i486" ;; +- arm*-empeg-*) arch="-march=armv4 -mtune=strongarm1100" ;; +- armv4*-*) arch="-march=armv4 -mtune=strongarm" ;; +- powerpc-*) ;; +- mips*-agenda-*) arch="-mcpu=vr4100" ;; +- mips*-luxsonor-*) arch="-mips1 -mcpu=r3000 -Wa,-m4010" ;; +- esac +- fi +- +- case "$optimize" in +- -O|"-O "*) +- optimize="-O" +- optimize="$optimize -fforce-mem" +- optimize="$optimize -fforce-addr" +- : #x optimize="$optimize -finline-functions" +- : #- optimize="$optimize -fstrength-reduce" +- optimize="$optimize -fthread-jumps" +- optimize="$optimize -fcse-follow-jumps" +- optimize="$optimize -fcse-skip-blocks" +- : #x optimize="$optimize -frerun-cse-after-loop" +- : #x optimize="$optimize -frerun-loop-opt" +- : #x optimize="$optimize -fgcse" +- optimize="$optimize -fexpensive-optimizations" +- optimize="$optimize -fregmove" +- : #* optimize="$optimize -fdelayed-branch" +- : #x optimize="$optimize -fschedule-insns" +- optimize="$optimize -fschedule-insns2" +- : #? optimize="$optimize -ffunction-sections" +- : #? optimize="$optimize -fcaller-saves" +- : #> optimize="$optimize -funroll-loops" +- : #> optimize="$optimize -funroll-all-loops" +- : #x optimize="$optimize -fmove-all-movables" +- : #x optimize="$optimize -freduce-all-givs" +- : #? optimize="$optimize -fstrict-aliasing" +- : #* optimize="$optimize -fstructure-noalias" +- +- case "$host" in +- arm*-*) +- optimize="$optimize -fstrength-reduce" +- ;; +- mips*-*) +- optimize="$optimize -fstrength-reduce" +- optimize="$optimize -finline-functions" +- ;; +- i?86-*) +- optimize="$optimize -fstrength-reduce" +- ;; +- powerpc-apple-*) +- # this triggers an internal compiler error with gcc2 +- : #optimize="$optimize -fstrength-reduce" +- +- # this is really only beneficial with gcc3 +- : #optimize="$optimize -finline-functions" +- ;; +- *) +- # this sometimes provokes bugs in gcc 2.95.2 +- : #optimize="$optimize -fstrength-reduce" +- ;; +- esac +- ;; +- esac ++ optimize="-O2" + fi + + case "$host" in +@@ -21497,6 +21433,7 @@ + then + case "$host" in + i?86-*) FPM="INTEL" ;; ++ x86_64*) FPM="64BIT" ;; + arm*-*) FPM="ARM" ;; + mips*-*) FPM="MIPS" ;; + sparc*-*) FPM="SPARC" ;; diff --git a/audio/paddleaudio/third_party/patches/sox.patch b/audio/paddleaudio/third_party/patches/sox.patch new file mode 100644 index 00000000..fe8df945 --- /dev/null +++ b/audio/paddleaudio/third_party/patches/sox.patch @@ -0,0 +1,16 @@ +See https://github.com/pytorch/audio/pull/1297 +diff -ru sox/src/formats.c sox/src/formats.c +--- sox/src/formats.c 2014-10-26 19:55:50.000000000 -0700 ++++ sox/src/formats.c 2021-02-22 16:01:02.833144070 -0800 +@@ -333,6 +333,10 @@ + assert(ft); + if (!ft->fp) + return sox_false; +- fstat(fileno((FILE*)ft->fp), &st); ++ int fd = fileno((FILE*)ft->fp); ++ if (fd < 0) ++ return sox_false; ++ if (fstat(fd, &st) < 0) ++ return sox_false; + return ((st.st_mode & S_IFMT) == S_IFREG); + } diff --git a/audio/paddleaudio/third_party/sox/CMakeLists.txt b/audio/paddleaudio/third_party/sox/CMakeLists.txt new file mode 100644 index 00000000..8a5bc55c --- /dev/null +++ b/audio/paddleaudio/third_party/sox/CMakeLists.txt @@ -0,0 +1,254 @@ +find_package(PkgConfig REQUIRED) + +include(ExternalProject) + +set(INSTALL_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../install) +set(ARCHIVE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../archives) +set(patch_dir ${CMAKE_CURRENT_SOURCE_DIR}/../patches) +set(COMMON_ARGS --quiet --disable-shared --enable-static --prefix=${INSTALL_DIR} --with-pic --disable-dependency-tracking --disable-debug --disable-examples --disable-doc) + +# To pass custom environment variables to ExternalProject_Add command, +# we need to do `${CMAKE_COMMAND} -E env ${envs} `. +# https://stackoverflow.com/a/62437353 +# We constrcut the custom environment variables here +set(envs + "PKG_CONFIG_PATH=${INSTALL_DIR}/lib/pkgconfig" + "LDFLAGS=-L${INSTALL_DIR}/lib $ENV{LDFLAGS}" + "CFLAGS=-I${INSTALL_DIR}/include -fvisibility=hidden $ENV{CFLAGS}" +) + +if (BUILD_MAD) + ExternalProject_Add(mad + PREFIX ${CMAKE_CURRENT_BINARY_DIR} + DOWNLOAD_DIR ${ARCHIVE_DIR} + URL https://downloads.sourceforge.net/project/mad/libmad/0.15.1b/libmad-0.15.1b.tar.gz + URL_HASH SHA256=bbfac3ed6bfbc2823d3775ebb931087371e142bb0e9bb1bee51a76a6e0078690 + PATCH_COMMAND patch < ${patch_dir}/libmad.patch && cp ${patch_dir}/config.guess ${patch_dir}/config.sub ${CMAKE_CURRENT_BINARY_DIR}/src/mad/ + CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env ${envs} ${CMAKE_CURRENT_BINARY_DIR}/src/mad/configure ${COMMON_ARGS} + DOWNLOAD_NO_PROGRESS ON + LOG_DOWNLOAD ON + LOG_UPDATE ON + LOG_CONFIGURE ON + LOG_BUILD ON + LOG_INSTALL ON + LOG_MERGED_STDOUTERR ON + LOG_OUTPUT_ON_FAILURE ON + ) +endif (BUILD_MAD) + +ExternalProject_Add(amr + PREFIX ${CMAKE_CURRENT_BINARY_DIR} + DOWNLOAD_DIR ${ARCHIVE_DIR} + URL https://sourceforge.net/projects/opencore-amr/files/opencore-amr/opencore-amr-0.1.5.tar.gz + URL_HASH SHA256=2c006cb9d5f651bfb5e60156dbff6af3c9d35c7bbcc9015308c0aff1e14cd341 + PATCH_COMMAND cp ${patch_dir}/config.guess ${patch_dir}/config.sub ${CMAKE_CURRENT_BINARY_DIR}/src/amr/ + CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env ${envs} ${CMAKE_CURRENT_BINARY_DIR}/src/amr/configure ${COMMON_ARGS} + DOWNLOAD_NO_PROGRESS ON + LOG_DOWNLOAD ON + LOG_UPDATE ON + LOG_CONFIGURE ON + LOG_BUILD ON + LOG_INSTALL ON + LOG_MERGED_STDOUTERR ON + LOG_OUTPUT_ON_FAILURE ON +) + +ExternalProject_Add(lame + PREFIX ${CMAKE_CURRENT_BINARY_DIR} + DOWNLOAD_DIR ${ARCHIVE_DIR} + URL https://downloads.sourceforge.net/project/lame/lame/3.99/lame-3.99.5.tar.gz + URL_HASH SHA256=24346b4158e4af3bd9f2e194bb23eb473c75fb7377011523353196b19b9a23ff + PATCH_COMMAND cp ${patch_dir}/config.guess ${patch_dir}/config.sub ${CMAKE_CURRENT_BINARY_DIR}/src/lame/ + CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env ${envs} ${CMAKE_CURRENT_BINARY_DIR}/src/lame/configure ${COMMON_ARGS} --enable-nasm + DOWNLOAD_NO_PROGRESS ON + LOG_DOWNLOAD ON + LOG_UPDATE ON + LOG_CONFIGURE ON + LOG_BUILD ON + LOG_INSTALL ON + LOG_MERGED_STDOUTERR ON + LOG_OUTPUT_ON_FAILURE ON +) + +ExternalProject_Add(ogg + PREFIX ${CMAKE_CURRENT_BINARY_DIR} + DOWNLOAD_DIR ${ARCHIVE_DIR} + URL https://ftp.osuosl.org/pub/xiph/releases/ogg/libogg-1.3.3.tar.gz + URL_HASH SHA256=c2e8a485110b97550f453226ec644ebac6cb29d1caef2902c007edab4308d985 + PATCH_COMMAND cp ${patch_dir}/config.guess ${patch_dir}/config.sub ${CMAKE_CURRENT_BINARY_DIR}/src/ogg/ + CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env ${envs} ${CMAKE_CURRENT_BINARY_DIR}/src/ogg/configure ${COMMON_ARGS} + DOWNLOAD_NO_PROGRESS ON + LOG_DOWNLOAD ON + LOG_UPDATE ON + LOG_CONFIGURE ON + LOG_BUILD ON + LOG_INSTALL ON + LOG_MERGED_STDOUTERR ON + LOG_OUTPUT_ON_FAILURE ON +) + +ExternalProject_Add(flac + PREFIX ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ogg + DOWNLOAD_DIR ${ARCHIVE_DIR} + URL https://ftp.osuosl.org/pub/xiph/releases/flac/flac-1.3.2.tar.xz + URL_HASH SHA256=91cfc3ed61dc40f47f050a109b08610667d73477af6ef36dcad31c31a4a8d53f + PATCH_COMMAND cp ${patch_dir}/config.guess ${patch_dir}/config.sub ${CMAKE_CURRENT_BINARY_DIR}/src/flac/ + CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env ${envs} ${CMAKE_CURRENT_BINARY_DIR}/src/flac/configure ${COMMON_ARGS} --with-ogg --disable-cpplibs + DOWNLOAD_NO_PROGRESS ON + LOG_DOWNLOAD ON + LOG_UPDATE ON + LOG_CONFIGURE ON + LOG_BUILD ON + LOG_INSTALL ON + LOG_MERGED_STDOUTERR ON + LOG_OUTPUT_ON_FAILURE ON +) + +ExternalProject_Add(vorbis + PREFIX ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ogg + DOWNLOAD_DIR ${ARCHIVE_DIR} + URL https://ftp.osuosl.org/pub/xiph/releases/vorbis/libvorbis-1.3.6.tar.gz + URL_HASH SHA256=6ed40e0241089a42c48604dc00e362beee00036af2d8b3f46338031c9e0351cb + PATCH_COMMAND cp ${patch_dir}/config.guess ${patch_dir}/config.sub ${CMAKE_CURRENT_BINARY_DIR}/src/vorbis/ + CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env ${envs} ${CMAKE_CURRENT_BINARY_DIR}/src/vorbis/configure ${COMMON_ARGS} --with-ogg + DOWNLOAD_NO_PROGRESS ON + LOG_DOWNLOAD ON + LOG_UPDATE ON + LOG_CONFIGURE ON + LOG_BUILD ON + LOG_INSTALL ON + LOG_MERGED_STDOUTERR ON + LOG_OUTPUT_ON_FAILURE ON +) + +ExternalProject_Add(opus + PREFIX ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ogg + DOWNLOAD_DIR ${ARCHIVE_DIR} + URL https://ftp.osuosl.org/pub/xiph/releases/opus/opus-1.3.1.tar.gz + URL_HASH SHA256=65b58e1e25b2a114157014736a3d9dfeaad8d41be1c8179866f144a2fb44ff9d + PATCH_COMMAND cp ${patch_dir}/config.guess ${patch_dir}/config.sub ${CMAKE_CURRENT_BINARY_DIR}/src/opus/ + CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env ${envs} ${CMAKE_CURRENT_BINARY_DIR}/src/opus/configure ${COMMON_ARGS} --with-ogg + DOWNLOAD_NO_PROGRESS ON + LOG_DOWNLOAD ON + LOG_UPDATE ON + LOG_CONFIGURE ON + LOG_BUILD ON + LOG_INSTALL ON + LOG_MERGED_STDOUTERR ON + LOG_OUTPUT_ON_FAILURE ON +) + +ExternalProject_Add(opusfile + PREFIX ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS opus + DOWNLOAD_DIR ${ARCHIVE_DIR} + URL https://ftp.osuosl.org/pub/xiph/releases/opus/opusfile-0.12.tar.gz + URL_HASH SHA256=118d8601c12dd6a44f52423e68ca9083cc9f2bfe72da7a8c1acb22a80ae3550b + PATCH_COMMAND cp ${patch_dir}/config.guess ${patch_dir}/config.sub ${CMAKE_CURRENT_BINARY_DIR}/src/opusfile/ + CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env ${envs} ${CMAKE_CURRENT_BINARY_DIR}/src/opusfile/configure ${COMMON_ARGS} --disable-http + DOWNLOAD_NO_PROGRESS ON + LOG_DOWNLOAD ON + LOG_UPDATE ON + LOG_CONFIGURE ON + LOG_BUILD ON + LOG_INSTALL ON + LOG_MERGED_STDOUTERR ON + LOG_OUTPUT_ON_FAILURE ON +) + +# OpenMP is by default compiled against GNU OpenMP, which conflicts with the version of OpenMP that PyTorch uses. +# See https://github.com/pytorch/audio/pull/1026 +# TODO: Add flags like https://github.com/suphoff/pytorch_parallel_extension_cpp/blob/master/setup.py +set(SOX_OPTIONS + --disable-openmp + --with-amrnb + --with-amrwb + --with-flac + --with-lame + --with-oggvorbis + --with-opus + --without-alsa + --without-ao + --without-coreaudio + --without-oss + --without-id3tag + --without-ladspa + --without-magic + --without-png + --without-pulseaudio + --without-sndfile + --without-sndio + --without-sunaudio + --without-waveaudio + --without-wavpack + --without-twolame + ) + +set(SOX_LIBRARIES + ${INSTALL_DIR}/lib/libsox.a + ${INSTALL_DIR}/lib/libopencore-amrnb.a + ${INSTALL_DIR}/lib/libopencore-amrwb.a + ${INSTALL_DIR}/lib/libmp3lame.a + ${INSTALL_DIR}/lib/libFLAC.a + ${INSTALL_DIR}/lib/libopusfile.a + ${INSTALL_DIR}/lib/libopus.a + ${INSTALL_DIR}/lib/libvorbisenc.a + ${INSTALL_DIR}/lib/libvorbisfile.a + ${INSTALL_DIR}/lib/libvorbis.a + ${INSTALL_DIR}/lib/libogg.a + ) + +set(sox_depends + ogg flac vorbis opusfile lame amr + ) + +if (BUILD_MAD) + list( + APPEND + SOX_OPTIONS + --with-mad + ) + list( + APPEND + SOX_LIBRARIES + ${INSTALL_DIR}/lib/libmad.a + ) + list( + APPEND + sox_depends + mad + ) +else () + list( + APPEND + SOX_OPTIONS + --without-mad + ) +endif (BUILD_MAD) + +ExternalProject_Add(sox + PREFIX ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${sox_depends} + DOWNLOAD_DIR ${ARCHIVE_DIR} + URL https://downloads.sourceforge.net/project/sox/sox/14.4.2/sox-14.4.2.tar.bz2 + URL_HASH SHA256=81a6956d4330e75b5827316e44ae381e6f1e8928003c6aa45896da9041ea149c + PATCH_COMMAND patch -p1 < ${patch_dir}/sox.patch && cp ${patch_dir}/config.guess ${patch_dir}/config.sub ${CMAKE_CURRENT_BINARY_DIR}/src/sox/ + CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env ${envs} ${CMAKE_CURRENT_BINARY_DIR}/src/sox/configure ${COMMON_ARGS} ${SOX_OPTIONS} + BUILD_BYPRODUCTS ${SOX_LIBRARIES} + DOWNLOAD_NO_PROGRESS ON + LOG_DOWNLOAD ON + LOG_UPDATE ON + LOG_CONFIGURE ON + LOG_BUILD ON + LOG_INSTALL ON + LOG_MERGED_STDOUTERR ON + LOG_OUTPUT_ON_FAILURE ON +) + +add_library(libsox INTERFACE) +add_dependencies(libsox sox) +target_include_directories(libsox INTERFACE ${INSTALL_DIR}/include) +target_link_libraries(libsox INTERFACE ${SOX_LIBRARIES}) \ No newline at end of file diff --git a/audio/paddleaudio/utils/__init__.py b/audio/paddleaudio/utils/__init__.py new file mode 100644 index 00000000..e66d1ab4 --- /dev/null +++ b/audio/paddleaudio/utils/__init__.py @@ -0,0 +1,27 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License" +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from .download import decompress +from .download import download_and_decompress +from .download import load_state_dict_from_url +from .env import DATA_HOME +from .env import MODEL_HOME +from .env import PPAUDIO_HOME +from .env import USER_HOME +from .error import ParameterError +from .log import Logger +from .log import logger +from .numeric import depth_convert +from .numeric import pcm16to32 +from .time import seconds_to_hms +from .time import Timer diff --git a/audio/paddleaudio/utils/download.py b/audio/paddleaudio/utils/download.py new file mode 100644 index 00000000..07d5eea8 --- /dev/null +++ b/audio/paddleaudio/utils/download.py @@ -0,0 +1,64 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License" +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import os +from typing import Dict +from typing import List + +from paddle.framework import load as load_state_dict +from paddle.utils import download + +from .log import logger + +download.logger = logger + +__all__ = [ + 'decompress', + 'download_and_decompress', + 'load_state_dict_from_url', +] + + +def decompress(file: str): + """ + Extracts all files from a compressed file. + """ + assert os.path.isfile(file), "File: {} not exists.".format(file) + download._decompress(file) + + +def download_and_decompress(archives: List[Dict[str, str]], + path: str, + decompress: bool=True): + """ + Download archieves and decompress to specific path. + """ + if not os.path.isdir(path): + os.makedirs(path) + + for archive in archives: + assert 'url' in archive and 'md5' in archive, \ + 'Dictionary keys of "url" and "md5" are required in the archive, but got: {list(archieve.keys())}' + download.get_path_from_url( + archive['url'], path, archive['md5'], decompress=decompress) + + +def load_state_dict_from_url(url: str, path: str, md5: str=None): + """ + Download and load a state dict from url + """ + if not os.path.isdir(path): + os.makedirs(path) + + download.get_path_from_url(url, path, md5) + return load_state_dict(os.path.join(path, os.path.basename(url))) diff --git a/audio/paddleaudio/utils/env.py b/audio/paddleaudio/utils/env.py new file mode 100644 index 00000000..a2d14b89 --- /dev/null +++ b/audio/paddleaudio/utils/env.py @@ -0,0 +1,60 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License" +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +''' +This module is used to store environmental variables in PaddleAudio. +PPAUDIO_HOME --> the root directory for storing PaddleAudio related data. Default to ~/.paddleaudio. Users can change the +├ default value through the PPAUDIO_HOME environment variable. +├─ MODEL_HOME --> Store model files. +└─ DATA_HOME --> Store automatically downloaded datasets. +''' +import os + +__all__ = [ + 'USER_HOME', + 'PPAUDIO_HOME', + 'MODEL_HOME', + 'DATA_HOME', +] + + +def _get_user_home(): + return os.path.expanduser('~') + + +def _get_ppaudio_home(): + if 'PPAUDIO_HOME' in os.environ: + home_path = os.environ['PPAUDIO_HOME'] + if os.path.exists(home_path): + if os.path.isdir(home_path): + return home_path + else: + raise RuntimeError( + 'The environment variable PPAUDIO_HOME {} is not a directory.'. + format(home_path)) + else: + return home_path + return os.path.join(_get_user_home(), '.paddleaudio') + + +def _get_sub_home(directory): + home = os.path.join(_get_ppaudio_home(), directory) + if not os.path.exists(home): + os.makedirs(home) + return home + + +USER_HOME = _get_user_home() +PPAUDIO_HOME = _get_ppaudio_home() +MODEL_HOME = _get_sub_home('models') +DATA_HOME = _get_sub_home('datasets') diff --git a/paddlespeech/audio/io/__init__.py b/audio/paddleaudio/utils/error.py similarity index 83% rename from paddlespeech/audio/io/__init__.py rename to audio/paddleaudio/utils/error.py index 185a92b8..f3977489 100644 --- a/paddlespeech/audio/io/__init__.py +++ b/audio/paddleaudio/utils/error.py @@ -11,3 +11,10 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + +__all__ = ['ParameterError'] + + +class ParameterError(Exception): + """Exception class for Parameter checking""" + pass diff --git a/audio/paddleaudio/utils/log.py b/audio/paddleaudio/utils/log.py new file mode 100644 index 00000000..5656b286 --- /dev/null +++ b/audio/paddleaudio/utils/log.py @@ -0,0 +1,139 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License" +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import contextlib +import functools +import logging +import threading +import time + +import colorlog + +__all__ = [ + 'Logger', + 'logger', +] + +log_config = { + 'DEBUG': { + 'level': 10, + 'color': 'purple' + }, + 'INFO': { + 'level': 20, + 'color': 'green' + }, + 'TRAIN': { + 'level': 21, + 'color': 'cyan' + }, + 'EVAL': { + 'level': 22, + 'color': 'blue' + }, + 'WARNING': { + 'level': 30, + 'color': 'yellow' + }, + 'ERROR': { + 'level': 40, + 'color': 'red' + }, + 'CRITICAL': { + 'level': 50, + 'color': 'bold_red' + } +} + + +class Logger(object): + ''' + Deafult logger in PaddleAudio + Args: + name(str) : Logger name, default is 'PaddleAudio' + ''' + + def __init__(self, name: str=None): + name = 'PaddleAudio' if not name else name + self.logger = logging.getLogger(name) + + for key, conf in log_config.items(): + logging.addLevelName(conf['level'], key) + self.__dict__[key] = functools.partial(self.__call__, conf['level']) + self.__dict__[key.lower()] = functools.partial(self.__call__, + conf['level']) + + self.format = colorlog.ColoredFormatter( + '%(log_color)s[%(asctime)-15s] [%(levelname)8s]%(reset)s - %(message)s', + log_colors={key: conf['color'] + for key, conf in log_config.items()}) + + self.handler = logging.StreamHandler() + self.handler.setFormatter(self.format) + + self.logger.addHandler(self.handler) + self.logLevel = 'DEBUG' + self.logger.setLevel(logging.DEBUG) + self.logger.propagate = False + self._is_enable = True + + def disable(self): + self._is_enable = False + + def enable(self): + self._is_enable = True + + @property + def is_enable(self) -> bool: + return self._is_enable + + def __call__(self, log_level: str, msg: str): + if not self.is_enable: + return + + self.logger.log(log_level, msg) + + @contextlib.contextmanager + def use_terminator(self, terminator: str): + old_terminator = self.handler.terminator + self.handler.terminator = terminator + yield + self.handler.terminator = old_terminator + + @contextlib.contextmanager + def processing(self, msg: str, interval: float=0.1): + ''' + Continuously print a progress bar with rotating special effects. + Args: + msg(str): Message to be printed. + interval(float): Rotation interval. Default to 0.1. + ''' + end = False + + def _printer(): + index = 0 + flags = ['\\', '|', '/', '-'] + while not end: + flag = flags[index % len(flags)] + with self.use_terminator('\r'): + self.info('{}: {}'.format(msg, flag)) + time.sleep(interval) + index += 1 + + t = threading.Thread(target=_printer) + t.start() + yield + end = True + + +logger = Logger() diff --git a/audio/paddleaudio/utils/numeric.py b/audio/paddleaudio/utils/numeric.py new file mode 100644 index 00000000..9fe00484 --- /dev/null +++ b/audio/paddleaudio/utils/numeric.py @@ -0,0 +1,107 @@ +# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from typing import Union + +import numpy as np + +__all__ = ["pcm16to32", "depth_convert"] + + +def pcm16to32(audio: np.ndarray) -> np.ndarray: + """pcm int16 to float32 + + Args: + audio (np.ndarray): Waveform with dtype of int16. + + Returns: + np.ndarray: Waveform with dtype of float32. + """ + if audio.dtype == np.int16: + audio = audio.astype("float32") + bits = np.iinfo(np.int16).bits + audio = audio / (2**(bits - 1)) + return audio + + +def _safe_cast(y: np.ndarray, dtype: Union[type, str]) -> np.ndarray: + """Data type casting in a safe way, i.e., prevent overflow or underflow. + + Args: + y (np.ndarray): Input waveform array in 1D or 2D. + dtype (Union[type, str]): Data type of waveform. + + Returns: + np.ndarray: `y` after safe casting. + """ + if 'float' in str(y.dtype): + return np.clip(y, np.finfo(dtype).min, + np.finfo(dtype).max).astype(dtype) + else: + return np.clip(y, np.iinfo(dtype).min, + np.iinfo(dtype).max).astype(dtype) + + +def depth_convert(y: np.ndarray, dtype: Union[type, str]) -> np.ndarray: + """Convert audio array to target dtype safely. + This function convert audio waveform to a target dtype, with addition steps of + preventing overflow/underflow and preserving audio range. + + Args: + y (np.ndarray): Input waveform array in 1D or 2D. + dtype (Union[type, str]): Data type of waveform. + + Returns: + np.ndarray: `y` after safe casting. + """ + + SUPPORT_DTYPE = ['int16', 'int8', 'float32', 'float64'] + if y.dtype not in SUPPORT_DTYPE: + raise ParameterError( + 'Unsupported audio dtype, ' + f'y.dtype is {y.dtype}, supported dtypes are {SUPPORT_DTYPE}') + + if dtype not in SUPPORT_DTYPE: + raise ParameterError( + 'Unsupported audio dtype, ' + f'target dtype is {dtype}, supported dtypes are {SUPPORT_DTYPE}') + + if dtype == y.dtype: + return y + + if dtype == 'float64' and y.dtype == 'float32': + return _safe_cast(y, dtype) + if dtype == 'float32' and y.dtype == 'float64': + return _safe_cast(y, dtype) + + if dtype == 'int16' or dtype == 'int8': + if y.dtype in ['float64', 'float32']: + factor = np.iinfo(dtype).max + y = np.clip(y * factor, np.iinfo(dtype).min, + np.iinfo(dtype).max).astype(dtype) + y = y.astype(dtype) + else: + if dtype == 'int16' and y.dtype == 'int8': + factor = np.iinfo('int16').max / np.iinfo('int8').max - EPS + y = y.astype('float32') * factor + y = y.astype('int16') + + else: # dtype == 'int8' and y.dtype=='int16': + y = y.astype('int32') * np.iinfo('int8').max / \ + np.iinfo('int16').max + y = y.astype('int8') + + if dtype in ['float32', 'float64']: + org_dtype = y.dtype + y = y.astype(dtype) / np.iinfo(org_dtype).max + return y diff --git a/audio/paddleaudio/utils/sox_utils.py b/audio/paddleaudio/utils/sox_utils.py new file mode 100644 index 00000000..305bb68b --- /dev/null +++ b/audio/paddleaudio/utils/sox_utils.py @@ -0,0 +1,103 @@ +from typing import Dict +from typing import List + +import paddleaudio +from paddleaudio._internal import module_utils as _mod_utils + + +@_mod_utils.requires_sox() +def set_seed(seed: int): + """Set libsox's PRNG + + Args: + seed (int): seed value. valid range is int32. + + See Also: + http://sox.sourceforge.net/sox.html + """ + paddleaudio._paddleaudio.sox_utils_set_seed(seed) + + +@_mod_utils.requires_sox() +def set_verbosity(verbosity: int): + """Set libsox's verbosity + + Args: + verbosity (int): Set verbosity level of libsox. + + * ``1`` failure messages + * ``2`` warnings + * ``3`` details of processing + * ``4``-``6`` increasing levels of debug messages + + See Also: + http://sox.sourceforge.net/sox.html + """ + paddleaudio._paddleaudio.sox_utils_set_verbosity(verbosity) + + +@_mod_utils.requires_sox() +def set_buffer_size(buffer_size: int): + """Set buffer size for sox effect chain + + Args: + buffer_size (int): Set the size in bytes of the buffers used for processing audio. + + See Also: + http://sox.sourceforge.net/sox.html + """ + paddleaudio._paddleaudio.sox_utils_set_buffer_size(buffer_size) + + +@_mod_utils.requires_sox() +def set_use_threads(use_threads: bool): + """Set multithread option for sox effect chain + + Args: + use_threads (bool): When ``True``, enables ``libsox``'s parallel effects channels processing. + To use mutlithread, the underlying ``libsox`` has to be compiled with OpenMP support. + + See Also: + http://sox.sourceforge.net/sox.html + """ + paddleaudio._paddleaudio.sox_utils_set_use_threads(use_threads) + + +@_mod_utils.requires_sox() +def list_effects() -> Dict[str, str]: + """List the available sox effect names + + Returns: + Dict[str, str]: Mapping from ``effect name`` to ``usage`` + """ + return dict(paddleaudio._paddleaudio.sox_utils_list_effects()) + + +@_mod_utils.requires_sox() +def list_read_formats() -> List[str]: + """List the supported audio formats for read + + Returns: + List[str]: List of supported audio formats + """ + return paddleaudio._paddleaudio.sox_utils_list_read_formats() + + +@_mod_utils.requires_sox() +def list_write_formats() -> List[str]: + """List the supported audio formats for write + + Returns: + List[str]: List of supported audio formats + """ + return paddleaudio._paddleaudio.sox_utils_list_write_formats() + + +@_mod_utils.requires_sox() +def get_buffer_size() -> int: + """Get buffer size for sox effect chain + + Returns: + int: size in bytes of buffers used for processing audio. + """ + return paddleaudio._paddleaudio.sox_utils_get_buffer_size() diff --git a/audio/paddleaudio/utils/tensor_utils.py b/audio/paddleaudio/utils/tensor_utils.py new file mode 100644 index 00000000..16f60810 --- /dev/null +++ b/audio/paddleaudio/utils/tensor_utils.py @@ -0,0 +1,192 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Unility functions for Transformer.""" +from typing import List +from typing import Tuple + +import paddle + +from .log import Logger + +__all__ = ["pad_sequence", "add_sos_eos", "th_accuracy", "has_tensor"] + +logger = Logger(__name__) + + +def has_tensor(val): + if isinstance(val, (list, tuple)): + for item in val: + if has_tensor(item): + return True + elif isinstance(val, dict): + for k, v in val.items(): + print(k) + if has_tensor(v): + return True + else: + return paddle.is_tensor(val) + + +def pad_sequence(sequences: List[paddle.Tensor], + batch_first: bool=False, + padding_value: float=0.0) -> paddle.Tensor: + r"""Pad a list of variable length Tensors with ``padding_value`` + + ``pad_sequence`` stacks a list of Tensors along a new dimension, + and pads them to equal length. For example, if the input is list of + sequences with size ``L x *`` and if batch_first is False, and ``T x B x *`` + otherwise. + + `B` is batch size. It is equal to the number of elements in ``sequences``. + `T` is length of the longest sequence. + `L` is length of the sequence. + `*` is any number of trailing dimensions, including none. + + Example: + >>> from paddle.nn.utils.rnn import pad_sequence + >>> a = paddle.ones(25, 300) + >>> b = paddle.ones(22, 300) + >>> c = paddle.ones(15, 300) + >>> pad_sequence([a, b, c]).shape + paddle.Tensor([25, 3, 300]) + + Note: + This function returns a Tensor of size ``T x B x *`` or ``B x T x *`` + where `T` is the length of the longest sequence. This function assumes + trailing dimensions and type of all the Tensors in sequences are same. + + Args: + sequences (list[Tensor]): list of variable length sequences. + batch_first (bool, optional): output will be in ``B x T x *`` if True, or in + ``T x B x *`` otherwise + padding_value (float, optional): value for padded elements. Default: 0. + + Returns: + Tensor of size ``T x B x *`` if :attr:`batch_first` is ``False``. + Tensor of size ``B x T x *`` otherwise + """ + + # assuming trailing dimensions and type of all the Tensors + # in sequences are same and fetching those from sequences[0] + max_size = paddle.shape(sequences[0]) + # (TODO Hui Zhang): slice not supprot `end==start` + # trailing_dims = max_size[1:] + trailing_dims = tuple( + max_size[1:].numpy().tolist()) if sequences[0].ndim >= 2 else () + max_len = max([s.shape[0] for s in sequences]) + if batch_first: + out_dims = (len(sequences), max_len) + trailing_dims + else: + out_dims = (max_len, len(sequences)) + trailing_dims + out_tensor = paddle.full(out_dims, padding_value, sequences[0].dtype) + for i, tensor in enumerate(sequences): + length = tensor.shape[0] + # use index notation to prevent duplicate references to the tensor + if batch_first: + # TODO (Hui Zhang): set_value op not supprot `end==start` + # TODO (Hui Zhang): set_value op not support int16 + # TODO (Hui Zhang): set_varbase 2 rank not support [0,0,...] + # out_tensor[i, :length, ...] = tensor + if length != 0: + out_tensor[i, :length] = tensor + else: + out_tensor[i, length] = tensor + else: + # TODO (Hui Zhang): set_value op not supprot `end==start` + # out_tensor[:length, i, ...] = tensor + if length != 0: + out_tensor[:length, i] = tensor + else: + out_tensor[length, i] = tensor + + return out_tensor + + +def add_sos_eos(ys_pad: paddle.Tensor, sos: int, eos: int, + ignore_id: int) -> Tuple[paddle.Tensor, paddle.Tensor]: + """Add and labels. + Args: + ys_pad (paddle.Tensor): batch of padded target sequences (B, Lmax) + sos (int): index of + eos (int): index of + ignore_id (int): index of padding + Returns: + ys_in (paddle.Tensor) : (B, Lmax + 1) + ys_out (paddle.Tensor) : (B, Lmax + 1) + Examples: + >>> sos_id = 10 + >>> eos_id = 11 + >>> ignore_id = -1 + >>> ys_pad + tensor([[ 1, 2, 3, 4, 5], + [ 4, 5, 6, -1, -1], + [ 7, 8, 9, -1, -1]], dtype=paddle.int32) + >>> ys_in,ys_out=add_sos_eos(ys_pad, sos_id , eos_id, ignore_id) + >>> ys_in + tensor([[10, 1, 2, 3, 4, 5], + [10, 4, 5, 6, 11, 11], + [10, 7, 8, 9, 11, 11]]) + >>> ys_out + tensor([[ 1, 2, 3, 4, 5, 11], + [ 4, 5, 6, 11, -1, -1], + [ 7, 8, 9, 11, -1, -1]]) + """ + # TODO(Hui Zhang): using comment code, + #_sos = paddle.to_tensor( + # [sos], dtype=paddle.long, stop_gradient=True, place=ys_pad.place) + #_eos = paddle.to_tensor( + # [eos], dtype=paddle.long, stop_gradient=True, place=ys_pad.place) + #ys = [y[y != ignore_id] for y in ys_pad] # parse padded ys + #ys_in = [paddle.cat([_sos, y], dim=0) for y in ys] + #ys_out = [paddle.cat([y, _eos], dim=0) for y in ys] + #return pad_sequence(ys_in, padding_value=eos), pad_sequence(ys_out, padding_value=ignore_id) + B = ys_pad.shape[0] + _sos = paddle.ones([B, 1], dtype=ys_pad.dtype) * sos + _eos = paddle.ones([B, 1], dtype=ys_pad.dtype) * eos + ys_in = paddle.cat([_sos, ys_pad], dim=1) + mask_pad = (ys_in == ignore_id) + ys_in = ys_in.masked_fill(mask_pad, eos) + + ys_out = paddle.cat([ys_pad, _eos], dim=1) + ys_out = ys_out.masked_fill(mask_pad, eos) + mask_eos = (ys_out == ignore_id) + ys_out = ys_out.masked_fill(mask_eos, eos) + ys_out = ys_out.masked_fill(mask_pad, ignore_id) + return ys_in, ys_out + + +def th_accuracy(pad_outputs: paddle.Tensor, + pad_targets: paddle.Tensor, + ignore_label: int) -> float: + """Calculate accuracy. + Args: + pad_outputs (Tensor): Prediction tensors (B * Lmax, D). + pad_targets (LongTensor): Target label tensors (B, Lmax, D). + ignore_label (int): Ignore label id. + Returns: + float: Accuracy value (0.0 - 1.0). + """ + pad_pred = pad_outputs.view(pad_targets.shape[0], pad_targets.shape[1], + pad_outputs.shape[1]).argmax(2) + mask = pad_targets != ignore_label + #TODO(Hui Zhang): sum not support bool type + # numerator = paddle.sum( + # pad_pred.masked_select(mask) == pad_targets.masked_select(mask)) + numerator = ( + pad_pred.masked_select(mask) == pad_targets.masked_select(mask)) + numerator = paddle.sum(numerator.type_as(pad_targets)) + #TODO(Hui Zhang): sum not support bool type + # denominator = paddle.sum(mask) + denominator = paddle.sum(mask.type_as(pad_targets)) + return float(numerator) / float(denominator) diff --git a/audio/paddleaudio/utils/time.py b/audio/paddleaudio/utils/time.py new file mode 100644 index 00000000..105208f9 --- /dev/null +++ b/audio/paddleaudio/utils/time.py @@ -0,0 +1,72 @@ +# Copyright (c) 2021 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License" +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import math +import time + +__all__ = [ + 'Timer', + 'seconds_to_hms', +] + + +class Timer(object): + '''Calculate runing speed and estimated time of arrival(ETA)''' + + def __init__(self, total_step: int): + self.total_step = total_step + self.last_start_step = 0 + self.current_step = 0 + self._is_running = True + + def start(self): + self.last_time = time.time() + self.start_time = time.time() + + def stop(self): + self._is_running = False + self.end_time = time.time() + + def count(self) -> int: + if not self.current_step >= self.total_step: + self.current_step += 1 + return self.current_step + + @property + def timing(self) -> float: + run_steps = self.current_step - self.last_start_step + self.last_start_step = self.current_step + time_used = time.time() - self.last_time + self.last_time = time.time() + return run_steps / time_used + + @property + def is_running(self) -> bool: + return self._is_running + + @property + def eta(self) -> str: + if not self.is_running: + return '00:00:00' + scale = self.total_step / self.current_step + remaining_time = (time.time() - self.start_time) * scale + return seconds_to_hms(remaining_time) + + +def seconds_to_hms(seconds: int) -> str: + '''Convert the number of seconds to hh:mm:ss''' + h = math.floor(seconds / 3600) + m = math.floor((seconds - h * 3600) / 60) + s = int(seconds - h * 3600 - m * 60) + hms_str = '{:0>2}:{:0>2}:{:0>2}'.format(h, m, s) + return hms_str diff --git a/audio/setup.py b/audio/setup.py new file mode 100644 index 00000000..e8d90606 --- /dev/null +++ b/audio/setup.py @@ -0,0 +1,293 @@ +# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import contextlib +import inspect +import io +import os +import platform +import subprocess as sp +import sys +from pathlib import Path +from typing import List +from typing import Tuple +from typing import Union + +import distutils.command.clean +from setuptools import Command +from setuptools import find_packages +from setuptools import setup +from setuptools.command.develop import develop +from setuptools.command.test import test + +from tools import setup_helpers + +ROOT_DIR = Path(__file__).parent.resolve() + +VERSION = '1.1.0' +COMMITID = 'none' + +base = [ + "kaldiio", + "librosa==0.8.1", + "scipy>=1.0.0", + "soundfile~=0.10", + "colorlog", + "pathos == 0.2.8", + "pybind11", + "parameterized", + "tqdm" +] + +requirements = { + "install": + base, + "develop": [ + "sox", + "soxbindings", + "pre-commit", + ], +} + +def check_call(cmd: str, shell=False, executable=None): + try: + sp.check_call( + cmd.split(), + shell=shell, + executable="/bin/bash" if shell else executable) + except sp.CalledProcessError as e: + print( + f"{__file__}:{inspect.currentframe().f_lineno}: CMD: {cmd}, Error:", + e.output, + file=sys.stderr) + raise e + + +def check_output(cmd: Union[str, List[str], Tuple[str]], shell=False): + try: + + if isinstance(cmd, (list, tuple)): + cmds = cmd + else: + cmds = cmd.split() + out_bytes = sp.check_output(cmds) + + except sp.CalledProcessError as e: + out_bytes = e.output # Output generated before error + code = e.returncode # Return code + print( + f"{__file__}:{inspect.currentframe().f_lineno}: CMD: {cmd}, Error:", + out_bytes, + file=sys.stderr) + return out_bytes.strip().decode('utf8') + +def _run_cmd(cmd): + try: + return subprocess.check_output( + cmd, cwd=ROOT_DIR, + stderr=subprocess.DEVNULL).decode("ascii").strip() + except Exception: + return None + +@contextlib.contextmanager +def pushd(new_dir): + old_dir = os.getcwd() + os.chdir(new_dir) + print(new_dir) + yield + os.chdir(old_dir) + print(old_dir) + +def read(*names, **kwargs): + with io.open( + os.path.join(os.path.dirname(__file__), *names), + encoding=kwargs.get("encoding", "utf8")) as fp: + return fp.read() + +def _remove(files: str): + for f in files: + f.unlink() + +################################# Install ################################## + + +def _post_install(install_lib_dir): + pass + +class DevelopCommand(develop): + def run(self): + develop.run(self) + # must after develop.run, or pkg install by shell will not see + self.execute(_post_install, (self.install_lib, ), msg="Post Install...") + + +class TestCommand(test): + def finalize_options(self): + test.finalize_options(self) + self.test_args = [] + self.test_suite = True + + def run_tests(self): + # Run nose ensuring that argv simulates running nosetests directly + import nose + nose.run_exit(argv=['nosetests', '-w', 'tests']) + + def run_benchmark(self): + for benchmark_item in glob.glob('tests/benchmark/*py'): + os.system(f'pytest {benchmark_item}') + + +# cmd: python setup.py upload +class UploadCommand(Command): + description = "Build and publish the package." + user_options = [] + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + try: + print("Removing previous dist/ ...") + shutil.rmtree(str(ROOT_DIR / "dist")) + except OSError: + pass + print("Building source distribution...") + sp.check_call([sys.executable, "setup.py", "sdist"]) + print("Uploading package to PyPi...") + sp.check_call(["twine", "upload", "dist/*"]) + sys.exit() + + +################################# Version ################################## +def _get_version(sha): + version = VERSION + if os.getenv("BUILD_VERSION"): + version = os.getenv("BUILD_VERSION") + elif sha is not None: + version += "+" + sha[:7] + return version + + +def _make_version_file(version, sha): + sha = "Unknown" if sha is None else sha + version_path = ROOT_DIR / "paddleaudio" / "__init__.py" + with open(version_path, "a") as f: + f.write(f"__version__ = '{version}'\n") + +def _rm_version(): + file_ = ROOT_DIR / "paddleaudio" / "__init__.py" + with open(file_, "r") as f: + lines = f.readlines() + with open(file_, "w") as f: + for line in lines: + if "__version__" not in line: + f.write(line) + + +################################# Steup ################################## +class clean(distutils.command.clean.clean): + def run(self): + # Run default behavior first + distutils.command.clean.clean.run(self) + + # Remove paddleaudio extension + for path in (ROOT_DIR / "paddleaudio").glob("**/*.so"): + print(f"removing '{path}'") + path.unlink() + # Remove build directory + build_dirs = [ + ROOT_DIR / "build", + ] + for path in build_dirs: + if path.exists(): + print(f"removing '{path}' (and everything under it)") + shutil.rmtree(str(path), ignore_errors=True) + + +def main(): + + sha = _run_cmd(["git", "rev-parse", "HEAD"]) # commit id + branch = _run_cmd(["git", "rev-parse", "--abbrev-ref", "HEAD"]) + tag = _run_cmd(["git", "describe", "--tags", "--exact-match", "@"]) + print("-- Git branch:", branch) + print("-- Git SHA:", sha) + print("-- Git tag:", tag) + version = _get_version(sha) + print("-- Building version", version) + _rm_version() + + _make_version_file(version, sha) + lib_package_data = {} + if platform.system() != 'Windows' and platform.system() != 'Linux': + lib_package_data = {'paddleaudio': ['lib/libgcc_s.1.1.dylib']} + + if platform.system() == 'Linux': + lib_package_data = {'paddleaudio': ['lib/lib*']} + + setup_info = dict( + # Metadata + name='paddleaudio', + version=VERSION, + author='PaddlePaddle Speech and Language Team', + author_email='paddlesl@baidu.com', + url='https://github.com/PaddlePaddle/PaddleSpeech/audio', + license='Apache 2.0', + description='Speech audio tools based on Paddlepaddle', + keywords=[ + "audio process" + "paddlepaddle", + ], + python_requires='>=3.7', + install_requires=requirements["install"], + extras_require={ + 'develop': + requirements["develop"], + #'test': ["nose", "torchaudio==0.10.2", "pytest-benchmark", "librosa=0.8.1", "parameterized", "paddlepaddle"], + }, + cmdclass={ + "build_ext": setup_helpers.CMakeBuild, + 'develop': DevelopCommand, + 'test': TestCommand, + 'upload': UploadCommand, + "clean": clean, + }, + + # Package info + packages=find_packages(include=('paddleaudio*')), + package_data=lib_package_data, + ext_modules=setup_helpers.get_ext_modules(), + zip_safe=True, + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'Intended Audience :: Science/Research', + 'Topic :: Scientific/Engineering :: Artificial Intelligence', + 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + ], + ) + + setup(**setup_info) + _rm_version() + +if __name__ == '__main__': + main() diff --git a/tests/unit/audio/backends/base.py b/audio/tests/backends/base.py similarity index 100% rename from tests/unit/audio/backends/base.py rename to audio/tests/backends/base.py diff --git a/audio/tests/backends/common.py b/audio/tests/backends/common.py new file mode 100644 index 00000000..79b922a9 --- /dev/null +++ b/audio/tests/backends/common.py @@ -0,0 +1,32 @@ + +def get_encoding(ext, dtype): + exts = { + "mp3", + "flac", + "vorbis", + } + encodings = { + "float32": "PCM_F", + "int32": "PCM_S", + "int16": "PCM_S", + "uint8": "PCM_U", + } + return ext.upper() if ext in exts else encodings[dtype] + + +def get_bit_depth(dtype): + bit_depths = { + "float32": 32, + "int32": 32, + "int16": 16, + "uint8": 8, + } + return bit_depths[dtype] + +def get_bits_per_sample(ext, dtype): + bits_per_samples = { + "flac": 24, + "mp3": 0, + "vorbis": 0, + } + return bits_per_samples.get(ext, get_bit_depth(dtype)) diff --git a/audio/tests/backends/soundfile/base.py b/audio/tests/backends/soundfile/base.py new file mode 100644 index 00000000..a6719188 --- /dev/null +++ b/audio/tests/backends/soundfile/base.py @@ -0,0 +1,34 @@ +# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import os +import unittest +import urllib.request + +mono_channel_wav = 'https://paddlespeech.bj.bcebos.com/PaddleAudio/zh.wav' +multi_channels_wav = 'https://paddlespeech.bj.bcebos.com/PaddleAudio/cat.wav' + + +class BackendTest(unittest.TestCase): + def setUp(self): + self.initWavInput() + + def initWavInput(self): + self.files = [] + for url in [mono_channel_wav, multi_channels_wav]: + if not os.path.isfile(os.path.basename(url)): + urllib.request.urlretrieve(url, os.path.basename(url)) + self.files.append(os.path.basename(url)) + + def initParmas(self): + raise NotImplementedError diff --git a/audio/tests/backends/soundfile/common.py b/audio/tests/backends/soundfile/common.py new file mode 100644 index 00000000..eecead97 --- /dev/null +++ b/audio/tests/backends/soundfile/common.py @@ -0,0 +1,89 @@ +import itertools +from unittest import skipIf + +from paddleaudio._internal.module_utils import is_module_available +from parameterized import parameterized + + +def name_func(func, _, params): + return f'{func.__name__}_{"_".join(str(arg) for arg in params.args)}' + + +def dtype2subtype(dtype): + return { + "float64": "DOUBLE", + "float32": "FLOAT", + "int32": "PCM_32", + "int16": "PCM_16", + "uint8": "PCM_U8", + "int8": "PCM_S8", + }[dtype] + + +def skipIfFormatNotSupported(fmt): + fmts = [] + if is_module_available("soundfile"): + import soundfile + + fmts = soundfile.available_formats() + return skipIf(fmt not in fmts, f'"{fmt}" is not supported by soundfile') + return skipIf(True, '"soundfile" not available.') + + +def parameterize(*params): + return parameterized.expand( + list(itertools.product(*params)), name_func=name_func) + + +def fetch_wav_subtype(dtype, encoding, bits_per_sample): + subtype = { + (None, None): dtype2subtype(dtype), + (None, 8): "PCM_U8", + ("PCM_U", None): "PCM_U8", + ("PCM_U", 8): "PCM_U8", + ("PCM_S", None): "PCM_32", + ("PCM_S", 16): "PCM_16", + ("PCM_S", 32): "PCM_32", + ("PCM_F", None): "FLOAT", + ("PCM_F", 32): "FLOAT", + ("PCM_F", 64): "DOUBLE", + ("ULAW", None): "ULAW", + ("ULAW", 8): "ULAW", + ("ALAW", None): "ALAW", + ("ALAW", 8): "ALAW", + }.get((encoding, bits_per_sample)) + if subtype: + return subtype + raise ValueError(f"wav does not support ({encoding}, {bits_per_sample}).") + +def get_encoding(ext, dtype): + exts = { + "mp3", + "flac", + "vorbis", + } + encodings = { + "float32": "PCM_F", + "int32": "PCM_S", + "int16": "PCM_S", + "uint8": "PCM_U", + } + return ext.upper() if ext in exts else encodings[dtype] + + +def get_bit_depth(dtype): + bit_depths = { + "float32": 32, + "int32": 32, + "int16": 16, + "uint8": 8, + } + return bit_depths[dtype] + +def get_bits_per_sample(ext, dtype): + bits_per_samples = { + "flac": 24, + "mp3": 0, + "vorbis": 0, + } + return bits_per_samples.get(ext, get_bit_depth(dtype)) diff --git a/audio/tests/backends/soundfile/common_utils b/audio/tests/backends/soundfile/common_utils new file mode 120000 index 00000000..3ff3cef8 --- /dev/null +++ b/audio/tests/backends/soundfile/common_utils @@ -0,0 +1 @@ +../../common_utils \ No newline at end of file diff --git a/audio/tests/backends/soundfile/info_test.py b/audio/tests/backends/soundfile/info_test.py new file mode 100644 index 00000000..6814369f --- /dev/null +++ b/audio/tests/backends/soundfile/info_test.py @@ -0,0 +1,199 @@ +#this code is from: https://github.com/pytorch/audio/blob/main/test/torchaudio_unittest/backend/soundfile/info_test.py +import tarfile +import unittest +import warnings +from unittest.mock import patch + +import paddle +import soundfile +from common import get_bits_per_sample +from common import get_encoding +from common import parameterize +from common import skipIfFormatNotSupported +from common_utils import get_wav_data +from common_utils import nested_params +from common_utils import save_wav +from common_utils import TempDirMixin +from paddleaudio.backends import soundfile_backend + + +class TestInfo(TempDirMixin, unittest.TestCase): + @parameterize( + ["float32", "int32"], + [8000, 16000], + [1, 2], ) + def test_wav(self, dtype, sample_rate, num_channels): + """`soundfile_backend.info` can check wav file correctly""" + duration = 1 + path = self.get_temp_path("data.wav") + data = get_wav_data( + dtype, + num_channels, + normalize=False, + num_frames=duration * sample_rate) + save_wav(path, data, sample_rate) + info = soundfile_backend.info(path) + assert info.sample_rate == sample_rate + assert info.num_frames == sample_rate * duration + assert info.num_channels == num_channels + assert info.bits_per_sample == get_bits_per_sample("wav", dtype) + assert info.encoding == get_encoding("wav", dtype) + + @parameterize([8000, 16000], [1, 2]) + @skipIfFormatNotSupported("FLAC") + def test_flac(self, sample_rate, num_channels): + """`soundfile_backend.info` can check flac file correctly""" + duration = 1 + num_frames = sample_rate * duration + #data = torch.randn(num_frames, num_channels).numpy() + data = paddle.randn(shape=[num_frames, num_channels]).numpy() + + path = self.get_temp_path("data.flac") + soundfile.write(path, data, sample_rate) + + info = soundfile_backend.info(path) + assert info.sample_rate == sample_rate + assert info.num_frames == num_frames + assert info.num_channels == num_channels + assert info.bits_per_sample == 16 + assert info.encoding == "FLAC" + + #@parameterize([8000, 16000], [1, 2]) + #@skipIfFormatNotSupported("OGG") + #def test_ogg(self, sample_rate, num_channels): + #"""`soundfile_backend.info` can check ogg file correctly""" + #duration = 1 + #num_frames = sample_rate * duration + ##data = torch.randn(num_frames, num_channels).numpy() + #data = paddle.randn(shape=[num_frames, num_channels]).numpy() + #print(len(data)) + #path = self.get_temp_path("data.ogg") + #soundfile.write(path, data, sample_rate) + + #info = soundfile_backend.info(path) + #print(info) + #assert info.sample_rate == sample_rate + #print("info") + #print(info.num_frames) + #print("jiji") + #print(sample_rate*duration) + ##assert info.num_frames == sample_rate * duration + #assert info.num_channels == num_channels + #assert info.bits_per_sample == 0 + #assert info.encoding == "VORBIS" + + @nested_params( + [8000, 16000], + [1, 2], + [("PCM_24", 24), ("PCM_32", 32)], ) + @skipIfFormatNotSupported("NIST") + def test_sphere(self, sample_rate, num_channels, subtype_and_bit_depth): + """`soundfile_backend.info` can check sph file correctly""" + duration = 1 + num_frames = sample_rate * duration + #data = torch.randn(num_frames, num_channels).numpy() + data = paddle.randn(shape=[num_frames, num_channels]).numpy() + path = self.get_temp_path("data.nist") + subtype, bits_per_sample = subtype_and_bit_depth + soundfile.write(path, data, sample_rate, subtype=subtype) + + info = soundfile_backend.info(path) + assert info.sample_rate == sample_rate + assert info.num_frames == sample_rate * duration + assert info.num_channels == num_channels + assert info.bits_per_sample == bits_per_sample + assert info.encoding == "PCM_S" + + def test_unknown_subtype_warning(self): + """soundfile_backend.info issues a warning when the subtype is unknown + + This will happen if a new subtype is supported in SoundFile: the _SUBTYPE_TO_BITS_PER_SAMPLE + dict should be updated. + """ + + def _mock_info_func(_): + class MockSoundFileInfo: + samplerate = 8000 + frames = 356 + channels = 2 + subtype = "UNSEEN_SUBTYPE" + format = "UNKNOWN" + + return MockSoundFileInfo() + + with patch("soundfile.info", _mock_info_func): + with warnings.catch_warnings(record=True) as w: + info = soundfile_backend.info("foo") + assert len(w) == 1 + assert "UNSEEN_SUBTYPE subtype is unknown to PaddleAudio" in str( + w[-1].message) + assert info.bits_per_sample == 0 + + +class TestFileObject(TempDirMixin, unittest.TestCase): + def _test_fileobj(self, ext, subtype, bits_per_sample): + """Query audio via file-like object works""" + duration = 2 + sample_rate = 16000 + num_channels = 2 + num_frames = sample_rate * duration + path = self.get_temp_path(f"test.{ext}") + + #data = torch.randn(num_frames, num_channels).numpy() + data = paddle.randn(shape=[num_frames, num_channels]).numpy() + soundfile.write(path, data, sample_rate, subtype=subtype) + + with open(path, "rb") as fileobj: + info = soundfile_backend.info(fileobj) + assert info.sample_rate == sample_rate + assert info.num_frames == num_frames + assert info.num_channels == num_channels + assert info.bits_per_sample == bits_per_sample + assert info.encoding == "FLAC" if ext == "flac" else "PCM_S" + + def test_fileobj_wav(self): + """Loading audio via file-like object works""" + self._test_fileobj("wav", "PCM_16", 16) + + @skipIfFormatNotSupported("FLAC") + def test_fileobj_flac(self): + """Loading audio via file-like object works""" + self._test_fileobj("flac", "PCM_16", 16) + + def _test_tarobj(self, ext, subtype, bits_per_sample): + """Query compressed audio via file-like object works""" + duration = 2 + sample_rate = 16000 + num_channels = 2 + num_frames = sample_rate * duration + audio_file = f"test.{ext}" + audio_path = self.get_temp_path(audio_file) + archive_path = self.get_temp_path("archive.tar.gz") + + #data = torch.randn(num_frames, num_channels).numpy() + data = paddle.randn(shape=[num_frames, num_channels]).numpy() + soundfile.write(audio_path, data, sample_rate, subtype=subtype) + + with tarfile.TarFile(archive_path, "w") as tarobj: + tarobj.add(audio_path, arcname=audio_file) + with tarfile.TarFile(archive_path, "r") as tarobj: + fileobj = tarobj.extractfile(audio_file) + info = soundfile_backend.info(fileobj) + assert info.sample_rate == sample_rate + assert info.num_frames == num_frames + assert info.num_channels == num_channels + assert info.bits_per_sample == bits_per_sample + assert info.encoding == "FLAC" if ext == "flac" else "PCM_S" + + def test_tarobj_wav(self): + """Query compressed audio via file-like object works""" + self._test_tarobj("wav", "PCM_16", 16) + + @skipIfFormatNotSupported("FLAC") + def test_tarobj_flac(self): + """Query compressed audio via file-like object works""" + self._test_tarobj("flac", "PCM_16", 16) + + +if __name__ == '__main__': + unittest.main() diff --git a/audio/tests/backends/soundfile/load_test.py b/audio/tests/backends/soundfile/load_test.py new file mode 100644 index 00000000..78a25487 --- /dev/null +++ b/audio/tests/backends/soundfile/load_test.py @@ -0,0 +1,363 @@ +#this code is from: https://github.com/pytorch/audio/blob/main/test/torchaudio_unittest/backend/soundfile/load_test.py +import os +import tarfile +import unittest +from unittest.mock import patch + +import numpy as np +import paddle +import soundfile +from common import dtype2subtype +from common import parameterize +from common import skipIfFormatNotSupported +from common_utils import get_wav_data +from common_utils import load_wav +from common_utils import normalize_wav +from common_utils import save_wav +from common_utils import TempDirMixin +from paddleaudio.backends import soundfile_backend +from parameterized import parameterized + + +def _get_mock_path( + ext: str, + dtype: str, + sample_rate: int, + num_channels: int, + num_frames: int, ): + return f"{dtype}_{sample_rate}_{num_channels}_{num_frames}.{ext}" + + +def _get_mock_params(path: str): + filename, ext = path.split(".") + parts = filename.split("_") + return { + "ext": ext, + "dtype": parts[0], + "sample_rate": int(parts[1]), + "num_channels": int(parts[2]), + "num_frames": int(parts[3]), + } + + +class SoundFileMock: + def __init__(self, path, mode): + assert mode == "r" + self.path = path + self._params = _get_mock_params(path) + self._start = None + + @property + def samplerate(self): + return self._params["sample_rate"] + + @property + def format(self): + if self._params["ext"] == "wav": + return "WAV" + if self._params["ext"] == "flac": + return "FLAC" + if self._params["ext"] == "ogg": + return "OGG" + if self._params["ext"] in ["sph", "nis", "nist"]: + return "NIST" + + @property + def subtype(self): + if self._params["ext"] == "ogg": + return "VORBIS" + return dtype2subtype(self._params["dtype"]) + + def _prepare_read(self, start, stop, frames): + assert stop is None + self._start = start + return frames + + def read(self, frames, dtype, always_2d): + assert always_2d + data = get_wav_data( + dtype, + self._params["num_channels"], + normalize=False, + num_frames=self._params["num_frames"], + channels_first=False, ).numpy() + return data[self._start:self._start + frames] + + def __enter__(self): + return self + + def __exit__(self, *args, **kwargs): + pass + + +class MockedLoadTest(unittest.TestCase): + def assert_dtype(self, ext, dtype, sample_rate, num_channels, normalize, + channels_first): + """When format is WAV or NIST, normalize=False will return the native dtype Tensor, otherwise float32""" + num_frames = 3 * sample_rate + path = _get_mock_path(ext, dtype, sample_rate, num_channels, num_frames) + expected_dtype = paddle.float32 if normalize or ext not in [ + "wav", "nist" + ] else getattr(paddle, dtype) + with patch("soundfile.SoundFile", SoundFileMock): + found, sr = soundfile_backend.load( + path, normalize=normalize, channels_first=channels_first) + assert found.dtype == expected_dtype + assert sample_rate == sr + + @parameterize( + ["int32", "float32", "float64"], + [8000, 16000], + [1, 2], + [True, False], + [True, False], ) + def test_wav(self, dtype, sample_rate, num_channels, normalize, + channels_first): + """Returns native dtype when normalize=False else float32""" + self.assert_dtype("wav", dtype, sample_rate, num_channels, normalize, + channels_first) + + @parameterize( + ["int32"], + [8000, 16000], + [1, 2], + [True, False], + [True, False], ) + def test_sphere(self, dtype, sample_rate, num_channels, normalize, + channels_first): + """Returns float32 always""" + self.assert_dtype("sph", dtype, sample_rate, num_channels, normalize, + channels_first) + + @parameterize([8000, 16000], [1, 2], [True, False], [True, False]) + def test_ogg(self, sample_rate, num_channels, normalize, channels_first): + """Returns float32 always""" + self.assert_dtype("ogg", "int16", sample_rate, num_channels, normalize, + channels_first) + + @parameterize([8000, 16000], [1, 2], [True, False], [True, False]) + def test_flac(self, sample_rate, num_channels, normalize, channels_first): + """`soundfile_backend.load` can load ogg format.""" + self.assert_dtype("flac", "int16", sample_rate, num_channels, normalize, + channels_first) + + +class LoadTestBase(TempDirMixin, unittest.TestCase): + def assert_wav( + self, + dtype, + sample_rate, + num_channels, + normalize, + channels_first=True, + duration=1, ): + """`soundfile_backend.load` can load wav format correctly. + + Wav data loaded with soundfile backend should match those with scipy + """ + path = self.get_temp_path("reference.wav") + num_frames = duration * sample_rate + data = get_wav_data( + dtype, + num_channels, + normalize=normalize, + num_frames=num_frames, + channels_first=channels_first, ) + save_wav(path, data, sample_rate, channels_first=channels_first) + expected = load_wav( + path, normalize=normalize, channels_first=channels_first)[0] + data, sr = soundfile_backend.load( + path, normalize=normalize, channels_first=channels_first) + assert sr == sample_rate + np.testing.assert_array_almost_equal(data.numpy(), expected.numpy()) + + def assert_sphere( + self, + dtype, + sample_rate, + num_channels, + channels_first=True, + duration=1, ): + """`soundfile_backend.load` can load SPHERE format correctly.""" + path = self.get_temp_path("reference.sph") + num_frames = duration * sample_rate + raw = get_wav_data( + dtype, + num_channels, + num_frames=num_frames, + normalize=False, + channels_first=False, ) + soundfile.write( + path, raw, sample_rate, subtype=dtype2subtype(dtype), format="NIST") + expected = normalize_wav(raw.t() if channels_first else raw) + data, sr = soundfile_backend.load(path, channels_first=channels_first) + assert sr == sample_rate + #self.assertEqual(data, expected, atol=1e-4, rtol=1e-8) + np.testing.assert_array_almost_equal(data.numpy(), expected.numpy()) + + def assert_flac( + self, + dtype, + sample_rate, + num_channels, + channels_first=True, + duration=1, ): + """`soundfile_backend.load` can load FLAC format correctly.""" + path = self.get_temp_path("reference.flac") + num_frames = duration * sample_rate + raw = get_wav_data( + dtype, + num_channels, + num_frames=num_frames, + normalize=False, + channels_first=False, ) + soundfile.write(path, raw, sample_rate) + expected = normalize_wav(raw.t() if channels_first else raw) + data, sr = soundfile_backend.load(path, channels_first=channels_first) + assert sr == sample_rate + #self.assertEqual(data, expected, atol=1e-4, rtol=1e-8) + np.testing.assert_array_almost_equal(data.numpy(), expected.numpy()) + + +class TestLoad(LoadTestBase): + """Test the correctness of `soundfile_backend.load` for various formats""" + + @parameterize( + ["float32", "int32"], + [8000, 16000], + [1, 2], + [False, True], + [False, True], ) + def test_wav(self, dtype, sample_rate, num_channels, normalize, + channels_first): + """`soundfile_backend.load` can load wav format correctly.""" + self.assert_wav(dtype, sample_rate, num_channels, normalize, + channels_first) + + @parameterize( + ["int32"], + [16000], + [2], + [False], ) + def test_wav_large(self, dtype, sample_rate, num_channels, normalize): + """`soundfile_backend.load` can load large wav file correctly.""" + two_hours = 2 * 60 * 60 + self.assert_wav( + dtype, sample_rate, num_channels, normalize, duration=two_hours) + + @parameterize(["float32", "int32"], [4, 8, 16, 32], [False, True]) + def test_multiple_channels(self, dtype, num_channels, channels_first): + """`soundfile_backend.load` can load wav file with more than 2 channels.""" + sample_rate = 8000 + normalize = False + self.assert_wav(dtype, sample_rate, num_channels, normalize, + channels_first) + + #@parameterize(["int32"], [8000, 16000], [1, 2], [False, True]) + #@skipIfFormatNotSupported("NIST") + #def test_sphere(self, dtype, sample_rate, num_channels, channels_first): + #"""`soundfile_backend.load` can load sphere format correctly.""" + #self.assert_sphere(dtype, sample_rate, num_channels, channels_first) + + #@parameterize(["int32"], [8000, 16000], [1, 2], [False, True]) + #@skipIfFormatNotSupported("FLAC") + #def test_flac(self, dtype, sample_rate, num_channels, channels_first): + #"""`soundfile_backend.load` can load flac format correctly.""" + #self.assert_flac(dtype, sample_rate, num_channels, channels_first) + + +class TestLoadFormat(TempDirMixin, unittest.TestCase): + """Given `format` parameter, `so.load` can load files without extension""" + + original = None + path = None + + def _make_file(self, format_): + sample_rate = 8000 + path_with_ext = self.get_temp_path(f"test.{format_}") + data = get_wav_data("float32", num_channels=2).numpy().T + soundfile.write(path_with_ext, data, sample_rate) + expected = soundfile.read(path_with_ext, dtype="float32")[0].T + path = os.path.splitext(path_with_ext)[0] + os.rename(path_with_ext, path) + return path, expected + + def _test_format(self, format_): + """Providing format allows to read file without extension""" + path, expected = self._make_file(format_) + found, _ = soundfile_backend.load(path) + #self.assertEqual(found, expected) + np.testing.assert_array_almost_equal(found, expected) + + @parameterized.expand([ + ("WAV", ), + ("wav", ), + ]) + def test_wav(self, format_): + self._test_format(format_) + + @parameterized.expand([ + ("FLAC", ), + ("flac", ), + ]) + @skipIfFormatNotSupported("FLAC") + def test_flac(self, format_): + self._test_format(format_) + + +class TestFileObject(TempDirMixin, unittest.TestCase): + def _test_fileobj(self, ext): + """Loading audio via file-like object works""" + sample_rate = 16000 + path = self.get_temp_path(f"test.{ext}") + + data = get_wav_data("float32", num_channels=2).numpy().T + soundfile.write(path, data, sample_rate) + expected = soundfile.read(path, dtype="float32")[0].T + + with open(path, "rb") as fileobj: + found, sr = soundfile_backend.load(fileobj) + assert sr == sample_rate + #self.assertEqual(expected, found) + np.testing.assert_array_almost_equal(found, expected) + + def test_fileobj_wav(self): + """Loading audio via file-like object works""" + self._test_fileobj("wav") + + def test_fileobj_flac(self): + """Loading audio via file-like object works""" + self._test_fileobj("flac") + + def _test_tarfile(self, ext): + """Loading audio via file-like object works""" + sample_rate = 16000 + audio_file = f"test.{ext}" + audio_path = self.get_temp_path(audio_file) + archive_path = self.get_temp_path("archive.tar.gz") + + data = get_wav_data("float32", num_channels=2).numpy().T + soundfile.write(audio_path, data, sample_rate) + expected = soundfile.read(audio_path, dtype="float32")[0].T + + with tarfile.TarFile(archive_path, "w") as tarobj: + tarobj.add(audio_path, arcname=audio_file) + with tarfile.TarFile(archive_path, "r") as tarobj: + fileobj = tarobj.extractfile(audio_file) + found, sr = soundfile_backend.load(fileobj) + + assert sr == sample_rate + #self.assertEqual(expected, found) + np.testing.assert_array_almost_equal(found.numpy(), expected) + + def test_tarfile_wav(self): + """Loading audio via file-like object works""" + self._test_tarfile("wav") + + def test_tarfile_flac(self): + """Loading audio via file-like object works""" + self._test_tarfile("flac") + + +if __name__ == '__main__': + unittest.main() diff --git a/audio/tests/backends/soundfile/save_test.py b/audio/tests/backends/soundfile/save_test.py new file mode 100644 index 00000000..4f3df6e4 --- /dev/null +++ b/audio/tests/backends/soundfile/save_test.py @@ -0,0 +1,323 @@ +import io +import unittest +from unittest.mock import patch + +import numpy as np +import paddle +import soundfile +from common import fetch_wav_subtype +from common import parameterize +from common import skipIfFormatNotSupported +from common_utils import get_wav_data +from common_utils import load_wav +from common_utils import nested_params +from common_utils import TempDirMixin +from paddleaudio.backends import soundfile_backend + + +class MockedSaveTest(unittest.TestCase): + @nested_params( + ["float32", "int32"], + [8000, 16000], + [1, 2], + [False, True], + [ + (None, None), + ("PCM_U", None), + ("PCM_U", 8), + ("PCM_S", None), + ("PCM_S", 16), + ("PCM_S", 32), + ("PCM_F", None), + ("PCM_F", 32), + ("PCM_F", 64), + ("ULAW", None), + ("ULAW", 8), + ("ALAW", None), + ("ALAW", 8), + ], ) + @patch("soundfile.write") + def test_wav(self, dtype, sample_rate, num_channels, channels_first, + enc_params, mocked_write): + """soundfile_backend.save passes correct subtype to soundfile.write when WAV""" + filepath = "foo.wav" + input_tensor = get_wav_data( + dtype, + num_channels, + num_frames=3 * sample_rate, + normalize=dtype == "float32", + channels_first=channels_first, ) + input_tensor = paddle.transpose(input_tensor, [1, 0]) + + encoding, bits_per_sample = enc_params + soundfile_backend.save( + filepath, + input_tensor, + sample_rate, + channels_first=channels_first, + encoding=encoding, + bits_per_sample=bits_per_sample, ) + + # on +Py3.8 call_args.kwargs is more descreptive + args = mocked_write.call_args[1] + assert args["file"] == filepath + assert args["samplerate"] == sample_rate + assert args["subtype"] == fetch_wav_subtype(dtype, encoding, + bits_per_sample) + assert args["format"] is None + tensor_result = paddle.transpose( + input_tensor, [1, 0]) if channels_first else input_tensor + #self.assertEqual(args["data"], tensor_result.numpy()) + np.testing.assert_array_almost_equal(args["data"].numpy(), + tensor_result.numpy()) + + @patch("soundfile.write") + def assert_non_wav( + self, + fmt, + dtype, + sample_rate, + num_channels, + channels_first, + mocked_write, + encoding=None, + bits_per_sample=None, ): + """soundfile_backend.save passes correct subtype and format to soundfile.write when SPHERE""" + filepath = f"foo.{fmt}" + input_tensor = get_wav_data( + dtype, + num_channels, + num_frames=3 * sample_rate, + normalize=False, + channels_first=channels_first, ) + input_tensor = paddle.transpose(input_tensor, [1, 0]) + + expected_data = paddle.transpose( + input_tensor, [1, 0]) if channels_first else input_tensor + + soundfile_backend.save( + filepath, + input_tensor, + sample_rate, + channels_first, + encoding=encoding, + bits_per_sample=bits_per_sample, ) + + # on +Py3.8 call_args.kwargs is more descreptive + args = mocked_write.call_args[1] + assert args["file"] == filepath + assert args["samplerate"] == sample_rate + if fmt in ["sph", "nist", "nis"]: + assert args["format"] == "NIST" + else: + assert args["format"] is None + np.testing.assert_array_almost_equal(args["data"].numpy(), + expected_data.numpy()) + #self.assertEqual(args["data"], expected_data) + + @nested_params( + ["sph", "nist", "nis"], + ["int32"], + [8000, 16000], + [1, 2], + [False, True], + [ + ("PCM_S", 8), + ("PCM_S", 16), + ("PCM_S", 24), + ("PCM_S", 32), + ("ULAW", 8), + ("ALAW", 8), + ("ALAW", 16), + ("ALAW", 24), + ("ALAW", 32), + ], ) + def test_sph(self, fmt, dtype, sample_rate, num_channels, channels_first, + enc_params): + """soundfile_backend.save passes default format and subtype (None-s) to + soundfile.write when not WAV""" + encoding, bits_per_sample = enc_params + self.assert_non_wav( + fmt, + dtype, + sample_rate, + num_channels, + channels_first, + encoding=encoding, + bits_per_sample=bits_per_sample) + + @parameterize( + ["int32"], + [8000, 16000], + [1, 2], + [False, True], + [8, 16, 24], ) + def test_flac(self, dtype, sample_rate, num_channels, channels_first, + bits_per_sample): + """soundfile_backend.save passes default format and subtype (None-s) to + soundfile.write when not WAV""" + self.assert_non_wav( + "flac", + dtype, + sample_rate, + num_channels, + channels_first, + bits_per_sample=bits_per_sample) + + @parameterize( + ["int32"], + [8000, 16000], + [1, 2], + [False, True], ) + def test_ogg(self, dtype, sample_rate, num_channels, channels_first): + """soundfile_backend.save passes default format and subtype (None-s) to + soundfile.write when not WAV""" + self.assert_non_wav("ogg", dtype, sample_rate, num_channels, + channels_first) + + +class SaveTestBase(TempDirMixin, unittest.TestCase): + def assert_wav(self, dtype, sample_rate, num_channels, num_frames): + """`soundfile_backend.save` can save wav format.""" + path = self.get_temp_path("data.wav") + expected = get_wav_data( + dtype, num_channels, num_frames=num_frames, normalize=False) + soundfile_backend.save(path, expected, sample_rate) + found, sr = load_wav(path, normalize=False) + assert sample_rate == sr + #self.assertEqual(found, expected) + np.testing.assert_array_almost_equal(found.numpy(), expected.numpy()) + + def _assert_non_wav(self, fmt, dtype, sample_rate, num_channels): + """`soundfile_backend.save` can save non-wav format. + + Due to precision missmatch, and the lack of alternative way to decode the + resulting files without using soundfile, only meta data are validated. + """ + num_frames = sample_rate * 3 + path = self.get_temp_path(f"data.{fmt}") + expected = get_wav_data( + dtype, num_channels, num_frames=num_frames, normalize=False) + soundfile_backend.save(path, expected, sample_rate) + sinfo = soundfile.info(path) + assert sinfo.format == fmt.upper() + #assert sinfo.frames == num_frames this go wrong + assert sinfo.channels == num_channels + assert sinfo.samplerate == sample_rate + + def assert_flac(self, dtype, sample_rate, num_channels): + """`soundfile_backend.save` can save flac format.""" + self._assert_non_wav("flac", dtype, sample_rate, num_channels) + + def assert_sphere(self, dtype, sample_rate, num_channels): + """`soundfile_backend.save` can save sph format.""" + self._assert_non_wav("nist", dtype, sample_rate, num_channels) + + def assert_ogg(self, dtype, sample_rate, num_channels): + """`soundfile_backend.save` can save ogg format. + + As we cannot inspect the OGG format (it's lossy), we only check the metadata. + """ + self._assert_non_wav("ogg", dtype, sample_rate, num_channels) + + +class TestSave(SaveTestBase): + @parameterize( + ["float32", "int32"], + [8000, 16000], + [1, 2], ) + def test_wav(self, dtype, sample_rate, num_channels): + """`soundfile_backend.save` can save wav format.""" + self.assert_wav(dtype, sample_rate, num_channels, num_frames=None) + + @parameterize( + ["float32", "int32"], + [4, 8, 16, 32], ) + def test_multiple_channels(self, dtype, num_channels): + """`soundfile_backend.save` can save wav with more than 2 channels.""" + sample_rate = 8000 + self.assert_wav(dtype, sample_rate, num_channels, num_frames=None) + + @parameterize( + ["int32"], + [8000, 16000], + [1, 2], ) + @skipIfFormatNotSupported("NIST") + def test_sphere(self, dtype, sample_rate, num_channels): + """`soundfile_backend.save` can save sph format.""" + self.assert_sphere(dtype, sample_rate, num_channels) + + @parameterize( + [8000, 16000], + [1, 2], ) + @skipIfFormatNotSupported("FLAC") + def test_flac(self, sample_rate, num_channels): + """`soundfile_backend.save` can save flac format.""" + self.assert_flac("float32", sample_rate, num_channels) + + @parameterize( + [8000, 16000], + [1, 2], ) + @skipIfFormatNotSupported("OGG") + def test_ogg(self, sample_rate, num_channels): + """`soundfile_backend.save` can save ogg/vorbis format.""" + self.assert_ogg("float32", sample_rate, num_channels) + + +class TestSaveParams(TempDirMixin, unittest.TestCase): + """Test the correctness of optional parameters of `soundfile_backend.save`""" + + @parameterize([True, False]) + def test_channels_first(self, channels_first): + """channels_first swaps axes""" + path = self.get_temp_path("data.wav") + data = get_wav_data("int32", 2, channels_first=channels_first) + soundfile_backend.save(path, data, 8000, channels_first=channels_first) + found = load_wav(path)[0] + expected = data if channels_first else data.transpose([1, 0]) + #self.assertEqual(found, expected, atol=1e-4, rtol=1e-8) + np.testing.assert_array_almost_equal(found.numpy(), expected.numpy()) + + +class TestFileObject(TempDirMixin, unittest.TestCase): + def _test_fileobj(self, ext): + """Saving audio to file-like object works""" + sample_rate = 16000 + path = self.get_temp_path(f"test.{ext}") + + subtype = "FLOAT" if ext == "wav" else None + data = get_wav_data("float32", num_channels=2) + soundfile.write(path, data.numpy().T, sample_rate, subtype=subtype) + expected = soundfile.read(path, dtype="float32")[0] + + fileobj = io.BytesIO() + soundfile_backend.save(fileobj, data, sample_rate, format=ext) + fileobj.seek(0) + found, sr = soundfile.read(fileobj, dtype="float32") + + assert sr == sample_rate + #self.assertEqual(expected, found, atol=1e-4, rtol=1e-8) + np.testing.assert_array_almost_equal(found, expected) + + def test_fileobj_wav(self): + """Saving audio via file-like object works""" + self._test_fileobj("wav") + + @skipIfFormatNotSupported("FLAC") + def test_fileobj_flac(self): + """Saving audio via file-like object works""" + self._test_fileobj("flac") + + @skipIfFormatNotSupported("NIST") + def test_fileobj_nist(self): + """Saving audio via file-like object works""" + self._test_fileobj("NIST") + + @skipIfFormatNotSupported("OGG") + def test_fileobj_ogg(self): + """Saving audio via file-like object works""" + self._test_fileobj("OGG") + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/unit/audio/backends/soundfile/test_io.py b/audio/tests/backends/soundfile/test_io.py similarity index 88% rename from tests/unit/audio/backends/soundfile/test_io.py rename to audio/tests/backends/soundfile/test_io.py index 26276751..eed1b39f 100644 --- a/tests/unit/audio/backends/soundfile/test_io.py +++ b/audio/tests/backends/soundfile/test_io.py @@ -16,16 +16,17 @@ import os import unittest import numpy as np +from paddleaudio.backends import soundfile_load as load +from paddleaudio.backends import soundfile_save as save import soundfile as sf -import paddlespeech.audio -from ..base import BackendTest +from base import BackendTest class TestIO(BackendTest): def test_load_mono_channel(self): sf_data, sf_sr = sf.read(self.files[0]) - pa_data, pa_sr = paddlespeech.audio.load( + pa_data, pa_sr = load( self.files[0], normal=False, dtype='float64') self.assertEqual(sf_data.dtype, pa_data.dtype) @@ -35,7 +36,7 @@ class TestIO(BackendTest): def test_load_multi_channels(self): sf_data, sf_sr = sf.read(self.files[1]) sf_data = sf_data.T # Channel dim first - pa_data, pa_sr = paddlespeech.audio.load( + pa_data, pa_sr = load( self.files[1], mono=False, normal=False, dtype='float64') self.assertEqual(sf_data.dtype, pa_data.dtype) @@ -49,7 +50,7 @@ class TestIO(BackendTest): pa_tmp_file = 'pa_tmp.wav' sf.write(sf_tmp_file, waveform, sr) - paddlespeech.audio.save(waveform, sr, pa_tmp_file) + save(waveform, sr, pa_tmp_file) self.assertTrue(filecmp.cmp(sf_tmp_file, pa_tmp_file)) for file in [sf_tmp_file, pa_tmp_file]: @@ -62,7 +63,7 @@ class TestIO(BackendTest): pa_tmp_file = 'pa_tmp.wav' sf.write(sf_tmp_file, waveform.T, sr) - paddlespeech.audio.save(waveform.T, sr, pa_tmp_file) + save(waveform.T, sr, pa_tmp_file) self.assertTrue(filecmp.cmp(sf_tmp_file, pa_tmp_file)) for file in [sf_tmp_file, pa_tmp_file]: diff --git a/audio/tests/backends/sox_io/common.py b/audio/tests/backends/sox_io/common.py new file mode 100644 index 00000000..eecead97 --- /dev/null +++ b/audio/tests/backends/sox_io/common.py @@ -0,0 +1,89 @@ +import itertools +from unittest import skipIf + +from paddleaudio._internal.module_utils import is_module_available +from parameterized import parameterized + + +def name_func(func, _, params): + return f'{func.__name__}_{"_".join(str(arg) for arg in params.args)}' + + +def dtype2subtype(dtype): + return { + "float64": "DOUBLE", + "float32": "FLOAT", + "int32": "PCM_32", + "int16": "PCM_16", + "uint8": "PCM_U8", + "int8": "PCM_S8", + }[dtype] + + +def skipIfFormatNotSupported(fmt): + fmts = [] + if is_module_available("soundfile"): + import soundfile + + fmts = soundfile.available_formats() + return skipIf(fmt not in fmts, f'"{fmt}" is not supported by soundfile') + return skipIf(True, '"soundfile" not available.') + + +def parameterize(*params): + return parameterized.expand( + list(itertools.product(*params)), name_func=name_func) + + +def fetch_wav_subtype(dtype, encoding, bits_per_sample): + subtype = { + (None, None): dtype2subtype(dtype), + (None, 8): "PCM_U8", + ("PCM_U", None): "PCM_U8", + ("PCM_U", 8): "PCM_U8", + ("PCM_S", None): "PCM_32", + ("PCM_S", 16): "PCM_16", + ("PCM_S", 32): "PCM_32", + ("PCM_F", None): "FLOAT", + ("PCM_F", 32): "FLOAT", + ("PCM_F", 64): "DOUBLE", + ("ULAW", None): "ULAW", + ("ULAW", 8): "ULAW", + ("ALAW", None): "ALAW", + ("ALAW", 8): "ALAW", + }.get((encoding, bits_per_sample)) + if subtype: + return subtype + raise ValueError(f"wav does not support ({encoding}, {bits_per_sample}).") + +def get_encoding(ext, dtype): + exts = { + "mp3", + "flac", + "vorbis", + } + encodings = { + "float32": "PCM_F", + "int32": "PCM_S", + "int16": "PCM_S", + "uint8": "PCM_U", + } + return ext.upper() if ext in exts else encodings[dtype] + + +def get_bit_depth(dtype): + bit_depths = { + "float32": 32, + "int32": 32, + "int16": 16, + "uint8": 8, + } + return bit_depths[dtype] + +def get_bits_per_sample(ext, dtype): + bits_per_samples = { + "flac": 24, + "mp3": 0, + "vorbis": 0, + } + return bits_per_samples.get(ext, get_bit_depth(dtype)) diff --git a/audio/tests/backends/sox_io/common_utils b/audio/tests/backends/sox_io/common_utils new file mode 120000 index 00000000..3ff3cef8 --- /dev/null +++ b/audio/tests/backends/sox_io/common_utils @@ -0,0 +1 @@ +../../common_utils \ No newline at end of file diff --git a/audio/tests/backends/sox_io/info_test.py b/audio/tests/backends/sox_io/info_test.py new file mode 100644 index 00000000..c459a6c2 --- /dev/null +++ b/audio/tests/backends/sox_io/info_test.py @@ -0,0 +1,322 @@ +import io +import itertools +import os +import platform +import tarfile +import unittest +from contextlib import contextmanager +if platform.system() == "Windows": + import warnings + warnings.warn("sox io not support in Windows, please skip test.") + exit() + +from parameterized import parameterized +from common import get_bits_per_sample, get_encoding + +from paddleaudio.backends import sox_io_backend + +from common_utils import ( + get_wav_data, + save_wav, + TempDirMixin, + sox_utils, ) + +#code is from:https://github.com/pytorch/audio/blob/main/torchaudio/test/torchaudio_unittest/backend/sox_io/info_test.py + + +class TestInfo(TempDirMixin, unittest.TestCase): + @parameterized.expand( + list( + itertools.product( + [ + "float32", + "int32", + ], + [8000, 16000], + [1, 2], )), ) + def test_wav(self, dtype, sample_rate, num_channels): + """`sox_io_backend.info` can check wav file correctly""" + duration = 1 + path = self.get_temp_path("data.wav") + data = get_wav_data( + dtype, + num_channels, + normalize=False, + num_frames=duration * sample_rate) + save_wav(path, data, sample_rate) + info = sox_io_backend.info(path) + assert info.sample_rate == sample_rate + assert info.num_frames == sample_rate * duration + assert info.num_channels == num_channels + assert info.bits_per_sample == sox_utils.get_bit_depth(dtype) + assert info.encoding == get_encoding("wav", dtype) + + @parameterized.expand( + list( + itertools.product( + ["float32", "int32"], + [8000, 16000], + [4, 8, 16, 32], )), ) + def test_wav_multiple_channels(self, dtype, sample_rate, num_channels): + """`sox_io_backend.info` can check wav file with channels more than 2 correctly""" + duration = 1 + path = self.get_temp_path("data.wav") + data = get_wav_data( + dtype, + num_channels, + normalize=False, + num_frames=duration * sample_rate) + save_wav(path, data, sample_rate) + info = sox_io_backend.info(path) + assert info.sample_rate == sample_rate + assert info.num_frames == sample_rate * duration + assert info.num_channels == num_channels + assert info.bits_per_sample == sox_utils.get_bit_depth(dtype) + + def test_ulaw(self): + """`sox_io_backend.info` can check ulaw file correctly""" + duration = 1 + num_channels = 1 + sample_rate = 8000 + path = self.get_temp_path("data.wav") + sox_utils.gen_audio_file( + path, + sample_rate=sample_rate, + num_channels=num_channels, + bit_depth=8, + encoding="u-law", + duration=duration) + info = sox_io_backend.info(path) + assert info.sample_rate == sample_rate + assert info.num_frames == sample_rate * duration + assert info.num_channels == num_channels + assert info.bits_per_sample == 8 + assert info.encoding == "ULAW" + + def test_alaw(self): + """`sox_io_backend.info` can check alaw file correctly""" + duration = 1 + num_channels = 1 + sample_rate = 8000 + path = self.get_temp_path("data.wav") + sox_utils.gen_audio_file( + path, + sample_rate=sample_rate, + num_channels=num_channels, + bit_depth=8, + encoding="a-law", + duration=duration) + info = sox_io_backend.info(path) + assert info.sample_rate == sample_rate + assert info.num_frames == sample_rate * duration + assert info.num_channels == num_channels + assert info.bits_per_sample == 8 + assert info.encoding == "ALAW" + + +#class TestInfoOpus(unittest.TestCase): +#@parameterized.expand( +#list( +#itertools.product( +#["96k"], +#[1, 2], +#[0, 5, 10], +#) +#), +#) +#def test_opus(self, bitrate, num_channels, compression_level): +#"""`sox_io_backend.info` can check opus file correcty""" +#path = data_utils.get_asset_path("io", f"{bitrate}_{compression_level}_{num_channels}ch.opus") +#info = sox_io_backend.info(path) +#assert info.sample_rate == 48000 +#assert info.num_frames == 32768 +#assert info.num_channels == num_channels +#assert info.bits_per_sample == 0 # bit_per_sample is irrelevant for compressed formats +#assert info.encoding == "OPUS" + + +class FileObjTestBase(TempDirMixin): + def _gen_file(self, + ext, + dtype, + sample_rate, + num_channels, + num_frames, + *, + comments=None): + path = self.get_temp_path(f"test.{ext}") + bit_depth = sox_utils.get_bit_depth(dtype) + duration = num_frames / sample_rate + comment_file = self._gen_comment_file(comments) if comments else None + + sox_utils.gen_audio_file( + path, + sample_rate, + num_channels=num_channels, + encoding=sox_utils.get_encoding(dtype), + bit_depth=bit_depth, + duration=duration, + comment_file=comment_file, ) + return path + + def _gen_comment_file(self, comments): + comment_path = self.get_temp_path("comment.txt") + with open(comment_path, "w") as file_: + file_.writelines(comments) + return comment_path + + +class Unseekable: + def __init__(self, fileobj): + self.fileobj = fileobj + + def read(self, n): + return self.fileobj.read(n) + + +class TestFileObject(FileObjTestBase, unittest.TestCase): + def _query_fileobj(self, + ext, + dtype, + sample_rate, + num_channels, + num_frames, + *, + comments=None): + path = self._gen_file( + ext, + dtype, + sample_rate, + num_channels, + num_frames, + comments=comments) + format_ = ext if ext in ["mp3"] else None + with open(path, "rb") as fileobj: + return sox_io_backend.info(fileobj, format_) + + def _query_bytesio(self, ext, dtype, sample_rate, num_channels, num_frames): + path = self._gen_file(ext, dtype, sample_rate, num_channels, num_frames) + format_ = ext if ext in ["mp3"] else None + with open(path, "rb") as file_: + fileobj = io.BytesIO(file_.read()) + return sox_io_backend.info(fileobj, format_) + + def _query_tarfile(self, ext, dtype, sample_rate, num_channels, num_frames): + audio_path = self._gen_file(ext, dtype, sample_rate, num_channels, + num_frames) + audio_file = os.path.basename(audio_path) + archive_path = self.get_temp_path("archive.tar.gz") + with tarfile.TarFile(archive_path, "w") as tarobj: + tarobj.add(audio_path, arcname=audio_file) + format_ = ext if ext in ["mp3"] else None + with tarfile.TarFile(archive_path, "r") as tarobj: + fileobj = tarobj.extractfile(audio_file) + return sox_io_backend.info(fileobj, format_) + + @contextmanager + def _set_buffer_size(self, buffer_size): + try: + original_buffer_size = get_buffer_size() + set_buffer_size(buffer_size) + yield + finally: + set_buffer_size(original_buffer_size) + + @parameterized.expand([ + ("wav", "float32"), + ("wav", "int32"), + ("wav", "int16"), + ("wav", "uint8"), + ]) + def test_fileobj(self, ext, dtype): + """Querying audio via file object works""" + sample_rate = 16000 + num_frames = 3 * sample_rate + num_channels = 2 + sinfo = self._query_fileobj(ext, dtype, sample_rate, num_channels, + num_frames) + + bits_per_sample = get_bits_per_sample(ext, dtype) + num_frames = 0 if ext in ["mp3", "vorbis"] else num_frames + + assert sinfo.sample_rate == sample_rate + assert sinfo.num_channels == num_channels + assert sinfo.num_frames == num_frames + assert sinfo.bits_per_sample == bits_per_sample + assert sinfo.encoding == get_encoding(ext, dtype) + + @parameterized.expand([ + ("wav", "float32"), + ("wav", "int32"), + ("wav", "int16"), + ("wav", "uint8"), + ]) + def test_bytesio(self, ext, dtype): + """Querying audio via ByteIO object works for small data""" + sample_rate = 16000 + num_frames = 3 * sample_rate + num_channels = 2 + sinfo = self._query_bytesio(ext, dtype, sample_rate, num_channels, + num_frames) + + bits_per_sample = get_bits_per_sample(ext, dtype) + num_frames = 0 if ext in ["mp3", "vorbis"] else num_frames + + assert sinfo.sample_rate == sample_rate + assert sinfo.num_channels == num_channels + assert sinfo.num_frames == num_frames + assert sinfo.bits_per_sample == bits_per_sample + assert sinfo.encoding == get_encoding(ext, dtype) + + @parameterized.expand([ + ("wav", "float32"), + ("wav", "int32"), + ("wav", "int16"), + ("wav", "uint8"), + ]) + def test_bytesio_tiny(self, ext, dtype): + """Querying audio via ByteIO object works for small data""" + sample_rate = 8000 + num_frames = 4 + num_channels = 2 + sinfo = self._query_bytesio(ext, dtype, sample_rate, num_channels, + num_frames) + + bits_per_sample = get_bits_per_sample(ext, dtype) + num_frames = 0 if ext in ["mp3", "vorbis"] else num_frames + + assert sinfo.sample_rate == sample_rate + assert sinfo.num_channels == num_channels + assert sinfo.num_frames == num_frames + assert sinfo.bits_per_sample == bits_per_sample + assert sinfo.encoding == get_encoding(ext, dtype) + + @parameterized.expand([ + ("wav", "float32"), + ("wav", "int32"), + ("wav", "int16"), + ("wav", "uint8"), + ("flac", "float32"), + ("vorbis", "float32"), + ("amb", "int16"), + ]) + def test_tarfile(self, ext, dtype): + """Querying compressed audio via file-like object works""" + sample_rate = 16000 + num_frames = 3.0 * sample_rate + num_channels = 2 + sinfo = self._query_tarfile(ext, dtype, sample_rate, num_channels, + num_frames) + + bits_per_sample = get_bits_per_sample(ext, dtype) + num_frames = 0 if ext in ["vorbis"] else num_frames + + assert sinfo.sample_rate == sample_rate + assert sinfo.num_channels == num_channels + assert sinfo.num_frames == num_frames + assert sinfo.bits_per_sample == bits_per_sample + assert sinfo.encoding == get_encoding(ext, dtype) + + +if __name__ == '__main__': + unittest.main() diff --git a/audio/tests/backends/sox_io/load_test.py b/audio/tests/backends/sox_io/load_test.py new file mode 100644 index 00000000..ca9bd7ad --- /dev/null +++ b/audio/tests/backends/sox_io/load_test.py @@ -0,0 +1,56 @@ +import itertools +import platform +import unittest +if platform.system() == "Windows": + import warnings + warnings.warn("sox io not support in Windows, please skip test.") + exit() + +from parameterized import parameterized +import numpy as np +from paddleaudio.backends import sox_io_backend + +from common_utils import ( + get_wav_data, + load_wav, + save_wav, ) + +#code is from:https://github.com/pytorch/audio/blob/main/torchaudio/test/torchaudio_unittest/backend/sox_io/load_test.py + + +class TestLoad(unittest.TestCase): + def assert_wav(self, dtype, sample_rate, num_channels, normalize, duration): + """`sox_io_backend.load` can load wav format correctly. + + Wav data loaded with sox_io backend should match those with scipy + """ + path = 'testdata/reference.wav' + data = get_wav_data( + dtype, + num_channels, + normalize=normalize, + num_frames=duration * sample_rate) + save_wav(path, data, sample_rate) + expected = load_wav(path, normalize=normalize)[0] + data, sr = sox_io_backend.load(path, normalize=normalize) + assert sr == sample_rate + np.testing.assert_array_almost_equal(data, expected, decimal=4) + + @parameterized.expand( + list( + itertools.product( + [ + "float64", + "float32", + "int32", + ], + [8000, 16000], + [1, 2], + [False, True], )), ) + def test_wav(self, dtype, sample_rate, num_channels, normalize): + """`sox_io_backend.load` can load wav format correctly.""" + self.assert_wav(dtype, sample_rate, num_channels, normalize, duration=1) + + +if __name__ == '__main__': + unittest.main() diff --git a/audio/tests/backends/sox_io/save_test.py b/audio/tests/backends/sox_io/save_test.py new file mode 100644 index 00000000..e73f9f63 --- /dev/null +++ b/audio/tests/backends/sox_io/save_test.py @@ -0,0 +1,188 @@ +import io +import platform +import unittest +if platform.system() == "Windows": + import warnings + warnings.warn("sox io not support in Windows, please skip test.") + exit() + +import numpy as np +from paddleaudio.backends import sox_io_backend + +from common_utils import (get_wav_data, load_wav, save_wav, nested_params, + TempDirMixin, sox_utils) + +#code is from:https://github.com/pytorch/audio/blob/main/torchaudio/test/torchaudio_unittest/backend/sox_io/save_test.py + + +def _get_sox_encoding(encoding): + encodings = { + "PCM_F": "floating-point", + "PCM_S": "signed-integer", + "PCM_U": "unsigned-integer", + "ULAW": "u-law", + "ALAW": "a-law", + } + return encodings.get(encoding) + + +class TestSaveBase(TempDirMixin): + def assert_save_consistency( + self, + format: str, + *, + compression: float=None, + encoding: str=None, + bits_per_sample: int=None, + sample_rate: float=8000, + num_channels: int=2, + num_frames: float=3 * 8000, + src_dtype: str="int32", + test_mode: str="path", ): + """`save` function produces file that is comparable with `sox` command + + To compare that the file produced by `save` function agains the file produced by + the equivalent `sox` command, we need to load both files. + But there are many formats that cannot be opened with common Python modules (like + SciPy). + So we use `sox` command to prepare the original data and convert the saved files + into a format that SciPy can read (PCM wav). + The following diagram illustrates this process. The difference is 2.1. and 3.1. + + This assumes that + - loading data with SciPy preserves the data well. + - converting the resulting files into WAV format with `sox` preserve the data well. + + x + | 1. Generate source wav file with SciPy + | + v + -------------- wav ---------------- + | | + | 2.1. load with scipy | 3.1. Convert to the target + | then save it into the target | format depth with sox + | format with paddleaudio | + v v + target format target format + | | + | 2.2. Convert to wav with sox | 3.2. Convert to wav with sox + | | + v v + wav wav + | | + | 2.3. load with scipy | 3.3. load with scipy + | | + v v + tensor -------> compare <--------- tensor + + """ + cmp_encoding = "floating-point" + cmp_bit_depth = 32 + + src_path = self.get_temp_path("1.source.wav") + tgt_path = self.get_temp_path(f"2.1.paddleaudio.{format}") + tst_path = self.get_temp_path("2.2.result.wav") + sox_path = self.get_temp_path(f"3.1.sox.{format}") + ref_path = self.get_temp_path("3.2.ref.wav") + + # 1. Generate original wav + data = get_wav_data( + src_dtype, num_channels, normalize=False, num_frames=num_frames) + save_wav(src_path, data, sample_rate) + + # 2.1. Convert the original wav to target format with paddleaudio + data = load_wav(src_path, normalize=False)[0] + if test_mode == "path": + sox_io_backend.save( + tgt_path, + data, + sample_rate, + compression=compression, + encoding=encoding, + bits_per_sample=bits_per_sample) + elif test_mode == "fileobj": + with open(tgt_path, "bw") as file_: + sox_io_backend.save( + file_, + data, + sample_rate, + format=format, + compression=compression, + encoding=encoding, + bits_per_sample=bits_per_sample, ) + elif test_mode == "bytesio": + file_ = io.BytesIO() + sox_io_backend.save( + file_, + data, + sample_rate, + format=format, + compression=compression, + encoding=encoding, + bits_per_sample=bits_per_sample, ) + file_.seek(0) + with open(tgt_path, "bw") as f: + f.write(file_.read()) + else: + raise ValueError(f"Unexpected test mode: {test_mode}") + # 2.2. Convert the target format to wav with sox + sox_utils.convert_audio_file( + tgt_path, tst_path, encoding=cmp_encoding, bit_depth=cmp_bit_depth) + # 2.3. Load with SciPy + found = load_wav(tst_path, normalize=False)[0] + + # 3.1. Convert the original wav to target format with sox + sox_encoding = _get_sox_encoding(encoding) + sox_utils.convert_audio_file( + src_path, + sox_path, + compression=compression, + encoding=sox_encoding, + bit_depth=bits_per_sample) + # 3.2. Convert the target format to wav with sox + sox_utils.convert_audio_file( + sox_path, ref_path, encoding=cmp_encoding, bit_depth=cmp_bit_depth) + # 3.3. Load with SciPy + expected = load_wav(ref_path, normalize=False)[0] + + np.testing.assert_array_almost_equal(found, expected) + + +class TestSave(TestSaveBase, unittest.TestCase): + @nested_params( + [ + "path", + ], + [ + ("PCM_U", 8), + ("PCM_S", 16), + ("PCM_S", 32), + ("PCM_F", 32), + ("PCM_F", 64), + ("ULAW", 8), + ("ALAW", 8), + ], ) + def test_save_wav(self, test_mode, enc_params): + encoding, bits_per_sample = enc_params + self.assert_save_consistency( + "wav", + encoding=encoding, + bits_per_sample=bits_per_sample, + test_mode=test_mode) + + @nested_params( + [ + "path", + ], + [ + ("float32", ), + ("int32", ), + ], ) + def test_save_wav_dtype(self, test_mode, params): + (dtype, ) = params + self.assert_save_consistency( + "wav", src_dtype=dtype, test_mode=test_mode) + + +if __name__ == '__main__': + unittest.main() diff --git a/audio/tests/backends/sox_io/smoke_test.py b/audio/tests/backends/sox_io/smoke_test.py new file mode 100644 index 00000000..89590d61 --- /dev/null +++ b/audio/tests/backends/sox_io/smoke_test.py @@ -0,0 +1,189 @@ +import io +import itertools +import platform +import unittest +if platform.system() == "Windows": + import warnings + warnings.warn("sox io not support in Windows, please skip test.") + exit() + +from parameterized import parameterized +from paddleaudio.backends import sox_io_backend +from common_utils import (get_wav_data, TempDirMixin, name_func) + + +class SmokeTest(TempDirMixin, unittest.TestCase): + """Run smoke test on various audio format + + The purpose of this test suite is to verify that sox_io_backend functionalities do not exhibit + abnormal behaviors. + + This test suite should be able to run without any additional tools (such as sox command), + however without such tools, the correctness of each function cannot be verified. + """ + + def run_smoke_test(self, + ext, + sample_rate, + num_channels, + *, + compression=None, + dtype="float32"): + duration = 1 + num_frames = sample_rate * duration + #path = self.get_temp_path(f"test.{ext}") + path = self.get_temp_path(f"test.{ext}") + original = get_wav_data( + dtype, num_channels, normalize=False, num_frames=num_frames) + + # 1. run save + sox_io_backend.save( + path, original, sample_rate, compression=compression) + # 2. run info + info = sox_io_backend.info(path) + assert info.sample_rate == sample_rate + assert info.num_channels == num_channels + # 3. run load + loaded, sr = sox_io_backend.load(path, normalize=False) + assert sr == sample_rate + assert loaded.shape[0] == num_channels + + @parameterized.expand( + list( + itertools.product( + ["float32", "int32"], + #["float32", "int32", "int16", "uint8"], + [8000, 16000], + [1, 2], )), + name_func=name_func, ) + def test_wav(self, dtype, sample_rate, num_channels): + """Run smoke test on wav format""" + self.run_smoke_test("wav", sample_rate, num_channels, dtype=dtype) + + #@parameterized.expand( + #list( + #itertools.product( + #[8000, 16000], + #[1, 2], + #[-4.2, -0.2, 0, 0.2, 96, 128, 160, 192, 224, 256, 320], + #) + #) + #) + #def test_mp3(self, sample_rate, num_channels, bit_rate): + #"""Run smoke test on mp3 format""" + #self.run_smoke_test("mp3", sample_rate, num_channels, compression=bit_rate) + + #@parameterized.expand( + #list( + #itertools.product( + #[8000, 16000], + #[1, 2], + #[-1, 0, 1, 2, 3, 3.6, 5, 10], + #) + #) + #) + #def test_vorbis(self, sample_rate, num_channels, quality_level): + #"""Run smoke test on vorbis format""" + #self.run_smoke_test("vorbis", sample_rate, num_channels, compression=quality_level) + + @parameterized.expand( + list(itertools.product( + [8000, 16000], + [1, 2], + list(range(9)), )), + name_func=name_func, ) + def test_flac(self, sample_rate, num_channels, compression_level): + """Run smoke test on flac format""" + self.run_smoke_test( + "flac", sample_rate, num_channels, compression=compression_level) + + +class SmokeTestFileObj(unittest.TestCase): + """Run smoke test on various audio format + + The purpose of this test suite is to verify that sox_io_backend functionalities do not exhibit + abnormal behaviors. + + This test suite should be able to run without any additional tools (such as sox command), + however without such tools, the correctness of each function cannot be verified. + """ + + def run_smoke_test(self, + ext, + sample_rate, + num_channels, + *, + compression=None, + dtype="float32"): + duration = 1 + num_frames = sample_rate * duration + original = get_wav_data( + dtype, num_channels, normalize=False, num_frames=num_frames) + + fileobj = io.BytesIO() + # 1. run save + sox_io_backend.save( + fileobj, original, sample_rate, compression=compression, format=ext) + # 2. run info + fileobj.seek(0) + info = sox_io_backend.info(fileobj, format=ext) + assert info.sample_rate == sample_rate + assert info.num_channels == num_channels + # 3. run load + fileobj.seek(0) + loaded, sr = sox_io_backend.load(fileobj, normalize=False, format=ext) + assert sr == sample_rate + assert loaded.shape[0] == num_channels + + @parameterized.expand( + list(itertools.product( + ["float32", "int32"], + [8000, 16000], + [1, 2], )), + name_func=name_func, ) + def test_wav(self, dtype, sample_rate, num_channels): + """Run smoke test on wav format""" + self.run_smoke_test("wav", sample_rate, num_channels, dtype=dtype) + + # not support yet + #@parameterized.expand( + #list( + #itertools.product( + #[8000, 16000], + #[1, 2], + #[-4.2, -0.2, 0, 0.2, 96, 128, 160, 192, 224, 256, 320], + #) + #) + #) + #def test_mp3(self, sample_rate, num_channels, bit_rate): + #"""Run smoke test on mp3 format""" + #self.run_smoke_test("mp3", sample_rate, num_channels, compression=bit_rate) + + #@parameterized.expand( + #list( + #itertools.product( + #[8000, 16000], + #[1, 2], + #[-1, 0, 1, 2, 3, 3.6, 5, 10], + #) + #) + #) + #def test_vorbis(self, sample_rate, num_channels, quality_level): + #"""Run smoke test on vorbis format""" + #self.run_smoke_test("vorbis", sample_rate, num_channels, compression=quality_level) + + @parameterized.expand( + list(itertools.product( + [8000, 16000], + [1, 2], + list(range(9)), )), + name_func=name_func, ) + def test_flac(self, sample_rate, num_channels, compression_level): + #"""Run smoke test on flac format""" + self.run_smoke_test( + "flac", sample_rate, num_channels, compression=compression_level) + + +if __name__ == '__main__': + #test_func() + unittest.main() diff --git a/audio/tests/backends/sox_io/sox_effect_test.py b/audio/tests/backends/sox_io/sox_effect_test.py new file mode 100644 index 00000000..4707afa2 --- /dev/null +++ b/audio/tests/backends/sox_io/sox_effect_test.py @@ -0,0 +1,364 @@ +#code is from: https://github.com/pytorch/audio/blob/main/test/torchaudio_unittest/sox_effect/sox_effect_test.py +import io +import itertools +import platform +import tarfile +import unittest +from pathlib import Path + +import numpy as np +if platform.system() == "Windows": + import warnings + warnings.warn("sox io not support in Windows, please skip test.") + exit() + +from parameterized import parameterized +from paddleaudio import sox_effects +from common_utils import (get_sinusoid, get_wav_data, load_wav, save_wav, + sox_utils, TempDirMixin, load_effects_params) + + +class TestSoxEffects(unittest.TestCase): + def test_init(self): + """Calling init_sox_effects multiple times does not crush""" + for _ in range(3): + sox_effects.init_sox_effects() + + +class TestSoxEffectsTensor(TempDirMixin, unittest.TestCase): + """Test suite for `apply_effects_tensor` function""" + + @parameterized.expand( + list( + itertools.product(["float32", "int32"], [8000, 16000], [1, 2, 4, 8], + [True, False])), ) + def test_apply_no_effect(self, dtype, sample_rate, num_channels, + channels_first): + """`apply_effects_tensor` without effects should return identical data as input""" + original = get_wav_data( + dtype, num_channels, channels_first=channels_first) + expected = original.clone() + + found, output_sample_rate = sox_effects.apply_effects_tensor( + expected, sample_rate, [], channels_first) + + assert (output_sample_rate == sample_rate) + # SoxEffect should not alter the input Tensor object + #self.assertEqual(original, expected) + np.testing.assert_array_almost_equal(original.numpy(), expected.numpy()) + + # SoxEffect should not return the same Tensor object + assert expected is not found + # Returned Tensor should equal to the input Tensor + #self.assertEqual(expected, found) + np.testing.assert_array_almost_equal(expected.numpy(), found.numpy()) + + @parameterized.expand( + load_effects_params("sox_effect_test_args.jsonl"), + name_func=lambda f, i, p: f'{f.__name__}_{i}_{p.args[0]["effects"][0][0]}', + ) + def test_apply_effects(self, args): + """`apply_effects_tensor` should return identical data as sox command""" + effects = args["effects"] + num_channels = args.get("num_channels", 2) + input_sr = args.get("input_sample_rate", 8000) + output_sr = args.get("output_sample_rate") + + input_path = self.get_temp_path("input.wav") + reference_path = self.get_temp_path("reference.wav") + + original = get_sinusoid( + frequency=800, + sample_rate=input_sr, + n_channels=num_channels, + dtype="float32") + save_wav(input_path, original, input_sr) + sox_utils.run_sox_effect( + input_path, reference_path, effects, output_sample_rate=output_sr) + + expected, expected_sr = load_wav(reference_path) + found, sr = sox_effects.apply_effects_tensor(original, input_sr, + effects) + + assert sr == expected_sr + #self.assertEqual(expected, found) + np.testing.assert_array_almost_equal(expected.numpy(), found.numpy()) + + +class TestSoxEffectsFile(TempDirMixin, unittest.TestCase): + """Test suite for `apply_effects_file` function""" + + @parameterized.expand( + list( + itertools.product( + ["float32", "int32"], + [8000, 16000], + [1, 2, 4, 8], + [False, True], )), + #name_func=name_func, + ) + def test_apply_no_effect(self, dtype, sample_rate, num_channels, + channels_first): + """`apply_effects_file` without effects should return identical data as input""" + path = self.get_temp_path("input.wav") + expected = get_wav_data( + dtype, num_channels, channels_first=channels_first) + save_wav(path, expected, sample_rate, channels_first=channels_first) + + found, output_sample_rate = sox_effects.apply_effects_file( + path, [], normalize=False, channels_first=channels_first) + + assert output_sample_rate == sample_rate + #self.assertEqual(expected, found) + np.testing.assert_array_almost_equal(expected.numpy(), found.numpy()) + + @parameterized.expand( + load_effects_params("sox_effect_test_args.jsonl"), + #name_func=lambda f, i, p: f'{f.__name__}_{i}_{p.args[0]["effects"][0][0]}', + ) + def test_apply_effects_str(self, args): + """`apply_effects_file` should return identical data as sox command""" + dtype = "int32" + channels_first = True + effects = args["effects"] + num_channels = args.get("num_channels", 2) + input_sr = args.get("input_sample_rate", 8000) + output_sr = args.get("output_sample_rate") + + input_path = self.get_temp_path("input.wav") + reference_path = self.get_temp_path("reference.wav") + data = get_wav_data(dtype, num_channels, channels_first=channels_first) + save_wav(input_path, data, input_sr, channels_first=channels_first) + sox_utils.run_sox_effect( + input_path, reference_path, effects, output_sample_rate=output_sr) + + expected, expected_sr = load_wav(reference_path) + found, sr = sox_effects.apply_effects_file( + input_path, effects, normalize=False, channels_first=channels_first) + + assert sr == expected_sr + #self.assertEqual(found, expected) + np.testing.assert_array_almost_equal(expected.numpy(), found.numpy()) + + def test_apply_effects_path(self): + """`apply_effects_file` should return identical data as sox command when file path is given as a Path Object""" + dtype = "int32" + channels_first = True + effects = [["hilbert"]] + num_channels = 2 + input_sr = 8000 + output_sr = 8000 + + input_path = self.get_temp_path("input.wav") + reference_path = self.get_temp_path("reference.wav") + data = get_wav_data(dtype, num_channels, channels_first=channels_first) + save_wav(input_path, data, input_sr, channels_first=channels_first) + sox_utils.run_sox_effect( + input_path, reference_path, effects, output_sample_rate=output_sr) + + expected, expected_sr = load_wav(reference_path) + found, sr = sox_effects.apply_effects_file( + Path(input_path), + effects, + normalize=False, + channels_first=channels_first) + + assert sr == expected_sr + #self.assertEqual(found, expected) + np.testing.assert_array_almost_equal(expected.numpy(), found.numpy()) + + +class TestFileFormats(TempDirMixin, unittest.TestCase): + """`apply_effects_file` gives the same result as sox on various file formats""" + + @parameterized.expand( + list(itertools.product( + ["float32", "int32"], + [8000, 16000], + [1, 2], )), + #name_func=lambda f, _, p: f'{f.__name__}_{"_".join(str(arg) for arg in p.args)}', + ) + def test_wav(self, dtype, sample_rate, num_channels): + """`apply_effects_file` works on various wav format""" + channels_first = True + effects = [["band", "300", "10"]] + + input_path = self.get_temp_path("input.wav") + reference_path = self.get_temp_path("reference.wav") + data = get_wav_data(dtype, num_channels, channels_first=channels_first) + save_wav(input_path, data, sample_rate, channels_first=channels_first) + sox_utils.run_sox_effect(input_path, reference_path, effects) + + expected, expected_sr = load_wav(reference_path) + found, sr = sox_effects.apply_effects_file( + input_path, effects, normalize=False, channels_first=channels_first) + + assert sr == expected_sr + #self.assertEqual(found, expected) + np.testing.assert_array_almost_equal(found.numpy(), expected.numpy()) + + #not support now + #@parameterized.expand( + #list( + #itertools.product( + #[8000, 16000], + #[1, 2], + #) + #), + ##name_func=lambda f, _, p: f'{f.__name__}_{"_".join(str(arg) for arg in p.args)}', + #) + #def test_flac(self, sample_rate, num_channels): + #"""`apply_effects_file` works on various flac format""" + #channels_first = True + #effects = [["band", "300", "10"]] + + #input_path = self.get_temp_path("input.flac") + #reference_path = self.get_temp_path("reference.wav") + #sox_utils.gen_audio_file(input_path, sample_rate, num_channels) + #sox_utils.run_sox_effect(input_path, reference_path, effects, output_bitdepth=32) + + #expected, expected_sr = load_wav(reference_path) + #found, sr = sox_effects.apply_effects_file(input_path, effects, channels_first=channels_first) + #save_wav(self.get_temp_path("result.wav"), found, sr, channels_first=channels_first) + + #assert sr == expected_sr + ##self.assertEqual(found, expected) + #np.testing.assert_array_almost_equal(found.numpy(), expected.numpy()) + + #@parameterized.expand( + #list( + #itertools.product( + #[8000, 16000], + #[1, 2], + #) + #), + ##name_func=lambda f, _, p: f'{f.__name__}_{"_".join(str(arg) for arg in p.args)}', + #) + #def test_vorbis(self, sample_rate, num_channels): + #"""`apply_effects_file` works on various vorbis format""" + #channels_first = True + #effects = [["band", "300", "10"]] + + #input_path = self.get_temp_path("input.vorbis") + #reference_path = self.get_temp_path("reference.wav") + #sox_utils.gen_audio_file(input_path, sample_rate, num_channels) + #sox_utils.run_sox_effect(input_path, reference_path, effects, output_bitdepth=32) + + #expected, expected_sr = load_wav(reference_path) + #found, sr = sox_effects.apply_effects_file(input_path, effects, channels_first=channels_first) + #save_wav(self.get_temp_path("result.wav"), found, sr, channels_first=channels_first) + + #assert sr == expected_sr + ##self.assertEqual(found, expected) + #np.testing.assert_array_almost_equal(found.numpy(), expected.numpy()) + + + #@skipIfNoExec("sox") + #@skipIfNoSox +class TestFileObject(TempDirMixin, unittest.TestCase): + @parameterized.expand([ + ("wav", None), + ]) + def test_fileobj(self, ext, compression): + """Applying effects via file object works""" + sample_rate = 16000 + channels_first = True + effects = [["band", "300", "10"]] + input_path = self.get_temp_path(f"input.{ext}") + reference_path = self.get_temp_path("reference.wav") + + #sox_utils.gen_audio_file(input_path, sample_rate, num_channels=2, compression=compression) + data = get_wav_data("int32", 2, channels_first=channels_first) + save_wav(input_path, data, sample_rate, channels_first=channels_first) + + sox_utils.run_sox_effect( + input_path, reference_path, effects, output_bitdepth=32) + expected, expected_sr = load_wav(reference_path) + + with open(input_path, "rb") as fileobj: + found, sr = sox_effects.apply_effects_file( + fileobj, effects, channels_first=channels_first) + save_wav( + self.get_temp_path("result.wav"), + found, + sr, + channels_first=channels_first) + assert sr == expected_sr + #self.assertEqual(found, expected) + np.testing.assert_array_almost_equal(found.numpy(), expected.numpy()) + + @parameterized.expand([ + ("wav", None), + ]) + def test_bytesio(self, ext, compression): + """Applying effects via BytesIO object works""" + sample_rate = 16000 + channels_first = True + effects = [["band", "300", "10"]] + input_path = self.get_temp_path(f"input.{ext}") + reference_path = self.get_temp_path("reference.wav") + + #sox_utils.gen_audio_file(input_path, sample_rate, num_channels=2, compression=compression) + data = get_wav_data("int32", 2, channels_first=channels_first) + save_wav(input_path, data, sample_rate, channels_first=channels_first) + sox_utils.run_sox_effect( + input_path, reference_path, effects, output_bitdepth=32) + expected, expected_sr = load_wav(reference_path) + + with open(input_path, "rb") as file_: + fileobj = io.BytesIO(file_.read()) + found, sr = sox_effects.apply_effects_file( + fileobj, effects, channels_first=channels_first) + save_wav( + self.get_temp_path("result.wav"), + found, + sr, + channels_first=channels_first) + assert sr == expected_sr + #self.assertEqual(found, expected) + print("found") + print(found) + print("expected") + print(expected) + np.testing.assert_array_almost_equal(found.numpy(), expected.numpy()) + + @parameterized.expand([ + ("wav", None), + ]) + def test_tarfile(self, ext, compression): + """Applying effects to compressed audio via file-like file works""" + sample_rate = 16000 + channels_first = True + effects = [["band", "300", "10"]] + audio_file = f"input.{ext}" + + input_path = self.get_temp_path(audio_file) + reference_path = self.get_temp_path("reference.wav") + archive_path = self.get_temp_path("archive.tar.gz") + data = get_wav_data("int32", 2, channels_first=channels_first) + save_wav(input_path, data, sample_rate, channels_first=channels_first) + + # sox_utils.gen_audio_file(input_path, sample_rate, num_channels=2, compression=compression) + sox_utils.run_sox_effect( + input_path, reference_path, effects, output_bitdepth=32) + + expected, expected_sr = load_wav(reference_path) + + with tarfile.TarFile(archive_path, "w") as tarobj: + tarobj.add(input_path, arcname=audio_file) + with tarfile.TarFile(archive_path, "r") as tarobj: + fileobj = tarobj.extractfile(audio_file) + found, sr = sox_effects.apply_effects_file( + fileobj, effects, channels_first=channels_first) + save_wav( + self.get_temp_path("result.wav"), + found, + sr, + channels_first=channels_first) + assert sr == expected_sr + #self.assertEqual(found, expected) + np.testing.assert_array_almost_equal(found.numpy(), expected.numpy()) + + +if __name__ == '__main__': + unittest.main() diff --git a/audio/tests/backends/sox_io/sox_effect_test_args.jsonl b/audio/tests/backends/sox_io/sox_effect_test_args.jsonl new file mode 100644 index 00000000..c1b5d19b --- /dev/null +++ b/audio/tests/backends/sox_io/sox_effect_test_args.jsonl @@ -0,0 +1,77 @@ +{"effects": [["allpass", "300", "10"]]} +{"effects": [["band", "300", "10"]]} +{"effects": [["bandpass", "300", "10"]]} +{"effects": [["bandreject", "300", "10"]]} +{"effects": [["bass", "-10"]]} +{"effects": [["biquad", "0.4", "0.2", "0.9", "0.7", "0.2", "0.6"]]} +{"effects": [["chorus", "0.7", "0.9", "55", "0.4", "0.25", "2", "-t"]]} +{"effects": [["chorus", "0.6", "0.9", "50", "0.4", "0.25", "2", "-t", "60", "0.32", "0.4", "1.3", "-s"]]} +{"effects": [["chorus", "0.5", "0.9", "50", "0.4", "0.25", "2", "-t", "60", "0.32", "0.4", "2.3", "-t", "40", "0.3", "0.3", "1.3", "-s"]]} +{"effects": [["channels", "1"]]} +{"effects": [["channels", "2"]]} +{"effects": [["channels", "3"]]} +{"effects": [["compand", "0.3,1", "6:-70,-60,-20", "-5", "-90", "0.2"]]} +{"effects": [["compand", ".1,.2", "-inf,-50.1,-inf,-50,-50", "0", "-90", ".1"]]} +{"effects": [["compand", ".1,.1", "-45.1,-45,-inf,0,-inf", "45", "-90", ".1"]]} +{"effects": [["contrast", "0"]]} +{"effects": [["contrast", "25"]]} +{"effects": [["contrast", "50"]]} +{"effects": [["contrast", "75"]]} +{"effects": [["contrast", "100"]]} +{"effects": [["dcshift", "1.0"]]} +{"effects": [["dcshift", "-1.0"]]} +{"effects": [["deemph"]], "input_sample_rate": 44100} +{"effects": [["dither", "-s"]]} +{"effects": [["dither", "-S"]]} +{"effects": [["divide"]]} +{"effects": [["downsample", "2"]], "input_sample_rate": 8000, "output_sample_rate": 4000} +{"effects": [["earwax"]], "input_sample_rate": 44100} +{"effects": [["echo", "0.8", "0.88", "60", "0.4"]]} +{"effects": [["echo", "0.8", "0.88", "6", "0.4"]]} +{"effects": [["echo", "0.8", "0.9", "1000", "0.3"]]} +{"effects": [["echo", "0.8", "0.9", "1000", "0.3", "1800", "0.25"]]} +{"effects": [["echos", "0.8", "0.7", "700", "0.25", "700", "0.3"]]} +{"effects": [["echos", "0.8", "0.7", "700", "0.25", "900", "0.3"]]} +{"effects": [["echos", "0.8", "0.7", "40", "0.25", "63", "0.3"]]} +{"effects": [["equalizer", "300", "10", "5"]]} +{"effects": [["fade", "q", "3"]]} +{"effects": [["fade", "h", "3"]]} +{"effects": [["fade", "t", "3"]]} +{"effects": [["fade", "l", "3"]]} +{"effects": [["fade", "p", "3"]]} +{"effects": [["fir", "0.0195", "-0.082", "0.234", "0.891", "-0.145", "0.043"]]} +{"effects": [["flanger"]]} +{"effects": [["gain", "-l", "-6"]]} +{"effects": [["highpass", "-1", "300"]]} +{"effects": [["highpass", "-2", "300"]]} +{"effects": [["hilbert"]]} +{"effects": [["loudness"]]} +{"effects": [["lowpass", "-1", "300"]]} +{"effects": [["lowpass", "-2", "300"]]} +{"effects": [["mcompand", "0.005,0.1 -47,-40,-34,-34,-17,-33", "100", "0.003,0.05 -47,-40,-34,-34,-17,-33", "400", "0.000625,0.0125 -47,-40,-34,-34,-15,-33", "1600", "0.0001,0.025 -47,-40,-34,-34,-31,-31,-0,-30", "6400", "0,0.025 -38,-31,-28,-28,-0,-25"]], "input_sample_rate": 44100} +{"effects": [["oops"]]} +{"effects": [["overdrive"]]} +{"effects": [["pad"]]} +{"effects": [["phaser"]]} +{"effects": [["remix", "6", "7", "8", "0"]], "num_channels": 8} +{"effects": [["remix", "1-3,7", "3"]], "num_channels": 8} +{"effects": [["repeat"]]} +{"effects": [["reverb"]]} +{"effects": [["reverse"]]} +{"effects": [["riaa"]], "input_sample_rate": 44100} +{"effects": [["silence", "0"]]} +{"effects": [["speed", "1.3"]], "input_sample_rate": 4000, "output_sample_rate": 5200} +{"effects": [["speed", "0.7"]], "input_sample_rate": 4000, "output_sample_rate": 2800} +{"effects": [["stat"]]} +{"effects": [["stats"]]} +{"effects": [["stretch"]]} +{"effects": [["swap"]]} +{"effects": [["synth"]]} +{"effects": [["tempo", "0.9"]]} +{"effects": [["tempo", "1.1"]]} +{"effects": [["treble", "3"]]} +{"effects": [["tremolo", "300", "40"]]} +{"effects": [["tremolo", "300", "50"]]} +{"effects": [["trim", "0", "0.1"]]} +{"effects": [["upsample", "2"]], "input_sample_rate": 8000, "output_sample_rate": 16000} +{"effects": [["vol", "3"]]} diff --git a/tests/benchmark/audio/README.md b/audio/tests/benchmark/README.md similarity index 97% rename from tests/benchmark/audio/README.md rename to audio/tests/benchmark/README.md index 9cade74e..b9034100 100644 --- a/tests/benchmark/audio/README.md +++ b/audio/tests/benchmark/README.md @@ -15,6 +15,7 @@ Result: ========================================================================== test session starts ========================================================================== platform linux -- Python 3.7.7, pytest-7.0.1, pluggy-1.0.0 benchmark: 3.4.1 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000) +rootdir: /ssd3/chenxiaojie06/PaddleSpeech/DeepSpeech/paddleaudio plugins: typeguard-2.12.1, benchmark-3.4.1, anyio-3.5.0 collected 4 items diff --git a/tests/benchmark/audio/log_melspectrogram.py b/audio/tests/benchmark/log_melspectrogram.py similarity index 87% rename from tests/benchmark/audio/log_melspectrogram.py rename to audio/tests/benchmark/log_melspectrogram.py index c85fcecf..1d03c1df 100644 --- a/tests/benchmark/audio/log_melspectrogram.py +++ b/audio/tests/benchmark/log_melspectrogram.py @@ -17,17 +17,15 @@ import urllib.request import librosa import numpy as np import paddle +import paddleaudio import torch import torchaudio -import paddlespeech.audio - wav_url = 'https://paddlespeech.bj.bcebos.com/PaddleAudio/zh.wav' if not os.path.isfile(os.path.basename(wav_url)): urllib.request.urlretrieve(wav_url, os.path.basename(wav_url)) -waveform, sr = paddlespeech.audio.load( - os.path.abspath(os.path.basename(wav_url))) +waveform, sr = paddleaudio.backends.soundfile_load(os.path.abspath(os.path.basename(wav_url))) waveform_tensor = paddle.to_tensor(waveform).unsqueeze(0) waveform_tensor_torch = torch.from_numpy(waveform).unsqueeze(0) @@ -57,7 +55,7 @@ def enable_gpu_device(): paddle.set_device('gpu') -log_mel_extractor = paddlespeech.audio.features.LogMelSpectrogram( +log_mel_extractor = paddle.audio.features.LogMelSpectrogram( **mel_conf, f_min=0.0, top_db=80.0, dtype=waveform_tensor.dtype) @@ -67,20 +65,20 @@ def log_melspectrogram(): def test_log_melspect_cpu(benchmark): enable_cpu_device() - feature_audio = benchmark(log_melspectrogram) + feature_paddleaudio = benchmark(log_melspectrogram) feature_librosa = librosa.feature.melspectrogram(waveform, **mel_conf) feature_librosa = librosa.power_to_db(feature_librosa, top_db=80.0) np.testing.assert_array_almost_equal( - feature_librosa, feature_audio, decimal=3) + feature_librosa, feature_paddleaudio, decimal=3) def test_log_melspect_gpu(benchmark): enable_gpu_device() - feature_audio = benchmark(log_melspectrogram) + feature_paddleaudio = benchmark(log_melspectrogram) feature_librosa = librosa.feature.melspectrogram(waveform, **mel_conf) feature_librosa = librosa.power_to_db(feature_librosa, top_db=80.0) np.testing.assert_array_almost_equal( - feature_librosa, feature_audio, decimal=2) + feature_librosa, feature_paddleaudio, decimal=2) mel_extractor_torchaudio = torchaudio.transforms.MelSpectrogram( @@ -104,11 +102,11 @@ def test_log_melspect_cpu_torchaudio(benchmark): waveform_tensor_torch = waveform_tensor_torch.to('cpu') amplitude_to_DB = amplitude_to_DB.to('cpu') - feature_audio = benchmark(log_melspectrogram_torchaudio) + feature_paddleaudio = benchmark(log_melspectrogram_torchaudio) feature_librosa = librosa.feature.melspectrogram(waveform, **mel_conf) feature_librosa = librosa.power_to_db(feature_librosa, top_db=80.0) np.testing.assert_array_almost_equal( - feature_librosa, feature_audio, decimal=3) + feature_librosa, feature_paddleaudio, decimal=3) def test_log_melspect_gpu_torchaudio(benchmark): diff --git a/tests/benchmark/audio/melspectrogram.py b/audio/tests/benchmark/melspectrogram.py similarity index 85% rename from tests/benchmark/audio/melspectrogram.py rename to audio/tests/benchmark/melspectrogram.py index 49815894..28c4ac80 100644 --- a/tests/benchmark/audio/melspectrogram.py +++ b/audio/tests/benchmark/melspectrogram.py @@ -17,17 +17,15 @@ import urllib.request import librosa import numpy as np import paddle +import paddleaudio import torch import torchaudio -import paddlespeech.audio - wav_url = 'https://paddlespeech.bj.bcebos.com/PaddleAudio/zh.wav' if not os.path.isfile(os.path.basename(wav_url)): urllib.request.urlretrieve(wav_url, os.path.basename(wav_url)) -waveform, sr = paddlespeech.audio.load( - os.path.abspath(os.path.basename(wav_url))) +waveform, sr = paddleaudio.backends.soundfile_load(os.path.abspath(os.path.basename(wav_url))) waveform_tensor = paddle.to_tensor(waveform).unsqueeze(0) waveform_tensor_torch = torch.from_numpy(waveform).unsqueeze(0) @@ -57,7 +55,7 @@ def enable_gpu_device(): paddle.set_device('gpu') -mel_extractor = paddlespeech.audio.features.MelSpectrogram( +mel_extractor = paddle.audio.features.MelSpectrogram( **mel_conf, f_min=0.0, dtype=waveform_tensor.dtype) @@ -67,18 +65,18 @@ def melspectrogram(): def test_melspect_cpu(benchmark): enable_cpu_device() - feature_audio = benchmark(melspectrogram) + feature_paddleaudio = benchmark(melspectrogram) feature_librosa = librosa.feature.melspectrogram(waveform, **mel_conf) np.testing.assert_array_almost_equal( - feature_librosa, feature_audio, decimal=3) + feature_librosa, feature_paddleaudio, decimal=3) def test_melspect_gpu(benchmark): enable_gpu_device() - feature_audio = benchmark(melspectrogram) + feature_paddleaudio = benchmark(melspectrogram) feature_librosa = librosa.feature.melspectrogram(waveform, **mel_conf) np.testing.assert_array_almost_equal( - feature_librosa, feature_audio, decimal=3) + feature_librosa, feature_paddleaudio, decimal=3) mel_extractor_torchaudio = torchaudio.transforms.MelSpectrogram( @@ -93,10 +91,10 @@ def test_melspect_cpu_torchaudio(benchmark): global waveform_tensor_torch, mel_extractor_torchaudio mel_extractor_torchaudio = mel_extractor_torchaudio.to('cpu') waveform_tensor_torch = waveform_tensor_torch.to('cpu') - feature_audio = benchmark(melspectrogram_torchaudio) + feature_paddleaudio = benchmark(melspectrogram_torchaudio) feature_librosa = librosa.feature.melspectrogram(waveform, **mel_conf) np.testing.assert_array_almost_equal( - feature_librosa, feature_audio, decimal=3) + feature_librosa, feature_paddleaudio, decimal=3) def test_melspect_gpu_torchaudio(benchmark): diff --git a/tests/benchmark/audio/mfcc.py b/audio/tests/benchmark/mfcc.py similarity index 86% rename from tests/benchmark/audio/mfcc.py rename to audio/tests/benchmark/mfcc.py index 4e286de9..544a5371 100644 --- a/tests/benchmark/audio/mfcc.py +++ b/audio/tests/benchmark/mfcc.py @@ -17,17 +17,15 @@ import urllib.request import librosa import numpy as np import paddle +import paddleaudio import torch import torchaudio -import paddlespeech.audio - wav_url = 'https://paddlespeech.bj.bcebos.com/PaddleAudio/zh.wav' if not os.path.isfile(os.path.basename(wav_url)): urllib.request.urlretrieve(wav_url, os.path.basename(wav_url)) -waveform, sr = paddlespeech.audio.load( - os.path.abspath(os.path.basename(wav_url))) +waveform, sr = paddleaudio.backends.soundfile_load(os.path.abspath(os.path.basename(wav_url))) waveform_tensor = paddle.to_tensor(waveform).unsqueeze(0) waveform_tensor_torch = torch.from_numpy(waveform).unsqueeze(0) @@ -66,7 +64,7 @@ def enable_gpu_device(): paddle.set_device('gpu') -mfcc_extractor = paddlespeech.audio.features.MFCC( +mfcc_extractor = paddle.audio.features.MFCC( **mfcc_conf, f_min=0.0, dtype=waveform_tensor.dtype) @@ -76,18 +74,18 @@ def mfcc(): def test_mfcc_cpu(benchmark): enable_cpu_device() - feature_audio = benchmark(mfcc) + feature_paddleaudio = benchmark(mfcc) feature_librosa = librosa.feature.mfcc(waveform, **mel_conf) np.testing.assert_array_almost_equal( - feature_librosa, feature_audio, decimal=3) + feature_librosa, feature_paddleaudio, decimal=3) def test_mfcc_gpu(benchmark): enable_gpu_device() - feature_audio = benchmark(mfcc) + feature_paddleaudio = benchmark(mfcc) feature_librosa = librosa.feature.mfcc(waveform, **mel_conf) np.testing.assert_array_almost_equal( - feature_librosa, feature_audio, decimal=3) + feature_librosa, feature_paddleaudio, decimal=3) del mel_conf_torchaudio['sample_rate'] @@ -105,10 +103,10 @@ def test_mfcc_cpu_torchaudio(benchmark): mel_extractor_torchaudio = mfcc_extractor_torchaudio.to('cpu') waveform_tensor_torch = waveform_tensor_torch.to('cpu') - feature_audio = benchmark(mfcc_torchaudio) + feature_paddleaudio = benchmark(mfcc_torchaudio) feature_librosa = librosa.feature.mfcc(waveform, **mel_conf) np.testing.assert_array_almost_equal( - feature_librosa, feature_audio, decimal=3) + feature_librosa, feature_paddleaudio, decimal=3) def test_mfcc_gpu_torchaudio(benchmark): diff --git a/audio/tests/common_utils/__init__.py b/audio/tests/common_utils/__init__.py new file mode 100644 index 00000000..70e53315 --- /dev/null +++ b/audio/tests/common_utils/__init__.py @@ -0,0 +1,15 @@ +from .case_utils import name_func +from .case_utils import TempDirMixin +from .data_utils import get_sinusoid +from .data_utils import load_effects_params +from .data_utils import load_params +from .parameterized_utils import nested_params +from .wav_utils import get_wav_data +from .wav_utils import load_wav +from .wav_utils import normalize_wav +from .wav_utils import save_wav + +__all__ = [ + "get_wav_data", "load_wav", "save_wav", "normalize_wav", "load_params", + "nested_params", "get_sinusoid", "name_func", "load_effects_params" +] diff --git a/audio/tests/common_utils/case_utils.py b/audio/tests/common_utils/case_utils.py new file mode 100644 index 00000000..65a78c5d --- /dev/null +++ b/audio/tests/common_utils/case_utils.py @@ -0,0 +1,50 @@ +import os.path +import tempfile + +#code is from:https://github.com/pytorch/audio/blob/main/test/torchaudio_unittest/common_utils/case_utils.py + + +def name_func(func, _, params): + return f'{func.__name__}_{"_".join(str(arg) for arg in params.args)}' + + +class TempDirMixin: + """Mixin to provide easy access to temp dir""" + + temp_dir_ = None + + @classmethod + def get_base_temp_dir(cls): + # If PADDLEAUDIO_TEST_TEMP_DIR is set, use it instead of temporary directory. + # this is handy for debugging. + key = "PADDLEAUDIO_TEST_TEMP_DIR" + if key in os.environ: + return os.environ[key] + if cls.temp_dir_ is None: + cls.temp_dir_ = tempfile.TemporaryDirectory() + return cls.temp_dir_.name + + @classmethod + def tearDownClass(cls): + if cls.temp_dir_ is not None: + try: + cls.temp_dir_.cleanup() + cls.temp_dir_ = None + except PermissionError: + # On Windows there is a know issue with `shutil.rmtree`, + # which fails intermittenly. + # + # https://github.com/python/cpython/issues/74168 + # + # We observed this on CircleCI, where Windows job raises + # PermissionError. + # + # Following the above thread, we ignore it. + pass + super().tearDownClass() + + def get_temp_path(self, *paths): + temp_dir = os.path.join(self.get_base_temp_dir(), self.id()) + path = os.path.join(temp_dir, *paths) + os.makedirs(os.path.dirname(path), exist_ok=True) + return path diff --git a/audio/tests/common_utils/data_utils.py b/audio/tests/common_utils/data_utils.py new file mode 100644 index 00000000..b5618618 --- /dev/null +++ b/audio/tests/common_utils/data_utils.py @@ -0,0 +1,135 @@ +import json +import os.path + +import paddle +from parameterized import param +#code is from:https://github.com/pytorch/audio/blob/main/test/torchaudio_unittest/common_utils/data_utils.py with modification. + +_TEST_DIR_PATH = os.path.realpath(os.path.join(os.path.dirname(__file__), "..")) + + +def get_asset_path(*paths): + """Return full path of a test asset""" + return os.path.join(_TEST_DIR_PATH, "assets", *paths) + + +def load_params(*paths): + with open(get_asset_path(*paths), "r") as file: + return [param(json.loads(line)) for line in file] + + +def load_effects_params(*paths): + params = [] + with open(*paths, "r") as file: + for line in file: + data = json.loads(line) + for effect in data["effects"]: + for i, arg in enumerate(effect): + if arg.startswith(""): + effect[i] = arg.replace("", get_asset_path()) + params.append(param(data)) + return params + + +def convert_tensor_encoding( + tensor: paddle.tensor, + dtype: paddle.dtype, ): + """Convert input tensor with values between -1 and 1 to integer encoding + Args: + tensor: input tensor, assumed between -1 and 1 + dtype: desired output tensor dtype + Returns: + Tensor: shape of (n_channels, sample_rate * duration) + """ + if dtype == paddle.int32: + tensor *= (tensor > 0) * 2147483647 + (tensor < 0) * 2147483648 + if dtype == paddle.int16: + tensor *= (tensor > 0) * 32767 + (tensor < 0) * 32768 + if dtype == paddle.uint8: + tensor *= (tensor > 0) * 127 + (tensor < 0) * 128 + tensor += 128 + tensor = paddle.to_tensor(tensor, dtype) + return tensor + + +#def get_whitenoise( +#*, +#sample_rate: int = 16000, +#duration: float = 1, # seconds +#n_channels: int = 1, +#seed: int = 0, +#dtype: Union[str, paddle.dtype] = "float32", +#device: Union[str, paddle.device] = "cpu", +#channels_first=True, +#scale_factor: float = 1, +#): +#"""Generate pseudo audio data with whitenoise +#Args: +#sample_rate: Sampling rate +#duration: Length of the resulting Tensor in seconds. +#n_channels: Number of channels +#seed: Seed value used for random number generation. +#Note that this function does not modify global random generator state. +#dtype: Torch dtype +#device: device +#channels_first: whether first dimension is n_channels +#scale_factor: scale the Tensor before clamping and quantization +#Returns: +#Tensor: shape of (n_channels, sample_rate * duration) +#""" +#if isinstance(dtype, str): +#dtype = getattr(paddle, dtype) +#if dtype not in [paddle.float64, paddle.float32, paddle.int32, paddle.int16, paddle.uint8]: +#raise NotImplementedError(f"dtype {dtype} is not supported.") +## According to the doc, folking rng on all CUDA devices is slow when there are many CUDA devices, +## so we only fork on CPU, generate values and move the data to the given device +#with paddle.random.fork_rng([]): +#paddle.random.manual_seed(seed) +#tensor = paddle.randn([n_channels, int(sample_rate * duration)], dtype=paddle.float32, device="cpu") +#tensor /= 2.0 +#tensor *= scale_factor +#tensor.clamp_(-1.0, 1.0) +#if not channels_first: +#tensor = tensor.t() + +#tensor = tensor.to(device) + +#return convert_tensor_encoding(tensor, dtype) + + +def get_sinusoid( + *, + frequency: float=300, + sample_rate: int=16000, + duration: float=1, # seconds + n_channels: int=1, + dtype: str="float32", + device: str="cpu", + channels_first: bool=True, ): + """Generate pseudo audio data with sine wave. + + Args: + frequency: Frequency of sine wave + sample_rate: Sampling rate + duration: Length of the resulting Tensor in seconds. + n_channels: Number of channels + dtype: Torch dtype + device: device + + Returns: + Tensor: shape of (n_channels, sample_rate * duration) + """ + if isinstance(dtype, str): + dtype = getattr(paddle, dtype) + pie2 = 2 * 3.141592653589793 + end = pie2 * frequency * duration + num_frames = int(sample_rate * duration) + # Randomize the initial phase. (except the first channel) + theta0 = pie2 * paddle.randn([n_channels, 1], dtype=paddle.float32) + theta0[0, :] = 0 + theta = paddle.linspace(0, end, num_frames, dtype=paddle.float32) + theta = theta0 + theta + tensor = paddle.sin(theta) + if not channels_first: + tensor = paddle.t(tensor) + return convert_tensor_encoding(tensor, dtype) diff --git a/audio/tests/common_utils/parameterized_utils.py b/audio/tests/common_utils/parameterized_utils.py new file mode 100644 index 00000000..ca4d3f51 --- /dev/null +++ b/audio/tests/common_utils/parameterized_utils.py @@ -0,0 +1,44 @@ +from itertools import product + +from parameterized import param +from parameterized import parameterized + + +def _name_func(func, _, params): + strs = [] + for arg in params.args: + if isinstance(arg, tuple): + strs.append("_".join(str(a) for a in arg)) + else: + strs.append(str(arg)) + # sanitize the test name + name = "_".join(strs) + return parameterized.to_safe_name(f"{func.__name__}_{name}") + + +def nested_params(*params_set, name_func=_name_func): + """Generate the cartesian product of the given list of parameters. + + Args: + params_set (list of parameters): Parameters. When using ``parameterized.param`` class, + all the parameters have to be specified with the class, only using kwargs. + """ + flatten = [p for params in params_set for p in params] + + # Parameters to be nested are given as list of plain objects + if all(not isinstance(p, param) for p in flatten): + args = list(product(*params_set)) + return parameterized.expand(args, name_func=_name_func) + + # Parameters to be nested are given as list of `parameterized.param` + if not all(isinstance(p, param) for p in flatten): + raise TypeError("When using ``parameterized.param``, " + "all the parameters have to be of the ``param`` type.") + if any(p.args for p in flatten): + raise ValueError( + "When using ``parameterized.param``, " + "all the parameters have to be provided as keyword argument.") + args = [param()] + for params in params_set: + args = [param(**x.kwargs, **y.kwargs) for x in args for y in params] + return parameterized.expand(args) diff --git a/audio/tests/common_utils/sox_utils.py b/audio/tests/common_utils/sox_utils.py new file mode 100644 index 00000000..6ceae081 --- /dev/null +++ b/audio/tests/common_utils/sox_utils.py @@ -0,0 +1,116 @@ +import subprocess +import sys +import warnings + + +def get_encoding(dtype): + encodings = { + "float32": "floating-point", + "int32": "signed-integer", + "int16": "signed-integer", + "uint8": "unsigned-integer", + } + return encodings[dtype] + + +def get_bit_depth(dtype): + bit_depths = { + "float32": 32, + "int32": 32, + "int16": 16, + "uint8": 8, + } + return bit_depths[dtype] + + +def gen_audio_file( + path, + sample_rate, + num_channels, + *, + encoding=None, + bit_depth=None, + compression=None, + attenuation=None, + duration=1, + comment_file=None, +): + """Generate synthetic audio file with `sox` command.""" + if path.endswith(".wav"): + warnings.warn("Use get_wav_data and save_wav to generate wav file for accurate result.") + command = [ + "sox", + "-V3", # verbose + "--no-dither", # disable automatic dithering + "-R", + # -R is supposed to be repeatable, though the implementation looks suspicious + # and not setting the seed to a fixed value. + # https://fossies.org/dox/sox-14.4.2/sox_8c_source.html + # search "sox_globals.repeatable" + ] + if bit_depth is not None: + command += ["--bits", str(bit_depth)] + command += [ + "--rate", + str(sample_rate), + "--null", # no input + "--channels", + str(num_channels), + ] + if compression is not None: + command += ["--compression", str(compression)] + if bit_depth is not None: + command += ["--bits", str(bit_depth)] + if encoding is not None: + command += ["--encoding", str(encoding)] + if comment_file is not None: + command += ["--comment-file", str(comment_file)] + command += [ + str(path), + "synth", + str(duration), # synthesizes for the given duration [sec] + "sawtooth", + "1", + # saw tooth covers the both ends of value range, which is a good property for test. + # similar to linspace(-1., 1.) + # this introduces bigger boundary effect than sine when converted to mp3 + ] + if attenuation is not None: + command += ["vol", f"-{attenuation}dB"] + print(" ".join(command), file=sys.stderr) + subprocess.run(command, check=True) + + +def convert_audio_file(src_path, dst_path, *, encoding=None, bit_depth=None, compression=None): + """Convert audio file with `sox` command.""" + command = ["sox", "-V3", "--no-dither", "-R", str(src_path)] + if encoding is not None: + command += ["--encoding", str(encoding)] + if bit_depth is not None: + command += ["--bits", str(bit_depth)] + if compression is not None: + command += ["--compression", str(compression)] + command += [dst_path] + print(" ".join(command), file=sys.stderr) + subprocess.run(command, check=True) + + +def _flattern(effects): + if not effects: + return effects + if isinstance(effects[0], str): + return effects + return [item for sublist in effects for item in sublist] + + +def run_sox_effect(input_file, output_file, effect, *, output_sample_rate=None, output_bitdepth=None): + """Run sox effects""" + effect = _flattern(effect) + command = ["sox", "-V", "--no-dither", input_file] + if output_bitdepth: + command += ["--bits", str(output_bitdepth)] + command += [output_file] + effect + if output_sample_rate: + command += ["rate", str(output_sample_rate)] + print(" ".join(command)) + subprocess.run(command, check=True) diff --git a/audio/tests/common_utils/wav_utils.py b/audio/tests/common_utils/wav_utils.py new file mode 100644 index 00000000..5cae6d8e --- /dev/null +++ b/audio/tests/common_utils/wav_utils.py @@ -0,0 +1,102 @@ +from typing import Optional + +import paddle +import scipy.io.wavfile + + +def normalize_wav(tensor: paddle.Tensor) -> paddle.Tensor: + if tensor.dtype == paddle.float32: + pass + elif tensor.dtype == paddle.int32: + tensor = paddle.cast(tensor, paddle.float32) + tensor[tensor > 0] /= 2147483647.0 + tensor[tensor < 0] /= 2147483648.0 + elif tensor.dtype == paddle.int16: + tensor = paddle.cast(tensor, paddle.float32) + tensor[tensor > 0] /= 32767.0 + tensor[tensor < 0] /= 32768.0 + elif tensor.dtype == paddle.uint8: + tensor = paddle.cast(tensor, paddle.float32) - 128 + tensor[tensor > 0] /= 127.0 + tensor[tensor < 0] /= 128.0 + return tensor + + +def get_wav_data( + dtype: str, + num_channels: int, + *, + num_frames: Optional[int]=None, + normalize: bool=True, + channels_first: bool=True, ): + """Generate linear signal of the given dtype and num_channels + + Data range is + [-1.0, 1.0] for float32, + [-2147483648, 2147483647] for int32 + [-32768, 32767] for int16 + [0, 255] for uint8 + + num_frames allow to change the linear interpolation parameter. + Default values are 256 for uint8, else 1 << 16. + 1 << 16 as default is so that int16 value range is completely covered. + """ + dtype_ = getattr(paddle, dtype) + + if num_frames is None: + if dtype == "uint8": + num_frames = 256 + else: + num_frames = 1 << 16 + + # paddle linspace not support uint8, int8, int16 + #if dtype == "uint8": + # base = paddle.linspace(0, 255, num_frames, dtype=dtype_) + #dtype_np = getattr(np, dtype) + #base_np = np.linspace(0, 255, num_frames, dtype_np) + #base = paddle.to_tensor(base_np, dtype=dtype_) + #elif dtype == "int8": + # base = paddle.linspace(-128, 127, num_frames, dtype=dtype_) + #dtype_np = getattr(np, dtype) + #base_np = np.linspace(-128, 127, num_frames, dtype_np) + #base = paddle.to_tensor(base_np, dtype=dtype_) + if dtype == "float32": + base = paddle.linspace(-1.0, 1.0, num_frames, dtype=dtype_) + elif dtype == "float64": + base = paddle.linspace(-1.0, 1.0, num_frames, dtype=dtype_) + elif dtype == "int32": + base = paddle.linspace( + -2147483648, 2147483647, num_frames, dtype=dtype_) + #elif dtype == "int16": + # base = paddle.linspace(-32768, 32767, num_frames, dtype=dtype_) + #dtype_np = getattr(np, dtype) + #base_np = np.linspace(-32768, 32767, num_frames, dtype_np) + #base = paddle.to_tensor(base_np, dtype=dtype_) + else: + raise NotImplementedError(f"Unsupported dtype {dtype}") + data = base.tile([num_channels, 1]) + if not channels_first: + data = data.transpose([1, 0]) + if normalize: + data = normalize_wav(data) + return data + + +def load_wav(path: str, normalize=True, channels_first=True) -> paddle.Tensor: + """Load wav file without paddleaudio""" + sample_rate, data = scipy.io.wavfile.read(path) + data = paddle.to_tensor(data.copy()) + if data.ndim == 1: + data = data.unsqueeze(1) + if normalize: + data = normalize_wav(data) + if channels_first: + data = data.transpose([1, 0]) + return data, sample_rate + + +def save_wav(path, data, sample_rate, channels_first=True): + """Save wav file without paddleaudio""" + if channels_first: + data = data.transpose([1, 0]) + scipy.io.wavfile.write(path, sample_rate, data.numpy()) diff --git a/paddlespeech/audio/sox_effects/__init__.py b/audio/tests/features/__init__.py similarity index 100% rename from paddlespeech/audio/sox_effects/__init__.py rename to audio/tests/features/__init__.py diff --git a/tests/unit/audio/features/base.py b/audio/tests/features/base.py similarity index 96% rename from tests/unit/audio/features/base.py rename to audio/tests/features/base.py index 6d59f72b..d183b72a 100644 --- a/tests/unit/audio/features/base.py +++ b/audio/tests/features/base.py @@ -17,8 +17,7 @@ import urllib.request import numpy as np import paddle - -from paddlespeech.audio import load +from paddleaudio.backends import soundfile_load as load wav_url = 'https://paddlespeech.bj.bcebos.com/PaddleAudio/zh.wav' diff --git a/tests/unit/audio/features/test_istft.py b/audio/tests/features/test_istft.py similarity index 96% rename from tests/unit/audio/features/test_istft.py rename to audio/tests/features/test_istft.py index f1e6e4e3..9cf8cdd6 100644 --- a/tests/unit/audio/features/test_istft.py +++ b/audio/tests/features/test_istft.py @@ -15,9 +15,9 @@ import unittest import numpy as np import paddle +from paddleaudio.functional.window import get_window from .base import FeatTest -from paddlespeech.audio.functional.window import get_window from paddlespeech.s2t.transform.spectrogram import IStft from paddlespeech.s2t.transform.spectrogram import Stft diff --git a/tests/unit/audio/features/test_kaldi.py b/audio/tests/features/test_kaldi.py similarity index 86% rename from tests/unit/audio/features/test_kaldi.py rename to audio/tests/features/test_kaldi.py index 2b0ece89..2bd5dc73 100644 --- a/tests/unit/audio/features/test_kaldi.py +++ b/audio/tests/features/test_kaldi.py @@ -15,11 +15,11 @@ import unittest import numpy as np import paddle +import paddleaudio import torch import torchaudio -import paddlespeech.audio -from .base import FeatTest +from base import FeatTest class TestKaldi(FeatTest): @@ -40,17 +40,17 @@ class TestKaldi(FeatTest): self.window_size, periodic=False, dtype=eval(f'torch.{self.dtype}')).pow(0.85) - p_hann_window = paddlespeech.audio.functional.window.get_window( + p_hann_window = paddleaudio.functional.window.get_window( 'hann', self.window_size, fftbins=False, dtype=eval(f'paddle.{self.dtype}')) - p_hamm_window = paddlespeech.audio.functional.window.get_window( + p_hamm_window = paddleaudio.functional.window.get_window( 'hamming', self.window_size, fftbins=False, dtype=eval(f'paddle.{self.dtype}')) - p_povey_window = paddlespeech.audio.functional.window.get_window( + p_povey_window = paddleaudio.functional.window.get_window( 'hann', self.window_size, fftbins=False, @@ -63,7 +63,7 @@ class TestKaldi(FeatTest): def test_fbank(self): ta_features = torchaudio.compliance.kaldi.fbank( torch.from_numpy(self.waveform.astype(self.dtype))) - pa_features = paddlespeech.audio.compliance.kaldi.fbank( + pa_features = paddleaudio.compliance.kaldi.fbank( paddle.to_tensor(self.waveform.astype(self.dtype))) np.testing.assert_array_almost_equal( ta_features, pa_features, decimal=4) @@ -71,7 +71,7 @@ class TestKaldi(FeatTest): def test_mfcc(self): ta_features = torchaudio.compliance.kaldi.mfcc( torch.from_numpy(self.waveform.astype(self.dtype))) - pa_features = paddlespeech.audio.compliance.kaldi.mfcc( + pa_features = paddleaudio.compliance.kaldi.mfcc( paddle.to_tensor(self.waveform.astype(self.dtype))) np.testing.assert_array_almost_equal( ta_features, pa_features, decimal=4) diff --git a/audio/tests/features/test_kaldi_feat.py b/audio/tests/features/test_kaldi_feat.py new file mode 100644 index 00000000..172a88b4 --- /dev/null +++ b/audio/tests/features/test_kaldi_feat.py @@ -0,0 +1,55 @@ +# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#import platform +import unittest + +import kaldiio +import numpy as np +from kaldiio import ReadHelper +from paddleaudio.kaldi import fbank as fbank +#from paddleaudio.kaldi import pitch as pitch + +# the groundtruth feats computed in kaldi command below. +#compute-fbank-feats --dither=0 scp:$wav_scp ark,t:fbank_feat.ark +#compute-kaldi-pitch-feats --sample-frequency=16000 scp:$wav_scp ark,t:pitch_feat.ark + + +class TestKaldiFbank(unittest.TestCase): + def test_fbank(self): + fbank_groundtruth = {} + with ReadHelper('ark:testdata/fbank_feat.ark') as reader: + for key, feat in reader: + fbank_groundtruth[key] = feat + + wav_rate, wav = kaldiio.wavio.read_wav('testdata/test.wav') + fbank_feat = fbank(wav) + fbank_check = fbank_groundtruth['test_wav'] + np.testing.assert_array_almost_equal(fbank_feat, fbank_check, decimal=4) + + #def test_pitch(self): + # pitch_groundtruth = {} + # if platform.system() != "Linux": + # pass + # with ReadHelper('ark:testdata/pitch_feat.ark') as reader: + # for key, feat in reader: + # pitch_groundtruth[key] = feat + + # wav_rate, wav = kaldiio.wavio.read_wav('testdata/test.wav') + # pitch_feat = pitch(wav) + # pitch_check = pitch_groundtruth['test_wav'] + # np.testing.assert_array_almost_equal(pitch_feat, pitch_check, decimal=4) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/unit/audio/features/test_librosa.py b/audio/tests/features/test_librosa.py similarity index 89% rename from tests/unit/audio/features/test_librosa.py rename to audio/tests/features/test_librosa.py index ffdec3e7..8cda25b1 100644 --- a/tests/unit/audio/features/test_librosa.py +++ b/audio/tests/features/test_librosa.py @@ -16,10 +16,10 @@ import unittest import librosa import numpy as np import paddle +import paddleaudio +from paddleaudio.functional.window import get_window -import paddlespeech.audio -from .base import FeatTest -from paddlespeech.audio.functional.window import get_window +from base import FeatTest class TestLibrosa(FeatTest): @@ -117,7 +117,7 @@ class TestLibrosa(FeatTest): htk=False, norm='slaney', dtype=self.waveform.dtype, ) - feature_compliance = paddlespeech.audio.compliance.librosa.compute_fbank_matrix( + feature_compliance = paddleaudio.compliance.librosa.compute_fbank_matrix( sr=self.sr, n_fft=self.n_fft, n_mels=self.n_mels, @@ -127,7 +127,7 @@ class TestLibrosa(FeatTest): norm='slaney', dtype=self.waveform.dtype, ) x = paddle.to_tensor(self.waveform) - feature_functional = paddlespeech.audio.functional.compute_fbank_matrix( + feature_functional = paddleaudio.functional.compute_fbank_matrix( sr=self.sr, n_fft=self.n_fft, n_mels=self.n_mels, @@ -156,8 +156,8 @@ class TestLibrosa(FeatTest): n_mels=self.n_mels, fmin=self.fmin) - # paddlespeech.audio.compliance.librosa: - feature_compliance = paddlespeech.audio.compliance.librosa.melspectrogram( + # paddleaudio.compliance.librosa: + feature_compliance = paddleaudio.compliance.librosa.melspectrogram( x=self.waveform, sr=self.sr, window_size=self.n_fft, @@ -166,10 +166,10 @@ class TestLibrosa(FeatTest): fmin=self.fmin, to_db=False) - # paddlespeech.audio.features.layer + # paddleaudio.features.layer x = paddle.to_tensor( self.waveform, dtype=paddle.float64).unsqueeze(0) # Add batch dim. - feature_extractor = paddlespeech.audio.features.MelSpectrogram( + feature_extractor = paddle.audio.features.MelSpectrogram( sr=self.sr, n_fft=self.n_fft, hop_length=self.hop_length, @@ -198,8 +198,8 @@ class TestLibrosa(FeatTest): fmin=self.fmin) feature_librosa = librosa.power_to_db(feature_librosa, top_db=None) - # paddlespeech.audio.compliance.librosa: - feature_compliance = paddlespeech.audio.compliance.librosa.melspectrogram( + # paddleaudio.compliance.librosa: + feature_compliance = paddleaudio.compliance.librosa.melspectrogram( x=self.waveform, sr=self.sr, window_size=self.n_fft, @@ -207,10 +207,10 @@ class TestLibrosa(FeatTest): n_mels=self.n_mels, fmin=self.fmin) - # paddlespeech.audio.features.layer + # paddleaudio.features.layer x = paddle.to_tensor( self.waveform, dtype=paddle.float64).unsqueeze(0) # Add batch dim. - feature_extractor = paddlespeech.audio.features.LogMelSpectrogram( + feature_extractor = paddle.audio.features.LogMelSpectrogram( sr=self.sr, n_fft=self.n_fft, hop_length=self.hop_length, @@ -243,8 +243,8 @@ class TestLibrosa(FeatTest): n_mels=self.n_mels, fmin=self.fmin) - # paddlespeech.audio.compliance.librosa: - feature_compliance = paddlespeech.audio.compliance.librosa.mfcc( + # paddleaudio.compliance.librosa: + feature_compliance = paddleaudio.compliance.librosa.mfcc( x=self.waveform, sr=self.sr, n_mfcc=self.n_mfcc, @@ -257,10 +257,10 @@ class TestLibrosa(FeatTest): fmin=self.fmin, top_db=self.top_db) - # paddlespeech.audio.features.layer + # paddle.audio.features.layer x = paddle.to_tensor( self.waveform, dtype=paddle.float64).unsqueeze(0) # Add batch dim. - feature_extractor = paddlespeech.audio.features.MFCC( + feature_extractor = paddle.audio.features.MFCC( sr=self.sr, n_mfcc=self.n_mfcc, n_fft=self.n_fft, diff --git a/tests/unit/audio/features/test_log_melspectrogram.py b/audio/tests/features/test_log_melspectrogram.py similarity index 94% rename from tests/unit/audio/features/test_log_melspectrogram.py rename to audio/tests/features/test_log_melspectrogram.py index 59eb73e8..7d568038 100644 --- a/tests/unit/audio/features/test_log_melspectrogram.py +++ b/audio/tests/features/test_log_melspectrogram.py @@ -15,8 +15,8 @@ import unittest import numpy as np import paddle +import paddleaudio -import paddlespeech.audio from .base import FeatTest from paddlespeech.s2t.transform.spectrogram import LogMelSpectrogram @@ -33,7 +33,7 @@ class TestLogMelSpectrogram(FeatTest): ps_res = ps_melspect(self.waveform.T).squeeze(1).T x = paddle.to_tensor(self.waveform) - ps_melspect = paddlespeech.audio.features.LogMelSpectrogram( + ps_melspect = paddleaudio.features.LogMelSpectrogram( self.sr, self.n_fft, self.hop_length, diff --git a/tests/unit/audio/features/test_spectrogram.py b/audio/tests/features/test_spectrogram.py similarity index 93% rename from tests/unit/audio/features/test_spectrogram.py rename to audio/tests/features/test_spectrogram.py index 7d908a7e..5fe5afee 100644 --- a/tests/unit/audio/features/test_spectrogram.py +++ b/audio/tests/features/test_spectrogram.py @@ -15,8 +15,8 @@ import unittest import numpy as np import paddle +import paddleaudio -import paddlespeech.audio from .base import FeatTest from paddlespeech.s2t.transform.spectrogram import Spectrogram @@ -31,7 +31,7 @@ class TestSpectrogram(FeatTest): ps_res = ps_spect(self.waveform.T).squeeze(1).T # Magnitude x = paddle.to_tensor(self.waveform) - pa_spect = paddlespeech.audio.features.Spectrogram( + pa_spect = paddle.audio.features.Spectrogram( self.n_fft, self.hop_length, power=1.0) pa_res = pa_spect(x).squeeze(0).numpy() diff --git a/tests/unit/audio/features/test_stft.py b/audio/tests/features/test_stft.py similarity index 95% rename from tests/unit/audio/features/test_stft.py rename to audio/tests/features/test_stft.py index 03448ca8..58792ffe 100644 --- a/tests/unit/audio/features/test_stft.py +++ b/audio/tests/features/test_stft.py @@ -15,9 +15,9 @@ import unittest import numpy as np import paddle +from paddleaudio.functional.window import get_window from .base import FeatTest -from paddlespeech.audio.functional.window import get_window from paddlespeech.s2t.transform.spectrogram import Stft diff --git a/audio/tests/features/testdata/fbank_feat.ark b/audio/tests/features/testdata/fbank_feat.ark new file mode 100644 index 0000000000000000000000000000000000000000..73b061882a96cb1708b2fb28c11d58dc81ac4145 GIT binary patch literal 86596 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%0DrZlk(1wWh8gI(H^>%Ltx1EGr^H@3u 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 rEFg?4Y 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 XE*_JI$aX~p-BQF`>Or`l*2n(=g3RXx literal 0 HcmV?d00001 diff --git a/audio/tests/features/testdata/fbank_feat_txt.ark b/audio/tests/features/testdata/fbank_feat_txt.ark new file mode 100644 index 00000000..03c5bbb3 --- /dev/null +++ b/audio/tests/features/testdata/fbank_feat_txt.ark @@ -0,0 +1,942 @@ +test_wav [ + 8.86961 7.025289 6.664165 7.169617 7.317829 7.188704 8.351522 8.843228 7.711394 7.231504 6.903938 7.053499 7.293597 8.331067 7.871729 9.206844 9.434045 9.768963 10.01864 10.25888 10.68228 10.55968 10.62156 + 8.364346 7.526375 6.915925 6.705005 7.641569 7.827819 8.253532 7.794802 7.522578 7.222802 7.388284 7.493527 8.257078 9.141049 8.994849 9.348937 9.015431 9.343955 10.42236 10.13459 10.40709 10.39534 10.22199 + 7.230361 6.771988 6.422344 7.535786 7.164408 6.342811 8.723886 8.481328 6.804535 7.276428 7.471786 7.581892 8.757826 8.764767 8.570841 8.741215 9.756334 9.515329 9.720121 10.26671 10.67728 10.5581 10.61378 + 8.872903 7.371414 6.360067 6.153208 7.333708 8.0974 8.851793 8.730195 7.622618 7.169075 7.85181 7.04872 7.978426 8.408302 8.802312 8.395834 9.217923 8.381662 9.777567 10.40261 10.37856 10.31888 10.34441 + 7.532231 6.751142 8.175496 7.593341 8.063697 8.369373 7.881088 8.15251 7.428252 7.103447 7.989072 7.949497 8.124873 8.496659 8.553727 8.761693 9.109408 9.4684 10.13223 10.16827 9.71019 10.59482 10.90851 + 6.746207 6.833902 8.067636 7.6485 7.013279 7.693223 8.147296 8.030097 7.067122 8.186153 7.784977 8.756321 8.6457 8.458344 7.769485 8.669812 9.332602 9.097817 9.444702 9.950351 10.18657 10.94016 11.36237 + 7.128079 7.711221 6.469111 6.649592 6.508276 7.082622 7.440871 7.623552 7.594537 7.354738 8.278585 8.652099 8.709033 8.605441 8.353497 8.4307 9.50789 9.312969 9.785786 10.18853 10.35708 10.71134 11.13716 + 8.201805 8.373339 7.420624 6.559644 6.318965 6.861043 7.299667 7.459548 7.392651 7.774329 7.917755 8.39668 8.304465 7.503887 8.319722 9.310571 9.144365 9.811728 9.738833 9.825593 10.65616 10.42954 10.28108 + 8.140867 8.803907 7.842072 6.760153 6.040633 7.099484 7.671337 7.230731 7.280159 7.825505 8.67013 7.970374 7.691161 7.603088 8.121623 8.186304 8.884687 9.764359 9.774008 10.14217 10.57618 10.70955 10.46593 + 9.335081 7.443659 6.302314 6.274453 6.648484 7.101899 8.31965 7.279731 7.011406 7.738722 8.111496 7.826912 9.217668 8.704701 8.520703 9.237556 9.141415 9.966189 10.13588 10.02818 10.32232 10.5961 10.39195 + 8.623323 6.642638 6.092153 6.010561 6.961139 7.523609 7.631736 6.81785 6.3952 7.437364 7.784822 8.421501 8.569756 8.931039 8.394889 8.730085 8.568606 9.672934 10.10908 10.22258 10.30489 10.11329 10.44942 + 8.340079 7.404711 6.73463 6.621991 6.641872 6.501914 7.429152 7.325783 7.075316 7.198759 7.573832 7.726879 8.173958 8.713857 8.947656 9.52945 9.495684 10.04432 9.70799 9.853218 10.34904 10.31011 10.33581 + 9.458065 6.449831 5.764122 6.112749 6.633307 6.588135 7.683532 7.854395 7.497962 6.587214 7.522266 8.246718 7.842532 8.194103 8.769976 9.494311 9.410578 9.623136 9.262513 9.66307 9.958323 10.58842 10.94138 + 9.336168 7.658177 6.552251 4.668932 5.951582 7.329723 6.881893 7.673193 8.018956 7.765876 8.092113 7.657069 8.385877 8.804426 8.99403 9.033966 9.84664 9.652982 9.678547 9.901299 10.55994 10.98264 10.42628 + 8.845008 7.827737 6.934293 6.809871 7.603892 7.622493 7.295815 7.316114 7.707997 8.28838 7.455571 7.749361 8.357333 8.413839 8.780228 9.111949 8.773423 9.546294 9.316511 9.7621 9.853085 10.24652 10.58686 + 9.845795 7.653487 6.760835 6.417203 6.483883 8.249362 8.985138 7.782502 8.038197 7.994664 7.512061 7.712332 8.276911 8.76414 8.027122 8.730929 8.986988 9.50342 9.225771 9.793655 10.35338 10.19652 10.67655 + 8.068243 7.419188 7.323668 6.883723 7.588089 7.267737 7.464292 8.121238 7.117096 7.165044 7.638491 7.958453 8.368897 8.497821 8.06803 8.636445 9.186031 9.296571 9.991373 10.35782 10.44223 10.60756 10.9512 + 8.540399 7.109134 6.417945 6.851756 7.50145 7.613665 7.395747 8.489678 7.192803 8.40198 8.48846 7.516579 8.291675 9.133558 8.94935 9.040503 9.475376 9.886353 10.04679 10.23742 10.22118 10.67988 10.29023 + 9.943547 6.745187 7.141617 7.058182 7.203416 8.045156 7.670315 7.748672 7.009519 7.208478 7.261959 8.346151 8.425858 8.822375 8.973361 9.907825 9.710265 9.542497 9.383007 9.832958 10.30413 10.9831 10.99566 + 9.077311 7.305787 7.036552 6.220779 6.492191 6.642952 8.301676 8.177285 7.706949 7.897906 7.814847 7.765959 8.228884 8.499186 8.701291 8.90225 9.000106 9.510703 9.477421 9.869934 10.31142 10.33504 10.62863 + 9.609183 6.470312 6.850113 7.247727 6.606174 7.178535 7.569305 7.858948 7.907071 7.322339 7.393857 8.411836 8.555615 8.416198 8.268435 8.814535 9.016805 9.221167 8.856338 9.819329 10.63624 10.67038 10.4818 + 8.896682 6.96038 7.062835 6.841669 6.556721 7.257597 9.19041 8.196911 7.560658 7.475944 7.570982 8.699837 8.718691 8.698103 9.604801 9.410757 9.482757 9.489608 10.0253 10.21278 10.42956 10.25078 10.40438 + 10.05012 7.997848 8.523865 7.810099 7.064989 7.438704 8.866325 8.385101 8.702444 7.532755 6.970853 8.658536 8.904259 8.098039 8.240754 9.383977 9.924636 9.825594 10.16586 9.929379 10.33848 10.6166 10.88825 + 9.945673 7.943526 8.236642 7.982066 7.059935 7.337771 8.282602 7.669298 6.812744 7.241785 7.059946 8.290605 8.874635 8.206081 9.037805 9.692253 9.771944 9.658941 9.887684 9.687504 10.12401 10.5467 10.46784 + 9.24334 8.005972 8.029324 7.080775 6.692482 7.339699 6.330315 7.749064 7.191765 7.614157 7.584374 8.707373 8.633794 7.661246 8.89745 8.717183 8.727042 9.601094 9.761832 9.91165 10.89605 10.7561 10.4439 + 9.008655 7.696863 8.051159 6.21505 6.986367 7.75211 7.125566 8.008561 8.631255 7.692895 7.423654 8.070612 7.934104 7.481667 7.878049 8.699003 9.587379 9.81943 9.638152 10.14519 10.48706 10.67093 10.95845 + 9.56725 7.086317 7.604103 6.621353 6.861667 6.762026 7.150949 8.061196 7.548547 6.552682 7.445788 8.408651 8.276496 7.768121 8.235985 9.36837 9.303123 9.568222 9.907539 10.04948 10.3193 10.38864 11.01985 + 9.435867 7.065077 8.260999 7.606821 7.11696 6.622849 7.453804 7.729028 6.969114 7.593372 8.076168 8.142076 7.987474 7.750668 8.709545 9.194512 9.28443 9.696207 10.08809 10.43397 10.95729 10.62421 10.32698 + 7.326074 6.968657 7.991836 8.057238 8.277269 6.64677 8.417533 8.390658 7.716748 7.254366 8.046806 8.952176 8.943592 8.626341 8.475414 8.366508 9.379741 9.649915 9.956218 10.14107 10.0686 10.3009 10.63128 + 8.867029 7.436584 8.325071 8.151552 7.802304 7.204567 7.817641 8.41904 8.079826 7.800119 7.738661 8.019406 8.496906 8.371117 8.895446 8.874495 9.541505 9.603241 9.702045 9.520963 10.07405 10.22188 10.69203 + 7.892157 7.706676 8.708323 8.090714 7.298046 7.119635 7.677824 8.568966 9.065309 7.950867 7.361037 8.006032 7.704475 8.136228 9.241001 9.342211 9.766371 9.645364 10.53355 10.35027 10.26263 10.8308 10.88063 + 9.17825 6.552885 7.865911 7.314655 7.429624 7.467305 7.331274 8.097523 8.513691 7.603092 8.123087 8.988563 8.24368 7.665757 8.043156 9.323641 9.559673 10.4114 10.44213 10.46176 10.31303 10.41219 10.04096 + 9.499084 6.913695 8.680465 8.913202 7.983476 7.54614 8.20214 8.433187 7.619872 7.079637 7.59503 7.827682 8.021211 8.954014 8.512682 8.685501 9.039448 9.882102 10.09629 9.995301 10.25326 10.52707 10.41024 + 8.841949 7.852749 8.220187 8.067208 6.958006 6.525739 7.606658 8.743006 8.618779 7.857424 7.85938 6.839745 8.494206 7.93556 8.554915 9.17892 9.411014 9.403722 9.75618 9.915223 10.4127 10.62058 10.75261 + 8.80931 7.176473 8.226482 8.048065 6.875594 7.035853 8.159007 8.788584 7.998541 7.745961 7.02769 7.343524 8.768233 8.742394 8.993815 8.919962 8.948602 9.299537 9.644719 9.328181 10.0551 10.60812 10.26714 + 8.416738 7.433433 8.202932 7.654713 7.487177 7.454067 7.807778 8.21654 7.973643 7.745943 7.477229 7.699207 8.724244 8.921854 9.167027 9.329788 9.198338 9.449234 9.350556 9.504007 10.01113 10.77754 10.79311 + 7.265522 8.85788 8.794858 6.660202 5.630237 7.668158 8.690257 8.2572 7.200497 7.342714 7.748627 8.173976 8.632828 8.53996 9.053345 9.001765 9.227647 10.09744 9.63631 10.25264 9.908823 11.11253 11.10346 + 8.641815 8.838676 8.314915 6.847168 5.620167 7.186635 8.486069 8.301935 7.863872 7.790708 7.733249 8.113005 8.49118 7.827488 8.389672 8.932463 9.147495 10.10519 10.22344 10.33928 10.09212 10.47646 10.13575 + 7.634632 6.384978 8.24638 7.503924 6.99588 7.640463 7.480204 7.821477 7.610888 7.87086 8.010333 7.981211 7.871301 8.551961 8.90114 9.304276 8.653571 9.205483 9.727321 10.48769 10.20595 10.13756 10.41514 + 8.518332 7.862745 8.034936 8.2016 6.675735 7.573159 8.171587 8.14386 7.991887 6.929758 7.718138 7.759238 8.233613 7.833398 9.010793 8.673923 9.241878 10.12235 10.55527 10.50808 10.58219 10.75335 10.4664 + 8.728762 7.574676 6.733976 7.824086 8.000705 7.302856 7.860464 8.410678 7.924881 6.743472 7.201906 7.431501 7.915576 8.187505 8.869449 8.608752 9.654437 10.20065 9.895975 10.05015 10.0937 11.01951 10.82668 + 7.629794 6.486794 6.295053 7.050656 7.523815 7.511558 8.234637 8.500476 8.005258 7.928689 8.219633 8.856939 8.562517 8.864034 9.246393 9.440809 9.380711 10.00752 9.569251 9.957119 10.54277 10.3938 10.80297 + 9.755882 6.397912 7.59745 7.644662 7.147341 7.108955 8.413054 8.151018 7.909472 7.656272 9.413692 11.06461 10.69808 10.64652 11.38675 11.21603 11.1705 10.6691 9.91959 10.95611 11.29983 11.17031 10.97226 + 9.474765 7.803607 8.385871 6.384162 7.948147 7.78756 8.428943 8.824086 8.393611 8.634716 8.208453 8.799019 9.028046 9.011028 9.807654 10.60488 10.43945 10.06425 10.27431 9.998198 11.03344 10.98869 10.06162 + 8.450254 7.49551 6.799858 6.310681 7.20258 8.050933 8.205685 8.776224 7.973586 7.784925 7.730099 8.780946 9.105856 8.773993 8.182259 8.562423 9.90097 9.80205 10.24598 10.17385 10.20839 10.46425 10.26094 + 8.252859 7.287729 7.475165 7.372538 6.989303 7.400277 6.899447 8.107825 7.968466 7.659332 8.521437 9.400009 9.208941 8.744918 8.120193 8.727738 9.995106 9.742892 9.950342 9.993467 10.03006 10.28127 10.45164 + 8.592541 7.811454 7.721195 6.033926 7.451138 7.954978 7.936564 8.032441 8.207817 7.761642 7.824575 8.806037 8.584102 9.029242 9.405202 9.570443 10.0666 9.799012 10.14801 10.00847 10.26264 10.47974 10.30221 + 8.263712 7.622368 7.388706 6.853238 7.028842 8.005108 7.877354 8.477866 8.796792 8.305426 8.403102 8.24081 8.906131 8.901518 9.546893 9.517485 9.934557 9.7798 10.04766 9.671112 10.1303 10.74265 10.63667 + 9.357657 8.026215 7.907611 7.254067 6.912889 7.510985 8.215575 8.492505 8.151805 7.310562 8.163457 8.056206 8.009975 8.118791 8.972518 9.303927 9.412671 9.438314 9.8068 9.750667 10.35458 10.35285 10.09931 + 8.511848 7.523149 8.195268 7.966453 7.744322 8.485562 8.004887 7.922952 8.245237 7.383193 7.844249 8.127041 8.127259 8.443523 8.102531 8.774108 8.681726 9.477551 8.882012 9.804171 10.55685 10.48077 10.37192 + 7.975676 7.082146 7.933741 6.951503 6.272567 7.088233 7.917744 7.379583 8.238628 7.820266 8.475443 8.424726 8.05715 8.067741 8.94787 8.867351 9.374059 9.266661 9.49703 10.01138 10.3168 10.71612 10.27346 + 8.813721 8.656951 7.681757 7.203363 7.488046 8.216457 8.221495 7.714307 7.938663 7.708951 8.019526 7.926672 7.864351 7.698409 8.801851 8.555806 9.788485 9.553381 9.62245 10.2158 9.915847 10.40117 10.80687 + 6.477304 7.734454 8.373713 6.965827 8.077723 8.442548 7.893295 8.407219 7.909376 8.080144 8.55372 7.803288 6.868239 8.449481 8.860373 8.910608 9.437862 9.165442 9.82246 10.30201 10.48472 10.77984 11.18594 + 9.42877 10.45913 10.29159 9.716755 9.220295 8.658374 6.794528 6.873018 7.217855 8.288869 8.809176 8.809996 8.093638 8.87556 9.207784 9.647738 9.825712 10.08347 9.918015 10.10553 10.54475 11.02415 10.92163 + 13.23045 15.72255 15.14105 16.25381 15.65043 12.23466 10.81911 10.62931 10.80597 11.77175 12.40585 12.68368 13.26326 11.76209 10.96095 14.30546 15.08591 12.37251 12.23275 11.97119 10.67747 10.71765 10.87486 + 13.65519 16.94076 16.0239 18.20588 16.9831 13.7648 12.16126 11.07043 11.80799 12.73412 12.98298 12.90398 14.25156 11.9087 10.24638 14.79149 15.7653 13.01829 12.92099 13.79269 11.3934 11.02932 11.13173 + 13.2973 17.06046 16.08663 18.53358 17.69795 13.95008 12.40663 11.98961 12.74475 13.79677 14.19347 14.3372 14.89816 13.25463 12.57261 16.00417 16.9676 13.95466 13.9528 14.54457 12.07387 11.57656 11.49943 + 11.75321 16.53652 16.49979 19.33843 18.63615 13.93179 11.64835 11.64008 12.97903 13.87796 14.90638 15.14878 15.24947 14.3757 13.73179 15.41584 15.93701 14.80291 14.12144 14.58253 12.19397 12.21274 12.09839 + 12.51654 17.17333 16.62761 20.22687 19.77942 13.27525 11.65 12.57212 13.83558 14.21572 15.06257 15.13174 14.75701 15.54657 14.15343 15.183 16.24345 14.94379 14.57995 14.90003 12.3515 12.90885 12.08299 + 12.59919 17.27024 16.59142 20.83065 20.46106 14.01313 12.53942 13.63936 14.82475 14.79233 15.39594 15.28064 15.61105 15.19362 13.62629 15.96059 17.22206 15.25036 15.57805 15.82821 13.8464 14.34314 12.32957 + 12.87339 17.40207 16.69853 20.75185 20.27472 14.82241 13.57454 13.89783 14.86771 15.49655 15.69082 15.79947 15.61258 16.25987 14.84013 16.51847 18.01414 15.01476 15.3407 16.01681 14.13993 14.62834 12.70863 + 13.17359 17.59177 16.96258 19.95944 18.9106 15.22701 13.8382 13.31798 14.14737 14.76138 15.86723 16.08653 15.98195 16.09414 14.82789 16.4943 17.65386 15.15134 15.58843 16.19427 14.51803 15.25478 14.10639 + 13.61701 17.7865 17.24287 20.31397 19.26783 15.3012 14.27533 14.00652 14.06157 13.97732 15.41972 16.40127 16.03523 14.89164 14.54936 15.81458 16.71577 14.59401 15.69445 17.08231 15.19946 15.35905 13.67907 + 14.00184 17.83053 17.10755 20.32385 19.31859 16.05173 14.2608 13.70262 13.87365 13.57939 14.82414 15.85239 16.70191 15.73202 13.95968 14.90556 15.42773 15.31231 15.90333 16.87693 15.50014 15.44649 13.9608 + 14.57378 17.84243 16.76657 19.45782 18.36666 15.36792 13.44019 13.72313 14.92875 14.36976 14.25026 15.64569 15.89312 14.45428 13.3161 14.53451 15.62068 14.59393 15.80624 17.03548 14.96751 15.02374 13.4909 + 15.21447 18.18304 16.96745 19.16579 18.69295 14.50879 13.17371 13.65467 15.22889 14.17074 13.75207 14.95903 15.48484 14.01785 11.96252 13.67882 15.37274 14.30946 15.06645 16.43822 14.03593 14.029 12.96432 + 15.69557 18.73573 17.84295 20.32482 19.18899 14.03427 12.50394 12.18307 15.27329 13.74666 13.09154 14.42644 14.96374 13.87214 11.83368 12.93985 14.45116 14.00281 15.06362 16.09592 13.61044 13.31885 12.74485 + 15.98183 18.61076 17.23318 18.58419 17.75739 14.66514 11.80884 13.04507 15.05399 13.23005 12.31126 13.64777 13.9603 12.94314 11.54242 12.23427 13.46654 13.42021 14.35025 15.38576 13.45529 13.28467 13.43179 + 16.36983 18.49153 17.46072 19.55406 17.8304 14.26844 11.65388 12.54786 13.89278 12.02204 11.70842 12.86929 13.92763 13.19448 11.47678 11.02568 13.00155 13.05625 13.99241 14.93269 13.36193 12.62123 11.25201 + 16.91053 19.31395 17.91098 18.89643 16.86432 13.72694 11.87185 13.24254 14.00205 12.05257 10.76908 12.87949 13.28356 11.94633 11.05437 11.12533 12.75738 12.05501 13.33129 14.07069 12.3796 11.7192 11.05169 + 17.29174 19.6846 17.73157 17.35478 16.16297 14.15115 11.11073 12.40668 13.28362 11.18836 11.07174 12.54022 13.02938 11.88706 10.44743 11.13548 12.77041 12.04656 12.83903 12.8876 11.77514 11.40439 10.56045 + 17.23432 19.42053 17.20756 16.95235 16.25754 13.89139 11.49117 11.78105 11.14721 10.20588 10.31909 12.48863 12.60464 11.83299 12.36004 11.31453 12.29337 12.36164 12.43958 12.93882 11.9782 12.27852 11.67843 + 16.53074 18.71063 16.51751 16.94945 16.05041 12.74791 10.03071 10.72647 10.99932 9.982744 10.96635 12.68731 12.83623 11.30921 11.54913 10.90253 11.23106 11.50233 12.71762 13.18253 11.76622 12.38223 11.70104 + 15.69217 17.89815 15.7483 14.97377 14.53612 11.96516 11.12983 9.131196 10.96767 10.9673 12.05442 12.21859 12.5895 10.55106 10.79283 10.54876 11.01498 10.7606 11.54235 11.51353 10.85205 11.89782 11.11372 + 13.86923 16.57632 15.26331 14.85788 14.39067 12.7171 11.17243 11.00468 11.68423 13.25407 12.60769 12.59903 12.12048 12.90249 13.94144 13.63238 13.21792 12.05534 12.87838 14.90839 15.73015 16.02667 15.69104 + 13.61382 16.34682 14.90382 14.8893 14.40084 13.49422 12.6097 13.05071 13.73629 15.75304 15.30508 13.85447 13.98672 15.82224 16.32998 16.46949 16.51073 13.91432 14.74784 17.0687 18.13454 18.42613 18.47258 + 14.55605 16.97081 15.03501 15.3369 14.16967 11.95557 10.91977 11.08843 11.75058 13.24282 12.85878 12.96099 13.28478 15.59657 14.72278 15.30267 15.28392 13.16171 14.36229 17.22016 18.41889 19.45104 19.20117 + 15.47327 17.72926 15.78447 15.42446 15.40768 12.5729 10.11994 10.89366 12.10496 13.59284 13.03747 13.40623 12.94038 16.30528 16.32386 14.95233 14.70298 13.22234 14.0664 16.60508 17.89858 18.17508 18.00281 + 16.2211 18.26995 17.28866 17.57735 15.27353 13.3695 11.53266 11.68451 11.35421 11.92297 12.33619 13.06362 12.45224 15.49412 16.06547 15.92675 15.58576 12.58488 13.5124 15.40464 16.18043 15.33247 15.62229 + 16.40175 18.42591 17.84651 18.44906 15.41768 13.78307 12.2946 12.64999 11.22093 11.41927 11.63084 10.93246 11.98123 14.54343 16.20716 16.31189 15.47426 12.76656 13.13507 15.26896 15.09722 14.0725 14.46431 + 16.6757 18.54147 17.61383 18.17967 15.46035 13.83175 11.8658 12.33601 11.13389 11.20397 11.12461 11.22868 12.22488 13.94469 15.89769 16.34622 15.46319 12.74337 14.20451 15.74678 15.10971 14.52905 14.4963 + 16.76283 18.5887 17.68788 18.20937 15.80549 13.99685 12.14814 12.54838 10.8432 11.06379 11.41749 11.17507 11.48255 12.85203 14.78319 16.05849 16.08326 13.06919 13.94136 15.39909 14.80749 14.61425 14.66702 + 16.64995 18.30085 17.58634 18.3342 16.15151 14.58886 11.89366 12.30556 11.47828 11.79887 11.09667 11.30176 11.16868 14.35984 14.87677 16.13587 16.49911 13.06481 14.12372 15.51614 15.46192 15.01677 14.90916 + 16.52672 18.18384 17.63734 18.262 16.74109 15.33377 12.32343 12.43404 12.25445 12.18885 12.2143 11.90199 11.90509 14.61265 15.28587 16.9969 17.03483 13.91487 14.51653 15.79068 15.46326 15.15117 14.32447 + 16.60033 18.01448 17.0511 17.71783 17.75788 16.23282 12.81127 12.78385 12.44913 12.3739 12.64835 12.44617 11.88231 14.26506 14.75798 16.41036 16.44767 13.79646 14.08188 15.74754 15.21961 14.5229 14.27368 + 16.58554 18.17001 15.6192 15.79108 17.83907 16.49691 13.58467 13.87136 13.15251 15.34053 15.64338 14.75528 12.38052 13.428 14.05307 15.71102 16.18547 13.51842 13.2032 15.82146 15.4119 15.28212 14.9756 + 16.81706 18.37561 15.55623 15.33322 17.88172 16.29072 14.11057 14.21804 13.08518 13.69146 14.14797 14.73615 13.03893 13.11906 14.03352 14.95837 16.48228 13.51765 14.07395 15.32152 14.80149 14.38036 14.81253 + 16.95721 18.65568 15.88247 14.83851 17.85907 16.19847 14.35859 14.35917 13.97949 13.80073 14.48404 15.46158 12.86709 12.57524 13.31753 13.57888 14.20235 13.62078 13.80603 14.58132 14.26628 15.57101 15.81091 + 16.92929 18.59748 16.18815 16.21428 17.81978 16.08954 14.27445 14.10925 13.96899 14.1386 14.35728 14.62649 12.22072 11.41585 11.79407 12.69996 13.94304 14.1476 14.00437 14.08617 14.21517 16.62167 16.08522 + 16.71347 18.34732 16.52386 16.70664 17.32837 15.66043 14.25163 14.13859 14.01087 14.8577 14.49389 14.02374 12.22193 12.02307 12.09513 12.59532 14.0639 13.72341 13.91703 14.04354 14.41245 16.46469 16.29546 + 16.4208 18.01264 16.88099 17.1442 16.57642 14.64041 13.85327 13.94551 14.64954 14.75776 14.21291 14.0806 12.50921 12.25238 11.75339 12.30866 14.22572 14.74261 14.3222 14.56139 14.66986 16.32808 16.07767 + 16.02585 17.41488 16.75061 17.14062 15.60216 13.43982 13.3218 13.5059 15.12315 15.74747 15.12732 14.97423 11.94713 11.64201 11.86428 12.81159 14.43116 14.51448 14.0855 13.91175 13.53605 16.79836 16.27676 + 15.54676 16.81251 16.59787 16.86721 13.93739 12.5265 13.02697 13.58001 14.63541 15.0335 15.03953 14.87171 11.96232 11.17344 11.23595 12.35256 13.94297 14.62026 14.15592 14.0969 13.14401 15.85678 16.06257 + 15.29335 16.5896 16.2428 16.40902 13.19328 11.96381 13.15584 13.4573 13.83275 14.18077 14.74952 13.4333 11.54537 11.45848 11.20981 11.44254 13.91985 14.09482 13.83819 13.72444 12.47239 15.62782 15.47463 + 15.03695 16.41206 15.76506 15.74993 14.99031 12.53887 14.11596 13.88085 13.56149 13.86856 14.16082 13.18733 10.81969 11.83342 11.28586 11.12295 13.29487 13.72911 13.43846 13.1814 11.87428 13.31521 14.49123 + 15.19216 16.60078 14.61114 14.46758 15.01078 13.24106 14.01335 13.57776 12.27156 13.13906 14.44345 13.87597 11.4695 11.93211 11.51881 11.86933 13.54847 13.27814 13.21632 13.90559 14.1398 14.70719 14.13614 + 14.67921 16.36944 14.31438 14.29885 14.20024 12.58013 12.82334 12.93028 12.20578 13.60437 14.77291 14.12951 12.00097 12.08059 12.8358 12.58611 13.60779 13.56823 13.54014 15.80377 16.93598 17.07594 16.38759 + 14.09909 15.53763 14.35333 14.36311 13.46532 12.24172 11.28518 11.79358 12.87418 12.31148 14.00558 14.61462 12.33662 12.94618 13.78474 13.02037 13.37858 14.04721 15.38267 18.08268 19.1378 18.01291 17.87174 + 13.13866 14.28506 13.46247 13.33514 12.55707 11.26553 10.44495 10.7674 12.86463 12.67321 13.31184 12.77522 11.95182 13.00975 13.90651 13.2495 14.2042 14.69992 15.82987 18.51886 19.10749 19.2898 19.35597 + 12.87447 13.63893 12.77628 12.87282 11.82412 10.49559 11.2738 11.53943 12.46402 13.76956 13.33128 13.70105 13.19503 13.69975 14.5716 14.70375 14.28935 14.60292 15.83705 18.41346 19.52948 19.5496 19.50181 + 12.43892 14.00917 11.94452 12.23295 13.09062 11.04633 10.81847 11.03293 12.85549 13.9011 14.34009 14.35291 13.72982 13.88251 14.83228 14.69408 14.73889 15.0092 16.64098 19.92425 20.72796 20.62129 20.78949 + 12.57589 14.14755 12.76697 12.63588 12.86826 10.93601 10.81407 11.39892 13.70268 15.24565 15.25113 15.90769 15.04436 13.95307 14.30912 15.37132 15.7328 15.43253 17.88644 20.36236 20.81531 21.52759 20.81256 + 11.55697 13.42533 11.86985 12.06834 12.89067 12.34525 12.49656 12.66325 13.77704 15.66433 16.10908 16.22246 15.24011 14.59829 15.35136 15.66786 15.75713 15.76261 17.19726 20.20177 20.41369 21.18811 20.27664 + 11.83781 11.78476 10.88169 12.16515 12.8652 12.27359 12.28256 13.37679 14.12796 16.37236 16.86594 16.74899 13.97586 14.41109 15.27517 15.25704 16.5749 15.36801 16.12666 18.94821 19.08416 20.14678 19.19424 + 11.29138 12.42908 11.55172 11.38864 12.08098 11.84389 12.93314 13.31962 14.90897 16.77657 16.34854 15.3447 13.96065 14.13565 14.2487 14.35624 16.70179 14.96482 14.82867 17.15551 17.93615 17.96055 17.92477 + 11.65481 13.19278 11.53873 12.37848 11.75132 12.30172 13.64941 13.70718 15.32799 15.79656 16.56388 15.52489 14.06691 13.22153 13.50002 13.39991 15.37756 14.06801 14.822 16.09739 16.44575 16.48426 16.86766 + 10.83643 12.90215 11.20963 11.4503 12.48443 12.65709 14.0135 14.90167 14.40853 15.14135 14.69142 14.34933 13.56003 13.23642 13.07663 13.29242 14.56171 13.91872 14.96106 16.0597 15.73718 16.22322 16.35061 + 11.76764 14.94607 14.002 12.59242 13.17363 12.93256 14.04444 14.95929 14.73403 14.32073 13.673 13.46165 12.49231 11.98037 12.40751 12.22861 14.2252 13.65674 14.01322 15.1333 14.95369 15.47503 16.08985 + 13.89615 16.76128 15.37205 14.68637 14.55084 14.06249 14.60906 15.02118 14.96586 14.17659 13.32582 13.36398 12.40657 11.75675 12.20584 11.6482 13.49479 13.45353 13.88642 14.68982 14.64923 14.41646 14.38884 + 14.84046 17.53472 16.00968 16.66092 14.74903 14.52959 14.44483 14.51026 14.96541 13.62469 12.74818 12.63656 11.79429 10.7945 10.28362 10.71255 12.82129 13.17442 13.48379 14.43379 14.1143 13.92664 13.57136 + 15.24041 17.86895 16.36832 17.24933 15.26022 14.57038 14.41827 14.01725 15.12896 14.63774 13.50198 13.16672 10.8975 10.52911 10.55219 10.53394 12.4472 12.95718 13.381 14.41105 14.18102 13.43132 13.21059 + 15.70714 18.11394 16.63966 17.54475 15.6258 14.62249 13.44291 14.33535 15.30156 14.73038 12.71182 11.88729 11.21848 11.80892 11.28598 11.00321 12.07642 12.61302 12.96624 13.64967 13.5497 13.66227 15.18429 + 16.24144 18.45406 16.74632 17.26887 16.00562 15.20356 14.67932 14.5265 15.42036 15.9411 12.72794 11.28165 11.22411 11.92239 11.05458 10.47411 11.84302 12.45643 12.32757 13.31341 13.65614 14.01642 15.6444 + 16.61691 18.75608 16.65908 16.05809 16.49335 15.44349 14.24317 13.74861 15.73102 16.03167 12.45979 11.43863 12.06159 11.43628 11.58031 10.96982 12.73052 13.25921 12.43133 13.44257 13.17499 13.61122 15.05092 + 16.80972 18.82384 16.56598 15.89633 16.39415 14.86858 13.51579 13.62421 15.78119 15.96636 12.04702 12.23535 12.08618 11.75746 11.64274 11.03938 12.40111 12.2805 12.29179 13.94364 13.40477 14.06813 15.88864 + 16.63538 18.58344 16.62993 16.47061 16.84248 15.18759 14.40616 13.94959 15.81456 15.56275 13.3875 12.99495 11.75405 11.29325 11.58946 10.95365 11.4328 12.09967 12.70522 13.64252 13.03335 14.81887 16.09799 + 16.17998 17.74599 16.20119 16.20937 17.15302 15.38168 15.02195 14.70119 15.58851 15.20672 13.59827 13.00949 12.00444 11.9139 12.20626 11.42136 12.15257 13.27923 12.24949 13.81115 13.19789 14.65822 15.93385 + 15.63498 16.83412 15.9795 16.01633 16.73568 14.96041 15.20592 14.9941 15.25987 15.44159 13.90219 13.12603 12.1955 12.00728 11.73402 11.36467 12.2128 13.21522 12.46231 13.59565 12.94152 14.60943 15.73831 + 15.64667 16.88672 16.15878 16.30288 14.82288 12.78746 14.38334 14.24222 15.33887 15.6051 14.19205 13.4702 11.48957 11.67185 11.36558 11.50844 11.67504 12.6779 12.27801 13.62284 12.4663 13.766 14.50554 + 16.1116 17.70094 16.61898 16.63179 14.50537 13.00403 14.49458 14.1091 14.77081 16.03117 14.69265 13.87188 11.65564 11.77306 11.69971 10.80465 11.53343 12.87404 11.97025 13.6426 11.97943 14.26168 15.0657 + 16.30089 17.96895 16.63384 16.78599 15.04495 13.30599 14.34233 14.00725 14.74239 15.8376 14.84421 13.98984 11.77159 11.99564 11.91329 11.26901 12.10066 14.10714 12.30086 12.97296 11.96393 13.5807 13.9249 + 16.11331 17.73792 16.50445 16.31737 15.14762 13.18915 13.33717 13.48801 15.0379 14.98231 14.6054 13.371 10.87303 10.89191 11.57216 11.3191 12.55595 13.71982 12.37777 13.36162 12.34734 12.24435 12.79598 + 15.58554 16.86545 15.76826 16.14976 14.84043 12.69869 13.17145 12.83138 13.56788 13.88323 12.82279 11.39667 10.49319 10.13156 10.41932 9.690503 11.98269 12.59705 11.74903 12.81073 11.81398 12.05253 12.92546 + 15.40506 16.56629 15.7819 16.15461 15.12686 13.06547 13.38418 13.28866 11.46526 12.2537 12.2489 12.08193 10.35587 10.30065 10.11564 10.48493 11.5637 11.96607 11.58659 11.82748 10.88299 11.80397 12.83247 + 15.64443 17.13461 15.25182 15.23267 14.96755 12.78958 12.95011 12.96703 12.13067 12.01262 11.68263 11.51331 10.19018 10.40131 10.45652 9.893003 10.72811 11.31659 10.82685 11.00283 10.72475 11.77716 12.83387 + 15.71785 17.45413 15.5854 15.01698 14.3412 11.71642 10.96577 11.03219 11.98035 12.71681 11.61681 10.26505 9.601998 11.02885 11.42186 10.48678 11.01715 11.48228 11.22338 11.53144 10.79359 11.5774 11.99054 + 15.35344 17.22778 14.87986 13.97491 13.06646 10.3242 10.27509 10.32014 10.23042 10.61678 9.737593 10.01495 9.896219 9.359041 9.507567 9.67006 10.14279 10.65076 10.4929 10.57693 10.33899 11.15093 10.91731 + 14.73678 16.47514 14.2816 13.68988 13.41362 11.12034 10.6353 10.23782 10.21636 10.79559 10.0661 10.10203 9.259908 9.090837 9.185848 9.923974 9.884459 10.10081 10.24038 10.84077 10.28913 10.66675 10.87501 + 13.34543 15.10439 13.62741 13.43057 13.13955 11.26525 10.73161 10.48361 8.931073 10.65594 10.13154 10.01275 7.661518 8.794694 8.97205 9.528334 9.832764 10.47726 10.19211 10.24871 10.08061 10.30916 10.30075 + 10.66386 12.08491 13.25964 13.39106 12.19863 10.12935 11.24597 11.23391 9.032432 10.34383 9.701096 9.622985 8.836775 8.976686 8.384194 8.839561 9.518727 10.43447 10.18111 9.936262 10.31167 10.56191 10.62938 + 9.864835 11.36032 12.31589 12.7327 11.80678 10.03503 10.97492 10.7874 9.752045 8.775429 8.095765 8.616365 8.481807 8.58884 9.057997 9.505718 10.19115 10.05057 10.04556 10.14044 10.41077 10.46338 10.77466 + 8.924618 11.12906 12.26638 12.40822 11.61309 9.230251 10.80107 10.44947 9.52743 9.636349 8.786553 9.209707 8.387288 8.182937 8.243385 9.326593 9.774284 10.1685 10.02218 10.17269 10.70011 10.49688 10.61065 + 10.46031 12.47213 11.88194 11.74302 11.8674 10.72511 11.42089 11.52294 12.66962 13.42245 12.90012 11.16923 10.26218 10.81529 10.76803 10.16122 9.826 9.915348 9.86139 10.30795 11.00059 11.42232 11.84945 + 12.26625 14.16075 12.72903 14.60278 14.37007 13.44235 12.87462 13.86045 15.57594 16.44231 16.44123 15.36868 13.77279 15.25738 15.21306 14.52211 13.27622 12.97607 12.30169 14.6017 16.36056 17.2046 17.79084 + 12.88817 14.4487 12.22028 13.27812 13.9757 13.02691 12.24403 13.3259 14.29199 15.57484 15.93701 16.14005 13.74489 14.87244 15.91617 16.46932 15.27049 13.81515 13.83679 16.70215 18.24577 19.36378 19.67374 + 12.00124 15.37659 15.7605 14.22926 13.0174 13.10144 11.59302 12.30477 13.87144 16.48501 16.24954 14.65436 13.44942 13.95702 15.1756 15.80813 15.05493 13.84618 12.97811 14.7254 15.89062 17.37466 17.80906 + 11.40193 16.41908 17.10656 18.07596 18.44994 14.86982 11.46446 13.08391 15.70103 16.6888 15.61081 14.1348 12.79728 13.32965 13.90944 13.99649 13.78145 12.89573 11.9253 13.4449 14.24946 14.72222 15.25539 + 11.44434 16.72986 17.13328 18.86685 20.06189 16.01924 13.17847 12.7762 17.20245 16.84163 15.03655 13.80424 11.7222 13.07261 13.83397 13.96507 13.21282 12.76647 11.96956 12.39547 12.90304 13.23651 13.36372 + 10.10038 17.35346 17.76299 18.8912 20.46964 16.98438 14.85764 12.72846 16.86143 16.67736 14.81743 13.0213 12.2606 12.59028 13.18198 14.63323 13.8833 13.29084 11.56619 12.33959 12.88427 13.18531 13.93617 + 11.39528 17.46929 17.92314 19.14353 20.62991 16.99307 15.65434 13.20797 15.76896 15.81176 13.97282 12.77619 11.2707 11.88343 12.92847 13.32276 13.7076 13.56002 11.07274 11.85992 12.23533 13.28798 15.04418 + 11.27983 17.707 18.24305 19.08735 20.56552 17.11422 16.33149 13.43726 14.52213 14.48029 13.66881 12.0331 11.1849 10.85902 12.006 12.63326 13.76072 13.75976 11.0928 11.39149 11.49694 13.85464 15.78272 + 10.4093 17.90809 18.4453 18.32235 20.13852 17.31362 17.00359 13.08082 13.30086 13.77682 13.61921 11.83928 11.08163 10.67048 11.6871 11.93983 13.32355 13.21966 11.60241 12.79556 10.68321 13.65361 15.63675 + 11.84527 18.06657 18.71371 18.15455 20.01794 17.42912 17.4063 13.26461 13.40163 13.82001 13.56594 12.01353 11.27739 11.76238 11.80556 12.06823 13.41709 13.49274 11.33103 12.29501 11.13933 13.40397 15.07014 + 11.17124 18.38477 19.04682 18.43799 20.28102 17.52682 17.30856 12.02493 13.38366 14.0256 12.96288 11.21294 11.00717 11.76898 11.5427 11.69634 12.18315 13.27228 12.24852 12.80867 11.54512 13.54795 14.91226 + 11.79911 18.40328 19.16708 18.38249 20.19378 17.33193 16.52722 12.74763 13.76983 14.43673 13.70339 10.60393 10.94744 11.23283 11.7214 10.93908 12.70267 13.61965 12.34297 12.93185 11.72894 13.06544 14.49717 + 11.53892 18.40511 19.1988 17.54394 19.24091 16.508 15.51558 11.58755 12.57494 12.96985 12.25272 10.15904 10.6575 10.79901 10.68679 10.70864 12.52 13.14616 11.88523 13.06559 11.90712 13.5841 14.07163 + 11.24507 18.51727 19.28405 15.55707 16.47013 15.3894 15.69263 12.4398 12.47237 12.42525 11.96187 10.67544 11.41625 11.04646 10.61635 10.52443 12.70697 13.38649 11.35408 12.26194 11.28505 13.45126 13.82557 + 11.66839 18.54571 19.34097 15.28023 16.99143 15.16772 15.61421 11.90284 12.48574 13.0267 11.92334 10.24209 11.53736 11.04919 10.13369 10.87534 12.32853 13.07113 11.1777 12.35379 11.42702 13.24903 13.47682 + 11.33042 18.52498 19.28227 15.3864 16.97915 14.79772 15.07273 11.31091 12.86657 13.35474 11.33974 9.940867 11.48389 10.81812 9.995729 10.97024 12.09934 12.44435 10.87501 12.38766 11.07067 13.29669 13.62211 + 11.3056 18.38368 19.13547 14.39207 16.36372 14.8709 14.99244 11.87787 12.69045 13.26806 11.46194 8.540175 10.51303 10.84584 10.27139 10.64636 11.47711 11.95679 10.57964 11.26044 10.76696 12.47983 12.5918 + 11.55642 18.09599 18.73145 15.37899 17.526 15.3725 15.14406 11.61225 12.45738 12.59422 10.7795 8.732772 9.834822 10.28893 9.671982 10.17871 10.94767 11.20057 10.26523 10.61838 10.52447 10.65674 11.38504 + 10.35312 17.61216 18.04445 15.89868 17.93677 15.43736 14.92465 10.95503 10.74119 11.10365 10.10367 8.769894 9.549344 9.448477 10.16095 9.991289 10.12919 10.67581 10.30081 10.2757 10.61878 11.08467 11.66054 + 10.57029 16.80257 17.43232 15.33712 17.40169 14.69754 13.7979 10.32226 10.54766 10.62139 9.538151 8.558439 8.763721 9.43328 9.823753 9.941569 10.20876 10.29988 9.839418 10.4947 10.67628 10.63656 11.36743 + 10.88069 16.40481 16.94452 14.63999 16.55998 13.75831 12.04815 10.07983 9.779847 9.41483 9.367539 8.638872 7.551649 8.591156 9.22356 9.253392 9.312204 10.12255 9.771526 10.66726 10.28885 10.70903 10.99041 + 10.68995 15.9614 16.66981 12.87561 15.32809 13.38852 13.34967 10.08965 9.267484 8.510974 8.937634 8.066225 7.492024 8.559677 9.379137 8.934621 9.395594 9.947574 9.762503 10.16802 10.52471 10.62313 10.52163 + 10.10682 15.73363 16.55744 12.98704 15.02876 13.22308 13.64594 9.306398 8.513678 8.840482 8.639846 8.813548 8.690471 9.372956 9.21609 9.271674 10.22423 10.17199 9.91317 10.44354 10.81481 10.71306 10.58567 + 10.53498 15.85452 16.48894 13.69206 15.73015 14.98417 13.35866 12.12415 12.66079 12.8636 12.57128 11.65822 10.42173 9.932479 10.30401 10.88577 10.69672 11.60742 10.67614 10.5448 11.20179 12.58635 13.41792 + 10.96499 15.49205 16.39072 14.58421 16.09194 14.12068 12.85947 10.9668 11.6846 11.61401 11.32033 10.1519 9.595464 9.126978 9.282423 9.871722 10.47498 11.93141 10.77083 10.79808 10.82797 12.26179 13.4949 + 10.02513 14.95054 15.62905 12.19269 13.6047 13.04639 12.49895 9.641859 10.20514 10.12763 9.829097 9.059594 8.440591 8.796917 9.133607 9.019018 10.19306 11.17522 10.55359 10.32446 10.81375 11.08818 12.5863 + 9.422004 14.69329 15.27407 12.65151 13.94985 12.99975 11.70039 9.886936 9.611949 10.09684 9.314202 9.766679 9.321723 8.8813 8.354034 8.528647 9.770875 10.59045 9.79849 10.28119 10.74567 10.74199 12.01325 + 11.32263 15.05788 15.25787 15.88041 15.08022 14.13194 12.75217 11.34698 9.656963 9.717936 9.331264 8.585823 9.124638 8.87212 8.442982 8.890512 9.943332 10.48704 10.23122 10.30991 10.68609 11.49976 11.52623 + 13.40004 15.62923 17.28662 19.34645 17.7522 14.88166 15.30356 13.29276 11.17599 10.1756 8.496209 8.687905 8.767036 7.138587 7.905816 9.155358 9.870092 11.34072 10.64145 10.56284 10.93426 11.27267 11.83103 + 14.40832 16.92628 18.28235 19.92572 17.42484 16.51931 17.17439 13.85876 11.32172 10.88191 9.843261 8.557543 8.702249 8.43864 8.905317 9.214272 10.79284 11.19663 10.4797 10.33966 11.11496 11.59549 12.62567 + 15.07293 17.23655 18.7453 20.03891 18.20394 17.32358 16.8287 15.43667 13.0998 12.50691 10.92344 10.46035 9.055417 9.849059 9.764614 11.06528 13.47688 13.99895 12.79779 12.12013 14.56175 14.29823 14.93593 + 15.40606 17.37957 18.26671 19.24368 19.16639 17.99058 16.59817 14.71303 12.86606 12.04343 11.7385 9.705905 9.335643 9.112566 9.626029 10.41576 13.39695 13.71364 12.28225 11.58019 13.76471 13.65422 14.2695 + 15.64262 17.64327 17.19793 17.43849 19.78349 18.46711 16.14888 16.3467 13.90007 12.84516 12.72527 10.46768 9.428706 9.794108 10.62751 11.56921 13.06464 12.97483 12.06074 12.42412 15.20449 15.17994 15.41442 + 15.52258 17.33565 17.81485 18.67915 20.30933 18.47157 16.206 16.37829 16.18498 14.41841 13.32267 11.18768 9.268024 10.2243 10.31914 12.25662 14.13956 13.31647 12.61077 13.04033 15.24885 15.47131 15.91226 + 14.72044 16.24851 17.39948 18.02982 19.91796 18.15676 16.86558 16.89674 18.34361 15.89882 13.7806 11.83443 10.31598 9.947871 10.88693 13.04681 14.78731 14.09249 13.07614 13.30486 14.96555 15.89365 16.21339 + 12.94438 14.262 17.79223 17.94734 18.41548 17.99884 16.59502 17.46818 19.89834 17.3267 14.68132 12.74071 10.74997 11.11081 11.97623 14.37372 15.06054 13.62777 12.74747 14.17785 13.22865 15.07573 15.67369 + 11.9413 14.24815 17.19899 17.78853 19.11386 17.95886 17.36403 17.78174 19.56396 18.41936 15.83994 13.5232 11.30285 11.13494 12.42168 14.83165 15.21284 13.93472 14.22501 15.48852 14.69647 14.61711 15.37134 + 13.80032 15.34109 17.01766 17.4297 17.96654 17.89771 17.45484 16.8976 18.80153 19.64696 17.77114 13.78843 12.49898 11.85447 13.19477 15.72658 16.27253 14.45719 14.41123 16.36125 15.85711 15.05885 15.80895 + 14.42734 15.95719 16.8165 17.40023 17.72437 18.32386 18.0716 16.22936 18.2776 19.88119 18.43486 14.89068 13.09634 12.62326 14.15393 16.27906 16.78207 14.59756 14.30995 16.8557 16.74701 13.89337 15.01136 + 13.1981 15.42621 17.47243 17.46376 16.60345 17.87163 18.14639 15.80742 17.8999 18.86058 18.78569 15.2696 12.82393 12.26846 14.426 16.64136 16.84205 14.60646 14.58007 16.73913 16.8489 13.78548 15.02061 + 14.53758 16.3012 17.77195 17.30224 18.26668 17.24618 16.88194 15.98161 16.4734 18.86712 18.13484 16.05925 12.78708 12.62607 14.48131 16.29807 16.06342 14.38513 14.33487 16.47281 16.5306 13.28973 14.15116 + 13.29329 15.40082 17.00095 16.67248 17.51397 17.87264 17.22511 15.87745 16.17892 17.66627 18.2972 16.80437 14.20589 12.99962 14.83743 16.62644 15.83631 14.91555 14.99209 17.03086 17.57478 15.26948 15.94518 + 12.60118 14.90419 15.67589 16.70849 16.94395 16.61985 16.03358 15.05487 15.99355 17.37237 17.7354 17.29265 14.00175 12.25772 14.06873 15.4484 15.89154 14.37778 13.64605 15.54983 15.959 14.05933 14.62753 + 13.46687 15.90945 16.95177 16.9344 16.38556 16.47206 16.12689 15.33399 16.03906 17.06773 16.99802 17.40662 13.44633 12.45146 13.97935 15.02217 15.57034 14.04656 14.18954 15.08655 15.59686 13.81427 14.97765 + 13.90775 15.34541 16.23319 17.18387 17.43214 17.35052 16.137 15.33103 15.5793 15.9622 16.35075 16.71717 13.52683 12.29098 13.29745 15.11607 15.76651 13.92764 14.35643 15.45294 15.56918 14.29957 14.75815 + 12.64945 15.05234 16.66335 17.67402 17.42719 17.03349 15.29472 13.20675 14.79218 16.15494 16.06722 15.88496 14.37258 12.09099 13.10366 14.55416 15.20259 14.15096 14.36049 15.3579 14.84148 14.77236 15.21402 + 13.63886 15.13448 17.12833 17.25077 17.57319 16.51949 14.79358 14.33071 15.40003 15.31295 14.707 15.51841 15.19862 12.16728 12.38076 13.97288 15.01122 13.84911 14.36896 15.99493 15.19653 15.71203 15.79622 + 14.08951 15.27791 16.67466 17.38719 17.48137 15.49804 14.64399 13.99431 14.06412 14.96719 14.45313 15.07616 15.22869 11.85608 11.80214 13.4829 14.47193 13.28767 13.82474 15.54498 14.68076 15.78218 15.55037 + 13.55869 15.54187 15.30116 16.07854 15.19114 14.89549 14.28598 13.40435 12.58551 12.71723 13.51417 14.44852 13.74717 11.34321 11.73631 12.8637 13.46484 12.86962 13.4271 14.9927 16.06477 17.438 17.00769 + 14.03767 15.96629 15.7353 15.65322 14.90481 14.81491 13.83562 13.2442 12.39499 12.31491 12.90394 14.30139 13.67956 10.38161 11.00745 11.82061 12.68529 12.26423 13.03794 13.27276 13.62125 14.79159 14.69412 + 14.69835 16.23426 15.85394 14.956 15.55784 15.1201 13.12001 11.77338 12.35275 13.03831 12.70205 13.89809 13.49535 10.74578 10.39047 11.01446 12.48515 11.93507 12.14398 12.33028 12.54093 12.23906 11.828 + 13.74119 15.01042 14.63525 14.21064 15.04048 14.53166 13.30235 11.51442 11.54563 12.75932 12.87387 12.97661 12.22388 9.737226 8.855085 10.95898 12.41875 11.5811 11.65182 12.16752 11.78344 11.6825 11.58462 + 13.79247 14.65056 14.39971 14.56042 14.50872 14.48325 12.4725 11.32357 11.69767 11.76651 11.72759 11.75854 11.63687 9.03346 8.632442 10.05544 11.80099 11.283 11.6399 11.7433 11.51521 11.30795 11.44739 + 12.4817 13.96644 14.94901 14.22443 14.7217 14.6918 12.00653 11.63244 11.49402 12.39865 11.62017 11.95335 10.76735 9.714913 9.972126 10.44865 11.5843 10.77267 11.39896 11.29488 11.80279 12.45774 12.97729 + 10.99601 12.48925 13.91558 13.39563 12.76022 12.97499 10.67527 11.2872 11.05415 11.66541 11.35227 12.07333 10.91728 8.470509 9.390297 9.98071 10.65596 10.24184 11.05679 11.06191 11.15756 11.02771 10.34172 + 11.55889 13.16734 13.01277 13.71551 11.48029 11.84606 12.16008 11.11222 11.96976 12.21278 12.98672 12.49252 11.65213 12.38936 13.03628 13.42355 12.62503 11.73549 11.11625 12.92801 14.35214 14.86793 15.02906 + 10.42775 12.77446 12.08269 12.53821 11.88333 11.98844 12.13725 11.12525 12.48612 13.17795 13.38511 12.48855 11.81891 12.69561 13.0315 13.13174 12.79269 11.96432 12.52114 14.89753 16.66616 17.80948 17.48282 + 11.87147 13.35178 12.29651 13.07613 13.38078 12.15471 11.58152 10.57638 11.48294 11.66988 10.83331 10.95618 10.80718 12.60706 13.70898 13.6584 13.12783 12.36247 13.81388 16.36983 18.19867 19.32249 19.17922 + 11.74805 13.03258 11.83647 10.99839 11.53884 11.58733 9.78068 10.00441 11.77152 12.2512 12.23087 13.11207 12.52222 13.45929 14.83094 14.73086 14.22945 13.92837 15.33029 17.7077 19.34477 20.7841 21.06674 + 12.91207 13.92127 12.60293 11.2707 11.34953 11.66406 10.61612 10.41087 11.90737 12.03745 12.62049 12.73641 12.31181 15.63897 15.29306 15.0111 14.84808 15.03202 16.69465 18.62388 20.75715 22.16574 21.82943 + 11.44404 13.17524 11.91981 11.4003 11.81657 10.4973 11.08138 10.91362 11.54249 11.68209 11.96895 12.71395 12.69716 15.29071 16.25213 15.87629 15.68893 14.98099 17.12889 18.67869 20.67893 22.07556 22.39026 + 12.4943 14.38828 15.40293 14.53412 12.63508 10.61958 11.04276 10.82849 11.58219 11.86627 13.09368 11.76773 11.85476 15.56555 15.54773 14.94056 15.46294 14.71608 16.38725 18.32511 19.88208 20.835 20.72138 + 12.06502 16.88607 18.26798 18.51961 18.70255 16.0556 11.92916 12.43737 13.55841 13.30871 12.92406 12.8846 12.13316 15.64249 15.85884 15.73912 15.52897 14.45106 15.96146 17.69323 18.59188 19.02813 19.20515 + 11.12339 17.63247 18.67913 18.09006 20.21992 17.90995 12.6137 12.48078 12.48313 12.308 12.5629 13.46077 13.18955 15.1113 15.49067 16.01841 16.40285 15.20319 16.30907 17.38044 16.9833 16.74973 17.5288 + 11.91933 17.71219 18.231 18.7819 20.81494 18.03944 12.80576 12.62215 13.70358 14.11044 12.98072 13.73609 12.88998 16.17216 15.98503 15.40534 16.2627 14.74855 14.94189 16.8815 16.57269 16.14612 17.22886 + 10.80071 17.88858 18.37441 19.57571 20.85023 16.92813 14.54551 14.11315 14.60858 14.76622 14.24647 13.50114 13.44903 15.14018 15.77174 16.55495 16.70045 14.63158 14.95225 16.897 16.51554 16.39054 16.95824 + 10.11735 17.24759 17.42141 19.76315 20.66429 16.31513 15.35933 15.46266 14.2526 14.75529 14.8134 13.60172 13.60854 15.0717 15.27205 15.8339 16.31969 14.84996 15.4993 17.3387 16.82842 15.77378 16.33088 + 11.58894 15.94406 15.80265 19.82332 19.81112 16.33015 15.83328 16.12075 14.11878 13.67492 14.12749 13.19047 13.91129 14.98374 14.91681 15.74825 16.61319 15.2014 15.57807 17.29531 16.63654 15.11069 16.41315 + 12.11817 16.37547 16.11933 19.74942 19.52616 15.253 15.60251 15.62886 13.73787 12.94972 14.53512 13.22932 13.03554 14.42677 14.02384 14.75253 15.97637 14.65869 15.00835 17.03261 16.1345 14.50001 16.3125 + 12.40414 17.09673 16.56095 19.30345 18.83337 15.53385 15.46334 15.14222 13.0195 13.65549 14.05797 13.27337 13.1787 13.58766 13.74914 13.84215 14.70384 13.81669 14.36711 16.36557 16.29565 15.23389 17.42854 + 13.11347 16.62823 16.08442 18.65703 19.09037 15.8935 15.2197 14.41059 14.00731 14.22315 14.2603 13.98392 12.74996 12.83496 12.62025 12.43474 14.70534 14.04083 13.31598 15.78194 16.0346 15.06308 16.98965 + 13.64474 17.90531 17.46792 19.65909 19.84337 16.36866 15.0947 14.08054 14.56519 14.61388 14.04171 13.45949 11.97165 12.38503 12.46181 12.36982 13.73913 13.27664 12.86066 14.10406 13.75239 13.30816 15.46512 + 14.14659 17.85538 16.63506 17.69785 17.37612 14.4621 14.21311 14.33 14.55272 14.0266 13.34313 12.6474 11.67851 12.07374 11.39458 12.00585 13.00983 13.07051 12.53945 13.83851 13.02929 13.78781 15.03182 + 14.42228 17.95903 16.79436 17.2867 17.37424 14.4423 12.56357 13.39425 14.97358 14.38745 12.16811 12.31725 11.24478 11.62687 11.66598 11.82592 13.17022 13.03793 12.44336 12.93122 12.50479 13.24247 14.70499 + 14.74926 17.10213 16.10866 18.41386 17.76688 14.66671 12.0519 12.36404 14.64657 13.54079 11.52991 11.67943 10.9411 11.91207 12.82024 12.68861 12.79371 12.74705 12.14265 12.87091 12.87852 13.3739 14.64138 + 14.77377 17.52547 16.45933 18.03106 17.99345 14.37569 12.28482 12.38711 13.49167 12.96972 12.2464 11.9446 12.06746 13.32547 13.42399 12.804 13.77554 13.8069 12.38337 13.585 13.03204 13.04344 13.17559 + 14.33936 16.05802 15.20399 17.29784 16.82894 14.67938 13.40503 14.50885 16.62795 16.48669 15.85748 14.00688 13.75563 13.0535 13.17885 13.77508 14.15859 14.48487 13.08349 14.30881 13.77487 13.4736 14.49444 + 14.44479 16.99548 15.77001 17.10963 16.52903 14.38944 13.44239 14.06731 16.73449 17.81567 16.47066 14.45036 13.36218 11.99206 12.16488 12.52671 13.19155 13.33347 12.81071 14.34716 14.2478 14.17321 15.78081 + 14.02579 17.08305 15.59479 15.89314 16.72221 14.24861 13.47705 14.10365 17.43085 18.94127 17.04 14.55132 12.30284 11.67238 12.13895 11.74947 12.48997 12.62799 12.61127 14.23425 14.47474 14.48953 16.6846 + 12.98601 16.43487 15.30097 15.82755 15.67639 13.93307 12.06077 14.01533 17.31017 17.03305 16.06872 14.63635 13.17454 12.51504 12.19607 11.30268 12.40507 12.31048 13.08197 14.41822 14.24439 14.75511 16.61187 + 12.36717 14.63769 12.81204 14.45165 13.75739 14.0802 13.48325 14.81929 17.03195 17.97369 16.36871 14.08131 13.45929 13.48154 13.22458 13.06752 12.79095 12.71402 13.04852 14.30993 14.63789 14.94942 16.90777 + 11.93372 14.51598 12.94251 14.97276 14.5037 13.14109 12.6004 15.28386 17.44633 17.41194 15.19083 13.97468 12.30335 11.49983 12.01657 12.22904 12.7571 12.17981 13.11903 15.56299 14.76678 16.0535 16.8217 + 12.47566 14.19673 11.79418 12.79603 13.27397 13.03157 14.51644 15.58098 16.45773 16.79338 14.77104 13.59812 12.80408 11.51495 12.39793 12.11381 12.55917 12.43729 13.31075 15.56109 14.98054 15.78551 17.06624 + 12.2613 15.1282 13.99666 13.71514 14.27644 13.59662 14.62273 14.96381 16.1037 15.93762 14.79321 13.20536 12.09543 11.00414 11.58279 11.8383 12.9191 12.51326 13.47569 16.26331 15.60605 16.11314 16.97496 + 12.43962 14.93872 12.93799 13.37484 14.13616 13.94297 13.31241 15.01747 17.49583 16.37276 14.13916 12.80372 11.91654 10.96919 11.34828 11.55524 13.11632 12.65655 13.44765 15.82003 15.211 15.98718 16.41879 + 12.20704 14.56072 12.76487 14.66424 14.36893 13.81488 12.67005 14.58511 15.8139 15.15328 13.72443 12.0736 11.75713 10.39046 10.3162 11.07518 13.42291 12.18517 12.6754 14.56121 13.61762 14.73406 15.99769 + 12.26719 14.83184 13.45157 13.9031 13.37387 13.42337 13.16261 14.01834 13.45282 14.0881 12.42882 10.71366 10.67623 9.888527 9.556181 10.42893 12.93971 12.08191 12.30767 14.6603 13.51334 14.00465 15.0682 + 13.72396 15.37463 14.90107 14.14883 14.71334 14.23447 14.62967 15.41844 13.9625 13.62733 12.02162 10.61794 9.693156 9.38163 9.607562 10.24143 12.10383 11.62002 11.61677 13.37127 12.76904 12.98495 13.14606 + 12.20455 15.08801 16.28204 15.02526 14.91302 14.54155 16.05799 15.24317 13.17851 13.23946 11.95253 10.98236 9.365788 9.214337 9.505456 10.4926 10.92743 11.19956 12.08282 12.89608 12.90349 12.21022 12.78775 + 13.37813 14.82368 16.03482 15.19005 16.00402 15.02282 15.84676 15.76265 13.34728 12.43027 11.99226 11.34601 9.200621 9.239662 9.540243 11.00728 12.16874 11.82533 11.95636 13.50077 12.4501 12.26641 12.92042 + 12.66223 14.38916 15.40475 14.94944 16.37963 15.2842 15.38037 15.8695 13.61753 13.21642 11.7801 11.01452 9.542067 9.19785 9.696535 10.70263 12.5226 12.46992 12.17822 13.79243 13.24585 13.34737 13.80106 + 11.79797 13.75565 14.85456 15.8272 16.72862 15.18555 15.07867 15.58954 14.36672 13.03338 12.42223 11.01965 9.557907 9.226222 9.848079 10.37486 11.38179 11.42391 12.02948 13.78323 12.73048 13.11424 13.94043 + 12.11479 14.20692 15.95607 15.77075 15.94264 15.04848 14.68504 15.58499 16.73163 14.876 13.04782 11.34189 9.554831 9.312258 9.405445 10.54039 12.08341 11.93015 12.28186 14.35123 13.82728 13.614 15.07887 + 11.63898 13.24526 15.91903 15.04351 15.44301 14.33202 14.69388 15.04394 16.66434 15.8622 13.70809 11.39564 9.984374 9.255429 9.771338 10.38831 12.17067 12.11052 12.20296 14.49279 14.43369 13.75218 14.74203 + 12.24345 14.6031 16.65611 14.82507 16.38684 15.87055 14.16241 14.53406 16.72972 16.78175 14.29714 11.9692 10.40499 9.319419 10.12237 10.74756 13.3062 12.89304 12.51923 15.03736 15.23477 14.65409 15.5749 + 12.77314 15.07554 16.98285 15.57631 16.88236 15.60186 13.31313 14.22181 15.56985 16.22102 14.96699 12.77368 10.60659 9.910019 11.08181 11.14588 13.15701 12.80074 12.45314 14.14474 14.5937 14.61545 14.75511 + 11.81499 14.35236 15.86184 15.37682 15.91093 14.51173 12.01213 12.23065 14.53028 15.26377 14.16712 12.59848 10.46429 9.104154 9.218637 9.824287 12.90368 12.61128 11.938 13.29911 13.48286 12.61785 12.90715 + 13.13115 13.74104 13.82935 14.62105 13.24596 12.30486 11.74188 12.57558 13.35646 14.14618 12.51182 9.92897 9.892784 8.203323 8.843456 9.73543 11.10688 11.40638 11.2225 11.48934 11.82256 11.33256 11.55696 + 13.5014 14.46064 13.74532 13.76357 14.51184 13.47387 12.36466 13.09748 12.37077 13.78026 12.56234 10.40441 9.652602 9.258573 9.640882 10.48315 11.37579 11.28799 11.69723 12.14306 11.93345 12.913 12.47232 + 12.11707 14.41063 14.2667 13.93633 13.81357 11.92622 11.39311 12.56514 11.91435 12.2172 11.83334 10.78043 8.693699 9.34746 10.5232 10.74675 11.27402 11.17691 11.69285 11.59679 11.87287 12.48645 13.45397 + 12.73287 14.28872 14.12864 14.03616 13.14685 12.7475 12.18416 12.10903 11.98855 12.79712 14.14206 13.79637 12.14143 12.49956 13.05192 13.06696 12.86376 11.90433 12.45795 13.79512 14.72743 15.6082 16.03671 + 13.99284 14.59781 15.86497 15.46929 15.35721 14.4506 13.86872 13.70982 12.77764 13.87713 16.70483 16.12365 13.74394 14.14882 14.72046 14.79044 14.55702 13.26187 13.4715 15.52675 16.2828 17.03688 16.70895 + 13.34487 14.76827 16.66223 16.6201 16.95358 16.41091 14.59964 14.13615 14.98613 15.52808 16.94203 16.33436 13.65864 12.67898 12.89719 13.15985 13.87055 13.7345 13.81801 15.46476 15.5521 15.17291 16.18393 + 11.05816 13.38546 16.02215 16.0239 16.26703 16.0995 16.00097 15.06918 15.39436 16.33123 17.5566 17.69474 14.73381 12.76004 13.46956 13.62351 14.9516 14.46614 13.86967 15.92621 15.77614 15.14017 16.05052 + 13.44674 14.49088 16.24643 15.87667 15.80228 16.08157 16.27578 16.11953 15.71647 16.77408 17.38955 17.5013 14.82175 12.91548 12.99751 13.53425 14.71743 14.50213 14.5002 15.80028 15.50727 14.7684 16.64701 + 14.21669 14.68321 15.45795 15.69659 15.90129 16.00284 16.18194 16.61241 15.75285 16.67905 17.6036 17.33829 14.63122 13.52661 13.50696 13.30114 14.47372 14.06641 14.2557 16.1715 16.02145 14.45178 16.56786 + 13.89403 13.90886 13.8904 15.63989 16.239 16.28278 16.61728 16.93481 16.17262 16.97161 18.37757 18.09198 15.23442 13.31642 13.7317 13.67162 14.61277 14.06051 14.15388 16.12131 16.52321 14.41898 16.15249 + 13.62738 14.29071 14.26743 15.39755 16.17412 15.93856 16.81614 17.35265 17.03794 17.52091 19.20687 18.71931 15.41284 13.25959 14.02276 14.49864 15.36796 14.59919 14.92057 16.34993 16.91476 14.57673 16.4135 + 13.91304 14.38333 15.62968 15.92608 15.88913 15.46424 17.23949 17.6737 17.75764 18.32361 19.38836 18.81548 14.75313 13.59059 14.47715 15.14762 15.94664 14.38755 14.47487 16.32241 16.61687 13.88076 16.03177 + 13.85443 14.63751 16.63829 16.22158 16.00957 14.22576 17.54068 17.94837 17.70362 18.35151 19.07424 18.78005 15.38741 13.65261 14.89409 15.66142 16.46598 15.04117 14.94388 16.69312 17.13902 14.49912 16.41131 + 14.16736 15.20732 17.17613 15.65355 14.81104 14.93489 17.35307 17.69239 17.67073 17.9412 18.96013 18.70328 15.56409 14.20707 14.99696 15.9745 17.04941 15.52152 15.42804 16.55275 17.19493 14.82115 16.61891 + 14.55787 15.56332 17.40187 15.79714 15.16877 15.23872 17.43352 17.88713 17.42919 17.76297 19.16017 18.89678 16.21667 14.49569 15.32538 16.22569 17.27599 15.67997 14.60294 16.08368 16.37376 14.28088 16.07994 + 14.28819 15.29579 16.9726 15.32225 14.96721 14.86766 17.3646 17.55653 16.88566 16.39165 17.97582 17.84254 15.61118 13.325 13.99773 15.29101 16.41687 14.61869 14.14026 15.54824 15.78234 13.5513 15.05351 + 12.89534 14.19264 16.16715 15.45553 14.97155 14.18898 16.54575 16.86909 17.18435 16.91364 17.95696 17.44936 14.9484 14.08921 14.70956 16.02505 16.75773 14.69309 14.29639 15.83831 15.93069 13.20353 15.10151 + 11.07287 14.01731 15.98684 15.85503 15.7026 14.17124 15.66209 16.81634 16.69689 17.06315 17.20102 16.85413 14.60714 13.95707 14.35706 15.67642 16.10085 14.48048 13.75637 15.7046 15.97279 12.9786 14.57301 + 13.20215 14.88027 16.1391 15.30331 14.56342 14.65112 16.31849 16.70032 16.30379 16.66877 17.15785 17.35987 14.50481 13.62881 13.80476 16.18898 16.30147 13.83495 13.66053 14.89267 14.77247 12.55925 14.45503 + 13.06126 13.95905 14.72861 15.52299 15.57437 14.80034 15.9755 16.06871 15.0617 16.14234 17.04153 17.21765 14.65441 13.15693 13.40964 15.61284 16.12287 13.8438 13.76604 15.52066 15.88898 13.41924 15.27078 + 12.30977 13.65725 15.62276 15.12077 15.0009 14.32717 15.56222 15.53802 15.59415 16.0091 15.70288 16.7213 14.29013 13.25526 13.82748 14.57323 15.08817 13.3787 13.22771 15.28969 15.54399 12.92044 15.89454 + 13.2355 12.87303 14.13755 14.8824 14.98317 14.09578 15.47559 14.69907 14.95037 15.00035 15.11841 15.08056 14.14125 13.50607 13.85954 13.90839 15.27832 13.46804 13.2138 15.63453 15.70406 13.72386 16.19327 + 14.09465 13.80971 14.12421 15.16251 15.51726 14.75089 15.25305 14.35832 14.27529 14.6144 14.28123 15.06938 13.85847 13.27495 13.16495 13.6391 14.85534 13.8216 13.19121 14.84105 14.6136 13.33247 14.90938 + 14.17169 14.05926 14.38466 15.14356 15.18883 14.75758 14.63411 13.4688 13.57265 14.68271 14.24092 14.8144 14.59175 13.15273 12.52619 13.557 14.39644 13.34105 12.86075 15.03354 14.76084 13.2114 14.36096 + 12.94696 14.0161 14.38512 14.93382 15.28973 13.96826 13.00536 14.16216 12.7634 13.37025 13.69937 14.27973 14.28271 12.20191 11.58065 13.0691 13.86918 12.99897 12.07017 12.88836 13.36119 13.8559 13.72427 + 14.00163 14.74522 13.96561 13.84176 13.14617 12.28493 13.21515 13.64575 12.21422 12.96211 13.65326 13.52429 13.38388 11.56683 10.93623 12.6721 12.94885 11.75394 11.93754 12.60298 12.66667 13.39652 13.23497 + 14.01837 14.65643 14.34931 12.89079 12.44645 12.47288 12.34212 12.92817 12.88704 12.13856 13.52651 14.26985 13.20721 11.13747 10.50563 12.04486 13.04915 12.82268 12.21847 14.11812 14.85144 15.37979 15.23997 + 13.68709 14.73938 13.74758 13.45861 13.16367 13.00546 12.78111 12.54126 11.39514 11.72749 12.81818 13.32782 12.47606 10.72879 11.57598 13.16603 12.89361 13.47785 12.73763 14.78968 15.50553 18.04643 17.43831 + 12.58177 13.59478 14.05649 13.31381 13.11898 12.32821 13.0021 12.62218 11.5436 12.16554 12.48717 11.81394 10.50265 8.973746 10.09016 12.06378 12.39155 12.9348 12.42843 14.2313 14.86099 18.31309 17.32769 + 12.75411 12.6887 12.49659 12.86026 12.59906 11.34449 12.41792 12.40671 10.25577 10.55363 11.62938 11.7192 11.39313 9.014958 9.833184 11.20295 11.54293 12.10884 12.57974 14.2528 15.04182 16.35394 17.43106 + 13.07687 12.93932 12.05013 11.10465 10.92198 10.16203 11.9019 12.00359 11.20652 11.09058 11.98129 10.94307 10.32444 9.561 10.09412 11.1652 11.89071 12.46157 13.37886 14.81377 15.79583 16.46983 16.3099 + 12.44068 12.16542 12.749 11.51701 11.75818 10.98072 11.45177 11.79877 11.41194 10.86352 11.39708 11.80494 10.21916 10.17022 10.94074 12.73651 13.22818 13.90771 14.318 16.45604 17.52512 18.12885 17.99037 + 12.84129 12.74346 11.62644 11.11886 11.60124 9.904121 10.04816 10.7376 10.31966 11.05361 11.45463 12.15746 11.15053 10.69711 11.84554 13.98456 14.60289 15.31413 15.49967 16.84238 18.05069 19.01266 18.84834 + 12.53398 11.96433 9.244778 8.572387 10.26614 9.39289 10.5042 10.72253 10.65647 12.75043 13.54582 13.20332 12.30947 11.8775 13.39289 15.73803 16.03198 17.65893 17.08116 18.5041 19.5771 20.24899 20.35165 + 12.00563 12.20351 10.276 9.838881 10.18145 9.774684 9.775256 10.85217 11.57201 12.9943 13.72266 13.57533 12.36394 12.41757 14.64912 16.40559 16.40833 17.94178 17.0117 19.56192 20.42689 21.10626 20.54123 + 11.91491 12.22656 10.11695 11.68905 13.45684 12.16579 11.17595 11.02882 13.19395 13.89767 15.23678 15.18915 13.55568 14.68092 15.77575 16.75733 17.13061 17.34155 17.21397 19.74595 20.57611 22.05666 21.36359 + 11.47355 11.28028 10.45217 11.66897 13.02617 12.40421 12.3838 12.38136 13.96514 15.68431 16.50488 16.14618 15.39929 15.91039 16.42896 17.51905 18.45585 18.59583 17.75705 19.62965 20.81224 22.27122 21.80147 + 12.52662 11.94858 10.55505 12.42413 13.77102 13.32065 12.9342 13.66854 14.39565 16.63391 17.94618 17.4248 17.46566 16.57822 16.48766 17.86062 18.27674 18.54208 17.23242 19.31924 20.2264 21.92178 21.3364 + 12.02873 11.26655 9.571183 11.58556 13.19479 12.65055 12.59585 14.40476 14.95008 16.4713 18.34317 18.11756 17.86362 16.10263 16.13897 16.93494 18.05787 17.58092 17.09885 18.80086 19.4799 20.65734 19.97151 + 10.86476 9.108546 9.552567 11.91356 12.38227 12.85921 11.95166 14.44887 16.19041 17.26532 17.9296 17.84677 17.18984 16.35882 15.73214 17.40462 17.33592 16.97078 16.5151 16.88156 17.47563 18.54997 17.5454 + 12.60442 11.37389 10.2908 11.55008 13.28062 14.39302 13.83146 14.80365 16.86215 17.7065 17.82295 16.90414 16.20468 15.93856 16.19014 16.84039 17.83888 16.61141 15.98524 16.43695 16.56059 17.5152 17.1277 + 12.41213 11.59318 10.9033 11.81183 13.5534 12.9817 12.88539 14.86807 16.76102 18.08833 17.84195 17.58156 15.95464 15.7454 16.94265 17.86619 18.44193 16.60484 16.43938 17.78119 16.30627 18.12034 18.17815 + 13.1731 10.92382 11.44889 11.58021 12.51424 12.61171 11.91807 14.35635 16.45222 18.2344 18.02634 18.44849 16.43503 15.31518 16.52777 16.50167 17.21136 16.07273 16.42845 17.90891 16.48678 18.03056 17.71963 + 12.32748 11.78882 9.929072 11.31927 12.64543 11.5685 10.86378 14.32529 17.28631 17.74572 17.08748 16.90873 15.3218 14.87652 15.57612 15.72205 15.87119 15.72217 15.76832 16.80227 16.46172 17.64037 17.64002 + 12.19527 11.26126 10.91108 11.51357 11.88646 12.82152 12.50746 15.10742 17.22483 18.49776 18.38831 17.32627 15.58188 14.17714 15.13854 16.44323 16.22381 16.09768 16.18013 17.33267 16.986 16.6601 17.38706 + 12.38316 10.9315 10.43465 10.56535 12.23314 11.90101 12.91362 14.65029 16.51646 18.22181 17.65647 16.37604 14.34329 13.71848 14.16311 15.41569 16.0204 15.15731 15.41514 16.67949 17.00598 15.89455 16.97392 + 11.30577 10.28441 9.945932 11.39331 12.30649 11.96362 11.83533 14.81513 17.35564 18.33072 18.05184 15.06798 14.07956 13.20424 13.25025 14.4972 15.11539 14.80095 14.70875 16.30007 16.03745 14.93294 16.70602 + 11.03609 11.63719 10.21918 10.6167 10.88359 11.86974 11.941 14.54243 18.29357 19.38234 16.87861 14.88691 13.15115 12.30057 13.38792 13.84875 14.85546 14.83342 14.70834 15.97845 15.37833 14.04472 16.18783 + 13.34986 14.78248 13.79236 13.17295 12.78349 12.70567 12.45625 14.42082 17.43404 18.18635 15.59477 14.50266 12.80653 12.16918 12.35644 12.73297 13.97416 14.38635 13.72448 14.86592 14.04201 13.30526 16.08755 + 13.90898 15.20264 15.9369 14.62706 13.72583 13.84371 14.70597 16.07141 18.13611 18.26731 15.36589 14.14722 12.81018 11.74895 11.73215 12.83022 13.8598 14.30199 14.02788 15.60954 14.73503 13.21847 15.28059 + 12.65622 13.64794 16.40841 15.1159 12.92702 14.19901 14.66136 16.53231 18.85972 18.87691 15.47901 14.02315 13.04305 11.90804 12.02419 12.18788 13.65167 14.87863 14.52754 15.9854 15.23429 13.42042 14.61205 + 13.16586 14.09224 15.83176 14.90314 13.58085 14.20237 14.74656 16.52872 18.05439 17.30598 14.93832 14.26765 12.49032 11.86487 11.99516 12.17831 14.06437 14.65313 14.55127 15.61084 14.85131 13.26807 13.69388 + 13.4008 14.57262 15.16415 15.02138 13.99929 13.74122 14.89686 16.43497 18.12129 17.57994 14.38773 13.34021 11.77359 11.37652 10.77275 11.62928 13.2342 14.23693 14.04607 15.04497 14.55735 12.52344 13.15258 + 12.58941 13.8753 16.66035 16.01036 13.94624 12.86215 14.91205 15.55473 17.18862 16.32199 14.95177 13.18178 11.81485 11.68483 12.10907 10.99695 12.64363 13.92735 13.98247 14.74136 14.57911 14.06635 14.69018 + 12.09153 13.84327 16.73779 16.22157 14.56728 12.37056 15.10593 15.10421 16.23268 15.88102 14.39125 12.42256 11.36509 10.85059 10.98364 10.62551 11.97378 13.79792 13.98639 13.31582 13.83742 14.01882 14.74063 + 14.35417 15.46001 15.97281 15.97872 15.05301 13.271 14.85252 14.8182 15.14593 14.99852 13.87889 12.85231 11.08199 10.78992 11.17402 10.49343 12.2052 13.27493 13.57413 13.50568 12.8814 12.89545 14.14766 + 15.11954 16.42943 16.03817 15.8695 14.94205 13.81331 14.11605 13.90077 13.72665 14.46259 13.50304 12.15606 10.48165 10.17465 10.4161 9.912415 11.29775 13.10989 13.52075 12.40094 12.22526 13.01442 14.24785 + 15.51706 16.90478 15.31721 15.63563 14.91287 12.8936 13.76248 14.32526 14.25805 14.36927 12.66967 12.0761 10.97497 9.48582 10.20874 10.5375 11.10218 12.73337 12.90427 12.4544 12.51494 12.39871 13.89166 + 15.83766 17.44094 15.59742 15.76098 14.73724 13.33175 13.09907 14.17982 13.79805 13.84968 12.17967 12.07957 11.02122 10.35668 10.95256 10.26702 11.17187 12.12873 12.05996 12.1097 11.7091 12.38269 13.66632 + 16.28121 17.97894 15.37001 15.30069 15.23669 13.93175 13.33285 13.58994 12.95194 12.94088 12.26932 12.17642 10.4537 10.94146 11.22272 10.16105 10.75595 11.85875 11.60317 11.8685 11.6532 12.81746 14.03694 + 16.65678 18.48001 16.02887 15.72611 14.88948 13.75212 13.00821 13.84919 13.9075 12.9391 12.38781 11.60378 10.67275 10.77736 11.13959 10.14937 11.14159 12.06991 11.99633 12.57936 11.69493 12.77214 13.83402 + 16.88027 18.81418 16.32965 15.89062 14.08585 12.9283 13.01355 14.09655 14.04627 13.70805 12.55457 12.14791 10.23291 10.64731 10.54774 10.34701 11.31384 11.85837 11.31565 12.61369 11.56602 12.69893 13.17958 + 16.86315 18.90851 16.74496 16.61073 13.71159 11.98781 12.37382 13.89134 13.772 14.01257 12.72022 12.62209 10.90394 10.19925 10.34741 10.43047 10.72284 11.89819 11.36431 12.45117 11.00326 12.7707 13.43279 + 16.55088 18.7428 16.48301 16.17187 14.55713 13.0492 13.09207 14.58359 13.44237 13.92513 13.25501 12.62462 10.7068 10.42912 10.70771 10.98847 11.20761 12.1386 10.86133 11.76409 10.89257 12.65522 13.48968 + 16.31133 18.53433 16.33694 15.77417 14.38188 12.95522 13.08407 14.4475 12.69577 13.27632 12.01401 11.38529 10.35604 10.744 10.78147 10.85441 10.54302 12.20752 11.1479 11.46902 11.70803 13.0295 13.62829 + 16.08484 18.22621 16.04434 15.1363 12.58238 11.89639 12.57116 13.53181 14.12426 14.20648 11.83826 11.15889 9.9625 10.32529 9.984441 10.63607 11.00616 12.5833 11.28041 11.6917 11.89614 12.90713 13.29698 + 15.68179 17.81505 16.00374 14.72812 14.84708 11.26076 12.04008 13.00523 14.13205 13.28368 11.21361 10.097 9.955743 10.26499 9.792438 9.863858 11.07145 12.3472 10.51815 10.99274 11.61275 13.19388 13.72785 + 14.55198 16.86638 15.5845 14.5592 14.73209 12.26241 10.81983 11.97698 13.21972 12.68782 10.91807 10.4126 9.59472 10.19615 9.584566 9.555467 10.64191 12.16234 10.07995 11.50145 12.38194 12.66189 12.71277 + 13.01004 15.59743 15.64693 14.08796 13.8486 11.90227 10.5667 10.79841 12.75414 11.89225 9.598926 9.206168 9.479238 8.989665 8.048623 9.163903 10.12297 11.2002 10.10107 10.59673 10.9262 11.29577 11.6629 + 13.91573 16.00849 15.0316 13.62253 12.95412 9.966305 10.1017 9.991467 9.596074 9.605503 9.343888 9.190509 8.865524 8.973734 8.919617 9.209815 10.08192 10.85753 10.00404 9.867455 10.36516 11.38496 11.67881 + 14.8278 16.76171 14.70668 13.6729 13.04084 9.941384 9.693533 10.51062 10.44051 10.19218 9.282916 9.032 8.986729 8.863693 9.409104 9.42415 9.590022 10.41179 9.549519 9.795614 10.16903 11.44894 11.5452 + 14.85072 16.98533 15.18039 13.29803 13.33594 9.623359 9.158119 10.2534 10.32673 9.353215 9.035355 9.315903 9.19397 9.060982 9.5213 9.576943 9.395948 9.738444 9.638402 9.812303 10.30602 10.88667 10.86766 + 14.6904 17.03413 15.28008 14.86307 14.88218 11.42855 9.846802 11.10628 10.92814 10.19661 10.62403 11.59551 10.50513 9.933274 11.18404 11.79196 12.68107 11.0639 9.99734 11.25623 10.75933 12.35911 12.92669 + 14.66271 16.80819 14.93047 15.11656 14.37493 12.24518 9.898335 10.41227 10.94259 9.562234 9.82956 10.52575 10.91687 11.3688 12.16531 13.12481 13.59379 11.50523 10.77534 12.16117 11.03448 13.25059 13.33833 + 14.15415 16.37178 13.99285 14.13001 14.35129 12.60274 10.18616 9.21813 9.342664 9.430008 10.54196 10.54733 10.69073 12.74719 13.28319 14.68884 15.19119 12.57943 11.37738 12.86609 11.79551 14.11093 13.75384 + 13.90892 16.035 14.79413 14.31352 13.37496 11.89549 10.30609 9.863048 9.564677 9.050714 9.766609 9.916749 9.736766 12.76469 13.29292 14.07608 15.34341 12.62214 11.65098 12.8247 11.93356 14.02104 13.58228 + 13.60849 15.51042 14.56466 14.77384 14.10931 12.00589 9.922771 10.00272 9.233529 8.90524 9.197906 9.287741 9.224826 11.10517 11.75283 12.47408 14.07385 11.81659 10.66715 11.48033 10.96836 11.95531 11.53177 + 12.63871 14.4074 14.43942 15.18537 14.02158 12.20864 10.17488 10.41652 8.884479 8.93423 10.05001 9.872001 10.05358 11.18607 11.13514 11.16448 12.71593 10.94455 10.9008 11.7045 11.48672 11.77495 11.38987 + 9.470507 13.9183 15.38312 15.29352 14.31409 12.62442 10.19246 9.844234 9.015466 8.645416 9.272799 8.95722 9.29404 9.842471 10.79908 11.95937 12.56295 10.62304 10.97254 11.55685 10.80731 10.67443 10.93567 + 9.689436 12.1227 15.04778 14.65507 12.59609 11.3594 10.29826 9.403631 8.485927 8.846539 8.928617 8.97279 8.096455 9.521686 11.13479 12.4645 12.32434 10.56936 10.17078 10.82339 10.85006 11.1649 11.38804 + 10.98928 14.05137 14.64129 16.00715 14.09278 11.13883 9.941337 9.854771 8.722589 9.290672 8.857082 8.991954 8.21919 9.559203 12.24082 13.63578 12.84687 11.02113 10.59677 11.14553 10.55739 11.74764 11.36405 + 12.63155 15.67096 14.68544 16.10676 14.76892 11.35606 9.204973 8.561191 8.504937 9.011736 8.932889 8.941728 8.647584 9.663374 12.39362 13.74842 12.88115 11.69389 10.94999 12.21108 11.19174 12.30093 11.63438 + 12.10139 15.86091 14.87627 15.61644 14.75041 11.31224 8.924519 8.241544 8.472549 8.927956 8.740888 9.051448 8.190333 9.251281 12.88241 13.90231 12.65679 11.89317 11.16821 12.41968 11.02865 12.32125 11.54047 + 11.38513 15.89259 15.55978 16.49071 15.93176 11.26818 9.120329 8.472344 9.292308 9.625569 9.129047 9.180802 8.588961 11.19132 14.33207 15.09559 13.56647 11.99036 11.61518 12.64687 10.69325 12.63932 11.8825 + 11.33489 16.01806 16.14634 17.10612 17.0976 11.94706 10.30982 9.953162 10.28553 10.87142 10.28722 9.505128 9.044598 12.70718 15.12737 15.38648 14.23596 12.67216 11.409 11.88481 10.85025 11.92107 11.80873 + 11.93121 16.69381 17.38387 17.6749 17.98742 13.28621 11.70719 10.43649 10.91934 11.77147 11.88422 11.13052 11.38345 13.80934 14.99402 15.98011 14.9559 12.99995 11.91326 12.93166 10.28887 13.55714 13.66834 + 11.91258 16.81928 16.99542 18.31518 19.14895 14.40946 11.01381 11.08671 11.39375 13.21682 13.19212 12.36498 12.30863 14.04659 13.71266 15.38462 14.6618 12.61307 12.04058 12.94022 10.91476 13.20159 13.73922 + 11.46789 17.38041 17.82477 18.2752 19.65061 15.74574 11.42097 11.61401 12.88716 14.96064 15.02348 12.79885 12.33007 13.6574 13.31771 14.41018 14.14105 12.70052 11.94269 13.00976 10.96539 14.06782 14.93854 + 10.82074 17.51527 17.82471 18.72139 20.24971 16.65067 13.19277 11.47689 13.75297 16.15981 15.89948 12.82615 11.78095 12.68962 13.03443 13.99312 14.44622 12.8687 12.09715 12.6253 11.04033 14.82887 15.41104 + 9.789128 17.60623 17.95166 19.12147 20.36291 16.45957 14.5175 11.95895 14.00584 15.58451 15.58284 13.09798 11.42349 12.38721 12.11636 14.10925 15.14098 13.66024 12.47174 12.31593 11.41416 15.35977 16.09952 + 10.03751 16.87819 17.14559 18.82435 19.69844 15.54911 14.925 13.47285 13.4415 14.02402 13.91976 12.55425 11.28404 11.29449 11.12666 12.90026 14.52994 14.08993 12.81467 12.58953 11.19166 14.11535 15.23785 + 10.93798 15.34559 15.48075 18.89332 19.13994 15.17251 14.3534 14.04348 13.22518 13.32802 12.83179 11.63252 10.8536 11.23855 11.16474 11.8378 13.7653 13.51799 12.46745 12.35123 10.79845 14.09694 15.23058 + 11.39907 15.94585 15.80858 18.99041 19.07972 14.78049 14.1376 14.13164 12.62071 13.95744 12.93874 11.53029 10.4269 11.58948 11.19517 11.35314 12.57921 12.77587 11.52973 10.72114 11.27394 14.35466 15.65102 + 12.50065 16.98081 16.2958 18.53826 18.03814 14.84136 14.63556 13.97874 13.23756 13.85454 13.35822 12.12458 10.75648 11.73495 10.99577 10.73339 12.26947 13.05336 11.80032 11.86604 11.52577 14.2759 16.10515 + 14.11917 17.50495 16.44348 18.44194 18.71619 15.23897 15.50647 14.00227 12.22151 13.66027 13.43431 11.87765 10.321 11.40836 10.86816 10.51086 12.05384 13.00603 11.8335 11.91356 11.91522 14.54532 16.75152 + 14.99379 17.92895 16.8975 18.70519 18.95995 15.73521 15.49823 12.87329 13.83215 13.62185 13.06669 12.05937 10.42773 10.73961 11.01464 10.88707 11.64818 12.58473 11.62818 11.58135 11.51648 14.55052 16.08885 + 15.54145 17.85107 16.98327 18.49837 17.73919 15.18074 14.75038 12.77507 14.06172 13.54413 12.30926 11.52931 10.36564 11.26507 11.33629 10.336 12.06124 12.62648 11.26266 11.69004 11.93004 14.61818 15.63297 + 16.0619 17.99921 16.89255 18.13565 17.83411 15.88005 14.93022 13.40796 14.59884 14.0794 12.40723 11.15436 10.18998 10.83631 11.137 10.82108 12.08101 13.16273 11.88929 11.08195 13.06093 14.60555 15.1582 + 16.41592 18.74591 17.24427 17.45222 17.14775 15.69397 14.63761 13.72616 14.99289 14.96222 12.8571 11.36845 10.90592 10.93947 10.72949 10.99991 12.06396 12.11599 11.73193 12.9482 13.76328 14.24961 14.51857 + 16.59169 18.33644 16.6729 17.92939 17.99472 15.11923 13.68678 12.95239 14.18037 14.98073 13.72894 12.48612 11.10576 11.19653 10.8321 11.57918 12.45367 12.50688 11.21697 13.96683 14.07828 13.39284 13.05171 + 16.37931 18.53983 17.11295 17.0422 17.68007 15.11545 13.4979 12.7253 12.83473 13.30683 14.44711 13.53261 11.2399 11.29604 10.66569 11.51261 12.52057 12.61519 11.1287 14.42613 14.21188 12.91073 13.62521 + 15.77711 16.81826 15.82284 16.26417 14.77699 13.92578 12.86666 12.48366 12.62949 12.08785 13.24057 12.46911 10.74334 10.64687 10.51021 10.52875 12.20348 12.29009 10.30058 13.55652 13.26817 13.30325 14.35986 + 15.82015 17.46225 15.50791 15.47983 15.53145 14.01977 13.48505 11.83372 11.23159 11.60364 11.24516 11.02851 9.994411 10.127 9.874244 9.952956 11.45186 11.57752 10.71201 11.31057 11.925 12.24183 13.13455 + 15.76488 17.33266 15.47503 15.67266 14.76409 13.55282 13.16781 10.86405 10.00527 10.30678 10.84636 10.38855 9.692888 10.62687 10.82284 10.76016 11.56705 11.6752 10.60772 11.25967 11.79259 12.14699 12.2975 + 15.79346 17.73031 15.68137 14.88974 13.31264 11.70068 10.75016 9.699461 11.59397 11.09019 10.58869 9.898376 8.929638 9.302467 9.637378 10.49889 11.22649 10.7973 10.0415 11.52228 10.99211 11.29036 11.60892 + 15.62056 17.43279 15.06023 14.50031 13.94001 11.11427 10.53508 10.24727 10.84226 10.2417 9.97341 9.786078 8.522814 8.529009 8.403295 8.864924 9.860689 10.12548 10.1109 10.64103 10.51032 10.91088 10.99742 + 14.92246 17.0135 15.04857 14.46992 14.52418 12.18941 11.24207 11.29555 12.0192 13.37018 13.99349 13.40953 12.65679 13.28639 14.09664 14.8326 14.6762 13.23665 11.35561 12.54946 14.12629 15.38962 14.67778 + 14.08548 16.16105 14.25179 13.72728 14.2951 12.56719 11.60147 10.79406 11.7847 13.46562 14.78914 15.1487 13.70664 13.32854 14.55988 16.36461 17.04605 15.91949 14.81793 16.99095 18.20212 19.37614 18.60077 + 13.56568 15.41561 13.65116 13.80278 12.35432 10.98944 10.19834 9.714845 10.88012 12.79503 15.31259 16.04332 15.07261 13.3139 16.37029 18.55497 19.72721 18.21986 16.88817 18.66231 19.42877 21.17449 21.34429 + 12.81052 14.82067 12.89596 12.89064 12.7278 9.884626 10.07492 10.31998 11.56146 13.61393 16.51963 16.62613 15.85953 13.70052 15.65685 20.09129 21.61243 19.28339 18.15581 19.5183 20.06223 21.13614 22.15731 + 12.4148 13.93226 11.91046 12.1541 12.28745 11.01894 10.5866 10.27591 12.31247 14.79986 17.28344 17.50844 16.46629 14.32873 15.63348 20.89158 21.659 18.84053 18.39215 19.79963 20.31273 21.56329 22.12888 + 12.91297 14.79987 12.71113 13.29154 13.44041 11.56215 10.49496 10.6033 12.03684 15.29325 17.49444 18.31451 16.88695 15.08641 17.40228 20.39707 21.17206 19.49123 17.8341 19.26415 20.69028 21.98025 21.82807 + 13.12175 14.64588 12.15094 11.35538 12.11017 10.95849 11.28366 11.61484 12.85101 16.09893 18.0477 18.62724 16.99242 15.1852 17.51782 19.87419 20.87705 19.48903 17.96088 19.17698 20.07501 21.74238 22.1084 + 13.00411 14.90964 12.85213 12.4642 12.19896 10.9285 11.4324 11.96293 13.98125 16.61734 18.89468 19.34385 16.26028 15.6907 16.13703 19.2197 20.6384 19.75793 18.03179 18.44206 20.21319 21.34424 21.91418 + 12.53915 14.56936 12.39721 13.01271 12.77172 11.97516 11.71099 11.24299 14.61106 16.86008 19.35301 19.43951 16.80698 15.8419 16.63168 19.33833 20.47225 19.18408 17.29919 18.15383 19.49329 20.5814 20.88459 + 12.15284 13.47757 10.60758 12.20281 13.0343 11.11187 10.97446 10.88111 14.17981 17.2116 19.27656 19.46944 16.21806 14.89002 15.36739 17.92939 19.41214 17.94695 16.58801 16.62714 18.35245 19.04712 19.02948 + 11.57865 13.99112 12.93595 11.18491 10.64367 10.56507 10.97282 12.65517 14.05695 15.92091 18.14322 18.23843 15.92175 14.87526 15.68978 16.53125 18.24236 17.28429 16.56872 15.94291 16.28687 17.53417 17.20242 + 11.28529 12.6209 11.65753 11.60241 11.58626 10.83581 10.27027 12.12041 14.267 15.48161 18.20242 17.39253 15.21907 14.75703 15.08057 16.2598 17.26633 17.07322 16.03636 15.15166 15.24807 16.5594 16.19338 + 10.91567 13.44223 11.93005 10.71006 11.31781 11.7807 10.22369 12.09495 13.98265 15.85598 16.988 16.82117 15.11764 13.72713 14.96652 15.93681 17.34565 16.43155 15.34467 14.63535 14.80754 16.85585 15.56012 + 11.37683 12.73687 10.39089 9.336987 9.831561 11.17227 10.51462 11.394 13.52753 17.06892 18.22289 16.58073 14.50473 13.4536 14.32212 15.69642 17.59365 16.24478 14.84533 14.05617 14.99095 16.48585 15.65335 + 11.15164 13.128 10.98207 9.10566 10.39393 10.70634 10.84463 11.06166 13.15317 16.16613 17.48376 16.13078 14.46833 12.60861 13.22436 15.39284 16.83496 15.80455 14.54673 13.42426 14.11045 16.32096 15.15128 + 11.15148 13.08558 11.24186 10.75418 11.27332 10.83193 10.16917 10.71663 12.71503 15.8612 16.86666 15.34324 13.83714 12.93292 12.91148 14.20864 16.26399 15.44005 14.26241 13.12186 13.5547 15.73553 15.21248 + 10.32435 11.80502 10.71903 10.12998 9.243139 10.32276 10.51204 9.885305 13.37798 15.24079 15.56827 14.59323 13.25549 12.08573 12.08292 13.83259 15.49082 14.31401 13.56349 12.58398 12.93209 15.15023 14.39397 + 9.55196 13.14198 12.09491 11.57915 11.7868 10.55393 9.688849 10.1536 12.66342 15.51936 15.60778 14.23114 13.59677 11.5554 12.35998 13.28365 14.88144 13.88197 13.16487 12.75849 12.51151 14.26958 14.14925 + 11.14226 15.27234 14.74488 14.24864 14.20611 12.25802 11.94632 12.10342 13.43625 15.97755 15.82204 14.68259 12.32215 10.68163 11.67134 13.07826 14.39713 13.78414 12.79398 12.32595 12.68034 13.86888 14.63672 + 11.82192 16.13304 15.37651 16.57404 16.01068 12.65684 12.84768 13.69928 14.84385 16.28752 15.21064 13.4215 11.93544 10.11553 10.96781 12.73654 13.93202 13.7597 12.7366 13.53805 13.78219 14.34586 15.65966 + 11.16597 15.60295 14.91919 17.65105 17.25035 13.94595 13.03636 13.82953 15.81298 16.99026 16.38386 13.44202 10.67358 10.54342 11.52007 12.54199 15.2073 14.85921 13.04932 13.6851 13.8579 15.4532 16.68387 + 10.51662 15.43471 14.99521 18.16862 17.72067 15.69889 14.12805 14.29307 16.52756 17.99657 16.64942 14.49547 11.61017 10.99339 11.64379 12.93506 15.5309 15.33103 13.2612 14.26317 14.55778 16.36224 17.4065 + 11.90673 16.09601 15.16525 18.22427 17.79523 16.65013 14.95471 14.57647 17.00873 18.48756 16.99238 14.55247 11.84007 11.13602 12.00641 12.06635 14.78719 14.60945 13.60264 14.37275 14.25254 15.76529 16.61269 + 11.77753 15.94984 15.05677 17.97174 17.373 17.41905 15.6169 14.73355 17.46013 18.81142 17.14954 15.49692 12.20508 11.44368 12.23865 12.73304 15.18181 14.84058 13.51544 14.6217 13.58304 15.94587 16.794 + 10.41407 15.72303 15.14866 17.70807 17.07916 17.71496 15.88798 14.64032 17.55777 18.87132 17.8488 15.62309 12.12702 11.63624 12.23076 13.35091 14.845 14.49619 13.87168 14.96588 14.34432 16.30333 17.20194 + 11.354 16.18204 15.44579 17.66172 16.96994 17.44448 15.66464 14.72198 17.39568 18.71137 18.22394 15.33202 11.91729 11.85658 12.49095 13.32615 14.09961 14.23158 14.58506 15.50219 16.31929 16.47316 17.44572 + 11.67818 16.44463 15.64772 17.72272 16.90357 16.48343 15.1468 14.66261 17.37626 18.67665 18.43763 16.17672 12.68573 12.12047 12.56644 13.53683 15.69811 15.11192 15.10006 15.99792 16.72847 15.30137 16.12518 + 11.81452 16.51302 15.8351 18.05065 17.12857 14.78708 14.79863 14.54577 17.14968 18.40389 18.33661 16.81234 12.45416 12.26822 13.33935 13.42922 16.02875 15.44887 14.45614 16.00752 17.02212 15.07159 15.84839 + 11.66775 16.09326 15.45326 18.56066 17.73905 16.56577 15.80819 14.98165 16.93577 18.54793 18.7565 16.84025 13.44262 11.95572 12.87634 13.43538 15.80291 15.6723 14.43189 16.38626 16.87126 15.30465 16.12741 + 11.18154 15.3121 14.98711 18.80246 18.00036 17.79539 16.50552 15.6597 16.54539 18.6693 18.88139 16.73647 13.84423 12.21505 12.68858 12.8891 14.96932 14.98377 14.47017 15.99248 16.69073 16.01393 17.04851 + 11.39253 14.84143 14.70809 18.75023 17.97756 18.1698 16.63218 15.90916 15.77523 18.6932 18.87343 16.30272 14.07881 12.17943 12.72991 13.99461 15.09284 14.90023 14.86968 15.68508 16.95284 15.82605 17.11721 + 11.86377 15.31133 14.59444 18.38207 17.6422 17.60731 16.2254 15.42659 15.86426 17.45585 17.54583 14.96311 13.32989 12.32241 12.82136 13.80614 16.04843 15.70225 14.60214 15.79691 16.55345 14.48477 16.21475 + 11.73533 15.70459 14.84644 17.80892 16.99964 15.85337 15.77551 15.02913 15.56236 17.03756 17.23155 14.80542 13.2883 11.4158 12.61335 13.34564 15.46394 15.25104 14.61511 15.47291 15.86379 15.43297 16.33346 + 10.81638 15.52154 14.9054 17.31289 16.38354 15.1073 15.92739 15.73638 16.55172 18.13293 18.54304 15.54773 13.22721 12.31508 13.16266 14.46879 16.11985 15.48509 14.86392 14.97309 15.37611 15.07204 15.87638 + 10.26267 15.25113 14.67625 17.21563 16.31719 14.77018 14.92522 15.68053 17.32211 18.43587 17.91074 15.57454 13.49049 12.60371 13.12548 14.11297 15.84021 15.45053 14.63433 14.96083 15.49248 13.76896 15.98103 + 10.69819 14.90186 14.28961 17.07143 16.24204 13.46402 14.20674 15.63925 17.09035 18.04039 17.88895 15.37756 12.4007 12.11112 12.55157 12.96375 15.69617 15.59222 14.15425 15.03544 15.54571 13.44852 15.65409 + 9.915021 13.82648 13.51551 16.30905 15.34114 15.36788 15.25857 15.57581 16.88137 17.62993 16.70799 14.8589 12.48502 11.65991 12.48844 12.89608 15.70182 15.3114 14.08729 15.36026 15.73317 12.51652 15.22122 + 8.68051 12.45413 12.77622 15.34519 14.32071 14.90527 13.81296 14.36697 15.78108 17.00771 16.51527 13.94231 11.84495 11.75392 12.35656 13.10109 14.77411 14.72279 13.45488 14.19114 14.58668 12.4133 15.44579 + 10.05589 13.40399 13.02372 15.95843 15.3619 14.07744 14.53119 14.84178 15.96589 16.57776 15.83888 13.69824 11.78194 11.49591 11.9167 13.30375 14.69968 14.48295 12.85517 14.20968 14.40717 12.00698 13.85596 + 11.27062 14.20996 13.00546 15.81915 15.0768 14.00317 14.15812 14.63979 15.93456 16.13581 15.80585 14.0422 10.93639 11.04755 11.28759 13.27676 14.53805 14.24715 12.69541 13.41502 13.21583 11.7954 14.09292 + 11.82292 14.00475 12.70146 14.15521 13.96183 13.86355 13.82776 14.55097 15.977 16.62114 15.52749 14.43003 11.57255 11.82603 11.46633 12.81295 13.52481 13.20844 12.48258 12.9837 12.70388 12.01538 14.10332 + 11.81733 14.02889 12.67105 14.56858 13.83397 13.15577 12.52465 13.75595 15.65288 15.53403 15.14229 13.78594 11.95139 10.92552 12.43795 12.72144 13.77396 13.37051 12.03247 13.05484 13.05454 12.07504 13.45452 + 11.53573 14.69543 13.47587 14.54975 14.09588 12.23716 11.75021 12.82979 16.3925 16.57847 15.83274 15.41968 12.53094 12.0738 12.46284 13.14491 13.65205 13.70883 12.48045 13.69684 14.24071 12.90326 13.78031 + 10.46279 13.28604 12.97831 14.62308 13.95662 12.26015 12.11266 13.03825 15.73407 15.80908 16.49609 15.14229 11.56523 11.61488 11.99993 13.00464 14.1577 13.95174 12.51486 14.32073 14.42474 13.37241 14.48748 + 10.48366 14.08517 13.39815 12.98214 12.39788 11.79398 12.84884 13.57857 15.40684 15.60652 15.99256 14.35739 12.09316 12.1796 13.10014 13.46264 14.51066 14.02861 12.81611 14.63739 14.12812 13.86828 14.78857 + 10.19182 14.69601 13.93462 12.37111 11.73293 10.65019 12.18164 13.14647 14.66128 15.14137 15.29343 13.7568 11.9698 12.07825 12.39925 13.19032 14.63404 14.42319 13.39776 15.18882 14.68679 13.48536 15.13381 + 10.34476 14.25978 13.3268 12.39499 12.02569 10.45536 10.55758 11.70175 15.12028 16.20647 14.50963 12.94513 11.49872 11.71179 12.57724 12.75515 13.61092 13.75198 13.41445 15.13885 14.47829 14.33477 15.76451 + 10.20487 12.92972 12.66008 12.42735 11.86889 10.36294 10.88357 12.17473 14.84048 16.0851 14.19029 12.64169 11.7309 11.68961 12.55493 12.70489 13.73792 13.65674 13.2528 14.73102 13.98869 14.62711 16.16679 + 9.423117 12.70624 12.09342 10.86123 10.24333 9.513165 11.43117 11.95184 14.92405 16.06582 14.44456 13.55055 11.02903 11.31061 12.30381 12.54599 13.94664 13.53971 13.29417 14.64221 13.89266 14.74018 16.82738 + 9.589171 12.47112 11.4502 9.131975 10.2197 10.17758 11.90142 12.39148 15.51737 16.71535 15.28426 13.4031 11.43478 11.36541 12.49403 13.41569 14.58072 13.76381 13.98231 14.93766 14.71383 14.92337 16.24941 + 9.503296 10.60735 9.271545 9.716817 9.667707 10.52781 11.40006 11.39293 14.60591 15.02798 15.03264 13.64719 11.30165 10.74731 12.65038 13.62129 14.64687 13.53882 15.08017 15.38536 14.1648 14.99333 16.10574 + 9.115908 11.43826 10.47298 10.14771 9.880108 9.726262 10.82772 12.05128 14.10969 15.54203 15.91383 13.69349 11.85291 11.131 13.34072 14.18404 15.38566 14.57817 14.72091 15.19623 14.41386 14.31567 15.83257 + 9.310917 10.28504 9.811528 10.01983 10.05451 9.4747 10.45637 11.62435 13.92147 16.01029 15.57332 13.24274 11.89735 11.36156 13.09891 14.07911 15.26106 14.44157 15.20087 15.08729 14.25182 14.66974 15.79354 + 9.030734 10.77806 10.31404 10.89988 11.18181 10.48961 10.15753 11.69851 14.42295 15.29466 14.48458 12.67693 11.36603 10.84135 11.80535 13.1063 15.03863 14.38514 14.50504 14.37171 13.37179 14.37903 15.7146 + 7.50856 11.28577 10.72007 10.24735 10.17675 10.65663 10.4188 11.49671 14.89423 16.14469 14.11847 12.56653 11.28391 10.65832 11.76379 12.46559 14.1188 13.54819 13.30948 13.31488 13.49415 14.57029 15.60327 + 8.540401 11.55971 10.55634 10.50483 10.84564 11.33433 10.85704 11.34962 13.89402 15.64289 14.49759 12.96604 10.80758 9.884405 10.34586 11.19268 13.79956 13.10055 13.12975 13.08347 12.67242 13.66006 14.28579 + 11.08979 14.00895 13.28101 13.3758 13.54885 12.20103 11.48327 11.56215 13.08175 15.66193 14.28371 12.17407 9.616703 9.735795 10.51737 10.66526 12.92028 12.44681 12.94642 12.86102 11.83186 12.5984 13.619 + 13.14398 15.67154 15.34065 16.24163 15.0646 12.54676 11.68441 12.30696 13.41859 15.11965 13.7802 11.35566 9.2933 9.63155 9.780502 10.87477 12.77262 12.19879 12.55715 12.45354 11.55077 12.7023 13.38533 + 14.07658 16.64854 16.01492 17.56435 15.44176 13.57488 11.04171 12.65898 13.55481 14.86006 13.82938 10.95063 9.030316 9.329757 10.07974 11.01514 14.26132 13.92831 12.94692 12.58781 11.56843 13.6175 14.11914 + 14.75746 16.99999 16.77552 18.02952 15.90656 14.6823 12.81055 14.08961 14.8099 15.94903 14.67507 12.65691 10.4475 10.15641 10.20949 12.54788 15.05997 14.94959 15.0851 14.83846 12.45193 15.8906 16.3022 + 15.11706 17.16695 16.8549 17.89547 16.15163 14.86321 13.19364 14.13913 14.60327 15.88921 14.64402 11.80295 9.991564 9.918747 10.29794 12.53781 15.5761 15.41474 14.87424 14.64059 12.25671 15.93182 16.2683 + 15.40835 17.45925 16.58469 16.7639 16.27665 14.75572 13.4018 13.85402 13.12921 15.10079 14.71053 11.99744 9.913205 9.705381 10.4648 12.13035 15.2068 15.025 13.92012 13.05841 12.5278 16.01011 16.19179 + 15.45827 17.28377 16.69241 16.92484 16.34339 14.48242 13.334 13.81861 13.36756 14.88102 14.33365 11.80426 9.697133 9.240011 10.16598 11.92516 14.61208 14.43869 14.29321 13.34631 12.60551 16.59318 16.53483 + 15.13229 16.73378 16.89245 17.1393 17.00265 14.96519 13.72409 14.02927 13.25723 15.83806 14.40103 12.19008 10.30029 9.71996 9.842927 12.35917 14.15221 13.89012 14.0526 13.6017 11.93211 16.53617 16.72011 + 14.57705 15.6379 16.80176 16.86314 16.66628 14.47865 13.58029 14.06467 13.77963 16.02653 14.70159 12.60434 10.41194 10.06278 10.46712 12.41631 14.66149 14.67236 13.84786 12.99362 12.37743 16.0895 16.24062 + 14.77465 16.17302 17.21569 17.31233 15.77169 13.20695 14.42118 14.81845 14.40012 16.4256 15.06712 12.46396 10.4278 10.54279 11.23261 13.11649 15.14829 15.03464 14.55053 13.6049 12.95183 16.86971 16.66895 + 15.05606 16.52065 17.57998 17.69038 16.2915 14.25601 14.84489 15.36414 15.11178 16.8064 15.81776 13.49685 11.06283 10.37466 11.31837 13.62332 15.70944 15.07266 14.53792 14.15806 14.11397 17.4218 17.03832 + 14.91461 16.54216 17.72679 17.76228 16.31701 14.88375 14.68025 15.63748 16.55472 17.25992 17.16825 15.38784 12.14931 11.18112 11.89017 14.01893 16.31654 15.77574 15.18395 14.93204 14.24931 17.65414 17.32909 + 14.06235 15.76193 17.18796 17.04869 16.16309 15.03378 14.50389 15.27548 16.01657 17.05204 16.83701 15.13547 11.94728 10.58079 11.4934 13.36317 15.81689 15.14827 14.4666 14.55218 13.48098 17.22405 16.73607 + 12.20449 13.63824 16.85458 16.60645 16.1426 16.12964 15.33291 15.36466 16.87278 18.04861 17.6068 15.85586 12.63215 11.99263 12.60324 14.04203 16.81243 16.22995 15.30362 15.6595 14.4412 17.11238 17.13072 + 10.72764 13.3548 16.83893 16.19029 15.82427 16.1692 15.66567 15.07302 15.77989 16.54188 16.30075 15.60935 12.34325 10.73788 11.86701 13.85012 16.31892 15.11082 14.34861 14.96774 14.47254 14.20635 15.01912 + 11.60129 14.09942 16.75742 16.20039 15.56324 15.11675 15.08574 14.86573 15.50838 17.15246 16.86691 15.52252 12.93029 10.73168 11.6597 14.13603 16.52929 15.7967 14.49399 14.30547 13.43089 14.71824 14.45458 + 14.02859 15.28391 15.8077 15.34497 15.03183 15.79032 16.02132 15.8215 15.79886 17.51942 17.64467 15.42086 12.91273 10.42257 12.27133 14.28747 16.08209 15.12793 13.71037 14.9558 14.61221 14.93433 14.82792 + 14.18848 15.49555 16.91608 15.82519 14.52658 13.71498 16.10671 15.94508 15.64377 17.88175 17.91087 15.55968 12.45656 10.9743 12.02333 13.80416 15.67876 14.38375 13.71993 15.67794 15.32651 14.47271 14.63666 + 12.46225 13.96751 15.86773 14.64494 14.40544 14.4847 15.3736 16.16685 15.92646 17.85421 18.09699 16.27834 12.37715 10.08867 11.90742 13.48527 15.4419 14.10502 13.62834 14.96597 14.83753 15.32119 15.06888 + 13.05211 14.65883 16.07732 15.72431 14.58461 14.83052 15.30428 15.05263 15.02916 16.23183 15.43935 14.29991 11.75905 11.07148 12.31495 13.43882 14.98901 13.66362 12.9674 14.51913 14.48279 14.95528 14.8023 + 12.55515 14.89801 14.75364 14.68671 14.14907 14.13834 14.59004 13.6544 13.79198 16.03756 16.11797 13.08031 11.24561 10.83997 11.88435 12.2136 13.93883 13.77705 12.87251 14.5708 14.71351 14.24566 14.39286 + 13.83093 15.56555 14.57585 14.22723 13.8579 13.36604 14.29341 13.54445 13.8807 14.93316 14.51684 12.76485 10.79458 10.58632 11.6082 12.35924 13.70661 13.07612 12.48223 12.69072 12.71655 12.41708 13.33185 + 14.18465 15.79808 14.80991 14.02056 12.90899 12.66223 13.70633 13.82998 14.50648 14.96112 15.03471 13.67727 10.52017 10.59638 11.55017 11.89342 12.99354 12.42688 11.95904 12.05783 11.865 12.08287 12.86125 + 13.25803 14.87615 13.40272 12.26935 12.22413 11.50442 12.41747 13.64918 15.23524 15.79496 15.65018 14.92247 10.86561 10.38056 10.77693 10.56645 12.19267 12.05079 11.84969 12.24822 11.36017 11.14598 12.25312 + 12.45111 13.44244 13.22482 12.22398 12.6735 12.62569 12.88445 12.80736 13.84655 14.60308 14.62763 13.62638 10.40659 11.13378 10.61906 9.895057 11.55708 10.91639 10.84196 11.58675 11.38106 11.41171 12.38584 + 11.51846 12.357 13.54436 12.86645 11.85491 11.5113 12.90373 11.86775 12.45533 13.17408 13.00485 11.74634 9.947218 9.551273 9.567058 9.890096 11.33025 11.05918 10.6337 10.56511 10.17946 10.49385 10.70985 + 10.98355 12.91067 13.64736 13.10762 10.84069 10.44838 11.49005 13.12592 12.14138 12.09276 13.07966 12.63638 9.730008 9.848679 9.347866 9.414342 10.63352 10.80758 10.35773 10.48653 10.42719 11.00619 10.87458 + 11.5681 13.21478 13.99549 13.15111 9.779534 10.84289 11.58694 12.34813 12.21469 12.28861 12.12051 10.8032 10.08394 10.18741 9.829264 9.272332 10.70571 10.90198 10.80318 10.46099 11.22602 10.77159 11.26807 + 10.92434 12.3015 13.37657 11.97633 9.846909 10.4243 11.08952 11.22303 10.78325 12.41337 12.66613 11.84448 9.250697 9.51819 9.804947 9.046823 10.62793 10.31114 10.24323 10.33592 10.83164 10.2066 10.32049 + 10.66577 12.14629 11.68034 9.369942 10.3728 9.903213 10.77546 11.68394 11.14858 12.63394 12.94571 11.37593 10.00287 9.558322 9.845166 10.15729 10.41532 9.908718 9.831599 10.41129 10.63024 10.63221 10.3509 + 11.6184 13.30724 11.55282 10.44678 10.41798 10.1706 10.90822 10.41916 10.55029 11.9752 13.67633 11.79162 10.79402 9.887626 10.8025 10.48727 10.37634 10.34983 10.57088 10.24781 10.26391 11.38719 11.21457 + 12.47586 13.6613 11.17755 10.26591 9.585545 8.900165 11.20114 11.92466 11.11244 11.51987 12.53722 10.63915 9.778065 10.18182 10.7475 10.43095 11.06562 10.47551 9.800613 9.590487 10.01961 10.74588 10.42597 + 10.77701 12.82747 11.43204 10.35398 8.763439 9.405526 10.82308 10.63848 11.23412 12.39658 12.69621 11.6574 9.802265 10.72791 10.38412 9.456212 10.21749 10.77777 10.52131 10.53893 10.67069 11.10977 11.42564 + 11.11245 11.89655 11.72092 11.20401 9.289854 9.801051 10.75733 10.49686 11.86391 12.7017 13.00109 11.37016 10.12052 10.20385 9.899943 10.12168 10.17335 10.37608 10.25884 10.2047 10.5204 11.22245 11.59654 + 10.07293 9.634727 11.54807 11.43805 9.092267 9.683016 10.62169 11.24899 11.31559 11.98399 12.95545 12.45842 10.71746 10.80228 11.2789 9.754478 10.40236 10.25889 10.37339 9.958531 10.25048 11.00226 11.04672 + 10.42046 11.45978 11.63636 10.63461 9.173406 8.947865 9.959992 9.868871 11.08009 11.51371 12.75934 12.10706 11.59692 10.57697 11.32946 10.17161 10.1469 10.40671 10.2813 10.1093 10.52484 11.39623 11.14704 + 10.24291 11.15639 10.16661 9.884758 9.216879 9.206462 10.319 10.26303 10.83201 12.79486 13.33239 11.75389 10.45811 9.76089 10.24667 9.601921 10.60552 10.51551 10.36004 10.27134 10.3252 11.17595 10.70015 + 9.422491 10.00307 9.304786 9.541505 9.374505 9.497077 9.545082 9.764844 10.62842 12.44954 12.87034 11.195 9.428076 10.17063 10.55802 9.774949 10.74514 10.18642 10.23282 10.14036 10.41611 10.48228 10.65022 + 9.431662 9.925019 10.80831 9.614099 8.896067 8.509177 8.498697 8.813649 10.72018 11.46818 11.79037 10.94821 10.24577 10.29312 10.4467 10.298 11.07519 10.74677 10.3395 10.43149 10.75678 12.64705 11.43539 + 9.448807 10.96071 9.576197 7.308518 8.883255 8.301652 8.766844 8.846759 10.22622 11.76619 12.58748 11.67644 10.72528 9.9915 10.21584 10.17676 11.38863 11.19318 9.839796 10.4516 11.10404 13.23044 11.87121 + 10.62994 11.5706 10.67056 9.265289 10.66641 8.995152 7.84528 9.58221 9.973509 10.98804 12.24823 11.57158 10.72447 10.14804 9.541183 9.675903 11.06965 10.92531 10.45405 11.09323 10.41595 11.13436 11.58708 + 10.39545 11.44841 10.55975 9.705544 10.74701 9.747647 8.344221 10.04484 10.6118 11.2215 12.51557 12.40113 10.53294 10.54167 10.73098 10.17531 11.36208 11.10819 10.5466 10.93015 10.72956 11.21366 11.42109 + 9.808684 10.54995 8.585596 8.609066 9.104267 8.886292 8.315665 9.341417 10.22613 11.00258 12.79302 12.32948 11.03078 10.42751 10.86653 10.40495 11.54998 10.99841 10.25156 10.56426 10.5004 11.59432 11.25886 + 9.783687 9.554473 9.224267 8.305874 9.829804 8.624431 9.106644 9.878937 9.975676 11.34404 12.81888 12.35782 11.02464 10.09095 10.79956 10.69735 11.30436 10.86825 10.91094 10.57113 10.4626 11.75406 11.67169 + 9.964293 8.91133 9.597994 8.38109 10.30375 10.12096 9.371438 9.138732 10.23402 11.95144 12.71362 11.4016 10.57952 10.13566 10.54746 10.2636 10.74369 10.67116 10.78272 10.88782 11.01974 11.17345 11.2694 + 7.987907 8.382757 9.251793 8.551911 8.960312 9.169497 9.091488 10.20918 11.97596 10.86922 12.52002 12.28877 10.51484 10.44818 10.56632 10.83528 10.84797 10.92687 10.48957 10.72594 10.87492 11.48538 11.44997 + 9.208866 8.503493 9.972482 9.071673 9.462955 8.664878 7.662143 9.897245 10.64185 10.91716 11.13872 12.12932 11.36929 11.34245 11.2402 10.83909 11.03287 10.97415 10.68303 10.55697 10.5056 11.69601 11.68266 + 9.830456 7.644547 9.789823 9.708093 10.68528 10.30543 9.51989 9.725322 10.94882 11.60964 13.0219 12.9686 12.24573 11.09353 11.58634 11.25884 10.88388 10.70734 11.0613 10.88753 10.72593 12.06754 11.55765 + 10.1434 9.600281 9.03609 9.28277 9.389798 9.782507 9.522714 9.155346 10.51925 11.83723 12.88527 13.05917 12.5973 11.04044 12.4058 11.76693 11.41435 11.29793 11.33523 11.57119 11.21824 12.67894 11.60467 + 9.044415 9.35461 8.125829 8.155436 10.21073 9.420408 8.485256 9.519516 10.80159 11.03508 12.98993 12.86469 12.11976 11.60614 11.22908 11.30737 11.26812 11.07872 11.49184 12.23885 12.01657 12.81706 11.95831 + 9.557627 10.25238 8.452591 8.784152 10.9992 9.823544 9.510536 10.38744 11.27521 11.65287 13.02484 12.76013 11.33446 11.90731 11.69084 11.35221 11.25088 11.1325 11.48025 11.96735 11.96839 12.37707 11.87469 + 10.23095 9.161502 8.99103 8.923839 9.805343 9.837078 10.10092 9.621623 10.84666 11.88772 13.02984 13.09626 11.90776 11.65839 11.98773 11.63274 11.56222 10.86827 11.27272 11.77922 11.91178 12.68476 12.05236 + 8.874165 7.809851 8.695865 8.223797 8.763299 9.566321 8.770155 9.008782 11.19149 11.83618 13.52786 13.19089 11.91928 11.96163 12.16576 11.49836 11.79749 10.94192 10.88142 11.00608 11.66361 12.51995 11.84705 + 8.437733 8.480018 7.988172 8.590461 9.420288 9.875575 9.504238 9.388609 10.90206 11.65753 13.35867 13.6816 12.48646 11.95356 12.16133 11.43572 11.42155 11.1403 10.60003 11.26259 11.62654 12.52154 11.82845 + 9.964341 8.923758 9.201259 9.687395 9.499485 9.23505 8.127786 8.023572 10.40248 11.75716 12.92915 12.92148 11.44932 11.59385 12.10262 10.90831 11.27934 11.05236 10.57544 10.54616 11.69068 12.66987 12.08684 + 7.982019 9.21278 8.003357 9.466326 10.96768 9.979166 8.679527 8.800684 10.64961 10.21235 12.66046 12.89555 12.06681 11.04834 11.27791 11.41866 11.11113 10.78568 10.56767 10.6112 11.04461 12.21314 11.62118 + 8.391383 9.194236 9.299546 9.597569 11.07862 9.571204 8.833894 8.325615 10.67207 11.36724 11.90812 12.3979 11.69843 11.26144 10.98187 10.62434 10.55542 10.79755 10.96708 11.02939 11.2887 11.80884 11.33321 + 8.614913 7.849565 7.212549 9.800451 10.48496 9.617368 9.147942 9.296861 10.13177 11.20514 12.3896 11.95667 11.57815 10.64794 11.444 10.93266 11.42423 10.72211 10.74878 10.75023 10.98787 11.26347 11.04169 + 7.535953 8.379319 8.465982 9.019888 8.474906 8.644684 9.380523 9.011086 9.468113 10.66582 11.96134 11.67335 10.88397 10.53693 10.82633 10.25402 10.21263 10.32749 10.5247 10.19471 11.01541 11.35801 10.77586 + 8.953902 8.443554 8.80369 8.623836 9.60961 8.834076 8.999572 8.835788 10.4686 10.42089 11.60229 11.5832 11.33114 10.05566 11.09636 10.20237 10.258 10.62062 10.48857 10.14489 11.40099 11.07088 10.64445 + 8.500578 8.544858 7.815175 8.512371 8.136365 8.380095 9.461661 9.8258 9.200838 10.49927 11.48638 11.7742 10.49608 9.742468 9.315075 9.044184 9.661458 9.323888 9.979736 10.51669 10.50003 10.75289 10.55279 + 8.140522 7.9291 8.467335 7.948473 7.84909 8.878713 9.228608 9.341762 9.541382 9.544453 9.793461 9.858119 10.06971 9.451279 8.51177 8.683918 9.586095 9.493937 10.0905 10.45376 10.50862 10.45298 10.89592 + 9.174277 7.910554 8.41324 7.574174 8.196404 8.136746 8.121965 7.858237 8.164356 9.198856 10.01476 9.891054 9.341989 8.451241 8.975142 8.96706 9.390165 9.745036 9.87119 9.820464 10.01453 10.3652 10.31961 + 9.053761 8.623909 7.898996 7.378081 7.484929 8.12253 7.869719 8.111196 7.660993 7.883986 8.894523 9.96628 9.412325 8.602212 8.978284 9.126224 9.584894 9.770082 10.27369 10.15531 10.25294 10.89211 10.43277 + 8.431073 9.066086 7.605909 6.630337 6.98281 6.810944 7.623994 7.17003 7.13573 8.053357 9.209491 9.415495 8.662474 9.053435 9.397181 9.446578 9.86334 10.01845 10.17869 10.7758 10.44624 10.38814 10.5821 + 8.942445 8.941487 8.016459 8.513492 8.322972 7.758252 7.450797 7.878129 7.665907 7.39168 7.762891 8.652268 8.399451 8.6668 9.206373 9.121785 10.34451 9.926603 10.24554 10.46929 10.76244 10.86812 10.72856 + 8.17054 7.23271 7.703678 7.791962 8.005906 6.999223 7.472554 7.710637 7.925411 7.891777 8.577125 8.739526 8.736339 9.344995 9.341995 9.167021 10.40602 9.640493 9.765063 10.79863 10.77817 10.97881 10.41767 + 8.539558 8.259498 8.565295 7.196165 6.610144 8.112316 7.271636 6.927933 7.946772 8.102273 7.875247 8.079875 9.375752 11.33052 10.81717 10.21815 10.39465 10.33572 9.731151 10.2445 10.49189 11.12053 10.76395 + 8.240433 8.050691 8.147851 7.085478 6.498564 7.444286 7.85869 7.59498 7.437051 8.215886 8.160721 8.345738 8.091083 9.35317 9.591409 9.448403 10.31442 11.06631 10.24245 10.9984 10.90544 11.08032 10.89513 + 8.550308 7.967917 8.127562 7.298366 7.333465 6.49415 7.352107 7.673339 7.976416 7.147206 8.484519 8.401263 8.720249 9.223616 9.384628 9.444779 9.571202 10.1814 10.25545 10.58296 10.57897 10.69769 10.3299 + 8.480454 8.143271 8.400046 8.316236 8.42107 7.541131 8.236753 8.256838 7.837097 7.3845 8.376999 8.782026 8.372799 9.166592 9.061806 8.605547 9.607065 10.09919 10.09029 10.41691 10.74461 10.34597 10.42431 + 8.515889 7.59104 8.670294 8.427706 8.82935 8.922324 7.844193 8.359896 10.19205 11.45637 11.92006 11.58537 10.71724 11.57984 12.44317 12.37348 11.76033 10.8328 10.71005 12.17016 13.12123 13.49667 12.32615 + 9.425822 7.528139 8.268925 8.60403 9.334135 8.938853 8.31205 8.903502 10.90029 12.15107 12.53318 12.27021 11.24565 12.047 13.05301 12.96288 12.37509 11.36883 10.95156 12.79793 14.03581 15.59215 14.59917 + 9.623832 8.175732 8.02983 8.535868 7.534457 7.092595 7.973546 8.616755 9.133872 9.538013 9.353437 10.20192 10.09133 10.06388 10.93718 11.69838 11.60221 10.81748 11.46689 14.67081 16.11157 18.09854 18.4955 + 9.071504 7.593831 8.569519 7.444993 7.027435 7.670381 7.727194 8.79417 8.78412 10.26749 10.49481 10.5346 10.10987 10.96909 11.81418 12.57517 12.30537 11.48952 13.16938 15.3521 18.07163 19.78831 20.41618 + 9.247075 8.342739 8.020856 8.513025 8.168929 8.344195 8.860204 9.17792 10.17791 11.50909 11.07768 11.59347 11.83874 13.10125 13.83476 13.61449 13.87037 13.02653 13.97317 17.72324 19.60029 21.56919 21.07852 + 9.474851 6.999008 7.357038 8.650279 9.284972 10.27271 10.72347 10.36143 11.41015 11.70805 12.40401 12.9837 12.64802 14.57075 15.13742 14.90884 14.86531 14.43093 15.03153 18.73455 20.89188 22.41504 21.75938 + 8.43823 7.602589 7.820417 9.932999 10.68121 10.14239 10.38401 10.35759 12.41399 12.78729 13.28866 13.30149 12.69712 15.16579 15.84278 16.40749 16.29351 14.39652 16.20181 19.23029 21.44838 22.89708 22.22457 + 8.946985 7.907045 8.086035 9.623978 11.20953 10.96355 11.34051 11.00923 13.56718 13.85 13.94107 14.30479 14.27669 15.13896 16.49326 16.82891 16.4699 15.615 17.10016 20.28425 22.26842 23.02636 22.56798 + 8.171475 8.090663 8.552567 10.50998 10.79045 10.79585 11.77593 11.28996 13.5619 14.10327 14.95813 15.71215 14.54693 15.72432 17.80629 18.37472 18.3487 16.05388 17.43175 20.30505 22.645 23.84378 23.11084 + 8.994973 8.401255 10.09553 11.87671 11.69541 11.35841 11.40016 11.24762 13.69226 14.48686 14.93769 14.94588 14.56451 15.99824 18.65324 18.06892 17.89767 16.18208 18.38502 20.50726 22.87932 22.78703 22.46937 + 9.782323 8.487134 9.374578 10.02132 10.83674 10.82025 10.9053 12.09562 13.35448 14.63213 14.8919 14.47755 14.00885 15.77607 18.01674 17.6033 18.13421 16.40895 17.83689 19.94169 22.52357 22.9064 22.88938 + 9.201106 9.515572 10.08597 10.27731 11.7706 10.57605 11.69659 11.87735 13.37034 14.23273 14.85306 14.77176 14.40004 15.35916 18.42156 17.62954 17.38562 16.92805 18.14824 19.61706 22.00475 23.03317 22.16275 + 8.748121 9.011187 9.970596 11.7954 12.21095 11.81142 11.55084 12.17591 13.77476 14.28944 14.6072 13.23592 13.31399 15.84934 19.25628 18.41424 18.67715 16.80241 18.52868 20.36193 21.57055 22.12268 21.53455 + 8.204464 8.748878 10.92591 11.5593 13.19048 12.10626 12.02502 12.05263 13.87249 14.59625 14.2399 13.71816 14.30343 15.58732 18.69723 18.77998 18.86568 17.82364 18.05045 20.36054 21.73694 21.81034 20.88315 + 9.566998 9.260836 10.80363 11.55462 12.66976 12.26979 12.27326 11.70452 12.91361 13.86102 14.35423 14.02273 14.13222 14.11232 18.20366 18.85984 18.12046 16.65557 17.61575 19.08671 20.27447 20.7323 19.79643 + 9.615409 8.908094 10.52764 12.07648 12.35761 11.91719 11.31676 10.78619 11.90151 13.13435 13.57572 13.40028 13.16485 14.1586 17.97005 18.6574 17.49142 16.61602 16.97221 18.26481 19.8641 19.66534 19.13474 + 9.518407 13.66414 14.68301 12.44051 12.49297 11.79643 11.65911 11.69031 11.55598 11.38682 13.41629 12.76745 13.40397 14.54726 16.66532 17.59948 16.87245 15.5036 16.39759 17.45155 18.24678 18.04825 17.68169 + 9.886844 16.85537 18.44436 16.27383 15.36497 13.84907 13.35042 12.87622 12.51681 12.60914 13.06418 12.81159 12.38205 13.68456 16.01586 16.22343 16.41093 15.50471 16.12335 16.51148 17.60962 16.92897 15.91486 + 9.932881 17.91137 19.72267 16.92243 17.54066 16.3813 13.78773 13.66247 14.68949 11.97803 13.91343 13.337 12.36077 13.84169 16.9629 16.03707 15.96878 15.15771 15.55823 16.98711 16.99094 16.69534 16.27666 + 9.659884 18.09613 19.96937 17.05172 18.13224 17.0855 14.13896 14.20781 15.18947 12.32284 14.15654 13.44278 11.52995 14.65633 17.3638 16.08185 14.93075 14.58424 15.63039 16.85744 16.92874 16.18911 15.86921 + 9.225732 17.84486 19.66064 16.73664 18.33948 17.20544 14.32049 13.99798 14.7793 14.12439 13.80436 13.0112 10.64079 14.43213 16.94347 15.76767 14.88731 14.56431 14.75913 16.57955 16.82982 14.99926 14.78576 + 9.978578 17.50287 18.95299 15.45618 18.21201 17.02547 14.4027 13.69167 14.05373 14.97102 13.77975 12.47722 10.86374 13.84392 16.04429 14.79358 15.0554 14.68087 14.3245 15.75972 15.80098 14.99877 15.23887 + 10.18868 17.52852 18.87184 15.18826 17.86486 16.54828 14.55269 13.6402 14.14156 15.5371 13.91196 12.7452 11.54375 13.17193 15.15623 14.38001 14.19828 14.53728 13.81415 15.57458 15.68111 14.43799 15.17661 + 9.751181 17.77567 19.34327 16.0554 17.61001 16.27066 14.56316 13.73765 14.17065 15.73401 13.81508 12.20863 11.4907 13.34967 15.23476 13.96594 13.74054 13.87504 13.52333 15.54135 15.94359 14.1128 15.21993 + 10.12562 17.83921 19.52574 16.31928 17.54741 16.23771 14.63482 13.75617 14.15524 15.65129 13.92926 12.40173 12.0656 13.88922 14.18397 13.18738 13.58043 13.53881 13.6731 15.04371 15.07353 14.68031 15.49483 + 9.599173 17.72381 19.33587 16.07591 17.93183 16.65643 14.42071 13.79618 14.27678 15.20349 13.67884 12.52839 12.27971 14.40655 13.7573 12.9882 12.78122 13.05482 13.01771 15.88482 15.94137 15.41255 15.99297 + 9.698954 17.42312 18.87054 15.56586 18.05615 16.68462 13.77028 13.44723 13.88304 14.32237 13.28011 12.27115 12.59856 13.77862 13.29578 12.70767 12.83421 13.53693 13.18715 15.76992 15.76956 15.14107 15.93729 + 10.26758 17.18085 18.16345 14.96107 18.03324 16.52345 12.76308 12.70064 12.66801 12.16031 13.20089 12.79833 12.22538 12.70561 13.22355 12.84058 12.97821 13.38026 12.87051 16.21716 16.48619 14.9863 15.6656 + 10.44387 17.23746 17.66788 14.44783 17.53398 15.96145 11.02331 12.6539 12.60111 13.22309 12.45506 12.53511 12.75447 13.26548 13.35413 13.15652 13.2005 13.69604 12.86835 15.36887 16.235 16.85772 16.54977 + 10.23759 17.50456 18.4933 14.91491 16.87596 15.31868 10.46777 12.26849 12.64224 12.68833 12.15624 12.53137 13.16832 14.12793 14.15696 13.49824 13.72296 13.93223 13.8926 16.50182 18.04177 18.58524 18.28993 + 10.35166 17.05918 18.34675 14.85144 15.89987 14.52373 9.76105 11.89735 12.64335 14.01029 13.73979 13.24587 13.72216 15.26234 15.57124 15.82822 14.74431 15.00797 14.97392 17.71632 19.5425 20.54453 20.59761 + 9.555297 15.74934 17.18219 13.77729 15.08703 13.69022 10.26886 10.90417 12.99767 13.61335 14.09484 13.58124 14.1276 15.82747 16.31124 16.64211 16.18265 15.21884 15.42664 17.76999 19.3903 20.87875 20.63521 + 8.862854 14.02321 15.96503 13.04325 13.75009 12.35207 10.97646 11.18398 12.65906 13.14713 13.12646 13.15454 14.30518 15.27275 15.80141 16.52305 15.9915 14.97509 15.27901 17.70121 19.40024 21.01748 20.90414 + 9.332595 14.84705 16.37164 13.3482 13.17087 10.29789 10.88544 11.37841 12.42703 13.47049 13.44906 13.32345 14.28644 15.11956 16.00913 15.9466 15.03316 14.63348 14.81898 18.33994 19.95979 22.53777 21.45701 + 9.877868 15.61059 16.83839 13.17729 12.35922 10.87731 12.04013 11.68683 13.13918 13.59089 14.2367 13.87884 13.71417 14.67861 15.89329 16.04236 15.43196 14.25506 15.13684 17.87797 19.84326 21.79107 21.3449 + 9.140958 14.76068 16.10624 13.06766 13.47845 11.71019 11.03197 11.56545 12.67004 13.27332 13.428 13.15621 14.76991 15.18839 16.24062 16.5626 15.80795 14.79566 15.80218 18.18081 20.55774 22.59585 22.16014 + 9.849629 12.1764 13.81905 12.20043 12.96303 11.37443 10.69466 11.47991 12.80477 12.91667 12.75835 13.21933 15.38656 15.95259 16.823 16.35283 16.43909 15.31986 16.49224 18.66559 20.4817 22.71685 21.85016 + 9.36202 14.08019 15.09082 12.18999 12.97359 12.29903 11.47363 11.54493 12.6398 12.84276 13.85428 13.64815 15.08565 15.69742 16.96827 16.94289 16.80222 15.64644 16.48269 19.1488 21.81447 23.17466 22.23106 + 9.57612 15.02501 16.06492 12.43449 13.88631 12.50848 11.44948 10.40349 11.57291 13.3126 14.31746 13.71188 14.76625 16.03081 16.07044 15.89003 16.94098 15.98808 16.14021 18.78211 21.02096 21.56885 20.88732 + 11.78685 15.01811 15.52112 13.93588 14.43564 12.86158 11.35952 11.50686 11.3272 13.30817 13.81913 13.40519 15.05978 15.84919 15.35929 13.89001 15.52159 15.23993 15.40555 17.11906 18.97794 19.44685 18.4641 + 13.22307 17.17885 17.15026 17.71776 17.11022 14.76405 13.44761 13.67636 13.50036 13.58129 13.95018 13.70605 14.88567 15.03857 14.84337 14.45563 14.86374 14.4649 14.85144 16.43289 16.89828 16.35405 16.94752 + 13.76669 17.1444 16.05253 18.63818 17.65204 16.25583 13.92566 13.3862 14.08959 14.32102 14.93346 14.31664 14.22204 15.10196 14.79939 14.6737 15.79121 14.93165 14.95335 16.65422 16.83254 16.26995 16.42411 + 14.16293 17.56638 16.68997 18.26259 16.92739 16.86658 14.25883 13.70589 14.65646 14.7077 15.98879 14.10393 13.49225 14.60994 14.55858 14.16977 16.02258 15.63895 14.65991 16.49418 17.11003 16.32096 16.05037 + 15.02538 17.72275 16.8508 18.08271 16.43288 16.42471 14.93453 14.1509 15.1513 14.99477 16.14239 14.24235 14.14056 14.526 13.32078 13.45539 15.79795 15.33149 14.69157 15.69744 16.36747 15.60381 15.84035 + 15.73365 18.03275 17.27561 18.75753 16.95327 15.80555 14.91612 15.18357 15.67709 14.97079 15.72672 15.15915 14.04085 14.47075 13.372 12.99742 14.7936 14.90177 14.24704 15.20238 16.2678 14.74167 16.11823 + 16.26338 18.46109 17.27797 17.99683 16.66476 16.05012 15.40509 15.53155 15.96098 15.33267 16.30002 15.72512 14.53671 14.58817 13.3534 12.64955 14.37179 14.83991 14.59077 15.26721 16.07784 14.28749 15.199 + 16.74382 18.91121 17.16111 18.04957 17.40871 16.48565 15.41961 15.17963 15.49974 15.08093 16.65548 15.9269 13.91849 13.88327 12.97235 12.65223 14.07203 14.75737 14.22784 14.64774 15.34663 14.63228 16.0888 + 16.98529 19.11541 16.87011 16.74349 16.9392 15.88544 15.29093 14.62053 14.88953 15.09566 16.51639 15.45968 13.03472 13.23992 12.823 11.87619 13.33451 14.86192 14.20521 15.69702 15.84917 14.75232 16.39235 + 17.02896 19.19595 17.16109 16.8834 16.3556 15.42284 14.74861 14.76104 14.92473 14.9596 16.6881 15.93081 13.33428 12.97397 11.86678 11.63746 13.04319 14.28844 13.94617 15.04595 15.0869 13.90257 15.89565 + 16.85907 18.78141 16.70892 17.00507 15.92624 15.45061 14.55362 14.95892 14.77704 14.90913 16.98108 16.19264 12.85906 12.48413 11.65127 11.22398 12.58972 13.57485 13.65335 14.72739 15.04537 13.98265 15.01211 + 16.64772 18.64282 16.71573 17.18554 14.0683 13.95587 14.57919 14.46328 14.58978 14.59518 17.19131 16.57361 11.92534 12.44241 11.72602 10.9928 12.40186 12.90819 13.48149 13.89237 14.56885 13.63314 15.65915 + 16.43158 18.50433 16.55654 16.99913 14.52069 14.72103 15.24015 14.21969 14.87307 14.52989 16.81562 16.34734 11.70553 12.3317 12.12857 10.71785 11.98174 13.78292 13.32322 13.64274 14.45671 12.9734 15.85995 + 16.15336 18.23414 16.24944 17.09164 15.74857 15.23684 15.1064 14.90766 14.93522 14.25292 15.3692 15.24385 11.05891 11.63416 11.56629 10.73565 11.83266 13.19534 12.7056 12.56191 13.70245 13.0052 15.80045 + 15.78317 18.05012 16.20395 16.93727 15.87394 15.19709 14.3524 15.32856 14.63064 14.19885 14.98944 14.75296 10.62751 12.19092 12.06983 10.93399 11.87565 13.48435 13.07779 12.09982 12.47317 12.27248 14.84082 + 15.39456 17.87456 16.5168 17.58305 15.61139 14.72246 13.21522 14.96642 14.20902 13.41669 14.09352 13.92773 10.64034 11.88319 11.45076 10.29098 11.03321 12.29812 12.70397 11.7358 12.26939 12.86226 14.78897 + 15.02351 17.78964 16.85471 18.13364 15.47449 12.54772 11.64264 14.01794 14.02666 13.12667 13.50438 13.3572 10.43241 11.16833 10.74877 10.2707 11.00853 12.42801 11.92629 11.09674 11.43357 12.86431 14.27416 + 15.09622 17.92495 16.85561 18.0988 15.42922 13.02023 12.68088 13.53072 14.48343 13.28728 12.15022 11.93546 9.953141 10.79692 10.91492 10.17909 10.60862 11.80252 11.56975 11.04416 11.2531 12.42208 13.60621 + 15.60607 18.24389 16.80309 17.81159 15.3917 13.49877 12.40576 13.14706 13.09449 12.85986 11.56458 11.51797 9.652337 9.949935 10.32372 9.620278 10.69048 12.02814 11.59302 11.66032 11.30074 12.12424 13.24895 + 15.90996 18.41689 16.64116 16.99631 14.64567 13.13194 12.07542 12.5822 11.69349 12.01927 12.62355 12.76301 9.990061 10.90189 10.44079 10.43505 10.66998 11.15739 10.76031 11.42792 10.89405 11.56491 12.52887 + 16.00483 18.30446 16.08208 15.86133 13.48314 11.61676 10.97823 11.11799 11.98649 11.92625 11.69026 11.50517 9.732758 11.60136 12.52013 10.93729 10.51151 10.81287 10.82617 11.56538 11.44386 11.92824 12.41918 + 15.85966 18.15599 15.9375 14.95248 13.4534 12.87702 10.83218 10.35821 11.16184 9.718279 11.04042 10.5775 9.815519 11.36835 11.88853 10.33835 10.50751 11.11677 11.1463 10.79816 10.42655 11.12749 11.29204 + 15.60883 17.95237 15.71562 14.06708 12.83226 12.50489 11.20979 11.6847 10.72093 10.36459 9.917531 9.80342 9.078652 10.03732 10.6124 10.40085 10.80684 10.88086 10.69822 11.10227 10.46698 10.97048 11.10912 + 15.2283 17.69786 15.57806 13.4353 12.19316 11.6246 11.0224 10.72823 9.161061 9.050728 9.18773 8.855824 9.341613 10.0488 10.13517 9.546819 9.585426 10.50744 10.39803 10.67031 10.51961 11.0685 11.14912 + 14.64353 17.16451 15.15834 12.35796 12.72608 12.63046 11.68167 12.3668 14.25992 15.93695 17.47775 17.61803 15.56757 12.9737 13.57319 14.56981 13.7044 12.71877 12.86225 13.4201 14.1629 15.60515 15.65097 + 14.06131 16.47077 14.28449 12.84858 13.9689 13.82744 13.50231 13.78073 15.74885 17.04282 18.63243 18.85933 16.36653 13.78124 14.21553 15.24145 14.55391 13.56051 13.44235 13.90322 15.24372 16.40675 16.67416 + 13.27166 15.83467 13.93871 14.37551 15.34901 13.98736 12.92254 13.76627 16.36158 17.10549 18.30305 19.29653 16.39378 14.40754 14.97054 15.40539 15.09013 14.31383 14.41104 14.81105 16.28816 17.46544 17.82826 + 12.96865 15.35536 13.58534 12.4107 13.17324 13.02235 12.97369 12.78875 14.50413 17.08331 17.24843 18.6091 16.95518 13.8684 14.65208 15.11388 14.85909 14.55639 14.21002 14.85613 16.15392 16.62638 16.38885 + 12.74014 14.99901 13.14317 12.02542 13.41206 14.03033 13.80731 13.07016 15.74668 17.29949 18.08332 19.69819 16.80057 14.75249 15.27074 15.78106 15.3902 15.52286 15.18476 16.46467 17.64916 18.49447 18.44848 + 12.97496 15.42951 13.52378 12.25876 12.9795 12.56979 12.22692 13.57497 16.76153 17.23584 17.87868 19.12806 16.46191 14.3858 15.09691 16.4803 16.39127 15.58957 15.17072 17.00169 18.0875 17.41795 17.79514 + 12.85048 15.10298 13.23063 12.87181 13.15208 14.49708 13.07669 14.13431 17.66776 18.05823 18.7889 19.8481 17.92514 14.92354 15.70432 16.74818 16.99346 15.81199 15.74879 17.21499 17.70545 17.515 17.36108 + 12.53143 14.44646 12.04487 12.66221 12.77981 14.32154 14.07838 15.03848 17.27874 19.03686 20.9606 21.08079 18.90958 15.76664 16.3242 16.48548 17.68536 17.00244 15.70976 17.18387 17.96568 17.45182 17.34422 + 12.27433 14.13071 11.95814 11.10963 12.29782 13.32947 14.22352 14.91252 17.05256 19.26613 20.56975 21.0718 19.55671 16.45315 16.53867 17.16694 17.57012 16.58561 15.94327 18.1118 18.44273 17.04756 17.29078 + 11.13274 13.31547 11.17836 10.36571 11.99675 13.38614 12.98195 13.65712 17.52398 18.83635 20.82496 21.25018 19.02944 15.45844 17.22033 16.9855 17.61625 16.53855 16.01951 17.28067 17.80919 16.29823 16.60528 + 11.49348 12.72149 11.09698 10.74121 11.50064 13.47943 13.28319 14.12894 16.37674 18.3519 20.4639 21.47652 19.33566 15.77035 17.06349 16.64114 18.07568 16.64302 15.36201 16.04336 16.91956 15.94561 15.99434 + 9.608139 11.75429 10.06571 11.10138 12.11786 11.58483 13.1205 14.21301 17.56386 19.13147 20.32322 20.12503 18.29022 15.28772 16.86212 17.37056 17.82328 16.0193 14.99997 15.78904 16.12305 14.55896 16.01875 + 10.37425 13.20676 13.2479 12.19929 12.48866 11.74895 13.30715 14.54824 16.49951 18.31167 18.70988 19.54097 18.35619 15.11479 16.51697 15.98994 17.09819 15.56539 14.59282 15.14828 15.75255 14.43601 15.72609 + 12.24577 13.50046 14.6557 13.99299 13.82843 14.16929 14.48501 15.5354 17.66883 19.51937 19.39577 20.05754 18.37926 15.92839 17.23025 16.90665 17.33165 15.74956 14.90289 15.95183 16.37263 14.70161 16.83718 + 11.82463 14.14506 14.91792 15.10521 13.43633 14.76357 14.50471 16.30964 18.37582 20.10591 19.70662 20.12249 18.50193 16.44523 17.84676 17.22989 18.06538 15.9933 14.78116 15.97121 16.63473 14.1447 16.62147 + 11.49515 12.57675 14.99955 15.12591 14.04319 14.90118 15.1428 16.90608 19.3396 19.58758 19.89336 20.47232 19.38027 16.83281 17.80051 16.78543 17.68006 16.1482 15.12499 16.29321 17.28958 14.39812 16.74356 + 12.83119 13.36858 15.33085 15.8993 14.98862 15.06884 16.27656 17.09787 19.89 19.45495 19.66687 20.41065 20.05285 16.93578 17.84101 17.22516 16.6539 15.47889 14.51853 15.41234 16.16337 13.92638 16.21105 + 12.24501 13.81182 15.54418 15.71802 14.7434 15.25031 16.42093 16.59528 18.99483 18.75741 18.65719 19.56692 19.45428 16.5445 17.94703 16.85806 17.52571 15.87006 14.62513 16.25237 16.75192 14.53815 16.44307 + 12.43977 14.4055 15.44666 14.61817 14.49417 15.49514 16.47301 15.8417 18.29481 18.40584 18.14786 18.99505 18.48756 16.02132 17.57674 16.21739 17.56367 15.96092 14.12129 15.69186 16.08254 13.29479 15.69123 + 13.40359 14.56592 14.56405 14.75467 14.80557 15.61075 16.30825 16.00495 17.7576 17.7054 18.44081 18.78086 17.91715 15.75736 16.79029 16.09669 16.95103 15.45958 14.6285 16.45296 17.08261 14.81537 16.62719 + 13.40314 14.79066 14.44038 14.9208 15.28192 15.93543 16.12896 15.63424 17.53443 16.71537 17.66586 18.26663 18.14948 15.38054 15.57821 15.0981 16.37489 14.9254 14.11771 16.22453 16.63408 13.88445 16.03982 + 13.27263 15.37152 14.98211 15.65071 16.10147 16.25443 16.39878 15.22288 16.07312 15.48051 16.95177 17.88365 17.64783 15.38212 14.85438 14.25418 15.50375 13.87315 13.85102 15.71543 15.58055 13.48126 13.54171 + 12.79769 14.03458 15.15382 15.77783 16.13641 15.98903 15.71088 14.83081 15.42676 14.42686 16.35059 17.19747 15.89601 14.84456 14.84353 13.56014 14.43848 14.02768 13.56937 15.50516 15.01935 13.35548 13.45835 + 12.93903 13.22154 14.53004 14.98958 15.29645 14.57587 14.04077 13.26272 15.32759 15.32236 15.08416 15.42689 14.78734 13.71587 13.79163 12.75518 14.19596 13.34965 13.19663 14.03743 14.96614 16.49541 15.33635 + 12.45864 14.61374 13.87632 13.62215 13.72294 14.31611 14.38086 14.30659 15.43989 15.61096 14.15577 14.35514 14.81355 13.64131 13.45614 12.72456 14.13968 12.8555 13.59385 14.17748 16.39845 18.38371 17.80732 + 12.33578 14.00891 13.07828 13.71424 14.10664 14.0687 13.13889 13.63885 14.8914 14.61784 13.90929 15.45194 14.73162 13.03892 13.2657 13.5637 13.76148 13.25706 15.10444 16.29706 16.97831 19.44946 19.79165 + 11.96159 13.51957 13.01035 13.54761 13.69153 14.00353 13.68937 12.58661 13.48017 14.13214 13.91707 15.31068 14.23771 12.71703 12.92456 13.6957 14.43063 13.71029 15.22686 17.15446 18.45062 20.47192 20.89673 + 12.05161 13.9109 13.16452 12.97099 13.18609 13.13105 12.99765 11.58859 13.5218 14.01596 14.89017 16.47881 15.28746 13.67678 14.92654 14.96245 15.16147 15.0592 16.54627 18.17285 20.02958 21.51884 20.96085 + 11.84209 13.52904 12.77278 13.65398 13.20718 12.63755 11.65749 11.51693 13.74348 13.71783 15.14928 15.87488 15.38048 14.01578 15.41916 16.25989 16.82 15.85239 17.44685 18.49073 20.68714 23.03419 22.3119 + 10.61903 12.43421 12.6958 13.1518 12.79633 12.09934 11.18174 12.15249 13.5683 12.78554 14.49859 16.1356 16.38138 15.60392 15.1943 16.6934 17.43444 17.15856 17.69735 18.73776 20.3603 23.0533 22.83157 + 11.10656 11.3087 12.01439 12.55888 12.73079 12.43724 11.43508 11.91604 11.70938 13.99057 15.63961 17.28322 16.47677 15.20305 15.66617 17.61813 17.6751 17.60305 17.77451 19.25667 19.94949 21.90983 21.98059 + 10.16949 12.0564 11.52991 9.989189 11.37004 11.68872 11.52673 12.63663 12.73647 14.62583 15.9687 17.945 17.5506 15.1612 15.45209 17.22151 17.94404 18.35554 18.20267 19.54291 20.5292 21.7872 21.39606 + 10.79198 11.45736 9.874234 11.29701 11.26837 11.21837 10.89179 11.62866 12.76508 14.6835 16.18573 17.36658 16.85448 15.94918 15.41981 17.08667 18.88033 18.45577 17.89176 18.7529 19.93383 20.91602 20.71407 + 10.99965 11.83611 10.76372 12.36669 12.88359 12.50686 12.02554 11.77562 12.96449 14.7419 17.49727 19.05647 17.76691 16.14889 15.8502 16.94702 18.54008 18.61212 17.60375 18.40122 18.75871 20.56727 20.2013 + 10.71721 12.24653 11.40098 12.01936 13.33127 11.90686 10.65947 11.82579 13.32248 15.28793 17.01124 18.64284 17.72797 16.53176 16.11108 16.70494 17.7008 16.87293 16.90321 17.01193 17.9865 19.62047 18.54438 + 10.84808 11.77401 10.30343 12.64884 13.14566 11.23923 10.88098 13.11721 13.34063 16.31784 16.83382 18.21345 17.3839 15.27081 15.50685 15.6567 16.72981 16.33526 16.45064 16.3113 16.49539 17.15864 16.46489 + 9.56905 11.07326 11.29717 12.00945 12.56924 11.74636 11.67797 12.27109 12.33903 15.01359 17.29026 17.98109 16.23312 14.9431 14.57544 15.20214 16.10517 16.19224 15.99478 15.25868 15.40402 16.06674 15.44316 + 10.508 10.89142 10.47173 10.95241 11.76593 10.98585 10.76927 12.52917 13.36483 14.31004 15.60877 16.93384 15.8426 14.30149 13.70551 14.91785 15.92855 15.53096 15.40199 15.04334 15.08857 15.01525 14.25574 + 10.63981 10.66823 10.20522 11.10935 11.52312 11.02348 10.33216 11.91914 13.66731 13.93949 16.71332 17.01327 15.03499 14.47763 14.38019 14.41362 15.83647 15.67047 14.47045 14.31423 14.19904 14.82268 14.1559 + 8.488654 10.10538 10.92675 10.09366 10.85918 11.26827 12.20304 11.80998 13.27558 13.73623 16.86435 16.49012 14.37312 13.21352 13.43559 13.28338 14.71591 15.0856 14.91768 13.89271 13.95088 14.00356 13.01124 + 10.81898 13.143 12.69162 12.18221 12.55161 11.64523 12.4468 13.02303 11.64384 13.77602 16.63419 16.27282 14.54465 12.69009 12.62936 13.17467 13.86224 14.07909 14.24895 13.57493 13.15678 13.56058 14.07112 + 11.59634 16.15282 16.04291 14.96902 14.3159 12.56915 13.37283 13.76863 13.54054 15.07096 17.31351 15.84202 14.1527 12.01037 12.44869 12.54331 13.90306 14.87695 13.81047 14.15678 13.85537 13.21027 14.43797 + 11.50468 16.01534 15.65023 16.86644 16.7308 13.15539 13.75249 14.19167 14.84136 15.6804 16.669 15.56741 13.27599 12.18581 12.46388 13.30746 14.70786 14.89385 13.59378 14.1329 13.90083 13.44039 14.82801 + 10.83374 15.32171 15.06138 17.48699 17.51079 13.80905 13.93069 14.59176 15.34084 16.82994 18.43996 15.90578 13.45436 11.4224 12.79343 13.04898 15.73146 15.33158 13.21905 14.24352 13.77445 13.27233 15.71018 + 10.87577 15.47127 14.70888 17.73984 17.70155 15.23704 14.3281 14.20854 15.22783 16.9904 18.81393 15.66493 12.64094 11.61629 12.53687 12.72079 15.53799 15.56939 13.56948 13.97123 13.99972 13.84164 15.74528 + 9.678106 15.093 14.73196 18.04266 17.9735 15.98976 14.84349 14.29395 15.38192 17.14681 18.48132 15.55414 11.81447 11.31875 12.51214 13.46786 15.07734 14.72639 12.89217 14.18221 13.942 14.46083 16.30935 + 9.27702 15.11397 14.6864 18.26455 18.06617 16.64905 15.23202 14.50276 15.79894 17.31784 17.91948 15.71456 11.84832 11.10479 12.36307 13.02193 14.84241 13.94248 12.61262 14.14687 13.61714 14.61513 16.65529 + 10.31116 15.20001 14.41779 18.20655 17.87181 17.37104 15.80053 14.73345 16.33781 17.26945 17.61073 15.76432 12.14948 11.87968 12.84734 13.35572 15.99964 15.05299 12.98151 13.59093 13.31608 14.26525 16.48788 + 11.03995 15.58457 14.74849 18.04535 17.62146 17.83399 16.13437 15.02326 16.80089 17.59974 17.02293 15.01341 11.93478 12.57936 13.6127 14.46719 15.84169 14.35661 12.21926 13.44742 13.43839 15.56678 17.01775 + 10.68538 15.81025 15.13098 18.21277 17.8306 18.10699 16.28154 15.16807 17.16661 18.57889 16.92452 14.87336 11.62055 12.73409 14.09998 15.07829 15.77258 14.38338 12.46635 13.38107 13.42031 15.66041 16.70012 + 10.80705 16.10119 15.46497 18.4098 18.0212 17.97135 16.10877 15.19762 17.24214 18.58634 16.91249 15.42679 11.88585 13.91432 14.45943 15.74949 15.78372 14.54293 11.42183 13.62915 13.34823 14.55129 16.14689 + 11.94579 16.89884 16.24403 18.55092 17.98724 17.20735 15.26665 14.65565 17.00868 18.47542 17.43419 16.03572 12.83943 14.0368 13.85004 14.82996 15.17481 14.47794 11.56922 13.58228 13.62983 13.59302 13.86895 + 12.38524 17.37505 16.7696 18.47446 17.26857 15.96833 14.24874 13.03042 16.26422 17.79273 17.16697 15.42565 13.5957 14.48988 15.57542 16.0727 14.58922 13.24106 11.54757 13.28529 14.40883 13.49071 12.73336 + 13.03429 17.5047 16.87468 18.99148 17.70904 16.97621 14.13228 12.62222 14.45247 16.32981 16.61537 14.76507 12.27121 13.29012 13.69145 14.77818 13.90872 12.32405 11.65774 13.06149 13.20633 12.79723 14.08691 + 13.543 17.20046 16.71364 19.24344 17.89848 16.23823 13.61446 12.58731 13.79848 15.94572 15.83083 13.94334 12.33336 12.76893 13.26038 13.66663 13.53549 12.52763 10.97694 12.18569 12.12396 12.52927 14.61442 + 13.93316 16.19917 16.60253 18.37887 16.34868 15.3344 13.60281 12.46623 14.00873 15.9596 15.67228 13.47866 13.02688 12.05685 12.97747 13.6599 13.39636 12.42592 11.40474 12.42729 12.39029 12.35651 14.67778 + 14.61915 17.17112 17.5472 19.37409 17.49985 15.90717 13.63423 11.95899 12.93315 15.14544 15.01936 13.25283 12.0639 11.61034 12.99201 13.5629 12.46684 11.77172 11.48846 12.21882 12.39871 12.28264 13.6378 + 15.01957 17.45677 17.19109 18.36874 17.45291 15.49117 13.40853 11.98515 13.43848 14.47075 13.50971 13.02251 11.71136 11.68848 13.16979 13.33672 12.58453 12.35258 10.77152 11.58895 12.66962 12.02104 13.51065 + 14.3445 16.08383 16.46618 18.37986 17.49754 15.51561 12.724 11.01192 12.56261 14.75061 13.70792 13.23755 11.97928 11.47003 12.88496 13.19709 12.51916 12.02072 11.23445 11.98222 12.57755 12.58092 13.76349 + 14.65777 17.34103 16.21595 17.87756 17.01974 14.35497 12.22902 10.17611 12.75461 14.43687 13.74576 11.96353 11.00994 10.55103 11.22951 12.06966 11.58648 11.32696 10.70369 11.09274 11.97977 11.15652 12.0545 + 13.84403 16.75669 15.79734 15.90669 15.87204 12.81713 11.09628 9.869988 11.99412 13.88495 12.05193 12.04093 9.865374 9.98888 10.54978 11.08329 11.51721 11.287 10.19415 10.19129 11.11317 10.82287 11.26695 + 12.03434 15.45859 15.11167 15.11641 15.16317 14.43024 11.65586 9.923259 11.36602 12.3641 11.81006 10.42863 9.54241 9.423545 10.06809 10.60886 9.986245 10.09022 9.895988 9.939384 10.39964 10.69043 10.78549 + 11.62159 13.6024 13.63344 15.00115 13.6637 13.22205 10.87602 8.768591 9.799749 12.06331 11.48621 10.03431 9.139547 9.069712 9.670283 10.33705 9.896729 9.732963 9.743416 9.657762 10.36899 10.26934 10.40027 + 11.81183 14.29564 13.63866 13.6298 13.18893 11.65243 10.08644 8.713807 10.15905 11.12224 10.93487 10.20999 9.452074 8.578758 9.197603 9.436418 9.835726 10.35768 10.15316 9.921681 10.65349 10.78003 10.5952 + 12.90228 14.67107 13.85628 14.29541 14.35086 11.63753 8.860591 8.425612 9.22795 10.41009 10.2974 10.03016 9.485574 8.96593 9.532566 10.04701 10.0559 10.14437 9.809213 10.09447 10.73098 10.48613 10.97839 + 12.14247 14.62703 13.12694 13.76217 13.40734 11.91112 10.03432 10.69592 11.79923 14.96772 14.89136 11.78498 11.03141 11.00658 12.59778 13.80232 13.4921 12.70364 11.11213 11.89696 12.66712 11.61696 12.02989 + 12.4993 14.9535 13.76636 14.01113 13.60918 11.75265 9.318426 8.455474 10.17039 14.04918 14.17533 10.41443 9.469222 8.986806 10.21098 11.43141 11.99639 11.92311 10.10373 10.33753 11.20988 10.83258 10.75896 + 12.68394 14.34959 12.56896 14.07434 13.22602 10.90166 8.476089 7.972132 8.819757 12.4768 12.05638 10.18155 9.379935 9.701678 9.932637 10.54168 10.334 10.5734 9.954249 10.09657 10.31382 10.49024 10.26939 + 11.75025 13.98052 12.16752 14.10251 13.03628 9.846658 8.836266 7.344621 8.59577 10.93273 10.1303 9.196149 9.388488 9.020345 10.06814 10.79366 10.47544 10.58243 10.03372 10.03065 10.42077 10.64656 10.34247 + 11.83129 14.04893 12.96955 13.20662 12.82525 9.696637 7.323474 8.177329 8.925125 10.40487 10.10953 8.585592 8.994603 8.708897 9.25738 9.843696 9.578411 9.801521 10.00724 10.09079 10.59092 10.64287 10.58762 + 9.790665 12.15129 12.64895 13.58438 12.95563 9.711924 8.575097 9.001645 8.701345 10.52208 10.09897 8.517282 8.199652 8.714253 9.041263 10.04511 10.11496 10.00863 9.805107 10.16271 11.0758 11.04546 10.75644 + 9.414765 12.83946 11.98675 12.726 12.46979 9.267152 9.039246 8.176429 8.053612 10.21454 10.41292 8.622193 7.66354 8.369621 8.642668 9.53128 9.865145 9.638438 9.991666 10.25773 10.73875 10.72115 10.65728 + 8.779566 11.93363 11.50436 10.50424 9.622196 9.267383 8.582248 8.53771 7.890744 10.04505 10.20443 8.095118 8.612964 8.879345 8.707826 9.227075 9.176577 8.984758 10.03803 10.03335 10.5386 10.95927 10.46486 + 10.31262 11.88174 11.99282 11.90945 9.594206 8.671169 8.318475 8.181697 8.13483 10.15049 10.25244 7.071458 7.442567 8.785149 9.691864 9.679538 9.605824 9.961757 10.42259 10.17275 10.75009 11.18037 10.72504 + 9.364248 11.96104 11.57661 12.01562 10.75383 6.945975 7.367255 7.736284 7.312432 8.969963 9.153383 7.523694 7.927206 8.369026 8.890183 8.625024 9.480546 9.502405 9.561119 10.14811 10.62848 10.7046 10.59589 + 8.967705 11.99061 11.26416 11.1752 9.580898 7.045003 7.880908 7.853495 8.063289 8.442639 8.374674 7.542927 8.712921 9.363231 8.787829 8.940651 8.762001 9.656484 9.560316 9.75022 9.998157 10.96855 10.83991 + 10.79417 10.95528 10.71563 9.979272 8.763993 8.042848 7.088778 6.67021 7.448594 8.206177 8.038567 8.22692 8.547086 7.783141 9.548368 9.043281 9.180702 10.15911 10.00978 9.834736 10.7707 10.82731 10.66534 + 9.814768 12.13082 11.35585 9.269568 8.184406 7.729397 6.804678 6.910729 7.282489 8.309008 8.415974 8.394214 7.598199 8.050271 7.802004 8.228464 9.248132 9.948934 9.981988 9.834879 10.65781 10.51871 10.32499 + 9.652328 11.7325 10.80005 9.37675 9.013214 7.384077 7.177258 6.372046 7.152434 7.877509 7.752526 8.609852 8.207765 8.032439 8.76918 8.827532 10.00151 9.213638 9.988755 9.774078 9.915665 9.921049 10.41844 + 9.466437 10.99616 10.88723 9.492371 8.961466 7.342728 7.358507 7.339148 7.301462 8.448383 7.55255 8.734881 8.417077 8.13668 8.47903 9.036981 9.332474 9.148084 9.903509 9.827729 9.972055 10.26808 10.0221 + 9.64337 12.05854 10.97252 10.21088 9.235108 7.101156 7.432653 8.052295 7.081014 6.580639 7.278504 8.679576 8.280871 7.73062 8.75071 8.532309 9.329696 9.757581 9.649004 9.204338 10.25986 10.47111 10.64369 + 8.506092 11.0128 10.68274 8.38584 7.542353 7.152045 7.326688 7.308694 7.642912 8.45325 8.044881 7.742783 7.756063 7.661537 8.275199 9.033102 9.546831 10.0107 9.767799 9.575422 10.45731 10.67947 10.68756 + 9.190603 9.530713 10.03561 9.930932 8.509491 7.016098 7.502091 7.892929 8.24372 8.307619 7.979042 8.022539 7.607509 8.970244 9.02056 9.152427 9.395686 9.654662 10.00138 9.774302 10.25611 10.34001 10.50237 + 8.055989 10.48079 9.946203 8.668516 8.405702 6.663228 7.840029 7.953751 7.417573 8.407804 8.318707 8.09545 8.268252 8.237886 8.408285 9.147687 9.502143 9.359609 9.642401 10.35548 10.85097 10.35253 10.09162 + 8.523983 10.58812 10.45586 9.346948 8.423094 6.955203 7.860735 7.061844 7.640428 8.098751 8.684567 8.348521 8.667695 8.787177 8.56147 9.256539 9.863892 9.601056 9.600217 10.26275 10.53992 10.67695 10.53019 + 8.54944 10.105 9.919378 8.700562 8.344156 7.651311 7.920049 8.247942 7.364244 7.392271 7.95518 8.640728 8.450232 9.633104 9.684301 9.891527 10.32047 10.18789 10.04646 9.441429 9.973019 10.92726 11.09868 + 8.736779 10.67164 10.37507 8.660881 7.410948 6.964188 8.062383 8.657798 8.175787 8.124825 8.552548 8.639585 9.223927 9.4434 9.095827 9.018662 9.108647 9.592525 9.690062 9.847681 10.15555 10.88235 11.25451 + 9.102138 10.24407 9.305552 7.972584 7.585355 7.130199 8.330059 8.346169 7.620663 7.047201 7.893063 7.923952 8.201685 8.679209 8.079769 8.701505 9.398625 9.830337 10.10372 10.45054 10.39745 10.80141 11.24109 + 8.334327 8.800347 7.70179 9.206788 7.814823 6.755373 8.229139 8.560562 8.269526 8.002664 8.831103 8.463894 8.141541 9.049789 8.658106 9.396982 9.033927 9.765074 9.622846 9.758422 10.27184 10.49305 10.98647 + 8.128156 10.12106 8.910188 8.762326 7.666012 6.949401 7.950629 8.1798 7.218674 8.2833 9.230454 8.431417 8.010346 8.432215 9.385073 9.408096 9.264348 9.842328 9.402944 10.16326 10.44512 10.27258 10.61046 + 9.777776 10.17425 9.162596 8.756527 9.402295 7.245045 6.706331 7.813528 7.5786 6.985806 8.648697 8.302122 8.295655 9.284019 9.676419 9.26071 9.179568 9.636398 10.1599 10.62567 10.66345 10.83598 11.00543 + 9.4869 9.121305 8.684687 8.700015 9.641527 9.05888 7.269274 8.791707 8.11694 7.627497 7.894124 8.112581 8.470985 9.387035 9.151163 9.710186 10.16911 10.16346 9.604074 9.987885 10.31005 10.77668 10.24039 + 7.866471 9.883156 9.715327 8.677911 9.516367 8.34246 6.843951 7.838072 7.583541 7.852625 8.79549 7.970304 7.762091 8.846987 8.563057 9.384929 9.697865 10.01375 9.721039 9.735113 10.16057 10.63224 10.43368 + 9.080318 8.246053 9.041588 9.417509 9.518696 7.989542 8.403717 7.463982 7.206106 7.981693 8.578013 8.107899 8.514518 7.873508 8.147607 8.765822 9.033931 9.63969 10.04347 10.20652 10.35496 10.39911 10.52451 + 9.13128 9.074238 9.046019 9.162777 9.203651 7.817907 8.81809 9.218652 8.689803 8.523691 8.246085 7.754849 8.768758 8.798193 9.144502 9.340219 8.860093 9.865606 10.48086 10.25486 10.56614 10.47177 10.82067 + 7.529797 9.080559 9.025809 9.092778 9.069881 7.946015 8.707859 8.578739 7.658888 7.688912 8.116105 8.22648 8.638034 8.394612 8.661798 9.266599 9.497275 10.0152 10.51752 10.32173 10.39726 10.61129 10.75329 + 8.053345 7.806293 9.592606 9.250229 10.12409 9.191865 7.827196 8.112508 8.535282 8.097424 9.000211 8.186003 8.92979 8.464402 8.670289 9.216187 8.820874 9.527074 9.87499 10.41622 10.59681 10.51637 10.55027 + 8.95764 9.454929 9.582647 10.45938 9.56032 9.433331 8.428452 8.183547 8.032114 8.346304 9.071006 7.965348 8.02034 8.698916 8.684159 9.147419 9.093694 9.894665 9.954333 9.885933 10.35833 10.71119 10.45288 + 8.644167 9.48673 9.193418 10.94477 10.83592 10.44026 9.28124 8.387064 8.084641 8.072067 8.342614 7.222877 7.512965 8.233858 8.808452 9.225642 9.137754 9.547808 9.455416 9.747941 9.971579 10.64146 10.5402 + 9.362679 9.369581 9.729918 10.51496 11.36854 10.06961 9.509526 8.469991 7.580379 7.402191 8.035908 8.038517 7.526035 8.179871 9.229591 9.114322 9.371479 8.994488 10.0342 9.992566 10.37911 10.48729 10.1093 + 9.234426 10.02822 10.1721 10.36807 11.38841 10.48724 9.894856 8.55321 7.932958 7.556772 6.919079 7.997473 8.420939 8.136166 8.576433 8.605513 9.379736 9.246395 9.551528 10.12443 10.13093 10.29004 10.01479 + 7.140229 7.340988 9.690434 11.22617 10.54758 9.907132 10.23349 7.846205 7.38694 7.756484 8.138117 7.949685 8.286037 7.875541 8.682808 9.680047 8.736238 9.250605 9.806664 9.929686 10.29157 10.88262 10.53922 + 7.609503 8.446361 7.927704 10.19232 9.981958 9.750368 10.16796 8.132822 7.723164 7.520811 7.855199 7.349501 7.666749 8.55197 8.498571 9.580915 9.49983 9.968874 9.624427 10.19383 9.79729 10.48853 10.77697 + 8.643137 8.622834 9.379179 10.04107 10.14056 9.3327 9.441696 7.66965 6.918215 7.325087 8.433827 7.403569 7.932473 9.355262 8.961336 9.433534 8.987287 9.442863 9.373944 9.75194 9.910201 10.3459 10.44656 + 8.102217 8.064889 10.64028 12.49552 11.73663 10.07672 9.110008 8.321954 7.095238 7.711002 7.663853 7.130478 7.74688 9.022202 9.092591 9.239534 9.148391 9.078291 9.648328 9.942706 10.50384 10.56971 10.62924 + 9.490426 8.373809 10.40654 12.21494 12.46647 10.44266 9.246642 8.188566 7.077143 7.664156 7.372887 8.266142 8.489851 8.568101 8.872865 9.148156 8.987501 9.957211 10.15845 9.909985 10.00002 10.26888 11.01089 + 8.798372 9.025386 11.31425 11.42679 10.40138 9.216152 9.422606 8.502447 7.214077 7.507916 7.581383 7.426453 7.74972 8.678537 8.401515 9.35175 9.522068 9.689368 10.34969 10.18144 10.10829 10.22423 10.66045 + 8.333779 8.557695 11.21921 11.96504 10.46627 9.176018 9.10099 8.930181 6.847103 7.599282 7.893191 7.544654 8.840031 8.585423 8.405005 8.922948 9.007273 9.288807 9.946635 10.77755 10.62705 10.41732 10.66157 + 7.559142 9.412998 10.48524 11.38948 11.17182 9.945296 9.195613 7.687301 7.509073 7.597322 8.275542 8.369865 7.823867 8.430854 8.52591 8.722355 9.220226 9.689714 9.526666 9.805052 10.34926 10.81544 10.53098 + 11.4918 14.00084 16.17197 16.42617 15.349 12.41796 11.39773 11.66957 13.16477 16.12451 15.81399 11.20098 8.846268 9.76712 11.6804 12.1915 12.09149 11.27532 10.66243 11.10545 11.89396 11.18614 11.62189 + 12.7754 14.8082 17.40473 16.74525 14.93741 12.71457 10.89088 11.37673 13.37231 16.26893 15.81663 11.01917 8.918041 9.566416 11.83768 12.05284 12.66729 11.85449 10.60257 11.00357 11.39586 10.96889 11.1296 + 13.36541 15.34315 17.2086 17.66689 14.65784 13.48884 12.4741 12.45936 13.42722 15.72825 15.79719 14.43064 12.889 10.51247 12.3019 12.30175 12.62188 12.04293 10.42985 10.74694 10.95862 12.90817 13.67789 + 14.24038 16.24807 17.4501 18.67464 18.25082 16.41788 13.84389 13.58693 15.07749 17.34456 17.16127 16.20167 15.06416 11.77578 12.49449 13.45164 13.06607 12.11281 10.48501 10.73691 11.01208 15.49132 16.34476 + 15.46261 17.19522 17.5746 18.0107 18.14651 16.75021 13.80587 13.41014 14.9592 17.26956 16.178 14.19134 11.93522 11.6239 12.82757 14.12765 13.70803 12.50571 10.75355 11.29876 11.34688 16.21528 16.64844 + 15.85424 17.58728 17.14469 17.19608 18.15673 16.2019 13.98389 14.04934 14.00954 16.88185 15.97725 14.16334 13.0995 11.42474 11.70505 13.47377 14.31889 12.8265 11.54771 11.79184 11.55733 14.441 14.84616 + 16.01069 17.67813 17.21562 17.56873 18.24154 16.43555 14.16117 14.16953 13.69155 17.32711 16.4162 13.46104 12.36254 11.61799 11.27409 13.07141 14.62038 12.76868 11.54521 12.79095 12.39311 14.58929 14.84247 + 16.09941 17.7363 16.53284 17.27621 18.18447 16.58593 14.33624 14.35762 14.19332 17.44104 17.00833 13.70702 12.45344 12.21988 13.11712 14.56761 16.01712 13.74598 12.13099 13.26468 13.10818 15.27417 15.56255 + 16.42318 17.98415 16.44273 16.57448 17.62833 16.28909 14.36828 14.6948 13.78254 16.22694 15.95521 14.12572 12.26701 11.85083 12.46605 14.29742 16.13456 13.84185 12.26186 12.63923 12.99921 15.56506 15.71164 + 16.66626 18.41392 16.23549 16.69583 17.69269 16.16858 13.91554 14.21553 13.17823 15.73793 16.4865 15.50647 12.3832 11.67269 11.71129 12.98511 15.19916 14.09333 12.14127 13.53028 13.66361 16.01434 15.8486 + 16.93131 18.80218 16.81364 17.33029 16.89452 15.53996 14.12277 13.81145 13.03909 15.09264 16.55081 16.00311 12.3552 12.25566 11.77334 12.40888 14.75922 13.7299 12.90318 13.40209 14.23598 16.61812 16.00201 + 17.09422 18.93463 16.77671 17.12045 16.87137 15.12399 13.50143 13.57805 12.80646 15.09097 16.1608 15.57011 12.32053 11.78571 11.45637 11.75474 14.11037 13.6446 12.81923 13.51655 14.85688 16.60715 15.62787 + 17.18843 19.15834 16.90851 16.13819 16.45988 14.99436 12.40748 12.04965 12.14001 13.26226 14.0558 13.51968 12.57948 11.99215 11.86336 12.04673 13.09016 13.15625 12.4888 12.85295 14.979 16.59832 15.41977 + 17.2978 19.31523 17.34639 17.72629 15.35329 13.78927 12.61048 11.38221 11.57769 13.06238 13.21393 12.3947 11.35336 11.29097 10.76642 10.99077 12.29214 12.08838 12.20228 12.01544 11.86961 12.86051 13.13075 + 17.29455 19.39194 17.52913 18.08251 14.94142 11.73378 12.26912 13.0758 11.84843 11.48248 13.48376 13.39672 11.70031 11.98227 11.31891 10.61537 11.5765 11.5006 12.25064 12.40025 11.37915 12.90368 12.65543 + 17.11082 19.30302 17.25127 17.65386 14.90019 12.53856 11.45692 13.26181 11.62685 11.53579 13.36939 12.93143 11.07778 11.12064 10.86599 10.39632 11.41168 11.49042 11.78741 12.00982 11.57957 12.68554 12.28857 + 16.81596 19.11267 17.10374 17.62917 15.10644 12.49786 11.83172 13.11271 12.08539 11.51897 12.27075 12.25029 10.05081 10.51726 10.23621 9.723547 11.47403 11.38655 11.75697 12.38226 11.68046 12.32908 12.25521 + 16.18891 18.7452 17.12043 17.90645 15.35898 12.87751 11.93357 11.34936 12.12841 12.00346 12.70253 13.15777 10.02708 9.598401 9.508228 9.663599 10.49346 10.55344 11.21704 12.04108 11.25043 12.10459 11.84318 + 15.3587 18.25852 17.01021 17.94506 15.36013 11.68553 10.90639 9.600526 11.48932 11.83768 11.86593 12.23622 10.37516 10.22231 9.242025 9.690022 10.79908 10.51446 10.66708 11.09983 11.20814 11.95468 11.32153 + 14.94686 18.0086 16.66558 16.91915 14.43537 11.78186 10.37191 10.1009 10.64833 11.51232 11.21707 11.44729 10.38132 10.89793 11.71739 11.39077 10.99349 11.08029 10.52239 10.70206 11.1781 11.64449 11.60586 + 15.2814 17.80077 15.7858 15.0733 13.19273 10.99025 11.4507 10.23576 11.88874 11.69319 11.04233 10.65287 11.20542 12.47354 12.96547 12.31134 12.59687 12.5441 11.05464 11.68961 12.18429 12.52163 12.24349 + 15.48732 17.81838 15.57828 14.95755 13.17332 11.15086 11.35037 10.27524 11.15464 11.83677 10.82353 10.86964 10.84648 12.2185 12.01063 11.35811 12.14788 12.13265 10.27622 11.16301 10.85313 11.33077 11.19722 + 15.64028 18.04706 15.90875 13.46693 12.71794 10.47495 10.41679 10.41669 11.51304 10.97092 10.52732 10.51946 10.18956 11.28483 11.5248 11.06633 10.79301 11.08132 10.48834 11.23098 10.90977 10.93761 11.2342 + 15.691 18.08693 15.95903 14.5647 13.08687 11.96817 11.98348 10.27445 10.82906 10.65066 10.7676 10.49903 10.69828 10.99068 11.62179 10.48239 10.64531 10.73122 10.30917 10.84842 11.01107 11.19445 10.96024 + 15.70235 17.87411 15.54562 14.17106 13.73759 13.39945 12.94871 11.86754 12.32136 13.34113 14.04813 13.64429 12.8817 14.03526 13.78847 14.31763 14.08296 12.28475 12.26945 13.41342 15.16624 15.82314 14.65862 + 15.111 17.38759 15.23328 14.86325 16.06344 16.10236 14.34899 14.72656 14.66246 16.20999 16.94175 17.2274 15.76645 16.78901 16.12683 16.99647 16.62846 13.99079 13.86968 16.12244 18.25901 19.34782 17.66724 + 15.03301 16.61184 14.17075 13.25705 14.54141 14.69707 13.71418 15.49278 15.08241 16.18828 17.16152 16.65724 15.34151 13.99803 13.97429 15.01542 15.35275 14.87416 14.55297 15.45949 16.31717 18.42344 16.47798 + 13.45951 15.78569 13.98534 13.04344 13.92377 14.24774 14.06853 15.00025 15.36809 16.57527 17.38427 17.02029 15.19632 14.64439 14.65536 14.64023 15.68649 15.49167 14.9104 15.35289 16.33175 18.0865 16.37584 + 13.07347 14.7915 12.67954 13.23848 14.1819 13.9683 13.21752 14.69234 17.90232 17.24359 17.5149 18.04125 15.55333 15.03565 14.85524 15.23376 15.60953 15.83141 15.17968 15.23778 16.42048 17.88067 16.92698 + 12.37712 14.9033 13.8624 13.76913 13.30324 14.3478 14.02929 14.99968 18.80645 18.39549 18.51047 18.57759 16.0035 14.5608 15.19788 16.24825 16.87722 16.25792 15.09623 15.11478 16.32289 17.68389 16.08194 + 12.66646 14.60857 12.63233 12.64944 13.4228 13.81045 14.2454 15.89087 17.83408 17.56272 18.75677 18.45625 15.79576 15.1664 15.52465 16.30447 17.33408 16.53073 14.91176 15.41308 16.24126 17.18274 15.97926 + 12.81794 14.79919 12.64581 13.73917 14.27735 12.86728 13.65656 16.11627 17.94633 17.98828 18.37813 18.78796 17.11118 16.00847 15.83047 16.21775 16.80949 15.5775 14.92074 15.60607 15.80383 16.84433 15.66324 + 12.5034 15.02809 13.34704 12.59922 13.16282 13.18943 13.6498 16.22042 17.74181 18.11487 19.01717 18.76182 16.68096 15.37317 16.47843 15.88386 16.60799 16.23045 15.78561 15.60193 15.58282 16.25681 16.40265 + 13.06095 14.47213 11.57626 11.60358 12.57563 13.76851 13.97735 14.38274 16.31401 17.55055 18.43916 18.5317 17.04531 14.99704 16.53685 17.02856 17.58887 16.26225 14.9763 14.45201 15.51912 15.21459 16.6817 + 12.86505 15.85786 14.75419 13.01475 12.67307 14.4433 15.20655 15.15495 16.09785 16.51936 17.95182 18.31379 16.75537 15.01732 15.75735 16.82045 17.44093 15.66828 14.33217 14.62619 15.22149 15.17155 17.20671 + 13.12523 16.5502 15.50594 15.34845 14.73641 15.80987 16.79601 17.06586 17.48354 16.89506 16.9989 17.87918 16.51353 15.33819 15.52597 17.10384 16.88856 15.24374 15.05034 15.51818 15.27853 14.60615 17.90415 + 12.69545 15.62294 14.66864 16.31116 15.64141 15.63712 16.38996 17.50281 17.90165 17.66326 17.61484 17.62272 16.00488 15.10813 14.94744 17.00407 17.49522 15.62474 15.2027 15.69592 15.58376 14.26855 17.33854 + 11.70221 15.91556 15.21819 16.73549 15.93421 15.34525 15.80453 16.35826 17.22446 17.98482 17.83851 17.86388 16.83107 16.10304 15.82976 17.13237 17.91541 15.26026 15.10075 15.98048 15.72246 14.22937 16.43557 + 12.58039 16.54533 15.48762 17.14302 16.20825 15.26482 15.79737 16.80904 17.53083 17.92554 17.57588 16.92835 16.21835 16.24897 16.26789 16.09962 16.68681 15.19278 14.3501 15.73407 15.31177 14.58012 16.44425 + 12.52427 16.68682 15.6768 17.41134 16.48487 16.26741 15.93352 16.79063 17.23722 17.75588 17.59963 16.55783 16.45605 16.19305 16.34012 15.8233 16.01214 14.49679 14.05411 15.83327 15.75738 14.70327 16.81463 + 13.08901 16.92737 15.88609 17.48892 16.60913 16.92618 15.98914 16.84229 16.96335 17.23575 17.33563 17.23502 16.5829 16.31644 16.85165 16.5601 16.59091 14.8695 14.04865 15.1961 15.04456 15.03072 16.76081 + 13.25131 16.82976 15.65168 17.33111 16.53403 16.99053 16.30228 16.67911 16.8145 17.26553 17.52997 17.40855 16.6225 16.19843 16.2693 16.43582 16.03738 13.90187 14.50136 15.9589 15.88366 15.52198 16.96512 + 12.86011 16.4959 15.32254 16.57291 15.98928 16.51817 16.59031 17.21095 17.32273 17.24012 17.26693 17.4594 16.63605 16.95958 17.0414 16.83618 16.73441 14.66106 14.54188 15.98279 15.59871 15.42995 16.82753 + 12.3734 16.38073 15.23618 15.71347 15.76168 16.80319 16.83652 17.01589 17.01722 16.75942 16.97372 17.74806 16.4648 16.73439 16.26514 16.33158 16.27448 13.83779 14.71891 15.37935 14.90931 15.09158 16.92881 + 13.16311 16.98702 15.90418 16.86047 16.45071 17.14736 16.50004 16.52925 16.95103 16.78541 16.71283 17.1481 15.68781 16.3924 15.7757 16.42943 16.24413 13.86384 14.62436 15.48191 14.93738 14.84517 16.21951 + 13.59746 17.23912 16.12656 17.50044 16.93601 17.43536 16.78477 16.41955 16.75596 16.41606 17.25539 17.49482 16.25328 16.44511 15.88677 16.52536 16.53255 14.46775 14.81169 15.79123 14.8385 15.43922 17.17949 + 13.25004 16.94229 15.93826 17.73337 17.23859 17.89079 17.34496 15.97857 15.19893 15.15988 16.91029 17.25496 16.62859 16.25416 15.48432 15.56355 16.21918 14.83444 15.0622 15.81797 15.64522 15.32866 16.89268 + 12.62537 16.47641 15.74364 17.85862 17.47602 18.16114 17.55453 15.95657 15.11439 15.08639 16.68707 16.74309 16.72464 16.83375 15.94381 16.12422 16.87457 15.30209 14.94832 16.63556 16.4691 15.57759 17.3871 + 12.6226 16.10576 15.56736 18.04713 17.56757 18.11768 17.59097 16.34696 15.375 14.14986 15.13968 15.70977 16.85783 17.35108 16.21252 16.49694 16.90446 14.97466 14.87337 16.36794 16.10093 15.90252 17.58858 + 12.7855 15.88645 15.67985 18.15505 17.37622 17.52381 17.42296 16.19477 15.50058 13.98604 15.38529 15.37528 15.93515 16.47197 15.3381 15.97862 16.40454 14.95582 15.0605 16.63037 15.63746 16.15078 17.13511 + 12.65207 15.56413 15.72229 18.15676 17.02139 16.67873 16.7427 15.08276 14.19359 12.79751 14.48611 15.64405 16.11738 15.6327 14.42156 14.5413 15.50612 14.09361 14.9137 16.91826 15.45331 16.1494 17.52712 + 12.56314 15.1539 15.81251 18.0403 17.20735 16.9832 15.43454 13.55827 13.10696 12.59839 13.23814 15.90523 16.60976 15.61129 13.46397 13.59857 14.55115 13.29045 14.68942 16.51844 14.84454 16.06714 16.90926 + 12.64222 15.16426 16.14207 18.01224 17.03122 16.82287 13.88338 12.87623 12.53724 12.11878 13.98522 15.42566 15.64351 14.8041 13.28969 13.65548 14.46269 13.32662 13.92385 14.84129 13.31076 15.09256 15.82777 + 12.85059 15.56986 16.29726 18.16373 16.31842 15.27977 12.30446 12.48871 12.05114 12.02475 13.32861 15.53581 15.44006 14.6188 12.19517 12.34837 13.52474 12.41385 13.52724 14.1898 12.92852 15.22741 15.49917 + 12.59761 14.4621 15.45572 17.18511 15.81542 15.78824 11.84854 11.87338 12.32981 11.94361 12.86972 14.26315 14.89518 14.27585 11.97713 12.49144 13.58669 12.65414 13.09633 13.75735 14.88413 16.18767 15.3166 + 12.64882 14.81147 14.45962 16.38583 15.72507 15.59489 12.376 11.95343 12.09104 11.86625 12.24266 13.68499 13.91463 13.24626 10.7595 12.1026 13.03325 11.85887 12.62869 13.85928 16.20368 18.33456 16.91249 + 12.12053 15.05175 14.22981 15.73134 14.97451 14.68129 13.12028 11.97819 11.38219 11.20992 11.37042 12.24603 13.25563 13.08652 10.43595 10.82252 11.63596 11.33452 11.86574 13.24061 15.52648 17.4119 16.81245 + 11.19065 14.21162 13.51477 14.70132 14.9847 15.25536 14.19143 12.48769 11.34493 9.814653 9.99059 11.53341 12.29802 11.784 10.16933 10.75036 11.20462 10.74964 11.48954 12.01296 13.41964 14.80476 14.38404 + 10.00999 12.74599 12.14843 14.51654 15.13391 15.2609 13.72731 11.66748 10.16199 9.022615 10.54656 11.04408 11.06597 10.65407 9.941639 9.674333 10.40516 10.81487 11.25098 11.6855 11.23007 11.83496 12.85696 + 10.08466 11.83495 11.54785 14.59242 13.85901 13.03223 12.44154 11.23707 9.762304 8.823195 10.68114 10.62345 10.76976 10.03955 9.436696 9.586202 11.22563 10.83237 11.0967 10.70838 10.57485 11.49634 11.70702 + 10.48889 12.83749 11.55844 14.30776 13.98957 13.29756 10.27051 11.19239 10.73191 8.282329 9.798191 11.28903 11.10856 10.43637 9.679453 9.696969 10.23166 10.5042 10.2436 10.84797 10.9094 11.14272 11.04821 + 10.19536 11.74172 11.33944 13.41399 13.06223 13.24977 10.40681 9.719469 9.504123 8.071562 9.28103 10.37499 11.08919 10.83884 9.125082 9.426393 10.12565 10.30615 10.62199 11.02502 10.98999 11.49767 10.76202 + 10.88223 12.977 11.56636 10.88618 12.75476 12.96493 10.24409 9.20118 8.650577 7.399164 8.780226 9.993423 11.50119 11.16004 9.309609 9.911741 10.38707 9.787494 10.5507 10.78142 10.8794 11.07239 10.47661 + 10.47173 13.69682 12.45944 12.35382 12.58801 12.46847 10.00025 10.27305 11.53972 12.61611 13.00516 13.0168 12.24968 12.74772 13.46355 13.35037 12.67452 11.07662 10.77348 12.34726 13.52226 13.54476 12.45269 + 10.39999 12.38218 11.96567 12.83998 11.78783 11.14596 10.65086 9.983682 11.39105 12.32884 12.81081 12.60661 11.70481 12.39304 13.20282 13.14799 12.67365 11.12111 11.64005 15.3785 17.26889 19.2809 18.58731 + 10.06194 11.97701 11.10652 11.85843 11.02539 11.2265 10.7828 9.873035 10.32476 11.25525 11.96012 12.71968 11.64478 11.76205 12.47315 13.18736 12.99789 12.38262 13.63724 17.6046 20.06792 21.4036 21.43807 + 9.509731 11.85916 11.10777 12.50954 12.18235 11.80091 11.27237 10.29496 12.2601 12.32322 13.42 14.12483 13.16052 13.71877 13.45849 13.22503 14.22825 13.49225 15.37626 19.22064 21.55962 22.73142 22.24018 + 9.15207 11.07175 10.86126 11.87928 12.69756 12.46886 11.5245 13.08445 13.88625 14.80505 14.99481 15.52319 15.05282 15.02273 14.87423 15.29251 15.91438 14.93134 16.80274 21.40077 22.54545 22.58663 22.29738 + 8.860111 10.74111 10.50416 12.93807 12.55079 13.06175 12.17259 13.51095 14.48125 15.87022 17.08365 15.86352 15.00874 14.95488 15.34727 15.18754 16.02795 15.03839 16.93264 19.53366 21.6056 21.50261 21.28786 + 10.75877 10.37344 10.58075 12.34408 12.94353 11.96778 11.4975 12.68404 14.95128 16.74927 16.77153 15.63198 13.98707 14.3226 14.68699 15.35497 16.36192 15.49951 15.98009 17.65331 18.19219 18.41393 18.1715 + 9.39485 10.52738 11.42019 12.21228 12.45682 12.18025 11.33037 12.44518 14.51794 15.84477 14.62877 14.42839 14.07113 13.2896 13.65752 13.82688 15.6779 15.75582 15.67209 17.08607 16.55664 17.27306 16.92216 + 9.381274 11.83695 11.8863 11.62455 11.99966 11.92766 12.16977 12.76743 14.2169 15.01731 14.82383 13.0397 13.42383 13.10484 13.42112 13.71131 14.73178 14.64882 14.79978 16.09834 16.16719 17.4647 16.77292 + 9.585428 10.993 10.5035 11.81598 11.44083 12.43314 11.64416 12.2522 14.10823 14.41961 14.19406 12.61582 11.76546 12.10949 13.28017 13.21444 14.49621 14.27315 14.40105 16.25966 16.55149 17.44991 16.81313 + 8.175888 11.18484 11.35472 11.47145 12.36835 12.95294 11.74024 12.37499 14.70968 14.42316 13.73902 12.62927 11.59597 12.07641 12.50259 12.79706 14.06515 14.19124 14.13676 15.15194 15.65369 18.23908 17.15416 + 9.260625 10.70815 9.263671 9.806908 11.83224 12.31308 11.73443 13.4353 14.60034 15.05972 13.83386 12.88395 11.57649 12.02801 12.20291 12.59455 14.31687 14.47969 14.6405 15.52516 15.67438 18.1055 17.65921 + 9.911335 10.50611 10.12131 9.937845 12.0217 12.06643 12.41577 13.58587 15.64432 14.32141 13.08726 12.30006 11.95351 12.71829 11.26942 12.65187 14.17917 13.53116 13.70112 15.10717 14.98607 17.59751 17.46946 + 9.024209 8.987809 9.160165 10.86254 12.75719 12.29259 12.99271 14.01397 14.93609 15.38431 14.07591 12.15879 11.33199 11.99755 11.29723 12.28741 13.99432 13.90781 13.2847 14.09958 14.27385 16.84444 16.68974 + 11.9299 13.47678 12.837 11.34953 12.98505 12.79702 14.93977 14.99091 14.8044 15.23901 13.70815 12.30987 11.19355 11.14724 11.14456 11.97139 13.38016 13.39131 13.10051 13.16111 13.63137 15.1045 15.48153 + 14.41228 16.11685 15.7402 15.5474 13.80078 14.53132 14.65797 14.65964 15.91533 16.35481 13.96598 12.8188 11.46816 10.90168 10.61743 11.47446 13.77556 13.64553 13.89567 13.91599 13.57935 15.20967 15.69138 + 15.08783 16.8548 16.49383 16.41818 14.09665 15.30241 14.7049 14.89885 15.83918 18.0921 16.22671 14.06109 11.65885 11.18969 11.55042 12.64634 15.04636 14.6538 14.17054 14.18222 15.13279 16.13375 16.41827 + 15.16109 16.7801 16.56729 16.43837 15.12475 14.8846 14.26196 15.39288 16.01123 17.1432 17.07575 15.71479 12.39699 11.93664 11.86322 12.98023 15.49663 15.14287 14.6057 14.86261 15.40321 16.85805 17.07797 + 15.09084 16.57223 16.80362 16.58635 15.20792 15.10697 14.90998 15.40153 15.35094 16.88487 17.96318 16.37918 12.55137 12.20782 12.30409 12.03328 14.67583 15.03111 14.56669 14.42755 15.211 17.10543 17.10053 + 14.69408 15.81541 16.46612 16.37843 15.2229 14.59921 13.61355 14.3778 14.58977 16.18752 17.5792 16.62926 12.41473 12.08434 12.35257 11.9434 14.27981 14.79067 13.80497 13.96159 14.25573 17.45439 16.78282 + 14.43095 15.41959 16.21408 16.07892 15.18807 14.0085 13.96235 14.37799 14.08463 15.0561 16.641 15.33981 12.0121 10.58257 10.67117 11.07191 13.45955 14.30978 13.5234 13.39615 13.00758 15.30398 14.59562 + 14.58677 15.82187 15.58251 15.65558 15.2996 14.34471 13.48909 13.53928 12.62957 13.79965 15.50717 14.88582 12.17444 11.02395 10.55875 10.28314 11.87372 12.80804 12.80521 12.87114 12.06493 14.00245 13.72964 + 14.95683 16.3266 15.16857 15.4292 15.51012 13.59986 12.23133 12.66695 12.21721 13.14255 14.3418 13.26337 11.4309 11.01019 10.59134 10.42654 11.62912 13.19194 13.29006 13.23644 11.81969 14.27526 13.76118 + 15.59626 17.08241 15.06971 15.65379 14.82292 13.31317 11.43343 11.9617 11.85537 12.2777 14.29009 13.45866 10.72894 10.13381 9.537953 9.800446 11.53536 12.31185 12.60432 11.92848 11.34388 12.84455 12.54369 + 16.04304 17.82648 15.79309 16.24393 14.73321 13.42411 10.97739 10.73002 11.65459 12.07827 13.2604 12.44436 10.10683 9.841578 9.451722 9.291868 11.41494 11.8923 11.88242 11.91761 11.27107 12.20158 12.1325 + 16.25625 18.22469 16.04192 16.12708 14.96282 13.00054 10.65593 11.17855 11.14046 12.55776 12.52015 11.42063 9.433226 9.00628 9.585645 9.290247 10.70464 11.49628 11.68974 12.07785 11.17581 11.06459 11.13168 + 16.04482 18.14673 15.86384 14.91284 13.05532 12.45239 11.72008 11.51588 11.29592 11.78518 12.2973 11.38977 10.29393 9.548064 9.722668 10.09043 10.84775 11.8718 11.26131 11.56782 11.02544 11.18756 11.03338 + 15.26615 17.28764 15.16505 14.44016 13.28143 12.07429 10.97578 11.15899 11.50891 11.01097 11.61723 11.36335 10.16722 9.822009 9.901525 10.20638 10.18853 11.82158 11.44402 10.68624 10.50383 11.11971 11.41882 + 13.81127 16.00517 14.23292 13.67491 13.21358 11.73995 9.486867 10.56567 9.862735 10.49043 11.29869 10.67811 9.464268 9.29777 9.709767 10.42091 10.48922 10.93209 10.42687 11.04964 12.2851 12.72577 12.39185 + 12.14262 14.94287 14.0802 14.32626 12.80348 10.66512 9.237948 9.739904 9.596913 10.61011 11.65724 10.63011 9.505305 9.886621 10.47523 11.0215 10.59563 10.78076 11.00128 13.60528 15.06756 16.77889 16.86555 + 10.83472 14.31258 12.90632 12.77348 12.20279 9.816229 9.277019 10.18015 9.096459 9.997322 11.51482 10.87203 9.44985 10.83187 12.01145 12.17475 12.17965 11.82202 12.18132 14.90107 17.46739 18.73794 18.04218 + 11.36189 14.46365 13.13914 12.11007 11.7593 10.0019 9.75862 10.77737 11.03628 12.26316 12.78038 11.30242 11.24927 12.86973 13.40784 13.56777 13.70109 13.02316 12.83176 16.09006 18.6455 20.18859 19.64919 + 12.72338 15.11341 13.32158 12.74039 11.63489 10.73177 10.17774 10.56085 11.74206 12.8467 13.64448 12.64196 13.06981 14.0199 13.50293 14.09397 14.45456 13.70243 13.05322 16.6385 19.08248 21.01362 20.21127 + 14.20404 15.91786 14.92538 15.12413 14.77285 12.89441 12.79587 11.85586 12.40155 12.80069 14.29893 14.05416 12.61579 13.57712 13.58947 13.30546 13.65021 12.84343 13.35256 14.62488 16.85373 18.83813 18.01679 + 14.53132 17.33943 16.26635 18.11593 17.92229 16.41659 14.97751 13.63034 14.93866 15.25947 16.20634 15.82948 14.33713 12.11738 13.05697 13.88322 14.07832 13.04026 13.09391 14.61522 14.58644 16.06195 17.62827 + 14.11968 16.69542 15.12335 18.32276 17.78468 17.74669 15.92803 14.50374 15.56971 16.23568 16.50482 16.08389 14.88408 13.72153 14.5031 14.91234 14.72964 13.03544 12.99891 14.99354 14.95278 15.26584 17.54221 + 12.58309 15.06906 15.59264 18.20675 17.21118 17.89225 16.83617 15.57529 15.21058 16.55922 16.91265 16.39316 15.46596 14.28004 14.31382 15.09121 15.5357 13.50103 13.16001 15.68369 15.05554 15.30483 17.25786 + 13.38149 16.1832 16.11864 18.42098 17.48438 17.89621 18.30592 16.22846 16.11395 16.84072 17.24339 16.6131 15.43338 14.83396 15.42686 15.86161 16.32101 15.09858 13.3338 15.44106 14.9978 14.34465 16.55215 + 14.37599 16.83687 16.94507 18.2267 16.84422 18.06699 18.55002 15.54701 16.76327 16.83081 17.27568 17.00946 16.32395 15.44895 15.63621 15.85781 16.74874 15.47261 13.73446 15.28013 15.02808 14.89976 16.14604 + 15.05994 16.78429 17.38232 18.71122 17.29902 17.89909 18.18792 17.06817 16.19571 16.4189 16.93696 17.02257 16.01795 15.9466 15.9529 16.66254 17.07906 14.79625 13.80007 15.45906 15.62278 14.89583 16.3634 + 15.31985 17.11125 16.90094 17.50673 18.12416 17.26594 18.61826 17.81363 16.92988 17.33131 17.22307 17.6853 17.16475 17.8327 17.79674 18.01369 18.75657 16.41342 14.83692 16.64419 17.08493 16.10439 17.74908 + 14.81287 16.67113 17.24438 17.58853 16.56671 17.4409 19.14317 17.1272 17.22141 17.37194 17.30178 17.35553 16.81259 17.59592 17.2289 17.99676 18.54756 16.97488 15.1711 17.01239 17.41453 16.61493 18.02877 + 14.10214 16.48185 16.70473 17.83863 17.09863 17.92168 18.60074 17.03748 17.01916 17.1662 17.45386 16.9503 17.22705 17.88758 17.25076 18.06163 18.82708 17.06891 15.6639 17.32082 17.86867 16.99368 18.4935 + 14.47095 16.27239 17.20447 16.68737 16.69925 16.92639 17.55458 16.4129 16.37072 16.70506 17.13801 16.74328 16.66209 17.66596 17.03108 17.52262 18.5179 16.57609 15.3233 16.59836 16.57953 15.76227 17.69633 + 13.24856 14.56392 16.16074 15.96923 16.11824 17.0615 17.37887 16.89696 16.49023 16.93359 17.04477 16.61666 17.3206 18.46787 17.4531 17.96162 19.10358 16.9934 15.63334 17.35444 17.27749 16.49334 18.51469 + 13.37477 14.40732 15.3166 16.12179 16.0549 17.9857 18.24229 15.97747 16.51573 16.80781 17.66976 16.77187 17.07391 18.37435 17.8941 17.99025 18.43793 16.58463 15.94918 17.33995 17.58286 16.71625 18.26147 + 13.93172 15.95009 16.12278 16.62806 16.77155 17.70332 18.30991 16.70022 16.12438 16.63647 16.96805 16.56441 16.83017 17.72108 17.19194 17.83681 18.34489 16.32563 15.5139 17.0459 17.14361 16.61393 17.9541 + 13.97243 15.71532 14.24013 15.94024 16.42779 17.24181 18.20842 16.16203 15.17454 15.77178 16.21501 15.67723 16.35604 17.39831 16.76721 17.22853 17.88738 15.95963 14.72727 16.41531 16.48671 16.12189 17.57236 + 13.12301 15.36403 14.92359 14.51013 15.03061 17.25054 18.02694 15.68099 14.72595 15.05695 15.51794 15.62031 15.82822 17.21599 16.45444 16.81317 17.53438 15.40422 14.59688 15.75735 15.82233 15.50435 17.19834 + 13.63734 15.75846 15.76891 16.33092 16.02224 15.02408 15.51878 13.95916 13.93933 15.34064 15.0723 15.51362 15.4393 16.63021 16.81165 16.50006 17.1815 15.69489 14.01058 16.13823 15.96146 15.71183 17.02876 + 13.19862 15.13811 14.20608 14.87641 13.86195 15.69343 16.0471 14.37471 12.70754 13.07089 13.74677 13.72397 14.30151 15.42236 14.87322 14.77012 16.19817 14.61831 14.36353 14.7163 14.1343 13.27196 14.94497 + 13.07108 14.96432 15.33887 15.41621 16.32788 16.2543 15.70077 13.64809 12.23586 13.97429 14.36621 14.99106 14.89284 15.73454 15.09936 16.08811 17.21342 14.54043 14.93736 16.18698 15.30752 15.68343 17.1031 + 12.17307 13.8663 14.74691 13.82833 15.29623 15.13898 14.94032 14.15431 13.22405 12.60576 13.50053 13.70859 14.2374 14.95605 14.1026 13.79868 15.32834 13.637 13.26627 13.85343 12.49579 13.00945 15.18009 + 12.25888 13.0473 13.98215 15.20883 14.53955 14.94806 15.02145 12.94323 12.06552 12.47472 12.65622 12.89004 14.31748 15.0515 13.45358 13.49286 14.73266 13.38384 12.97834 13.98633 13.04967 14.18903 15.84389 + 12.96655 13.67219 15.57411 16.78977 16.06032 13.93955 14.09687 13.42214 11.74005 12.39577 12.6212 13.11872 14.73195 14.7253 13.47528 13.54747 14.39114 12.98527 13.10725 14.80631 13.07693 14.86841 16.53499 + 10.92069 13.71753 13.87495 14.55451 14.22817 13.57006 13.76763 12.60664 11.17306 11.62393 12.25004 11.81037 13.68605 13.56421 12.86382 12.15489 13.24436 12.0221 12.38628 13.08033 11.34459 12.62064 14.02351 + 11.5769 13.79373 14.03928 14.87669 13.80563 13.71551 14.04329 12.57617 11.19479 11.37623 10.92707 11.98935 12.46576 12.69549 12.09219 11.78906 13.35041 11.86954 12.23488 13.13238 10.95979 13.09149 14.30729 + 10.74847 13.18716 13.24313 12.67642 13.39273 12.77279 12.68126 11.46781 10.00143 9.96587 10.43531 11.69773 12.84946 12.46179 11.33073 10.75771 12.40113 11.34042 11.7729 12.49863 11.20993 11.8419 11.45071 + 11.98419 13.92745 12.27599 12.76547 12.93638 12.34464 12.64864 11.34802 10.23944 9.272176 10.22678 11.26323 11.94163 12.36338 11.0064 10.31246 12.27957 11.23496 11.376 12.09842 11.01261 11.91821 11.30618 + 11.78683 13.45012 12.74603 13.96315 13.49791 12.03139 12.34809 11.16278 9.914504 8.632698 10.02429 10.49186 10.53503 11.25449 10.37139 10.6025 11.64441 10.73072 11.01634 10.47035 9.727706 10.35247 10.77614 + 11.52162 12.46663 11.89697 14.03308 13.64052 11.73958 11.48232 9.956855 8.571227 8.986975 9.7626 10.39963 10.45375 10.46915 10.78598 10.67382 11.57074 10.30212 9.981796 10.63711 10.12906 10.43168 10.83299 + 10.47112 11.29303 12.18044 11.88793 10.8442 10.59727 11.06464 9.595164 9.016017 8.599607 8.99171 9.042683 10.83571 10.81611 9.935513 10.24435 10.53577 9.991421 10.32891 10.49404 10.43093 10.59912 10.33619 + 11.11114 12.00806 12.1118 11.56301 11.28773 11.11582 11.11947 10.14109 9.354194 9.394133 8.459903 9.571765 10.62552 10.94563 10.39587 10.17176 11.07636 10.36478 10.18448 10.5289 10.58135 11.55 12.22885 + 9.4698 10.58114 11.28397 11.78697 11.88663 11.19909 11.94986 8.947301 8.886659 9.0145 8.525748 9.001356 10.4408 10.03337 9.662758 9.838831 10.64479 9.835432 9.941095 11.35219 12.8765 13.6802 13.69769 + 9.431818 10.37749 9.402466 11.65605 12.08712 11.54609 11.73875 8.867339 9.922234 10.61318 10.76134 10.87708 10.16173 10.49702 11.13492 10.97119 10.87446 9.942475 10.69347 12.76756 14.78555 15.63049 15.3683 + 9.377582 10.94667 11.15134 12.46202 11.73515 10.88063 11.25175 9.65536 11.14065 11.88232 11.94413 11.89391 10.90223 11.40995 12.1747 11.62114 11.59762 10.78957 11.37593 13.46099 15.81433 17.16356 17.15998 + 9.576265 11.13453 10.96931 10.33049 10.23858 10.44758 10.76839 8.92027 9.525447 10.34433 9.90531 9.858282 9.904203 11.16824 11.88748 11.96853 11.37809 11.49845 11.76391 14.43392 16.65162 18.26472 17.6829 + 9.068971 10.9832 11.37302 11.22971 9.851135 9.574883 9.735991 9.078069 9.105554 10.11883 10.55576 10.29943 9.432137 11.80125 12.00973 12.56134 12.2724 12.0964 12.37772 14.91978 17.71165 19.49631 18.86863 + 10.06087 11.30585 10.13232 9.214253 8.846772 9.325168 10.23292 9.699574 10.61134 11.65377 12.32304 11.8669 11.56203 13.31346 13.75598 14.85218 14.10346 13.62769 13.95514 16.67099 18.7434 19.90597 20.01846 + 9.883128 9.034005 10.26573 9.922016 9.431784 8.751758 10.03959 10.56669 12.11162 13.17491 13.19539 13.8256 12.93738 14.20389 14.85687 14.53677 14.83226 13.86686 14.76633 17.12251 20.07998 22.08923 21.37822 + 9.735471 10.76163 10.26859 10.09679 10.99103 9.163989 9.649745 10.54053 11.75659 12.85315 12.67314 13.1372 12.42263 13.90682 15.30728 15.41667 14.59862 14.48736 15.16387 17.94337 20.98712 23.08864 22.34081 + 8.208311 9.269901 9.024371 9.993098 11.19902 9.208381 9.769848 11.40966 12.96586 14.02102 14.22982 14.19959 13.32056 14.34479 16.31928 16.57144 15.62925 14.52523 16.18148 18.67118 20.88604 23.1573 22.48557 + 8.409505 9.858494 9.497688 10.31458 11.88834 9.982258 10.32621 11.68756 12.89914 13.39427 14.21135 14.64743 13.3156 14.29278 16.14828 15.96886 15.35177 15.5271 16.91822 18.72905 21.57679 23.61708 23.15364 + 8.934703 9.573559 9.441955 8.765581 10.14542 9.618201 10.5309 11.58863 12.7027 13.85705 14.36015 14.13195 13.7818 15.45178 16.22048 15.6483 15.75696 15.26135 16.86555 19.28024 22.11215 24.40104 23.93007 + 8.684306 8.866581 8.434036 8.029984 10.91321 11.22698 10.23968 11.33583 12.85784 13.39227 14.42832 14.13936 13.58225 16.27074 17.07727 16.51017 16.3993 16.14433 16.99274 19.69513 23.2365 24.59908 24.04222 + 8.643711 9.807123 9.50042 9.324292 10.83774 11.15303 10.39372 11.22418 12.95454 13.35421 14.3774 13.54205 13.72393 17.05115 17.68932 16.12386 15.83425 16.01268 17.58204 19.77008 23.01275 24.67944 24.14307 + 7.688036 8.535404 9.236469 10.83574 11.49044 11.28016 11.46384 12.16683 13.50681 13.8899 13.76011 13.80902 13.72299 16.50299 18.20104 16.61069 16.52741 16.1986 18.36386 20.11685 22.81937 23.6178 23.35131 + 10.17826 8.205004 9.347319 12.3267 13.34488 11.22051 10.90597 11.83232 13.47991 13.05177 12.89349 13.7592 13.9114 16.77988 18.02056 16.96051 16.41222 16.26488 18.08109 20.25217 22.95943 23.70821 22.32704 + 9.040493 8.760096 8.527176 9.89751 10.56365 9.972799 10.10701 10.67076 12.86245 12.45882 13.2768 14.06905 14.12293 17.41031 18.83172 16.48821 16.42317 16.09177 17.95219 20.02211 23.01787 23.78878 22.42188 + 9.399467 7.837672 9.216712 10.6783 10.54392 10.52287 11.50509 11.89538 12.42534 13.73793 12.68215 13.53249 13.51577 16.23136 18.68464 17.29062 16.49525 15.83475 18.22473 19.69581 22.15342 22.9642 22.44792 + 8.776668 9.244227 9.925127 11.38309 10.78001 10.8051 10.342 11.05655 12.77748 14.84761 13.69246 13.08996 13.35411 16.14378 18.69108 17.32467 17.7679 16.45478 17.57494 19.5618 22.46428 22.97581 21.49464 + 8.894381 9.427493 10.64883 11.46896 10.78124 9.074279 10.21656 11.47114 12.82468 12.99811 12.95543 12.60641 13.26145 15.72201 18.2295 17.42356 17.95378 16.7246 17.54635 19.24829 21.3367 21.933 20.77688 + 9.042042 8.76036 10.59839 11.72806 12.2252 10.01876 10.65936 11.28481 12.38915 12.18661 11.73761 12.19487 12.95884 15.87654 18.47887 17.35869 17.52493 16.28156 16.84913 18.45013 20.72539 20.79898 19.75169 + 10.06297 12.90782 12.88049 10.60552 11.71332 10.51397 9.21656 10.82334 11.41065 12.40258 13.10792 13.42447 12.83565 15.32679 18.56764 17.4046 16.69835 15.57237 16.41321 17.4268 18.71982 19.50741 18.1189 + 9.921656 16.52995 17.53824 15.70275 15.32331 12.92085 12.47987 12.10413 12.42868 12.15174 12.5522 11.8612 12.07436 13.91121 17.06873 16.3721 16.04488 15.49017 15.58386 17.01062 17.89955 17.60539 17.31772 + 11.36264 17.33499 18.40477 17.00244 18.4658 15.7809 13.04894 11.71813 12.67283 13.29064 13.457 12.057 11.66851 13.66247 17.68314 16.57116 15.98984 14.92666 16.06848 17.17213 17.39293 17.66684 17.4516 + 10.62282 17.60542 18.32822 17.09484 19.33393 16.77328 12.53772 11.13757 13.03852 14.02408 14.38111 13.17555 12.76845 14.38874 17.52058 16.74355 16.38338 15.24556 15.39419 16.26055 16.64132 16.97384 16.89183 + 10.47606 17.70844 18.43419 17.68979 19.75669 16.95199 11.87117 12.01979 13.45656 14.36295 14.81179 13.49354 12.68675 14.77314 17.4999 16.20375 15.68843 15.2202 15.67017 16.32603 16.47589 16.68805 16.65318 + 10.98051 17.72518 18.46542 18.05853 19.93608 16.77472 12.27509 12.2058 13.27803 14.52329 15.10997 13.82026 12.10452 13.67547 16.55763 15.52405 15.14251 14.49046 15.37576 16.78892 16.64076 16.92625 17.4085 + 10.61574 17.7157 18.246 18.44892 20.22108 17.01719 13.5045 12.65141 12.83893 14.40987 15.02312 13.29914 13.55334 15.01744 16.19461 15.84118 15.6518 14.68441 15.11373 16.25138 16.23415 16.90338 17.4352 + 10.69256 17.79112 18.42277 18.3324 20.11359 16.95714 13.75263 13.08895 13.11321 14.44533 15.22537 13.40903 13.27475 15.41946 15.46931 15.14482 15.65731 14.71926 15.28069 17.00951 16.92454 17.32482 17.49486 + 10.73899 17.75406 18.27556 17.87709 19.47082 15.97545 13.59863 13.1574 13.07014 14.04729 14.62672 13.32558 12.45246 15.42138 14.76762 14.0249 15.21964 14.52955 15.10038 17.08111 17.13165 16.41658 17.86931 + 9.618335 17.47857 17.98661 17.55681 18.80152 14.7733 13.48202 12.65518 13.07832 13.56103 13.67241 12.99241 12.99369 14.52666 14.21892 14.50008 15.0451 14.01138 14.70819 17.09094 17.17188 17.34347 17.56815 + 11.57962 16.64433 17.44956 16.3053 18.07066 14.92957 13.12261 12.80251 13.23727 13.17543 12.1275 12.36365 13.38163 14.66714 14.47845 14.50121 14.2356 14.29019 15.07313 17.368 19.52579 21.40561 20.27804 + 10.79007 16.8456 17.36958 16.39471 18.20171 15.12339 12.79474 10.63306 11.83869 12.54484 13.22725 12.38338 12.43016 14.38861 14.98121 15.43519 15.47331 14.8543 16.05373 18.43741 20.38091 22.39174 21.13035 + 11.29912 15.8077 16.54896 16.36229 18.221 15.09686 11.11965 9.979053 10.83024 13.19693 13.55953 12.65395 12.45233 12.89222 15.0179 15.60445 14.92871 14.3497 15.49676 18.35275 19.81606 22.24131 21.26899 + 10.58634 15.52423 16.11585 16.42515 18.17402 15.00347 10.48176 11.49412 11.44849 12.50072 12.81663 12.37796 12.68344 12.88461 13.50673 14.09901 14.95052 14.7693 14.78594 17.34875 19.04901 21.56851 20.89043 + 9.614183 15.8431 16.38121 15.86494 17.58311 14.49032 11.02957 10.22538 9.527282 10.68686 12.11254 12.43469 12.31795 13.20986 13.51692 13.65216 14.22145 14.17002 14.99944 16.96408 18.17498 20.48696 20.18502 + 10.00105 14.93148 15.57334 14.18313 15.84909 13.41543 10.91846 10.40649 10.04232 11.3954 11.08972 10.65496 11.28233 12.84103 13.64318 13.3043 13.51652 13.92885 14.5559 16.84107 18.5327 20.81325 20.21984 + 8.364077 14.47285 15.08788 13.55744 15.35808 12.38914 10.11354 10.69198 10.48146 11.39112 12.41379 12.30688 11.5294 12.61159 13.42383 14.04588 13.40064 13.34902 14.46458 16.70078 18.35919 20.76412 19.943 + 9.641496 14.35918 14.95244 14.19986 15.70295 12.1898 10.18897 9.93488 11.01549 11.42438 12.44854 12.86371 11.5565 13.17326 13.65934 14.53895 13.90649 12.75882 14.03429 16.0668 18.27451 20.19135 19.50024 + 8.998137 15.16441 15.34586 14.19052 15.65328 11.88099 10.49521 10.64151 11.44318 12.29878 12.59886 12.93051 11.39418 13.67889 14.56925 14.55365 14.10461 13.24354 14.07761 16.63885 18.53308 20.30708 20.31402 + 8.946667 14.58632 14.90333 13.99691 15.40372 11.7403 9.425845 11.23817 12.30968 13.13296 12.68324 12.09499 11.59824 14.54168 14.32594 15.0352 14.37218 13.71023 14.06014 17.93824 19.09838 21.4241 20.9504 + 9.447927 13.22877 13.59622 13.29706 14.72807 10.7798 10.31542 10.73058 12.23221 13.26059 12.40936 13.92871 12.75949 13.78177 15.05717 15.43973 15.04269 14.58284 15.24181 18.27596 20.08396 22.0071 21.56488 + 8.942842 12.3533 12.72897 12.85406 14.50646 11.68498 10.16205 11.14519 12.01001 13.16905 12.63423 12.60056 13.09376 15.80599 15.64569 15.43857 15.37898 14.46924 16.22836 18.90742 20.43498 22.41726 21.382 + 9.982429 12.81441 13.19554 12.47804 14.03025 11.90199 10.44281 10.78242 11.68254 11.92076 12.58015 12.23102 12.68594 15.57787 14.6662 15.05085 15.23528 13.82845 15.80674 17.82709 19.04893 21.38214 19.97395 + 9.869902 15.98693 17.0225 15.82257 16.53093 14.39392 13.55346 13.0189 13.5896 13.15744 13.18752 12.97133 12.9559 14.33525 13.73748 13.04741 14.4363 13.70424 15.57391 16.99634 17.09068 17.86176 18.46799 + 11.20318 16.74047 17.93852 17.55423 19.5529 17.38312 15.2714 13.70905 14.90142 15.34691 15.5448 12.94791 14.09873 15.03901 14.75121 14.84058 15.57433 14.77669 15.42783 17.23933 17.08564 16.54724 18.42886 + 10.00581 16.99308 17.64376 17.35759 19.84209 17.72609 16.06901 13.94531 14.6718 16.30286 16.95507 13.571 14.54512 15.59432 15.28288 16.09251 16.91068 14.48281 15.62211 16.89838 16.95857 16.07769 17.91624 + 10.26451 17.10342 17.68718 17.12966 18.80752 16.72883 17.11706 14.18921 15.08757 17.02866 17.63015 14.70868 14.18901 15.36524 15.08402 16.53765 17.53717 14.94885 15.30448 16.46144 16.25202 15.7747 17.48356 + 10.57615 17.0614 17.78706 16.76842 18.19138 17.44588 18.12214 14.26881 15.37736 17.35711 17.82368 16.12888 14.66693 15.406 15.11084 16.10291 17.21349 14.85879 14.25072 15.92479 15.79147 15.51069 17.19426 + 9.48652 16.911 17.18489 17.25456 18.75069 18.27182 18.70464 15.1519 15.75929 18.05139 18.79077 18.19505 14.61217 15.21042 14.50809 16.6024 17.5759 14.97376 14.62691 15.54537 15.83502 14.67196 16.36763 + 9.985225 16.74782 17.24112 17.25887 18.27095 18.51624 18.83493 16.13771 15.24101 18.14103 19.13589 18.64982 14.49468 13.72041 14.16209 16.20668 16.41984 14.58272 14.20643 15.07785 16.00739 15.01148 16.37612 + 8.453218 16.44852 16.53209 17.24354 18.39434 18.03302 18.01833 17.08058 14.58579 18.43371 19.00238 18.1741 14.07615 12.95393 12.84746 15.36702 15.92542 14.33508 13.597 14.46982 16.11521 14.52893 17.01237 + 9.352406 16.0076 16.35685 16.47454 16.98188 17.873 17.68334 17.51781 14.67671 18.76287 18.72811 17.72148 13.38931 12.69698 13.39213 14.65827 15.30858 15.00858 14.27974 14.66115 15.79502 13.98777 16.22205 + 9.672524 14.51215 14.49281 17.57202 18.71115 18.5533 18.56325 18.01528 15.20035 18.96259 18.58512 17.13366 13.41415 12.05879 12.61457 14.21013 14.71272 14.4392 14.22963 14.40962 15.35258 14.32724 15.84399 + 9.294844 14.85708 15.12689 17.73894 18.71831 18.47003 18.26656 17.68312 15.53754 18.16326 17.79104 15.80158 12.8884 11.75445 11.60362 14.12353 14.61159 13.81479 13.50152 14.23705 15.36493 14.26479 16.47873 + 8.768968 15.45139 15.69153 17.49653 18.18467 18.7153 18.61305 16.95576 17.30786 17.01988 16.78725 15.96744 13.20623 11.91497 10.71408 13.16159 14.11679 14.18742 13.99787 14.10843 14.59204 14.09456 16.28277 + 9.966001 15.72716 15.85344 17.40161 18.01343 18.25241 18.0155 17.06878 17.53317 16.64179 16.41474 15.78998 13.04946 11.22962 10.81054 12.68965 14.08202 14.16896 13.95166 14.16778 15.26385 14.41844 15.19306 + 9.675585 15.72316 15.94752 16.87626 17.34341 17.19278 16.94996 16.84417 17.60019 16.79139 15.83696 14.38576 11.87001 11.4011 11.60792 12.10766 13.4803 13.23835 12.92072 14.22253 15.03658 14.08519 14.59369 + 8.843268 15.32752 15.28943 16.05486 16.22001 15.98866 16.2473 16.98972 17.01772 16.9006 16.29029 14.78596 10.99794 11.25592 11.78223 12.48373 13.76172 13.57825 13.22475 14.14124 14.77772 14.18768 15.47867 + 9.988932 15.02293 15.06712 16.10529 16.68101 14.5116 15.2082 16.47553 16.51361 17.52498 17.05329 14.73698 11.47603 11.26132 11.55777 12.60618 14.0904 13.70418 12.92093 14.22556 15.54817 14.19895 15.33703 + 9.373691 14.60107 14.44204 16.04573 16.79593 15.65233 15.8361 15.7082 15.76369 17.70542 17.72066 15.61394 12.20694 11.29502 12.29862 12.74661 13.96791 13.84649 13.18555 14.58438 16.68427 15.04027 15.47512 + 9.678563 14.24788 13.43199 15.21713 15.649 16.42124 16.40302 16.3937 15.09498 16.95411 17.4481 16.14875 12.54013 12.54267 13.3262 13.59261 15.50308 14.9336 13.64636 14.634 16.40599 14.55039 15.61549 + 10.3574 15.14911 14.75568 15.57308 15.29968 16.94623 16.488 16.12799 15.32639 15.1618 16.76408 16.33082 12.41711 12.65482 13.66632 13.70024 15.7187 15.12583 13.7085 15.76237 17.63751 14.65858 15.68508 + 10.7973 15.52072 14.91968 15.44418 16.11236 16.58336 16.3113 15.46747 14.99532 15.4524 15.88347 15.21543 12.29164 12.56826 12.42722 12.49476 14.77974 14.30123 13.07562 15.28318 17.01124 14.17749 15.07545 + 11.72055 14.70813 14.33016 15.86267 16.10855 16.00652 15.79218 14.71058 14.24063 14.49434 15.89563 14.62785 13.76968 14.33625 13.71839 14.10913 14.64376 14.01509 13.78574 15.50584 16.63034 17.31616 17.19594 + 11.63814 14.6362 14.03121 15.15585 15.78361 15.6864 15.34269 14.2795 14.29758 15.06682 15.55843 14.9841 14.52794 14.38037 14.58176 15.49519 16.01623 14.98994 16.1685 17.89346 19.66196 20.41212 19.85152 + 11.20878 14.61073 14.74267 14.05825 15.03689 14.27688 13.934 13.7751 13.63184 13.97793 14.57368 14.39714 14.21094 14.06395 14.52638 15.53573 16.57571 15.51742 17.78471 19.37989 21.206 22.15632 21.33591 + 10.45609 14.74396 14.32149 13.18418 13.62141 14.69138 14.50593 13.22618 13.50317 13.10417 13.68227 13.81558 14.411 13.72738 14.54855 15.41036 16.20938 15.58582 17.08364 18.51379 20.73615 22.85728 22.03742 + 9.892098 14.23401 13.93373 13.37699 13.63826 14.14518 13.86748 12.95281 12.58174 13.15587 13.58239 13.93001 14.0541 14.45245 14.81557 15.24402 15.24497 14.92806 16.91828 19.23318 21.00042 22.89181 22.22411 + 8.341251 13.11608 13.08356 12.63929 12.80791 12.68776 12.75128 13.05065 11.75436 13.81135 13.41625 12.83436 13.37474 14.62833 15.19703 15.31099 14.71062 15.49001 16.62827 18.99271 20.98364 22.86999 22.26387 + 9.033696 14.0476 13.55966 12.04009 12.19411 12.87305 12.72984 12.89095 12.6282 13.35929 13.01105 12.84826 12.64545 13.73565 15.17793 15.06093 14.961 14.88946 16.90281 18.75511 20.67654 22.07957 22.50922 + 9.733017 14.11259 13.47146 12.15287 12.6395 12.34159 11.76064 12.4338 12.91527 13.87755 13.5646 12.60468 13.0273 14.35512 15.41535 15.23291 15.34092 14.8519 15.97584 18.7154 21.41322 22.42959 23.02157 + 8.697401 12.64109 12.03506 13.12382 13.84305 11.54991 11.58626 11.05973 11.39041 14.03995 14.19523 13.01843 13.23257 14.71688 15.60102 14.75217 15.33405 14.82154 16.59491 18.86729 21.02257 22.58431 22.67589 + 9.467981 13.12758 12.64267 12.87043 13.40577 11.28999 10.91053 11.14885 11.37754 13.09841 14.32014 13.18549 12.73418 14.28728 14.77119 14.96639 15.80654 15.12319 16.99688 18.39054 20.01874 22.73198 22.52024 + 9.764517 13.51877 13.10358 11.6141 12.21243 11.77503 11.65425 10.68032 11.29402 14.13506 14.52542 13.75776 13.12655 14.90901 14.66903 15.17769 14.88138 14.35127 15.96097 17.97906 20.40209 22.40177 22.05331 + 9.448538 12.1722 12.48105 11.88218 12.70931 12.26048 11.73737 10.74498 12.15391 13.1719 13.41782 12.89102 12.99866 15.54638 15.04468 15.65555 15.56007 14.52773 15.27511 18.04018 19.91089 21.79332 21.80087 + 8.763973 11.93478 11.62999 11.1273 12.46282 11.85779 11.41145 11.12057 12.67596 13.37468 12.94005 12.73368 12.64572 15.58685 15.55944 15.38471 14.74936 14.22819 16.26686 18.11139 19.70222 21.42123 21.45517 + 8.306178 9.252882 9.844025 11.26888 10.83866 11.36586 11.08982 10.34481 11.93284 12.10324 12.52144 12.75002 12.61329 14.85645 14.61 14.17808 15.05804 14.43231 15.56323 17.80058 18.97718 20.20973 19.62204 + 11.66475 14.36481 14.23808 12.98364 11.47848 11.98761 11.70588 10.32386 11.46336 12.05531 11.33844 11.47684 12.1066 15.10926 14.7728 13.86291 14.54506 14.13791 14.81665 16.32619 17.66183 18.56561 18.54115 + 13.551 15.17671 16.76833 15.87209 15.30873 11.94541 10.92486 11.23132 11.48584 13.04966 12.27409 11.97255 11.65486 14.969 14.62882 13.83866 13.70533 13.39387 13.99928 15.66928 16.7587 16.72407 16.5293 + 12.76456 14.3368 17.05488 16.533 16.53849 12.9081 12.13053 12.54419 11.06146 11.80947 11.34655 11.66301 11.78405 14.4737 14.33242 13.79723 14.23168 13.66543 14.59762 15.88466 16.25202 16.19663 16.058 + 12.61326 13.91244 16.62031 16.31223 16.49494 13.073 12.46359 13.13346 11.08299 12.0898 12.36925 12.15002 11.55262 14.73402 14.74845 14.4159 15.61065 14.59098 14.15156 15.8146 16.68856 15.90823 15.70445 + 13.72736 14.60473 16.47353 16.42584 16.23803 13.49946 13.32173 13.55856 11.47504 12.57557 13.47919 12.9309 12.37103 15.21231 15.2276 15.20738 16.28225 14.82661 15.0314 16.40727 17.3156 15.99237 15.84188 + 13.94386 14.66504 15.96983 16.09722 16.08472 13.28472 14.21239 13.74801 11.81877 13.22909 13.79301 13.41157 12.9921 15.82657 15.1773 15.28221 16.49038 15.39782 15.09806 16.5119 17.39903 16.71843 16.71772 + 13.7535 15.06458 15.29338 15.41305 15.38942 12.72157 14.62464 13.94845 13.23048 13.06216 13.54 12.74474 13.10539 14.58909 14.21166 14.60993 15.12524 14.18825 14.24555 15.87136 16.5764 16.25047 16.18505 + 12.87529 15.11237 17.02908 16.62773 14.56104 13.15497 14.62326 14.26014 13.4457 13.14097 14.58538 14.2613 13.35599 14.02586 13.7997 13.72621 14.95759 14.69201 13.92311 15.1955 15.75672 15.51821 15.26852 + 14.88954 16.34912 15.96387 16.39929 15.98874 13.18963 13.59016 13.90227 13.35802 13.87966 14.67649 14.61434 13.03115 13.09564 12.67227 13.00849 14.46988 13.8509 13.29925 14.3982 14.9851 14.69154 15.44168 + 15.86816 17.34939 15.38919 15.75551 14.90487 13.54091 13.4477 14.09079 13.59264 14.21146 13.69315 13.91447 12.34339 11.63899 11.57346 11.78274 13.74251 13.4482 12.77586 13.67141 13.67339 14.87321 15.2977 + 16.05614 17.80696 15.5341 15.78276 14.98103 13.54131 13.31379 14.18915 14.42894 15.16783 13.61487 12.74171 12.90643 12.58465 11.50915 11.18896 13.20965 13.26229 12.49681 12.70356 12.91538 14.117 14.82414 + 16.10782 18.11083 15.80454 15.79372 14.83904 13.20489 12.75853 14.28803 15.24095 14.56751 12.98957 12.72844 12.02183 12.00667 11.41799 10.49253 12.19255 12.55818 12.08304 12.07552 12.59875 13.70636 14.76297 + 16.48818 18.47473 16.28432 16.31223 13.50066 12.83804 13.33812 14.4014 15.45115 14.3461 12.77997 12.5514 11.41195 11.27457 9.940119 10.18195 11.79267 12.34199 12.40519 12.08422 12.70067 13.04217 14.32282 + 16.58727 18.6493 16.57899 16.56668 13.67641 12.70696 13.48661 14.16076 15.01744 13.59957 12.72013 12.734 11.13864 11.15946 10.10025 10.58426 12.08072 12.54692 12.42924 12.06971 13.08699 12.87339 13.88818 + 16.46552 18.60275 16.49226 16.2148 14.75385 13.87186 13.3091 13.94653 14.61146 13.24284 12.09616 11.93655 11.40253 11.15538 10.07787 9.789737 11.60745 12.03494 11.88888 12.21645 12.83724 12.63594 13.60312 + 16.42601 18.49523 16.10097 14.80393 15.19385 14.14109 13.04128 13.45862 14.21167 13.62841 11.52437 10.68034 10.83395 10.6881 10.08846 10.43773 10.89225 11.42747 11.60774 13.2441 13.97224 11.69935 13.44734 + 16.38114 18.33183 15.98755 14.96587 15.34151 13.83746 12.48781 12.56362 13.59895 13.62195 11.67679 11.84699 11.38903 10.70972 10.37174 10.57339 10.79502 11.32389 11.35826 13.23451 14.09176 12.10806 13.03413 + 16.22431 17.94065 15.84532 15.77651 16.14402 14.11528 12.80109 13.16647 14.27707 13.8602 11.68355 11.39642 10.84387 10.61396 9.958416 9.830719 10.69155 11.12258 11.26303 13.12325 13.99851 11.67061 12.9628 + 15.79015 17.19271 15.68581 15.83383 16.8037 14.45622 12.3988 12.65228 14.56971 13.79517 10.82631 9.563471 10.30857 9.767759 10.45248 9.569944 10.59113 11.10918 11.09264 12.86746 13.68986 11.89357 12.99938 + 15.2913 16.56188 15.66566 15.6369 15.88488 12.91912 13.74727 13.40825 14.74673 12.99693 11.53082 10.84703 10.13508 9.678087 9.518613 9.16087 10.43609 11.18994 10.68452 11.71982 12.54992 12.5922 13.53928 + 14.92816 16.64891 16.45783 16.25064 16.19631 13.19242 13.77824 14.43863 15.26204 15.19201 12.83485 11.38921 10.91256 11.21886 10.82799 12.13972 13.95731 12.54356 12.07864 12.44756 12.12163 12.97568 14.7286 + 15.13246 17.03524 15.97898 15.40096 15.36011 13.36627 13.54038 14.25134 15.57904 16.03234 13.77775 12.25592 10.30315 10.69148 10.83694 12.56628 14.37519 12.44555 12.39819 13.07718 11.56385 13.12282 15.2996 + 15.5982 17.27533 16.06111 15.55091 15.21003 14.51325 13.98331 14.05154 14.17753 16.04241 15.2899 13.87621 11.14674 10.69581 11.96811 14.34262 14.9851 13.41596 13.65708 14.32451 13.39169 12.8552 14.58668 + 14.91011 16.69463 15.57334 15.01008 15.36094 14.74225 14.26171 13.92481 13.91756 15.95652 16.69785 16.57439 12.48547 11.33714 12.08421 15.43829 15.81007 13.38737 13.30834 14.67949 14.26769 13.08782 14.67955 + 13.62063 15.99206 15.72027 15.10223 15.1442 15.15948 14.64559 13.86851 12.91738 14.75704 17.01463 17.44593 14.92344 12.40641 12.71938 16.50565 16.63255 13.53405 12.98962 15.20941 15.12311 13.42216 15.25566 + 15.30581 16.69621 15.66549 15.66445 16.12411 15.10698 15.31674 14.56899 12.28834 15.10705 16.23727 16.72542 15.70731 13.46484 13.77255 16.57919 16.63644 13.61368 13.20484 14.98691 15.30538 13.48375 14.71632 + 14.74377 15.90853 16.02791 15.53424 15.54271 15.17513 15.70076 15.01335 12.41409 14.9216 15.6982 15.6553 14.80186 13.63735 14.12234 16.54076 16.64301 13.9333 13.65155 15.33424 15.71972 14.00131 14.723 + 13.63359 14.52747 16.50115 15.59644 14.78498 14.94671 15.83353 15.04419 12.83702 15.15406 14.95185 15.14444 14.53343 14.07447 14.4541 16.43644 16.78634 13.96131 13.85571 15.59888 16.54558 14.7505 15.17269 + 15.09095 15.96606 16.31398 15.20485 14.33777 14.76441 15.66189 15.22405 12.65838 15.33522 14.93109 15.00384 14.89501 14.88041 14.65054 16.52218 17.02337 14.22087 14.82588 15.54361 17.17436 15.60863 15.80521 + 15.26671 15.98887 16.00072 15.11115 14.80698 14.81987 15.41057 15.06448 12.76331 14.64497 15.39146 15.59635 15.41956 14.94571 14.81527 16.34526 16.81958 14.88231 15.31733 15.54397 16.68404 16.08439 16.50137 + 14.82031 15.1955 15.81392 14.74509 13.80763 14.37639 15.23381 14.08953 13.05277 14.46634 15.04908 15.01031 14.62417 15.87493 14.96298 16.19891 16.81858 14.21854 14.53428 15.09545 15.81948 15.23545 15.83264 + 14.47235 14.64184 15.65243 14.61598 13.88489 14.16747 15.15573 13.50578 13.0122 14.29219 15.03921 14.0275 13.43212 15.84878 14.55854 14.92103 15.28403 13.08439 14.17978 15.17877 15.71674 13.99384 15.05782 + 14.84271 15.37902 15.59052 14.53613 13.88661 14.49238 15.42805 13.71516 12.43119 13.77503 14.90729 14.78802 14.2214 15.8298 14.60928 14.95287 15.55999 13.44495 13.74132 15.10681 16.23884 14.13648 15.53918 + 14.90855 15.54294 15.92132 14.83505 13.52632 14.82017 15.56321 12.84901 12.36878 12.84481 13.59268 13.83251 13.89308 14.76446 13.99949 14.81193 15.35496 13.92693 14.23314 14.60406 16.01696 13.7629 15.32229 + 14.34874 14.65815 15.8946 14.89865 13.93546 14.51131 15.38126 12.69744 12.18602 11.85635 12.55572 13.3946 13.59089 14.50925 13.44721 14.08821 15.0949 13.18603 13.76213 14.69819 15.71466 14.58852 16.02038 + 14.12023 14.95996 14.94988 14.08591 14.26389 15.05177 15.42382 14.23856 13.6525 13.4122 13.92378 13.58581 13.71874 15.37159 13.65612 14.78279 15.35144 12.51747 13.10743 13.96306 14.74565 13.60848 15.42061 + 13.03794 14.3534 14.61717 14.4276 14.20311 14.88333 16.29688 13.85629 12.45 12.66146 13.211 13.02482 13.61957 15.25426 13.65302 14.40839 15.28184 12.64547 12.91868 13.65031 15.02586 13.60051 14.85588 + 12.80224 14.75209 15.02113 14.23392 13.57626 14.24956 14.97642 13.44714 11.19146 11.87919 12.94502 13.08048 12.71222 14.35021 13.33305 13.61649 14.14324 12.44998 12.97652 14.02602 14.98486 13.33818 14.75948 + 13.16971 14.54832 13.68839 12.53332 12.31186 13.85297 14.5408 12.63638 10.70129 10.50308 11.22596 12.15875 12.87752 14.50729 13.75705 13.03189 14.20643 12.62912 13.04198 13.98892 14.39815 13.00242 14.93295 + 12.32784 14.45602 13.87575 12.9689 12.90031 12.28457 12.79731 12.50595 11.64002 10.49457 12.00499 11.84997 12.20587 13.69985 12.74907 13.07555 13.99215 12.42866 12.85618 13.70903 14.67477 12.98654 15.42268 + 13.97398 15.76731 14.26718 13.93614 13.3635 12.53315 12.47679 12.46972 10.95234 10.91325 11.5195 11.66877 12.05166 13.99423 12.40961 12.80348 13.87326 12.39891 12.30221 12.87925 14.37371 12.68297 14.16082 + 14.184 16.03344 14.21303 12.96472 13.494 13.50742 12.40183 12.13428 11.0977 10.09598 11.73288 11.88649 11.97245 12.51878 11.91088 12.78705 13.84648 12.43722 11.65642 12.11694 12.9002 12.40637 14.5552 + 13.45738 15.55 14.00745 12.82178 12.33664 11.91138 12.54709 12.3248 11.18794 10.89101 11.58366 11.65439 12.35607 12.53853 11.86051 12.31624 13.99491 12.18095 11.57812 11.95029 13.21074 12.14984 13.58227 + 12.75044 14.95871 13.36009 13.20988 12.31619 11.93608 11.93395 12.31846 11.38457 11.1864 11.68532 11.97781 12.34169 12.93438 11.78984 12.08069 13.22225 11.86906 11.43046 11.13731 12.37698 12.51735 13.13258 + 12.4284 14.15335 13.07597 11.76668 11.4075 11.61571 12.59232 11.93633 12.10711 12.10692 12.07862 13.06988 12.64771 14.07678 12.7958 11.8246 12.44421 11.69291 11.27304 11.0924 11.61102 12.44975 12.95033 + 11.18342 11.99406 12.91778 11.29505 11.66648 10.58673 12.03916 12.36362 11.94302 11.53715 12.30084 12.70467 12.66107 14.14579 13.0996 11.72703 12.5057 11.78472 11.09719 10.43786 12.19886 12.96147 12.51465 + 11.16282 12.16246 10.93769 9.637576 10.93914 11.07357 12.48322 11.72473 10.95556 11.30609 12.062 12.24674 12.28014 13.23953 12.19423 12.05279 12.69395 11.68082 11.26663 10.09986 10.75675 11.19461 11.49739 + 10.68674 13.24407 11.97083 9.004447 9.364768 10.10863 12.20102 11.69279 10.02772 10.04208 11.35098 11.3541 12.00796 12.02115 10.81907 10.48873 11.81506 11.11288 11.23438 10.76176 10.97295 11.27559 11.05051 + 11.25764 13.04875 10.96754 9.594095 9.950509 9.515109 10.90454 10.44351 10.30407 10.58162 11.61821 10.79663 11.97438 12.49545 10.3045 10.45207 10.77822 10.23204 10.59221 10.55611 10.73 11.25474 10.92693 + 10.77957 12.84101 10.96046 9.825205 9.921305 10.27295 11.00374 9.069146 9.143576 9.619534 9.886018 9.683996 11.43353 11.68437 10.21345 9.418591 10.17027 9.919155 9.729475 10.12781 10.13701 10.20216 10.49461 + 10.50037 12.81704 10.93899 8.919902 9.181169 9.454815 9.864502 9.160638 7.950092 8.281847 9.608268 9.700049 10.10044 10.43497 10.07498 9.374721 10.09255 9.838331 9.777655 9.972161 10.5668 10.3991 10.44382 + 10.9499 12.69608 10.32951 8.873781 9.134046 8.758868 9.037214 8.199131 7.569058 8.374478 9.34644 9.717125 9.732412 10.43906 8.821301 8.579359 9.148021 9.602486 9.728423 9.711402 10.63859 10.64856 10.91956 + 11.27755 12.95559 10.74122 9.436187 9.256186 9.484769 9.586822 8.388662 7.160058 8.068262 9.397726 9.698923 10.2531 10.30162 9.136081 8.90623 9.838286 9.443312 9.586123 9.845777 10.52771 11.16902 11.16297 + 10.61487 12.45117 10.35181 8.28381 7.78183 7.990805 8.725731 8.746444 7.518696 7.472842 8.455659 9.625546 9.859873 9.179219 8.927357 9.205382 9.615867 10.14538 10.18613 10.31581 10.64848 10.60155 10.51831 + 9.637716 11.20578 9.314525 7.651236 7.868614 8.413736 9.356231 8.070017 8.155 7.390232 8.749864 9.116447 9.567814 9.369207 9.694729 9.147233 9.482315 9.444982 9.887435 9.599484 9.862085 10.45017 10.60415 + 8.626204 9.187364 9.029099 8.493851 7.209723 7.690259 8.042856 7.316092 8.150468 8.128954 8.491919 8.446475 8.681947 8.747696 8.789898 9.233255 10.31805 9.660691 9.486577 10.23518 9.970778 9.988548 10.34961 + 9.442098 8.689657 9.283001 7.998808 6.22693 6.962261 9.189577 8.19746 7.75057 8.493724 8.455694 7.799927 8.001998 8.729837 9.923102 9.574437 9.835016 9.547566 9.595923 10.27687 9.859653 10.14931 10.31981 + 9.97291 9.826776 8.408665 7.496465 8.021371 7.480401 6.859789 7.656764 8.673873 8.172565 8.807829 9.007799 9.039057 8.638908 9.110216 9.078871 9.454023 9.512172 9.644451 9.953197 10.47481 10.57586 11.22771 + 7.733074 9.88445 8.777633 8.313414 8.081938 7.271777 8.92068 8.634503 8.78035 8.908507 9.615075 9.58618 10.19903 9.804263 9.953383 9.936666 10.38816 9.741985 10.35782 10.95726 11.52999 11.21175 11.48313 + 10.96227 9.154578 8.698281 7.998504 7.728884 6.602579 8.022117 8.025263 9.123106 8.844633 8.908731 10.21477 9.655914 9.417415 9.496919 9.789172 10.42569 10.3659 11.02518 10.92264 11.54033 11.66203 11.41841 + 10.86337 9.16935 9.55076 7.872849 7.942523 7.244263 7.779666 7.115378 7.89739 8.183571 8.867866 9.697963 9.289954 9.11062 9.085766 9.426685 9.012211 9.846148 10.90698 10.7715 10.9265 11.21198 11.22182 + 9.851903 10.36412 8.908376 7.846486 7.46843 7.405447 7.65377 8.196727 7.064144 8.249885 8.761197 8.942713 9.204334 8.628345 8.864152 9.710718 9.490798 9.545791 10.44929 10.69282 10.93337 10.86275 10.97815 + 11.25423 10.87261 8.889456 7.461558 7.015344 7.351716 8.382879 8.503271 7.68911 7.91639 8.643186 8.87805 9.065163 9.099637 9.386837 9.786633 10.39724 10.21951 10.45584 10.46524 10.56583 10.76281 10.16936 + 10.68273 9.817769 8.26171 8.016673 8.054502 7.516357 7.65291 7.820787 7.519141 7.720293 7.444949 8.414156 9.215623 8.999539 9.341202 10.16718 9.897247 10.21318 10.05819 10.82438 10.51383 10.53982 10.3831 + 9.478124 9.167519 9.010949 7.99378 7.74503 6.204545 7.584774 7.112559 7.285814 7.478474 7.668393 7.974492 8.957026 8.854042 8.923244 10.00307 9.395614 9.709485 9.681889 10.5162 10.42807 10.61794 10.64966 + 10.47888 7.862177 7.167226 8.245425 7.75247 7.6353 7.317435 8.184895 7.887283 7.203204 8.485103 8.665626 8.602202 8.789666 8.851316 9.74954 9.710799 9.547979 9.842075 10.54527 10.60706 10.56509 10.56258 + 9.801606 8.885911 7.033187 7.478241 6.67696 7.042121 7.39953 7.7513 7.696868 7.563337 8.205177 8.162902 8.607606 8.607259 8.436271 9.256256 9.444992 9.138235 9.183046 10.3544 10.70144 10.78459 10.73416 + 8.729084 9.680941 8.473826 7.128935 7.117252 7.535285 7.976354 8.395122 7.388021 7.665706 7.663038 7.935491 7.918374 8.014401 9.072487 9.747068 9.442183 10.02906 9.621786 10.21271 10.62509 10.38278 10.59681 + 9.071483 8.072448 8.148285 8.208888 6.865083 7.357923 7.7726 7.642717 7.373379 7.314355 6.811155 8.184822 8.202291 9.007961 8.424119 9.344726 9.500482 9.494846 9.845454 10.21647 10.59387 10.59069 10.30458 + 9.551182 8.782715 9.017637 8.455041 7.292755 6.371716 8.139261 7.807406 6.868361 7.754928 8.373298 7.639658 8.648994 8.402566 9.127618 9.492344 9.512679 9.582137 10.01061 10.36339 10.5303 10.40906 10.77627 + 10.07956 9.123581 9.239129 7.188252 7.983877 7.36659 7.689083 7.349831 6.549315 7.351971 7.765708 8.071753 8.22062 8.234403 9.252606 9.165942 9.141379 9.528788 10.10328 10.09754 10.46661 10.03632 10.45256 + 10.45638 9.177458 8.172346 6.487576 7.374524 7.649885 7.573339 6.959404 7.260113 6.734559 7.447418 8.466086 8.382314 8.260194 8.807273 9.042309 9.807338 9.552331 9.757251 10.22166 10.39291 10.50788 10.47725 + 11.13613 7.758188 7.694408 7.91339 6.591579 7.690708 7.883243 6.817697 6.937893 6.617546 7.995504 8.199288 8.606889 7.930028 8.27558 9.028001 9.202659 9.807508 10.00867 10.30622 10.30117 10.22799 10.23392 + 10.84375 8.658377 8.964039 7.577753 5.997756 6.243629 6.59042 7.667848 7.77693 7.160565 7.672915 8.214157 7.767068 8.187228 8.741798 9.008192 8.761507 9.775722 9.991219 10.50324 10.35022 10.56752 10.42405 + 9.856415 8.33345 7.864082 7.394323 7.037837 7.429994 7.053414 8.148975 7.734139 7.73136 8.126728 8.250671 8.107525 8.775521 8.947971 9.013912 8.709566 9.952392 10.0177 10.55204 10.14417 10.48263 10.89007 + 11.22424 8.785028 8.384821 6.571542 6.668182 6.510378 7.012306 7.705905 6.736544 7.154219 8.193804 7.920104 8.426934 8.557131 8.854219 9.46376 10.04807 9.964184 9.701237 9.969363 10.31233 9.983346 10.42036 + 11.32489 8.361799 9.032561 7.834023 7.204043 6.074617 7.471241 8.117975 7.885279 7.726513 8.305443 8.50788 8.930935 8.875918 8.786529 9.071368 10.33387 9.977633 10.02416 10.06347 10.81268 11.20751 10.80103 + 10.06941 6.52937 7.571665 6.465108 6.881132 6.903685 7.100048 7.664837 8.100976 8.205462 8.043177 8.448032 8.847051 8.371434 9.149493 9.756526 10.0323 9.265886 10.12252 10.04926 10.18163 10.70782 10.76523 + 10.31685 7.024517 5.869543 5.841137 6.383105 6.215681 7.949264 7.746029 7.835628 7.861153 8.296052 8.204115 8.655869 8.276096 8.929706 8.994547 9.447945 9.700706 10.37251 10.27933 10.33522 10.72122 10.48557 + 10.91509 7.91675 8.244367 6.124669 6.732698 7.492036 8.363312 8.124249 8.108838 7.358902 8.053522 7.411808 8.716305 8.342316 8.457804 9.005593 9.336573 10.05767 9.578779 10.344 10.4169 10.55184 10.60104 + 10.32075 8.261191 8.136519 6.961864 6.799195 7.198459 7.713956 6.972926 6.764237 7.75519 7.804876 7.912869 8.563591 8.987835 9.309792 9.610885 9.492855 9.925476 10.05713 10.52021 10.62411 10.47692 10.59566 + 9.86818 8.572285 8.530177 7.359343 7.294422 6.582515 6.642591 7.496963 7.95584 7.202435 7.878167 8.503516 8.365258 9.081302 8.992274 9.197975 9.750833 9.722472 9.760623 9.966175 10.59453 10.34588 10.6256 + 10.25583 7.347171 7.843901 6.244213 6.5353 6.617674 7.064937 7.198507 6.530531 7.619411 7.706642 8.672579 8.665023 9.01842 9.258142 9.454097 9.65053 9.569846 10.16275 10.25629 10.61747 10.79843 11.0936 + 11.3455 8.169881 7.368921 6.081739 7.016907 7.807951 8.699952 7.736558 6.748681 7.378449 7.852139 8.727378 9.049452 8.352005 9.397895 9.694369 9.896957 9.87398 9.838742 10.29402 10.58788 9.819412 10.6098 + 11.90842 7.299352 8.247597 6.929464 5.668571 7.252095 8.041783 7.685021 7.479499 7.446598 7.486227 8.122455 8.321239 8.863303 8.986836 9.896532 10.01282 9.700945 10.13044 10.36466 10.79011 10.65473 10.96276 + 11.22572 8.534163 7.996068 7.321532 6.95424 7.306089 8.242614 7.205853 7.210618 7.631306 8.128415 8.210221 7.745347 8.572201 9.164312 9.454391 9.143291 9.996038 9.919705 10.61216 10.94615 11.0497 10.50996 + 11.99271 8.205185 9.120809 8.732939 7.727026 7.459247 9.017488 8.611053 9.431357 8.641321 9.48832 9.588237 9.180689 8.96218 9.460557 9.78753 10.30156 10.65017 10.35131 10.94515 12.2452 13.12846 12.19351 + 11.79253 9.627811 9.291315 7.524522 6.52378 7.294258 8.705289 7.135509 8.226054 8.742827 9.064022 9.066143 8.709431 8.971881 9.728841 9.269897 9.861171 10.09128 10.07978 10.35271 10.13663 11.0843 11.13849 + 10.56259 9.232968 9.15976 6.620336 6.150416 6.181613 7.583303 6.586105 7.54056 7.118359 8.163677 8.156444 8.354664 8.431437 9.365588 9.699803 9.425529 9.986162 10.02214 9.757886 9.986296 10.37123 11.12805 + 11.46303 8.064131 8.81135 8.263533 7.772588 6.787446 7.045297 7.507244 7.852684 7.8141 8.464418 8.278731 8.368163 9.374079 9.125239 9.165322 8.79129 9.501587 10.15753 9.792738 10.41069 10.7875 10.81474 + 11.05755 8.402949 8.103877 8.311771 7.382164 7.518039 8.082945 8.166703 8.587493 8.333583 7.051155 8.128522 9.163286 9.241158 9.282046 9.011143 9.606009 9.214875 9.92196 10.35358 10.19762 11.04288 11.07783 + 9.634427 8.981845 9.008856 8.496973 8.026294 6.744306 7.501451 7.233029 7.99301 7.752292 7.717514 8.286538 9.62907 9.19305 8.897845 9.250588 9.401032 9.651338 10.04432 10.55263 10.60681 10.55907 10.44911 + 9.16023 9.718575 9.712869 9.499995 9.118845 7.099794 7.596916 7.821012 7.377513 7.772183 8.825283 8.444467 8.374529 8.278049 8.336656 9.146471 9.616967 9.900914 9.985411 10.23762 10.56904 10.3051 10.82687 + 10.26259 9.707577 7.891645 7.938838 8.196815 7.030812 7.729892 8.539984 7.097323 7.639617 8.26931 8.103879 8.820785 8.756143 8.088627 7.633537 8.861978 9.411267 9.507934 9.617286 10.103 10.32123 10.60248 + 9.416736 8.067968 8.121611 8.702229 8.153371 7.398215 8.101689 7.075943 7.48716 7.682926 7.930077 7.562144 7.981254 8.482229 8.007603 8.850947 9.587449 9.971848 10.00796 10.0423 10.69032 11.02298 10.9979 + 9.875791 9.180859 8.33131 7.329909 7.138772 6.531456 7.820087 7.730562 7.006549 7.355982 8.221857 7.397998 8.619684 9.434539 8.325085 8.88515 9.217641 10.11225 10.50192 10.39947 10.4549 10.84156 10.37869 + 10.21343 8.826467 8.089176 7.33913 7.855656 7.318372 7.243741 7.606412 7.226522 7.431902 7.899468 8.143455 8.827127 9.14143 8.887341 8.847593 9.56572 9.84055 9.684552 9.931477 10.32066 10.90385 10.82047 + 8.685722 8.634503 8.177417 7.393768 7.107471 7.263133 7.5967 7.938039 6.885909 7.359814 8.428015 8.861872 8.920212 7.885601 8.544594 9.326226 9.538568 9.941797 10.37305 10.73129 10.24279 10.99198 10.71185 + 9.49738 9.815642 8.553341 7.461448 7.640052 7.98072 7.311526 8.038465 7.255682 6.560006 7.371539 7.967708 8.035154 7.652845 8.489419 9.47836 9.636602 9.799182 10.27001 10.54407 10.1862 10.5361 11.08353 + 9.237267 8.263541 7.987866 7.640958 7.269582 6.937474 6.849837 7.717149 7.656352 7.173209 7.225511 7.958437 8.102967 8.547636 8.357112 8.845057 9.423195 9.327521 10.10193 10.10455 10.16159 10.10588 10.53935 + 8.52638 7.134833 7.512567 7.138206 6.746409 7.367229 7.365469 6.383105 7.514528 7.68226 8.600569 8.164186 8.062187 8.582131 9.225581 9.787975 9.365478 9.480648 9.960615 9.841322 9.842931 10.30676 10.41945 + 9.151896 6.975066 6.058979 7.529419 6.584132 6.413368 7.757835 7.395633 7.860129 7.49466 8.109066 7.873742 8.698033 9.225484 9.379223 9.214903 9.393662 9.46548 9.963161 10.11975 10.1148 10.09052 10.40652 + 9.582273 6.791395 7.215298 7.381511 7.378876 7.854954 7.890674 7.242115 7.359837 7.425533 8.024413 7.492979 8.248519 8.686938 8.807583 9.178555 9.128061 9.372928 9.666212 9.78712 9.796471 10.31979 10.85505 ] diff --git a/audio/tests/features/testdata/pitch_feat.ark b/audio/tests/features/testdata/pitch_feat.ark new file mode 100644 index 0000000000000000000000000000000000000000..ee7d4c7fcde24f6dbf74fcbde386e94924f1ae07 GIT binary patch literal 7552 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 literal 0 HcmV?d00001 diff --git a/audio/tests/features/testdata/pitch_feat_txt.ark b/audio/tests/features/testdata/pitch_feat_txt.ark new file mode 100644 index 00000000..da72c588 --- /dev/null +++ b/audio/tests/features/testdata/pitch_feat_txt.ark @@ -0,0 +1,942 @@ +test_wav [ + 0.4300044 238.1164 + 0.7310953 238.1164 + 0.0589752 238.1164 + 0.3706925 238.1164 + 0.6353879 238.1164 + 0.4168843 238.1164 + 0.7615743 238.1164 + 0.7359886 238.1164 + 0.5104555 238.1164 + 0.843815 238.1164 + 0.574707 238.1164 + 0.8757079 238.1164 + 0.8545787 238.1164 + 0.5284878 238.1164 + 0.8398617 238.1164 + 0.8405749 238.1164 + 0.7291093 238.1164 + 0.3530312 238.1164 + 0.8516902 238.1164 + 0.8366287 238.1164 + 0.7860549 238.1164 + 0.7996263 238.1164 + 0.8769377 238.1164 + 0.8062987 238.1164 + 0.5502667 238.1164 + 0.8050084 236.9318 + 0.8435217 235.753 + 0.6781067 234.5801 + 0.6275977 233.4131 + 0.8354951 232.2518 + 0.7898548 231.0963 + 0.6786529 229.9466 + 0.5784115 228.8026 + 0.8078744 227.6642 + 0.7860623 226.5316 + 0.3529067 225.4046 + 0.7398534 224.2832 + 0.7554479 223.1673 + 0.3688865 222.057 + 0.431939 220.9523 + -0.2559481 219.853 + 0.5161331 218.7592 + 0.736354 217.6709 + 0.2649688 216.5879 + 0.6187224 215.5104 + 0.8040497 214.4382 + 0.4176693 213.3714 + 0.7362868 212.3098 + 0.6412426 211.2535 + 0.01994592 210.2025 + -0.09901931 209.1568 + 0.4139394 208.1162 + 0.7458671 207.0808 + 0.7015585 206.0505 + 0.8989595 206.0505 + 0.9356874 209.1568 + 0.9370709 212.3098 + 0.9640602 216.5879 + 0.9851464 217.6709 + 0.9874667 217.6709 + 0.9662011 215.5104 + 0.9561557 211.2535 + 0.9835509 209.1568 + 0.9764188 209.1568 + 0.8779365 208.1162 + 0.8911879 207.0808 + 0.8716732 205.0254 + 0.8663442 197.9909 + 0.9105747 191.1978 + 0.9614732 191.1978 + 0.9704162 187.4211 + 0.9690547 186.4887 + 0.9596871 187.4211 + 0.9392474 190.2465 + 0.8951484 192.1537 + 0.8852764 190.2465 + 0.9345272 185.5609 + 0.9304817 182.8051 + 0.9600235 180.0902 + 0.9829449 178.3027 + 0.9876611 178.3027 + 0.9889374 178.3027 + 0.9911457 178.3027 + 0.9835688 176.533 + 0.9755108 175.6547 + 0.9868198 174.7808 + 0.9932452 175.6547 + 0.9902206 175.6547 + 0.9920143 176.533 + 0.9922135 175.6547 + 0.9892406 174.7808 + 0.9888865 172.185 + 0.9796383 172.185 + 0.9825094 170.476 + 0.9640273 170.476 + 0.9416605 173.046 + 0.9690976 173.9112 + 0.9527848 173.9112 + 0.8625391 173.046 + 0.8571138 173.9112 + 0.826855 174.7808 + 0.8046249 176.533 + 0.6064064 178.3027 + 0.4831207 180.9906 + 0.4654697 183.7191 + 0.6392145 186.4887 + 0.6342434 190.2465 + 0.8626602 194.0801 + 0.953752 194.0801 + 0.987371 191.1978 + 0.9915444 188.3582 + 0.9920763 186.4887 + 0.9898383 184.6377 + 0.9908906 181.8956 + 0.9892344 180.9906 + 0.9779232 179.1942 + 0.962887 175.6547 + 0.9623674 170.476 + 0.9719465 171.3284 + 0.9843823 173.046 + 0.9776363 173.9112 + 0.9711902 173.9112 + 0.9477384 172.185 + 0.9629893 172.185 + 0.9716213 173.9112 + 0.9675733 176.533 + 0.9643332 178.3027 + 0.9592766 180.0902 + 0.8798139 181.8956 + 0.7133937 185.5609 + 0.558998 190.2465 + 0.3460312 196.0257 + 0.645723 201.9805 + 0.538826 208.1162 + 0.6076745 216.5879 + 0.5921855 228.8026 + 0.8826846 238.1164 + 0.9501736 242.9146 + 0.9898381 245.3498 + 0.9941772 245.3498 + 0.9901065 246.5766 + 0.9880084 247.8095 + 0.9941741 249.0485 + 0.9959649 249.0485 + 0.9930359 250.2938 + 0.9856881 252.8029 + 0.9906433 252.8029 + 0.9973371 252.8029 + 0.9980655 252.8029 + 0.9958057 251.5452 + 0.9909865 250.2938 + 0.990723 249.0485 + 0.987911 249.0485 + 0.9759061 251.5452 + 0.9722236 251.5452 + 0.9578422 251.5452 + 0.936386 250.2938 + 0.9094891 245.3498 + 0.8420637 235.753 + 0.6358631 218.7592 + 0.757064 198.9809 + 0.8944787 192.1537 + 0.9415626 188.3582 + 0.9400396 183.7191 + 0.9019431 180.9906 + 0.904547 175.6547 + 0.8742625 173.046 + 0.7475201 169.6279 + 0.5156456 164.6269 + 0.4781345 161.3751 + 0.4000205 149.7427 + 0.4614965 147.5188 + 0.4422437 141.7487 + 0.3527509 136.2042 + 0.191848 130.2255 + 0.3072545 128.2915 + 0.5506754 127.0181 + 0.6923887 129.5776 + 0.6101584 132.8496 + 0.3040284 138.2575 + 0.1897903 155.8383 + 0.4685728 164.6269 + 0.5444003 164.6269 + 0.5138596 162.9929 + 0.5802024 160.5723 + 0.2090085 160.5723 + 0.2416739 162.9929 + 0.2459603 168.784 + 0.6345571 176.533 + 0.5914032 184.6377 + 0.4447052 193.1145 + 0.2762254 201.9805 + 0.2842087 212.3098 + -0.1974148 224.2832 + 0.5095772 238.1164 + 0.7442543 252.8029 + 0.8915138 252.8029 + 0.9569594 247.8095 + 0.9625007 242.9146 + 0.9194686 235.753 + 0.8994824 227.6642 + 0.9381443 218.7592 + 0.7226841 219.853 + 0.6949252 223.1673 + 0.6777956 222.057 + 0.7978759 214.4382 + 0.8507771 209.1568 + 0.7713854 211.2535 + 0.6789038 211.2535 + 0.7500904 206.0505 + 0.7494768 204.0054 + 0.7971382 206.0505 + 0.8222839 206.0505 + 0.7602305 200.9756 + 0.6428912 197.0059 + 0.6536841 193.1145 + 0.7034875 188.3582 + 0.5804744 181.8956 + 0.4417492 173.046 + 0.3494109 161.3751 + 0.1981726 148.2564 + 0.6261529 140.3417 + 0.7587391 136.8852 + 0.3861219 134.8523 + 0.2585415 138.2575 + 0.4469885 141.7487 + 0.7737626 140.3417 + 0.7246874 138.2575 + 0.6511324 136.8852 + 0.4254677 136.8852 + 0.1780608 141.7487 + 0.4341104 146.7849 + 0.2834992 149.7427 + 0.4845652 151.2439 + 0.4879717 151.2439 + 0.5601908 152.0001 + 0.7019184 152.0001 + 0.8482625 152.0001 + 0.8672082 152.0001 + 0.8626106 151.2439 + 0.8626055 149.7427 + 0.9013379 148.2564 + 0.8793075 146.7849 + 0.8914734 144.605 + 0.8311929 141.7487 + 0.6839114 139.6435 + 0.5378032 136.8852 + 0.3579432 131.531 + 0.3721773 123.8897 + 0.259927 118.4515 + 0.4204291 116.1118 + 0.7305987 117.2759 + 0.6386622 122.6601 + 0.4521356 127.0181 + 0.1688575 132.1886 + 0.2404094 136.8852 + 0.161655 139.6435 + 0.361738 140.3417 + -0.02500387 138.2575 + 0.4358978 136.2042 + 0.4791997 134.8523 + 0.5491226 134.1814 + 0.6218376 133.5138 + 0.566164 133.5138 + 0.6004029 134.1814 + 0.4282176 134.8523 + 0.2462277 135.5266 + 0.468562 136.8852 + 0.1709644 137.5696 + -0.1866998 138.2575 + -0.2322609 138.9488 + -0.3413201 138.9488 + -0.5628413 138.9488 + -0.3278477 138.2575 + -0.1193225 138.9488 + 0.04384596 140.3417 + 0.04935479 142.4574 + 0.1841608 144.605 + 0.5726393 147.5188 + 0.6960948 151.2439 + 0.7003101 153.5239 + 0.7147154 155.8383 + 0.7900128 158.9785 + 0.7915136 160.5723 + 0.8635668 162.9929 + 0.9153162 167.1087 + 0.9632365 168.784 + 0.9837555 173.046 + 0.9895447 174.7808 + 0.9936979 178.3027 + 0.9939244 180.0902 + 0.9943298 180.9906 + 0.9935455 183.7191 + 0.9959602 184.6377 + 0.9956325 184.6377 + 0.9592334 185.5609 + 0.8768551 189.3 + 0.7307228 193.1145 + 0.511133 192.1537 + 0.6857247 188.3582 + 0.9158598 184.6377 + 0.9108561 186.4887 + 0.9116812 187.4211 + 0.9223366 187.4211 + 0.8974369 188.3582 + 0.8092315 189.3 + 0.6424754 190.2465 + 0.415496 193.1145 + -0.09486372 194.0801 + 0.06201403 196.0257 + 0.7022306 199.9758 + 0.8596367 206.0505 + 0.8891434 212.3098 + 0.8837855 220.9523 + 0.7532349 229.9466 + 0.8273478 235.753 + 0.8926229 240.5036 + 0.9618158 242.9146 + 0.9802947 242.9146 + 0.9757999 241.7061 + 0.9556377 236.9318 + 0.9495205 228.8026 + 0.8714783 222.057 + 0.6913882 217.6709 + 0.5996734 214.4382 + 0.5818065 196.0257 + 0.7346321 182.8051 + 0.8720678 184.6377 + 0.8043494 181.8956 + 0.7913815 184.6377 + 0.8019897 179.1942 + 0.8272967 177.4156 + 0.917188 173.9112 + 0.948779 178.3027 + 0.9491397 179.1942 + 0.9660939 181.8956 + 0.948925 181.8956 + 0.9438759 183.7191 + 0.9082419 182.8051 + 0.8157918 182.8051 + 0.7726267 183.7191 + 0.7925529 184.6377 + 0.82323 185.5609 + 0.7710455 186.4887 + 0.7245523 187.4211 + 0.6725792 188.3582 + 0.6098945 190.2465 + 0.6053364 192.1537 + 0.5669799 194.0801 + 0.6425812 196.0257 + 0.5920788 198.9809 + 0.5982969 201.9805 + 0.5845767 206.0505 + 0.8673239 210.2025 + 0.8958703 213.3714 + 0.9322492 215.5104 + 0.9763456 216.5879 + 0.9871499 217.6709 + 0.9847117 216.5879 + 0.9803717 215.5104 + 0.9864718 214.4382 + 0.9891353 213.3714 + 0.9886542 212.3098 + 0.9887235 211.2535 + 0.9895412 211.2535 + 0.987739 210.2025 + 0.9813522 211.2535 + 0.979372 210.2025 + 0.9715059 211.2535 + 0.9659958 212.3098 + 0.9582934 213.3714 + 0.9457313 214.4382 + 0.9001306 214.4382 + 0.8375491 214.4382 + 0.8580287 213.3714 + 0.8090529 212.3098 + 0.7204515 211.2535 + 0.727868 211.2535 + 0.8134795 211.2535 + 0.7847017 212.3098 + 0.855688 212.3098 + 0.9308017 212.3098 + 0.8832355 211.2535 + 0.7976366 210.2025 + 0.6125828 208.1162 + 0.7335693 206.0505 + 0.5974885 204.0054 + 0.5784881 201.9805 + 0.626056 199.9758 + 0.3227086 197.9909 + 0.4882276 196.0257 + 0.5797763 194.0801 + 0.6834005 191.1978 + 0.8976591 190.2465 + 0.9618267 188.3582 + 0.9768698 185.5609 + 0.9630268 181.8956 + 0.951674 178.3027 + 0.9476838 173.9112 + 0.9517156 172.185 + 0.9504529 169.6279 + 0.9870636 169.6279 + 0.9741296 168.784 + 0.9508372 168.784 + 0.90883 165.4501 + 0.8581914 161.3751 + 0.8267851 156.6175 + 0.6312259 150.4914 + 0.546525 146.7849 + 0.5260749 146.7849 + 0.4342186 153.5239 + 0.492891 160.5723 + 0.6401276 166.2773 + 0.7607003 168.784 + 0.8006893 169.6279 + 0.754485 169.6279 + 0.3563844 168.784 + 0.2301813 168.784 + 0.5213931 168.784 + 0.4302804 169.6279 + 0.203999 170.476 + 0.5803608 172.185 + 0.799207 173.9112 + 0.7838959 175.6547 + 0.6272278 177.4156 + 0.5649745 179.1942 + 0.1334038 180.9906 + 0.4665532 182.8051 + 0.7017906 184.6377 + 0.574991 186.4887 + 0.1776394 188.3582 + 0.7058933 190.2465 + 0.7285945 192.1537 + 0.5696192 194.0801 + 0.5714518 196.0257 + 0.8047431 198.9809 + 0.7406042 200.9756 + 0.3201705 202.9904 + 0.7420282 205.0254 + 0.8154832 207.0808 + 0.7317356 209.1568 + 0.3067544 211.2535 + 0.8014479 213.3714 + 0.8102676 215.5104 + 0.4439816 217.6709 + 0.8234195 219.853 + 0.7885534 222.057 + 0.4245847 224.2832 + 0.00418177 226.5316 + 0.3035149 228.8026 + 0.6538261 231.0963 + 0.847946 233.4131 + 0.6219971 235.753 + 0.858582 236.9318 + 0.8284138 239.307 + 0.4044586 240.5036 + 0.7807371 241.7061 + 0.6421613 242.9146 + 0.4366452 244.1292 + 0.8172922 245.3498 + 0.7077252 246.5766 + 0.7137164 247.8095 + 0.8416377 249.0485 + 0.8084357 250.2938 + 0.6429872 251.5452 + 0.8479958 252.8029 + 0.8376269 254.067 + 0.5148344 255.3373 + 0.8253111 256.614 + 0.792549 257.8971 + 0.7535735 259.1866 + 0.08307214 260.4825 + 0.7227954 261.7849 + 0.7564813 264.4093 + 0.4621202 265.7313 + 0.4633192 267.06 + 0.7247673 268.3953 + 0.7581604 269.7372 + 0.5511292 271.0859 + 0.9282034 272.4414 + 0.9706463 271.0859 + 0.9854098 273.8036 + 0.9971125 273.8036 + 0.9954729 272.4414 + 0.9888761 269.7372 + 0.9950762 267.06 + 0.9968371 269.7372 + 0.9980098 269.7372 + 0.997749 268.3953 + 0.9915248 265.7313 + 0.9653835 259.1866 + 0.9663866 256.614 + 0.9707376 259.1866 + 0.9852746 263.0938 + 0.982106 265.7313 + 0.9469596 267.06 + 0.9675582 264.4093 + 0.9636477 261.7849 + 0.9427549 257.8971 + 0.7895892 251.5452 + 0.8654758 245.3498 + 0.5468963 235.753 + 0.683558 220.9523 + 0.8124123 211.2535 + 0.9160304 205.0254 + 0.9279389 197.0059 + 0.9523526 191.1978 + 0.9704132 186.4887 + 0.9692958 185.5609 + 0.9680378 184.6377 + 0.9765167 184.6377 + 0.9860514 182.8051 + 0.9883636 182.8051 + 0.9909548 181.8956 + 0.991558 183.7191 + 0.9909708 185.5609 + 0.9903755 187.4211 + 0.9908676 189.3 + 0.9932029 191.1978 + 0.9957952 191.1978 + 0.9942914 191.1978 + 0.9921359 189.3 + 0.9943052 187.4211 + 0.9959483 186.4887 + 0.995445 188.3582 + 0.9905357 190.2465 + 0.9810765 189.3 + 0.9172173 189.3 + 0.8372061 189.3 + 0.7548154 189.3 + 0.7888815 189.3 + 0.7481033 187.4211 + 0.598231 181.8956 + 0.4594265 180.9906 + 0.3907863 181.8956 + 0.3352165 180.9906 + 0.1176405 178.3027 + 0.07078327 173.046 + -0.04527154 169.6279 + -0.1348021 168.784 + 0.04466751 167.1087 + 0.09953013 164.6269 + 0.0682964 162.182 + 0.06978174 163.8079 + 0.1322864 165.4501 + 0.2013361 169.6279 + 0.2920707 174.7808 + 0.3333177 176.533 + 0.3332107 176.533 + 0.3530797 177.4156 + 0.4650808 179.1942 + 0.5486825 180.9906 + 0.4580497 182.8051 + 0.4908407 184.6377 + 0.4697383 186.4887 + 0.3304503 188.3582 + 0.3914385 190.2465 + 0.6257187 192.1537 + 0.3618582 195.0505 + 0.1043633 197.9909 + 0.3943349 200.9756 + 0.5306126 204.0054 + 0.1373097 207.0808 + 0.2010625 210.2025 + 0.3670651 213.3714 + 0.3145425 216.5879 + 0.8130425 219.853 + 0.8798357 222.057 + 0.9403205 223.1673 + 0.97297 224.2832 + 0.9836287 223.1673 + 0.9911686 222.057 + 0.9915301 220.9523 + 0.9894016 219.853 + 0.9908847 218.7592 + 0.9930059 217.6709 + 0.9904228 217.6709 + 0.962741 213.3714 + 0.9670218 209.1568 + 0.9779282 206.0505 + 0.9113222 202.9904 + 0.863717 196.0257 + 0.7813085 193.1145 + 0.7102375 197.0059 + 0.6673803 201.9805 + 0.7775867 202.9904 + 0.8043374 204.0054 + 0.7297218 200.9756 + 0.5714095 197.0059 + 0.5821126 194.0801 + 0.7140544 193.1145 + 0.7262678 194.0801 + 0.7389845 195.0505 + 0.7598057 196.0257 + 0.7466297 197.9909 + 0.6394933 199.9758 + 0.6129577 201.9805 + 0.672642 202.9904 + 0.715472 202.9904 + 0.7394825 202.9904 + 0.5446303 202.9904 + 0.7562882 202.9904 + 0.7478679 202.9904 + 0.3738182 201.9805 + 0.7470279 200.9756 + 0.7631992 199.9758 + 0.4866134 198.9809 + 0.6727687 197.9909 + 0.6740197 197.0059 + 0.5121869 196.0257 + 0.5960711 195.0505 + 0.4775652 194.0801 + 0.7727789 193.1145 + 0.7569727 192.1537 + 0.3645771 190.2465 + 0.7480979 188.3582 + 0.669458 186.4887 + 0.06009803 184.6377 + -0.2068706 182.8051 + 0.2833839 180.9906 + 0.6422815 179.1942 + 0.3581112 177.4156 + 0.2500266 175.6547 + 0.3498747 173.9112 + 0.569603 172.185 + 0.097353 170.476 + 0.3730714 168.784 + -0.1027659 167.1087 + -0.1343261 165.4501 + -0.1674749 163.8079 + -0.09346908 162.182 + 0.02017644 160.5723 + 0.01829195 158.9785 + 0.2345988 157.4006 + 0.254218 155.8383 + 0.4619125 154.2915 + 0.2792292 165.4501 + 0.6047359 176.533 + 0.7835823 178.3027 + 0.8670148 175.6547 + 0.9359789 173.9112 + 0.9631585 175.6547 + 0.9587292 176.533 + 0.9695755 176.533 + 0.9835575 179.1942 + 0.9900097 179.1942 + 0.9886937 180.0902 + 0.9895495 180.9906 + 0.9944302 183.7191 + 0.9966992 183.7191 + 0.9946688 186.4887 + 0.992237 189.3 + 0.9917749 191.1978 + 0.9863276 192.1537 + 0.9785607 192.1537 + 0.9806378 190.2465 + 0.9924863 189.3 + 0.99017 188.3582 + 0.9820646 187.4211 + 0.9470521 188.3582 + 0.8216711 187.4211 + 0.7153458 190.2465 + 0.7442878 193.1145 + 0.5942695 196.0257 + 0.5665525 197.0059 + 0.535006 195.0505 + 0.6266476 193.1145 + 0.6059493 194.0801 + 0.6425897 198.9809 + 0.8118222 206.0505 + 0.8551767 210.2025 + 0.8889339 211.2535 + 0.947816 211.2535 + 0.9787855 209.1568 + 0.988611 208.1162 + 0.987321 207.0808 + 0.9843316 205.0254 + 0.9737096 204.0054 + 0.9765185 202.9904 + 0.9856598 202.9904 + 0.9898718 204.0054 + 0.9886413 204.0054 + 0.989678 202.9904 + 0.9851701 200.9756 + 0.9817263 198.9809 + 0.9766667 196.0257 + 0.9717003 194.0801 + 0.9652438 193.1145 + 0.9332103 193.1145 + 0.9138572 194.0801 + 0.8846416 194.0801 + 0.9482771 195.0505 + 0.9305538 196.0257 + 0.8390872 197.0059 + 0.7701216 197.9909 + 0.7264634 197.9909 + 0.7708728 197.9909 + 0.8309863 197.9909 + 0.8339165 197.9909 + 0.723191 197.0059 + 0.7247626 196.0257 + 0.608819 195.0505 + 0.3213559 194.0801 + 0.2234745 193.1145 + -0.02573975 191.1978 + 0.05725723 189.3 + 0.218053 187.4211 + 0.4777128 185.5609 + 0.5337029 182.8051 + 0.2318052 180.0902 + 0.4889631 177.4156 + 0.08109105 174.7808 + 0.7427355 172.185 + 0.8433576 171.3284 + 0.938632 170.476 + 0.9607551 168.784 + 0.9642593 167.1087 + 0.963613 166.2773 + 0.9651926 167.1087 + 0.9591027 168.784 + 0.9754831 172.185 + 0.9820764 176.533 + 0.9875088 180.0902 + 0.9851621 181.8956 + 0.9825414 183.7191 + 0.9723955 185.5609 + 0.9567259 187.4211 + 0.8835074 189.3 + 0.8628154 192.1537 + 0.9157982 195.0505 + 0.5938084 198.9809 + 0.6943321 209.1568 + 0.7714629 212.3098 + 0.8662375 211.2535 + 0.849728 205.0254 + 0.7945137 196.0257 + 0.7933522 188.3582 + 0.6368725 178.3027 + 0.5897154 167.1087 + 0.3229582 158.1876 + 0.2969382 144.605 + 0.3351896 141.7487 + 0.384491 146.0546 + 0.04096958 151.2439 + 0.1369363 172.185 + 0.3727793 180.9906 + 0.4194036 183.7191 + 0.4396896 185.5609 + 0.5275242 185.5609 + 0.2498598 186.4887 + 0.05898202 190.2465 + 0.04901373 195.0505 + 0.1100158 197.9909 + 0.2187118 198.9809 + 0.4847157 197.9909 + 0.4972816 197.9909 + 0.5150168 198.9809 + 0.5341532 200.9756 + 0.2631531 202.9904 + 0.1344986 205.0254 + 0.1769232 207.0808 + 0.2019505 209.1568 + 0.3217678 211.2535 + 0.366561 213.3714 + 0.3891162 215.5104 + 0.3456501 217.6709 + 0.3461981 219.853 + 0.1349857 222.057 + 0.06558777 224.2832 + 0.397542 226.5316 + 0.697022 228.8026 + 0.4396819 231.0963 + 0.7819827 233.4131 + 0.7121504 235.753 + 0.2208821 238.1164 + 0.7816582 240.5036 + 0.7002975 242.9146 + 0.03593514 245.3498 + 0.1999688 247.8095 + 0.6519101 249.0485 + 0.5185443 251.5452 + 0.9164662 254.067 + 0.9069665 255.3373 + 0.9549389 254.067 + 0.9885621 252.8029 + 0.9925307 251.5452 + 0.9942389 249.0485 + 0.996495 249.0485 + 0.9955549 247.8095 + 0.9936098 246.5766 + 0.980476 245.3498 + 0.9671 246.5766 + 0.9642795 249.0485 + 0.9722587 249.0485 + 0.974653 249.0485 + 0.9627596 247.8095 + 0.9594576 247.8095 + 0.9352374 247.8095 + 0.9516878 246.5766 + 0.9676836 245.3498 + 0.9551129 246.5766 + 0.9023392 249.0485 + 0.8472683 251.5452 + 0.8856258 254.067 + 0.8884159 255.3373 + 0.9254211 255.3373 + 0.9612991 254.067 + 0.9673759 250.2938 + 0.9728466 245.3498 + 0.967782 242.9146 + 0.9654288 240.5036 + 0.9444144 238.1164 + 0.9269778 236.9318 + 0.9488637 236.9318 + 0.9771202 236.9318 + 0.9830479 235.753 + 0.97499 234.5801 + 0.968623 233.4131 + 0.9521769 233.4131 + 0.9390221 233.4131 + 0.9339334 233.4131 + 0.887956 232.2518 + 0.9046763 231.0963 + 0.8827981 229.9466 + 0.8236388 231.0963 + 0.7853005 231.0963 + 0.8529194 229.9466 + 0.8421693 227.6642 + 0.880672 224.2832 + 0.8143209 219.853 + 0.8765387 215.5104 + 0.8224545 210.2025 + 0.5476995 204.0054 + 0.3984698 197.0059 + 0.2524756 189.3 + 0.0114817 180.9906 + 0.2040667 172.185 + -0.1669366 163.8079 + 0.504205 154.2915 + 0.7180874 151.2439 + 0.8632782 151.2439 + 0.8973259 150.4914 + 0.9185986 151.2439 + 0.7587776 154.2915 + 0.7147576 159.7734 + 0.7589291 165.4501 + 0.8840488 171.3284 + 0.972356 175.6547 + 0.9816653 179.1942 + 0.988443 180.9906 + 0.9923041 181.8956 + 0.9943824 182.8051 + 0.9964477 183.7191 + 0.9936554 181.8956 + 0.9887792 180.0902 + 0.9771134 175.6547 + 0.9618319 170.476 + 0.9328524 167.9442 + 0.819974 168.784 + 0.8450117 170.476 + 0.8293109 171.3284 + 0.6963592 171.3284 + 0.6338254 164.6269 + 0.7285098 158.9785 + 0.7664092 154.2915 + 0.8064316 152.0001 + 0.9278697 152.0001 + 0.9533199 151.2439 + 0.9601526 149.7427 + 0.9562845 148.9977 + 0.963026 149.7427 + 0.9602929 149.7427 + 0.8744306 150.4914 + 0.5927675 154.2915 + 0.3564559 159.7734 + 0.2405773 164.6269 + 0.2860079 169.6279 + 0.6529107 173.9112 + 0.835753 176.533 + 0.8849658 179.1942 + 0.875807 180.9906 + 0.823069 182.8051 + 0.6626108 184.6377 + 0.2836408 187.4211 + 0.6051244 190.2465 + 0.7253854 193.1145 + 0.765985 196.0257 + 0.8194041 198.9809 + 0.6263421 201.9805 + 0.8017131 205.0254 + 0.682602 209.1568 + 0.5036187 213.3714 + 0.5814967 218.7592 + 0.814868 224.2832 + 0.8269848 229.9466 + 0.7358219 235.753 + 0.4268066 241.7061 + 0.8835183 247.8095 + 0.8267587 254.067 + 0.5491355 259.1866 + 0.8746379 264.4093 + 0.8389229 269.7372 + 0.6826822 275.1726 + 0.9028191 280.7175 + 0.8286572 286.3741 + 0.4525082 292.1447 + 0.7583002 298.0316 + 0.9073849 304.037 + 0.9051184 310.1635 + 0.64346 316.4135 + 0.9253378 322.7894 + 0.9132795 329.2938 + 0.6598362 334.2579 + 0.9356085 339.2969 + 0.9236958 344.4118 + 0.8228285 349.6039 + 0.9376769 354.8742 + 0.9356694 360.224 + 0.9184375 365.6544 + 0.9058462 371.1667 + 0.9012623 374.8877 + 0.9611984 378.6459 + 0.9567629 382.4419 + 0.7675866 384.3541 + 0.9645271 386.2758 + 0.9546855 388.2072 + 0.7844603 388.2072 + 0.9640375 388.2072 + 0.9452955 388.2072 + 0.5753929 388.2072 + 0.394645 388.2072 + 0.5464906 388.2072 + 0.7876784 388.2072 + 0.7982122 388.2072 + 0.5932955 388.2072 + 0.4936025 388.2072 + 0.7250561 388.2072 + 0.6559195 388.2072 + 0.7656435 388.2072 + 0.9420093 388.2072 + 0.7749707 388.2072 ] diff --git a/audio/tests/features/testdata/test.wav b/audio/tests/features/testdata/test.wav new file mode 100644 index 0000000000000000000000000000000000000000..6fdfee3c038b30ecc164c5169a44ac6d9c3583d5 GIT binary patch literal 301838 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�eXsJzN|`RhwNDPb6Gn?TK`oS;5Qs3l^*1xe<7Q6 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 RKF$xW#^{Ws@2sfy{{Y;CAXxwa literal 0 HcmV?d00001 diff --git a/audio/tools/setup_helpers/__init__.py b/audio/tools/setup_helpers/__init__.py new file mode 100644 index 00000000..5d375081 --- /dev/null +++ b/audio/tools/setup_helpers/__init__.py @@ -0,0 +1 @@ +from .extension import * diff --git a/audio/tools/setup_helpers/extension.py b/audio/tools/setup_helpers/extension.py new file mode 100644 index 00000000..9b0aca4b --- /dev/null +++ b/audio/tools/setup_helpers/extension.py @@ -0,0 +1,152 @@ +import os +import platform +import subprocess +from pathlib import Path + +import distutils.sysconfig +from setuptools import Extension +from setuptools.command.build_ext import build_ext + +__all__ = [ + "get_ext_modules", + "CMakeBuild", +] + +_THIS_DIR = Path(__file__).parent.resolve() +_ROOT_DIR = _THIS_DIR.parent.parent.resolve() +_PADDLESPEECH_DIR = _ROOT_DIR / "paddleaudio" + + +def _get_build(var, default=False): + if var not in os.environ: + return default + + val = os.environ.get(var, "0") + trues = ["1", "true", "TRUE", "on", "ON", "yes", "YES"] + falses = ["0", "false", "FALSE", "off", "OFF", "no", "NO"] + if val in trues: + return True + if val not in falses: + print(f"WARNING: Unexpected environment variable value `{var}={val}`. " + f"Expected one of {trues + falses}") + return False + + +_BUILD_SOX = False if platform.system() == "Windows" else _get_build( + "BUILD_SOX", True) +_BUILD_MAD = _get_build("BUILD_MAD", False) +_BUILD_KALDI = False if platform.system() == "Windows" else _get_build( + "BUILD_KALDI", True) +_PADDLESPEECH_CUDA_ARCH_LIST = os.environ.get("PADDLESPEECH_CUDA_ARCH_LIST", + None) + +def get_ext_modules(): + if platform.system() == "Windows": + return [] + modules = [ + Extension(name="paddleaudio.lib.libpaddleaudio", sources=[]), + Extension(name="paddleaudio._paddleaudio", sources=[]), + ] + return modules + + +# Based off of +# https://github.com/pybind/cmake_example/blob/580c5fd29d4651db99d8874714b07c0c49a53f8a/setup.py +class CMakeBuild(build_ext): + def run(self): + try: + subprocess.check_output(["cmake", "--version"]) + except OSError: + raise RuntimeError("CMake is not available.") from None + super().run() + + def build_extension(self, ext): + # Since two library files (libpaddleaudio and _paddleaudio) need to be + # recognized by setuptools, we instantiate `Extension` twice. (see `get_ext_modules`) + # This leads to the situation where this `build_extension` method is called twice. + # However, the following `cmake` command will build all of them at the same time, + # so, we do not need to perform `cmake` twice. + # Therefore we call `cmake` only for `paddleaudio._paddleaudio`. + if ext.name != "paddleaudio._paddleaudio": + return + + extdir = os.path.abspath( + os.path.dirname(self.get_ext_fullpath(ext.name))) + + # required for auto-detection of auxiliary "native" libs + if not extdir.endswith(os.path.sep): + extdir += os.path.sep + + cfg = "Debug" if self.debug else "Release" + + cmake_args = [ + f"-DCMAKE_BUILD_TYPE={cfg}", + # f"-DCMAKE_PREFIX_PATH={torch.utils.cmake_prefix_path}", + f"-DCMAKE_INSTALL_PREFIX={extdir}", + "-DCMAKE_VERBOSE_MAKEFILE=ON", + f"-DPYTHON_INCLUDE_DIR={distutils.sysconfig.get_python_inc()}", + #f"-DPYTHON_LIBRARY={distutils.sysconfig.get_config_var('LIBDIR')}", + f"-DBUILD_SOX:BOOL={'ON' if _BUILD_SOX else 'OFF'}", + f"-DBUILD_MAD:BOOL={'ON' if _BUILD_MAD else 'OFF'}", + f"-DBUILD_KALDI:BOOL={'ON' if _BUILD_KALDI else 'OFF'}", + # f"-DBUILD_RNNT:BOOL={'ON' if _BUILD_RNNT else 'OFF'}", + # f"-DBUILD_CTC_DECODER:BOOL={'ON' if _BUILD_CTC_DECODER else 'OFF'}", + "-DBUILD_PADDLEAUDIO_PYTHON_EXTENSION:BOOL=ON", + # f"-DUSE_ROCM:BOOL={'ON' if _USE_ROCM else 'OFF'}", + # f"-DUSE_CUDA:BOOL={'ON' if _USE_CUDA else 'OFF'}", + # f"-DUSE_OPENMP:BOOL={'ON' if _USE_OPENMP else 'OFF'}", + # f"-DUSE_FFMPEG:BOOL={'ON' if _USE_FFMPEG else 'OFF'}", + ] + build_args = ["--target", "install"] + # Pass CUDA architecture to cmake + if _PADDLESPEECH_CUDA_ARCH_LIST is not None: + # Convert MAJOR.MINOR[+PTX] list to new style one + # defined at https://cmake.org/cmake/help/latest/prop_tgt/CUDA_ARCHITECTURES.html + _arches = _PADDLESPEECH_CUDA_ARCH_LIST.replace(".", "").replace( + " ", ";").split(";") + _arches = [ + arch[:-4] if arch.endswith("+PTX") else f"{arch}-real" + for arch in _arches + ] + cmake_args += [f"-DCMAKE_CUDA_ARCHITECTURES={';'.join(_arches)}"] + + # Default to Ninja + if "CMAKE_GENERATOR" not in os.environ or platform.system() == "Windows": + cmake_args += ["-GNinja"] + + if platform.system() == "Windows": + import sys + + python_version = sys.version_info + cmake_args += [ + "-DCMAKE_C_COMPILER=cl", + "-DCMAKE_CXX_COMPILER=cl", + f"-DPYTHON_VERSION={python_version.major}.{python_version.minor}", + ] + + # Set CMAKE_BUILD_PARALLEL_LEVEL to control the parallel build level + # across all generators. + if "CMAKE_BUILD_PARALLEL_LEVEL" not in os.environ: + # self.parallel is a Python 3 only way to set parallel jobs by hand + # using -j in the build_ext call, not supported by pip or PyPA-build. + if hasattr(self, "parallel") and self.parallel: + # CMake 3.12+ only. + build_args += ["-j{}".format(self.parallel)] + + if not os.path.exists(self.build_temp): + os.makedirs(self.build_temp) + + print( + f"cmake {_ROOT_DIR} {' '.join(cmake_args)}, cwd={self.build_temp}") + subprocess.check_call( + ["cmake", str(_ROOT_DIR)] + cmake_args, cwd=self.build_temp) + print(f"cmake --build . {' '.join(build_args)}, cwd={self.build_temp}") + subprocess.check_call( + ["cmake", "--build", "."] + build_args, cwd=self.build_temp) + + def get_ext_filename(self, fullname): + ext_filename = super().get_ext_filename(fullname) + ext_filename_parts = ext_filename.split(".") + without_abi = ext_filename_parts[:-2] + ext_filename_parts[-1:] + ext_filename = ".".join(without_abi) + return ext_filename diff --git a/docs/source/api/paddlespeech.audio.backends.rst b/docs/source/api/paddlespeech.audio.backends.rst deleted file mode 100644 index e8917897..00000000 --- a/docs/source/api/paddlespeech.audio.backends.rst +++ /dev/null @@ -1,16 +0,0 @@ -paddlespeech.audio.backends package -=================================== - -.. automodule:: paddlespeech.audio.backends - :members: - :undoc-members: - :show-inheritance: - -Submodules ----------- - -.. toctree:: - :maxdepth: 4 - - paddlespeech.audio.backends.soundfile_backend - paddlespeech.audio.backends.sox_backend diff --git a/docs/source/api/paddlespeech.audio.backends.soundfile_backend.rst b/docs/source/api/paddlespeech.audio.backends.soundfile_backend.rst deleted file mode 100644 index 5c4ef388..00000000 --- a/docs/source/api/paddlespeech.audio.backends.soundfile_backend.rst +++ /dev/null @@ -1,7 +0,0 @@ -paddlespeech.audio.backends.soundfile\_backend module -===================================================== - -.. automodule:: paddlespeech.audio.backends.soundfile_backend - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/api/paddlespeech.audio.backends.sox_backend.rst b/docs/source/api/paddlespeech.audio.backends.sox_backend.rst deleted file mode 100644 index a99c49de..00000000 --- a/docs/source/api/paddlespeech.audio.backends.sox_backend.rst +++ /dev/null @@ -1,7 +0,0 @@ -paddlespeech.audio.backends.sox\_backend module -=============================================== - -.. automodule:: paddlespeech.audio.backends.sox_backend - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/api/paddlespeech.audio.compliance.kaldi.rst b/docs/source/api/paddlespeech.audio.compliance.kaldi.rst deleted file mode 100644 index f1459cf1..00000000 --- a/docs/source/api/paddlespeech.audio.compliance.kaldi.rst +++ /dev/null @@ -1,7 +0,0 @@ -paddlespeech.audio.compliance.kaldi module -========================================== - -.. automodule:: paddlespeech.audio.compliance.kaldi - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/api/paddlespeech.audio.compliance.rst b/docs/source/api/paddlespeech.audio.compliance.rst deleted file mode 100644 index 515d25e9..00000000 --- a/docs/source/api/paddlespeech.audio.compliance.rst +++ /dev/null @@ -1,16 +0,0 @@ -paddlespeech.audio.compliance package -===================================== - -.. automodule:: paddlespeech.audio.compliance - :members: - :undoc-members: - :show-inheritance: - -Submodules ----------- - -.. toctree:: - :maxdepth: 4 - - paddlespeech.audio.compliance.kaldi - paddlespeech.audio.compliance.librosa diff --git a/docs/source/api/paddlespeech.audio.datasets.dataset.rst b/docs/source/api/paddlespeech.audio.datasets.dataset.rst deleted file mode 100644 index 41243fb7..00000000 --- a/docs/source/api/paddlespeech.audio.datasets.dataset.rst +++ /dev/null @@ -1,7 +0,0 @@ -paddlespeech.audio.datasets.dataset module -========================================== - -.. automodule:: paddlespeech.audio.datasets.dataset - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/api/paddlespeech.audio.datasets.hey_snips.rst b/docs/source/api/paddlespeech.audio.datasets.hey_snips.rst deleted file mode 100644 index ce08b700..00000000 --- a/docs/source/api/paddlespeech.audio.datasets.hey_snips.rst +++ /dev/null @@ -1,7 +0,0 @@ -paddlespeech.audio.datasets.hey\_snips module -============================================= - -.. automodule:: paddlespeech.audio.datasets.hey_snips - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/api/paddlespeech.audio.datasets.rirs_noises.rst b/docs/source/api/paddlespeech.audio.datasets.rirs_noises.rst deleted file mode 100644 index 3015ba9e..00000000 --- a/docs/source/api/paddlespeech.audio.datasets.rirs_noises.rst +++ /dev/null @@ -1,7 +0,0 @@ -paddlespeech.audio.datasets.rirs\_noises module -=============================================== - -.. automodule:: paddlespeech.audio.datasets.rirs_noises - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/api/paddlespeech.audio.datasets.rst b/docs/source/api/paddlespeech.audio.datasets.rst deleted file mode 100644 index bfc313a7..00000000 --- a/docs/source/api/paddlespeech.audio.datasets.rst +++ /dev/null @@ -1,22 +0,0 @@ -paddlespeech.audio.datasets package -=================================== - -.. automodule:: paddlespeech.audio.datasets - :members: - :undoc-members: - :show-inheritance: - -Submodules ----------- - -.. toctree:: - :maxdepth: 4 - - paddlespeech.audio.datasets.dataset - paddlespeech.audio.datasets.esc50 - paddlespeech.audio.datasets.gtzan - paddlespeech.audio.datasets.hey_snips - paddlespeech.audio.datasets.rirs_noises - paddlespeech.audio.datasets.tess - paddlespeech.audio.datasets.urban_sound - paddlespeech.audio.datasets.voxceleb diff --git a/docs/source/api/paddlespeech.audio.datasets.tess.rst b/docs/source/api/paddlespeech.audio.datasets.tess.rst deleted file mode 100644 index d845e6d6..00000000 --- a/docs/source/api/paddlespeech.audio.datasets.tess.rst +++ /dev/null @@ -1,7 +0,0 @@ -paddlespeech.audio.datasets.tess module -======================================= - -.. automodule:: paddlespeech.audio.datasets.tess - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/api/paddlespeech.audio.datasets.urban_sound.rst b/docs/source/api/paddlespeech.audio.datasets.urban_sound.rst deleted file mode 100644 index 4efa060a..00000000 --- a/docs/source/api/paddlespeech.audio.datasets.urban_sound.rst +++ /dev/null @@ -1,7 +0,0 @@ -paddlespeech.audio.datasets.urban\_sound module -=============================================== - -.. automodule:: paddlespeech.audio.datasets.urban_sound - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/api/paddlespeech.audio.datasets.voxceleb.rst b/docs/source/api/paddlespeech.audio.datasets.voxceleb.rst deleted file mode 100644 index 179053dc..00000000 --- a/docs/source/api/paddlespeech.audio.datasets.voxceleb.rst +++ /dev/null @@ -1,7 +0,0 @@ -paddlespeech.audio.datasets.voxceleb module -=========================================== - -.. automodule:: paddlespeech.audio.datasets.voxceleb - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/api/paddlespeech.audio.functional.functional.rst b/docs/source/api/paddlespeech.audio.functional.functional.rst deleted file mode 100644 index 80cc5a5a..00000000 --- a/docs/source/api/paddlespeech.audio.functional.functional.rst +++ /dev/null @@ -1,7 +0,0 @@ -paddlespeech.audio.functional.functional module -=============================================== - -.. automodule:: paddlespeech.audio.functional.functional - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/api/paddlespeech.audio.functional.rst b/docs/source/api/paddlespeech.audio.functional.rst deleted file mode 100644 index 4e979dd9..00000000 --- a/docs/source/api/paddlespeech.audio.functional.rst +++ /dev/null @@ -1,16 +0,0 @@ -paddlespeech.audio.functional package -===================================== - -.. automodule:: paddlespeech.audio.functional - :members: - :undoc-members: - :show-inheritance: - -Submodules ----------- - -.. toctree:: - :maxdepth: 4 - - paddlespeech.audio.functional.functional - paddlespeech.audio.functional.window diff --git a/docs/source/api/paddlespeech.audio.functional.window.rst b/docs/source/api/paddlespeech.audio.functional.window.rst deleted file mode 100644 index 34776275..00000000 --- a/docs/source/api/paddlespeech.audio.functional.window.rst +++ /dev/null @@ -1,7 +0,0 @@ -paddlespeech.audio.functional.window module -=========================================== - -.. automodule:: paddlespeech.audio.functional.window - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/api/paddlespeech.audio.metric.rst b/docs/source/api/paddlespeech.audio.metric.rst deleted file mode 100644 index a6d411dd..00000000 --- a/docs/source/api/paddlespeech.audio.metric.rst +++ /dev/null @@ -1,15 +0,0 @@ -paddlespeech.audio.metric package -================================= - -.. automodule:: paddlespeech.audio.metric - :members: - :undoc-members: - :show-inheritance: - -Submodules ----------- - -.. toctree:: - :maxdepth: 4 - - paddlespeech.audio.metric.eer diff --git a/docs/source/api/paddlespeech.audio.rst b/docs/source/api/paddlespeech.audio.rst index 4ed7e467..368ffda9 100644 --- a/docs/source/api/paddlespeech.audio.rst +++ b/docs/source/api/paddlespeech.audio.rst @@ -12,12 +12,10 @@ Subpackages .. toctree:: :maxdepth: 4 - paddlespeech.audio.backends - paddlespeech.audio.compliance - paddlespeech.audio.datasets paddlespeech.audio.features paddlespeech.audio.functional paddlespeech.audio.io + paddlespeech.audio.kaldi paddlespeech.audio.metric paddlespeech.audio.sox_effects paddlespeech.audio.streamdata diff --git a/docs/source/api/paddlespeech.audio.utils.rst b/docs/source/api/paddlespeech.audio.utils.rst index 217afa8f..0f1150ff 100644 --- a/docs/source/api/paddlespeech.audio.utils.rst +++ b/docs/source/api/paddlespeech.audio.utils.rst @@ -18,5 +18,6 @@ Submodules paddlespeech.audio.utils.error paddlespeech.audio.utils.log paddlespeech.audio.utils.numeric + paddlespeech.audio.utils.sox_utils paddlespeech.audio.utils.tensor_utils paddlespeech.audio.utils.time diff --git a/docs/source/api/paddlespeech.cls.exps.panns.rst b/docs/source/api/paddlespeech.cls.exps.panns.rst index 72f30ba6..c50382fb 100644 --- a/docs/source/api/paddlespeech.cls.exps.panns.rst +++ b/docs/source/api/paddlespeech.cls.exps.panns.rst @@ -19,4 +19,3 @@ Submodules .. toctree:: :maxdepth: 4 - diff --git a/docs/source/api/paddlespeech.kws.exps.mdtc.plot_det_curve.rst b/docs/source/api/paddlespeech.kws.exps.mdtc.plot_det_curve.rst new file mode 100644 index 00000000..46a149b0 --- /dev/null +++ b/docs/source/api/paddlespeech.kws.exps.mdtc.plot_det_curve.rst @@ -0,0 +1,7 @@ +paddlespeech.kws.exps.mdtc.plot\_det\_curve module +================================================== + +.. automodule:: paddlespeech.kws.exps.mdtc.plot_det_curve + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/api/paddlespeech.rst b/docs/source/api/paddlespeech.rst index d06cd2c7..70b93ca0 100644 --- a/docs/source/api/paddlespeech.rst +++ b/docs/source/api/paddlespeech.rst @@ -23,3 +23,11 @@ Subpackages paddlespeech.text paddlespeech.utils paddlespeech.vector + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + paddlespeech.version diff --git a/docs/source/api/paddlespeech.t2s.exps.rst b/docs/source/api/paddlespeech.t2s.exps.rst index 643f97b4..58bbccb2 100644 --- a/docs/source/api/paddlespeech.t2s.exps.rst +++ b/docs/source/api/paddlespeech.t2s.exps.rst @@ -34,6 +34,7 @@ Submodules paddlespeech.t2s.exps.ort_predict paddlespeech.t2s.exps.ort_predict_e2e paddlespeech.t2s.exps.ort_predict_streaming + paddlespeech.t2s.exps.stream_play_tts paddlespeech.t2s.exps.syn_utils paddlespeech.t2s.exps.synthesize paddlespeech.t2s.exps.synthesize_e2e diff --git a/docs/source/api/paddlespeech.t2s.exps.stream_play_tts.rst b/docs/source/api/paddlespeech.t2s.exps.stream_play_tts.rst new file mode 100644 index 00000000..cb22dde0 --- /dev/null +++ b/docs/source/api/paddlespeech.t2s.exps.stream_play_tts.rst @@ -0,0 +1,7 @@ +paddlespeech.t2s.exps.stream\_play\_tts module +============================================== + +.. automodule:: paddlespeech.t2s.exps.stream_play_tts + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/api/paddlespeech.t2s.models.vits.monotonic_align.core.rst b/docs/source/api/paddlespeech.t2s.models.vits.monotonic_align.core.rst new file mode 100644 index 00000000..7aaba795 --- /dev/null +++ b/docs/source/api/paddlespeech.t2s.models.vits.monotonic_align.core.rst @@ -0,0 +1,7 @@ +paddlespeech.t2s.models.vits.monotonic\_align.core module +========================================================= + +.. automodule:: paddlespeech.t2s.models.vits.monotonic_align.core + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/api/paddlespeech.t2s.models.vits.monotonic_align.rst b/docs/source/api/paddlespeech.t2s.models.vits.monotonic_align.rst new file mode 100644 index 00000000..25c819a7 --- /dev/null +++ b/docs/source/api/paddlespeech.t2s.models.vits.monotonic_align.rst @@ -0,0 +1,16 @@ +paddlespeech.t2s.models.vits.monotonic\_align package +===================================================== + +.. automodule:: paddlespeech.t2s.models.vits.monotonic_align + :members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + paddlespeech.t2s.models.vits.monotonic_align.core + paddlespeech.t2s.models.vits.monotonic_align.setup diff --git a/docs/source/api/paddlespeech.t2s.models.vits.monotonic_align.setup.rst b/docs/source/api/paddlespeech.t2s.models.vits.monotonic_align.setup.rst new file mode 100644 index 00000000..a93c3b8b --- /dev/null +++ b/docs/source/api/paddlespeech.t2s.models.vits.monotonic_align.setup.rst @@ -0,0 +1,7 @@ +paddlespeech.t2s.models.vits.monotonic\_align.setup module +========================================================== + +.. automodule:: paddlespeech.t2s.models.vits.monotonic_align.setup + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/api/paddlespeech.version.rst b/docs/source/api/paddlespeech.version.rst new file mode 100644 index 00000000..707c5f88 --- /dev/null +++ b/docs/source/api/paddlespeech.version.rst @@ -0,0 +1,7 @@ +paddlespeech.version module +=========================== + +.. automodule:: paddlespeech.version + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/audio_api/modules.rst b/docs/source/audio_api/modules.rst new file mode 100644 index 00000000..f4d1fda6 --- /dev/null +++ b/docs/source/audio_api/modules.rst @@ -0,0 +1,7 @@ +audio +===== + +.. toctree:: + :maxdepth: 4 + + paddleaudio diff --git a/docs/source/audio_api/paddleaudio.backends.common.rst b/docs/source/audio_api/paddleaudio.backends.common.rst new file mode 100644 index 00000000..c936645e --- /dev/null +++ b/docs/source/audio_api/paddleaudio.backends.common.rst @@ -0,0 +1,7 @@ +paddleaudio.backends.common module +================================== + +.. automodule:: paddleaudio.backends.common + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/api/paddlespeech.audio.sox_effects.rst b/docs/source/audio_api/paddleaudio.backends.no_backend.rst similarity index 51% rename from docs/source/api/paddlespeech.audio.sox_effects.rst rename to docs/source/audio_api/paddleaudio.backends.no_backend.rst index 75f991a1..bf01dab2 100644 --- a/docs/source/api/paddlespeech.audio.sox_effects.rst +++ b/docs/source/audio_api/paddleaudio.backends.no_backend.rst @@ -1,7 +1,7 @@ -paddlespeech.audio.sox\_effects package +paddleaudio.backends.no\_backend module ======================================= -.. automodule:: paddlespeech.audio.sox_effects +.. automodule:: paddleaudio.backends.no_backend :members: :undoc-members: :show-inheritance: diff --git a/docs/source/audio_api/paddleaudio.backends.rst b/docs/source/audio_api/paddleaudio.backends.rst new file mode 100644 index 00000000..79907dd2 --- /dev/null +++ b/docs/source/audio_api/paddleaudio.backends.rst @@ -0,0 +1,19 @@ +paddleaudio.backends package +============================ + +.. automodule:: paddleaudio.backends + :members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + paddleaudio.backends.common + paddleaudio.backends.no_backend + paddleaudio.backends.soundfile_backend + paddleaudio.backends.sox_io_backend + paddleaudio.backends.utils diff --git a/docs/source/audio_api/paddleaudio.backends.soundfile_backend.rst b/docs/source/audio_api/paddleaudio.backends.soundfile_backend.rst new file mode 100644 index 00000000..6146373c --- /dev/null +++ b/docs/source/audio_api/paddleaudio.backends.soundfile_backend.rst @@ -0,0 +1,7 @@ +paddleaudio.backends.soundfile\_backend module +============================================== + +.. automodule:: paddleaudio.backends.soundfile_backend + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/api/paddlespeech.audio.compliance.librosa.rst b/docs/source/audio_api/paddleaudio.backends.sox_io_backend.rst similarity index 50% rename from docs/source/api/paddlespeech.audio.compliance.librosa.rst rename to docs/source/audio_api/paddleaudio.backends.sox_io_backend.rst index 85271bee..04972706 100644 --- a/docs/source/api/paddlespeech.audio.compliance.librosa.rst +++ b/docs/source/audio_api/paddleaudio.backends.sox_io_backend.rst @@ -1,7 +1,7 @@ -paddlespeech.audio.compliance.librosa module +paddleaudio.backends.sox\_io\_backend module ============================================ -.. automodule:: paddlespeech.audio.compliance.librosa +.. automodule:: paddleaudio.backends.sox_io_backend :members: :undoc-members: :show-inheritance: diff --git a/docs/source/audio_api/paddleaudio.backends.utils.rst b/docs/source/audio_api/paddleaudio.backends.utils.rst new file mode 100644 index 00000000..c4cd5e1e --- /dev/null +++ b/docs/source/audio_api/paddleaudio.backends.utils.rst @@ -0,0 +1,7 @@ +paddleaudio.backends.utils module +================================= + +.. automodule:: paddleaudio.backends.utils + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/audio_api/paddleaudio.compliance.kaldi.rst b/docs/source/audio_api/paddleaudio.compliance.kaldi.rst new file mode 100644 index 00000000..81bb7d64 --- /dev/null +++ b/docs/source/audio_api/paddleaudio.compliance.kaldi.rst @@ -0,0 +1,7 @@ +paddleaudio.compliance.kaldi module +=================================== + +.. automodule:: paddleaudio.compliance.kaldi + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/audio_api/paddleaudio.compliance.librosa.rst b/docs/source/audio_api/paddleaudio.compliance.librosa.rst new file mode 100644 index 00000000..553e4d3a --- /dev/null +++ b/docs/source/audio_api/paddleaudio.compliance.librosa.rst @@ -0,0 +1,7 @@ +paddleaudio.compliance.librosa module +===================================== + +.. automodule:: paddleaudio.compliance.librosa + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/audio_api/paddleaudio.compliance.rst b/docs/source/audio_api/paddleaudio.compliance.rst new file mode 100644 index 00000000..137599bb --- /dev/null +++ b/docs/source/audio_api/paddleaudio.compliance.rst @@ -0,0 +1,16 @@ +paddleaudio.compliance package +============================== + +.. automodule:: paddleaudio.compliance + :members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + paddleaudio.compliance.kaldi + paddleaudio.compliance.librosa diff --git a/docs/source/audio_api/paddleaudio.datasets.dataset.rst b/docs/source/audio_api/paddleaudio.datasets.dataset.rst new file mode 100644 index 00000000..ebf4ea18 --- /dev/null +++ b/docs/source/audio_api/paddleaudio.datasets.dataset.rst @@ -0,0 +1,7 @@ +paddleaudio.datasets.dataset module +=================================== + +.. automodule:: paddleaudio.datasets.dataset + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/audio_api/paddleaudio.datasets.esc50.rst b/docs/source/audio_api/paddleaudio.datasets.esc50.rst new file mode 100644 index 00000000..2730fb91 --- /dev/null +++ b/docs/source/audio_api/paddleaudio.datasets.esc50.rst @@ -0,0 +1,7 @@ +paddleaudio.datasets.esc50 module +================================= + +.. automodule:: paddleaudio.datasets.esc50 + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/audio_api/paddleaudio.datasets.gtzan.rst b/docs/source/audio_api/paddleaudio.datasets.gtzan.rst new file mode 100644 index 00000000..da3600cb --- /dev/null +++ b/docs/source/audio_api/paddleaudio.datasets.gtzan.rst @@ -0,0 +1,7 @@ +paddleaudio.datasets.gtzan module +================================= + +.. automodule:: paddleaudio.datasets.gtzan + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/audio_api/paddleaudio.datasets.hey_snips.rst b/docs/source/audio_api/paddleaudio.datasets.hey_snips.rst new file mode 100644 index 00000000..29da9fa8 --- /dev/null +++ b/docs/source/audio_api/paddleaudio.datasets.hey_snips.rst @@ -0,0 +1,7 @@ +paddleaudio.datasets.hey\_snips module +====================================== + +.. automodule:: paddleaudio.datasets.hey_snips + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/api/paddlespeech.audio.datasets.gtzan.rst b/docs/source/audio_api/paddleaudio.datasets.rirs_noises.rst similarity index 51% rename from docs/source/api/paddlespeech.audio.datasets.gtzan.rst rename to docs/source/audio_api/paddleaudio.datasets.rirs_noises.rst index 47252e8d..26f52346 100644 --- a/docs/source/api/paddlespeech.audio.datasets.gtzan.rst +++ b/docs/source/audio_api/paddleaudio.datasets.rirs_noises.rst @@ -1,7 +1,7 @@ -paddlespeech.audio.datasets.gtzan module +paddleaudio.datasets.rirs\_noises module ======================================== -.. automodule:: paddlespeech.audio.datasets.gtzan +.. automodule:: paddleaudio.datasets.rirs_noises :members: :undoc-members: :show-inheritance: diff --git a/docs/source/audio_api/paddleaudio.datasets.rst b/docs/source/audio_api/paddleaudio.datasets.rst new file mode 100644 index 00000000..7a0b6f3f --- /dev/null +++ b/docs/source/audio_api/paddleaudio.datasets.rst @@ -0,0 +1,22 @@ +paddleaudio.datasets package +============================ + +.. automodule:: paddleaudio.datasets + :members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + paddleaudio.datasets.dataset + paddleaudio.datasets.esc50 + paddleaudio.datasets.gtzan + paddleaudio.datasets.hey_snips + paddleaudio.datasets.rirs_noises + paddleaudio.datasets.tess + paddleaudio.datasets.urban_sound + paddleaudio.datasets.voxceleb diff --git a/docs/source/audio_api/paddleaudio.datasets.tess.rst b/docs/source/audio_api/paddleaudio.datasets.tess.rst new file mode 100644 index 00000000..7a4ad62a --- /dev/null +++ b/docs/source/audio_api/paddleaudio.datasets.tess.rst @@ -0,0 +1,7 @@ +paddleaudio.datasets.tess module +================================ + +.. automodule:: paddleaudio.datasets.tess + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/api/paddlespeech.audio.datasets.esc50.rst b/docs/source/audio_api/paddleaudio.datasets.urban_sound.rst similarity index 51% rename from docs/source/api/paddlespeech.audio.datasets.esc50.rst rename to docs/source/audio_api/paddleaudio.datasets.urban_sound.rst index 80e4a418..ee4ad47e 100644 --- a/docs/source/api/paddlespeech.audio.datasets.esc50.rst +++ b/docs/source/audio_api/paddleaudio.datasets.urban_sound.rst @@ -1,7 +1,7 @@ -paddlespeech.audio.datasets.esc50 module +paddleaudio.datasets.urban\_sound module ======================================== -.. automodule:: paddlespeech.audio.datasets.esc50 +.. automodule:: paddleaudio.datasets.urban_sound :members: :undoc-members: :show-inheritance: diff --git a/docs/source/api/paddlespeech.audio.metric.eer.rst b/docs/source/audio_api/paddleaudio.datasets.voxceleb.rst similarity index 52% rename from docs/source/api/paddlespeech.audio.metric.eer.rst rename to docs/source/audio_api/paddleaudio.datasets.voxceleb.rst index bbe88122..b8f90366 100644 --- a/docs/source/api/paddlespeech.audio.metric.eer.rst +++ b/docs/source/audio_api/paddleaudio.datasets.voxceleb.rst @@ -1,7 +1,7 @@ -paddlespeech.audio.metric.eer module +paddleaudio.datasets.voxceleb module ==================================== -.. automodule:: paddlespeech.audio.metric.eer +.. automodule:: paddleaudio.datasets.voxceleb :members: :undoc-members: :show-inheritance: diff --git a/docs/source/audio_api/paddleaudio.features.layers.rst b/docs/source/audio_api/paddleaudio.features.layers.rst new file mode 100644 index 00000000..90833e0a --- /dev/null +++ b/docs/source/audio_api/paddleaudio.features.layers.rst @@ -0,0 +1,7 @@ +paddleaudio.features.layers module +================================== + +.. automodule:: paddleaudio.features.layers + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/audio_api/paddleaudio.features.rst b/docs/source/audio_api/paddleaudio.features.rst new file mode 100644 index 00000000..86ecb5c9 --- /dev/null +++ b/docs/source/audio_api/paddleaudio.features.rst @@ -0,0 +1,15 @@ +paddleaudio.features package +============================ + +.. automodule:: paddleaudio.features + :members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + paddleaudio.features.layers diff --git a/docs/source/audio_api/paddleaudio.functional.functional.rst b/docs/source/audio_api/paddleaudio.functional.functional.rst new file mode 100644 index 00000000..d1f72052 --- /dev/null +++ b/docs/source/audio_api/paddleaudio.functional.functional.rst @@ -0,0 +1,7 @@ +paddleaudio.functional.functional module +======================================== + +.. automodule:: paddleaudio.functional.functional + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/audio_api/paddleaudio.functional.rst b/docs/source/audio_api/paddleaudio.functional.rst new file mode 100644 index 00000000..be76de79 --- /dev/null +++ b/docs/source/audio_api/paddleaudio.functional.rst @@ -0,0 +1,16 @@ +paddleaudio.functional package +============================== + +.. automodule:: paddleaudio.functional + :members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + paddleaudio.functional.functional + paddleaudio.functional.window diff --git a/docs/source/audio_api/paddleaudio.functional.window.rst b/docs/source/audio_api/paddleaudio.functional.window.rst new file mode 100644 index 00000000..46d89f3f --- /dev/null +++ b/docs/source/audio_api/paddleaudio.functional.window.rst @@ -0,0 +1,7 @@ +paddleaudio.functional.window module +==================================== + +.. automodule:: paddleaudio.functional.window + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/audio_api/paddleaudio.kaldi.kaldi.rst b/docs/source/audio_api/paddleaudio.kaldi.kaldi.rst new file mode 100644 index 00000000..90b07661 --- /dev/null +++ b/docs/source/audio_api/paddleaudio.kaldi.kaldi.rst @@ -0,0 +1,7 @@ +paddleaudio.kaldi.kaldi module +============================== + +.. automodule:: paddleaudio.kaldi.kaldi + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/audio_api/paddleaudio.kaldi.rst b/docs/source/audio_api/paddleaudio.kaldi.rst new file mode 100644 index 00000000..c61b4366 --- /dev/null +++ b/docs/source/audio_api/paddleaudio.kaldi.rst @@ -0,0 +1,15 @@ +paddleaudio.kaldi package +========================= + +.. automodule:: paddleaudio.kaldi + :members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + paddleaudio.kaldi.kaldi diff --git a/docs/source/audio_api/paddleaudio.metric.eer.rst b/docs/source/audio_api/paddleaudio.metric.eer.rst new file mode 100644 index 00000000..e4b4f5f3 --- /dev/null +++ b/docs/source/audio_api/paddleaudio.metric.eer.rst @@ -0,0 +1,7 @@ +paddleaudio.metric.eer module +============================= + +.. automodule:: paddleaudio.metric.eer + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/audio_api/paddleaudio.metric.rst b/docs/source/audio_api/paddleaudio.metric.rst new file mode 100644 index 00000000..0074f0b5 --- /dev/null +++ b/docs/source/audio_api/paddleaudio.metric.rst @@ -0,0 +1,15 @@ +paddleaudio.metric package +========================== + +.. automodule:: paddleaudio.metric + :members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + paddleaudio.metric.eer diff --git a/docs/source/audio_api/paddleaudio.rst b/docs/source/audio_api/paddleaudio.rst new file mode 100644 index 00000000..1f3b2a1c --- /dev/null +++ b/docs/source/audio_api/paddleaudio.rst @@ -0,0 +1,23 @@ +paddleaudio package +=================== + +.. automodule:: paddleaudio + :members: + :undoc-members: + :show-inheritance: + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + paddleaudio.backends + paddleaudio.compliance + paddleaudio.datasets + paddleaudio.features + paddleaudio.functional + paddleaudio.kaldi + paddleaudio.metric + paddleaudio.sox_effects + paddleaudio.utils diff --git a/docs/source/audio_api/paddleaudio.sox_effects.rst b/docs/source/audio_api/paddleaudio.sox_effects.rst new file mode 100644 index 00000000..16979dd7 --- /dev/null +++ b/docs/source/audio_api/paddleaudio.sox_effects.rst @@ -0,0 +1,15 @@ +paddleaudio.sox\_effects package +================================ + +.. automodule:: paddleaudio.sox_effects + :members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + paddleaudio.sox_effects.sox_effects diff --git a/docs/source/audio_api/paddleaudio.sox_effects.sox_effects.rst b/docs/source/audio_api/paddleaudio.sox_effects.sox_effects.rst new file mode 100644 index 00000000..d9616d2e --- /dev/null +++ b/docs/source/audio_api/paddleaudio.sox_effects.sox_effects.rst @@ -0,0 +1,7 @@ +paddleaudio.sox\_effects.sox\_effects module +============================================ + +.. automodule:: paddleaudio.sox_effects.sox_effects + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/audio_api/paddleaudio.utils.download.rst b/docs/source/audio_api/paddleaudio.utils.download.rst new file mode 100644 index 00000000..43ff441a --- /dev/null +++ b/docs/source/audio_api/paddleaudio.utils.download.rst @@ -0,0 +1,7 @@ +paddleaudio.utils.download module +================================= + +.. automodule:: paddleaudio.utils.download + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/audio_api/paddleaudio.utils.env.rst b/docs/source/audio_api/paddleaudio.utils.env.rst new file mode 100644 index 00000000..eb398712 --- /dev/null +++ b/docs/source/audio_api/paddleaudio.utils.env.rst @@ -0,0 +1,7 @@ +paddleaudio.utils.env module +============================ + +.. automodule:: paddleaudio.utils.env + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/audio_api/paddleaudio.utils.error.rst b/docs/source/audio_api/paddleaudio.utils.error.rst new file mode 100644 index 00000000..0965835a --- /dev/null +++ b/docs/source/audio_api/paddleaudio.utils.error.rst @@ -0,0 +1,7 @@ +paddleaudio.utils.error module +============================== + +.. automodule:: paddleaudio.utils.error + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/audio_api/paddleaudio.utils.log.rst b/docs/source/audio_api/paddleaudio.utils.log.rst new file mode 100644 index 00000000..ccd6c8c2 --- /dev/null +++ b/docs/source/audio_api/paddleaudio.utils.log.rst @@ -0,0 +1,7 @@ +paddleaudio.utils.log module +============================ + +.. automodule:: paddleaudio.utils.log + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/audio_api/paddleaudio.utils.numeric.rst b/docs/source/audio_api/paddleaudio.utils.numeric.rst new file mode 100644 index 00000000..dc8bd5a5 --- /dev/null +++ b/docs/source/audio_api/paddleaudio.utils.numeric.rst @@ -0,0 +1,7 @@ +paddleaudio.utils.numeric module +================================ + +.. automodule:: paddleaudio.utils.numeric + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/audio_api/paddleaudio.utils.rst b/docs/source/audio_api/paddleaudio.utils.rst new file mode 100644 index 00000000..71ebe88e --- /dev/null +++ b/docs/source/audio_api/paddleaudio.utils.rst @@ -0,0 +1,22 @@ +paddleaudio.utils package +========================= + +.. automodule:: paddleaudio.utils + :members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +.. toctree:: + :maxdepth: 4 + + paddleaudio.utils.download + paddleaudio.utils.env + paddleaudio.utils.error + paddleaudio.utils.log + paddleaudio.utils.numeric + paddleaudio.utils.sox_utils + paddleaudio.utils.tensor_utils + paddleaudio.utils.time diff --git a/docs/source/audio_api/paddleaudio.utils.sox_utils.rst b/docs/source/audio_api/paddleaudio.utils.sox_utils.rst new file mode 100644 index 00000000..66a150ee --- /dev/null +++ b/docs/source/audio_api/paddleaudio.utils.sox_utils.rst @@ -0,0 +1,7 @@ +paddleaudio.utils.sox\_utils module +=================================== + +.. automodule:: paddleaudio.utils.sox_utils + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/audio_api/paddleaudio.utils.tensor_utils.rst b/docs/source/audio_api/paddleaudio.utils.tensor_utils.rst new file mode 100644 index 00000000..b4bdc8e4 --- /dev/null +++ b/docs/source/audio_api/paddleaudio.utils.tensor_utils.rst @@ -0,0 +1,7 @@ +paddleaudio.utils.tensor\_utils module +====================================== + +.. automodule:: paddleaudio.utils.tensor_utils + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/audio_api/paddleaudio.utils.time.rst b/docs/source/audio_api/paddleaudio.utils.time.rst new file mode 100644 index 00000000..e57c595b --- /dev/null +++ b/docs/source/audio_api/paddleaudio.utils.time.rst @@ -0,0 +1,7 @@ +paddleaudio.utils.time module +============================= + +.. automodule:: paddleaudio.utils.time + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/cls/custom_dataset.md b/docs/source/cls/custom_dataset.md index b7c06cd7..7482d5ed 100644 --- a/docs/source/cls/custom_dataset.md +++ b/docs/source/cls/custom_dataset.md @@ -14,7 +14,7 @@ Assuming you have some wave files that stored in your own directory. You should Here is an example to build your custom dataset in `custom_dataset.py`: ```python -from paddlespeech.audio.datasets.dataset import AudioClassificationDataset +from paddleaudio.datasets.dataset import AudioClassificationDataset class CustomDataset(AudioClassificationDataset): meta_file = '/PATH/TO/META_FILE.txt' @@ -48,7 +48,7 @@ class CustomDataset(AudioClassificationDataset): Then you can build dataset and data loader from `CustomDataset`: ```python import paddle -from paddlespeech.audio.features import LogMelSpectrogram +from paddleaudio.features import LogMelSpectrogram from custom_dataset import CustomDataset diff --git a/docs/source/conf.py b/docs/source/conf.py index cd9b1807..d7d4d201 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -26,6 +26,8 @@ import sys import recommonmark.parser import sphinx_rtd_theme sys.path.insert(0, os.path.abspath('../..')) +sys.path.insert(0, os.path.abspath('../../audio')) + autodoc_mock_imports = ["soundfile", "librosa"] diff --git a/docs/source/index.rst b/docs/source/index.rst index 8540d3fc..48bba3bb 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -70,6 +70,7 @@ Contents :maxdepth: 2 :caption: API Reference + paddleaudio paddlespeech.audio paddlespeech.cli paddlespeech.cls diff --git a/docs/source/install.md b/docs/source/install.md index aa3d311c..61133dfa 100644 --- a/docs/source/install.md +++ b/docs/source/install.md @@ -63,8 +63,8 @@ pip install paddlespeech -i https://pypi.tuna.tsinghua.edu.cn/simple ``` You can also specify the version of paddlepaddle or install the develop version. ```bash -# install 2.3.1 version. Note, 2.3.1 is just an example, please follow the minimum dependency of paddlepaddle for your selection -pip install paddlepaddle==2.3.1 -i https://mirror.baidu.com/pypi/simple +# install 2.4.0 version. Note, 2.4.0 is just an example, please follow the minimum dependency of paddlepaddle for your selection +pip install paddlepaddle==2.4.0 -i https://mirror.baidu.com/pypi/simple # install develop version pip install paddlepaddle==0.0.0 -f https://www.paddlepaddle.org.cn/whl/linux/cpu-mkl/develop.html ``` @@ -124,9 +124,9 @@ conda install -y -c gcc_linux-64=8.4.0 gxx_linux-64=8.4.0 ``` (Hip: Do not use the last script if you want to install by **Hard** way): ### Install PaddlePaddle -You can choose the `PaddlePaddle` version based on your system. For example, for CUDA 10.2, CuDNN7.6 install paddlepaddle-gpu 2.4rc: +You can choose the `PaddlePaddle` version based on your system. For example, for CUDA 10.2, CuDNN7.6 install paddlepaddle-gpu 2.4: ```bash -# Note, 2.4rc is just an example, please follow the minimum dependency of paddlepaddle for your selection +# Note, 2.4 is just an example, please follow the minimum dependency of paddlepaddle for your selection python3 -m pip install paddlepaddle-gpu==2.4.0rc0 -i https://mirror.baidu.com/pypi/simple ``` You can also install the develop version of paddlepaddle. For example, for CUDA 10.2, CuDNN7.6 install paddlepaddle-gpu develop: @@ -188,10 +188,10 @@ conda activate tools/venv conda install -y -c conda-forge sox libsndfile swig bzip2 libflac bc ``` ### Install PaddlePaddle -Make sure you have GPU and the paddlepaddle version is right. For example, for CUDA 10.2, CuDNN7.6 install paddle 2.4rc: +Make sure you have GPU and the paddlepaddle version is right. For example, for CUDA 10.2, CuDNN7.6 install paddle 2.4: ```bash -# Note, 2.4rc is just an example, please follow the minimum dependency of paddlepaddle for your selection -python3 -m pip install paddlepaddle-gpu==2.4.0rc0 -i https://mirror.baidu.com/pypi/simple +# Note, 2.4 is just an example, please follow the minimum dependency of paddlepaddle for your selection +python3 -m pip install paddlepaddle-gpu==2.4.0 -i https://mirror.baidu.com/pypi/simple ``` You can also install the develop version of paddlepaddle. For example, for CUDA 10.2, CuDNN7.6 install paddlepaddle-gpu develop: ```bash diff --git a/examples/esc50/cls0/conf/panns.yaml b/examples/esc50/cls0/conf/panns.yaml index 1f0323f0..a0668b27 100644 --- a/examples/esc50/cls0/conf/panns.yaml +++ b/examples/esc50/cls0/conf/panns.yaml @@ -1,5 +1,5 @@ data: - dataset: 'paddlespeech.audio.datasets:ESC50' + dataset: 'paddle.audio.datasets:ESC50' num_classes: 50 train: mode: 'train' @@ -33,4 +33,4 @@ training: predicting: audio_file: '/audio/dog.wav' top_k: 10 - checkpoint: './checkpoint/epoch_50/model.pdparams' \ No newline at end of file + checkpoint: './checkpoint/epoch_50/model.pdparams' diff --git a/examples/hey_snips/kws0/conf/mdtc.yaml b/examples/hey_snips/kws0/conf/mdtc.yaml index 54d05947..857d36d4 100644 --- a/examples/hey_snips/kws0/conf/mdtc.yaml +++ b/examples/hey_snips/kws0/conf/mdtc.yaml @@ -2,7 +2,7 @@ ########################################### # Data # ########################################### -dataset: 'paddlespeech.audio.datasets:HeySnips' +dataset: 'paddleaudio.datasets:HeySnips' data_dir: '../tests/hey_snips_research_6k_en_train_eval_clean_ter' ############################################ diff --git a/examples/tess/README.md b/examples/tess/README.md new file mode 100644 index 00000000..0439841c --- /dev/null +++ b/examples/tess/README.md @@ -0,0 +1,34 @@ +# 背景 + +TESS音频情绪分类任务。 +从而校验和测试 paddle.audio 的feature, backend等相关模块。 + +本实验采用了PaddleSpeech提供了PANNs的CNN14的预训练模型进行finetune: +- CNN14: 该模型主要包含12个卷积层和2个全连接层,模型参数的数量为 79.6M,embbedding维度是 2048。 + +`PANNs`([PANNs: Large-Scale Pretrained Audio Neural Networks for Audio Pattern Recognition](https://arxiv.org/pdf/1912.10211.pdf))是基于Audioset数据集训练的声音分类/识别的模型。经过预训练后,模型可以用于提取音频的embbedding。本示例将使用`PANNs`的预训练模型Finetune完成声音分类的任务。 + +## 数据集 + +[TESS: Toronto emotional speech set](https://tspace.library.utoronto.ca/handle/1807/24487) 是一个包含有 200 个目标词的时长为 2 ~ 3 秒的音频,七种情绪的数据集。由两个女演员录制(24岁和64岁),其中情绪分别是愤怒,恶心,害怕,高兴,惊喜,伤心,平淡。 + +## 模型指标 + +根据 `TESS` 提供的fold信息,对数据集进行 5-fold 的 fine-tune 训练和评估,dev准确率如下: + +|Model|feat_type|Acc| note | +|--|--|--| -- | +|CNN14| mfcc | 0.9929 |3 epoch | +|CNN14| logmelspectrogram | 0.9983 | 3 epoch | +|CNN14| spectrogram| 0.95 | 11 epoch | +|CNN14| melspectrogram| 0.9375 | 17 epoch | + +### 模型训练 + +启动训练: +```shell +$ CUDA_VISIBLE_DEVICES=0 ./run.sh 1 conf/panns_mfcc.yaml +$ CUDA_VISIBLE_DEVICES=0 ./run.sh 1 conf/panns_logmelspectrogram.yaml +$ CUDA_VISIBLE_DEVICES=0 ./run.sh 1 conf/panns_melspectrogram.yaml +$ CUDA_VISIBLE_DEVICES=0 ./run.sh 1 conf/panns_pectrogram.yaml +``` diff --git a/examples/tess/cls0/conf/panns_logmelspectrogram.yaml b/examples/tess/cls0/conf/panns_logmelspectrogram.yaml new file mode 100644 index 00000000..c48e517e --- /dev/null +++ b/examples/tess/cls0/conf/panns_logmelspectrogram.yaml @@ -0,0 +1,32 @@ +data: + dataset: 'paddle.audio.datasets:TESS' + num_classes: 7 + train: + mode: 'train' + split: 1 + feat_type: 'logmelspectrogram' + dev: + mode: 'dev' + split: 1 + feat_type: 'logmelspectrogram' + +model: + backbone: 'paddlespeech.cls.models:cnn14' + +feature: + n_fft: 1024 + hop_length: 320 + window: 'hann' + win_length: 1024 + f_min: 50.0 + f_max: 14000.0 + n_mels: 64 + +training: + epochs: 5 + learning_rate: 0.0005 + num_workers: 2 + batch_size: 128 + checkpoint_dir: './checkpoint_logmelspectrogram' + save_freq: 1 + log_freq: 1 diff --git a/examples/tess/cls0/conf/panns_melspectrogram.yaml b/examples/tess/cls0/conf/panns_melspectrogram.yaml new file mode 100644 index 00000000..66aa4a71 --- /dev/null +++ b/examples/tess/cls0/conf/panns_melspectrogram.yaml @@ -0,0 +1,32 @@ +data: + dataset: 'paddle.audio.datasets:TESS' + num_classes: 7 + train: + mode: 'train' + split: 1 + feat_type: 'melspectrogram' + dev: + mode: 'dev' + split: 1 + feat_type: 'melspectrogram' + +model: + backbone: 'paddlespeech.cls.models:cnn14' + +feature: + n_fft: 1024 + hop_length: 320 + window: 'hann' + win_length: 1024 + f_min: 50.0 + f_max: 14000.0 + n_mels: 64 + +training: + epochs: 10 + learning_rate: 0.0005 + num_workers: 2 + batch_size: 128 + checkpoint_dir: './checkpoint_melspectrogram' + save_freq: 1 + log_freq: 1 diff --git a/examples/tess/cls0/conf/panns_mfcc.yaml b/examples/tess/cls0/conf/panns_mfcc.yaml new file mode 100644 index 00000000..6800e3ab --- /dev/null +++ b/examples/tess/cls0/conf/panns_mfcc.yaml @@ -0,0 +1,33 @@ +data: + dataset: 'paddle.audio.datasets:TESS' + num_classes: 7 + train: + mode: 'train' + split: 1 + feat_type: 'mfcc' + dev: + mode: 'dev' + split: 1 + feat_type: 'mfcc' + +model: + backbone: 'paddlespeech.cls.models:cnn14' + +feature: + n_fft: 1024 + hop_length: 320 + window: 'hann' + win_length: 1024 + f_min: 50.0 + f_max: 14000.0 + n_mfcc: 64 + n_mels: 64 + +training: + epochs: 5 + learning_rate: 0.0005 + num_workers: 2 + batch_size: 128 + checkpoint_dir: './checkpoint_mfcc' + save_freq: 1 + log_freq: 1 diff --git a/examples/tess/cls0/conf/panns_spectrogram.yaml b/examples/tess/cls0/conf/panns_spectrogram.yaml new file mode 100644 index 00000000..8d88f41c --- /dev/null +++ b/examples/tess/cls0/conf/panns_spectrogram.yaml @@ -0,0 +1,28 @@ +data: + dataset: 'paddle.audio.datasets:TESS' + num_classes: 7 + train: + mode: 'train' + split: 1 + feat_type: 'spectrogram' + dev: + mode: 'dev' + split: 1 + feat_type: 'spectrogram' + +model: + backbone: 'paddlespeech.cls.models:cnn14' + +feature: + n_fft: 126 + hop_length: 320 + window: 'hann' + +training: + epochs: 10 + learning_rate: 0.0005 + num_workers: 2 + batch_size: 128 + checkpoint_dir: './checkpoint_spectrogram' + save_freq: 1 + log_freq: 1 diff --git a/examples/tess/cls0/local/train.py b/examples/tess/cls0/local/train.py new file mode 100644 index 00000000..25382d8c --- /dev/null +++ b/examples/tess/cls0/local/train.py @@ -0,0 +1,191 @@ +# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import argparse +import os + +import paddle +import yaml +from paddleaudio.utils import logger +from paddleaudio.utils import Timer + +from paddlespeech.cls.models import SoundClassifier +from paddlespeech.utils.dynamic_import import dynamic_import + +# yapf: disable +parser = argparse.ArgumentParser(__doc__) +parser.add_argument("--cfg_path", type=str, required=True) +args = parser.parse_args() +# yapf: enable + + +def _collate_features(batch): + # (feat, label) + # (( n_mels, length), label) + feats = [] + labels = [] + lengths = [] + for sample in batch: + feats.append(paddle.transpose(sample[0], perm=[1, 0])) + lengths.append(sample[0].shape[1]) + labels.append(sample[1]) + + max_length = max(lengths) + for i in range(len(feats)): + feats[i] = paddle.nn.functional.pad( + feats[i], [0, max_length - feats[i].shape[0], 0, 0], + data_format='NLC') + + return paddle.stack(feats), paddle.to_tensor(labels), paddle.to_tensor( + lengths) + + +if __name__ == "__main__": + nranks = paddle.distributed.get_world_size() + if paddle.distributed.get_world_size() > 1: + paddle.distributed.init_parallel_env() + local_rank = paddle.distributed.get_rank() + + args.cfg_path = os.path.abspath(os.path.expanduser(args.cfg_path)) + with open(args.cfg_path, 'r') as f: + config = yaml.safe_load(f) + + model_conf = config['model'] + data_conf = config['data'] + feat_conf = config['feature'] + feat_type = data_conf['train']['feat_type'] + training_conf = config['training'] + + # Dataset + + # set audio backend, make sure paddleaudio >= 1.0.2 installed. + paddle.audio.backends.set_backend('soundfile') + + ds_class = dynamic_import(data_conf['dataset']) + train_ds = ds_class(**data_conf['train'], **feat_conf) + dev_ds = ds_class(**data_conf['dev'], **feat_conf) + train_sampler = paddle.io.DistributedBatchSampler( + train_ds, + batch_size=training_conf['batch_size'], + shuffle=True, + drop_last=False) + train_loader = paddle.io.DataLoader( + train_ds, + batch_sampler=train_sampler, + num_workers=training_conf['num_workers'], + return_list=True, + use_buffer_reader=True, + collate_fn=_collate_features) + + # Model + backbone_class = dynamic_import(model_conf['backbone']) + backbone = backbone_class(pretrained=True, extract_embedding=True) + model = SoundClassifier(backbone, num_class=data_conf['num_classes']) + model = paddle.DataParallel(model) + optimizer = paddle.optimizer.Adam( + learning_rate=training_conf['learning_rate'], + parameters=model.parameters()) + criterion = paddle.nn.loss.CrossEntropyLoss() + + steps_per_epoch = len(train_sampler) + timer = Timer(steps_per_epoch * training_conf['epochs']) + timer.start() + + for epoch in range(1, training_conf['epochs'] + 1): + model.train() + + avg_loss = 0 + num_corrects = 0 + num_samples = 0 + for batch_idx, batch in enumerate(train_loader): + feats, labels, length = batch # feats-->(N, length, n_mels) + + logits = model(feats) + + loss = criterion(logits, labels) + loss.backward() + optimizer.step() + if isinstance(optimizer._learning_rate, + paddle.optimizer.lr.LRScheduler): + optimizer._learning_rate.step() + optimizer.clear_grad() + + # Calculate loss + avg_loss += loss.numpy()[0] + + # Calculate metrics + preds = paddle.argmax(logits, axis=1) + num_corrects += (preds == labels).numpy().sum() + num_samples += feats.shape[0] + + timer.count() + + if (batch_idx + 1 + ) % training_conf['log_freq'] == 0 and local_rank == 0: + lr = optimizer.get_lr() + avg_loss /= training_conf['log_freq'] + avg_acc = num_corrects / num_samples + + print_msg = feat_type + ' Epoch={}/{}, Step={}/{}'.format( + epoch, training_conf['epochs'], batch_idx + 1, + steps_per_epoch) + print_msg += ' loss={:.4f}'.format(avg_loss) + print_msg += ' acc={:.4f}'.format(avg_acc) + print_msg += ' lr={:.6f} step/sec={:.2f} | ETA {}'.format( + lr, timer.timing, timer.eta) + logger.train(print_msg) + + avg_loss = 0 + num_corrects = 0 + num_samples = 0 + + if epoch % training_conf[ + 'save_freq'] == 0 and batch_idx + 1 == steps_per_epoch and local_rank == 0: + dev_sampler = paddle.io.BatchSampler( + dev_ds, + batch_size=training_conf['batch_size'], + shuffle=False, + drop_last=False) + dev_loader = paddle.io.DataLoader( + dev_ds, + batch_sampler=dev_sampler, + num_workers=training_conf['num_workers'], + return_list=True, + use_buffer_reader=True, + collate_fn=_collate_features) + + model.eval() + num_corrects = 0 + num_samples = 0 + with logger.processing('Evaluation on validation dataset'): + for batch_idx, batch in enumerate(dev_loader): + feats, labels, length = batch + logits = model(feats) + + preds = paddle.argmax(logits, axis=1) + num_corrects += (preds == labels).numpy().sum() + num_samples += feats.shape[0] + + print_msg = '[Evaluation result] ' + str(feat_type) + print_msg += ' dev_acc={:.4f}'.format(num_corrects / num_samples) + + logger.eval(print_msg) + + # Save model + save_dir = os.path.join(training_conf['checkpoint_dir'], + 'epoch_{}'.format(epoch)) + logger.info('Saving model checkpoint to {}'.format(save_dir)) + paddle.save(model.state_dict(), + os.path.join(save_dir, 'model.pdparams')) + paddle.save(optimizer.state_dict(), + os.path.join(save_dir, 'model.pdopt')) diff --git a/examples/tess/cls0/local/train.sh b/examples/tess/cls0/local/train.sh new file mode 100755 index 00000000..953c56bf --- /dev/null +++ b/examples/tess/cls0/local/train.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +ngpu=$1 +cfg_path=$2 + +if [ ${ngpu} -gt 0 ]; then + python3 -m paddle.distributed.launch --gpus $CUDA_VISIBLE_DEVICES local/train.py \ + --cfg_path ${cfg_path} +else + python3 local/train.py \ + --cfg_path ${cfg_path} +fi diff --git a/examples/tess/cls0/path.sh b/examples/tess/cls0/path.sh new file mode 100644 index 00000000..3eff28e4 --- /dev/null +++ b/examples/tess/cls0/path.sh @@ -0,0 +1,13 @@ +#!/bin/bash +export MAIN_ROOT=`realpath ${PWD}/../../../` + +export PATH=${MAIN_ROOT}:${MAIN_ROOT}/utils:${PATH} +export LC_ALL=C + +export PYTHONDONTWRITEBYTECODE=1 +# Use UTF-8 in Python to avoid UnicodeDecodeError when LC_ALL=C +export PYTHONIOENCODING=UTF-8 +export PYTHONPATH=${MAIN_ROOT}:${PYTHONPATH} + +MODEL=panns +export BIN_DIR=${MAIN_ROOT}/paddlespeech/cls/exps/${MODEL} \ No newline at end of file diff --git a/examples/tess/cls0/run.sh b/examples/tess/cls0/run.sh new file mode 100755 index 00000000..0e407b40 --- /dev/null +++ b/examples/tess/cls0/run.sh @@ -0,0 +1,35 @@ +#!/bin/bash +set -e +source path.sh + +ngpu=$(echo $CUDA_VISIBLE_DEVICES | awk -F "," '{print NF}') + +stage=$1 +stop_stage=100 + +if [ ${stage} -le 1 ] && [ ${stop_stage} -ge 1 ]; then + cfg_path=$2 + ./local/train.sh ${ngpu} ${cfg_path} || exit -1 + exit 0 +fi + +if [ ${stage} -le 2 ] && [ ${stop_stage} -ge 2 ]; then + cfg_path=$2 + ./local/infer.sh ${cfg_path} || exit -1 + exit 0 +fi + +if [ ${stage} -le 3 ] && [ ${stop_stage} -ge 3 ]; then + ckpt=$2 + output_dir=$3 + ./local/export.sh ${ckpt} ${output_dir} || exit -1 + exit 0 +fi + +if [ ${stage} -le 4 ] && [ ${stop_stage} -ge 4 ]; then + infer_device=$2 + graph_dir=$3 + audio_file=$4 + ./local/static_model_infer.sh ${infer_device} ${graph_dir} ${audio_file} || exit -1 + exit 0 +fi diff --git a/examples/voxceleb/sv0/local/data_prepare.py b/examples/voxceleb/sv0/local/data_prepare.py index e5a5dff7..b4486b6f 100644 --- a/examples/voxceleb/sv0/local/data_prepare.py +++ b/examples/voxceleb/sv0/local/data_prepare.py @@ -14,9 +14,9 @@ import argparse import paddle +from paddleaudio.datasets.voxceleb import VoxCeleb from yacs.config import CfgNode -from paddlespeech.audio.datasets.voxceleb import VoxCeleb from paddlespeech.s2t.utils.log import Log from paddlespeech.vector.io.augment import build_augment_pipeline from paddlespeech.vector.training.seeding import seed_everything diff --git a/examples/voxceleb/sv0/local/make_rirs_noise_csv_dataset_from_json.py b/examples/voxceleb/sv0/local/make_rirs_noise_csv_dataset_from_json.py index 7ad9bd6e..11908fe6 100644 --- a/examples/voxceleb/sv0/local/make_rirs_noise_csv_dataset_from_json.py +++ b/examples/voxceleb/sv0/local/make_rirs_noise_csv_dataset_from_json.py @@ -21,9 +21,9 @@ import os from typing import List import tqdm +from paddleaudio.backends import soundfile_load as load_audio from yacs.config import CfgNode -from paddlespeech.audio import load as load_audio from paddlespeech.s2t.utils.log import Log from paddlespeech.vector.utils.vector_utils import get_chunks diff --git a/examples/voxceleb/sv0/local/make_vox_csv_dataset_from_json.py b/examples/voxceleb/sv0/local/make_vox_csv_dataset_from_json.py index 40adf53d..ebeb598a 100644 --- a/examples/voxceleb/sv0/local/make_vox_csv_dataset_from_json.py +++ b/examples/voxceleb/sv0/local/make_vox_csv_dataset_from_json.py @@ -22,9 +22,9 @@ import os import random import tqdm +from paddleaudio.backends import soundfile_load as load_audio from yacs.config import CfgNode -from paddlespeech.audio import load as load_audio from paddlespeech.s2t.utils.log import Log from paddlespeech.vector.utils.vector_utils import get_chunks diff --git a/paddlespeech/__init__.py b/paddlespeech/__init__.py index b781c4a8..6c7e75c1 100644 --- a/paddlespeech/__init__.py +++ b/paddlespeech/__init__.py @@ -12,5 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. import _locale - _locale._getdefaultlocale = (lambda *args: ['en_US', 'utf8']) diff --git a/paddlespeech/audio/.gitignore b/paddlespeech/audio/.gitignore new file mode 100644 index 00000000..24343e79 --- /dev/null +++ b/paddlespeech/audio/.gitignore @@ -0,0 +1 @@ +fc_patch/ diff --git a/paddlespeech/audio/__init__.py b/paddlespeech/audio/__init__.py index a9195810..a7cf6caa 100644 --- a/paddlespeech/audio/__init__.py +++ b/paddlespeech/audio/__init__.py @@ -11,15 +11,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from . import compliance -from . import datasets -from . import features -from . import functional -from . import io -from . import metric -from . import sox_effects from . import streamdata from . import text from . import transform -from .backends import load -from .backends import save diff --git a/paddlespeech/audio/backends/soundfile_backend.py b/paddlespeech/audio/backends/soundfile_backend.py deleted file mode 100644 index c1155654..00000000 --- a/paddlespeech/audio/backends/soundfile_backend.py +++ /dev/null @@ -1,325 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import os -import warnings -from typing import Optional -from typing import Tuple -from typing import Union - -import numpy as np -import resampy -import soundfile as sf -from scipy.io import wavfile - -from ..utils import ParameterError - -__all__ = [ - 'resample', - 'to_mono', - 'depth_convert', - 'normalize', - 'save', - 'load', -] -NORMALMIZE_TYPES = ['linear', 'gaussian'] -MERGE_TYPES = ['ch0', 'ch1', 'random', 'average'] -RESAMPLE_MODES = ['kaiser_best', 'kaiser_fast'] -EPS = 1e-8 - - -def resample(y: np.ndarray, - src_sr: int, - target_sr: int, - mode: str='kaiser_fast') -> np.ndarray: - """Audio resampling. - - Args: - y (np.ndarray): Input waveform array in 1D or 2D. - src_sr (int): Source sample rate. - target_sr (int): Target sample rate. - mode (str, optional): The resampling filter to use. Defaults to 'kaiser_fast'. - - Returns: - np.ndarray: `y` resampled to `target_sr` - """ - - if mode == 'kaiser_best': - warnings.warn( - f'Using resampy in kaiser_best to {src_sr}=>{target_sr}. This function is pretty slow, \ - we recommend the mode kaiser_fast in large scale audio trainning') - - if not isinstance(y, np.ndarray): - raise ParameterError( - 'Only support numpy np.ndarray, but received y in {type(y)}') - - if mode not in RESAMPLE_MODES: - raise ParameterError(f'resample mode must in {RESAMPLE_MODES}') - - return resampy.resample(y, src_sr, target_sr, filter=mode) - - -def to_mono(y: np.ndarray, merge_type: str='average') -> np.ndarray: - """Convert sterior audio to mono. - - Args: - y (np.ndarray): Input waveform array in 1D or 2D. - merge_type (str, optional): Merge type to generate mono waveform. Defaults to 'average'. - - Returns: - np.ndarray: `y` with mono channel. - """ - - if merge_type not in MERGE_TYPES: - raise ParameterError( - f'Unsupported merge type {merge_type}, available types are {MERGE_TYPES}' - ) - if y.ndim > 2: - raise ParameterError( - f'Unsupported audio array, y.ndim > 2, the shape is {y.shape}') - if y.ndim == 1: # nothing to merge - return y - - if merge_type == 'ch0': - return y[0] - if merge_type == 'ch1': - return y[1] - if merge_type == 'random': - return y[np.random.randint(0, 2)] - - # need to do averaging according to dtype - - if y.dtype == 'float32': - y_out = (y[0] + y[1]) * 0.5 - elif y.dtype == 'int16': - y_out = y.astype('int32') - y_out = (y_out[0] + y_out[1]) // 2 - y_out = np.clip(y_out, np.iinfo(y.dtype).min, - np.iinfo(y.dtype).max).astype(y.dtype) - - elif y.dtype == 'int8': - y_out = y.astype('int16') - y_out = (y_out[0] + y_out[1]) // 2 - y_out = np.clip(y_out, np.iinfo(y.dtype).min, - np.iinfo(y.dtype).max).astype(y.dtype) - else: - raise ParameterError(f'Unsupported dtype: {y.dtype}') - return y_out - - -def _safe_cast(y: np.ndarray, dtype: Union[type, str]) -> np.ndarray: - """Data type casting in a safe way, i.e., prevent overflow or underflow. - - Args: - y (np.ndarray): Input waveform array in 1D or 2D. - dtype (Union[type, str]): Data type of waveform. - - Returns: - np.ndarray: `y` after safe casting. - """ - if 'float' in str(y.dtype): - return np.clip(y, np.finfo(dtype).min, - np.finfo(dtype).max).astype(dtype) - else: - return np.clip(y, np.iinfo(dtype).min, - np.iinfo(dtype).max).astype(dtype) - - -def depth_convert(y: np.ndarray, dtype: Union[type, str]) -> np.ndarray: - """Convert audio array to target dtype safely. This function convert audio waveform to a target dtype, with addition steps of - preventing overflow/underflow and preserving audio range. - - Args: - y (np.ndarray): Input waveform array in 1D or 2D. - dtype (Union[type, str]): Data type of waveform. - - Returns: - np.ndarray: `y` after safe casting. - """ - - SUPPORT_DTYPE = ['int16', 'int8', 'float32', 'float64'] - if y.dtype not in SUPPORT_DTYPE: - raise ParameterError( - 'Unsupported audio dtype, ' - f'y.dtype is {y.dtype}, supported dtypes are {SUPPORT_DTYPE}') - - if dtype not in SUPPORT_DTYPE: - raise ParameterError( - 'Unsupported audio dtype, ' - f'target dtype is {dtype}, supported dtypes are {SUPPORT_DTYPE}') - - if dtype == y.dtype: - return y - - if dtype == 'float64' and y.dtype == 'float32': - return _safe_cast(y, dtype) - if dtype == 'float32' and y.dtype == 'float64': - return _safe_cast(y, dtype) - - if dtype == 'int16' or dtype == 'int8': - if y.dtype in ['float64', 'float32']: - factor = np.iinfo(dtype).max - y = np.clip(y * factor, np.iinfo(dtype).min, - np.iinfo(dtype).max).astype(dtype) - y = y.astype(dtype) - else: - if dtype == 'int16' and y.dtype == 'int8': - factor = np.iinfo('int16').max / np.iinfo('int8').max - EPS - y = y.astype('float32') * factor - y = y.astype('int16') - - else: # dtype == 'int8' and y.dtype=='int16': - y = y.astype('int32') * np.iinfo('int8').max / \ - np.iinfo('int16').max - y = y.astype('int8') - - if dtype in ['float32', 'float64']: - org_dtype = y.dtype - y = y.astype(dtype) / np.iinfo(org_dtype).max - return y - - -def sound_file_load(file: os.PathLike, - offset: Optional[float]=None, - dtype: str='int16', - duration: Optional[int]=None) -> Tuple[np.ndarray, int]: - """Load audio using soundfile library. This function load audio file using libsndfile. - - Args: - file (os.PathLike): File of waveform. - offset (Optional[float], optional): Offset to the start of waveform. Defaults to None. - dtype (str, optional): Data type of waveform. Defaults to 'int16'. - duration (Optional[int], optional): Duration of waveform to read. Defaults to None. - - Returns: - Tuple[np.ndarray, int]: Waveform in ndarray and its samplerate. - """ - with sf.SoundFile(file) as sf_desc: - sr_native = sf_desc.samplerate - if offset: - sf_desc.seek(int(offset * sr_native)) - if duration is not None: - frame_duration = int(duration * sr_native) - else: - frame_duration = -1 - y = sf_desc.read(frames=frame_duration, dtype=dtype, always_2d=False).T - - return y, sf_desc.samplerate - - -def normalize(y: np.ndarray, norm_type: str='linear', - mul_factor: float=1.0) -> np.ndarray: - """Normalize an input audio with additional multiplier. - - Args: - y (np.ndarray): Input waveform array in 1D or 2D. - norm_type (str, optional): Type of normalization. Defaults to 'linear'. - mul_factor (float, optional): Scaling factor. Defaults to 1.0. - - Returns: - np.ndarray: `y` after normalization. - """ - - if norm_type == 'linear': - amax = np.max(np.abs(y)) - factor = 1.0 / (amax + EPS) - y = y * factor * mul_factor - elif norm_type == 'gaussian': - amean = np.mean(y) - astd = np.std(y) - astd = max(astd, EPS) - y = mul_factor * (y - amean) / astd - else: - raise NotImplementedError(f'norm_type should be in {NORMALMIZE_TYPES}') - - return y - - -def save(y: np.ndarray, sr: int, file: os.PathLike) -> None: - """Save audio file to disk. This function saves audio to disk using scipy.io.wavfile, with additional step to convert input waveform to int16. - - Args: - y (np.ndarray): Input waveform array in 1D or 2D. - sr (int): Sample rate. - file (os.PathLike): Path of auido file to save. - """ - if not file.endswith('.wav'): - raise ParameterError( - f'only .wav file supported, but dst file name is: {file}') - - if sr <= 0: - raise ParameterError( - f'Sample rate should be larger than 0, recieved sr = {sr}') - - if y.dtype not in ['int16', 'int8']: - warnings.warn( - f'input data type is {y.dtype}, will convert data to int16 format before saving' - ) - y_out = depth_convert(y, 'int16') - else: - y_out = y - - wavfile.write(file, sr, y_out) - - -def load( - file: os.PathLike, - sr: Optional[int]=None, - mono: bool=True, - merge_type: str='average', # ch0,ch1,random,average - normal: bool=True, - norm_type: str='linear', - norm_mul_factor: float=1.0, - offset: float=0.0, - duration: Optional[int]=None, - dtype: str='float32', - resample_mode: str='kaiser_fast') -> Tuple[np.ndarray, int]: - """Load audio file from disk. This function loads audio from disk using using audio beackend. - - Args: - file (os.PathLike): Path of auido file to load. - sr (Optional[int], optional): Sample rate of loaded waveform. Defaults to None. - mono (bool, optional): Return waveform with mono channel. Defaults to True. - merge_type (str, optional): Merge type of multi-channels waveform. Defaults to 'average'. - normal (bool, optional): Waveform normalization. Defaults to True. - norm_type (str, optional): Type of normalization. Defaults to 'linear'. - norm_mul_factor (float, optional): Scaling factor. Defaults to 1.0. - offset (float, optional): Offset to the start of waveform. Defaults to 0.0. - duration (Optional[int], optional): Duration of waveform to read. Defaults to None. - dtype (str, optional): Data type of waveform. Defaults to 'float32'. - resample_mode (str, optional): The resampling filter to use. Defaults to 'kaiser_fast'. - - Returns: - Tuple[np.ndarray, int]: Waveform in ndarray and its samplerate. - """ - - y, r = sound_file_load(file, offset=offset, dtype=dtype, duration=duration) - - if not ((y.ndim == 1 and len(y) > 0) or (y.ndim == 2 and len(y[0]) > 0)): - raise ParameterError(f'audio file {file} looks empty') - - if mono: - y = to_mono(y, merge_type) - - if sr is not None and sr != r: - y = resample(y, r, sr, mode=resample_mode) - r = sr - - if normal: - y = normalize(y, norm_type, norm_mul_factor) - elif dtype in ['int8', 'int16']: - # still need to do normalization, before depth convertion - y = normalize(y, 'linear', 1.0) - - y = depth_convert(y, dtype) - return y, r diff --git a/paddlespeech/audio/backends/sox_backend.py b/paddlespeech/audio/backends/sox_backend.py deleted file mode 100644 index 97043fd7..00000000 --- a/paddlespeech/audio/backends/sox_backend.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/paddlespeech/audio/streamdata/autodecode.py b/paddlespeech/audio/streamdata/autodecode.py index d7f7937b..2e82226d 100644 --- a/paddlespeech/audio/streamdata/autodecode.py +++ b/paddlespeech/audio/streamdata/autodecode.py @@ -295,7 +295,7 @@ def torch_video(key, data): def paddle_audio(key, data): - """Decode audio using the paddlespeech.audio library. + """Decode audio using the paddleaudio library. :param key: file name extension :param data: data to be decoded @@ -304,13 +304,13 @@ def paddle_audio(key, data): if extension not in ["flac", "mp3", "sox", "wav", "m4a", "ogg", "wma"]: return None - import paddlespeech.audio + import paddleaudio with tempfile.TemporaryDirectory() as dirname: fname = os.path.join(dirname, f"file.{extension}") with open(fname, "wb") as stream: stream.write(data) - return paddlespeech.audio.load(fname) + return paddleaudio.backends.soundfile_load(fname) ################################################################ diff --git a/paddlespeech/audio/streamdata/filters.py b/paddlespeech/audio/streamdata/filters.py index 68d6830b..110b4a30 100644 --- a/paddlespeech/audio/streamdata/filters.py +++ b/paddlespeech/audio/streamdata/filters.py @@ -22,11 +22,11 @@ from fnmatch import fnmatch from functools import reduce import paddle +from paddleaudio import backends +from paddleaudio.compliance import kaldi from . import autodecode from . import utils -from .. import backends -from ..compliance import kaldi from ..transform.cmvn import GlobalCMVN from ..transform.spec_augment import freq_mask from ..transform.spec_augment import time_mask diff --git a/paddlespeech/audio/streamdata/tariterators.py b/paddlespeech/audio/streamdata/tariterators.py index 79b81c0c..3adf4892 100644 --- a/paddlespeech/audio/streamdata/tariterators.py +++ b/paddlespeech/audio/streamdata/tariterators.py @@ -20,7 +20,7 @@ trace = False meta_prefix = "__" meta_suffix = "__" -import paddlespeech +import paddleaudio import paddle import numpy as np @@ -111,7 +111,7 @@ def tar_file_iterator(fileobj, assert pos > 0 prefix, postfix = name[:pos], name[pos + 1:] if postfix == 'wav': - waveform, sample_rate = paddlespeech.audio.load( + waveform, sample_rate = paddleaudio.backends.soundfile_load( stream.extractfile(tarinfo), normal=False) result = dict( fname=prefix, wav=waveform, sample_rate=sample_rate) @@ -163,7 +163,7 @@ def tar_file_and_group_iterator(fileobj, if postfix == 'txt': example['txt'] = file_obj.read().decode('utf8').strip() elif postfix in AUDIO_FORMAT_SETS: - waveform, sample_rate = paddlespeech.audio.load( + waveform, sample_rate = paddleaudio.backends.soundfile_load( file_obj, normal=False) waveform = paddle.to_tensor( np.expand_dims(np.array(waveform), 0), diff --git a/paddlespeech/audio/transform/spectrogram.py b/paddlespeech/audio/transform/spectrogram.py index 84812a2c..f2dab316 100644 --- a/paddlespeech/audio/transform/spectrogram.py +++ b/paddlespeech/audio/transform/spectrogram.py @@ -15,10 +15,9 @@ import librosa import numpy as np import paddle +from paddleaudio.compliance import kaldi from python_speech_features import logfbank -from ..compliance import kaldi - def stft(x, n_fft, diff --git a/paddlespeech/audio/utils/__init__.py b/paddlespeech/audio/utils/__init__.py index f1e5deb0..18c59ff1 100644 --- a/paddlespeech/audio/utils/__init__.py +++ b/paddlespeech/audio/utils/__init__.py @@ -19,5 +19,7 @@ from .download import load_state_dict_from_url from .error import ParameterError from .log import Logger from .log import logger +from .numeric import depth_convert +from .numeric import pcm16to32 from .time import seconds_to_hms from .time import Timer diff --git a/paddlespeech/audio/utils/numeric.py b/paddlespeech/audio/utils/numeric.py index 126cada5..9fe00484 100644 --- a/paddlespeech/audio/utils/numeric.py +++ b/paddlespeech/audio/utils/numeric.py @@ -11,8 +11,12 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +from typing import Union + import numpy as np +__all__ = ["pcm16to32", "depth_convert"] + def pcm16to32(audio: np.ndarray) -> np.ndarray: """pcm int16 to float32 @@ -28,3 +32,76 @@ def pcm16to32(audio: np.ndarray) -> np.ndarray: bits = np.iinfo(np.int16).bits audio = audio / (2**(bits - 1)) return audio + + +def _safe_cast(y: np.ndarray, dtype: Union[type, str]) -> np.ndarray: + """Data type casting in a safe way, i.e., prevent overflow or underflow. + + Args: + y (np.ndarray): Input waveform array in 1D or 2D. + dtype (Union[type, str]): Data type of waveform. + + Returns: + np.ndarray: `y` after safe casting. + """ + if 'float' in str(y.dtype): + return np.clip(y, np.finfo(dtype).min, + np.finfo(dtype).max).astype(dtype) + else: + return np.clip(y, np.iinfo(dtype).min, + np.iinfo(dtype).max).astype(dtype) + + +def depth_convert(y: np.ndarray, dtype: Union[type, str]) -> np.ndarray: + """Convert audio array to target dtype safely. + This function convert audio waveform to a target dtype, with addition steps of + preventing overflow/underflow and preserving audio range. + + Args: + y (np.ndarray): Input waveform array in 1D or 2D. + dtype (Union[type, str]): Data type of waveform. + + Returns: + np.ndarray: `y` after safe casting. + """ + + SUPPORT_DTYPE = ['int16', 'int8', 'float32', 'float64'] + if y.dtype not in SUPPORT_DTYPE: + raise ParameterError( + 'Unsupported audio dtype, ' + f'y.dtype is {y.dtype}, supported dtypes are {SUPPORT_DTYPE}') + + if dtype not in SUPPORT_DTYPE: + raise ParameterError( + 'Unsupported audio dtype, ' + f'target dtype is {dtype}, supported dtypes are {SUPPORT_DTYPE}') + + if dtype == y.dtype: + return y + + if dtype == 'float64' and y.dtype == 'float32': + return _safe_cast(y, dtype) + if dtype == 'float32' and y.dtype == 'float64': + return _safe_cast(y, dtype) + + if dtype == 'int16' or dtype == 'int8': + if y.dtype in ['float64', 'float32']: + factor = np.iinfo(dtype).max + y = np.clip(y * factor, np.iinfo(dtype).min, + np.iinfo(dtype).max).astype(dtype) + y = y.astype(dtype) + else: + if dtype == 'int16' and y.dtype == 'int8': + factor = np.iinfo('int16').max / np.iinfo('int8').max - EPS + y = y.astype('float32') * factor + y = y.astype('int16') + + else: # dtype == 'int8' and y.dtype=='int16': + y = y.astype('int32') * np.iinfo('int8').max / \ + np.iinfo('int16').max + y = y.astype('int8') + + if dtype in ['float32', 'float64']: + org_dtype = y.dtype + y = y.astype(dtype) / np.iinfo(org_dtype).max + return y diff --git a/paddlespeech/cli/cls/infer.py b/paddlespeech/cli/cls/infer.py index c869e28b..5e2168e3 100644 --- a/paddlespeech/cli/cls/infer.py +++ b/paddlespeech/cli/cls/infer.py @@ -21,12 +21,12 @@ from typing import Union import numpy as np import paddle import yaml +from paddle.audio.features import LogMelSpectrogram +from paddleaudio.backends import soundfile_load as load from ..executor import BaseExecutor from ..log import logger from ..utils import stats_wrapper -from paddlespeech.audio import load -from paddlespeech.audio.features import LogMelSpectrogram __all__ = ['CLSExecutor'] diff --git a/paddlespeech/cli/kws/infer.py b/paddlespeech/cli/kws/infer.py index 111cfd75..ce2f3f46 100644 --- a/paddlespeech/cli/kws/infer.py +++ b/paddlespeech/cli/kws/infer.py @@ -20,12 +20,12 @@ from typing import Union import paddle import yaml +from paddleaudio.backends import soundfile_load as load_audio +from paddleaudio.compliance.kaldi import fbank as kaldi_fbank from ..executor import BaseExecutor from ..log import logger from ..utils import stats_wrapper -from paddlespeech.audio import load -from paddlespeech.audio.compliance.kaldi import fbank as kaldi_fbank __all__ = ['KWSExecutor'] @@ -139,7 +139,7 @@ class KWSExecutor(BaseExecutor): Input content can be a text(tts), a file(asr, cls) or a streaming(not supported yet). """ assert os.path.isfile(audio_file) - waveform, _ = load(audio_file) + waveform, _ = load_audio(audio_file) if isinstance(audio_file, (str, os.PathLike)): logger.debug("Preprocessing audio_file:" + audio_file) diff --git a/paddlespeech/cli/vector/infer.py b/paddlespeech/cli/vector/infer.py index 11198724..57a78165 100644 --- a/paddlespeech/cli/vector/infer.py +++ b/paddlespeech/cli/vector/infer.py @@ -22,13 +22,13 @@ from typing import Union import paddle import soundfile +from paddleaudio.backends import soundfile_load as load_audio +from paddleaudio.compliance.librosa import melspectrogram from yacs.config import CfgNode from ..executor import BaseExecutor from ..log import logger from ..utils import stats_wrapper -from paddlespeech.audio.backends import load as load_audio -from paddlespeech.audio.compliance.librosa import melspectrogram from paddlespeech.vector.io.batch import feature_normalize from paddlespeech.vector.modules.sid_model import SpeakerIdetification diff --git a/paddlespeech/cls/exps/panns/deploy/predict.py b/paddlespeech/cls/exps/panns/deploy/predict.py index fe1c93fa..f14b4421 100644 --- a/paddlespeech/cls/exps/panns/deploy/predict.py +++ b/paddlespeech/cls/exps/panns/deploy/predict.py @@ -16,12 +16,11 @@ import os import numpy as np from paddle import inference +from paddle.audio.datasets import ESC50 +from paddle.audio.features import MelSpectrogram +from paddleaudio.backends import soundfile_load as load_audio from scipy.special import softmax -from paddlespeech.audio.backends import load as load_audio -from paddlespeech.audio.datasets import ESC50 -from paddlespeech.audio.features import melspectrogram - # yapf: disable parser = argparse.ArgumentParser() parser.add_argument("--model_dir", type=str, required=True, default="./export", help="The directory to static model.") @@ -42,7 +41,7 @@ def extract_features(files: str, **kwargs): srs = [] max_length = float('-inf') for file in files: - waveform, sr = load_audio(file, sr=None) + waveform, sr = load_audio(file) max_length = max(max_length, len(waveform)) waveforms.append(waveform) srs.append(sr) @@ -54,7 +53,7 @@ def extract_features(files: str, **kwargs): pad_width = max_length - len(waveforms[i]) waveforms[i] = np.pad(waveforms[i], pad_width=(0, pad_width)) - feat = melspectrogram(waveforms[i], sr, **kwargs).transpose() + feat = MelSpectrogram(waveforms[i], sr, **kwargs).transpose() feats.append(feat) return np.stack(feats, axis=0) diff --git a/paddlespeech/cls/exps/panns/export_model.py b/paddlespeech/cls/exps/panns/export_model.py index e62d58f0..63b22981 100644 --- a/paddlespeech/cls/exps/panns/export_model.py +++ b/paddlespeech/cls/exps/panns/export_model.py @@ -15,8 +15,8 @@ import argparse import os import paddle +from paddleaudio.datasets import ESC50 -from paddlespeech.audio.datasets import ESC50 from paddlespeech.cls.models import cnn14 from paddlespeech.cls.models import SoundClassifier diff --git a/paddlespeech/cls/exps/panns/predict.py b/paddlespeech/cls/exps/panns/predict.py index 97759a89..4681e4dc 100644 --- a/paddlespeech/cls/exps/panns/predict.py +++ b/paddlespeech/cls/exps/panns/predict.py @@ -17,12 +17,13 @@ import os import paddle import paddle.nn.functional as F import yaml +from paddle.audio.features import LogMelSpectrogram +from paddleaudio.backends import soundfile_load as load_audio +from paddleaudio.utils import logger -from paddlespeech.audio.backends import load as load_audio -from paddlespeech.audio.features import LogMelSpectrogram -from paddlespeech.audio.utils import logger from paddlespeech.cls.models import SoundClassifier from paddlespeech.utils.dynamic_import import dynamic_import +#from paddleaudio.features import LogMelSpectrogram # yapf: disable parser = argparse.ArgumentParser(__doc__) diff --git a/paddlespeech/cls/exps/panns/train.py b/paddlespeech/cls/exps/panns/train.py index 13389308..b768919b 100644 --- a/paddlespeech/cls/exps/panns/train.py +++ b/paddlespeech/cls/exps/panns/train.py @@ -16,10 +16,10 @@ import os import paddle import yaml +from paddle.audio.features import LogMelSpectrogram +from paddleaudio.utils import logger +from paddleaudio.utils import Timer -from paddlespeech.audio.features import LogMelSpectrogram -from paddlespeech.audio.utils import logger -from paddlespeech.audio.utils import Timer from paddlespeech.cls.models import SoundClassifier from paddlespeech.utils.dynamic_import import dynamic_import diff --git a/paddlespeech/cls/models/panns/panns.py b/paddlespeech/cls/models/panns/panns.py index 37deae80..6f9af9b5 100644 --- a/paddlespeech/cls/models/panns/panns.py +++ b/paddlespeech/cls/models/panns/panns.py @@ -15,8 +15,8 @@ import os import paddle.nn as nn import paddle.nn.functional as F +from paddleaudio.utils.download import load_state_dict_from_url -from paddlespeech.audio.utils.download import load_state_dict_from_url from paddlespeech.utils.env import MODEL_HOME __all__ = ['CNN14', 'CNN10', 'CNN6', 'cnn14', 'cnn10', 'cnn6'] diff --git a/paddlespeech/kws/exps/mdtc/train.py b/paddlespeech/kws/exps/mdtc/train.py index d5bb5e02..bb727d36 100644 --- a/paddlespeech/kws/exps/mdtc/train.py +++ b/paddlespeech/kws/exps/mdtc/train.py @@ -14,10 +14,10 @@ import os import paddle +from paddleaudio.utils import logger +from paddleaudio.utils import Timer from yacs.config import CfgNode -from paddlespeech.audio.utils import logger -from paddlespeech.audio.utils import Timer from paddlespeech.kws.exps.mdtc.collate import collate_features from paddlespeech.kws.models.loss import max_pooling_loss from paddlespeech.kws.models.mdtc import KWSModel diff --git a/paddlespeech/s2t/frontend/featurizer/audio_featurizer.py b/paddlespeech/s2t/frontend/featurizer/audio_featurizer.py index ac5720fd..22329d5e 100644 --- a/paddlespeech/s2t/frontend/featurizer/audio_featurizer.py +++ b/paddlespeech/s2t/frontend/featurizer/audio_featurizer.py @@ -14,11 +14,10 @@ """Contains the audio featurizer class.""" import numpy as np import paddle +import paddleaudio.compliance.kaldi as kaldi from python_speech_features import delta from python_speech_features import mfcc -import paddlespeech.audio.compliance.kaldi as kaldi - class AudioFeaturizer(): """Audio featurizer, for extracting features from audio contents of diff --git a/paddlespeech/s2t/models/u2_st/u2_st.py b/paddlespeech/s2t/models/u2_st/u2_st.py index 31defbba..016087d6 100644 --- a/paddlespeech/s2t/models/u2_st/u2_st.py +++ b/paddlespeech/s2t/models/u2_st/u2_st.py @@ -24,9 +24,9 @@ from typing import Tuple import paddle from paddle import jit from paddle import nn +from paddleaudio.utils.tensor_utils import add_sos_eos +from paddleaudio.utils.tensor_utils import th_accuracy -from paddlespeech.audio.utils.tensor_utils import add_sos_eos -from paddlespeech.audio.utils.tensor_utils import th_accuracy from paddlespeech.s2t.frontend.utility import IGNORE_ID from paddlespeech.s2t.frontend.utility import load_cmvn from paddlespeech.s2t.modules.cmvn import GlobalCMVN diff --git a/paddlespeech/s2t/modules/fbank.py b/paddlespeech/s2t/modules/fbank.py index 8d76a472..30671c27 100644 --- a/paddlespeech/s2t/modules/fbank.py +++ b/paddlespeech/s2t/modules/fbank.py @@ -1,7 +1,7 @@ import paddle from paddle import nn +from paddleaudio.compliance import kaldi -from paddlespeech.audio.compliance import kaldi from paddlespeech.s2t.utils.log import Log logger = Log(__name__).getlog() diff --git a/paddlespeech/server/engine/vector/python/vector_engine.py b/paddlespeech/server/engine/vector/python/vector_engine.py index 7b8f667d..7d86f3df 100644 --- a/paddlespeech/server/engine/vector/python/vector_engine.py +++ b/paddlespeech/server/engine/vector/python/vector_engine.py @@ -16,9 +16,9 @@ from collections import OrderedDict import numpy as np import paddle +from paddleaudio.backends import soundfile_load as load_audio +from paddleaudio.compliance.librosa import melspectrogram -from paddlespeech.audio.backends import load as load_audio -from paddlespeech.audio.compliance.librosa import melspectrogram from paddlespeech.cli.log import logger from paddlespeech.cli.vector.infer import VectorExecutor from paddlespeech.server.engine.base_engine import BaseEngine diff --git a/paddlespeech/server/util.py b/paddlespeech/server/util.py index 32546a33..6aa6fd58 100644 --- a/paddlespeech/server/util.py +++ b/paddlespeech/server/util.py @@ -24,11 +24,11 @@ from typing import Any from typing import Dict import paddle +import paddleaudio import requests import yaml from paddle.framework import load -import paddlespeech.audio from .entry import client_commands from .entry import server_commands from paddlespeech.cli import download @@ -289,7 +289,7 @@ def _note_one_stat(cls_name, params={}): if 'audio_file' in params: try: - _, sr = paddlespeech.audio.load(params['audio_file']) + _, sr = paddleaudio.backends.soundfile_load(params['audio_file']) except Exception: sr = -1 diff --git a/paddlespeech/vector/exps/ecapa_tdnn/extract_emb.py b/paddlespeech/vector/exps/ecapa_tdnn/extract_emb.py index cd4538bb..821b1dee 100644 --- a/paddlespeech/vector/exps/ecapa_tdnn/extract_emb.py +++ b/paddlespeech/vector/exps/ecapa_tdnn/extract_emb.py @@ -16,10 +16,10 @@ import os import time import paddle +from paddleaudio.backends import soundfile_load as load_audio +from paddleaudio.compliance.librosa import melspectrogram from yacs.config import CfgNode -from paddlespeech.audio.backends import load as load_audio -from paddlespeech.audio.compliance.librosa import melspectrogram from paddlespeech.s2t.utils.log import Log from paddlespeech.vector.io.batch import feature_normalize from paddlespeech.vector.models.ecapa_tdnn import EcapaTdnn diff --git a/paddlespeech/vector/exps/ecapa_tdnn/test.py b/paddlespeech/vector/exps/ecapa_tdnn/test.py index 6c87dbe7..f15dbf9b 100644 --- a/paddlespeech/vector/exps/ecapa_tdnn/test.py +++ b/paddlespeech/vector/exps/ecapa_tdnn/test.py @@ -18,10 +18,10 @@ import numpy as np import paddle from paddle.io import BatchSampler from paddle.io import DataLoader +from paddleaudio.metric import compute_eer from tqdm import tqdm from yacs.config import CfgNode -from paddlespeech.audio.metric import compute_eer from paddlespeech.s2t.utils.log import Log from paddlespeech.vector.io.batch import batch_feature_normalize from paddlespeech.vector.io.dataset import CSVDataset diff --git a/paddlespeech/vector/exps/ecapa_tdnn/train.py b/paddlespeech/vector/exps/ecapa_tdnn/train.py index 961b75e2..bf014045 100644 --- a/paddlespeech/vector/exps/ecapa_tdnn/train.py +++ b/paddlespeech/vector/exps/ecapa_tdnn/train.py @@ -20,9 +20,9 @@ import paddle from paddle.io import BatchSampler from paddle.io import DataLoader from paddle.io import DistributedBatchSampler +from paddleaudio.compliance.librosa import melspectrogram from yacs.config import CfgNode -from paddlespeech.audio.compliance.librosa import melspectrogram from paddlespeech.s2t.utils.log import Log from paddlespeech.vector.io.augment import build_augment_pipeline from paddlespeech.vector.io.augment import waveform_augment diff --git a/paddlespeech/vector/exps/ge2e/speaker_verification_dataset.py b/paddlespeech/vector/exps/ge2e/speaker_verification_dataset.py index ae6f6ad9..87b7d59a 100644 --- a/paddlespeech/vector/exps/ge2e/speaker_verification_dataset.py +++ b/paddlespeech/vector/exps/ge2e/speaker_verification_dataset.py @@ -22,7 +22,7 @@ from paddlespeech.vector.exps.ge2e.random_cycle import random_cycle class MultiSpeakerMelDataset(Dataset): - """A 2 layer directory thatn contains mel spectrograms in *.npy format. + """A 2 layer directory that contains mel spectrograms in *.npy format. An Example file structure tree is shown below. We prefer to preprocess raw datasets and organized them like this. diff --git a/paddlespeech/vector/io/dataset.py b/paddlespeech/vector/io/dataset.py index 245b2959..dff8ad9f 100644 --- a/paddlespeech/vector/io/dataset.py +++ b/paddlespeech/vector/io/dataset.py @@ -15,9 +15,9 @@ from dataclasses import dataclass from dataclasses import fields from paddle.io import Dataset +from paddleaudio.backends import soundfile_load as load_audio +from paddleaudio.compliance.librosa import melspectrogram -from paddlespeech.audio import load as load_audio -from paddlespeech.audio.compliance.librosa import melspectrogram from paddlespeech.s2t.utils.log import Log logger = Log(__name__).getlog() diff --git a/paddlespeech/vector/io/dataset_from_json.py b/paddlespeech/vector/io/dataset_from_json.py index 12e84577..852f39a9 100644 --- a/paddlespeech/vector/io/dataset_from_json.py +++ b/paddlespeech/vector/io/dataset_from_json.py @@ -16,10 +16,9 @@ from dataclasses import dataclass from dataclasses import fields from paddle.io import Dataset - -from paddlespeech.audio import load as load_audio -from paddlespeech.audio.compliance.librosa import melspectrogram -from paddlespeech.audio.compliance.librosa import mfcc +from paddleaudio.backends import soundfile_load as load_audio +from paddleaudio.compliance.librosa import melspectrogram +from paddleaudio.compliance.librosa import mfcc @dataclass diff --git a/setup.py b/setup.py index 5ed216f3..c55bc15b 100644 --- a/setup.py +++ b/setup.py @@ -76,6 +76,7 @@ base = [ "pyyaml", "pybind11", "paddleslim==2.3.4", + "paddleaudio>=1.0.2", ] server = ["fastapi", "uvicorn", "pattern_singleton", "websockets"] diff --git a/speechx/speechx/kaldi/base/kaldi-types.h b/speechx/speechx/kaldi/base/kaldi-types.h index 4fa8f224..c6a3e1ae 100644 --- a/speechx/speechx/kaldi/base/kaldi-types.h +++ b/speechx/speechx/kaldi/base/kaldi-types.h @@ -42,6 +42,8 @@ typedef float BaseFloat; // for discussion on what to do if you need compile kaldi // without OpenFST, see the bottom of this this file +#ifndef COMPILE_WITHOUT_OPENFST + #include namespace kaldi { @@ -55,9 +57,10 @@ namespace kaldi { typedef double double64; } // end namespace kaldi +#else // In a theoretical case you decide compile Kaldi without the OpenFST // comment the previous namespace statement and uncomment the following -/* + namespace kaldi { typedef int8_t int8; typedef int16_t int16; @@ -71,6 +74,6 @@ namespace kaldi { typedef float float32; typedef double double64; } // end namespace kaldi -*/ +#endif #endif // KALDI_BASE_KALDI_TYPES_H_ diff --git a/speechx/speechx/kaldi/feat/feature-plp.h b/speechx/speechx/kaldi/feat/feature-plp.h index 4f156ca1..cce6ee1c 100644 --- a/speechx/speechx/kaldi/feat/feature-plp.h +++ b/speechx/speechx/kaldi/feat/feature-plp.h @@ -27,7 +27,7 @@ #include "feat/feature-functions.h" #include "feat/feature-window.h" #include "feat/mel-computations.h" -#include "itf/options-itf.h" +#include "util/options-itf.h" namespace kaldi { /// @addtogroup feat FeatureExtraction diff --git a/speechx/speechx/kaldi/feat/online-feature-itf.h b/speechx/speechx/kaldi/feat/online-feature-itf.h new file mode 100644 index 00000000..a0211c09 --- /dev/null +++ b/speechx/speechx/kaldi/feat/online-feature-itf.h @@ -0,0 +1,125 @@ +// feat/online-feature-itf.h + +// Copyright 2013 Johns Hopkins University (author: Daniel Povey) + +// See ../../COPYING for clarification regarding multiple authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED +// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +// MERCHANTABLITY OR NON-INFRINGEMENT. +// See the Apache 2 License for the specific language governing permissions and +// limitations under the License. + +#ifndef KALDI_FEAT_ONLINE_FEATURE_ITF_H_ +#define KALDI_FEAT_ONLINE_FEATURE_ITF_H_ 1 +#include "base/kaldi-common.h" +#include "matrix/matrix-lib.h" + +namespace kaldi { +/// @ingroup Interfaces +/// @{ + +/** + OnlineFeatureInterface is an interface for online feature processing (it is + also usable in the offline setting, but currently we're not using it for + that). This is for use in the online2/ directory, and it supersedes the + interface in ../online/online-feat-input.h. We have a slightly different + model that puts more control in the hands of the calling thread, and won't + involve waiting on semaphores in the decoding thread. + + This interface only specifies how the object *outputs* the features. + How it obtains the features, e.g. from a previous object or objects of type + OnlineFeatureInterface, is not specified in the interface and you will + likely define new constructors or methods in the derived type to do that. + + You should appreciate that this interface is designed to allow random + access to features, as long as they are ready. That is, the user + can call GetFrame for any frame less than NumFramesReady(), and when + implementing a child class you must not make assumptions about the + order in which the user makes these calls. +*/ + +class OnlineFeatureInterface { + public: + virtual int32 Dim() const = 0; /// returns the feature dimension. + + /// Returns the total number of frames, since the start of the utterance, that + /// are now available. In an online-decoding context, this will likely + /// increase with time as more data becomes available. + virtual int32 NumFramesReady() const = 0; + + /// Returns true if this is the last frame. Frame indices are zero-based, so the + /// first frame is zero. IsLastFrame(-1) will return false, unless the file + /// is empty (which is a case that I'm not sure all the code will handle, so + /// be careful). This function may return false for some frame if + /// we haven't yet decided to terminate decoding, but later true if we decide + /// to terminate decoding. This function exists mainly to correctly handle + /// end effects in feature extraction, and is not a mechanism to determine how + /// many frames are in the decodable object (as it used to be, and for backward + /// compatibility, still is, in the Decodable interface). + virtual bool IsLastFrame(int32 frame) const = 0; + + /// Gets the feature vector for this frame. Before calling this for a given + /// frame, it is assumed that you called NumFramesReady() and it returned a + /// number greater than "frame". Otherwise this call will likely crash with + /// an assert failure. This function is not declared const, in case there is + /// some kind of caching going on, but most of the time it shouldn't modify + /// the class. + virtual void GetFrame(int32 frame, VectorBase *feat) = 0; + + + /// This is like GetFrame() but for a collection of frames. There is a + /// default implementation that just gets the frames one by one, but it + /// may be overridden for efficiency by child classes (since sometimes + /// it's more efficient to do things in a batch). + virtual void GetFrames(const std::vector &frames, + MatrixBase *feats) { + KALDI_ASSERT(static_cast(frames.size()) == feats->NumRows()); + for (size_t i = 0; i < frames.size(); i++) { + SubVector feat(*feats, i); + GetFrame(frames[i], &feat); + } + } + + + // Returns frame shift in seconds. Helps to estimate duration from frame + // counts. + virtual BaseFloat FrameShiftInSeconds() const = 0; + + /// Virtual destructor. Note: constructors that take another member of + /// type OnlineFeatureInterface are not expected to take ownership of + /// that pointer; the caller needs to keep track of that manually. + virtual ~OnlineFeatureInterface() { } + +}; + + +/// Add a virtual class for "source" features such as MFCC or PLP or pitch +/// features. +class OnlineBaseFeature: public OnlineFeatureInterface { + public: + /// This would be called from the application, when you get more wave data. + /// Note: the sampling_rate is typically only provided so the code can assert + /// that it matches the sampling rate expected in the options. + virtual void AcceptWaveform(BaseFloat sampling_rate, + const VectorBase &waveform) = 0; + + /// InputFinished() tells the class you won't be providing any + /// more waveform. This will help flush out the last few frames + /// of delta or LDA features (it will typically affect the return value + /// of IsLastFrame. + virtual void InputFinished() = 0; +}; + + +/// @} +} // namespace Kaldi + +#endif // KALDI_ITF_ONLINE_FEATURE_ITF_H_ diff --git a/speechx/speechx/kaldi/feat/online-feature.h b/speechx/speechx/kaldi/feat/online-feature.h index f2ebe45b..f9b26ecc 100644 --- a/speechx/speechx/kaldi/feat/online-feature.h +++ b/speechx/speechx/kaldi/feat/online-feature.h @@ -34,7 +34,7 @@ #include "feat/feature-mfcc.h" #include "feat/feature-plp.h" #include "feat/feature-fbank.h" -#include "itf/online-feature-itf.h" +#include "feat/online-feature-itf.h" namespace kaldi { /// @addtogroup onlinefeat OnlineFeatureExtraction diff --git a/speechx/speechx/kaldi/feat/pitch-functions.h b/speechx/speechx/kaldi/feat/pitch-functions.h index 70e85380..9edf6c9f 100644 --- a/speechx/speechx/kaldi/feat/pitch-functions.h +++ b/speechx/speechx/kaldi/feat/pitch-functions.h @@ -31,7 +31,7 @@ #include "base/kaldi-error.h" #include "feat/mel-computations.h" -#include "itf/online-feature-itf.h" +#include "feat/online-feature-itf.h" #include "matrix/matrix-lib.h" #include "util/common-utils.h" diff --git a/speechx/speechx/kaldi/matrix/kaldi-blas.h b/speechx/speechx/kaldi/matrix/kaldi-blas.h index 143781c8..e8a703c0 100644 --- a/speechx/speechx/kaldi/matrix/kaldi-blas.h +++ b/speechx/speechx/kaldi/matrix/kaldi-blas.h @@ -96,6 +96,12 @@ #elif defined(HAVE_OPENBLAS) // getting cblas.h and lapacke.h from /. // putting in "" not <> to search -I before system libraries. + #if defined(_MSC_VER) + #include + #define LAPACK_COMPLEX_CUSTOM + #define lapack_complex_float _Fcomplex + #define lapack_complex_double _Dcomplex + #endif #include "cblas.h" #include "lapacke.h" #undef I diff --git a/tests/unit/audio/features/__init__.py b/tests/unit/audio/features/__init__.py deleted file mode 100644 index 97043fd7..00000000 --- a/tests/unit/audio/features/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/tools/extras/install_openblas.sh b/tools/extras/install_openblas.sh index b1e4d3da..91b6444b 100755 --- a/tools/extras/install_openblas.sh +++ b/tools/extras/install_openblas.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -OPENBLAS_VERSION=0.3.13 +OPENBLAS_VERSION=0.3.10 WGET=${WGET:-wget}