From 9dcc7a15b1b1d1cf9cb7fd5fa6154e6a56a01045 Mon Sep 17 00:00:00 2001 From: wangaq Date: Mon, 25 Apr 2022 16:48:38 +0800 Subject: [PATCH] init v0.10.0 --- .circleci/build_docs/build_docs.sh | 13 + .circleci/build_docs/commit_docs.sh | 35 + .circleci/build_docs/install_wheels.sh | 12 + .circleci/config.yml | 3589 +++++++++++++++++ .circleci/config.yml.in | 694 ++++ .circleci/regenerate.py | 247 ++ .circleci/smoke_test/docker/Dockerfile | 36 + .circleci/unittest/linux/README.md | 6 + .circleci/unittest/linux/docker/.dockerignore | 2 + .circleci/unittest/linux/docker/.gitignore | 2 + .circleci/unittest/linux/docker/Dockerfile | 56 + .../unittest/linux/docker/build_and_push.sh | 26 + .../docker/scripts/copy_kaldi_executables.sh | 58 + .circleci/unittest/linux/scripts/install.sh | 72 + .../linux/scripts/run_clang_format.py | 340 ++ .../linux/scripts/run_style_checks.sh | 49 + .circleci/unittest/linux/scripts/run_test.sh | 27 + .circleci/unittest/linux/scripts/setup_env.sh | 43 + .circleci/unittest/windows/README.md | 4 + .../unittest/windows/scripts/environment.yml | 16 + .circleci/unittest/windows/scripts/install.sh | 66 + .../windows/scripts/install_conda.bat | 1 + .../unittest/windows/scripts/run_test.sh | 14 + .../unittest/windows/scripts/set_cuda_envs.sh | 44 + .../unittest/windows/scripts/setup_env.sh | 42 + .clang-format | 88 + .clang-tidy | 36 + .flake8 | 4 + .gitattributes | 2 + .github/ISSUE_TEMPLATE/bug-report.md | 53 + .github/ISSUE_TEMPLATE/documentation.md | 9 + .github/ISSUE_TEMPLATE/feature-request.md | 24 + .../ISSUE_TEMPLATE/questions-help-support.md | 13 + .github/pytorch-probot.yml | 1 + .github/workflows/bandit.yml | 23 + .github/workflows/codeql.yml | 37 + .gitignore | 127 + .gitmodules | 4 + CMakeLists.txt | 129 + CODE_OF_CONDUCT.md | 76 + CONTRIBUTING.md | 174 + LICENSE | 25 + README.md | 152 +- build_tools/__init__.py | 0 build_tools/convert_fairseq_models.py | 84 + build_tools/setup_helpers/__init__.py | 1 + build_tools/setup_helpers/extension.py | 139 + build_tools/travis/install.sh | 81 + build_tools/travis/test_script.sh | 49 + cmake/LoadHIP.cmake | 234 ++ docs/Makefile | 27 + docs/make.bat | 36 + docs/requirements.txt | 5 + docs/source/_static/img/pytorch-logo-dark.png | Bin 0 -> 15625 bytes docs/source/_static/img/pytorch-logo-dark.svg | 33 + .../source/_static/img/pytorch-logo-flame.png | Bin 0 -> 22916 bytes docs/source/_templates/layout.html | 8 + docs/source/backend.rst | 92 + docs/source/compliance.kaldi.rst | 31 + docs/source/conf.py | 257 ++ docs/source/datasets.rst | 121 + docs/source/functional.rst | 281 ++ docs/source/index.rst | 55 + docs/source/kaldi_io.rst | 43 + docs/source/models.rst | 128 + docs/source/pipelines.rst | 238 ++ docs/source/refs.bib | 216 + docs/source/sox_effects.rst | 33 + docs/source/torchaudio.rst | 32 + docs/source/transforms.rst | 211 + docs/source/utils.rst | 11 + examples/beamforming/MVDR_tutorial.ipynb | 578 +++ examples/interactive_asr/README.md | 63 + examples/interactive_asr/__init__.py | 3 + examples/interactive_asr/asr.py | 47 + examples/interactive_asr/data/sample.wav | Bin 0 -> 676174 bytes examples/interactive_asr/utils.py | 189 + examples/interactive_asr/vad.py | 269 ++ examples/libtorchaudio/.gitignore | 4 + examples/libtorchaudio/CMakeLists.txt | 17 + examples/libtorchaudio/README.md | 30 + .../libtorchaudio/augmentation/CMakeLists.txt | 3 + examples/libtorchaudio/augmentation/README.md | 36 + .../augmentation/create_jittable_pipeline.py | 83 + examples/libtorchaudio/augmentation/main.cpp | 21 + examples/libtorchaudio/build.sh | 17 + examples/libtorchaudio/data/README.md | 5 + examples/libtorchaudio/data/input.wav | Bin 0 -> 108844 bytes examples/libtorchaudio/data/rir.wav | Bin 0 -> 9338 bytes .../speech_recognition/CMakeLists.txt | 6 + .../speech_recognition/README.md | 187 + .../build_pipeline_from_fairseq.py | 182 + ..._pipeline_from_huggingface_transformers.py | 120 + .../speech_recognition/greedy_decoder.py | 28 + .../speech_recognition/parse_librispeech.py | 55 + .../speech_recognition/parse_voxforge.py | 67 + .../speech_recognition/transcribe.cpp | 38 + .../speech_recognition/transcribe_list.cpp | 66 + examples/pipeline_tacotron2/README.md | 258 ++ examples/pipeline_tacotron2/datasets.py | 171 + examples/pipeline_tacotron2/inference.py | 353 ++ examples/pipeline_tacotron2/loss.py | 82 + examples/pipeline_tacotron2/text/__init__.py | 0 examples/pipeline_tacotron2/text/numbers.py | 116 + .../text/text_preprocessing.py | 164 + examples/pipeline_tacotron2/train.py | 528 +++ examples/pipeline_tacotron2/utils.py | 76 + examples/pipeline_wav2letter/README.md | 50 + examples/pipeline_wav2letter/ctc_decoders.py | 15 + examples/pipeline_wav2letter/datasets.py | 113 + .../pipeline_wav2letter/languagemodels.py | 38 + examples/pipeline_wav2letter/main.py | 663 +++ examples/pipeline_wav2letter/transforms.py | 11 + examples/pipeline_wav2letter/utils.py | 55 + examples/pipeline_wavernn/README.md | 47 + examples/pipeline_wavernn/datasets.py | 121 + examples/pipeline_wavernn/inference.py | 88 + examples/pipeline_wavernn/losses.py | 119 + examples/pipeline_wavernn/main.py | 399 ++ examples/pipeline_wavernn/processing.py | 36 + examples/pipeline_wavernn/utils.py | 61 + .../wavernn_inference_wrapper.py | 181 + examples/source_separation/README.md | 76 + .../source_separation/conv_tasnet/README.md | 44 + .../source_separation/conv_tasnet/__init__.py | 6 + .../source_separation/conv_tasnet/train.py | 332 ++ .../source_separation/conv_tasnet/trainer.py | 165 + examples/source_separation/eval.py | 106 + examples/source_separation/lightning_train.py | 465 +++ examples/source_separation/train.py | 176 + examples/source_separation/utils/__init__.py | 7 + .../utils/dataset/__init__.py | 3 + .../source_separation/utils/dataset/utils.py | 89 + .../utils/dataset/wsj0mix.py | 70 + .../source_separation/utils/dist_utils.py | 86 + examples/source_separation/utils/metrics.py | 204 + examples/test/__init__.py | 0 examples/test/test_interactive_asr.py | 105 + mypy.ini | 3 + packaging/README.md | 95 + packaging/build_conda.sh | 14 + packaging/build_wheel.sh | 18 + packaging/pkg_helpers.bash | 302 ++ packaging/torchaudio/bld.bat | 5 + packaging/torchaudio/build.sh | 4 + packaging/torchaudio/meta.yaml | 61 + packaging/vc_env_helper.bat | 39 + packaging/vs2019/activate.bat | 44 + packaging/vs2019/conda_build_config.yaml | 24 + packaging/vs2019/install_activate.bat | 30 + packaging/vs2019/install_runtime.bat | 49 + packaging/vs2019/meta.yaml | 24 + packaging/windows/internal/cuda_install.bat | 235 ++ packaging/windows/internal/driver_update.bat | 25 + requirements.txt | 20 + setup.cfg | 2 + setup.py | 119 + test/integration_tests/__init__.py | 0 test/integration_tests/conftest.py | 37 + .../tacotron2_pipeline_test.py | 28 + .../wav2vec2_pipeline_test.py | 70 + test/torchaudio_unittest/README.md | 148 + test/torchaudio_unittest/__init__.py | 4 + ...SRI-VOiCES-src-sp0307-ch127535-sg0042.flac | Bin 0 -> 67549 bytes .../assets/VCTK-Corpus/txt/p224/p224_002.txt | 1 + .../VCTK-Corpus/wav48/p224/p224_002.wav | Bin 0 -> 84 bytes .../assets/io/96k_0_1ch.opus | Bin 0 -> 8259 bytes .../assets/io/96k_0_2ch.opus | Bin 0 -> 6740 bytes .../assets/io/96k_10_1ch.opus | Bin 0 -> 15033 bytes .../assets/io/96k_10_2ch.opus | Bin 0 -> 15160 bytes .../assets/io/96k_5_1ch.opus | Bin 0 -> 13074 bytes .../assets/io/96k_5_2ch.opus | Bin 0 -> 10618 bytes .../assets/io/generate_opus.py | 50 + .../torchaudio_unittest/assets/kaldi_file.wav | Bin 0 -> 84 bytes .../assets/kaldi_file_8000.wav | Bin 0 -> 16044 bytes .../assets/kaldi_test_fbank_args.jsonl | 88 + .../assets/kaldi_test_mfcc_args.jsonl | 114 + .../assets/kaldi_test_pitch_args.jsonl | 5 + .../assets/kaldi_test_spectrogram_args.jsonl | 109 + test/torchaudio_unittest/assets/mat.ark | Bin 0 -> 112 bytes .../assets/mp3_without_ext | Bin 0 -> 15453 bytes test/torchaudio_unittest/assets/sinewave.wav | Bin 0 -> 256080 bytes .../assets/sox_effect_test_args.jsonl | 88 + .../assets/sox_effect_test_fir_coeffs.txt | 1 + .../steam-train-whistle-daniel_simon.mp3 | Bin 0 -> 166720 bytes .../steam-train-whistle-daniel_simon.wav | Bin 0 -> 1107596 bytes .../assets/vad-go-mono-32000.wav | Bin 0 -> 54660 bytes .../assets/vad-go-stereo-44100.wav | Bin 0 -> 173400 bytes test/torchaudio_unittest/assets/vec_flt.ark | Bin 0 -> 81 bytes test/torchaudio_unittest/assets/vec_int.ark | Bin 0 -> 81 bytes .../fairseq/generate_hubert_model_config.py | 92 + .../fairseq/generate_wav2vec2_model_config.py | 106 + .../wav2vec2/fairseq/hubert_base_ls960.json | 69 + .../wav2vec2/fairseq/hubert_large_ll60k.json | 68 + .../hubert_large_ll60k_finetune_ls960.json | 89 + .../fairseq/hubert_xtralarge_ll60k.json | 68 + ...hubert_xtralarge_ll60k_finetune_ls960.json | 89 + .../assets/wav2vec2/fairseq/libri960_big.json | 54 + .../wav2vec2/fairseq/wav2vec_large_960h.json | 146 + .../fairseq/wav2vec_large_lv60k_960h.json | 146 + .../wav2vec_large_lv60k_self_960h.json | 146 + .../wav2vec2/fairseq/wav2vec_small.json | 54 + .../wav2vec2/fairseq/wav2vec_small_960h.json | 146 + .../wav2vec2/fairseq/wav2vec_vox_new.json | 54 + .../assets/wav2vec2/fairseq/xlsr_53_56k.json | 51 + .../facebook/wav2vec2-base-10k-voxpopuli.json | 68 + .../facebook/wav2vec2-base-960h.json | 68 + .../huggingface/facebook/wav2vec2-base.json | 77 + .../wav2vec2-large-960h-lv60-self.json | 68 + .../facebook/wav2vec2-large-960h-lv60.json | 68 + .../facebook/wav2vec2-large-960h.json | 68 + .../facebook/wav2vec2-large-lv60.json | 68 + .../wav2vec2-large-xlsr-53-german.json | 68 + .../facebook/wav2vec2-large-xlsr-53.json | 75 + .../huggingface/facebook/wav2vec2-large.json | 68 + .../generate_huggingface_model_config.py | 37 + test/torchaudio_unittest/backend/__init__.py | 0 test/torchaudio_unittest/backend/common.py | 25 + .../backend/soundfile/__init__.py | 0 .../backend/soundfile/common.py | 57 + .../backend/soundfile/info_test.py | 190 + .../backend/soundfile/load_test.py | 357 ++ .../backend/soundfile/save_test.py | 295 ++ .../backend/sox_io/__init__.py | 0 .../backend/sox_io/common.py | 14 + .../backend/sox_io/info_test.py | 537 +++ .../backend/sox_io/load_test.py | 535 +++ .../backend/sox_io/roundtrip_test.py | 54 + .../backend/sox_io/save_test.py | 402 ++ .../backend/sox_io/smoke_test.py | 155 + .../backend/sox_io/torchscript_test.py | 148 + .../torchaudio_unittest/backend/utils_test.py | 36 + .../common_utils/__init__.py | 63 + .../common_utils/backend_utils.py | 21 + .../common_utils/case_utils.py | 123 + .../common_utils/data_utils.py | 155 + .../common_utils/func_utils.py | 10 + .../common_utils/kaldi_utils.py | 38 + .../common_utils/parameterized_utils.py | 53 + .../common_utils/psd_utils.py | 27 + .../common_utils/rnnt_utils.py | 603 +++ .../common_utils/sox_utils.py | 106 + .../common_utils/wav_utils.py | 92 + .../compliance_kaldi_test.py | 76 + test/torchaudio_unittest/datasets/__init__.py | 0 .../datasets/cmuarctic_test.py | 84 + .../datasets/cmudict_test.py | 218 + .../datasets/commonvoice_test.py | 148 + .../datasets/datasets_test.py | 15 + .../datasets/gtzan_test.py | 127 + .../datasets/librispeech_test.py | 128 + .../datasets/libritts_test.py | 89 + .../datasets/ljspeech_test.py | 92 + .../datasets/speechcommands_test.py | 161 + .../datasets/tedlium_test.py | 150 + .../datasets/utils_test.py | 37 + .../torchaudio_unittest/datasets/vctk_test.py | 107 + .../datasets/yesno_test.py | 67 + test/torchaudio_unittest/example/__init__.py | 8 + .../example/souce_sepration/__init__.py | 0 .../example/souce_sepration/metrics_test.py | 39 + .../example/souce_sepration/sdr_reference.py | 98 + .../example/souce_sepration/wsj0mix_test.py | 111 + .../example/tacotron2/__init__.py | 0 .../tacotron2/tacotron2_loss_cpu_test.py | 23 + .../tacotron2/tacotron2_loss_gpu_test.py | 26 + .../example/tacotron2/tacotron2_loss_impl.py | 111 + .../tacotron2/test_text_preprocessing.py | 97 + .../functional/__init__.py | 0 .../functional/autograd_cpu_test.py | 13 + .../functional/autograd_cuda_test.py | 15 + .../functional/autograd_impl.py | 269 ++ .../functional/batch_consistency_test.py | 249 ++ .../functional/functional_cpu_test.py | 63 + .../functional/functional_cuda_test.py | 21 + .../functional/functional_impl.py | 584 +++ .../kaldi_compatibility_cpu_test.py | 19 + .../kaldi_compatibility_cuda_test.py | 16 + .../kaldi_compatibility_test_impl.py | 60 + .../librosa_compatibility_cpu_test.py | 10 + .../librosa_compatibility_cuda_test.py | 12 + .../librosa_compatibility_test_impl.py | 161 + .../functional/sox_compatibility_test.py | 299 ++ .../torchscript_consistency_cpu_test.py | 14 + .../torchscript_consistency_cuda_test.py | 16 + .../torchscript_consistency_impl.py | 718 ++++ test/torchaudio_unittest/kaldi_io_test.py | 33 + test/torchaudio_unittest/models/__init__.py | 0 .../torchaudio_unittest/models/models_test.py | 248 ++ .../models/tacotron2/__init__.py | 0 .../models/tacotron2/model_test_cpu_test.py | 23 + .../models/tacotron2/model_test_gpu_test.py | 26 + .../models/tacotron2/model_test_impl.py | 381 ++ .../models/wav2vec2/__init__.py | 0 .../wav2vec2/fairseq_integration_test.py | 240 ++ .../wav2vec2/huggingface_intergration_test.py | 224 + .../models/wav2vec2/model_test.py | 243 ++ .../sox_effect/__init__.py | 0 test/torchaudio_unittest/sox_effect/common.py | 26 + .../sox_effect/dataset_test.py | 158 + .../sox_effect/smoke_test.py | 78 + .../sox_effect/sox_effect_test.py | 423 ++ .../sox_effect/torchscript_test.py | 96 + .../transforms/__init__.py | 0 .../transforms/autograd_cpu_test.py | 10 + .../transforms/autograd_cuda_test.py | 15 + .../transforms/autograd_test_impl.py | 337 ++ .../transforms/batch_consistency_test.py | 228 ++ .../kaldi_compatibility_cpu_test.py | 14 + .../kaldi_compatibility_cuda_test.py | 16 + .../transforms/kaldi_compatibility_impl.py | 55 + .../librosa_compatibility_cpu_test.py | 9 + .../librosa_compatibility_cuda_test.py | 10 + .../librosa_compatibility_test_impl.py | 141 + .../transforms/sox_compatibility_test.py | 88 + .../torchscript_consistency_cpu_test.py | 14 + .../torchscript_consistency_cuda_test.py | 16 + .../torchscript_consistency_impl.py | 202 + .../transforms/transforms_cpu_test.py | 14 + .../transforms/transforms_cuda_test.py | 19 + .../transforms/transforms_test.py | 314 ++ .../transforms/transforms_test_impl.py | 134 + test/torchaudio_unittest/utils/__init__.py | 0 .../utils/sox_utils_test.py | 49 + third_party/CMakeLists.txt | 24 + third_party/kaldi/CMakeLists.txt | 32 + third_party/kaldi/README.md | 6 + third_party/kaldi/kaldi.patch | 76 + third_party/kaldi/src/matrix/kaldi-matrix.cc | 39 + third_party/kaldi/src/matrix/kaldi-matrix.h | 178 + third_party/kaldi/src/matrix/kaldi-vector.cc | 42 + third_party/kaldi/src/matrix/kaldi-vector.h | 319 ++ third_party/sox/CMakeLists.txt | 216 + third_party/sox/patch/config.guess | 1702 ++++++++ third_party/sox/patch/config.sub | 1864 +++++++++ third_party/sox/patch/libmad.patch | 86 + third_party/sox/patch/sox.patch | 16 + torchaudio/__init__.py | 38 + torchaudio/_extension.py | 27 + torchaudio/_internal/__init__.py | 0 torchaudio/_internal/module_utils.py | 125 + torchaudio/backend/__init__.py | 10 + torchaudio/backend/common.py | 51 + torchaudio/backend/no_backend.py | 22 + torchaudio/backend/soundfile_backend.py | 433 ++ torchaudio/backend/sox_io_backend.py | 317 ++ torchaudio/backend/utils.py | 83 + torchaudio/compliance/__init__.py | 5 + torchaudio/compliance/kaldi.py | 750 ++++ torchaudio/csrc/CMakeLists.txt | 171 + torchaudio/csrc/kaldi.cpp | 93 + torchaudio/csrc/lfilter.cpp | 283 ++ torchaudio/csrc/overdrive.cpp | 52 + torchaudio/csrc/pybind/pybind.cpp | 27 + torchaudio/csrc/pybind/sox/effects.cpp | 117 + torchaudio/csrc/pybind/sox/effects.h | 17 + torchaudio/csrc/pybind/sox/effects_chain.cpp | 227 ++ torchaudio/csrc/pybind/sox/effects_chain.h | 28 + torchaudio/csrc/pybind/sox/io.cpp | 190 + torchaudio/csrc/pybind/sox/io.h | 31 + torchaudio/csrc/pybind/sox/utils.cpp | 31 + torchaudio/csrc/pybind/sox/utils.h | 12 + torchaudio/csrc/rnnt/autograd.cpp | 56 + torchaudio/csrc/rnnt/compute.cpp | 25 + torchaudio/csrc/rnnt/compute.h | 11 + torchaudio/csrc/rnnt/compute_alphas.cpp | 11 + torchaudio/csrc/rnnt/compute_betas.cpp | 11 + torchaudio/csrc/rnnt/cpu/compute.cpp | 148 + torchaudio/csrc/rnnt/cpu/compute_alphas.cpp | 70 + torchaudio/csrc/rnnt/cpu/compute_betas.cpp | 75 + torchaudio/csrc/rnnt/cpu/cpu_kernels.h | 498 +++ torchaudio/csrc/rnnt/cpu/cpu_transducer.h | 184 + torchaudio/csrc/rnnt/cpu/kernel_utils.h | 66 + torchaudio/csrc/rnnt/cpu/math.h | 42 + torchaudio/csrc/rnnt/gpu/compute.cu | 151 + torchaudio/csrc/rnnt/gpu/compute_alphas.cu | 73 + torchaudio/csrc/rnnt/gpu/compute_betas.cu | 78 + torchaudio/csrc/rnnt/gpu/gpu_kernel_utils.cuh | 98 + torchaudio/csrc/rnnt/gpu/gpu_kernels.cuh | 409 ++ torchaudio/csrc/rnnt/gpu/gpu_transducer.h | 391 ++ torchaudio/csrc/rnnt/gpu/half.cuh | 38 + torchaudio/csrc/rnnt/gpu/kernel_utils.h | 66 + torchaudio/csrc/rnnt/gpu/kernels.h | 108 + torchaudio/csrc/rnnt/gpu/math.cuh | 48 + torchaudio/csrc/rnnt/macros.cpp | 16 + torchaudio/csrc/rnnt/macros.h | 21 + torchaudio/csrc/rnnt/options.h | 77 + torchaudio/csrc/rnnt/types.cpp | 41 + torchaudio/csrc/rnnt/types.h | 23 + torchaudio/csrc/rnnt/workspace.h | 223 + torchaudio/csrc/sox/effects.cpp | 155 + torchaudio/csrc/sox/effects.h | 29 + torchaudio/csrc/sox/effects_chain.cpp | 323 ++ torchaudio/csrc/sox/effects_chain.h | 63 + torchaudio/csrc/sox/io.cpp | 143 + torchaudio/csrc/sox/io.h | 40 + torchaudio/csrc/sox/types.cpp | 139 + torchaudio/csrc/sox/types.h | 60 + torchaudio/csrc/sox/utils.cpp | 522 +++ torchaudio/csrc/sox/utils.h | 118 + torchaudio/csrc/utils.cpp | 30 + torchaudio/datasets/__init__.py | 32 + torchaudio/datasets/cmuarctic.py | 173 + torchaudio/datasets/cmudict.py | 182 + torchaudio/datasets/commonvoice.py | 76 + torchaudio/datasets/gtzan.py | 1113 +++++ torchaudio/datasets/librimix.py | 89 + torchaudio/datasets/librispeech.py | 143 + torchaudio/datasets/libritts.py | 150 + torchaudio/datasets/ljspeech.py | 89 + torchaudio/datasets/speechcommands.py | 148 + torchaudio/datasets/tedlium.py | 195 + torchaudio/datasets/utils.py | 284 ++ torchaudio/datasets/vctk.py | 275 ++ torchaudio/datasets/yesno.py | 87 + torchaudio/functional/__init__.py | 105 + torchaudio/functional/filtering.py | 1636 ++++++++ torchaudio/functional/functional.py | 1812 +++++++++ torchaudio/kaldi_io.py | 130 + torchaudio/models/__init__.py | 31 + torchaudio/models/conv_tasnet.py | 321 ++ torchaudio/models/deepspeech.py | 91 + torchaudio/models/tacotron2.py | 1109 +++++ torchaudio/models/wav2letter.py | 74 + torchaudio/models/wav2vec2/__init__.py | 23 + torchaudio/models/wav2vec2/components.py | 717 ++++ torchaudio/models/wav2vec2/model.py | 590 +++ torchaudio/models/wav2vec2/utils/__init__.py | 7 + .../models/wav2vec2/utils/import_fairseq.py | 219 + .../wav2vec2/utils/import_huggingface.py | 80 + torchaudio/models/wavernn.py | 411 ++ torchaudio/pipelines/__init__.py | 57 + torchaudio/pipelines/_tts/__init__.py | 16 + torchaudio/pipelines/_tts/impl.py | 356 ++ torchaudio/pipelines/_tts/interface.py | 272 ++ torchaudio/pipelines/_tts/utils.py | 229 ++ torchaudio/pipelines/_wav2vec2.py | 1001 +++++ torchaudio/prototype/__init__.py | 0 torchaudio/sox_effects/__init__.py | 22 + torchaudio/sox_effects/sox_effects.py | 273 ++ torchaudio/transforms.py | 2050 ++++++++++ torchaudio/utils/__init__.py | 8 + torchaudio/utils/sox_utils.py | 102 + 443 files changed, 60405 insertions(+), 1 deletion(-) create mode 100644 .circleci/build_docs/build_docs.sh create mode 100644 .circleci/build_docs/commit_docs.sh create mode 100644 .circleci/build_docs/install_wheels.sh create mode 100644 .circleci/config.yml create mode 100644 .circleci/config.yml.in create mode 100644 .circleci/regenerate.py create mode 100644 .circleci/smoke_test/docker/Dockerfile create mode 100644 .circleci/unittest/linux/README.md create mode 100644 .circleci/unittest/linux/docker/.dockerignore create mode 100644 .circleci/unittest/linux/docker/.gitignore create mode 100644 .circleci/unittest/linux/docker/Dockerfile create mode 100644 .circleci/unittest/linux/docker/build_and_push.sh create mode 100644 .circleci/unittest/linux/docker/scripts/copy_kaldi_executables.sh create mode 100644 .circleci/unittest/linux/scripts/install.sh create mode 100644 .circleci/unittest/linux/scripts/run_clang_format.py create mode 100644 .circleci/unittest/linux/scripts/run_style_checks.sh create mode 100644 .circleci/unittest/linux/scripts/run_test.sh create mode 100644 .circleci/unittest/linux/scripts/setup_env.sh create mode 100644 .circleci/unittest/windows/README.md create mode 100644 .circleci/unittest/windows/scripts/environment.yml create mode 100644 .circleci/unittest/windows/scripts/install.sh create mode 100644 .circleci/unittest/windows/scripts/install_conda.bat create mode 100644 .circleci/unittest/windows/scripts/run_test.sh create mode 100644 .circleci/unittest/windows/scripts/set_cuda_envs.sh create mode 100644 .circleci/unittest/windows/scripts/setup_env.sh create mode 100644 .clang-format create mode 100644 .clang-tidy create mode 100644 .flake8 create mode 100644 .gitattributes create mode 100644 .github/ISSUE_TEMPLATE/bug-report.md create mode 100644 .github/ISSUE_TEMPLATE/documentation.md create mode 100644 .github/ISSUE_TEMPLATE/feature-request.md create mode 100644 .github/ISSUE_TEMPLATE/questions-help-support.md create mode 100644 .github/pytorch-probot.yml create mode 100644 .github/workflows/bandit.yml create mode 100644 .github/workflows/codeql.yml create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 CMakeLists.txt create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 build_tools/__init__.py create mode 100644 build_tools/convert_fairseq_models.py create mode 100644 build_tools/setup_helpers/__init__.py create mode 100644 build_tools/setup_helpers/extension.py create mode 100644 build_tools/travis/install.sh create mode 100644 build_tools/travis/test_script.sh create mode 100644 cmake/LoadHIP.cmake create mode 100644 docs/Makefile create mode 100644 docs/make.bat create mode 100644 docs/requirements.txt create mode 100644 docs/source/_static/img/pytorch-logo-dark.png create mode 100644 docs/source/_static/img/pytorch-logo-dark.svg create mode 100644 docs/source/_static/img/pytorch-logo-flame.png create mode 100644 docs/source/_templates/layout.html create mode 100644 docs/source/backend.rst create mode 100644 docs/source/compliance.kaldi.rst create mode 100644 docs/source/conf.py create mode 100644 docs/source/datasets.rst create mode 100644 docs/source/functional.rst create mode 100644 docs/source/index.rst create mode 100644 docs/source/kaldi_io.rst create mode 100644 docs/source/models.rst create mode 100644 docs/source/pipelines.rst create mode 100644 docs/source/refs.bib create mode 100644 docs/source/sox_effects.rst create mode 100644 docs/source/torchaudio.rst create mode 100644 docs/source/transforms.rst create mode 100644 docs/source/utils.rst create mode 100644 examples/beamforming/MVDR_tutorial.ipynb create mode 100644 examples/interactive_asr/README.md create mode 100644 examples/interactive_asr/__init__.py create mode 100644 examples/interactive_asr/asr.py create mode 100644 examples/interactive_asr/data/sample.wav create mode 100644 examples/interactive_asr/utils.py create mode 100644 examples/interactive_asr/vad.py create mode 100644 examples/libtorchaudio/.gitignore create mode 100644 examples/libtorchaudio/CMakeLists.txt create mode 100644 examples/libtorchaudio/README.md create mode 100644 examples/libtorchaudio/augmentation/CMakeLists.txt create mode 100644 examples/libtorchaudio/augmentation/README.md create mode 100644 examples/libtorchaudio/augmentation/create_jittable_pipeline.py create mode 100644 examples/libtorchaudio/augmentation/main.cpp create mode 100644 examples/libtorchaudio/build.sh create mode 100644 examples/libtorchaudio/data/README.md create mode 100644 examples/libtorchaudio/data/input.wav create mode 100644 examples/libtorchaudio/data/rir.wav create mode 100644 examples/libtorchaudio/speech_recognition/CMakeLists.txt create mode 100644 examples/libtorchaudio/speech_recognition/README.md create mode 100644 examples/libtorchaudio/speech_recognition/build_pipeline_from_fairseq.py create mode 100644 examples/libtorchaudio/speech_recognition/build_pipeline_from_huggingface_transformers.py create mode 100644 examples/libtorchaudio/speech_recognition/greedy_decoder.py create mode 100644 examples/libtorchaudio/speech_recognition/parse_librispeech.py create mode 100644 examples/libtorchaudio/speech_recognition/parse_voxforge.py create mode 100644 examples/libtorchaudio/speech_recognition/transcribe.cpp create mode 100644 examples/libtorchaudio/speech_recognition/transcribe_list.cpp create mode 100644 examples/pipeline_tacotron2/README.md create mode 100644 examples/pipeline_tacotron2/datasets.py create mode 100644 examples/pipeline_tacotron2/inference.py create mode 100644 examples/pipeline_tacotron2/loss.py create mode 100644 examples/pipeline_tacotron2/text/__init__.py create mode 100644 examples/pipeline_tacotron2/text/numbers.py create mode 100644 examples/pipeline_tacotron2/text/text_preprocessing.py create mode 100644 examples/pipeline_tacotron2/train.py create mode 100644 examples/pipeline_tacotron2/utils.py create mode 100644 examples/pipeline_wav2letter/README.md create mode 100644 examples/pipeline_wav2letter/ctc_decoders.py create mode 100644 examples/pipeline_wav2letter/datasets.py create mode 100644 examples/pipeline_wav2letter/languagemodels.py create mode 100644 examples/pipeline_wav2letter/main.py create mode 100644 examples/pipeline_wav2letter/transforms.py create mode 100644 examples/pipeline_wav2letter/utils.py create mode 100644 examples/pipeline_wavernn/README.md create mode 100644 examples/pipeline_wavernn/datasets.py create mode 100644 examples/pipeline_wavernn/inference.py create mode 100644 examples/pipeline_wavernn/losses.py create mode 100644 examples/pipeline_wavernn/main.py create mode 100644 examples/pipeline_wavernn/processing.py create mode 100644 examples/pipeline_wavernn/utils.py create mode 100644 examples/pipeline_wavernn/wavernn_inference_wrapper.py create mode 100644 examples/source_separation/README.md create mode 100644 examples/source_separation/conv_tasnet/README.md create mode 100644 examples/source_separation/conv_tasnet/__init__.py create mode 100644 examples/source_separation/conv_tasnet/train.py create mode 100644 examples/source_separation/conv_tasnet/trainer.py create mode 100644 examples/source_separation/eval.py create mode 100644 examples/source_separation/lightning_train.py create mode 100644 examples/source_separation/train.py create mode 100644 examples/source_separation/utils/__init__.py create mode 100644 examples/source_separation/utils/dataset/__init__.py create mode 100644 examples/source_separation/utils/dataset/utils.py create mode 100644 examples/source_separation/utils/dataset/wsj0mix.py create mode 100644 examples/source_separation/utils/dist_utils.py create mode 100644 examples/source_separation/utils/metrics.py create mode 100644 examples/test/__init__.py create mode 100644 examples/test/test_interactive_asr.py create mode 100644 mypy.ini create mode 100644 packaging/README.md create mode 100644 packaging/build_conda.sh create mode 100644 packaging/build_wheel.sh create mode 100644 packaging/pkg_helpers.bash create mode 100644 packaging/torchaudio/bld.bat create mode 100644 packaging/torchaudio/build.sh create mode 100644 packaging/torchaudio/meta.yaml create mode 100644 packaging/vc_env_helper.bat create mode 100644 packaging/vs2019/activate.bat create mode 100644 packaging/vs2019/conda_build_config.yaml create mode 100644 packaging/vs2019/install_activate.bat create mode 100644 packaging/vs2019/install_runtime.bat create mode 100644 packaging/vs2019/meta.yaml create mode 100644 packaging/windows/internal/cuda_install.bat create mode 100644 packaging/windows/internal/driver_update.bat create mode 100644 requirements.txt create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 test/integration_tests/__init__.py create mode 100644 test/integration_tests/conftest.py create mode 100644 test/integration_tests/tacotron2_pipeline_test.py create mode 100644 test/integration_tests/wav2vec2_pipeline_test.py create mode 100644 test/torchaudio_unittest/README.md create mode 100644 test/torchaudio_unittest/__init__.py create mode 100644 test/torchaudio_unittest/assets/Lab41-SRI-VOiCES-src-sp0307-ch127535-sg0042.flac create mode 100644 test/torchaudio_unittest/assets/VCTK-Corpus/txt/p224/p224_002.txt create mode 100644 test/torchaudio_unittest/assets/VCTK-Corpus/wav48/p224/p224_002.wav create mode 100644 test/torchaudio_unittest/assets/io/96k_0_1ch.opus create mode 100644 test/torchaudio_unittest/assets/io/96k_0_2ch.opus create mode 100644 test/torchaudio_unittest/assets/io/96k_10_1ch.opus create mode 100644 test/torchaudio_unittest/assets/io/96k_10_2ch.opus create mode 100644 test/torchaudio_unittest/assets/io/96k_5_1ch.opus create mode 100644 test/torchaudio_unittest/assets/io/96k_5_2ch.opus create mode 100644 test/torchaudio_unittest/assets/io/generate_opus.py create mode 100644 test/torchaudio_unittest/assets/kaldi_file.wav create mode 100644 test/torchaudio_unittest/assets/kaldi_file_8000.wav create mode 100644 test/torchaudio_unittest/assets/kaldi_test_fbank_args.jsonl create mode 100644 test/torchaudio_unittest/assets/kaldi_test_mfcc_args.jsonl create mode 100644 test/torchaudio_unittest/assets/kaldi_test_pitch_args.jsonl create mode 100644 test/torchaudio_unittest/assets/kaldi_test_spectrogram_args.jsonl create mode 100644 test/torchaudio_unittest/assets/mat.ark create mode 100644 test/torchaudio_unittest/assets/mp3_without_ext create mode 100644 test/torchaudio_unittest/assets/sinewave.wav create mode 100644 test/torchaudio_unittest/assets/sox_effect_test_args.jsonl create mode 100644 test/torchaudio_unittest/assets/sox_effect_test_fir_coeffs.txt create mode 100644 test/torchaudio_unittest/assets/steam-train-whistle-daniel_simon.mp3 create mode 100644 test/torchaudio_unittest/assets/steam-train-whistle-daniel_simon.wav create mode 100644 test/torchaudio_unittest/assets/vad-go-mono-32000.wav create mode 100644 test/torchaudio_unittest/assets/vad-go-stereo-44100.wav create mode 100644 test/torchaudio_unittest/assets/vec_flt.ark create mode 100644 test/torchaudio_unittest/assets/vec_int.ark create mode 100644 test/torchaudio_unittest/assets/wav2vec2/fairseq/generate_hubert_model_config.py create mode 100644 test/torchaudio_unittest/assets/wav2vec2/fairseq/generate_wav2vec2_model_config.py create mode 100644 test/torchaudio_unittest/assets/wav2vec2/fairseq/hubert_base_ls960.json create mode 100644 test/torchaudio_unittest/assets/wav2vec2/fairseq/hubert_large_ll60k.json create mode 100644 test/torchaudio_unittest/assets/wav2vec2/fairseq/hubert_large_ll60k_finetune_ls960.json create mode 100644 test/torchaudio_unittest/assets/wav2vec2/fairseq/hubert_xtralarge_ll60k.json create mode 100644 test/torchaudio_unittest/assets/wav2vec2/fairseq/hubert_xtralarge_ll60k_finetune_ls960.json create mode 100644 test/torchaudio_unittest/assets/wav2vec2/fairseq/libri960_big.json create mode 100644 test/torchaudio_unittest/assets/wav2vec2/fairseq/wav2vec_large_960h.json create mode 100644 test/torchaudio_unittest/assets/wav2vec2/fairseq/wav2vec_large_lv60k_960h.json create mode 100644 test/torchaudio_unittest/assets/wav2vec2/fairseq/wav2vec_large_lv60k_self_960h.json create mode 100644 test/torchaudio_unittest/assets/wav2vec2/fairseq/wav2vec_small.json create mode 100644 test/torchaudio_unittest/assets/wav2vec2/fairseq/wav2vec_small_960h.json create mode 100644 test/torchaudio_unittest/assets/wav2vec2/fairseq/wav2vec_vox_new.json create mode 100644 test/torchaudio_unittest/assets/wav2vec2/fairseq/xlsr_53_56k.json create mode 100644 test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-base-10k-voxpopuli.json create mode 100644 test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-base-960h.json create mode 100644 test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-base.json create mode 100644 test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-large-960h-lv60-self.json create mode 100644 test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-large-960h-lv60.json create mode 100644 test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-large-960h.json create mode 100644 test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-large-lv60.json create mode 100644 test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-large-xlsr-53-german.json create mode 100644 test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-large-xlsr-53.json create mode 100644 test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-large.json create mode 100644 test/torchaudio_unittest/assets/wav2vec2/huggingface/generate_huggingface_model_config.py create mode 100644 test/torchaudio_unittest/backend/__init__.py create mode 100644 test/torchaudio_unittest/backend/common.py create mode 100644 test/torchaudio_unittest/backend/soundfile/__init__.py create mode 100644 test/torchaudio_unittest/backend/soundfile/common.py create mode 100644 test/torchaudio_unittest/backend/soundfile/info_test.py create mode 100644 test/torchaudio_unittest/backend/soundfile/load_test.py create mode 100644 test/torchaudio_unittest/backend/soundfile/save_test.py create mode 100644 test/torchaudio_unittest/backend/sox_io/__init__.py create mode 100644 test/torchaudio_unittest/backend/sox_io/common.py create mode 100644 test/torchaudio_unittest/backend/sox_io/info_test.py create mode 100644 test/torchaudio_unittest/backend/sox_io/load_test.py create mode 100644 test/torchaudio_unittest/backend/sox_io/roundtrip_test.py create mode 100644 test/torchaudio_unittest/backend/sox_io/save_test.py create mode 100644 test/torchaudio_unittest/backend/sox_io/smoke_test.py create mode 100644 test/torchaudio_unittest/backend/sox_io/torchscript_test.py create mode 100644 test/torchaudio_unittest/backend/utils_test.py create mode 100644 test/torchaudio_unittest/common_utils/__init__.py create mode 100644 test/torchaudio_unittest/common_utils/backend_utils.py create mode 100644 test/torchaudio_unittest/common_utils/case_utils.py create mode 100644 test/torchaudio_unittest/common_utils/data_utils.py create mode 100644 test/torchaudio_unittest/common_utils/func_utils.py create mode 100644 test/torchaudio_unittest/common_utils/kaldi_utils.py create mode 100644 test/torchaudio_unittest/common_utils/parameterized_utils.py create mode 100644 test/torchaudio_unittest/common_utils/psd_utils.py create mode 100644 test/torchaudio_unittest/common_utils/rnnt_utils.py create mode 100644 test/torchaudio_unittest/common_utils/sox_utils.py create mode 100644 test/torchaudio_unittest/common_utils/wav_utils.py create mode 100644 test/torchaudio_unittest/compliance_kaldi_test.py create mode 100644 test/torchaudio_unittest/datasets/__init__.py create mode 100644 test/torchaudio_unittest/datasets/cmuarctic_test.py create mode 100644 test/torchaudio_unittest/datasets/cmudict_test.py create mode 100644 test/torchaudio_unittest/datasets/commonvoice_test.py create mode 100644 test/torchaudio_unittest/datasets/datasets_test.py create mode 100644 test/torchaudio_unittest/datasets/gtzan_test.py create mode 100644 test/torchaudio_unittest/datasets/librispeech_test.py create mode 100644 test/torchaudio_unittest/datasets/libritts_test.py create mode 100644 test/torchaudio_unittest/datasets/ljspeech_test.py create mode 100644 test/torchaudio_unittest/datasets/speechcommands_test.py create mode 100644 test/torchaudio_unittest/datasets/tedlium_test.py create mode 100644 test/torchaudio_unittest/datasets/utils_test.py create mode 100644 test/torchaudio_unittest/datasets/vctk_test.py create mode 100644 test/torchaudio_unittest/datasets/yesno_test.py create mode 100644 test/torchaudio_unittest/example/__init__.py create mode 100644 test/torchaudio_unittest/example/souce_sepration/__init__.py create mode 100644 test/torchaudio_unittest/example/souce_sepration/metrics_test.py create mode 100644 test/torchaudio_unittest/example/souce_sepration/sdr_reference.py create mode 100644 test/torchaudio_unittest/example/souce_sepration/wsj0mix_test.py create mode 100644 test/torchaudio_unittest/example/tacotron2/__init__.py create mode 100644 test/torchaudio_unittest/example/tacotron2/tacotron2_loss_cpu_test.py create mode 100644 test/torchaudio_unittest/example/tacotron2/tacotron2_loss_gpu_test.py create mode 100644 test/torchaudio_unittest/example/tacotron2/tacotron2_loss_impl.py create mode 100644 test/torchaudio_unittest/example/tacotron2/test_text_preprocessing.py create mode 100644 test/torchaudio_unittest/functional/__init__.py create mode 100644 test/torchaudio_unittest/functional/autograd_cpu_test.py create mode 100644 test/torchaudio_unittest/functional/autograd_cuda_test.py create mode 100644 test/torchaudio_unittest/functional/autograd_impl.py create mode 100644 test/torchaudio_unittest/functional/batch_consistency_test.py create mode 100644 test/torchaudio_unittest/functional/functional_cpu_test.py create mode 100644 test/torchaudio_unittest/functional/functional_cuda_test.py create mode 100644 test/torchaudio_unittest/functional/functional_impl.py create mode 100644 test/torchaudio_unittest/functional/kaldi_compatibility_cpu_test.py create mode 100644 test/torchaudio_unittest/functional/kaldi_compatibility_cuda_test.py create mode 100644 test/torchaudio_unittest/functional/kaldi_compatibility_test_impl.py create mode 100644 test/torchaudio_unittest/functional/librosa_compatibility_cpu_test.py create mode 100644 test/torchaudio_unittest/functional/librosa_compatibility_cuda_test.py create mode 100644 test/torchaudio_unittest/functional/librosa_compatibility_test_impl.py create mode 100644 test/torchaudio_unittest/functional/sox_compatibility_test.py create mode 100644 test/torchaudio_unittest/functional/torchscript_consistency_cpu_test.py create mode 100644 test/torchaudio_unittest/functional/torchscript_consistency_cuda_test.py create mode 100644 test/torchaudio_unittest/functional/torchscript_consistency_impl.py create mode 100644 test/torchaudio_unittest/kaldi_io_test.py create mode 100644 test/torchaudio_unittest/models/__init__.py create mode 100644 test/torchaudio_unittest/models/models_test.py create mode 100644 test/torchaudio_unittest/models/tacotron2/__init__.py create mode 100644 test/torchaudio_unittest/models/tacotron2/model_test_cpu_test.py create mode 100644 test/torchaudio_unittest/models/tacotron2/model_test_gpu_test.py create mode 100644 test/torchaudio_unittest/models/tacotron2/model_test_impl.py create mode 100644 test/torchaudio_unittest/models/wav2vec2/__init__.py create mode 100644 test/torchaudio_unittest/models/wav2vec2/fairseq_integration_test.py create mode 100644 test/torchaudio_unittest/models/wav2vec2/huggingface_intergration_test.py create mode 100644 test/torchaudio_unittest/models/wav2vec2/model_test.py create mode 100644 test/torchaudio_unittest/sox_effect/__init__.py create mode 100644 test/torchaudio_unittest/sox_effect/common.py create mode 100644 test/torchaudio_unittest/sox_effect/dataset_test.py create mode 100644 test/torchaudio_unittest/sox_effect/smoke_test.py create mode 100644 test/torchaudio_unittest/sox_effect/sox_effect_test.py create mode 100644 test/torchaudio_unittest/sox_effect/torchscript_test.py create mode 100644 test/torchaudio_unittest/transforms/__init__.py create mode 100644 test/torchaudio_unittest/transforms/autograd_cpu_test.py create mode 100644 test/torchaudio_unittest/transforms/autograd_cuda_test.py create mode 100644 test/torchaudio_unittest/transforms/autograd_test_impl.py create mode 100644 test/torchaudio_unittest/transforms/batch_consistency_test.py create mode 100644 test/torchaudio_unittest/transforms/kaldi_compatibility_cpu_test.py create mode 100644 test/torchaudio_unittest/transforms/kaldi_compatibility_cuda_test.py create mode 100644 test/torchaudio_unittest/transforms/kaldi_compatibility_impl.py create mode 100644 test/torchaudio_unittest/transforms/librosa_compatibility_cpu_test.py create mode 100644 test/torchaudio_unittest/transforms/librosa_compatibility_cuda_test.py create mode 100644 test/torchaudio_unittest/transforms/librosa_compatibility_test_impl.py create mode 100644 test/torchaudio_unittest/transforms/sox_compatibility_test.py create mode 100644 test/torchaudio_unittest/transforms/torchscript_consistency_cpu_test.py create mode 100644 test/torchaudio_unittest/transforms/torchscript_consistency_cuda_test.py create mode 100644 test/torchaudio_unittest/transforms/torchscript_consistency_impl.py create mode 100644 test/torchaudio_unittest/transforms/transforms_cpu_test.py create mode 100644 test/torchaudio_unittest/transforms/transforms_cuda_test.py create mode 100644 test/torchaudio_unittest/transforms/transforms_test.py create mode 100644 test/torchaudio_unittest/transforms/transforms_test_impl.py create mode 100644 test/torchaudio_unittest/utils/__init__.py create mode 100644 test/torchaudio_unittest/utils/sox_utils_test.py create mode 100644 third_party/CMakeLists.txt create mode 100644 third_party/kaldi/CMakeLists.txt create mode 100644 third_party/kaldi/README.md create mode 100644 third_party/kaldi/kaldi.patch create mode 100644 third_party/kaldi/src/matrix/kaldi-matrix.cc create mode 100644 third_party/kaldi/src/matrix/kaldi-matrix.h create mode 100644 third_party/kaldi/src/matrix/kaldi-vector.cc create mode 100644 third_party/kaldi/src/matrix/kaldi-vector.h create mode 100644 third_party/sox/CMakeLists.txt create mode 100644 third_party/sox/patch/config.guess create mode 100644 third_party/sox/patch/config.sub create mode 100644 third_party/sox/patch/libmad.patch create mode 100644 third_party/sox/patch/sox.patch create mode 100644 torchaudio/__init__.py create mode 100644 torchaudio/_extension.py create mode 100644 torchaudio/_internal/__init__.py create mode 100644 torchaudio/_internal/module_utils.py create mode 100644 torchaudio/backend/__init__.py create mode 100644 torchaudio/backend/common.py create mode 100644 torchaudio/backend/no_backend.py create mode 100644 torchaudio/backend/soundfile_backend.py create mode 100644 torchaudio/backend/sox_io_backend.py create mode 100644 torchaudio/backend/utils.py create mode 100644 torchaudio/compliance/__init__.py create mode 100644 torchaudio/compliance/kaldi.py create mode 100644 torchaudio/csrc/CMakeLists.txt create mode 100644 torchaudio/csrc/kaldi.cpp create mode 100644 torchaudio/csrc/lfilter.cpp create mode 100644 torchaudio/csrc/overdrive.cpp create mode 100644 torchaudio/csrc/pybind/pybind.cpp create mode 100644 torchaudio/csrc/pybind/sox/effects.cpp create mode 100644 torchaudio/csrc/pybind/sox/effects.h create mode 100644 torchaudio/csrc/pybind/sox/effects_chain.cpp create mode 100644 torchaudio/csrc/pybind/sox/effects_chain.h create mode 100644 torchaudio/csrc/pybind/sox/io.cpp create mode 100644 torchaudio/csrc/pybind/sox/io.h create mode 100644 torchaudio/csrc/pybind/sox/utils.cpp create mode 100644 torchaudio/csrc/pybind/sox/utils.h create mode 100644 torchaudio/csrc/rnnt/autograd.cpp create mode 100644 torchaudio/csrc/rnnt/compute.cpp create mode 100644 torchaudio/csrc/rnnt/compute.h create mode 100644 torchaudio/csrc/rnnt/compute_alphas.cpp create mode 100644 torchaudio/csrc/rnnt/compute_betas.cpp create mode 100644 torchaudio/csrc/rnnt/cpu/compute.cpp create mode 100644 torchaudio/csrc/rnnt/cpu/compute_alphas.cpp create mode 100644 torchaudio/csrc/rnnt/cpu/compute_betas.cpp create mode 100644 torchaudio/csrc/rnnt/cpu/cpu_kernels.h create mode 100644 torchaudio/csrc/rnnt/cpu/cpu_transducer.h create mode 100644 torchaudio/csrc/rnnt/cpu/kernel_utils.h create mode 100644 torchaudio/csrc/rnnt/cpu/math.h create mode 100644 torchaudio/csrc/rnnt/gpu/compute.cu create mode 100644 torchaudio/csrc/rnnt/gpu/compute_alphas.cu create mode 100644 torchaudio/csrc/rnnt/gpu/compute_betas.cu create mode 100644 torchaudio/csrc/rnnt/gpu/gpu_kernel_utils.cuh create mode 100644 torchaudio/csrc/rnnt/gpu/gpu_kernels.cuh create mode 100644 torchaudio/csrc/rnnt/gpu/gpu_transducer.h create mode 100644 torchaudio/csrc/rnnt/gpu/half.cuh create mode 100644 torchaudio/csrc/rnnt/gpu/kernel_utils.h create mode 100644 torchaudio/csrc/rnnt/gpu/kernels.h create mode 100644 torchaudio/csrc/rnnt/gpu/math.cuh create mode 100644 torchaudio/csrc/rnnt/macros.cpp create mode 100644 torchaudio/csrc/rnnt/macros.h create mode 100644 torchaudio/csrc/rnnt/options.h create mode 100644 torchaudio/csrc/rnnt/types.cpp create mode 100644 torchaudio/csrc/rnnt/types.h create mode 100644 torchaudio/csrc/rnnt/workspace.h create mode 100644 torchaudio/csrc/sox/effects.cpp create mode 100644 torchaudio/csrc/sox/effects.h create mode 100644 torchaudio/csrc/sox/effects_chain.cpp create mode 100644 torchaudio/csrc/sox/effects_chain.h create mode 100644 torchaudio/csrc/sox/io.cpp create mode 100644 torchaudio/csrc/sox/io.h create mode 100644 torchaudio/csrc/sox/types.cpp create mode 100644 torchaudio/csrc/sox/types.h create mode 100644 torchaudio/csrc/sox/utils.cpp create mode 100644 torchaudio/csrc/sox/utils.h create mode 100644 torchaudio/csrc/utils.cpp create mode 100644 torchaudio/datasets/__init__.py create mode 100644 torchaudio/datasets/cmuarctic.py create mode 100644 torchaudio/datasets/cmudict.py create mode 100644 torchaudio/datasets/commonvoice.py create mode 100644 torchaudio/datasets/gtzan.py create mode 100644 torchaudio/datasets/librimix.py create mode 100644 torchaudio/datasets/librispeech.py create mode 100644 torchaudio/datasets/libritts.py create mode 100644 torchaudio/datasets/ljspeech.py create mode 100644 torchaudio/datasets/speechcommands.py create mode 100644 torchaudio/datasets/tedlium.py create mode 100644 torchaudio/datasets/utils.py create mode 100644 torchaudio/datasets/vctk.py create mode 100644 torchaudio/datasets/yesno.py create mode 100644 torchaudio/functional/__init__.py create mode 100644 torchaudio/functional/filtering.py create mode 100644 torchaudio/functional/functional.py create mode 100644 torchaudio/kaldi_io.py create mode 100644 torchaudio/models/__init__.py create mode 100644 torchaudio/models/conv_tasnet.py create mode 100644 torchaudio/models/deepspeech.py create mode 100644 torchaudio/models/tacotron2.py create mode 100644 torchaudio/models/wav2letter.py create mode 100644 torchaudio/models/wav2vec2/__init__.py create mode 100644 torchaudio/models/wav2vec2/components.py create mode 100644 torchaudio/models/wav2vec2/model.py create mode 100644 torchaudio/models/wav2vec2/utils/__init__.py create mode 100644 torchaudio/models/wav2vec2/utils/import_fairseq.py create mode 100644 torchaudio/models/wav2vec2/utils/import_huggingface.py create mode 100644 torchaudio/models/wavernn.py create mode 100644 torchaudio/pipelines/__init__.py create mode 100644 torchaudio/pipelines/_tts/__init__.py create mode 100644 torchaudio/pipelines/_tts/impl.py create mode 100644 torchaudio/pipelines/_tts/interface.py create mode 100644 torchaudio/pipelines/_tts/utils.py create mode 100644 torchaudio/pipelines/_wav2vec2.py create mode 100644 torchaudio/prototype/__init__.py create mode 100644 torchaudio/sox_effects/__init__.py create mode 100644 torchaudio/sox_effects/sox_effects.py create mode 100644 torchaudio/transforms.py create mode 100644 torchaudio/utils/__init__.py create mode 100644 torchaudio/utils/sox_utils.py diff --git a/.circleci/build_docs/build_docs.sh b/.circleci/build_docs/build_docs.sh new file mode 100644 index 00000000..2864a729 --- /dev/null +++ b/.circleci/build_docs/build_docs.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -ex +# shellcheck disable=SC1091 +source ./packaging/pkg_helpers.bash +export NO_CUDA_PACKAGE=1 +setup_env 0.8.0 +setup_wheel_python + +pushd docs +pip install -r requirements.txt +make html +popd diff --git a/.circleci/build_docs/commit_docs.sh b/.circleci/build_docs/commit_docs.sh new file mode 100644 index 00000000..59374dce --- /dev/null +++ b/.circleci/build_docs/commit_docs.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +set -ex + + +if [ "$2" == "" ]; then + echo call as "$0" "" "" + echo where src is the root of the built documentation git checkout and + echo branch should be "main" or "1.7" or so + exit 1 +fi + +src=$1 +target=$2 + +echo "committing docs from ${src} to ${target}" + +pushd "${src}" +git checkout gh-pages +mkdir -p ./"${target}" +rm -rf ./"${target}"/* +cp -r "${src}/docs/build/html/"* ./"$target" +if [ "${target}" == "main" ]; then + mkdir -p ./_static + rm -rf ./_static/* + cp -r "${src}/docs/build/html/_static/"* ./_static + git add --all ./_static || true +fi +git add --all ./"${target}" || true +git config user.email "soumith+bot@pytorch.org" +git config user.name "pytorchbot" +# If there aren't changes, don't make a commit; push is no-op +git commit -m "auto-generating sphinx docs" || true +git remote add https https://github.com/pytorch/audio.git +git push -u https gh-pages diff --git a/.circleci/build_docs/install_wheels.sh b/.circleci/build_docs/install_wheels.sh new file mode 100644 index 00000000..3e151540 --- /dev/null +++ b/.circleci/build_docs/install_wheels.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -ex + +# shellcheck disable=SC1091 +source ./packaging/pkg_helpers.bash +export NO_CUDA_PACKAGE=1 +setup_env 0.8.0 +setup_wheel_python +setup_pip_pytorch_version +# pytorch is already installed +pip install --no-deps ~/workspace/torchaudio* diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..55c75d54 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,3589 @@ +version: 2.1 + +# How to test the Linux jobs: +# - Install CircleCI local CLI: https://circleci.com/docs/2.0/local-cli/ +# - circleci config process .circleci/config.yml > gen.yml && circleci local execute -c gen.yml --job binary_linux_wheel_py3.8 +# - Replace binary_linux_wheel_py3.8 with the name of the job you want to test. +# Job names are 'name:' key. + +executors: + windows-cpu: + machine: + resource_class: windows.xlarge + image: windows-server-2019-vs2019:stable + shell: bash.exe + + windows-gpu: + machine: + resource_class: windows.gpu.nvidia.medium + image: windows-server-2019-nvidia:stable + shell: bash.exe + +commands: + generate_cache_key: + description: "Generates a cache key file that changes daily" + steps: + - run: + name: Generate cache key + command: echo "$(date +"%Y-%m-%d")" > .cachekey + designate_upload_channel: + description: "inserts the correct upload channel into ${BASH_ENV}" + steps: + - run: + name: adding UPLOAD_CHANNEL to BASH_ENV + command: | + # Hardcoded for release branch + echo "export UPLOAD_CHANNEL=test" >> ${BASH_ENV} + install_build_tools_macos: + description: "installs tools required to build torchaudio" + steps: + - run: + name: Install build tools + command: HOMEBREW_NO_AUTO_UPDATE=1 brew install pkg-config wget + # Disable brew auto update which is very slow + load_conda_channel_flags: + description: "Determines whether we need extra conda channels" + steps: + - run: + name: Adding CONDA_CHANNEL_FLAGS to BASH_ENV + command: | + CONDA_CHANNEL_FLAGS="" + # formerly used to add conda-forge flags for Python 3.9, reserving the mechanism for future python upgrades + windows_install_cuda: + description: "Install desired CUDA version on Windows runners" + steps: + - run: + name: Install CUDA + command: | + packaging/windows/internal/cuda_install.bat + +binary_common: &binary_common + parameters: + # Edit these defaults to do a release + build_version: + description: "version number of release binary; by default, build a nightly" + type: string + default: "0.10.0" + pytorch_version: + description: "PyTorch version to build against; by default, use a nightly" + type: string + default: "1.10.0" + # Don't edit these + python_version: + description: "Python version to build against (e.g., 3.8)" + type: string + cuda_version: + description: "CUDA version to build against (e.g., cpu, cu101)" + type: string + default: "cpu" + wheel_docker_image: + description: "Wheel only: what docker image to use" + type: string + default: "pytorch/manylinux-cuda102" + conda_docker_image: + description: "Conda only: what docker image to use" + type: string + default: "pytorch/conda-builder:cuda102" + environment: &environment + PYTHON_VERSION: << parameters.python_version >> + BUILD_VERSION: << parameters.build_version >> + PYTORCH_VERSION: << parameters.pytorch_version >> + CU_VERSION: << parameters.cuda_version >> + +smoke_test_common: &smoke_test_common + <<: *binary_common + docker: + - image: pytorch/torchaudio_unittest_base:smoke_test-20211019 + resource_class: large + +jobs: + circleci_consistency: + docker: + - image: cimg/python:3.8 + steps: + - checkout + - run: + command: | + pip install --user --progress-bar off jinja2 pyyaml + python .circleci/regenerate.py + git diff --exit-code || (echo ".circleci/config.yml not in sync with config.yml.in! Run .circleci/regenerate.py to update config"; exit 1) + + download_third_parties_nix: + docker: + - image: "pytorch/torchaudio_unittest_base:manylinux" + resource_class: small + steps: + - checkout + - generate_cache_key + - restore_cache: + + keys: + - tp-nix-v2-{{ checksum ".cachekey" }} + + - run: + command: | + mkdir -p third_party/sox/archives/ + wget --no-clobber --directory-prefix=third_party/sox/archives/ $(awk '/URL /{print $2}' third_party/sox/CMakeLists.txt) + - save_cache: + + key: tp-nix-v2-{{ checksum ".cachekey" }} + + paths: + - third_party/sox/archives + - persist_to_workspace: + root: third_party + paths: + - sox/archives + + binary_linux_wheel: + <<: *binary_common + docker: + - image: << parameters.wheel_docker_image >> + resource_class: 2xlarge+ + steps: + - checkout + - designate_upload_channel + - attach_workspace: + at: third_party + - run: packaging/build_wheel.sh + - store_artifacts: + path: dist + - persist_to_workspace: + root: dist + paths: + - "*" + + binary_linux_conda: + <<: *binary_common + docker: + - image: "<< parameters.conda_docker_image >>" + resource_class: 2xlarge+ + steps: + - checkout + - load_conda_channel_flags + - attach_workspace: + at: third_party + - run: packaging/build_conda.sh + - store_artifacts: + path: /opt/conda/conda-bld/linux-64 + - persist_to_workspace: + root: /opt/conda + paths: + - "conda-bld/*" + + binary_macos_wheel: + <<: *binary_common + macos: + xcode: "12.0" + steps: + - checkout + - install_build_tools_macos + - designate_upload_channel + - load_conda_channel_flags + - attach_workspace: + at: third_party + - run: + # Cannot easily deduplicate this as source'ing activate + # will set environment variables which we need to propagate + # to build_wheel.sh + command: | + curl -o conda.sh https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh + sh conda.sh -b + source $HOME/miniconda3/bin/activate + packaging/build_wheel.sh + - store_artifacts: + path: dist + - persist_to_workspace: + root: dist + paths: + - "*" + + binary_macos_conda: + <<: *binary_common + macos: + xcode: "12.0" + steps: + - checkout + - install_build_tools_macos + - load_conda_channel_flags + - attach_workspace: + at: third_party + - run: + command: | + curl -o conda.sh https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh + sh conda.sh -b + source $HOME/miniconda3/bin/activate + conda install -yq conda-build + packaging/build_conda.sh + - store_artifacts: + path: /Users/distiller/miniconda3/conda-bld/osx-64 + - persist_to_workspace: + root: /Users/distiller/miniconda3 + paths: + - "conda-bld/*" + + binary_windows_wheel: + <<: *binary_common + executor: + name: windows-cpu + steps: + - checkout + - designate_upload_channel + - load_conda_channel_flags + - windows_install_cuda + - run: + name: Build wheel packages + command: | + set -ex + eval "$('/C/tools/miniconda3/Scripts/conda.exe' 'shell.bash' 'hook')" + conda activate base + bash packaging/build_wheel.sh + - store_artifacts: + path: dist + - persist_to_workspace: + root: dist + paths: + - "*" + + binary_windows_conda: + <<: *binary_common + executor: + name: windows-cpu + steps: + - checkout + - load_conda_channel_flags + - windows_install_cuda + - run: + name: Build conda packages + no_output_timeout: 20m + command: | + set -ex + eval "$('/C/tools/miniconda3/Scripts/conda.exe' 'shell.bash' 'hook')" + conda activate base + conda install -yq conda-build "conda-package-handling!=1.5.0" + # cudatoolkit >= 11 isn't available for windows in the nvidia channel + if [[ "${CU_VERSION}" =~ cu11.* ]]; then + export CONDA_CHANNEL_FLAGS="-c conda-forge" + fi + bash packaging/build_conda.sh + - store_artifacts: + path: C:/tools/miniconda3/conda-bld/win-64 + - persist_to_workspace: + root: C:/tools/miniconda3 + paths: + - "conda-bld/*" + + # Requires org-member context + binary_conda_upload: + docker: + - image: continuumio/miniconda + steps: + - attach_workspace: + at: ~/workspace + - designate_upload_channel + - run: + command: | + # Prevent credential from leaking + conda install -yq anaconda-client + set -x + anaconda -t "${CONDA_PYTORCHBOT_TOKEN}" upload ~/workspace/conda-bld/*/*.tar.bz2 -u "pytorch-${UPLOAD_CHANNEL}" --label main --no-progress --force + + # Requires org-member context + binary_wheel_upload: + parameters: + subfolder: + description: "What whl subfolder to upload to, e.g., blank or cu100/ (trailing slash is important)" + type: string + docker: + - image: cimg/python:3.8 + steps: + - attach_workspace: + at: ~/workspace + - checkout + - designate_upload_channel + - run: + command: | + pip install --user awscli + export PATH="$HOME/.local/bin:$PATH" + # Prevent credential from leaking + set +x + export AWS_ACCESS_KEY_ID="${PYTORCH_BINARY_AWS_ACCESS_KEY_ID}" + export AWS_SECRET_ACCESS_KEY="${PYTORCH_BINARY_AWS_SECRET_ACCESS_KEY}" + set -x + for pkg in ~/workspace/*.whl; do + aws s3 cp "$pkg" "s3://pytorch/whl/${UPLOAD_CHANNEL}/<< parameters.subfolder >>" --acl public-read + done + + smoke_test_linux_conda: + <<: *smoke_test_common + steps: + - attach_workspace: + at: ~/workspace + - designate_upload_channel + - load_conda_channel_flags + - run: + name: install binaries + command: | + set -x + source /usr/local/etc/profile.d/conda.sh && conda activate python${PYTHON_VERSION} + conda install -v -y -c pytorch-${UPLOAD_CHANNEL} pytorch cpuonly + conda install -v -y -c file://$HOME/workspace/conda-bld torchaudio + - run: + name: smoke test + command: | + source /usr/local/etc/profile.d/conda.sh && conda activate python${PYTHON_VERSION} + python -c "import torchaudio" + + smoke_test_linux_conda_gpu: + <<: *smoke_test_common + steps: + - attach_workspace: + at: ~/workspace + - designate_upload_channel + - load_conda_channel_flags + - run: + name: install binaries + command: | + set -x + source /usr/local/etc/profile.d/conda.sh && conda activate python${PYTHON_VERSION} + conda install -v -y -c pytorch-${UPLOAD_CHANNEL} pytorch cudatoolkit=${CU_VERSION:2:2}.${CU_VERSION:4} -c conda-forge + conda install -v -y -c file://$HOME/workspace/conda-bld torchaudio + - run: + name: smoke test + command: | + source /usr/local/etc/profile.d/conda.sh && conda activate python${PYTHON_VERSION} + python -c "import torchaudio" + + smoke_test_linux_pip: + <<: *smoke_test_common + steps: + - attach_workspace: + at: ~/workspace + - designate_upload_channel + - load_conda_channel_flags + - run: + name: install binaries + command: | + set -x + source /usr/local/etc/profile.d/conda.sh && conda activate python${PYTHON_VERSION} + pip install $(ls ~/workspace/torchaudio*.whl) -f "https://download.pytorch.org/whl/${UPLOAD_CHANNEL}/${CU_VERSION}/torch_${UPLOAD_CHANNEL}.html" + - run: + name: smoke test + command: | + source /usr/local/etc/profile.d/conda.sh && conda activate python${PYTHON_VERSION} + python -c "import torchaudio" + + smoke_test_windows_conda: + <<: *binary_common + executor: + name: windows-cpu + steps: + - attach_workspace: + at: ~/workspace + - designate_upload_channel + - load_conda_channel_flags + - run: + name: install binaries + command: | + set -x + eval "$('/C/tools/miniconda3/Scripts/conda.exe' 'shell.bash' 'hook')" + conda env remove -n python${PYTHON_VERSION} || true + conda create -yn python${PYTHON_VERSION} python=${PYTHON_VERSION} + conda activate python${PYTHON_VERSION} + conda install -v -y -c pytorch-${UPLOAD_CHANNEL} pytorch cpuonly + conda install -v -y $(ls ~/workspace/torchaudio*.tar.bz2) + - run: + name: smoke test + command: | + eval "$('/C/tools/miniconda3/Scripts/conda.exe' 'shell.bash' 'hook')" + conda activate python${PYTHON_VERSION} + python -c "import torchaudio" + + smoke_test_windows_pip: + <<: *binary_common + executor: + name: windows-cpu + steps: + - attach_workspace: + at: ~/workspace + - designate_upload_channel + - load_conda_channel_flags + - run: + name: install binaries + command: | + set -x + eval "$('/C/tools/miniconda3/Scripts/conda.exe' 'shell.bash' 'hook')" + conda env remove -n python${PYTHON_VERSION} || true + conda create -yn python${PYTHON_VERSION} python=${PYTHON_VERSION} + conda activate python${PYTHON_VERSION} + pip install $(ls ~/workspace/torchaudio*.whl) -f "https://download.pytorch.org/whl/${UPLOAD_CHANNEL}/torch_${UPLOAD_CHANNEL}.html" + - run: + name: smoke test + command: | + eval "$('/C/tools/miniconda3/Scripts/conda.exe' 'shell.bash' 'hook')" + conda activate python${PYTHON_VERSION} + python -c "import torchaudio" + + smoke_test_docker_image_build: + machine: + image: ubuntu-1604:201903-01 + resource_class: large + environment: + image_name: torchaudio/smoke_test + steps: + - checkout + - run: + name: build_docker image + no_output_timeout: "1h" + command: | + cd .circleci/smoke_test/docker && docker build . -t ${image_name}:${CIRCLE_WORKFLOW_ID} + - run: + name: upload docker image + no_output_timeout: "1h" + command: | + set +x + export AWS_ACCESS_KEY_ID=${ECR_AWS_ACCESS_KEY} + export AWS_SECRET_ACCESS_KEY=${ECR_AWS_SECRET_ACCESS_KEY} + eval $(aws ecr get-login --region us-east-1 --no-include-email) + set -x + docker tag ${image_name}:${CIRCLE_WORKFLOW_ID} 308535385114.dkr.ecr.us-east-1.amazonaws.com/${image_name}:${CIRCLE_WORKFLOW_ID} + docker tag ${image_name}:${CIRCLE_WORKFLOW_ID} 308535385114.dkr.ecr.us-east-1.amazonaws.com/${image_name}:latest + docker push 308535385114.dkr.ecr.us-east-1.amazonaws.com/${image_name}:${CIRCLE_WORKFLOW_ID} + docker push 308535385114.dkr.ecr.us-east-1.amazonaws.com/${image_name}:latest + + unittest_linux_cpu: + <<: *binary_common + docker: + - image: pytorch/torchaudio_unittest_base:manylinux-20210121 + resource_class: 2xlarge+ + steps: + - checkout + - attach_workspace: + at: third_party + - designate_upload_channel + - load_conda_channel_flags + - run: + name: Setup + command: .circleci/unittest/linux/scripts/setup_env.sh + - run: + name: Install torchaudio + command: .circleci/unittest/linux/scripts/install.sh + - run: + name: Run tests + command: .circleci/unittest/linux/scripts/run_test.sh + - store_test_results: + path: test-results + - store_artifacts: + path: test/htmlcov + unittest_linux_gpu: + <<: *binary_common + machine: + image: ubuntu-1604-cuda-10.1:201909-23 + resource_class: gpu.small + environment: + <<: *environment + image_name: pytorch/torchaudio_unittest_base:manylinux-cuda10.2-cudnn8-20210623 + steps: + - checkout + - attach_workspace: + at: third_party + - designate_upload_channel + - load_conda_channel_flags + - run: + name: Pull Docker image + command: docker pull --quiet "${image_name}" + - run: + name: Setup + command: docker run -t --gpus all -e PYTHON_VERSION -v $PWD:$PWD -w $PWD "${image_name}" .circleci/unittest/linux/scripts/setup_env.sh + - run: + name: Install torchaudio + command: docker run -t --gpus all -e UPLOAD_CHANNEL -e CONDA_CHANNEL_FLAGS -v $PWD:$PWD -w $PWD "${image_name}" .circleci/unittest/linux/scripts/install.sh + - run: + name: Run tests + command: docker run -t --gpus all -v $PWD:$PWD -w $PWD -e "TORCHAUDIO_TEST_FORCE_CUDA=1" "${image_name}" .circleci/unittest/linux/scripts/run_test.sh + - store_test_results: + path: test-results + - store_artifacts: + path: test/htmlcov + + unittest_windows_cpu: + <<: *binary_common + executor: + name: windows-cpu + steps: + - checkout + - designate_upload_channel + - load_conda_channel_flags + - run: + name: Setup + command: .circleci/unittest/windows/scripts/setup_env.sh + - run: + name: Install torchaudio + command: .circleci/unittest/windows/scripts/install.sh + - run: + name: Run tests + command: .circleci/unittest/windows/scripts/run_test.sh + - store_test_results: + path: test-results + - store_artifacts: + path: test/htmlcov + + unittest_windows_gpu: + <<: *binary_common + executor: + name: windows-gpu + environment: + <<: *environment + CUDA_VERSION: "10.2" + TORCHAUDIO_TEST_FORCE_CUDA: 1 + steps: + - checkout + - designate_upload_channel + - load_conda_channel_flags + - run: + name: Setup + command: .circleci/unittest/windows/scripts/setup_env.sh + - run: + name: Install CUDA + command: packaging/windows/internal/cuda_install.bat + - run: + name: Update CUDA driver + command: packaging/windows/internal/driver_update.bat + - run: + name: Install torchaudio + command: .circleci/unittest/windows/scripts/install.sh + - run: + name: Run tests + command: .circleci/unittest/windows/scripts/run_test.sh + - store_test_results: + path: test-results + - store_artifacts: + path: test/htmlcov + + unittest_macos_cpu: + <<: *binary_common + macos: + xcode: "12.0" + resource_class: large + steps: + - checkout + - install_build_tools_macos + - load_conda_channel_flags + - attach_workspace: + at: third_party + - designate_upload_channel + - run: + name: Setup + command: .circleci/unittest/linux/scripts/setup_env.sh + - run: + name: Install torchaudio + command: .circleci/unittest/linux/scripts/install.sh + - run: + name: Run tests + command: .circleci/unittest/linux/scripts/run_test.sh + - store_test_results: + path: test-results + - store_artifacts: + path: test/htmlcov + + stylecheck: + <<: *binary_common + docker: + - image: "pytorch/torchaudio_unittest_base:manylinux" + resource_class: medium + steps: + - checkout + - designate_upload_channel + - load_conda_channel_flags + - run: + name: Setup + command: .circleci/unittest/linux/scripts/setup_env.sh + - run: + name: Run style check + command: .circleci/unittest/linux/scripts/run_style_checks.sh + + build_docs: + <<: *binary_common + docker: + - image: "pytorch/manylinux-cuda100" + resource_class: 2xlarge+ + steps: + - attach_workspace: + at: ~/workspace + - checkout + - designate_upload_channel + - load_conda_channel_flags + - run: + name: Install pytorch-audio + command: .circleci/build_docs/install_wheels.sh + - run: + name: Build docs + command: .circleci/build_docs/build_docs.sh + - persist_to_workspace: + root: ./ + paths: + - "*" + - store_artifacts: + path: ./docs/build/html + destination: docs + + upload_docs: + <<: *binary_common + docker: + - image: "pytorch/manylinux-cuda100" + resource_class: 2xlarge+ + steps: + - attach_workspace: + at: ~/workspace + - run: + name: Generate netrc + command: | + # set credentials for https pushing + # requires the org-member context + cat > ~/.netrc \< gen.yml && circleci local execute -c gen.yml --job binary_linux_wheel_py3.8 +# - Replace binary_linux_wheel_py3.8 with the name of the job you want to test. +# Job names are 'name:' key. + +executors: + windows-cpu: + machine: + resource_class: windows.xlarge + image: windows-server-2019-vs2019:stable + shell: bash.exe + + windows-gpu: + machine: + resource_class: windows.gpu.nvidia.medium + image: windows-server-2019-nvidia:stable + shell: bash.exe + +commands: + generate_cache_key: + description: "Generates a cache key file that changes daily" + steps: + - run: + name: Generate cache key + command: echo "$(date +"%Y-%m-%d")" > .cachekey + designate_upload_channel: + description: "inserts the correct upload channel into ${BASH_ENV}" + steps: + - run: + name: adding UPLOAD_CHANNEL to BASH_ENV + command: | + # Hardcoded for release branch + echo "export UPLOAD_CHANNEL=test" >> ${BASH_ENV} + install_build_tools_macos: + description: "installs tools required to build torchaudio" + steps: + - run: + name: Install build tools + command: HOMEBREW_NO_AUTO_UPDATE=1 brew install pkg-config wget + # Disable brew auto update which is very slow + load_conda_channel_flags: + description: "Determines whether we need extra conda channels" + steps: + - run: + name: Adding CONDA_CHANNEL_FLAGS to BASH_ENV + command: | + CONDA_CHANNEL_FLAGS="" + # formerly used to add conda-forge flags for Python 3.9, reserving the mechanism for future python upgrades + windows_install_cuda: + description: "Install desired CUDA version on Windows runners" + steps: + - run: + name: Install CUDA + command: | + packaging/windows/internal/cuda_install.bat + +binary_common: &binary_common + parameters: + # Edit these defaults to do a release + build_version: + description: "version number of release binary; by default, build a nightly" + type: string + default: "0.10.0" + pytorch_version: + description: "PyTorch version to build against; by default, use a nightly" + type: string + default: "1.10.0" + # Don't edit these + python_version: + description: "Python version to build against (e.g., 3.8)" + type: string + cuda_version: + description: "CUDA version to build against (e.g., cpu, cu101)" + type: string + default: "cpu" + wheel_docker_image: + description: "Wheel only: what docker image to use" + type: string + default: "pytorch/manylinux-cuda102" + conda_docker_image: + description: "Conda only: what docker image to use" + type: string + default: "pytorch/conda-builder:cuda102" + environment: &environment + PYTHON_VERSION: << parameters.python_version >> + BUILD_VERSION: << parameters.build_version >> + PYTORCH_VERSION: << parameters.pytorch_version >> + CU_VERSION: << parameters.cuda_version >> + +smoke_test_common: &smoke_test_common + <<: *binary_common + docker: + - image: pytorch/torchaudio_unittest_base:smoke_test-20211019 + resource_class: large + +jobs: + circleci_consistency: + docker: + - image: cimg/python:3.8 + steps: + - checkout + - run: + command: | + pip install --user --progress-bar off jinja2 pyyaml + python .circleci/regenerate.py + git diff --exit-code || (echo ".circleci/config.yml not in sync with config.yml.in! Run .circleci/regenerate.py to update config"; exit 1) + + download_third_parties_nix: + docker: + - image: "pytorch/torchaudio_unittest_base:manylinux" + resource_class: small + steps: + - checkout + - generate_cache_key + - restore_cache: + {% raw %} + keys: + - tp-nix-v2-{{ checksum ".cachekey" }} + {% endraw %} + - run: + command: | + mkdir -p third_party/sox/archives/ + wget --no-clobber --directory-prefix=third_party/sox/archives/ $(awk '/URL /{print $2}' third_party/sox/CMakeLists.txt) + - save_cache: + {% raw %} + key: tp-nix-v2-{{ checksum ".cachekey" }} + {% endraw %} + paths: + - third_party/sox/archives + - persist_to_workspace: + root: third_party + paths: + - sox/archives + + binary_linux_wheel: + <<: *binary_common + docker: + - image: << parameters.wheel_docker_image >> + resource_class: 2xlarge+ + steps: + - checkout + - designate_upload_channel + - attach_workspace: + at: third_party + - run: packaging/build_wheel.sh + - store_artifacts: + path: dist + - persist_to_workspace: + root: dist + paths: + - "*" + + binary_linux_conda: + <<: *binary_common + docker: + - image: "<< parameters.conda_docker_image >>" + resource_class: 2xlarge+ + steps: + - checkout + - load_conda_channel_flags + - attach_workspace: + at: third_party + - run: packaging/build_conda.sh + - store_artifacts: + path: /opt/conda/conda-bld/linux-64 + - persist_to_workspace: + root: /opt/conda + paths: + - "conda-bld/*" + + binary_macos_wheel: + <<: *binary_common + macos: + xcode: "12.0" + steps: + - checkout + - install_build_tools_macos + - designate_upload_channel + - load_conda_channel_flags + - attach_workspace: + at: third_party + - run: + # Cannot easily deduplicate this as source'ing activate + # will set environment variables which we need to propagate + # to build_wheel.sh + command: | + curl -o conda.sh https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh + sh conda.sh -b + source $HOME/miniconda3/bin/activate + packaging/build_wheel.sh + - store_artifacts: + path: dist + - persist_to_workspace: + root: dist + paths: + - "*" + + binary_macos_conda: + <<: *binary_common + macos: + xcode: "12.0" + steps: + - checkout + - install_build_tools_macos + - load_conda_channel_flags + - attach_workspace: + at: third_party + - run: + command: | + curl -o conda.sh https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh + sh conda.sh -b + source $HOME/miniconda3/bin/activate + conda install -yq conda-build + packaging/build_conda.sh + - store_artifacts: + path: /Users/distiller/miniconda3/conda-bld/osx-64 + - persist_to_workspace: + root: /Users/distiller/miniconda3 + paths: + - "conda-bld/*" + + binary_windows_wheel: + <<: *binary_common + executor: + name: windows-cpu + steps: + - checkout + - designate_upload_channel + - load_conda_channel_flags + - windows_install_cuda + - run: + name: Build wheel packages + command: | + set -ex + eval "$('/C/tools/miniconda3/Scripts/conda.exe' 'shell.bash' 'hook')" + conda activate base + bash packaging/build_wheel.sh + - store_artifacts: + path: dist + - persist_to_workspace: + root: dist + paths: + - "*" + + binary_windows_conda: + <<: *binary_common + executor: + name: windows-cpu + steps: + - checkout + - load_conda_channel_flags + - windows_install_cuda + - run: + name: Build conda packages + no_output_timeout: 20m + command: | + set -ex + eval "$('/C/tools/miniconda3/Scripts/conda.exe' 'shell.bash' 'hook')" + conda activate base + conda install -yq conda-build "conda-package-handling!=1.5.0" + # cudatoolkit >= 11 isn't available for windows in the nvidia channel + if [[ "${CU_VERSION}" =~ cu11.* ]]; then + export CONDA_CHANNEL_FLAGS="-c conda-forge" + fi + bash packaging/build_conda.sh + - store_artifacts: + path: C:/tools/miniconda3/conda-bld/win-64 + - persist_to_workspace: + root: C:/tools/miniconda3 + paths: + - "conda-bld/*" + + # Requires org-member context + binary_conda_upload: + docker: + - image: continuumio/miniconda + steps: + - attach_workspace: + at: ~/workspace + - designate_upload_channel + - run: + command: | + # Prevent credential from leaking + conda install -yq anaconda-client + set -x + anaconda -t "${CONDA_PYTORCHBOT_TOKEN}" upload ~/workspace/conda-bld/*/*.tar.bz2 -u "pytorch-${UPLOAD_CHANNEL}" --label main --no-progress --force + + # Requires org-member context + binary_wheel_upload: + parameters: + subfolder: + description: "What whl subfolder to upload to, e.g., blank or cu100/ (trailing slash is important)" + type: string + docker: + - image: cimg/python:3.8 + steps: + - attach_workspace: + at: ~/workspace + - checkout + - designate_upload_channel + - run: + command: | + pip install --user awscli + export PATH="$HOME/.local/bin:$PATH" + # Prevent credential from leaking + set +x + export AWS_ACCESS_KEY_ID="${PYTORCH_BINARY_AWS_ACCESS_KEY_ID}" + export AWS_SECRET_ACCESS_KEY="${PYTORCH_BINARY_AWS_SECRET_ACCESS_KEY}" + set -x + for pkg in ~/workspace/*.whl; do + aws s3 cp "$pkg" "s3://pytorch/whl/${UPLOAD_CHANNEL}/<< parameters.subfolder >>" --acl public-read + done + + smoke_test_linux_conda: + <<: *smoke_test_common + steps: + - attach_workspace: + at: ~/workspace + - designate_upload_channel + - load_conda_channel_flags + - run: + name: install binaries + command: | + set -x + source /usr/local/etc/profile.d/conda.sh && conda activate python${PYTHON_VERSION} + conda install -v -y -c pytorch-${UPLOAD_CHANNEL} pytorch cpuonly + conda install -v -y -c file://$HOME/workspace/conda-bld torchaudio + - run: + name: smoke test + command: | + source /usr/local/etc/profile.d/conda.sh && conda activate python${PYTHON_VERSION} + python -c "import torchaudio" + + smoke_test_linux_conda_gpu: + <<: *smoke_test_common + steps: + - attach_workspace: + at: ~/workspace + - designate_upload_channel + - load_conda_channel_flags + - run: + name: install binaries + command: | + set -x + source /usr/local/etc/profile.d/conda.sh && conda activate python${PYTHON_VERSION} + conda install -v -y -c pytorch-${UPLOAD_CHANNEL} pytorch cudatoolkit=${CU_VERSION:2:2}.${CU_VERSION:4} -c conda-forge + conda install -v -y -c file://$HOME/workspace/conda-bld torchaudio + - run: + name: smoke test + command: | + source /usr/local/etc/profile.d/conda.sh && conda activate python${PYTHON_VERSION} + python -c "import torchaudio" + + smoke_test_linux_pip: + <<: *smoke_test_common + steps: + - attach_workspace: + at: ~/workspace + - designate_upload_channel + - load_conda_channel_flags + - run: + name: install binaries + command: | + set -x + source /usr/local/etc/profile.d/conda.sh && conda activate python${PYTHON_VERSION} + pip install $(ls ~/workspace/torchaudio*.whl) -f "https://download.pytorch.org/whl/${UPLOAD_CHANNEL}/${CU_VERSION}/torch_${UPLOAD_CHANNEL}.html" + - run: + name: smoke test + command: | + source /usr/local/etc/profile.d/conda.sh && conda activate python${PYTHON_VERSION} + python -c "import torchaudio" + + smoke_test_windows_conda: + <<: *binary_common + executor: + name: windows-cpu + steps: + - attach_workspace: + at: ~/workspace + - designate_upload_channel + - load_conda_channel_flags + - run: + name: install binaries + command: | + set -x + eval "$('/C/tools/miniconda3/Scripts/conda.exe' 'shell.bash' 'hook')" + conda env remove -n python${PYTHON_VERSION} || true + conda create -yn python${PYTHON_VERSION} python=${PYTHON_VERSION} + conda activate python${PYTHON_VERSION} + conda install -v -y -c pytorch-${UPLOAD_CHANNEL} pytorch cpuonly + conda install -v -y $(ls ~/workspace/torchaudio*.tar.bz2) + - run: + name: smoke test + command: | + eval "$('/C/tools/miniconda3/Scripts/conda.exe' 'shell.bash' 'hook')" + conda activate python${PYTHON_VERSION} + python -c "import torchaudio" + + smoke_test_windows_pip: + <<: *binary_common + executor: + name: windows-cpu + steps: + - attach_workspace: + at: ~/workspace + - designate_upload_channel + - load_conda_channel_flags + - run: + name: install binaries + command: | + set -x + eval "$('/C/tools/miniconda3/Scripts/conda.exe' 'shell.bash' 'hook')" + conda env remove -n python${PYTHON_VERSION} || true + conda create -yn python${PYTHON_VERSION} python=${PYTHON_VERSION} + conda activate python${PYTHON_VERSION} + pip install $(ls ~/workspace/torchaudio*.whl) -f "https://download.pytorch.org/whl/${UPLOAD_CHANNEL}/torch_${UPLOAD_CHANNEL}.html" + - run: + name: smoke test + command: | + eval "$('/C/tools/miniconda3/Scripts/conda.exe' 'shell.bash' 'hook')" + conda activate python${PYTHON_VERSION} + python -c "import torchaudio" + + smoke_test_docker_image_build: + machine: + image: ubuntu-1604:201903-01 + resource_class: large + environment: + image_name: torchaudio/smoke_test + steps: + - checkout + - run: + name: build_docker image + no_output_timeout: "1h" + command: | + cd .circleci/smoke_test/docker && docker build . -t ${image_name}:${CIRCLE_WORKFLOW_ID} + - run: + name: upload docker image + no_output_timeout: "1h" + command: | + set +x + export AWS_ACCESS_KEY_ID=${ECR_AWS_ACCESS_KEY} + export AWS_SECRET_ACCESS_KEY=${ECR_AWS_SECRET_ACCESS_KEY} + eval $(aws ecr get-login --region us-east-1 --no-include-email) + set -x + docker tag ${image_name}:${CIRCLE_WORKFLOW_ID} 308535385114.dkr.ecr.us-east-1.amazonaws.com/${image_name}:${CIRCLE_WORKFLOW_ID} + docker tag ${image_name}:${CIRCLE_WORKFLOW_ID} 308535385114.dkr.ecr.us-east-1.amazonaws.com/${image_name}:latest + docker push 308535385114.dkr.ecr.us-east-1.amazonaws.com/${image_name}:${CIRCLE_WORKFLOW_ID} + docker push 308535385114.dkr.ecr.us-east-1.amazonaws.com/${image_name}:latest + + unittest_linux_cpu: + <<: *binary_common + docker: + - image: pytorch/torchaudio_unittest_base:manylinux-20210121 + resource_class: 2xlarge+ + steps: + - checkout + - attach_workspace: + at: third_party + - designate_upload_channel + - load_conda_channel_flags + - run: + name: Setup + command: .circleci/unittest/linux/scripts/setup_env.sh + - run: + name: Install torchaudio + command: .circleci/unittest/linux/scripts/install.sh + - run: + name: Run tests + command: .circleci/unittest/linux/scripts/run_test.sh + - store_test_results: + path: test-results + - store_artifacts: + path: test/htmlcov + unittest_linux_gpu: + <<: *binary_common + machine: + image: ubuntu-1604-cuda-10.1:201909-23 + resource_class: gpu.small + environment: + <<: *environment + image_name: pytorch/torchaudio_unittest_base:manylinux-cuda10.2-cudnn8-20210623 + steps: + - checkout + - attach_workspace: + at: third_party + - designate_upload_channel + - load_conda_channel_flags + - run: + name: Pull Docker image + command: docker pull --quiet "${image_name}" + - run: + name: Setup + command: docker run -t --gpus all -e PYTHON_VERSION -v $PWD:$PWD -w $PWD "${image_name}" .circleci/unittest/linux/scripts/setup_env.sh + - run: + name: Install torchaudio + command: docker run -t --gpus all -e UPLOAD_CHANNEL -e CONDA_CHANNEL_FLAGS -v $PWD:$PWD -w $PWD "${image_name}" .circleci/unittest/linux/scripts/install.sh + - run: + name: Run tests + command: docker run -t --gpus all -v $PWD:$PWD -w $PWD -e "TORCHAUDIO_TEST_FORCE_CUDA=1" "${image_name}" .circleci/unittest/linux/scripts/run_test.sh + - store_test_results: + path: test-results + - store_artifacts: + path: test/htmlcov + + unittest_windows_cpu: + <<: *binary_common + executor: + name: windows-cpu + steps: + - checkout + - designate_upload_channel + - load_conda_channel_flags + - run: + name: Setup + command: .circleci/unittest/windows/scripts/setup_env.sh + - run: + name: Install torchaudio + command: .circleci/unittest/windows/scripts/install.sh + - run: + name: Run tests + command: .circleci/unittest/windows/scripts/run_test.sh + - store_test_results: + path: test-results + - store_artifacts: + path: test/htmlcov + + unittest_windows_gpu: + <<: *binary_common + executor: + name: windows-gpu + environment: + <<: *environment + CUDA_VERSION: "10.2" + TORCHAUDIO_TEST_FORCE_CUDA: 1 + steps: + - checkout + - designate_upload_channel + - load_conda_channel_flags + - run: + name: Setup + command: .circleci/unittest/windows/scripts/setup_env.sh + - run: + name: Install CUDA + command: packaging/windows/internal/cuda_install.bat + - run: + name: Update CUDA driver + command: packaging/windows/internal/driver_update.bat + - run: + name: Install torchaudio + command: .circleci/unittest/windows/scripts/install.sh + - run: + name: Run tests + command: .circleci/unittest/windows/scripts/run_test.sh + - store_test_results: + path: test-results + - store_artifacts: + path: test/htmlcov + + unittest_macos_cpu: + <<: *binary_common + macos: + xcode: "12.0" + resource_class: large + steps: + - checkout + - install_build_tools_macos + - load_conda_channel_flags + - attach_workspace: + at: third_party + - designate_upload_channel + - run: + name: Setup + command: .circleci/unittest/linux/scripts/setup_env.sh + - run: + name: Install torchaudio + command: .circleci/unittest/linux/scripts/install.sh + - run: + name: Run tests + command: .circleci/unittest/linux/scripts/run_test.sh + - store_test_results: + path: test-results + - store_artifacts: + path: test/htmlcov + + stylecheck: + <<: *binary_common + docker: + - image: "pytorch/torchaudio_unittest_base:manylinux" + resource_class: medium + steps: + - checkout + - designate_upload_channel + - load_conda_channel_flags + - run: + name: Setup + command: .circleci/unittest/linux/scripts/setup_env.sh + - run: + name: Run style check + command: .circleci/unittest/linux/scripts/run_style_checks.sh + + build_docs: + <<: *binary_common + docker: + - image: "pytorch/manylinux-cuda100" + resource_class: 2xlarge+ + steps: + - attach_workspace: + at: ~/workspace + - checkout + - designate_upload_channel + - load_conda_channel_flags + - run: + name: Install pytorch-audio + command: .circleci/build_docs/install_wheels.sh + - run: + name: Build docs + command: .circleci/build_docs/build_docs.sh + - persist_to_workspace: + root: ./ + paths: + - "*" + - store_artifacts: + path: ./docs/build/html + destination: docs + + upload_docs: + <<: *binary_common + docker: + - image: "pytorch/manylinux-cuda100" + resource_class: 2xlarge+ + steps: + - attach_workspace: + at: ~/workspace + - run: + name: Generate netrc + command: | + # set credentials for https pushing + # requires the org-member context + cat > ~/.netrc \<> ~/.bashrc +RUN source /usr/local/etc/profile.d/conda.sh && conda activate python3.6 && conda install -y -c conda-forge sox && conda install -y numpy +RUN source /usr/local/etc/profile.d/conda.sh && conda activate python3.7 && conda install -y -c conda-forge sox && conda install -y numpy +RUN source /usr/local/etc/profile.d/conda.sh && conda activate python3.8 && conda install -y -c conda-forge sox && conda install -y numpy +CMD [ "/bin/bash"] diff --git a/.circleci/unittest/linux/README.md b/.circleci/unittest/linux/README.md new file mode 100644 index 00000000..0a4b0e0e --- /dev/null +++ b/.circleci/unittest/linux/README.md @@ -0,0 +1,6 @@ +This directory contains; + + - docker + Docker image definition and scripts to build and update Docker image for unittest. + - scripts + Scripts used by CircleCI to run unit tests. diff --git a/.circleci/unittest/linux/docker/.dockerignore b/.circleci/unittest/linux/docker/.dockerignore new file mode 100644 index 00000000..1398d409 --- /dev/null +++ b/.circleci/unittest/linux/docker/.dockerignore @@ -0,0 +1,2 @@ +* +!scripts diff --git a/.circleci/unittest/linux/docker/.gitignore b/.circleci/unittest/linux/docker/.gitignore new file mode 100644 index 00000000..7e977058 --- /dev/null +++ b/.circleci/unittest/linux/docker/.gitignore @@ -0,0 +1,2 @@ +scripts/build_third_parties.sh +Dockerfile.tmp diff --git a/.circleci/unittest/linux/docker/Dockerfile b/.circleci/unittest/linux/docker/Dockerfile new file mode 100644 index 00000000..c47a8963 --- /dev/null +++ b/.circleci/unittest/linux/docker/Dockerfile @@ -0,0 +1,56 @@ +FROM ubuntu:18.04 as builder + +RUN apt update -q + +################################################################################ +# Build Kaldi +################################################################################ +RUN apt install -q -y \ + autoconf \ + automake \ + bzip2 \ + g++ \ + gfortran \ + git \ + libatlas-base-dev \ + libtool \ + make \ + python2.7 \ + python3 \ + sox \ + subversion \ + unzip \ + wget \ + zlib1g-dev + +# KALDI uses MKL as a default math library, but we are going to copy featbin binaries and dependent +# shared libraries to the final image, so we use ATLAS, which is easy to reinstall in the final image. +RUN git clone --depth 1 https://github.com/kaldi-asr/kaldi.git /opt/kaldi && \ + cd /opt/kaldi/tools && \ + make -j $(nproc) && \ + cd /opt/kaldi/src && \ + ./configure --shared --mathlib=ATLAS --use-cuda=no && \ + make featbin -j $(nproc) + +# Copy featbins and dependent libraries +ADD ./scripts /scripts +RUN bash /scripts/copy_kaldi_executables.sh /opt/kaldi /kaldi + +################################################################################ +# Build the final image +################################################################################ +FROM BASE_IMAGE +RUN apt update && apt install -y \ + g++ \ + gfortran \ + git \ + libatlas3-base \ + libsndfile1 \ + wget \ + curl \ + make \ + file \ + pkg-config \ + && rm -rf /var/lib/apt/lists/* +COPY --from=builder /kaldi /kaldi +ENV PATH="${PATH}:/kaldi/bin" LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:/kaldi/lib" diff --git a/.circleci/unittest/linux/docker/build_and_push.sh b/.circleci/unittest/linux/docker/build_and_push.sh new file mode 100644 index 00000000..e7ced13a --- /dev/null +++ b/.circleci/unittest/linux/docker/build_and_push.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +set -euo pipefail + +if [ $# -ne 1 ]; then + printf "Usage %s \n\n" "$0" + exit 1 +fi + +datestr="$(date "+%Y%m%d")" +if [ "$1" = "cpu" ]; then + base_image="ubuntu:18.04" + image="pytorch/torchaudio_unittest_base:manylinux-${datestr}" +else + base_image="nvidia/cuda:$1-devel-ubuntu18.04" + docker pull "${base_image}" + image="pytorch/torchaudio_unittest_base:manylinux-cuda$1-${datestr}" +fi + +cd "$( dirname "${BASH_SOURCE[0]}" )" + +# docker build also accepts reading from STDIN +# but in that case, no context (other files) can be passed, so we write out Dockerfile +sed "s|BASE_IMAGE|${base_image}|g" Dockerfile > Dockerfile.tmp +docker build -t "${image}" -f Dockerfile.tmp . +docker push "${image}" diff --git a/.circleci/unittest/linux/docker/scripts/copy_kaldi_executables.sh b/.circleci/unittest/linux/docker/scripts/copy_kaldi_executables.sh new file mode 100644 index 00000000..b0cf2071 --- /dev/null +++ b/.circleci/unittest/linux/docker/scripts/copy_kaldi_executables.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash + +list_executables() { + # List up executables in the given directory + find "$1" -type f -executable +} + +list_kaldi_libraries() { + # List up shared libraries used by executables found in the given directory ($1) + # that reside in Kaldi directory ($2) + while read file; do + ldd "${file}" | grep -o "${2}.* "; + done < <(list_executables "$1") | sort -u +} + +set -euo pipefail + +kaldi_root="$(realpath "$1")" +target_dir="$(realpath "$2")" + +bin_dir="${target_dir}/bin" +lib_dir="${target_dir}/lib" + +mkdir -p "${bin_dir}" "${lib_dir}" + +# 1. Copy featbins +printf "Copying executables to %s\n" "${bin_dir}" +while read file; do + printf " %s\n" "${file}" + cp "${file}" "${bin_dir}" +done < <(list_executables "${kaldi_root}/src/featbin") + +# 2. Copy dependent libraries from Kaldi +printf "Copying libraries to %s\n" "${lib_dir}" +while read file; do + printf " %s\n" "$file" + # If it is not symlink, just copy to the target directory + if [ ! -L "${file}" ]; then + cp "${file}" "${lib_dir}" + continue + fi + + # If it is symlink, + # 1. Copy the actual library to the target directory. + library="$(realpath "${file}")" + cp "${library}" "${lib_dir}" + # 2. then if the name of the symlink is different from the actual library name, + # create the symlink in the target directory. + lib_name="$(basename "${library}")" + link_name="$(basename "${file}")" + if [ "${lib_name}" != "${link_name}" ]; then + printf " Linking %s -> %s\n" "${lib_name}" "${link_name}" + ( + cd "${lib_dir}" + ln -sf "${lib_name}" "${link_name}" + ) + fi +done < <(list_kaldi_libraries "${bin_dir}" "${kaldi_root}") diff --git a/.circleci/unittest/linux/scripts/install.sh b/.circleci/unittest/linux/scripts/install.sh new file mode 100644 index 00000000..b7dc9da0 --- /dev/null +++ b/.circleci/unittest/linux/scripts/install.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash + +unset PYTORCH_VERSION +# For unittest, nightly PyTorch is used as the following section, +# so no need to set PYTORCH_VERSION. +# In fact, keeping PYTORCH_VERSION forces us to hardcode PyTorch version in config. + +set -e + +root_dir="$(git rev-parse --show-toplevel)" +conda_dir="${root_dir}/conda" +env_dir="${root_dir}/env" + +cd "${root_dir}" + +case "$(uname -s)" in + Darwin*) os=MacOSX;; + *) os=Linux +esac + +# 0. Activate conda env +eval "$("${conda_dir}/bin/conda" shell.bash hook)" +conda activate "${env_dir}" + +# 1. Install PyTorch +if [ -z "${CUDA_VERSION:-}" ] ; then + if [ "${os}" == MacOSX ] ; then + cudatoolkit='' + else + cudatoolkit="cpuonly" + fi +else + version="$(python -c "print('.'.join(\"${CUDA_VERSION}\".split('.')[:2]))")" + cudatoolkit="cudatoolkit=${version}" +fi +printf "Installing PyTorch with %s\n" "${cudatoolkit}" +( + if [ "${os}" == MacOSX ] ; then + # TODO: this can be removed as soon as linking issue could be resolved + # see https://github.com/pytorch/pytorch/issues/62424 from details + MKL_CONSTRAINT='mkl==2021.2.0' + else + MKL_CONSTRAINT='' + fi + set -x + conda install ${CONDA_CHANNEL_FLAGS:-} -y -c "pytorch-${UPLOAD_CHANNEL}" $MKL_CONSTRAINT "pytorch-${UPLOAD_CHANNEL}::pytorch" ${cudatoolkit} +) + +# 2. Install torchaudio +printf "* Installing torchaudio\n" +git submodule update --init --recursive +python setup.py install + +# 3. Install Test tools +printf "* Installing test tools\n" +NUMBA_DEV_CHANNEL="" +if [[ "$(python --version)" = *3.9* ]]; then + # Numba isn't available for Python 3.9 except on the numba dev channel and building from source fails + # See https://github.com/librosa/librosa/issues/1270#issuecomment-759065048 + NUMBA_DEV_CHANNEL="-c numba/label/dev" +fi +# Note: installing librosa via pip fail because it will try to compile numba. +( + set -x + conda install -y -c conda-forge ${NUMBA_DEV_CHANNEL} 'librosa>=0.8.0' parameterized 'requests>=2.20' + pip install kaldi-io SoundFile coverage pytest pytest-cov scipy transformers expecttest unidecode inflect +) +# Install fairseq +git clone https://github.com/pytorch/fairseq +cd fairseq +git checkout e47a4c8 +pip install . diff --git a/.circleci/unittest/linux/scripts/run_clang_format.py b/.circleci/unittest/linux/scripts/run_clang_format.py new file mode 100644 index 00000000..fd2913bd --- /dev/null +++ b/.circleci/unittest/linux/scripts/run_clang_format.py @@ -0,0 +1,340 @@ +#!/usr/bin/env python +"""A wrapper script around clang-format, suitable for linting multiple files +and to use for continuous integration. + +This is an alternative API for the clang-format command line. +It runs over multiple files and directories in parallel. +A diff output is produced and a sensible exit code is returned. + +""" + +import argparse +import codecs +import difflib +import fnmatch +import io +import multiprocessing +import os +import signal +import subprocess +import sys +import traceback + +from functools import partial + +try: + from subprocess import DEVNULL # py3k +except ImportError: + DEVNULL = open(os.devnull, "wb") + + +DEFAULT_EXTENSIONS = 'c,h,C,H,cpp,hpp,cc,hh,c++,h++,cxx,hxx,cu' + + +class ExitStatus: + SUCCESS = 0 + DIFF = 1 + TROUBLE = 2 + + +def list_files(files, recursive=False, extensions=None, exclude=None): + if extensions is None: + extensions = [] + if exclude is None: + exclude = [] + + out = [] + for file in files: + if recursive and os.path.isdir(file): + for dirpath, dnames, fnames in os.walk(file): + fpaths = [os.path.join(dirpath, fname) for fname in fnames] + for pattern in exclude: + # os.walk() supports trimming down the dnames list + # by modifying it in-place, + # to avoid unnecessary directory listings. + dnames[:] = [ + x for x in dnames + if + not fnmatch.fnmatch(os.path.join(dirpath, x), pattern) + ] + fpaths = [ + x for x in fpaths if not fnmatch.fnmatch(x, pattern) + ] + for f in fpaths: + ext = os.path.splitext(f)[1][1:] + if ext in extensions: + out.append(f) + else: + out.append(file) + return out + + +def make_diff(file, original, reformatted): + return list( + difflib.unified_diff( + original, + reformatted, + fromfile='{}\t(original)'.format(file), + tofile='{}\t(reformatted)'.format(file), + n=3)) + + +class DiffError(Exception): + def __init__(self, message, errs=None): + super(DiffError, self).__init__(message) + self.errs = errs or [] + + +class UnexpectedError(Exception): + def __init__(self, message, exc=None): + super(UnexpectedError, self).__init__(message) + self.formatted_traceback = traceback.format_exc() + self.exc = exc + + +def run_clang_format_diff_wrapper(args, file): + try: + ret = run_clang_format_diff(args, file) + return ret + except DiffError: + raise + except Exception as e: + raise UnexpectedError('{}: {}: {}'.format(file, e.__class__.__name__, + e), e) + + +def run_clang_format_diff(args, file): + try: + with io.open(file, 'r', encoding='utf-8') as f: + original = f.readlines() + except IOError as exc: + raise DiffError(str(exc)) + invocation = [args.clang_format_executable, file] + + # Use of utf-8 to decode the process output. + # + # Hopefully, this is the correct thing to do. + # + # It's done due to the following assumptions (which may be incorrect): + # - clang-format will returns the bytes read from the files as-is, + # without conversion, and it is already assumed that the files use utf-8. + # - if the diagnostics were internationalized, they would use utf-8: + # > Adding Translations to Clang + # > + # > Not possible yet! + # > Diagnostic strings should be written in UTF-8, + # > the client can translate to the relevant code page if needed. + # > Each translation completely replaces the format string + # > for the diagnostic. + # > -- http://clang.llvm.org/docs/InternalsManual.html#internals-diag-translation + + try: + proc = subprocess.Popen( + invocation, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True, + encoding='utf-8') + except OSError as exc: + raise DiffError( + "Command '{}' failed to start: {}".format( + subprocess.list2cmdline(invocation), exc + ) + ) + proc_stdout = proc.stdout + proc_stderr = proc.stderr + + # hopefully the stderr pipe won't get full and block the process + outs = list(proc_stdout.readlines()) + errs = list(proc_stderr.readlines()) + proc.wait() + if proc.returncode: + raise DiffError( + "Command '{}' returned non-zero exit status {}".format( + subprocess.list2cmdline(invocation), proc.returncode + ), + errs, + ) + return make_diff(file, original, outs), errs + + +def bold_red(s): + return '\x1b[1m\x1b[31m' + s + '\x1b[0m' + + +def colorize(diff_lines): + def bold(s): + return '\x1b[1m' + s + '\x1b[0m' + + def cyan(s): + return '\x1b[36m' + s + '\x1b[0m' + + def green(s): + return '\x1b[32m' + s + '\x1b[0m' + + def red(s): + return '\x1b[31m' + s + '\x1b[0m' + + for line in diff_lines: + if line[:4] in ['--- ', '+++ ']: + yield bold(line) + elif line.startswith('@@ '): + yield cyan(line) + elif line.startswith('+'): + yield green(line) + elif line.startswith('-'): + yield red(line) + else: + yield line + + +def print_diff(diff_lines, use_color): + if use_color: + diff_lines = colorize(diff_lines) + sys.stdout.writelines(diff_lines) + + +def print_trouble(prog, message, use_colors): + error_text = 'error:' + if use_colors: + error_text = bold_red(error_text) + print("{}: {} {}".format(prog, error_text, message), file=sys.stderr) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + '--clang-format-executable', + metavar='EXECUTABLE', + help='path to the clang-format executable', + default='clang-format') + parser.add_argument( + '--extensions', + help='comma separated list of file extensions (default: {})'.format( + DEFAULT_EXTENSIONS), + default=DEFAULT_EXTENSIONS) + parser.add_argument( + '-r', + '--recursive', + action='store_true', + help='run recursively over directories') + parser.add_argument('files', metavar='file', nargs='+') + parser.add_argument( + '-q', + '--quiet', + action='store_true') + parser.add_argument( + '-j', + metavar='N', + type=int, + default=0, + help='run N clang-format jobs in parallel' + ' (default number of cpus + 1)') + parser.add_argument( + '--color', + default='auto', + choices=['auto', 'always', 'never'], + help='show colored diff (default: auto)') + parser.add_argument( + '-e', + '--exclude', + metavar='PATTERN', + action='append', + default=[], + help='exclude paths matching the given glob-like pattern(s)' + ' from recursive search') + + args = parser.parse_args() + + # use default signal handling, like diff return SIGINT value on ^C + # https://bugs.python.org/issue14229#msg156446 + signal.signal(signal.SIGINT, signal.SIG_DFL) + try: + signal.SIGPIPE + except AttributeError: + # compatibility, SIGPIPE does not exist on Windows + pass + else: + signal.signal(signal.SIGPIPE, signal.SIG_DFL) + + colored_stdout = False + colored_stderr = False + if args.color == 'always': + colored_stdout = True + colored_stderr = True + elif args.color == 'auto': + colored_stdout = sys.stdout.isatty() + colored_stderr = sys.stderr.isatty() + + version_invocation = [args.clang_format_executable, str("--version")] + try: + subprocess.check_call(version_invocation, stdout=DEVNULL) + except subprocess.CalledProcessError as e: + print_trouble(parser.prog, str(e), use_colors=colored_stderr) + return ExitStatus.TROUBLE + except OSError as e: + print_trouble( + parser.prog, + "Command '{}' failed to start: {}".format( + subprocess.list2cmdline(version_invocation), e + ), + use_colors=colored_stderr, + ) + return ExitStatus.TROUBLE + + retcode = ExitStatus.SUCCESS + files = list_files( + args.files, + recursive=args.recursive, + exclude=args.exclude, + extensions=args.extensions.split(',')) + + if not files: + return + + njobs = args.j + if njobs == 0: + njobs = multiprocessing.cpu_count() + 1 + njobs = min(len(files), njobs) + + if njobs == 1: + # execute directly instead of in a pool, + # less overhead, simpler stacktraces + it = (run_clang_format_diff_wrapper(args, file) for file in files) + pool = None + else: + pool = multiprocessing.Pool(njobs) + it = pool.imap_unordered( + partial(run_clang_format_diff_wrapper, args), files) + while True: + try: + outs, errs = next(it) + except StopIteration: + break + except DiffError as e: + print_trouble(parser.prog, str(e), use_colors=colored_stderr) + retcode = ExitStatus.TROUBLE + sys.stderr.writelines(e.errs) + except UnexpectedError as e: + print_trouble(parser.prog, str(e), use_colors=colored_stderr) + sys.stderr.write(e.formatted_traceback) + retcode = ExitStatus.TROUBLE + # stop at the first unexpected error, + # something could be very wrong, + # don't process all files unnecessarily + if pool: + pool.terminate() + break + else: + sys.stderr.writelines(errs) + if outs == []: + continue + if not args.quiet: + print_diff(outs, use_color=colored_stdout) + if retcode == ExitStatus.SUCCESS: + retcode = ExitStatus.DIFF + return retcode + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/.circleci/unittest/linux/scripts/run_style_checks.sh b/.circleci/unittest/linux/scripts/run_style_checks.sh new file mode 100644 index 00000000..b1ef0f1e --- /dev/null +++ b/.circleci/unittest/linux/scripts/run_style_checks.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +set -eux + +root_dir="$(git rev-parse --show-toplevel)" +conda_dir="${root_dir}/conda" +env_dir="${root_dir}/env" +this_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +eval "$("${conda_dir}/bin/conda" shell.bash hook)" +conda activate "${env_dir}" + +# 1. Install tools +conda install flake8 +printf "Installed flake8: " +flake8 --version + +clangformat_path="${root_dir}/clang-format" +curl https://oss-clang-format.s3.us-east-2.amazonaws.com/linux64/clang-format-linux64 -o "${clangformat_path}" +chmod +x "${clangformat_path}" +printf "Installed clang-fortmat" +"${clangformat_path}" --version + +# 2. Run style checks +# We want to run all the style checks even if one of them fail. + +set +e + +exit_status=0 + +printf "\x1b[34mRunning flake8:\x1b[0m\n" +flake8 torchaudio test build_tools/setup_helpers docs/source/conf.py examples +status=$? +exit_status="$((exit_status+status))" +if [ "${status}" -ne 0 ]; then + printf "\x1b[31mflake8 failed. Check the format of Python files.\x1b[0m\n" +fi + +printf "\x1b[34mRunning clang-format:\x1b[0m\n" +"${this_dir}"/run_clang_format.py \ + -r torchaudio/csrc third_party/kaldi/src \ + --clang-format-executable "${clangformat_path}" \ + && git diff --exit-code +status=$? +exit_status="$((exit_status+status))" +if [ "${status}" -ne 0 ]; then + printf "\x1b[31mC++ files are not formatted. Please use clang-format to format CPP files.\x1b[0m\n" +fi +exit $exit_status diff --git a/.circleci/unittest/linux/scripts/run_test.sh b/.circleci/unittest/linux/scripts/run_test.sh new file mode 100644 index 00000000..4e8d2474 --- /dev/null +++ b/.circleci/unittest/linux/scripts/run_test.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +set -e + +eval "$(./conda/bin/conda shell.bash hook)" +conda activate ./env + +case "$(uname -s)" in + Darwin*) os=MacOSX;; + *) os=Linux +esac + +python -m torch.utils.collect_env + +export TORCHAUDIO_TEST_FAIL_IF_NO_EXTENSION=1 +export PATH="${PWD}/third_party/install/bin/:${PATH}" + +declare -a args=( + '-v' + '--cov=torchaudio' + "--junitxml=${PWD}/test-results/junit.xml" + '--durations' '20' +) + +cd test +pytest "${args[@]}" torchaudio_unittest +coverage html diff --git a/.circleci/unittest/linux/scripts/setup_env.sh b/.circleci/unittest/linux/scripts/setup_env.sh new file mode 100644 index 00000000..085dc61a --- /dev/null +++ b/.circleci/unittest/linux/scripts/setup_env.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +# This script is for setting up environment in which unit test is ran. +# To speed up the CI time, the resulting environment is cached. +# +# Do not install PyTorch and torchaudio here, otherwise they also get cached. + +set -ex + +root_dir="$(git rev-parse --show-toplevel)" +conda_dir="${root_dir}/conda" +env_dir="${root_dir}/env" + +cd "${root_dir}" + +case "$(uname -s)" in + Darwin*) os=MacOSX;; + *) os=Linux +esac + +# 1. Install conda at ./conda +if [ ! -d "${conda_dir}" ]; then + printf "* Installing conda\n" + wget --quiet -O miniconda.sh "http://repo.continuum.io/miniconda/Miniconda3-latest-${os}-x86_64.sh" + bash ./miniconda.sh -b -f -p "${conda_dir}" + eval "$("${conda_dir}/bin/conda" shell.bash hook)" + conda update --quiet -y conda + printf "* Updating the base Python version to %s\n" "${PYTHON_VERSION}" + conda install --quiet -y python="${PYTHON_VERSION}" +else + eval "$("${conda_dir}/bin/conda" shell.bash hook)" +fi + + +# 2. Create test environment at ./env +if [ ! -d "${env_dir}" ]; then + printf "* Creating a test environment with PYTHON_VERSION=%s\n" "${PYTHON_VERSION}\n" + conda create --prefix "${env_dir}" -y python="${PYTHON_VERSION}" +fi +conda activate "${env_dir}" + +# 3. Install minimal build tools +pip --quiet install cmake ninja diff --git a/.circleci/unittest/windows/README.md b/.circleci/unittest/windows/README.md new file mode 100644 index 00000000..2c06af62 --- /dev/null +++ b/.circleci/unittest/windows/README.md @@ -0,0 +1,4 @@ +This directory contains; + + - scripts + Scripts used by CircleCI to run unit tests. diff --git a/.circleci/unittest/windows/scripts/environment.yml b/.circleci/unittest/windows/scripts/environment.yml new file mode 100644 index 00000000..16225c9e --- /dev/null +++ b/.circleci/unittest/windows/scripts/environment.yml @@ -0,0 +1,16 @@ +channels: + - defaults +dependencies: + - flake8 + - pytest + - pytest-cov + - codecov + - scipy >= 1.4.1 + - pip + - pip: + - kaldi-io + - PySoundFile + - future + - parameterized + - dataclasses + - expecttest diff --git a/.circleci/unittest/windows/scripts/install.sh b/.circleci/unittest/windows/scripts/install.sh new file mode 100644 index 00000000..f99f46aa --- /dev/null +++ b/.circleci/unittest/windows/scripts/install.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash + +unset PYTORCH_VERSION +# For unittest, nightly PyTorch is used as the following section, +# so no need to set PYTORCH_VERSION. +# In fact, keeping PYTORCH_VERSION forces us to hardcode PyTorch version in config. + +set -ex + +root_dir="$(git rev-parse --show-toplevel)" +conda_dir="${root_dir}/conda" +env_dir="${root_dir}/env" +this_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +cd "${root_dir}" + +# 0. Activate conda env +eval "$("${conda_dir}/Scripts/conda.exe" 'shell.bash' 'hook')" +conda activate "${env_dir}" + +source "$this_dir/set_cuda_envs.sh" + +# 1. Install PyTorch +if [ -z "${CUDA_VERSION:-}" ] ; then + cudatoolkit="cpuonly" +else + version="$(python -c "print('.'.join(\"${CUDA_VERSION}\".split('.')[:2]))")" + cudatoolkit="cudatoolkit=${version}" +fi +printf "Installing PyTorch with %s\n" "${cudatoolkit}" +conda install -y -c "pytorch-${UPLOAD_CHANNEL}" -c conda-forge "pytorch-${UPLOAD_CHANNEL}"::pytorch "${cudatoolkit}" pytest + +torch_cuda=$(python -c "import torch; print(torch.cuda.is_available())") +echo torch.cuda.is_available is $torch_cuda + +if [ ! -z "${CUDA_VERSION:-}" ] ; then + if [ "$torch_cuda" == "False" ]; then + echo "torch with cuda installed but torch.cuda.is_available() is False" + exit 1 + fi +fi + +# 2. Install torchaudio +printf "* Installing torchaudio\n" +git submodule update --init --recursive +"$root_dir/packaging/vc_env_helper.bat" python setup.py install + +# 3. Install Test tools +printf "* Installing test tools\n" +NUMBA_DEV_CHANNEL="" +if [[ "$(python --version)" = *3.9* ]]; then + # Numba isn't available for Python 3.9 except on the numba dev channel and building from source fails + # See https://github.com/librosa/librosa/issues/1270#issuecomment-759065048 + NUMBA_DEV_CHANNEL="-c numba/label/dev" +fi +# Note: installing librosa via pip fail because it will try to compile numba. +( + set -x + conda install -y -c conda-forge ${NUMBA_DEV_CHANNEL} 'librosa>=0.8.0' parameterized 'requests>=2.20' + pip install kaldi-io SoundFile coverage pytest pytest-cov scipy transformers expecttest unidecode inflect +) +# Install fairseq +git clone https://github.com/pytorch/fairseq +cd fairseq +git checkout e47a4c8 +pip install . diff --git a/.circleci/unittest/windows/scripts/install_conda.bat b/.circleci/unittest/windows/scripts/install_conda.bat new file mode 100644 index 00000000..6052ad08 --- /dev/null +++ b/.circleci/unittest/windows/scripts/install_conda.bat @@ -0,0 +1 @@ +start /wait "" "%miniconda_exe%" /S /InstallationType=JustMe /RegisterPython=0 /AddToPath=0 /D=%tmp_conda% diff --git a/.circleci/unittest/windows/scripts/run_test.sh b/.circleci/unittest/windows/scripts/run_test.sh new file mode 100644 index 00000000..f5ec80e0 --- /dev/null +++ b/.circleci/unittest/windows/scripts/run_test.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +set -ex + +eval "$(./conda/Scripts/conda.exe 'shell.bash' 'hook')" +conda activate ./env + +this_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +source "$this_dir/set_cuda_envs.sh" + +python -m torch.utils.collect_env +cd test +pytest --cov=torchaudio --junitxml=../test-results/junit.xml -v --durations 20 torchaudio_unittest +coverage html diff --git a/.circleci/unittest/windows/scripts/set_cuda_envs.sh b/.circleci/unittest/windows/scripts/set_cuda_envs.sh new file mode 100644 index 00000000..37b53d02 --- /dev/null +++ b/.circleci/unittest/windows/scripts/set_cuda_envs.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +set -ex + +echo CU_VERSION is "${CU_VERSION}" +echo CUDA_VERSION is "${CUDA_VERSION}" + +# Currenly, CU_VERSION and CUDA_VERSION are not consistent. +# to understand this code, please checck out https://github.com/pytorch/vision/issues/4443 +version="cpu" +if [[ ! -z "${CUDA_VERSION}" ]] ; then + version="$CUDA_VERSION" +else + if [[ ${#CU_VERSION} -eq 5 ]]; then + version="${CU_VERSION:2:2}.${CU_VERSION:4:1}" + fi +fi + +# Don't use if [[ "$version" == "cpu" ]]; then exit 0 fi. +# It would exit the shell. One result is cpu tests would not run if the shell exit. +# Unless there's an error, Don't exit. +if [[ "$version" != "cpu" ]]; then + # set cuda envs + export PATH="/c/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v${version}/bin:/c/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v${version}/libnvvp:$PATH" + export CUDA_PATH_V${version/./_}="C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v${version}" + export CUDA_PATH="C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v${version}" + + if [ ! -d "$CUDA_PATH" ] + then + echo "$CUDA_PATH" does not exist + exit 1 + fi + + # check cuda driver version + for path in '/c/Program Files/NVIDIA Corporation/NVSMI/nvidia-smi.exe' /c/Windows/System32/nvidia-smi.exe; do + if [[ -x "$path" ]]; then + "$path" || echo "true"; + break + fi + done + + which nvcc + nvcc --version + env | grep CUDA +fi diff --git a/.circleci/unittest/windows/scripts/setup_env.sh b/.circleci/unittest/windows/scripts/setup_env.sh new file mode 100644 index 00000000..5f092bfc --- /dev/null +++ b/.circleci/unittest/windows/scripts/setup_env.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +# This script is for setting up environment in which unit test is ran. +# To speed up the CI time, the resulting environment is cached. +# +# Do not install PyTorch and torchaudio here, otherwise they also get cached. + +set -e + +this_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +root_dir="$(git rev-parse --show-toplevel)" +conda_dir="${root_dir}/conda" +env_dir="${root_dir}/env" + +cd "${root_dir}" + +# 1. Install conda at ./conda +if [ ! -d "${conda_dir}" ]; then + printf "* Installing conda\n" + export tmp_conda="$(echo $conda_dir | tr '/' '\\')" + export miniconda_exe="$(echo $root_dir | tr '/' '\\')\\miniconda.exe" + curl --silent --output miniconda.exe https://repo.anaconda.com/miniconda/Miniconda3-latest-Windows-x86_64.exe -O + "$this_dir/install_conda.bat" + unset tmp_conda + unset miniconda_exe + eval "$("${conda_dir}/Scripts/conda.exe" 'shell.bash' 'hook')" + conda update --quiet -y conda + printf "* Updating the base Python version to %s\n" "${PYTHON_VERSION}" + conda install --quiet -y python="$PYTHON_VERSION" +else + eval "$("${conda_dir}/Scripts/conda.exe" 'shell.bash' 'hook')" +fi + +# 2. Create test environment at ./env +if [ ! -d "${env_dir}" ]; then + printf "* Creating a test environment with PYTHON_VERSION=%s\n" "${PYTHON_VERSION}" + conda create --prefix "${env_dir}" -y python="${PYTHON_VERSION}" +fi +conda activate "${env_dir}" + +# 3. Install minimal build tools +pip --quiet install cmake ninja diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..73304266 --- /dev/null +++ b/.clang-format @@ -0,0 +1,88 @@ +--- +AccessModifierOffset: -1 +AlignAfterOpenBracket: AlwaysBreak +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlinesLeft: true +AlignOperands: false +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: true +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: false +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ForEachMacros: [ FOR_EACH_RANGE, FOR_EACH, ] +IncludeCategories: + - Regex: '^<.*\.h(pp)?>' + Priority: 1 + - Regex: '^<.*' + Priority: 2 + - Regex: '.*' + Priority: 3 +IndentCaseLabels: true +IndentWidth: 2 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: false +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 2000000 +PointerAlignment: Left +ReflowComments: true +SortIncludes: true +SpaceAfterCStyleCast: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 8 +UseTab: Never +... diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 00000000..e2d7eb38 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,36 @@ +--- +# NOTE there must be no spaces before the '-' and check name. +# If you edit this list, please verify list of enabled check with +# clang-tidy --list-checks +InheritParentConfig: true +Checks: ' +bugprone-*, +-bugprone-forward-declaration-namespace, +-bugprone-macro-parentheses, +-clang-analyzer-*, +cppcoreguidelines-*, +-cppcoreguidelines-interfaces-global-init, +-cppcoreguidelines-owning-memory, +-cppcoreguidelines-pro-bounds-array-to-pointer-decay, +-cppcoreguidelines-pro-bounds-constant-array-index, +-cppcoreguidelines-pro-bounds-pointer-arithmetic, +-cppcoreguidelines-pro-type-cstyle-cast, +-cppcoreguidelines-pro-type-reinterpret-cast, +-cppcoreguidelines-pro-type-static-cast-downcast, +-cppcoreguidelines-pro-type-union-access, +-cppcoreguidelines-pro-type-vararg, +-cppcoreguidelines-special-member-functions, +-facebook-hte-RelativeInclude, +hicpp-exception-baseclass, +hicpp-avoid-goto, +modernize-*, +-modernize-return-braced-init-list, +-modernize-use-auto, +-modernize-use-default-member-init, +-modernize-use-using, +performance-unnecessary-value-param, +' +HeaderFilterRegex: 'torchaudio/.*' +AnalyzeTemporaryDtors: false +CheckOptions: +... diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000..f344aec8 --- /dev/null +++ b/.flake8 @@ -0,0 +1,4 @@ +[flake8] +max-line-length = 120 +ignore = E305,E402,E721,E741,F405,W503,W504,F999 +exclude = build,docs/source,_ext,third_party diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..fb21d618 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# To exclude autogenerated files from code reviews +.circleci/config.yml linguist-generated=true diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 00000000..fb50bd85 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,53 @@ +--- +name: "\U0001F41B Bug Report" +about: Submit a bug report to help us improve Torchaudio + +--- + +## 🐛 Bug + + + +## To Reproduce + +Steps to reproduce the behavior: + +1. +1. +1. + + + +## Expected behavior + + + +## Environment + + - What commands did you used to install torchaudio (conda/pip/build from source)? + - If you are building from source, which commit is it? + - What does `torchaudio.__version__` print? (If applicable) + +Please copy and paste the output from our +[environment collection script](https://raw.githubusercontent.com/pytorch/pytorch/master/torch/utils/collect_env.py) +(or fill out the checklist below manually). + +You can get the script and run it with: +``` +wget https://raw.githubusercontent.com/pytorch/pytorch/master/torch/utils/collect_env.py +# For security purposes, please check the contents of collect_env.py before running it. +python collect_env.py +``` + + - PyTorch Version (e.g., 1.0): + - OS (e.g., Linux): + - How you installed PyTorch (`conda`, `pip`, source): + - Build command you used (if compiling from source): + - Python version: + - CUDA/cuDNN version: + - GPU models and configuration: + - Any other relevant information: + +## Additional context + + diff --git a/.github/ISSUE_TEMPLATE/documentation.md b/.github/ISSUE_TEMPLATE/documentation.md new file mode 100644 index 00000000..ae745121 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation.md @@ -0,0 +1,9 @@ +--- +name: "\U0001F4DA Documentation" +about: Report an issue related to https://pytorch.org/audio + +--- + +## 📚 Documentation + + diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md new file mode 100644 index 00000000..f3896e53 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -0,0 +1,24 @@ +--- +name: "\U0001F680Feature Request" +about: Submit a proposal/request for a new Torchaudio feature + +--- + +## 🚀 Feature + + +## Motivation + + + +## Pitch + + + +## Alternatives + + + +## Additional context + + diff --git a/.github/ISSUE_TEMPLATE/questions-help-support.md b/.github/ISSUE_TEMPLATE/questions-help-support.md new file mode 100644 index 00000000..77bfb55b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/questions-help-support.md @@ -0,0 +1,13 @@ +--- +name: "❓Questions/Help/Support" +about: Do you need support? We have resources. + +--- + +## ❓ Questions and Help + +### Please note that this issue tracker is not a help form and this issue will be closed. + +We have a set of [listed resources available on the website](https://pytorch.org/resources). Our primary means of support is our discussion forum: + +- [Discussion Forum](https://discuss.pytorch.org/) diff --git a/.github/pytorch-probot.yml b/.github/pytorch-probot.yml new file mode 100644 index 00000000..d9daca0f --- /dev/null +++ b/.github/pytorch-probot.yml @@ -0,0 +1 @@ +tracking_issue: 736 diff --git a/.github/workflows/bandit.yml b/.github/workflows/bandit.yml new file mode 100644 index 00000000..84200b43 --- /dev/null +++ b/.github/workflows/bandit.yml @@ -0,0 +1,23 @@ +# GitHub Actions Bandit Workflow + +name: Bandit + +on: + pull_request: + branches: [ main ] + + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + # Task will fail if any high-severity issues are found + # Ignoring submodules + - name: Run Bandit Security Analysis + run: | + python -m pip install bandit + python -m bandit -r . -x ./third_party -lll diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000..cf535862 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,37 @@ +# GitHub Actions CodeQL Workflow + +name: CodeQL + +on: + pull_request: + branches: [ main ] + + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: python, cpp + + - name: Update submodules + run: git submodule update --init --recursive + + - name: Install Torch + run: | + python -m pip install cmake ninja + python -m pip install --pre torch -f https://download.pytorch.org/whl/nightly/cpu/torch_nightly.html + + - name: Build TorchAudio + run: USE_CUDA=0 python setup.py develop --user + + # If any code scanning alerts are found, they will be under Security -> CodeQL + # Link: https://github.com/pytorch/audio/security/code-scanning + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..ae628691 --- /dev/null +++ b/.gitignore @@ -0,0 +1,127 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# temp files +~* +*.swp + +# C extensions / folders +*.so +_ext/ + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ +docs/src/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints/ + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# dotenv +.env + +# virtualenv +.venv +venv/ +ENV/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# PyCharm project settings +.idea + +# OSX dir files +.DS_Store + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +# Generated Files +test/assets/sinewave.wav +torchaudio/version.py +gen.yml + +# Examples +examples/interactive_asr/data/*.txt +examples/interactive_asr/data/*.model +examples/interactive_asr/data/*.pt + +# third parties +third_party/install/ +third_party/sox/archives/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..72484612 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "kaldi"] + path = third_party/kaldi/submodule + url = https://github.com/kaldi-asr/kaldi + ignore = dirty diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..5e2555a2 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,129 @@ +cmake_minimum_required(VERSION 3.18 FATAL_ERROR) + +# Most of the configurations are taken from PyTorch +# https://github.com/pytorch/pytorch/blob/0c9fb4aff0d60eaadb04e4d5d099fb1e1d5701a9/CMakeLists.txt + +# 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(torchaudio) + + +# 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." + "PyTorch requires -std=c++14. Please remove -std=c++ settings in your environment.") +endif() + +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_C_STANDARD 11) + +# https://developercommunity.visualstudio.com/t/VS-16100-isnt-compatible-with-CUDA-11/1433342 +if(MSVC) + if(USE_CUDA) + set(CMAKE_CXX_STANDARD 17) + endif() +endif() + + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +# Apple specific +if(APPLE) + # Get clang version on macOS + execute_process( COMMAND ${CMAKE_CXX_COMPILER} --version OUTPUT_VARIABLE clang_full_version_string ) + string(REGEX REPLACE "Apple LLVM version ([0-9]+\\.[0-9]+).*" "\\1" CLANG_VERSION_STRING ${clang_full_version_string}) + message( STATUS "CLANG_VERSION_STRING: " ${CLANG_VERSION_STRING} ) + + # RPATH stuff + set(CMAKE_MACOSX_RPATH ON) + + set(CMAKE_SHARED_LIBRARY_SUFFIX ".so") +endif() + + +# Options +option(BUILD_SOX "Build libsox statically" ON) +option(BUILD_KALDI "Build kaldi statically" ON) +option(BUILD_RNNT "Enable RNN transducer" ON) +option(BUILD_TORCHAUDIO_PYTHON_EXTENSION "Build Python extension" OFF) +option(USE_CUDA "Enable CUDA support" OFF) +option(USE_ROCM "Enable ROCM support" OFF) + + +# check that USE_CUDA and USE_ROCM are not set at the same time +if(USE_CUDA AND USE_ROCM) + message(FATAL "CUDA and ROCm are mutually exclusive") +endif() + +if(USE_ROCM) + # Find the HIP package, set the HIP paths, load the HIP CMake. + include(cmake/LoadHIP.cmake) + if(NOT PYTORCH_FOUND_HIP) + set(USE_ROCM OFF) + endif() +endif() + +if(USE_CUDA) + enable_language(CUDA) +endif() + +find_package(Torch REQUIRED) + +# https://github.com/pytorch/pytorch/issues/54174 +function(CUDA_CONVERT_FLAGS EXISTING_TARGET) + get_property(old_flags TARGET ${EXISTING_TARGET} PROPERTY INTERFACE_COMPILE_OPTIONS) + if(NOT "${old_flags}" STREQUAL "") + string(REPLACE ";" "," CUDA_flags "${old_flags}") + set_property(TARGET ${EXISTING_TARGET} PROPERTY INTERFACE_COMPILE_OPTIONS + "$<$>:${old_flags}>$<$>:-Xcompiler=${CUDA_flags}>" + ) + endif() +endfunction() + +if(MSVC) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4819") + if(USE_CUDA) + set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -Xcompiler=/wd4819") + foreach(diag cc_clobber_ignored integer_sign_change useless_using_declaration + set_but_not_used field_without_dll_interface + base_class_has_different_dll_interface + dll_interface_conflict_none_assumed + dll_interface_conflict_dllexport_assumed + implicit_return_from_non_void_function + unsigned_compare_with_zero + declared_but_not_referenced + bad_friend_decl) + string(APPEND CMAKE_CUDA_FLAGS " -Xcudafe --diag_suppress=${diag}") + endforeach() + CUDA_CONVERT_FLAGS(torch_cpu) + if(TARGET torch_cuda) + CUDA_CONVERT_FLAGS(torch_cuda) + endif() + if(TARGET torch_cuda_cu) + CUDA_CONVERT_FLAGS(torch_cuda_cu) + endif() + if(TARGET torch_cuda_cpp) + CUDA_CONVERT_FLAGS(torch_cuda_cpp) + endif() + endif() +endif() + +# TORCH_CXX_FLAGS contains the same -D_GLIBCXX_USE_CXX11_ABI value as PyTorch +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall ${TORCH_CXX_FLAGS}") + +add_subdirectory(third_party) +add_subdirectory(torchaudio/csrc) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..b91e23b1 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to make participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or +advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic +address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a +professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies within all project spaces, and it also applies when +an individual is representing the project or its community in public spaces. +Examples of representing a project or community include using an official +project e-mail address, posting via an official social media account, or acting +as an appointed representative at an online or offline event. Representation of +a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at . All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..897e9907 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,174 @@ +# Contributing to Torchaudio +We want to make contributing to this project as easy and transparent as possible. + +## TL;DR + +Please let us know if you encounter a bug by filing an [issue](https://github.com/pytorch/audio/issues). + +We appreciate all contributions. If you are planning to contribute back +bug-fixes, please do so without any further discussion. + +If you plan to contribute new features, utility functions or extensions to the +core, please first open an issue and discuss the feature with us. Sending a PR +without discussion might end up resulting in a rejected PR, because we might be +taking the core in a different direction than you might be aware of. + +Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the +safe disclosure of security bugs. In those cases, please go through the +process outlined on that page and do not file a public issue. + +Fixing bugs and implementing new features are not the only way you can +contribute. It also helps the project when you report problems you're facing, +and when you give a :+1: on issues that others reported and that are relevant +to you. + +You can also help by improving the documentation. This is no less important +than improving the library itself! If you find a typo in the documentation, +do not hesitate to submit a pull request. + +If you're not sure what you want to work on, you can pick an issue from the +[list of open issues labelled as "help +wanted"](https://github.com/pytorch/audio/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22). +Comment on the issue that you want to work on it and send a PR with your fix +(see below). + +## Contributor License Agreement ("CLA") +In order to accept your pull request, we need you to submit a CLA. You only need +to do this once to work on any of Facebook's open source projects. + +Complete your CLA here: + +## Development installation + +We recommend using a `conda` environment to contribute efficiently to +torchaudio. + +### Install PyTorch Nightly + +```bash +conda install pytorch -c pytorch-nightly +``` + +### Install Torchaudio + +```bash +# Install build-time dependencies +pip install cmake ninja pkgconfig +``` + +```bash +# Build torchaudio +git clone https://github.com/pytorch/audio.git +cd audio +git submodule update --init --recursive +python setup.py develop +# or, for OSX +# MACOSX_DEPLOYMENT_TARGET=10.9 CC=clang CXX=clang++ python setup.py develop +``` + +Some environmnet variables that change the build behavior +- `BUILD_SOX`: Deteremines whether build and bind libsox in non-Windows environments. (no effect in Windows as libsox integration is not available) Default value is 1 (build and bind). Use 0 for disabling it. +- `USE_CUDA`: Determines whether build the custom CUDA kernel. Default to the availability of CUDA-compatible GPUs. + +If you built sox, set the `PATH` variable so that the tests properly use the newly built `sox` binary: + +```bash +export PATH="/third_party/install/bin:${PATH}" +``` + +The following dependencies are also needed for testing: + +```bash +pip install typing pytest scipy numpy parameterized +``` + +Optional packages to install if you want to run related tests: + +- `librosa` +- `requests` +- `soundfile` +- `kaldi_io` +- `transformers` +- `fairseq` (it has to be newer than `0.10.2`, so you will need to install from + source. Commit `e6eddd80` is known to work.) +- `unidecode` (dependency for testing text preprocessing functions for examples/pipeline_tacotron2) +- `inflect` (dependency for testing text preprocessing functions for examples/pipeline_tacotron2) + +## Development Process + +If you plan to modify the code or documentation, please follow the steps below: + +1. Fork the repository and create your branch from `main`: `$ git checkout main && git checkout -b my_cool_feature` +2. If you have modified the code (new feature or bug-fix), [please add tests](test/torchaudio_unittest/). +3. If you have changed APIs, [update the documentation](#Documentation). + +For more details about pull requests, +please read [GitHub's guides](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request). + +If you would like to contribute a new model, please see [here](#New-model). + +If you would like to contribute a new dataset, please see [here](#New-dataset). + +## Testing + +Please refer to our [testing guidelines](test/torchaudio_unittest/) for more +details. + +## Documentation + +Torchaudio uses [Google style](http://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html) +for formatting docstrings. Length of line inside docstrings block must be limited to 120 characters. + +To build the docs, first install the requirements: + +```bash +cd docs +pip install -r requirements.txt +``` + +Then: + +```bash +cd docs +make html +``` + +The built docs should now be available in `docs/build/html` + +## Conventions + +As a good software development practice, we try to stick to existing variable +names and shape (for tensors). +The following are some of the conventions that we follow. + +- We use an ellipsis "..." as a placeholder for the rest of the dimensions of a + tensor, e.g. optional batching and channel dimensions. If batching, the + "batch" dimension should come in the first diemension. +- Tensors are assumed to have "channel" dimension coming before the "time" + dimension. The bins in frequency domain (freq and mel) are assumed to come + before the "time" dimension but after the "channel" dimension. These + ordering makes the tensors consistent with PyTorch's dimensions. +- For size names, the prefix `n_` is used (e.g. "a tensor of size (`n_freq`, + `n_mels`)") whereas dimension names do not have this prefix (e.g. "a tensor of + dimension (channel, time)") + +Here are some of the examples of commonly used variables with thier names, +meanings, and shapes (or units): + +* `waveform`: a tensor of audio samples with dimensions (..., channel, time) +* `sample_rate`: the rate of audio dimensions (samples per second) +* `specgram`: a tensor of spectrogram with dimensions (..., channel, freq, time) +* `mel_specgram`: a mel spectrogram with dimensions (..., channel, mel, time) +* `hop_length`: the number of samples between the starts of consecutive frames +* `n_fft`: the number of Fourier bins +* `n_mels`, `n_mfcc`: the number of mel and MFCC bins +* `n_freq`: the number of bins in a linear spectrogram +* `f_min`: the lowest frequency of the lowest band in a spectrogram +* `f_max`: the highest frequency of the highest band in a spectrogram +* `win_length`: the length of the STFT window +* `window_fn`: for functions that creates windows e.g. `torch.hann_window` + +## License + +By contributing to Torchaudio, you agree that your contributions will be licensed +under the LICENSE file in the root directory of this source tree. diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..1bec23ea --- /dev/null +++ b/LICENSE @@ -0,0 +1,25 @@ +BSD 2-Clause License + +Copyright (c) 2017 Facebook Inc. (Soumith Chintala), +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index fa6bbe4a..c3072ec5 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,152 @@ -# Torchaudio +torchaudio: an audio library for PyTorch +======================================== +[![Build Status](https://circleci.com/gh/pytorch/audio.svg?style=svg)](https://app.circleci.com/pipelines/github/pytorch/audio) +[![Documentation](https://img.shields.io/badge/dynamic/json.svg?label=docs&url=https%3A%2F%2Fpypi.org%2Fpypi%2Ftorchaudio%2Fjson&query=%24.info.version&colorB=brightgreen&prefix=v)](https://pytorch.org/audio/) + +The aim of torchaudio is to apply [PyTorch](https://github.com/pytorch/pytorch) to +the audio domain. By supporting PyTorch, torchaudio follows the same philosophy +of providing strong GPU acceleration, having a focus on trainable features through +the autograd system, and having consistent style (tensor names and dimension names). +Therefore, it is primarily a machine learning library and not a general signal +processing library. The benefits of PyTorch can be seen in torchaudio through +having all the computations be through PyTorch operations which makes it easy +to use and feel like a natural extension. + +- [Support audio I/O (Load files, Save files)](http://pytorch.org/audio/stable/) + - Load a variety of audio formats, such as `wav`, `mp3`, `ogg`, `flac`, `opus`, `sphere`, into a torch Tensor using SoX + - [Kaldi (ark/scp)](http://pytorch.org/audio/stable/kaldi_io.html) +- [Dataloaders for common audio datasets](http://pytorch.org/audio/stable/datasets.html) +- Common audio transforms + - [Spectrogram, AmplitudeToDB, MelScale, MelSpectrogram, MFCC, MuLawEncoding, MuLawDecoding, Resample](http://pytorch.org/audio/stable/transforms.html) +- Compliance interfaces: Run code using PyTorch that align with other libraries + - [Kaldi: spectrogram, fbank, mfcc](https://pytorch.org/audio/stable/compliance.kaldi.html) + +Dependencies +------------ +* PyTorch (See below for the compatible versions) +* [optional] vesis84/kaldi-io-for-python commit cb46cb1f44318a5d04d4941cf39084c5b021241e or above + +The following are the corresponding ``torchaudio`` versions and supported Python versions. + +| ``torch`` | ``torchaudio`` | ``python`` | +| ------------------------ | ------------------------ | ------------------------------- | +| ``master`` / ``nightly`` | ``main`` / ``nightly`` | ``>=3.6``, ``<=3.9`` | +| ``1.9.0`` | ``0.9.0`` | ``>=3.6``, ``<=3.9`` | +| ``1.8.0`` | ``0.8.0`` | ``>=3.6``, ``<=3.9`` | +| ``1.7.1`` | ``0.7.2`` | ``>=3.6``, ``<=3.9`` | +| ``1.7.0`` | ``0.7.0`` | ``>=3.6``, ``<=3.8`` | +| ``1.6.0`` | ``0.6.0`` | ``>=3.6``, ``<=3.8`` | +| ``1.5.0`` | ``0.5.0`` | ``>=3.5``, ``<=3.8`` | +| ``1.4.0`` | ``0.4.0`` | ``==2.7``, ``>=3.5``, ``<=3.8`` | + + +Installation +------------ + +### Binary Distributions + +To install the latest version using anaconda, run: + +``` +conda install -c pytorch torchaudio +``` + +To install the latest pip wheels, run: + +``` +pip install torchaudio -f https://download.pytorch.org/whl/torch_stable.html +``` + +(If you do not have torch already installed, this will default to installing +torch from PyPI. If you need a different torch configuration, preinstall torch +before running this command.) + +### Nightly build + +Note that nightly build is built on PyTorch's nightly build. Therefore, you need to install the latest PyTorch when you use nightly build of torchaudio. + +**pip** + +``` +pip install --pre torchaudio -f https://download.pytorch.org/whl/nightly/torch_nightly.html +``` + +**conda** + +``` +conda install -y -c pytorch-nightly torchaudio +``` + +### From Source + +On non-Windows platforms, the build process builds libsox and codecs that torchaudio need to link to. It will fetch and build libmad, lame, flac, vorbis, opus, and libsox before building extension. This process requires `cmake` and `pkg-config`. libsox-based features can be disabled with `BUILD_SOX=0`. +The build process also builds the RNN transducer loss. This functionality can be disabled by setting the environment variable `BUILD_RNNT=0`. + +```bash +# Linux +python setup.py install + +# OSX +MACOSX_DEPLOYMENT_TARGET=10.9 CC=clang CXX=clang++ python setup.py install + +# Windows +# We need to use the MSVC x64 toolset for compilation, with Visual Studio's vcvarsall.bat or directly with vcvars64.bat. +# These batch files are under Visual Studio's installation folder, under 'VC\Auxiliary\Build\'. +# More information available at: +# https://docs.microsoft.com/en-us/cpp/build/how-to-enable-a-64-bit-visual-cpp-toolset-on-the-command-line?view=msvc-160#use-vcvarsallbat-to-set-a-64-bit-hosted-build-architecture +call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" x64 && set BUILD_SOX=0 && python setup.py install +# or +call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat" && set BUILD_SOX=0 && python setup.py install +``` + +This is known to work on linux and unix distributions such as Ubuntu and CentOS 7 and macOS. +If you try this on a new system and find a solution to make it work, feel free to share it by opening an issue. + +Quick Usage +----------- + +```python +import torchaudio + +waveform, sample_rate = torchaudio.load('foo.wav') # load tensor from file +torchaudio.save('foo_save.wav', waveform, sample_rate) # save tensor to file +``` + +Backend Dispatch +---------------- + +By default in OSX and Linux, torchaudio uses SoX as a backend to load and save files. +The backend can be changed to [SoundFile](https://pysoundfile.readthedocs.io/en/latest/) +using the following. See [SoundFile](https://pysoundfile.readthedocs.io/en/latest/) +for installation instructions. + +```python +import torchaudio +torchaudio.set_audio_backend("soundfile") # switch backend + +waveform, sample_rate = torchaudio.load('foo.wav') # load tensor from file, as usual +torchaudio.save('foo_save.wav', waveform, sample_rate) # save tensor to file, as usual +``` + +**Note** +- SoundFile currently does not support mp3. +- "soundfile" backend is not supported by TorchScript. + +API Reference +------------- + +API Reference is located here: http://pytorch.org/audio/ + +Contributing Guidelines +----------------------- + +Please refer to [CONTRIBUTING.md](./CONTRIBUTING.md) + +Disclaimer on Datasets +---------------------- + +This is a utility library that downloads and prepares public datasets. We do not host or distribute these datasets, vouch for their quality or fairness, or claim that you have license to use the dataset. It is your responsibility to determine whether you have permission to use the dataset under the dataset's license. + +If you're a dataset owner and wish to update any part of it (description, citation, etc.), or do not want your dataset to be included in this library, please get in touch through a GitHub issue. Thanks for your contribution to the ML community! +>>>>>>> init v0.10.0 diff --git a/build_tools/__init__.py b/build_tools/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/build_tools/convert_fairseq_models.py b/build_tools/convert_fairseq_models.py new file mode 100644 index 00000000..8bdb7cf1 --- /dev/null +++ b/build_tools/convert_fairseq_models.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +"""Convert a Wav2Vec2/HuBERT model published by fairseq into torchaudio format + +Examples + +``` +python convert_fairseq_models.py \ + --input-file hubert_base_ls960.pt \ + --output-file hubert_fairseq_base_ls960.pth + +python convert_fairseq_models.py \ + --input-file hubert_large_ll60k.pt \ + --output-file hubert_fairseq_large_ll60k.pth + +python convert_fairseq_models.py \ + --input-file hubert_large_ll60k_finetune_ls960.pt \ + --output-file hubert_fairseq_large_ll60k_asr_ls960.pth + +python convert_fairseq_models.py \ + --input-file hubert_xtralarge_ll60k.pt \ + --output-file hubert_fairseq_xlarge_ll60k.pth + +python convert_fairseq_models.py \ + --input-file hubert_xtralarge_ll60k_finetune_ls960.pt \ + --output-file hubert_fairseq_xlarge_ll60k_asr_ls960.pth +""" + +import argparse + +# Note: Avoiding the import of torch and fairseq on global scope as they are slow + + +def _parse_args(): + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawTextHelpFormatter, + ) + parser.add_argument( + '--input-file', required=True, + help='Input model file.' + ) + parser.add_argument( + '--output-file', required=False, + help='Output model file.' + ) + parser.add_argument( + '--dict-dir', + help=( + 'Directory where letter vocabulary file, `dict.ltr.txt`, is found. ' + 'Required when loading wav2vec2 model. ' + 'https://dl.fbaipublicfiles.com/fairseq/wav2vec/dict.ltr.txt' + ) + ) + return parser.parse_args() + + +def _load_model(input_file, dict_dir): + import fairseq + + overrides = {} if dict_dir is None else {'data': dict_dir} + models, _, _ = fairseq.checkpoint_utils.load_model_ensemble_and_task( + [input_file], arg_overrides=overrides, + ) + return models[0] + + +def _import_model(model): + from torchaudio.models.wav2vec2.utils import import_fairseq_model + + if model.__class__.__name__ in ['HubertCtc', 'Wav2VecCtc']: + model = model.w2v_encoder + model = import_fairseq_model(model) + return model + + +def _main(args): + import torch + model = _load_model(args.input_file, args.dict_dir) + model = _import_model(model) + torch.save(model.state_dict(), args.output_file) + + +if __name__ == '__main__': + _main(_parse_args()) diff --git a/build_tools/setup_helpers/__init__.py b/build_tools/setup_helpers/__init__.py new file mode 100644 index 00000000..7afa3f31 --- /dev/null +++ b/build_tools/setup_helpers/__init__.py @@ -0,0 +1 @@ +from .extension import * # noqa diff --git a/build_tools/setup_helpers/extension.py b/build_tools/setup_helpers/extension.py new file mode 100644 index 00000000..8f062c6f --- /dev/null +++ b/build_tools/setup_helpers/extension.py @@ -0,0 +1,139 @@ +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 +import torch + +__all__ = [ + 'get_ext_modules', + 'CMakeBuild', +] + +_THIS_DIR = Path(__file__).parent.resolve() +_ROOT_DIR = _THIS_DIR.parent.parent.resolve() +_TORCHAUDIO_DIR = _ROOT_DIR / 'torchaudio' + + +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_KALDI = False if platform.system() == 'Windows' else _get_build("BUILD_KALDI", True) +_BUILD_RNNT = _get_build("BUILD_RNNT", True) +_USE_ROCM = _get_build("USE_ROCM", torch.cuda.is_available() and torch.version.hip is not None) +_USE_CUDA = _get_build("USE_CUDA", torch.cuda.is_available() and torch.version.hip is None) +_TORCH_CUDA_ARCH_LIST = os.environ.get('TORCH_CUDA_ARCH_LIST', None) + + +def get_ext_modules(): + return [ + Extension(name='torchaudio.lib.libtorchaudio', sources=[]), + Extension(name='torchaudio._torchaudio', sources=[]), + ] + + +# 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 (libtorchaudio and _torchaudio) 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 `torchaudio._torchaudio`. + if ext.name != 'torchaudio._torchaudio': + 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"-DBUILD_SOX:BOOL={'ON' if _BUILD_SOX else 'OFF'}", + f"-DBUILD_KALDI:BOOL={'ON' if _BUILD_KALDI else 'OFF'}", + f"-DBUILD_RNNT:BOOL={'ON' if _BUILD_RNNT else 'OFF'}", + "-DBUILD_TORCHAUDIO_PYTHON_EXTENSION:BOOL=ON", + f"-DUSE_ROCM:BOOL={'ON' if _USE_ROCM else 'OFF'}", + f"-DUSE_CUDA:BOOL={'ON' if _USE_CUDA else 'OFF'}", + ] + build_args = [ + '--target', 'install' + ] + # Pass CUDA architecture to cmake + if _TORCH_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 = _TORCH_CUDA_ARCH_LIST.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) + + subprocess.check_call( + ["cmake", str(_ROOT_DIR)] + cmake_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/build_tools/travis/install.sh b/build_tools/travis/install.sh new file mode 100644 index 00000000..3586d5bd --- /dev/null +++ b/build_tools/travis/install.sh @@ -0,0 +1,81 @@ +#!/bin/bash +# This script is meant to be called by the "install" step defined in +# .travis.yml. See http://docs.travis-ci.com/ for more details. +# The behavior of the script is controlled by environment variabled defined +# in the .travis.yml in the top level folder of the project. + + set -e + + echo 'List files from cached directories' +if [ -d $HOME/download ]; then + echo 'download:' + ls $HOME/download +fi +if [ -d $HOME/.cache/pip ]; then + echo 'pip:' + ls $HOME/.cache/pip +fi + + # Deactivate the travis-provided virtual environment and setup a +# conda-based environment instead +deactivate + + # Add the miniconda bin directory to $PATH +export PATH=/home/travis/miniconda3/bin:$PATH +echo $PATH + + # Use the miniconda installer for setup of conda itself +pushd . +cd +mkdir -p download +cd download +if [[ ! -f /home/travis/miniconda3/bin/activate ]] +then + if [[ ! -f miniconda.sh ]] + then + wget http://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh \ + -O miniconda.sh + fi + chmod +x miniconda.sh && ./miniconda.sh -b -f + conda update --yes conda + echo "Creating environment to run tests in." + conda create -n testenv --yes python="$PYTHON_VERSION" +fi +cd .. +popd + + # Activate the python environment we created. +source activate testenv + + # Install requirements via pip in our conda environment +conda install -y pytorch cpuonly -c pytorch-nightly +pip install -r requirements.txt + + # Install the following only if running tests +if [[ "$SKIP_INSTALL" != "true" ]]; then + # TorchAudio CPP Extensions + python setup.py install +fi + +if [[ "$RUN_EXAMPLE_TESTS" == "true" ]]; then + # Install dependencies + pip install sentencepiece PyAudio + + if [[ ! -d $HOME/download/fairseq ]]; then + # Install fairseq from source + git clone https://github.com/pytorch/fairseq $HOME/download/fairseq + fi + + pushd $HOME/download/fairseq + pip install --editable . + popd + + mkdir -p $HOME/download/data + # Install dictionary, sentence piece model, and model + # These are cached so they are not downloaded if they already exist + wget -nc -O $HOME/download/data/dict.txt https://download.pytorch.org/models/audio/dict.txt || true + wget -nc -O $HOME/download/data/spm.model https://download.pytorch.org/models/audio/spm.model || true + wget -nc -O $HOME/download/data/model.pt https://download.pytorch.org/models/audio/checkpoint_avg_60_80.pt || true +fi + +echo "Finished installation" diff --git a/build_tools/travis/test_script.sh b/build_tools/travis/test_script.sh new file mode 100644 index 00000000..e854a211 --- /dev/null +++ b/build_tools/travis/test_script.sh @@ -0,0 +1,49 @@ +#!/bin/bash +# This script is meant to be called by the "script" step defined in +# .travis.yml. See http://docs.travis-ci.com/ for more details. +# The behavior of the script is controlled by environment variabled defined +# in the .travis.yml in the top level folder of the project. +set -e + +python --version +python -c 'import torch;print("torch:", torch.__version__)' + +run_tests() { + # find all the test files that match "test*.py" + TEST_FILES="$(find test -type f -name "test*.py" | sort)" + echo "Test files are:" + echo $TEST_FILES + + echo "Executing tests:" + EXIT_STATUS=0 + for FILE in $TEST_FILES; do + # run each file on a separate process. if one fails, just keep going and + # return the final exit status. + python -m pytest -v $FILE + STATUS=$? + EXIT_STATUS="$(($EXIT_STATUS+STATUS))" + done + + echo "Done, exit status: $EXIT_STATUS" + exit $EXIT_STATUS +} + +if [[ "$RUN_FLAKE8" == "true" ]]; then + flake8 +fi + +if [[ "$SKIP_TESTS" != "true" ]]; then + echo "run_tests" + run_tests +fi + +if [[ "$RUN_EXAMPLE_TESTS" == "true" ]]; then + echo "run_example_tests" + pushd examples + ASR_MODEL_PATH=$HOME/download/data/model.pt \ + ASR_INPUT_FILE=interactive_asr/data/sample.wav \ + ASR_DATA_PATH=$HOME/download/data \ + ASR_USER_DIR=$HOME/download/fairseq/examples/speech_recognition \ + python -m unittest test/test_interactive_asr.py + popd +fi diff --git a/cmake/LoadHIP.cmake b/cmake/LoadHIP.cmake new file mode 100644 index 00000000..41a537b5 --- /dev/null +++ b/cmake/LoadHIP.cmake @@ -0,0 +1,234 @@ +set(PYTORCH_FOUND_HIP FALSE) + +if(NOT DEFINED ENV{ROCM_PATH}) + set(ROCM_PATH /opt/rocm) +else() + set(ROCM_PATH $ENV{ROCM_PATH}) +endif() + +# HIP_PATH +if(NOT DEFINED ENV{HIP_PATH}) + set(HIP_PATH ${ROCM_PATH}/hip) +else() + set(HIP_PATH $ENV{HIP_PATH}) +endif() + +if(NOT EXISTS ${HIP_PATH}) + return() +endif() + +# HCC_PATH +if(NOT DEFINED ENV{HCC_PATH}) + set(HCC_PATH ${ROCM_PATH}/hcc) +else() + set(HCC_PATH $ENV{HCC_PATH}) +endif() + +# HSA_PATH +if(NOT DEFINED ENV{HSA_PATH}) + set(HSA_PATH ${ROCM_PATH}/hsa) +else() + set(HSA_PATH $ENV{HSA_PATH}) +endif() + +# ROCBLAS_PATH +if(NOT DEFINED ENV{ROCBLAS_PATH}) + set(ROCBLAS_PATH ${ROCM_PATH}/rocblas) +else() + set(ROCBLAS_PATH $ENV{ROCBLAS_PATH}) +endif() + +# ROCFFT_PATH +if(NOT DEFINED ENV{ROCFFT_PATH}) + set(ROCFFT_PATH ${ROCM_PATH}/rocfft) +else() + set(ROCFFT_PATH $ENV{ROCFFT_PATH}) +endif() + +# HIPFFT_PATH +if(NOT DEFINED ENV{HIPFFT_PATH}) + set(HIPFFT_PATH ${ROCM_PATH}/hipfft) +else() + set(HIPFFT_PATH $ENV{HIPFFT_PATH}) +endif() + +# HIPSPARSE_PATH +if(NOT DEFINED ENV{HIPSPARSE_PATH}) + set(HIPSPARSE_PATH ${ROCM_PATH}/hipsparse) +else() + set(HIPSPARSE_PATH $ENV{HIPSPARSE_PATH}) +endif() + +# THRUST_PATH +if(DEFINED ENV{THRUST_PATH}) + set(THRUST_PATH $ENV{THRUST_PATH}) +else() + set(THRUST_PATH ${ROCM_PATH}/include) +endif() + +# HIPRAND_PATH +if(NOT DEFINED ENV{HIPRAND_PATH}) + set(HIPRAND_PATH ${ROCM_PATH}/hiprand) +else() + set(HIPRAND_PATH $ENV{HIPRAND_PATH}) +endif() + +# ROCRAND_PATH +if(NOT DEFINED ENV{ROCRAND_PATH}) + set(ROCRAND_PATH ${ROCM_PATH}/rocrand) +else() + set(ROCRAND_PATH $ENV{ROCRAND_PATH}) +endif() + +# MIOPEN_PATH +if(NOT DEFINED ENV{MIOPEN_PATH}) + set(MIOPEN_PATH ${ROCM_PATH}/miopen) +else() + set(MIOPEN_PATH $ENV{MIOPEN_PATH}) +endif() + +# RCCL_PATH +if(NOT DEFINED ENV{RCCL_PATH}) + set(RCCL_PATH ${ROCM_PATH}/rccl) +else() + set(RCCL_PATH $ENV{RCCL_PATH}) +endif() + +# ROCPRIM_PATH +if(NOT DEFINED ENV{ROCPRIM_PATH}) + set(ROCPRIM_PATH ${ROCM_PATH}/rocprim) +else() + set(ROCPRIM_PATH $ENV{ROCPRIM_PATH}) +endif() + +# HIPCUB_PATH +if(NOT DEFINED ENV{HIPCUB_PATH}) + set(HIPCUB_PATH ${ROCM_PATH}/hipcub) +else() + set(HIPCUB_PATH $ENV{HIPCUB_PATH}) +endif() + +# ROCTHRUST_PATH +if(NOT DEFINED ENV{ROCTHRUST_PATH}) + set(ROCTHRUST_PATH ${ROCM_PATH}/rocthrust) +else() + set(ROCTHRUST_PATH $ENV{ROCTHRUST_PATH}) +endif() + +# ROCTRACER_PATH +if(NOT DEFINED ENV{ROCTRACER_PATH}) + set(ROCTRACER_PATH ${ROCM_PATH}/roctracer) +else() + set(ROCTRACER_PATH $ENV{ROCTRACER_PATH}) +endif() + +if(NOT DEFINED ENV{PYTORCH_ROCM_ARCH}) + set(PYTORCH_ROCM_ARCH gfx803;gfx900;gfx906;gfx908) +else() + set(PYTORCH_ROCM_ARCH $ENV{PYTORCH_ROCM_ARCH}) +endif() + +# Add HIP to the CMAKE Module Path +set(CMAKE_MODULE_PATH ${HIP_PATH}/cmake ${CMAKE_MODULE_PATH}) + +# Disable Asserts In Code (Can't use asserts on HIP stack.) +add_definitions(-DNDEBUG) + +macro(find_package_and_print_version PACKAGE_NAME) + find_package("${PACKAGE_NAME}" ${ARGN}) + message("${PACKAGE_NAME} VERSION: ${${PACKAGE_NAME}_VERSION}") +endmacro() + +# Find the HIP Package +find_package_and_print_version(HIP 1.0) + +if(HIP_FOUND) + set(PYTORCH_FOUND_HIP TRUE) + + # Find ROCM version for checks + file(READ "${ROCM_PATH}/.info/version-dev" ROCM_VERSION_DEV_RAW) + string(REGEX MATCH "^([0-9]+)\.([0-9]+)\.([0-9]+)-.*$" ROCM_VERSION_DEV_MATCH ${ROCM_VERSION_DEV_RAW}) + if(ROCM_VERSION_DEV_MATCH) + set(ROCM_VERSION_DEV_MAJOR ${CMAKE_MATCH_1}) + set(ROCM_VERSION_DEV_MINOR ${CMAKE_MATCH_2}) + set(ROCM_VERSION_DEV_PATCH ${CMAKE_MATCH_3}) + set(ROCM_VERSION_DEV "${ROCM_VERSION_DEV_MAJOR}.${ROCM_VERSION_DEV_MINOR}.${ROCM_VERSION_DEV_PATCH}") + endif() + message("\n***** ROCm version from ${ROCM_PATH}/.info/version-dev ****\n") + message("ROCM_VERSION_DEV: ${ROCM_VERSION_DEV}") + message("ROCM_VERSION_DEV_MAJOR: ${ROCM_VERSION_DEV_MAJOR}") + message("ROCM_VERSION_DEV_MINOR: ${ROCM_VERSION_DEV_MINOR}") + message("ROCM_VERSION_DEV_PATCH: ${ROCM_VERSION_DEV_PATCH}") + + message("\n***** Library versions from dpkg *****\n") + execute_process(COMMAND dpkg -l COMMAND grep rocm-dev COMMAND awk "{print $2 \" VERSION: \" $3}") + execute_process(COMMAND dpkg -l COMMAND grep rocm-libs COMMAND awk "{print $2 \" VERSION: \" $3}") + execute_process(COMMAND dpkg -l COMMAND grep hsakmt-roct COMMAND awk "{print $2 \" VERSION: \" $3}") + execute_process(COMMAND dpkg -l COMMAND grep rocr-dev COMMAND awk "{print $2 \" VERSION: \" $3}") + execute_process(COMMAND dpkg -l COMMAND grep -w hcc COMMAND awk "{print $2 \" VERSION: \" $3}") + execute_process(COMMAND dpkg -l COMMAND grep hip_base COMMAND awk "{print $2 \" VERSION: \" $3}") + execute_process(COMMAND dpkg -l COMMAND grep hip_hcc COMMAND awk "{print $2 \" VERSION: \" $3}") + + message("\n***** Library versions from cmake find_package *****\n") + + set(CMAKE_HCC_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG}) + set(CMAKE_HCC_FLAGS_RELEASE ${CMAKE_CXX_FLAGS_RELEASE}) + ### Remove setting of Flags when FindHIP.CMake PR #558 is accepted.### + + set(hip_DIR ${HIP_PATH}/lib/cmake/hip) + set(hsa-runtime64_DIR ${ROCM_PATH}/lib/cmake/hsa-runtime64) + set(AMDDeviceLibs_DIR ${ROCM_PATH}/lib/cmake/AMDDeviceLibs) + set(amd_comgr_DIR ${ROCM_PATH}/lib/cmake/amd_comgr) + set(rocrand_DIR ${ROCRAND_PATH}/lib/cmake/rocrand) + set(hiprand_DIR ${HIPRAND_PATH}/lib/cmake/hiprand) + set(rocblas_DIR ${ROCBLAS_PATH}/lib/cmake/rocblas) + set(miopen_DIR ${MIOPEN_PATH}/lib/cmake/miopen) + set(rocfft_DIR ${ROCFFT_PATH}/lib/cmake/rocfft) + set(hipfft_DIR ${HIPFFT_PATH}/lib/cmake/hipfft) + set(hipsparse_DIR ${HIPSPARSE_PATH}/lib/cmake/hipsparse) + set(rccl_DIR ${RCCL_PATH}/lib/cmake/rccl) + set(rocprim_DIR ${ROCPRIM_PATH}/lib/cmake/rocprim) + set(hipcub_DIR ${HIPCUB_PATH}/lib/cmake/hipcub) + set(rocthrust_DIR ${ROCTHRUST_PATH}/lib/cmake/rocthrust) + + find_package_and_print_version(hip REQUIRED) + find_package_and_print_version(hsa-runtime64 REQUIRED) + find_package_and_print_version(amd_comgr REQUIRED) + find_package_and_print_version(rocrand REQUIRED) + find_package_and_print_version(hiprand REQUIRED) + find_package_and_print_version(rocblas REQUIRED) + find_package_and_print_version(miopen REQUIRED) + find_package_and_print_version(rocfft REQUIRED) + #if(ROCM_VERSION_DEV VERSION_GREATER_EQUAL "4.1.0") + find_package_and_print_version(hipfft REQUIRED) + #endif() + find_package_and_print_version(hipsparse REQUIRED) + find_package_and_print_version(rccl) + find_package_and_print_version(rocprim REQUIRED) + find_package_and_print_version(hipcub REQUIRED) + find_package_and_print_version(rocthrust REQUIRED) + + if(HIP_COMPILER STREQUAL clang) + set(hip_library_name amdhip64) + else() + set(hip_library_name hip_hcc) + endif() + message("HIP library name: ${hip_library_name}") + + # TODO: hip_hcc has an interface include flag "-hc" which is only + # recognizable by hcc, but not gcc and clang. Right now in our + # setup, hcc is only used for linking, but it should be used to + # compile the *_hip.cc files as well. + find_library(PYTORCH_HIP_HCC_LIBRARIES ${hip_library_name} HINTS ${HIP_PATH}/lib) + # TODO: miopen_LIBRARIES should return fullpath to the library file, + # however currently it's just the lib name + find_library(PYTORCH_MIOPEN_LIBRARIES ${miopen_LIBRARIES} HINTS ${MIOPEN_PATH}/lib) + # TODO: rccl_LIBRARIES should return fullpath to the library file, + # however currently it's just the lib name + find_library(PYTORCH_RCCL_LIBRARIES ${rccl_LIBRARIES} HINTS ${RCCL_PATH}/lib) + # hiprtc is part of HIP + find_library(ROCM_HIPRTC_LIB ${hip_library_name} HINTS ${HIP_PATH}/lib) + # roctx is part of roctracer + find_library(ROCM_ROCTX_LIB roctx64 HINTS ${ROCTRACER_PATH}/lib) + set(roctracer_INCLUDE_DIRS ${ROCTRACER_PATH}/include) +endif() diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..76b60439 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,27 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = -W # converts warnings into error +SPHINXBUILD = sphinx-build +SPHINXPROJ = torchaudio +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +docset: html + doc2dash --name $(SPHINXPROJ) --icon $(SOURCEDIR)/_static/img/pytorch-logo-flame.png --enable-js --online-redirect-url http://pytorch.org/audio/ --force $(BUILDDIR)/html/ + + # Manually fix because Zeal doesn't deal well with `icon.png`-only at 2x resolution. + cp $(SPHINXPROJ).docset/icon.png $(SPHINXPROJ).docset/icon@2x.png + convert $(SPHINXPROJ).docset/icon@2x.png -resize 16x16 $(SPHINXPROJ).docset/icon.png + +.PHONY: help Makefile docset + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 00000000..224ce5d7 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,36 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build +set SPHINXPROJ=torchaudio + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 00000000..7bb247e1 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,5 @@ +sphinx==3.5.4 +-e git+git://github.com/pytorch/pytorch_sphinx_theme.git#egg=pytorch_sphinx_theme +sphinxcontrib.katex +sphinxcontrib.bibtex +matplotlib diff --git a/docs/source/_static/img/pytorch-logo-dark.png b/docs/source/_static/img/pytorch-logo-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..b7a1ceb964af782b8a453b3eb6f8eb82b7ddbd49 GIT binary patch literal 15625 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 literal 0 HcmV?d00001 diff --git a/docs/source/_static/img/pytorch-logo-dark.svg b/docs/source/_static/img/pytorch-logo-dark.svg new file mode 100644 index 00000000..5e530003 --- /dev/null +++ b/docs/source/_static/img/pytorch-logo-dark.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/docs/source/_static/img/pytorch-logo-flame.png b/docs/source/_static/img/pytorch-logo-flame.png new file mode 100644 index 0000000000000000000000000000000000000000..bad49bf30b4afe5bb34f76d73a03e39fc48bbedb GIT binary patch literal 22916 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 Nkbkh>g=Oo~{{W#u9;5&O literal 0 HcmV?d00001 diff --git a/docs/source/_templates/layout.html b/docs/source/_templates/layout.html new file mode 100644 index 00000000..8727b6ce --- /dev/null +++ b/docs/source/_templates/layout.html @@ -0,0 +1,8 @@ +{% extends "!layout.html" %} + +{% block sidebartitle %} + + {% include "searchbox.html" %} +{% endblock %} diff --git a/docs/source/backend.rst b/docs/source/backend.rst new file mode 100644 index 00000000..6eda2253 --- /dev/null +++ b/docs/source/backend.rst @@ -0,0 +1,92 @@ +.. _backend: + +torchaudio.backend +================== + +Overview +~~~~~~~~ + +:mod:`torchaudio.backend` module provides implementations for audio file I/O functionalities, which are ``torchaudio.info``, ``torchaudio.load``, and ``torchaudio.save``. + +There are currently four implementations available. + +* :ref:`"sox_io" ` (default on Linux/macOS) +* :ref:`"soundfile" ` (default on Windows) + +.. note:: + Instead of calling functions in ``torchaudio.backend`` directly, please use ``torchaudio.info``, ``torchaudio.load``, and ``torchaudio.save`` with proper backend set with :func:`torchaudio.set_audio_backend`. + +Availability +------------ + +``"sox_io"`` backend requires C++ extension module, which is included in Linux/macOS binary distributions. This backend is not available on Windows. + +``"soundfile"`` backend requires ``SoundFile``. Please refer to `the SoundFile documentation `_ for the installation. + +Common Data Structure +~~~~~~~~~~~~~~~~~~~~~ + +Structures used to report the metadata of audio files. + +AudioMetaData +------------- + +.. autoclass:: torchaudio.backend.common.AudioMetaData + +.. _sox_io_backend: + +Sox IO Backend +~~~~~~~~~~~~~~ + +The ``"sox_io"`` backend is available and default on Linux/macOS and not available on Windows. + +I/O functions of this backend support `TorchScript `_. + +You can switch from another backend to the ``sox_io`` backend with the following; + +.. code:: + + torchaudio.set_audio_backend("sox_io") + +info +---- + +.. autofunction:: torchaudio.backend.sox_io_backend.info + +load +---- + +.. autofunction:: torchaudio.backend.sox_io_backend.load + +save +---- + +.. autofunction:: torchaudio.backend.sox_io_backend.save + +.. _soundfile_backend: + +Soundfile Backend +~~~~~~~~~~~~~~~~~ + +The ``"soundfile"`` backend is available when `SoundFile `_ is installed. This backend is the default on Windows. + +You can switch from another backend to the ``"soundfile"`` backend with the following; + +.. code:: + + torchaudio.set_audio_backend("soundfile") + +info +---- + +.. autofunction:: torchaudio.backend.soundfile_backend.info + +load +---- + +.. autofunction:: torchaudio.backend.soundfile_backend.load + +save +---- + +.. autofunction:: torchaudio.backend.soundfile_backend.save diff --git a/docs/source/compliance.kaldi.rst b/docs/source/compliance.kaldi.rst new file mode 100644 index 00000000..72827ca3 --- /dev/null +++ b/docs/source/compliance.kaldi.rst @@ -0,0 +1,31 @@ +.. role:: hidden + :class: hidden-section + +torchaudio.compliance.kaldi +============================ + +.. currentmodule:: torchaudio.compliance.kaldi + +The useful processing operations of kaldi_ can be performed with torchaudio. +Various functions with identical parameters are given so that torchaudio can +produce similar outputs. + +.. _kaldi: https://github.com/kaldi-asr/kaldi + +Functions +--------- + +:hidden:`spectrogram` +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: spectrogram + +:hidden:`fbank` +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: fbank + +:hidden:`mfcc` +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: mfcc diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 00000000..9bed75a7 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,257 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# PyTorch documentation build configuration file, created by +# sphinx-quickstart on Fri Dec 23 13:31:47 2016. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) +import pytorch_sphinx_theme + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +needs_sphinx = '1.6' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.autosummary', + 'sphinx.ext.doctest', + 'sphinx.ext.intersphinx', + 'sphinx.ext.todo', + 'sphinx.ext.coverage', + 'sphinx.ext.napoleon', + 'sphinx.ext.viewcode', + 'sphinxcontrib.katex', + 'sphinxcontrib.bibtex', +] + +# katex options +# +# + +katex_options = r''' +delimiters : [ + {left: "$$", right: "$$", display: true}, + {left: "\\(", right: "\\)", display: false}, + {left: "\\[", right: "\\]", display: true} +] +''' + +bibtex_bibfiles = ['refs.bib'] + +napoleon_use_ivar = True +napoleon_numpy_docstring = False +napoleon_google_docstring = True + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'Torchaudio' +copyright = '2018, Torchaudio Contributors' +author = 'Torchaudio Contributors' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +# TODO: change to [:2] at v1.0 +version = '0.10.0 ' +# The full version, including alpha/beta/rc tags. +# TODO: verify this works as expected +release = '0.10.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = [] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'pytorch_sphinx_theme' +html_theme_path = [pytorch_sphinx_theme.get_html_theme_path()] + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +html_theme_options = { + 'pytorch_project': 'audio', + 'collapse_navigation': False, + 'display_version': True, + 'logo_only': True, + 'navigation_with_keys': True, + 'analytics_id': 'UA-117752657-2', +} + +html_logo = '_static/img/pytorch-logo-dark.svg' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] +html_css_files = [ + 'https://cdn.jsdelivr.net/npm/katex@0.10.0-beta/dist/katex.min.css' +] + +# -- Options for HTMLHelp output ------------------------------------------ + +# Output file base name for HTML help builder. +htmlhelp_basename = 'TorchAudiodoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'pytorch.tex', 'Torchaudio Documentation', + 'Torch Contributors', 'manual'), +] + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'Torchaudio', 'Torchaudio Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'Torchaudio', 'Torchaudio Documentation', + author, 'Torchaudio', 'Load audio files into pytorch tensors.', + 'Miscellaneous'), +] + + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = { + 'python': ('https://docs.python.org/', None), + 'numpy': ('https://docs.scipy.org/doc/numpy/', None), + 'torch': ('https://pytorch.org/docs/stable/', None), +} + +# -- A patch that prevents Sphinx from cross-referencing ivar tags ------- +# See http://stackoverflow.com/a/41184353/3343043 + +from docutils import nodes +from sphinx.util.docfields import TypedField +from sphinx import addnodes + + +def patched_make_field(self, types, domain, items, **kw): + # `kw` catches `env=None` needed for newer sphinx while maintaining + # backwards compatibility when passed along further down! + + # type: (list, str, tuple) -> nodes.field + def handle_item(fieldarg, content): + par = nodes.paragraph() + par += addnodes.literal_strong('', fieldarg) # Patch: this line added + # par.extend(self.make_xrefs(self.rolename, domain, fieldarg, + # addnodes.literal_strong)) + if fieldarg in types: + par += nodes.Text(' (') + # NOTE: using .pop() here to prevent a single type node to be + # inserted twice into the doctree, which leads to + # inconsistencies later when references are resolved + fieldtype = types.pop(fieldarg) + if len(fieldtype) == 1 and isinstance(fieldtype[0], nodes.Text): + typename = u''.join(n.astext() for n in fieldtype) + typename = typename.replace('int', 'python:int') + typename = typename.replace('long', 'python:long') + typename = typename.replace('float', 'python:float') + typename = typename.replace('type', 'python:type') + par.extend(self.make_xrefs(self.typerolename, domain, typename, + addnodes.literal_emphasis, **kw)) + else: + par += fieldtype + par += nodes.Text(')') + par += nodes.Text(' -- ') + par += content + return par + + fieldname = nodes.field_name('', self.label) + if len(items) == 1 and self.can_collapse: + fieldarg, content = items[0] + bodynode = handle_item(fieldarg, content) + else: + bodynode = self.list_type() + for fieldarg, content in items: + bodynode += nodes.list_item('', handle_item(fieldarg, content)) + fieldbody = nodes.field_body('', bodynode) + return nodes.field('', fieldname, fieldbody) + +TypedField.make_field = patched_make_field diff --git a/docs/source/datasets.rst b/docs/source/datasets.rst new file mode 100644 index 00000000..8189bb82 --- /dev/null +++ b/docs/source/datasets.rst @@ -0,0 +1,121 @@ +torchaudio.datasets +==================== + +All datasets are subclasses of :class:`torch.utils.data.Dataset` +and have ``__getitem__`` and ``__len__`` methods implemented. +Hence, they can all be passed to a :class:`torch.utils.data.DataLoader` +which can load multiple samples parallelly using ``torch.multiprocessing`` workers. +For example: :: + + yesno_data = torchaudio.datasets.YESNO('.', download=True) + data_loader = torch.utils.data.DataLoader(yesno_data, + batch_size=1, + shuffle=True, + num_workers=args.nThreads) + +The following datasets are available: + +.. contents:: Datasets + :local: + +All the datasets have almost similar API. They all have two common arguments: +``transform`` and ``target_transform`` to transform the input and target respectively. + + +.. currentmodule:: torchaudio.datasets + + +CMUARCTIC +~~~~~~~~~ + +.. autoclass:: CMUARCTIC + :members: + :special-members: __getitem__ + + +CMUDict +~~~~~~~~~ + +.. autoclass:: CMUDict + :members: + :special-members: __getitem__ + + +COMMONVOICE +~~~~~~~~~~~ + +.. autoclass:: COMMONVOICE + :members: + :special-members: __getitem__ + + +GTZAN +~~~~~ + +.. autoclass:: GTZAN + :members: + :special-members: __getitem__ + + +LIBRISPEECH +~~~~~~~~~~~ + +.. autoclass:: LIBRISPEECH + :members: + :special-members: __getitem__ + + +LIBRITTS +~~~~~~~~ + +.. autoclass:: LIBRITTS + :members: + :special-members: __getitem__ + + +LJSPEECH +~~~~~~~~ + +.. autoclass:: LJSPEECH + :members: + :special-members: __getitem__ + + +SPEECHCOMMANDS +~~~~~~~~~~~~~~ + +.. autoclass:: SPEECHCOMMANDS + :members: + :special-members: __getitem__ + + +TEDLIUM +~~~~~~~~~~~~~~ + +.. autoclass:: TEDLIUM + :members: + :special-members: __getitem__ + + +VCTK +~~~~ + +.. autoclass:: VCTK + :members: + :special-members: __getitem__ + + +VCTK_092 +~~~~~~~~ + +.. autoclass:: VCTK_092 + :members: + :special-members: __getitem__ + + +YESNO +~~~~~ + +.. autoclass:: YESNO + :members: + :special-members: __getitem__ diff --git a/docs/source/functional.rst b/docs/source/functional.rst new file mode 100644 index 00000000..5ff8ec69 --- /dev/null +++ b/docs/source/functional.rst @@ -0,0 +1,281 @@ +.. role:: hidden + :class: hidden-section + +torchaudio.functional +===================== + +.. currentmodule:: torchaudio.functional + +Functions to perform common audio operations. + +:hidden:`Utility` +~~~~~~~~~~~~~~~~~ + +amplitude_to_DB +--------------- + +.. autofunction:: amplitude_to_DB + +DB_to_amplitude +--------------- + +.. autofunction:: DB_to_amplitude + +create_fb_matrix +---------------- + +.. autofunction:: create_fb_matrix + +melscale_fbanks +--------------- + +.. autofunction:: melscale_fbanks + +linear_fbanks +------------- + +.. autofunction:: linear_fbanks + +create_dct +---------- + +.. autofunction:: create_dct + +mask_along_axis +--------------- + +.. autofunction:: mask_along_axis + +mask_along_axis_iid +------------------- + +.. autofunction:: mask_along_axis_iid + +mu_law_encoding +--------------- + +.. autofunction:: mu_law_encoding + +mu_law_decoding +--------------- + +.. autofunction:: mu_law_decoding + +apply_codec +----------- + +.. autofunction:: apply_codec + +resample +-------- + +.. autofunction:: resample + +:hidden:`Complex Utility` +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Utilities for pseudo complex tensor. This is not for the native complex dtype, such as `cfloat64`, but for tensors with real-value type and have extra dimension at the end for real and imaginary parts. + +angle +----- + +.. autofunction:: angle + +complex_norm +------------ + +.. autofunction:: complex_norm + +magphase +-------- + +.. autofunction:: magphase + +:hidden:`Filtering` +~~~~~~~~~~~~~~~~~~~ + + +allpass_biquad +-------------- + +.. autofunction:: allpass_biquad + +band_biquad +----------- + +.. autofunction:: band_biquad + +bandpass_biquad +--------------- + +.. autofunction:: bandpass_biquad + +bandreject_biquad +----------------- + +.. autofunction:: bandreject_biquad + +bass_biquad +----------- + +.. autofunction:: bass_biquad + +biquad +------ + +.. autofunction:: biquad + +contrast +-------- + +.. autofunction:: contrast + +dcshift +------- + +.. autofunction:: dcshift + +deemph_biquad +------------- + +.. autofunction:: deemph_biquad + + +dither +------ + +.. autofunction:: dither + +equalizer_biquad +---------------- + +.. autofunction:: equalizer_biquad + +filtfilt +-------- + +.. autofunction:: filtfilt + +flanger +------- + +.. autofunction:: flanger + +gain +---- + +.. autofunction:: gain + +highpass_biquad +--------------- + +.. autofunction:: highpass_biquad + +lfilter +------- + +.. autofunction:: lfilter + +lowpass_biquad +-------------- + +.. autofunction:: lowpass_biquad + +overdrive +--------- + +.. autofunction:: overdrive + +phaser +------ + +.. autofunction:: phaser + +riaa_biquad +----------- + +.. autofunction:: riaa_biquad + +treble_biquad +------------- + +.. autofunction:: treble_biquad + +:hidden:`Feature Extractions` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:hidden:`vad` +------------- + +.. autofunction:: vad + +:hidden:`spectrogram` +--------------------- + +.. autofunction:: spectrogram + +:hidden:`inverse_spectrogram` +----------------------------- + +.. autofunction:: inverse_spectrogram + +:hidden:`griffinlim` +-------------------- + +.. autofunction:: griffinlim + +:hidden:`phase_vocoder` +----------------------- + +.. autofunction:: phase_vocoder + +:hidden:`pitch_shift` +--------------------- + +.. autofunction:: pitch_shift + +:hidden:`compute_deltas` +------------------------ + +.. autofunction:: compute_deltas + +:hidden:`detect_pitch_frequency` +-------------------------------- + +.. autofunction:: detect_pitch_frequency + +:hidden:`sliding_window_cmn` +---------------------------- + +.. autofunction:: sliding_window_cmn + +:hidden:`compute_kaldi_pitch` +----------------------------- + +.. autofunction:: compute_kaldi_pitch + +:hidden:`spectral_centroid` +--------------------------- + +.. autofunction:: spectral_centroid + +:hidden:`Loss` +~~~~~~~~~~~~~~ + +rnnt_loss +--------- + +.. autofunction:: rnnt_loss + +:hidden:`Metric` +~~~~~~~~~~~~~~~~ + +edit_distance +------------- + +.. autofunction:: edit_distance + +References +~~~~~~~~~~ + +.. footbibliography:: diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 00000000..89c9da5c --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,55 @@ +torchaudio +========== +This library is part of the `PyTorch +`_ project. PyTorch is an open source +machine learning framework. + +Features described in this documentation are classified by release status: + + *Stable:* These features will be maintained long-term and there should generally + be no major performance limitations or gaps in documentation. + We also expect to maintain backwards compatibility (although + breaking changes can happen and notice will be given one release ahead + of time). + + *Beta:* Features are tagged as Beta because the API may change based on + user feedback, because the performance needs to improve, or because + coverage across operators is not yet complete. For Beta features, we are + committing to seeing the feature through to the Stable classification. + We are not, however, committing to backwards compatibility. + + *Prototype:* These features are typically not available as part of + binary distributions like PyPI or Conda, except sometimes behind run-time + flags, and are at an early stage for feedback and testing. + + +The :mod:`torchaudio` package consists of I/O, popular datasets and common audio transformations. + +.. toctree:: + :maxdepth: 2 + :caption: Package Reference + + torchaudio + backend + functional + transforms + datasets + models + pipelines + sox_effects + compliance.kaldi + kaldi_io + utils + + +.. toctree:: + :maxdepth: 1 + :caption: PyTorch Libraries + + PyTorch + torchaudio + torchtext + torchvision + TorchElastic + TorchServe + PyTorch on XLA Devices diff --git a/docs/source/kaldi_io.rst b/docs/source/kaldi_io.rst new file mode 100644 index 00000000..2744bcc8 --- /dev/null +++ b/docs/source/kaldi_io.rst @@ -0,0 +1,43 @@ +.. role:: hidden + :class: hidden-section + +torchaudio.kaldi_io +====================== + +.. currentmodule:: torchaudio.kaldi_io + +To use this module, the dependency kaldi_io_ needs to be installed. +This is a light wrapper around ``kaldi_io`` that returns :class:`torch.Tensor`. + +.. _kaldi_io: https://github.com/vesis84/kaldi-io-for-python + +Vectors +------- + +:hidden:`read_vec_int_ark` +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: read_vec_int_ark + +:hidden:`read_vec_flt_scp` +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: read_vec_flt_scp + +:hidden:`read_vec_flt_ark` +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: read_vec_flt_ark + +Matrices +-------- + +:hidden:`read_mat_scp` +~~~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: read_mat_scp + +:hidden:`read_mat_ark` +~~~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: read_mat_ark diff --git a/docs/source/models.rst b/docs/source/models.rst new file mode 100644 index 00000000..70738f8d --- /dev/null +++ b/docs/source/models.rst @@ -0,0 +1,128 @@ +.. role:: hidden + :class: hidden-section + +torchaudio.models +================= + +.. currentmodule:: torchaudio.models + +The models subpackage contains definitions of models for addressing common audio tasks. + + +ConvTasNet +~~~~~~~~~~ + +.. autoclass:: ConvTasNet + + .. automethod:: forward + + +DeepSpeech +~~~~~~~~~~ + +.. autoclass:: DeepSpeech + + .. automethod:: forward + + +Tacotron2 +~~~~~~~~~ + +.. autoclass:: Tacotron2 + + .. automethod:: forward + + .. automethod:: infer + +Wav2Letter +~~~~~~~~~~ + +.. autoclass:: Wav2Letter + + .. automethod:: forward + + +Wav2Vec2.0 / HuBERT +~~~~~~~~~~~~~~~~~~~ + +Model +----- + +Wav2Vec2Model +^^^^^^^^^^^^^ + +.. autoclass:: Wav2Vec2Model + + .. automethod:: extract_features + + .. automethod:: forward + +Factory Functions +----------------- + +wav2vec2_model +^^^^^^^^^^^^^^ + +.. autofunction:: wav2vec2_model + + +wav2vec2_base +^^^^^^^^^^^^^ + +.. autofunction:: wav2vec2_base + +wav2vec2_large +^^^^^^^^^^^^^^ + +.. autofunction:: wav2vec2_large + +wav2vec2_large_lv60k +^^^^^^^^^^^^^^^^^^^^ + +.. autofunction:: wav2vec2_large_lv60k + +hubert_base +^^^^^^^^^^^ + +.. autofunction:: hubert_base + +hubert_large +^^^^^^^^^^^^ + +.. autofunction:: hubert_large + +hubert_xlarge +^^^^^^^^^^^^^ + +.. autofunction:: hubert_xlarge + +Utility Functions +----------------- + +.. currentmodule:: torchaudio.models.wav2vec2.utils + +import_huggingface_model +^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autofunction:: import_huggingface_model + +import_fairseq_model +^^^^^^^^^^^^^^^^^^^^ + +.. autofunction:: import_fairseq_model + +.. currentmodule:: torchaudio.models + +WaveRNN +~~~~~~~ + +.. autoclass:: WaveRNN + + .. automethod:: forward + + .. automethod:: infer + +References +~~~~~~~~~~ + +.. footbibliography:: diff --git a/docs/source/pipelines.rst b/docs/source/pipelines.rst new file mode 100644 index 00000000..962eae9f --- /dev/null +++ b/docs/source/pipelines.rst @@ -0,0 +1,238 @@ +torchaudio.pipelines +==================== + +.. currentmodule:: torchaudio.pipelines + +The pipelines subpackage contains API to access the models with pretrained weights, and information/helper functions associated the pretrained weights. + +wav2vec 2.0 / HuBERT - Representation Learning +---------------------------------------------- + +.. autoclass:: Wav2Vec2Bundle + :members: sample_rate + + .. automethod:: get_model + +WAV2VEC2_BASE +~~~~~~~~~~~~~ + +.. container:: py attribute + + .. autodata:: WAV2VEC2_BASE + :no-value: + +WAV2VEC2_LARGE +~~~~~~~~~~~~~~ + +.. container:: py attribute + + .. autodata:: WAV2VEC2_LARGE + :no-value: + +WAV2VEC2_LARGE_LV60K +~~~~~~~~~~~~~~~~~~~~ + +.. container:: py attribute + + .. autodata:: WAV2VEC2_LARGE_LV60K + :no-value: + + +WAV2VEC2_XLSR53 +~~~~~~~~~~~~~~~ + +.. container:: py attribute + + .. autodata:: WAV2VEC2_XLSR53 + :no-value: + +HUBERT_BASE +~~~~~~~~~~~ + +.. container:: py attribute + + .. autodata:: HUBERT_BASE + :no-value: + +HUBERT_LARGE +~~~~~~~~~~~~ + +.. container:: py attribute + + .. autodata:: HUBERT_LARGE + :no-value: + +HUBERT_XLARGE +~~~~~~~~~~~~~ + +.. container:: py attribute + + .. autodata:: HUBERT_XLARGE + :no-value: + +wav2vec 2.0 / HuBERT - Fine-tuned ASR +------------------------------------- + +.. autoclass:: Wav2Vec2ASRBundle + :members: sample_rate + + .. automethod:: get_model + + .. automethod:: get_labels + + +WAV2VEC2_ASR_BASE_10M +~~~~~~~~~~~~~~~~~~~~~ + +.. container:: py attribute + + .. autodata:: WAV2VEC2_ASR_BASE_10M + :no-value: + +WAV2VEC2_ASR_BASE_100H +~~~~~~~~~~~~~~~~~~~~~~ + +.. container:: py attribute + + .. autodata:: WAV2VEC2_ASR_BASE_100H + :no-value: + +WAV2VEC2_ASR_BASE_960H +~~~~~~~~~~~~~~~~~~~~~~ + +.. container:: py attribute + + .. autodata:: WAV2VEC2_ASR_BASE_960H + :no-value: + +WAV2VEC2_ASR_LARGE_10M +~~~~~~~~~~~~~~~~~~~~~~ + +.. container:: py attribute + + .. autodata:: WAV2VEC2_ASR_LARGE_10M + :no-value: + +WAV2VEC2_ASR_LARGE_100H +~~~~~~~~~~~~~~~~~~~~~~~ + +.. container:: py attribute + + .. autodata:: WAV2VEC2_ASR_LARGE_100H + :no-value: + +WAV2VEC2_ASR_LARGE_960H +~~~~~~~~~~~~~~~~~~~~~~~ + +.. container:: py attribute + + .. autodata:: WAV2VEC2_ASR_LARGE_960H + :no-value: + +WAV2VEC2_ASR_LARGE_LV60K_10M +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. container:: py attribute + + .. autodata:: WAV2VEC2_ASR_LARGE_LV60K_10M + :no-value: + +WAV2VEC2_ASR_LARGE_LV60K_100H +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. container:: py attribute + + .. autodata:: WAV2VEC2_ASR_LARGE_LV60K_100H + :no-value: + +WAV2VEC2_ASR_LARGE_LV60K_960H +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. container:: py attribute + + .. autodata:: WAV2VEC2_ASR_LARGE_LV60K_960H + :no-value: + +HUBERT_ASR_LARGE +~~~~~~~~~~~~~~~~ + +.. container:: py attribute + + .. autodata:: HUBERT_ASR_LARGE + :no-value: + +HUBERT_ASR_XLARGE +~~~~~~~~~~~~~~~~~ + +.. container:: py attribute + + .. autodata:: HUBERT_ASR_XLARGE + :no-value: + +Tacotron2 Text-To-Speech +------------------------ + +Tacotron2TTSBundle +~~~~~~~~~~~~~~~~~~ + +.. autoclass:: Tacotron2TTSBundle + + .. automethod:: get_text_processor + + .. automethod:: get_tacotron2 + + .. automethod:: get_vocoder + +Tacotron2TTSBundle - TextProcessor +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autoclass:: torchaudio.pipelines::Tacotron2TTSBundle.TextProcessor + :members: tokens + :special-members: __call__ + + +Tacotron2TTSBundle - Vocoder +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autoclass:: torchaudio.pipelines::Tacotron2TTSBundle.Vocoder + :members: sample_rate + :special-members: __call__ + + +TACOTRON2_WAVERNN_PHONE_LJSPEECH +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. container:: py attribute + + .. autodata:: TACOTRON2_WAVERNN_PHONE_LJSPEECH + :no-value: + + +TACOTRON2_WAVERNN_CHAR_LJSPEECH +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. container:: py attribute + + .. autodata:: TACOTRON2_WAVERNN_CHAR_LJSPEECH + :no-value: + +TACOTRON2_GRIFFINLIM_PHONE_LJSPEECH +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. container:: py attribute + + .. autodata:: TACOTRON2_GRIFFINLIM_PHONE_LJSPEECH + :no-value: + +TACOTRON2_GRIFFINLIM_CHAR_LJSPEECH +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. container:: py attribute + + .. autodata:: TACOTRON2_GRIFFINLIM_CHAR_LJSPEECH + :no-value: + +References +---------- + +.. footbibliography:: diff --git a/docs/source/refs.bib b/docs/source/refs.bib new file mode 100644 index 00000000..c76513de --- /dev/null +++ b/docs/source/refs.bib @@ -0,0 +1,216 @@ +@article{specaugment, + title={SpecAugment: A Simple Data Augmentation Method for Automatic Speech Recognition}, + url={http://dx.doi.org/10.21437/Interspeech.2019-2680}, + DOI={10.21437/interspeech.2019-2680}, + journal={Interspeech 2019}, + publisher={ISCA}, + author={Park, Daniel S. and Chan, William and Zhang, Yu and Chiu, Chung-Cheng and Zoph, Barret and Cubuk, Ekin D. and Le, Quoc V.}, + year={2019}, + month={Sep} +} +@misc{ljspeech17, + author = {Keith Ito and Linda Johnson}, + title = {The LJ Speech Dataset}, + howpublished = {\url{https://keithito.com/LJ-Speech-Dataset/}}, + year = {2017} +} +@misc{conneau2020unsupervised, + title={Unsupervised Cross-lingual Representation Learning for Speech Recognition}, + author={Alexis Conneau and Alexei Baevski and Ronan Collobert and Abdelrahman Mohamed and Michael Auli}, + year={2020}, + eprint={2006.13979}, + archivePrefix={arXiv}, + primaryClass={cs.CL} +} +@inproceedings{Gales2014SpeechRA, + title={Speech recognition and keyword spotting for low-resource languages: Babel project research at CUED}, + author={Mark John Francis Gales and Kate Knill and Anton Ragni and Shakti Prasad Rath}, + booktitle={SLTU}, + year={2014} +} +@misc{ardila2020common, + title={Common Voice: A Massively-Multilingual Speech Corpus}, + author={Rosana Ardila and Megan Branson and Kelly Davis and Michael Henretty and Michael Kohler and Josh Meyer and Reuben Morais and Lindsay Saunders and Francis M. Tyers and Gregor Weber}, + year={2020}, + eprint={1912.06670}, + archivePrefix={arXiv}, + primaryClass={cs.CL} +} +@article{Pratap_2020, + title={MLS: A Large-Scale Multilingual Dataset for Speech Research}, + url={http://dx.doi.org/10.21437/Interspeech.2020-2826}, + DOI={10.21437/interspeech.2020-2826}, + journal={Interspeech 2020}, + publisher={ISCA}, + author={Pratap, Vineel and Xu, Qiantong and Sriram, Anuroop and Synnaeve, Gabriel and Collobert, Ronan}, + year={2020}, + month={Oct} +} +@INPROCEEDINGS{librilight, + author={J. {Kahn} and M. {Rivière} and W. {Zheng} and E. {Kharitonov} and Q. {Xu} and P. E. {Mazaré} and J. {Karadayi} and V. {Liptchinsky} and R. {Collobert} and C. {Fuegen} and T. {Likhomanenko} and G. {Synnaeve} and A. {Joulin} and A. {Mohamed} and E. {Dupoux}}, + booktitle={ICASSP 2020 - 2020 IEEE International Conference on Acoustics, Speech and Signal Processing (ICASSP)}, + title={Libri-Light: A Benchmark for ASR with Limited or No Supervision}, + year={2020}, + pages={7669-7673}, + note = {\url{https://github.com/facebookresearch/libri-light}}, +} +@INPROCEEDINGS{7178964, + author={Panayotov, Vassil and Chen, Guoguo and Povey, Daniel and Khudanpur, Sanjeev}, + booktitle={2015 IEEE International Conference on Acoustics, Speech and Signal Processing (ICASSP)}, + title={Librispeech: An ASR corpus based on public domain audio books}, + year={2015}, + volume={}, + number={}, + pages={5206-5210}, + doi={10.1109/ICASSP.2015.7178964} +} +@inproceedings{ott2019fairseq, + title = {fairseq: A Fast, Extensible Toolkit for Sequence Modeling}, + author = {Myle Ott and Sergey Edunov and Alexei Baevski and Angela Fan and Sam Gross and Nathan Ng and David Grangier and Michael Auli}, + booktitle = {Proceedings of NAACL-HLT 2019: Demonstrations}, + year = {2019}, +} +@misc{baevski2020wav2vec, + title={wav2vec 2.0: A Framework for Self-Supervised Learning of Speech Representations}, + author={Alexei Baevski and Henry Zhou and Abdelrahman Mohamed and Michael Auli}, + year={2020}, + eprint={2006.11477}, + archivePrefix={arXiv}, + primaryClass={cs.CL} +} +@misc{hsu2021hubert, + title={HuBERT: Self-Supervised Speech Representation Learning by Masked Prediction of Hidden Units}, + author={Wei-Ning Hsu and Benjamin Bolte and Yao-Hung Hubert Tsai and Kushal Lakhotia and Ruslan Salakhutdinov and Abdelrahman Mohamed}, + year={2021}, + eprint={2106.07447}, + archivePrefix={arXiv}, + primaryClass={cs.CL} +} +@misc{hannun2014deep, + title={Deep Speech: Scaling up end-to-end speech recognition}, + author={Awni Hannun and Carl Case and Jared Casper and Bryan Catanzaro and Greg Diamos and Erich Elsen and Ryan Prenger and Sanjeev Satheesh and Shubho Sengupta and Adam Coates and Andrew Y. Ng}, + year={2014}, + eprint={1412.5567}, + archivePrefix={arXiv}, + primaryClass={cs.CL} +} +@misc{graves2012sequence, + title={Sequence Transduction with Recurrent Neural Networks}, + author={Alex Graves}, + year={2012}, + eprint={1211.3711}, + archivePrefix={arXiv}, + primaryClass={cs.NE} +} +@misc{collobert2016wav2letter, + title={Wav2Letter: an End-to-End ConvNet-based Speech Recognition System}, + author={Ronan Collobert and Christian Puhrsch and Gabriel Synnaeve}, + year={2016}, + eprint={1609.03193}, + archivePrefix={arXiv}, + primaryClass={cs.LG} +} +@misc{kalchbrenner2018efficient, + title={Efficient Neural Audio Synthesis}, + author={Nal Kalchbrenner and Erich Elsen and Karen Simonyan and Seb Noury and Norman Casagrande and Edward Lockhart and Florian Stimberg and Aaron van den Oord and Sander Dieleman and Koray Kavukcuoglu}, + year={2018}, + eprint={1802.08435}, + archivePrefix={arXiv}, + primaryClass={cs.SD} +} +@article{Luo_2019, + title={Conv-TasNet: Surpassing Ideal Time–Frequency Magnitude Masking for Speech Separation}, + volume={27}, + ISSN={2329-9304}, + url={http://dx.doi.org/10.1109/TASLP.2019.2915167}, + DOI={10.1109/taslp.2019.2915167}, + number={8}, + journal={IEEE/ACM Transactions on Audio, Speech, and Language Processing}, + publisher={Institute of Electrical and Electronics Engineers (IEEE)}, + author={Luo, Yi and Mesgarani, Nima}, + year={2019}, + month={Aug}, + pages={1256–1266} +} +@InProceedings{ brian_mcfee-proc-scipy-2015, + author = { {B}rian {M}c{F}ee and {C}olin {R}affel and {D}awen {L}iang and {D}aniel {P}.{W}. {E}llis and {M}att {M}c{V}icar and {E}ric {B}attenberg and {O}riol {N}ieto }, + title = { librosa: {A}udio and {M}usic {S}ignal {A}nalysis in {P}ython }, + booktitle = { {P}roceedings of the 14th {P}ython in {S}cience {C}onference }, + pages = { 18 - 24 }, + year = { 2015 }, + editor = { {K}athryn {H}uff and {J}ames {B}ergstra }, + doi = { 10.25080/Majora-7b98e3ed-003 } +} +@INPROCEEDINGS{6701851, + author={Perraudin, Nathanaël and Balazs, Peter and Søndergaard, Peter L.}, + booktitle={2013 IEEE Workshop on Applications of Signal Processing to Audio and Acoustics}, + title={A fast Griffin-Lim algorithm}, + year={2013}, + volume={}, + number={}, + pages={1-4}, + doi={10.1109/WASPAA.2013.6701851}} +@INPROCEEDINGS{1172092, + author={Griffin, D. and Jae Lim}, + booktitle={ICASSP '83. IEEE International Conference on Acoustics, Speech, and Signal Processing}, + title={Signal estimation from modified short-time Fourier transform}, + year={1983}, + volume={8}, + number={}, + pages={804-807}, + doi={10.1109/ICASSP.1983.1172092}} +@INPROCEEDINGS{6854049, + author={Ghahremani, Pegah and BabaAli, Bagher and Povey, Daniel and Riedhammer, Korbinian and Trmal, Jan and Khudanpur, Sanjeev}, + booktitle={2014 IEEE International Conference on Acoustics, Speech and Signal Processing (ICASSP)}, + title={A pitch extraction algorithm tuned for automatic speech recognition}, + year={2014}, + volume={}, + number={}, + pages={2494-2498}, + doi={10.1109/ICASSP.2014.6854049}} +@inproceedings{shen2018natural, + title={Natural TTS Synthesis by Conditioning WaveNet on Mel Spectrogram Predictions}, + author={Shen, Jonathan and Pang, Ruoming and Weiss, Ron J and Schuster, Mike and Jaitly, Navdeep and Yang, Zongheng and Chen, Zhifeng and Zhang, Yu and Wang, Yuxuan and Skerrv-Ryan, Rj and others}, + booktitle={2018 IEEE International Conference on Acoustics, Speech and Signal Processing (ICASSP)}, + pages={4779--4783}, + year={2018}, + organization={IEEE} +} +@inproceedings{souden2009optimal, + title={On optimal frequency-domain multichannel linear filtering for noise reduction}, + author={Souden, Mehrez and Benesty, Jacob and Affes, Sofiene}, + booktitle={IEEE Transactions on audio, speech, and language processing}, + volume={18}, + number={2}, + pages={260--276}, + year={2009}, + publisher={IEEE} +} +@inproceedings{higuchi2016robust, + title={Robust MVDR beamforming using time-frequency masks for online/offline ASR in noise}, + author={Higuchi, Takuya and Ito, Nobutaka and Yoshioka, Takuya and Nakatani, Tomohiro}, + booktitle={2016 IEEE International Conference on Acoustics, Speech and Signal Processing (ICASSP)}, + pages={5210--5214}, + year={2016}, + organization={IEEE} +} +@article{mises1929praktische, + title={Praktische Verfahren der Gleichungsaufl{\"o}sung.}, + author={Mises, RV and Pollaczek-Geiringer, Hilda}, + journal={ZAMM-Journal of Applied Mathematics and Mechanics/Zeitschrift f{\"u}r Angewandte Mathematik und Mechanik}, + volume={9}, + number={1}, + pages={58--77}, + year={1929}, + publisher={Wiley Online Library} +} +@article{higuchi2017online, + title={Online MVDR beamformer based on complex Gaussian mixture model with spatial prior for noise robust ASR}, + author={Higuchi, Takuya and Ito, Nobutaka and Araki, Shoko and Yoshioka, Takuya and Delcroix, Marc and Nakatani, Tomohiro}, + journal={IEEE/ACM Transactions on Audio, Speech, and Language Processing}, + volume={25}, + number={4}, + pages={780--793}, + year={2017}, + publisher={IEEE} +} diff --git a/docs/source/sox_effects.rst b/docs/source/sox_effects.rst new file mode 100644 index 00000000..6eee11d8 --- /dev/null +++ b/docs/source/sox_effects.rst @@ -0,0 +1,33 @@ +.. _sox_effects: + +torchaudio.sox_effects +====================== + +.. currentmodule:: torchaudio.sox_effects + +Resource initialization / shutdown +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: init_sox_effects + +.. autofunction:: shutdown_sox_effects + +Listing supported effects +~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. autofunction:: effect_names + +Applying effects +~~~~~~~~~~~~~~~~ + +Apply SoX effects chain on torch.Tensor or on file and load as torch.Tensor. + +Applying effects on Tensor +-------------------------- + +.. autofunction:: apply_effects_tensor + +Applying effects on file +------------------------ + +.. autofunction:: apply_effects_file diff --git a/docs/source/torchaudio.rst b/docs/source/torchaudio.rst new file mode 100644 index 00000000..cb616d01 --- /dev/null +++ b/docs/source/torchaudio.rst @@ -0,0 +1,32 @@ +torchaudio +========== + +I/O functionalities +~~~~~~~~~~~~~~~~~~~ + +Audio I/O functions are implemented in :ref:`torchaudio.backend` module, but for the ease of use, the following functions are made available on :mod:`torchaudio` module. There are different backends available and you can switch backends with :func:`set_audio_backend`. + +Refer to :ref:`backend` for the detail. + +.. function:: torchaudio.info(filepath: str, ...) + + Fetch meta data of an audio file. Refer to :ref:`backend` for the detail. + +.. function:: torchaudio.load(filepath: str, ...) + + Load audio file into torch.Tensor object. Refer to :ref:`backend` for the detail. + +.. function:: torchaudio.save(filepath: str, src: torch.Tensor, sample_rate: int, ...) + + Save torch.Tensor object into an audio format. Refer to :ref:`backend` for the detail. + +.. currentmodule:: torchaudio + +Backend Utilities +~~~~~~~~~~~~~~~~~ + +.. autofunction:: list_audio_backends + +.. autofunction:: get_audio_backend + +.. autofunction:: set_audio_backend diff --git a/docs/source/transforms.rst b/docs/source/transforms.rst new file mode 100644 index 00000000..f05c7eba --- /dev/null +++ b/docs/source/transforms.rst @@ -0,0 +1,211 @@ +.. role:: hidden + :class: hidden-section + +torchaudio.transforms +====================== + +.. currentmodule:: torchaudio.transforms + +Transforms are common audio transforms. They can be chained together using :class:`torch.nn.Sequential` + +:hidden:`Utility` +~~~~~~~~~~~~~~~~~~ + +:hidden:`AmplitudeToDB` +----------------------- + +.. autoclass:: AmplitudeToDB + + .. automethod:: forward + +:hidden:`MelScale` +------------------ + +.. autoclass:: MelScale + + .. automethod:: forward + +:hidden:`InverseMelScale` +------------------------- + +.. autoclass:: InverseMelScale + + .. automethod:: forward + +:hidden:`MuLawEncoding` +----------------------- + +.. autoclass:: MuLawEncoding + + .. automethod:: forward + +:hidden:`MuLawDecoding` +----------------------- + +.. autoclass:: MuLawDecoding + + .. automethod:: forward + +:hidden:`Resample` +------------------ + +.. autoclass:: Resample + + .. automethod:: forward + +:hidden:`FrequencyMasking` +-------------------------- + +.. autoclass:: FrequencyMasking + + .. automethod:: forward + +:hidden:`TimeMasking` +--------------------- + +.. autoclass:: TimeMasking + + .. automethod:: forward + +:hidden:`TimeStretch` +--------------------- + +.. autoclass:: TimeStretch + + .. automethod:: forward + +:hidden:`Fade` +-------------- + +.. autoclass:: Fade + + .. automethod:: forward + +:hidden:`Vol` +------------- + +.. autoclass:: Vol + + .. automethod:: forward + +:hidden:`Complex Utility` +~~~~~~~~~~~~~~~~~~~~~~~~~ + +:hidden:`ComplexNorm` +--------------------- + +.. autoclass:: ComplexNorm + + .. automethod:: forward + +:hidden:`Feature Extractions` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:hidden:`Spectrogram` +--------------------- + +.. autoclass:: Spectrogram + + .. automethod:: forward + +:hidden:`InverseSpectrogram` +---------------------------- + +.. autoclass:: InverseSpectrogram + + .. automethod:: forward + +:hidden:`MelSpectrogram` +------------------------ + +.. autoclass:: MelSpectrogram + + .. automethod:: forward + +:hidden:`GriffinLim` +-------------------- + +.. autoclass:: GriffinLim + + .. automethod:: forward + +:hidden:`MFCC` +-------------- + +.. autoclass:: MFCC + + .. automethod:: forward + +:hidden:`LFCC` +-------------- + +.. autoclass:: LFCC + + .. automethod:: forward + +:hidden:`ComputeDeltas` +----------------------- + +.. autoclass:: ComputeDeltas + + .. automethod:: forward + +:hidden:`PitchShift` +-------------------- + +.. autoclass:: PitchShift + + .. automethod:: forward + +:hidden:`SlidingWindowCmn` +-------------------------- + +.. autoclass:: SlidingWindowCmn + + .. automethod:: forward + +:hidden:`SpectralCentroid` +-------------------------- + +.. autoclass:: SpectralCentroid + + .. automethod:: forward + +:hidden:`Vad` +------------- + +.. autoclass:: Vad + + .. automethod:: forward + +:hidden:`Loss` +~~~~~~~~~~~~~~ + +:hidden:`RNNTLoss` +------------------ + +.. autoclass:: RNNTLoss + + .. automethod:: forward + +:hidden:`Multi-channel` +~~~~~~~~~~~~~~~~~~~~~~~ + +:hidden:`PSD` +------------- + +.. autoclass:: PSD + + .. automethod:: forward + +:hidden:`MVDR` +-------------- + +.. autoclass:: MVDR + + .. automethod:: forward + +References +~~~~~~~~~~ + +.. footbibliography:: diff --git a/docs/source/utils.rst b/docs/source/utils.rst new file mode 100644 index 00000000..dc5ad0fd --- /dev/null +++ b/docs/source/utils.rst @@ -0,0 +1,11 @@ +torchaudio.utils +================ + +torchaudio.utils.sox_utils +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Utility module to configure libsox. +This affects functionalities in :ref:`Sox IO backend` and :ref:`Sox Effects`. + +.. automodule:: torchaudio.utils.sox_utils + :members: diff --git a/examples/beamforming/MVDR_tutorial.ipynb b/examples/beamforming/MVDR_tutorial.ipynb new file mode 100644 index 00000000..1cd69ee1 --- /dev/null +++ b/examples/beamforming/MVDR_tutorial.ipynb @@ -0,0 +1,578 @@ +{ + "nbformat": 4, + "nbformat_minor": 2, + "metadata": { + "colab": { + "name": "Copy of Copy of torchaudio_MVDR_tutorial.ipynb", + "provenance": [], + "collapsed_sections": [] + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3.9.6 64-bit ('dev': conda)" + }, + "language_info": { + "name": "python", + "version": "3.9.6", + "mimetype": "text/x-python", + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "pygments_lexer": "ipython3", + "nbconvert_exporter": "python", + "file_extension": ".py" + }, + "interpreter": { + "hash": "6a702c257b9a40163843ba760790c17a6ddd2abeef8febce55475eea4b92c28c" + } + }, + "cells": [ + { + "cell_type": "markdown", + "source": [ + "\"Open" + ], + "metadata": { + "id": "xheYDPUcYGbp" + } + }, + { + "cell_type": "markdown", + "source": [ + "This is a tutorial on how to apply MVDR beamforming by using [torchaudio](https://github.com/pytorch/audio)\n", + "-----------\n", + "\n", + "The multi-channel audio example is selected from [ConferencingSpeech](https://github.com/ConferencingSpeech/ConferencingSpeech2021) dataset. \n", + "\n", + "```\n", + "original filename: SSB07200001\\#noise-sound-bible-0038\\#7.86_6.16_3.00_3.14_4.84_134.5285_191.7899_0.4735\\#15217\\#25.16333303751458\\#0.2101221178590021.wav\n", + "```\n", + "\n", + "Note:\n", + "- You need to use the nightly torchaudio in order to use the MVDR and InverseSpectrogram modules.\n", + "\n", + "\n", + "Steps\n", + "\n", + "- Ideal Ratio Mask (IRM) is generated by dividing the clean/noise magnitude by the mixture magnitude.\n", + "- We test all three solutions (``ref_channel``, ``stv_evd``, ``stv_power``) of torchaudio's MVDR module.\n", + "- We test the single-channel and multi-channel masks for MVDR beamforming. The multi-channel mask is averaged along channel dimension when computing the covariance matrices of speech and noise, respectively." + ], + "metadata": { + "id": "L6R0MXe5Wr19" + } + }, + { + "cell_type": "code", + "execution_count": null, + "source": [ + "!pip install --pre torchaudio -f https://download.pytorch.org/whl/nightly/torch_nightly.html --force" + ], + "outputs": [], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "juO6PE9XLctD", + "outputId": "8777ba14-da99-4c18-d80f-b070ad9861af" + } + }, + { + "cell_type": "code", + "execution_count": null, + "source": [ + "import torch\n", + "import torchaudio\n", + "import IPython.display as ipd" + ], + "outputs": [], + "metadata": { + "id": "T4u4unhFMMBG" + } + }, + { + "cell_type": "markdown", + "source": [ + "### Load audios of mixture, reverberated clean speech, and dry clean speech." + ], + "metadata": { + "id": "bDILVXkeg2s3" + } + }, + { + "cell_type": "code", + "execution_count": null, + "source": [ + "!curl -LJO https://github.com/nateanl/torchaudio_mvdr_tutorial/raw/main/wavs/mix.wav\n", + "!curl -LJO https://github.com/nateanl/torchaudio_mvdr_tutorial/raw/main/wavs/reverb_clean.wav\n", + "!curl -LJO https://github.com/nateanl/torchaudio_mvdr_tutorial/raw/main/wavs/clean.wav" + ], + "outputs": [], + "metadata": { + "id": "2XIyMa_VKv0c", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "404f46a6-e70c-4f80-af8d-d356408a9f18" + } + }, + { + "cell_type": "code", + "execution_count": null, + "source": [ + "mix, sr = torchaudio.load('mix.wav')\n", + "reverb_clean, sr2 = torchaudio.load('reverb_clean.wav')\n", + "clean, sr3 = torchaudio.load('clean.wav')\n", + "assert sr == sr2\n", + "noise = mix - reverb_clean" + ], + "outputs": [], + "metadata": { + "id": "iErB6UhQPtD3" + } + }, + { + "cell_type": "markdown", + "source": [ + "## Note: The MVDR Module requires ``torch.cdouble`` dtype for noisy STFT. We need to convert the dtype of the waveforms to ``torch.double``" + ], + "metadata": { + "id": "Aq-x_fo5VkwL" + } + }, + { + "cell_type": "code", + "execution_count": null, + "source": [ + "mix = mix.to(torch.double)\n", + "noise = noise.to(torch.double)\n", + "clean = clean.to(torch.double)\n", + "reverb_clean = reverb_clean.to(torch.double)" + ], + "outputs": [], + "metadata": { + "id": "5c66pHcQV0P9" + } + }, + { + "cell_type": "markdown", + "source": [ + "### Initilize the Spectrogram and InverseSpectrogram modules" + ], + "metadata": { + "id": "05D26we0V4P-" + } + }, + { + "cell_type": "code", + "execution_count": null, + "source": [ + "stft = torchaudio.transforms.Spectrogram(n_fft=1024, hop_length=256, return_complex=True, power=None)\n", + "istft = torchaudio.transforms.InverseSpectrogram(n_fft=1024, hop_length=256)" + ], + "outputs": [], + "metadata": { + "id": "NcGhD7_TUKd1" + } + }, + { + "cell_type": "markdown", + "source": [ + "### Compute the complex-valued STFT of mixture, clean speech, and noise" + ], + "metadata": { + "id": "-dlJcuSNUCgA" + } + }, + { + "cell_type": "code", + "execution_count": null, + "source": [ + "spec_mix = stft(mix)\n", + "spec_clean = stft(clean)\n", + "spec_reverb_clean = stft(reverb_clean)\n", + "spec_noise = stft(noise)" + ], + "outputs": [], + "metadata": { + "id": "w1vO7w1BUKt4" + } + }, + { + "cell_type": "markdown", + "source": [ + "### Generate the Ideal Ratio Mask (IRM)\n", + "Note: we found using the mask directly peforms better than using the square root of it. This is slightly different from the definition of IRM." + ], + "metadata": { + "id": "8SBchrDhURK1" + } + }, + { + "cell_type": "code", + "execution_count": null, + "source": [ + "def get_irms(spec_clean, spec_noise, spec_mix):\n", + " mag_mix = spec_mix.abs() ** 2\n", + " mag_clean = spec_clean.abs() ** 2\n", + " mag_noise = spec_noise.abs() ** 2\n", + " irm_speech = mag_clean / (mag_clean + mag_noise)\n", + " irm_noise = mag_noise / (mag_clean + mag_noise)\n", + "\n", + " return irm_speech, irm_noise" + ], + "outputs": [], + "metadata": { + "id": "2gB63BoWUmHZ" + } + }, + { + "cell_type": "markdown", + "source": [ + "## Note: We use reverberant clean speech as the target here, you can also set it to dry clean speech" + ], + "metadata": { + "id": "reGMDyNCaE7L" + } + }, + { + "cell_type": "code", + "execution_count": null, + "source": [ + "irm_speech, irm_noise = get_irms(spec_reverb_clean, spec_noise, spec_mix)" + ], + "outputs": [], + "metadata": { + "id": "HSTCGy_5Uqzx" + } + }, + { + "cell_type": "markdown", + "source": [ + "### Apply MVDR beamforming by using multi-channel masks" + ], + "metadata": { + "id": "1R5I_TmSUbS0" + } + }, + { + "cell_type": "code", + "execution_count": null, + "source": [ + "results_multi = {}\n", + "for solution in ['ref_channel', 'stv_evd', 'stv_power']:\n", + " mvdr = torchaudio.transforms.MVDR(ref_channel=0, solution=solution, multi_mask=True)\n", + " stft_est = mvdr(spec_mix, irm_speech, irm_noise)\n", + " est = istft(stft_est, length=mix.shape[-1])\n", + " results_multi[solution] = est" + ], + "outputs": [], + "metadata": { + "id": "SiWFZgCbadz7" + } + }, + { + "cell_type": "markdown", + "source": [ + "### Apply MVDR beamforming by using single-channel masks \n", + "(We use the 1st channel as an example. The channel selection may depend on the design of the microphone array)" + ], + "metadata": { + "id": "Ukez6_lcUfna" + } + }, + { + "cell_type": "code", + "execution_count": null, + "source": [ + "results_single = {}\n", + "for solution in ['ref_channel', 'stv_evd', 'stv_power']:\n", + " mvdr = torchaudio.transforms.MVDR(ref_channel=0, solution=solution, multi_mask=False)\n", + " stft_est = mvdr(spec_mix, irm_speech[0], irm_noise[0])\n", + " est = istft(stft_est, length=mix.shape[-1])\n", + " results_single[solution] = est" + ], + "outputs": [], + "metadata": { + "id": "kLeNKsk-VLm5" + } + }, + { + "cell_type": "markdown", + "source": [ + "### Compute Si-SDR scores" + ], + "metadata": { + "id": "uJjJNdYiUnf0" + } + }, + { + "cell_type": "code", + "execution_count": null, + "source": [ + "def si_sdr(estimate, reference, epsilon=1e-8):\n", + " estimate = estimate - estimate.mean()\n", + " reference = reference - reference.mean()\n", + " reference_pow = reference.pow(2).mean(axis=1, keepdim=True)\n", + " mix_pow = (estimate * reference).mean(axis=1, keepdim=True)\n", + " scale = mix_pow / (reference_pow + epsilon)\n", + "\n", + " reference = scale * reference\n", + " error = estimate - reference\n", + "\n", + " reference_pow = reference.pow(2)\n", + " error_pow = error.pow(2)\n", + "\n", + " reference_pow = reference_pow.mean(axis=1)\n", + " error_pow = error_pow.mean(axis=1)\n", + "\n", + " sisdr = 10 * torch.log10(reference_pow) - 10 * torch.log10(error_pow)\n", + " return sisdr.item()" + ], + "outputs": [], + "metadata": { + "id": "MgmAJcyiU-FU" + } + }, + { + "cell_type": "markdown", + "source": [ + "### Single-channel mask results" + ], + "metadata": { + "id": "3TCJEwTOUxci" + } + }, + { + "cell_type": "code", + "execution_count": null, + "source": [ + "for solution in results_single:\n", + " print(solution+\": \", si_sdr(results_single[solution][None,...], reverb_clean[0:1]))" + ], + "outputs": [], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "NrUXXj98VVY7", + "outputId": "bc113347-70e3-47a9-8479-8aeeeca80abf" + } + }, + { + "cell_type": "markdown", + "source": [ + "### Multi-channel mask results" + ], + "metadata": { + "id": "-7AnjM-gU3c8" + } + }, + { + "cell_type": "code", + "execution_count": null, + "source": [ + "for solution in results_multi:\n", + " print(solution+\": \", si_sdr(results_multi[solution][None,...], reverb_clean[0:1]))" + ], + "outputs": [], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "S_VINTnlXobM", + "outputId": "234b5615-63e7-44d8-f816-a6cc05999e52" + } + }, + { + "cell_type": "markdown", + "source": [ + "### Display the mixture audio" + ], + "metadata": { + "id": "_vOK8vgmU_UP" + } + }, + { + "cell_type": "code", + "execution_count": null, + "source": [ + "print(\"Mixture speech\")\n", + "ipd.Audio(mix[0], rate=16000)" + ], + "outputs": [], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 92 + }, + "id": "QaKauQIHYctE", + "outputId": "674c7f9b-62a3-4298-81ac-d3ab1ee43cd7" + } + }, + { + "cell_type": "markdown", + "source": [ + "### Display the noise" + ], + "metadata": { + "id": "R-QGGm87VFQI" + } + }, + { + "cell_type": "code", + "execution_count": null, + "source": [ + "print(\"Noise\")\n", + "ipd.Audio(noise[0], rate=16000)" + ], + "outputs": [], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 92 + }, + "id": "l1WgzxIZYhlk", + "outputId": "7b100679-b4a0-47ff-b30b-9f4cb9dca3d1" + } + }, + { + "cell_type": "markdown", + "source": [ + "### Display the clean speech" + ], + "metadata": { + "id": "P3kB-jzpVKKu" + } + }, + { + "cell_type": "code", + "execution_count": null, + "source": [ + "print(\"Clean speech\")\n", + "ipd.Audio(clean[0], rate=16000)" + ], + "outputs": [], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 92 + }, + "id": "pwAWvlRAVJkT", + "outputId": "5e173a1b-2ba8-4797-8f3a-e41cbf05ac2b" + } + }, + { + "cell_type": "markdown", + "source": [ + "### Display the enhanced audios¶" + ], + "metadata": { + "id": "RIlyzL1wVTnr" + } + }, + { + "cell_type": "code", + "execution_count": null, + "source": [ + "print(\"multi-channel mask, ref_channel solution\")\n", + "ipd.Audio(results_multi['ref_channel'], rate=16000)" + ], + "outputs": [], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 92 + }, + "id": "M3YQsledVIQ5", + "outputId": "43d9ee34-6933-401b-baf9-e4cdb7d79b63" + } + }, + { + "cell_type": "code", + "execution_count": null, + "source": [ + "print(\"multi-channel mask, stv_evd solution\")\n", + "ipd.Audio(results_multi['stv_evd'], rate=16000)" + ], + "outputs": [], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 92 + }, + "id": "UhYOHLvCVWBN", + "outputId": "761468ec-ebf9-4b31-ad71-bfa2e15fed37" + } + }, + { + "cell_type": "code", + "execution_count": null, + "source": [ + "print(\"multi-channel mask, stv_power solution\")\n", + "ipd.Audio(results_multi['stv_power'], rate=16000)" + ], + "outputs": [], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 92 + }, + "id": "9dv8VDtCVXzd", + "outputId": "1ae61ea3-d3c4-479f-faad-7439f942aac1" + } + }, + { + "cell_type": "code", + "execution_count": null, + "source": [ + "print(\"single-channel mask, ref_channel solution\")\n", + "ipd.Audio(results_single['ref_channel'], rate=16000)" + ], + "outputs": [], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 92 + }, + "id": "jCFUN890VZdh", + "outputId": "c0d2a928-5dd0-4584-b277-7838ac4a9e6b" + } + }, + { + "cell_type": "code", + "execution_count": null, + "source": [ + "print(\"single-channel mask, stv_evd solution\")\n", + "ipd.Audio(results_single['stv_evd'], rate=16000)" + ], + "outputs": [], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 92 + }, + "id": "hzlzagsKVbAv", + "outputId": "96af9e37-82ca-4544-9c08-421fe222bde4" + } + }, + { + "cell_type": "code", + "execution_count": null, + "source": [ + "print(\"single-channel mask, stv_power solution\")\n", + "ipd.Audio(results_single['stv_power'], rate=16000)" + ], + "outputs": [], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 92 + }, + "id": "A4igQpTnVctG", + "outputId": "cf968089-9274-4c1c-a1a5-32b220de0bf9" + } + } + ] +} \ No newline at end of file diff --git a/examples/interactive_asr/README.md b/examples/interactive_asr/README.md new file mode 100644 index 00000000..39a5c53b --- /dev/null +++ b/examples/interactive_asr/README.md @@ -0,0 +1,63 @@ +# asr-demo + +To run this demo, you need the following libraries +- [python3](https://www.python.org/download/releases/3.0/) +- [pyaudio](https://people.csail.mit.edu/hubert/pyaudio/) +- [torchaudio](https://github.com/pytorch/audio/tree/master/torchaudio) +- [pytorch](https://pytorch.org/) +- [librosa](https://librosa.github.io/librosa/) +- [fairseq](https://github.com/pytorch/fairseq) (clone the github repository) +and the following models +- [dictionary](https://download.pytorch.org/models/audio/dict.txt) +- [sentence piece model](https://download.pytorch.org/models/audio/spm.model) +- [model](https://download.pytorch.org/models/audio/checkpoint_avg_60_80.pt) + +## Installation + +We recommend that you use [conda](https://docs.conda.io/en/latest/miniconda.html) to install the dependencies when available. +```bash +# Assume that all commands are from the examples folder +cd examples + +# Install dependencies +conda install -c pytorch torchaudio +conda install -c conda-forge librosa +conda install pyaudio +pip install sentencepiece + +# Install fairseq from source +git clone https://github.com/pytorch/fairseq interactive_asr/fairseq +pushd interactive_asr/fairseq +export CFLAGS='-stdlib=libc++' # For Mac only +pip install --editable . +popd + +# Install dictionary, sentence piece model, and model +wget -O interactive_asr/data/dict.txt https://download.pytorch.org/models/audio/dict.txt +wget -O interactive_asr/data/spm.model https://download.pytorch.org/models/audio/spm.model +wget -O interactive_asr/data/model.pt https://download.pytorch.org/models/audio/checkpoint_avg_60_80.pt +``` + +## Run +On a file +```bash +INPUT_FILE=interactive_asr/data/sample.wav +python -m interactive_asr.asr interactive_asr/data --input_file $INPUT_FILE --max-tokens 10000000 --nbest 1 \ + --path interactive_asr/data/model.pt --beam 40 --task speech_recognition \ + --user-dir interactive_asr/fairseq/examples/speech_recognition +``` + +As a microphone +```bash +python -m interactive_asr.asr interactive_asr/data --max-tokens 10000000 --nbest 1 \ + --path interactive_asr/data/model.pt --beam 40 --task speech_recognition \ + --user-dir interactive_asr/fairseq/examples/speech_recognition +``` +To run the testcase associated with this example +```bash +ASR_MODEL_PATH=interactive_asr/data/model.pt \ +ASR_INPUT_FILE=interactive_asr/data/sample.wav \ +ASR_DATA_PATH=interactive_asr/data \ +ASR_USER_DIR=interactive_asr/fairseq/examples/speech_recognition \ +python -m unittest test/test_interactive_asr.py +``` diff --git a/examples/interactive_asr/__init__.py b/examples/interactive_asr/__init__.py new file mode 100644 index 00000000..57e1c910 --- /dev/null +++ b/examples/interactive_asr/__init__.py @@ -0,0 +1,3 @@ +from . import utils, vad + +__all__ = ['utils', 'vad'] diff --git a/examples/interactive_asr/asr.py b/examples/interactive_asr/asr.py new file mode 100644 index 00000000..8e2510f3 --- /dev/null +++ b/examples/interactive_asr/asr.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# Copyright (c) 2017-present, Facebook, Inc. +# All rights reserved. +# +# This source code is licensed under the license found in the LICENSE file in +# the root directory of this source tree. An additional grant of patent rights +# can be found in the PATENTS file in the same directory. +""" +Run inference for pre-processed data with a trained model. +""" + +import datetime as dt +import logging + +from fairseq import options + +from interactive_asr.utils import add_asr_eval_argument, setup_asr, get_microphone_transcription, transcribe_file + + +def main(args): + logger = logging.getLogger(__name__) + logger.setLevel(logging.INFO) + task, generator, models, sp, tgt_dict = setup_asr(args, logger) + + print("READY!") + if args.input_file: + transcription_time, transcription = transcribe_file(args, task, generator, models, sp, tgt_dict) + print("transcription:", transcription) + print("transcription_time:", transcription_time) + else: + for transcription in get_microphone_transcription(args, task, generator, models, sp, tgt_dict): + print( + "{}: {}".format( + dt.datetime.now().strftime("%H:%M:%S"), transcription[0][0] + ) + ) + + +def cli_main(): + parser = options.get_generation_parser() + parser = add_asr_eval_argument(parser) + args = options.parse_args_and_arch(parser) + main(args) + + +if __name__ == "__main__": + cli_main() diff --git a/examples/interactive_asr/data/sample.wav b/examples/interactive_asr/data/sample.wav new file mode 100644 index 0000000000000000000000000000000000000000..d52e38dbb09005e008ce437a5c6c683e040968d4 GIT binary patch literal 676174 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≫*etS% z&Y%w|Cv*xjg*k%nKz&bv#P^Zeq>IcY-;s9GLzbf!!6S4A6{b468V)^#8e3^6?gWf` zg0R2~<$z~aBixG0aYE1^^<@C+e;eT2R^k)lXJjerHI+giqY9{obx8D!CKk-Ok^_M%%%hDzHHOnWsE zD>@HsydAydBkV^{RL2s63kfSw!@i<^Ttt6=9FEWSk$%w8Iz{_ODn;92^*R@AiQIa2 zLK5(A0xIuc=no}=3?FnXc5q7)e)>4_xZ6rl7w!lS}|=rcQ_OYaM(rq-9~v$&O>%idgX zn?R>P_aGOX0}X2qu*=o3BC-QI?VALdU^95#li~6aF1R>&I`AP-K2SYC2C4(d*m1R5BXp4W_~P= z-&?HX{UVpb!@`e4XZX5&R^XbyS>T3$NY&@ z5A#p?+q{R5@VP-D7-2qiJSYvRfeN4U>wx1h)sf@wtH_ z|L^{{{wn@Df$RP>{!?I8a8>Xs(AqGdq$xmDX4H$gJ8L%Fu*N_I{)e9k%n6JQY~`o( zAuzwsLz3_e=zx9DXY3Ca1_$%A_)fv(;N-A1)FXB^S^$n@5K%+9zuUfF zzB<0gKAUg1e?LDkcq6hh`YK_XpaD9&ba?Qmf(KoQbMY#AkDnoT@KAVPr~tLu0{v_` z5WrVqMbVdN!&r5G3*W`}+_%V=aI;>D+yUww6e%111&sMKl8M_dpjdTuSHuA3^CnPSHNhunWLE`^k?`J% zOv55*-tkFhlThzU2rLPWMW=-OM9+jixEyXosc4DF?r^6_EvS{dBVF(b%!`dmNP`}= ziTDfscovxrUab_?;(5?7_5=Gp1jZUCTrRkR)&4HL`iw|~Pv|3|18RmhniN|ZmBxDD zb2=Q3nu&0@bjI42jlR4+_)9tTI~T45I11>l7Tg~5p-=q-4@v{F6xo&>NOk}ZSdJix z5u&HU_M%-vT66(^_hrJ?Kv?U*yKNIyhW7OX8c;Ua!2r0)H)7x-;+jw=dIaQ=Nlhao zbUQko=|>-?^2j@68FC*mOdgKM79dC(nC5`EIhg_1Wqm>?t_ZX=L3B(s4t0!&GWG)N z>mV?KqrrQI$VFs2eV-agSEavEPpBQ#C29)wk}`_hkXOO6)`XP309qa|Dvm7;fT!lQ zxC_?u(ZB)|;cvMq(hz6KL~0Aeu-jQX+nsC4&0vd|rSt>p8JR&Yf^YR99JHT-q?f~$ z`xsqAd#IMxq5BOdnu*JhX;cw;of<%2qW7~4*t5)FhM+E!FMwF;i&qo3foiUZ0#GJ7 zVIAZfKH~YF3%iK)!nvZ`qTh&pcx7Wm$HB&x0!mpc`ieT5L!G8Z(ow1;bDi!*H=-Mo zy~&5D-BDz^hDG~=L+c-_D7+{51l{Zdu?6o04Zd%g@S|X);4gs|j+oBixxWg=gW>xN z?$^24zgwh+YCvUCD)NfhDcT^4BB?htb_LOUkq|u)?g3jr5sr*>xZ!p~r(X|7C>x#eRd_u<0})L? zUnWUd11&o#`n1w6&6-S7tazqMk=V7=j`rcXSgfAo%7if?qIRV63S@c6_ z1$){UNsZ=EJU*lQ<&f0z6Klj{aZav6|5B6KL2z)feG>?UHDdooi$!{bYefcz^PtlV zLn84m*qV_@d8|f1c3r3yofS3{{S^I!YJE_g4@P%_NDlrx6Zl$)GitEtZ{X5r;7u*z z0SF9_2L9O_jQ7@3VGUs&Vm~3H&QP=IvXIZ`QEO1)YeR>h1Z27n+Q=%n^7PP1J44g$ zN!B4hLub8#nI(&WNX|i%I~3d$+!bye9vpieeFhF#gbuWupdI?w=LtN#S~tY`VmsB7 zo<d{zlZiQYz@M``{cKgnzG z{^!!V!>%m1%T?&gc5B_iK(By0LcuMZ9r^;4ejY3LjmW;pQ~oM{)5W`J&pOZQkUKOR z`0En#YCYkV>?JXQj9jBab=OfQo&-Jh>vOzEi*`x2m+~~|$Hfr}3 zugjb2P4+hNUh#+nI{$UlS^NL>=nj!}=)Laneffq#S@2h&GCzq|fDyhJ-0v^+tNE6E zYar6iNO87@PmTj8G6!nM5rIaS1Gdlt$I?UJXf$kszed!x+ zi-i{9IQSK%P-{j&%lJF0jy;JsM#`jjY<$9VXrddUr?D2!2B*F-B8!ZM_cemr(~q1% zj>BrS7Ik}zXa#Xkcm=BAGpxA36K7Ee=0i)Ous4t3H`>Z`d;`8hpnjxPI3PGJH~|Oh zZu&kwiao+^<}{KiEXkRnsr>^U=}}k~Sq6vJJ^l?p9v*-Q91A5-cgA4F2oOT?Nbp~~ zpaaFHJ#>b)Iu86{XDXBWjQU{$k30+BhqmA%OCU{9Qcy{-K3XyMJbXEFFw`}?AN}G2 z_#19gU#PZ{RLKO!NEaj4irNTnU^YSJSYhN<_;BQVxC^wY8t|+3#C|>{+MqhG5Z^+L zOF?$B8(5QqVAarTxaXF{98rz1i+~{di0;7&YK1<$1SuswnDI_v4Cj-h$+2Jve-P!x zJ-~|2M=JA3WF55d7m@h2fdpXVPJ%HtiONAK-GPcUjWEEkdJhcXUaY|-kqc-L@rTt3 z`(w57P8|__!aLViI8QhT-0oaBgd##Q^v!9ch}tDeB+`&rxE*Z(k5tu!5y0$UQHk4= zJ;ZyMvvhCfGQAabuR_8n@cRp+6QDBPhP#`Ou8AE3+FQYDxucT0(&G|FI-DEL9is+N zyW`%+V2NNDE{x0YIqd{1{8+SFv=6*&58%s}$fnGNuQ;2WNZzJ*(=9kVJD$^WN2o27 zj_gM^r*2an=p%FyrKK%`3}F@MT2uwi&9cEX$76gu4krmuhDV5V}7IGQE!nv zc>uS~3h{h#W%P2d$OTjaRh<5tnnJH(r0jI=8`q3m!d0QtsMF9dmtmgfR=6p;g!4mH z#gB+v@MJ;{iVTAuLVQ0 zE!-e7An+|v(pSs3)IHAKyr@ml2xn90QFkf#g`kNa2Ua))Pr*<^L+pVAd@A()ve1@F z_o&J|9LbC+CsE zSz-VjI~UwdJ%72@y8dzXbG>vmaP@WnaFukr9hnx9rHaF8zZ+;D=!$c0r}(Vc!<1s* zkfo_%V9Ec5FZNd8QsApQ*S*@c6Q*5(D_rC#Qlk!a@pthz3bqPSWYZOs-P>p zG>*WLz-i|?XFppLTNUqH4-=agZ7wb%ULmKoAHJ>~V5{jsR$#L`$8B=Van5tm?r`8w z{t)=m^U$Hj0aewCUWxP%C<5#Jqx?&N#|fz8iQszTQ(nDb8V-SlE*-iVitvM=aSB5V zpk=4=qxovyzTWY^cD}RxUpyDi3rzvTD~))D$Jybe_NW_oYBzqoIE zetNDz`)mfz=`oYTw4&BggRmd6P=yfVm3W61%_tgD^tNcRugC|6Huel?s5+BFFDEm} zk8ql9cCT`uu`RJZu}!qiakp`Q;J@<5aJk5*=&)ESyptrf`jzN^;(o`v#P4A3HW1U1 zn)n@8rIbfzg`0)U!9D&d{-*A-ZoTJ|M;tN+`$p$R|3LzA3TlBnQaQ?Fy5V==nRf6f zN|C;PD0~B-enTNC%oW^)|DhC=xGQiVl@Ig}4Ds~x+;VSqFGLNU1-_(ew0|^#NGHa! z?bx0YMlzK9oomcaXKMqAT@Y3hbp&(T0&dztaM4DfV=93!i=)bPLXP?=Ts0L$SMa(+ zqAu`V5^%t~qOs^l%nJJey(~}Eg_w=rc{sO+J0d+NwQ(P~&Fl`g1e?jmsLvD$bg~)R zq6f_Lf#BBQV&8dRYyV&VFu39Q)I>@t&yc${DT!URtF4fD>n*W&lo=Q`lu%M zFd>-mAM$X|kP+z&oR((%${wvbuHE@vA{T1bv^JGd%L z1ty-(n1p__FJ9{pc#O30LDWTl{tohVqv87L48O}HV7K8|*FVZv%O|P_s+XvCsZ7$c zvZy$jq$7FZPJzh*DL)D}k*?wEct4s#3)6EKxQEJ9%1m`_H6^Pg%V8g|1(JUyQuZ~| z6?uzI@WxWe-U*}O$Q)=Y>%e75fF2ico4K!YvEs7)fm|cE%2bjl#{h3Of#OmgKJez@ za_CfRgw)U%K7d1R$kb<6N=8a1b9Fc)eVTqnULv0$8`B;-+XpH{tz`}{e^ZmG1IV|W zWN0>B(pK^eZ0>AifRFIQ`4nh9DR3mGfxCWAtRgxiC;0~J*HIvrdjhSnWh6VYo*%)- z0`|aNXdE)kWtj{RuM%{fau zTRZwY6c}F60Zz$$<{8sK@t2~VYMpAPa+7ifGn45KZ*>)aLw|Me9Iw>d3n<}~r&5p&B5EBhYNHv5+nnVS166o!@=l`s1L5WUSO~vQX1OMdAL!i6f`L!>wz;_1GOpO zllu!jw>;y0ReTHh9{gi?(yzn+wgL+K4NR4t2b9wgj=Ocx7|P>V{6Ma(B#wmx4$gj< zh%p5^ya7x@aY{k?MbAZM(B$0#LSvic&2HB&4Lv8snkrEE~JmqB$-66hU?1%9M}hFjEFvrEQDh94$k?z$T|#$ z4?GM$Z7qBwul=|EeIt`16X-kiPQ^JzLv=ZIuKI;KU7e_YDfLLzWD9Z~aK@j(+QFvY zG_ThwEh=koWw+Xs9g{uFJkrpcAQ$%s$ueZx^9R?xLdKBdx)yE(uNuTHx>~4PPLSsRj%Fp zfpudnJDGhdyDfX8+NNrzX{;%u=&Pv1b>}A1haD;NhLQhps*ii^)`(bN7- z{=Kf{t^`+Z(E{I2-+btrbHek(hoQ`c!7RtG3s1qB)W^HPJJT`WG1vB+ZLe#htEs=8 zKgLUfOQEV-pug!7-o%t)duX5cr1z=Y;x6T{XWIm;GB9c7(m?TkHa%d;J}Z?<2t zKd@)p_uFeZyEp~ze3#St%$aUaws&_vlgzr_|hiyXbvUH@u&x0v7_6!$qMkk-G4!xgr~&Oq@r$ zxH(H-Q2^5LW{ zf|p@DYQ!L9C)B)>pW*7_x@O;Q?_=+6Z&);}=z`DUn}fv4ad=p(qr#Sgmi9!zgJ%>7 zMgTvo#>ASk$TMk?JH8b79JmfH`jzjBkMZaD!oHAi3iO1*`~vvD(ueL$HvuoL{=f6I4fgpo^;LR8s!&qOU9#th)mLEpBH_^l*y1|Y){j&U z^+P6-57jK!>`9uM^jy7H{Z{rsHUWsL95sw8M|LAqNrE)PRayWhp&FG<^_47=oRvM5 zjg@zluaUKtIav$aiWorLhX>r||KTqd_~~y7XT(~}Y%%=VCJhrk!T7gdc5I(^8JEMf>V)4>k5{; zjby5%hNhgR94>a;P!(3L1Op=&NWt(((tw zSHjhxn@8XqdxzA}DJbT5q9Q18t-)Fjiv5P9T^iWu9?@R#C>)8}k)eDd+%7x^_CCVB z=SE5Yl)hqLvR9yqFN^*Wz3X4%|EFj}k-(YcEaUp!Ro36h-vkphC$R(An4*eut?HfX zPuY4|7Ttud0;YN|<~jvJ3E^V?j{duz7oLN@***&Hlw#35_yd39b3F!Cf)3A(_bK3# zM#4L17L_0}sJYa2#=>-Deq%;K>v#Z%_F_!snFQS?o*0`9ugy5%sV=CY3+Ux^Wq7tb zMm5p(@LAqMpY8Pf{Y#;AK7p@W&c>Lzl3o%ur{?xxo2S?aJDS_Tm18Th62?z&W416i zXe*sZNX2U~+oK8Tzal-B>`b!am+*~GB+g>;L^k~g zt>koEMM*h{R2r6al2(&;l@FFH)nnAO`VaL@`B(Wbj8B$xzI3n?h3?3eU}9aQoMo_sn+nYW3oj1-Ly+sj1$g zo=qR6AHtz|#D5-@bie;S{G+EZ=c^YIJgr4M62XO-fV55UANs6unEa9&{TP{wOh#RJ zcR1wrp8~&4h9jpE-=>L0eR>r25TDiYWEE&MXOUenAiXdT9L-`lh33G2Y7ATn zoOF+LS1tPFtX;%Ae--^IS_7QAzi4UECC7e8(C)UYT;*Nw1LgQPk*d+ZFkxjY=2%UI z7Kn-%dmB*lC5rkMsciu(VY^_hYWLXsIO;e&&TpuQE1jk7dA2pS61Ly$BKuYQV|!P} zVn=u9a%bP7K}F4-HJ#t=N=FZ8j&qJD-%}EOb226y)Z(@LNY7ADcgIG@0?R&22cy*Z zRXaXal8_F7rOnD}=?Yyl|QEJg2?walzMO}-=+Wxc6v;AwE?fBcVz_H0u z#WC4&#A(H)bnCkQE%Fp~ayD{~w^y@ow2rebHcm6%%wLz!=N-%2t#7P$2>>>LjS9vULjM^v0x?k?Ugy#T+v$2x&*fww(+(Twz)Qy?Y{M(t%YrhqnzWL z{gQp6t*h;=W4Gg#d#8J+Z?$hx;CNtm;A!BbkN0)*%=Bcr+PZ4F=DPlL-*8uPFLQ5o z-F2;VO?GW@4|2Ej)bR{L&vw(Z*(33sbf0tAb8o@#R(S_`J3&=X32LDEt_)X#hv_=> zjDrLz{z$f_Y(y2Y3MuMPBrAFiiR5;0f0h+@7Z1j|UxP8xBK8RLj9x{%$sCdvzaX{< z8wu-ST2?dUhcc0xS_p?&GJHN8;APy&oCmZoBkd-;BCDm?u9%Ir$*QiYnW5gGK83#Z zj_R1|h3b_`ugX>Zqt3=9{1$8OX?kd`sAjA3|nx_m- zG3jb+O-Vg8)ifiOeU(wEMs{41BdIHEDVwC+tJG*_YHDfrX=LjD>YK`-@}T0C;-dVA ze1>d`Y^iLf%qqVtZ=@uYyz->7hcc$%n6D8HK4mDD>S9vvQ ziHf9_Ne0bti8;#ZszK5-(uvGr<}_JI9u&WTqT7v(Q3U;xImW2C+iV%hC+<&)3dl0T zF_OESlzYH7V8hH!<_I2jo>`CoZA=L^i<`l1;=aO~-I@jaNlnJogchiW<;4AvMOh@i zfj+Av{67TsmZYfKQS%yQp=MDH6Ufwv>FPc7~o`A=dG}-`zOon&9IA)10N8Y|7 zeDaNBzu<68hHvpg53YjlL{q|n`Bih_s7-;-LJifm8l1T2Fpa7)`ABR-|5r%Aq)Rd^la8-qOm*fp zO*0kgG4x{UJ;l*8(Kn?qKd54KBGrP5`?;D>J*WzFBAvuU=)c(7tXguGdnf53St_X} zdBg4E#N1PME!&wL!q#GEvBTLJ>^k-+>tQvLS(4?_AJT`w*8fU`((>GP?j75lO9n?e zUphltPo|M=mkp48lpT?MlU|XQK_|VGCD@wGH9DW!#f;**;Ozd$ot7My#H2y#64_B% zSJ{5qBq@d)z134?NvF@YCHA3Su?U{~U(fwi`Hb7Cxl^kk2P1V#&%7XF zjqC}M7!S}a@X%`k6Me#)@8kS}fD%<^YhV>$gHH%v=BM%%`DFN#mb%Nkw>gJ6n>bhp z=Xh)1=ve1C4J{eanPerw7wp9c1-U?=U*99tX? z=Q-!5qSi%c9itqBtR<{Q(+txmV-MpxQ*T7_<(9BTX6+0R!hnUc%_quyBE z7&fdlG&j7}FVdIO=NHb`*VE@2=NpGwBIf4Suw{vLf;DPQv2C&Z!w|zZ!&iNtUZAJ+Web}Zx(e(CzJgN)e-)VWGYT%|Pbnx}u(+T_ zL9pO*!H2@_g_PmDzKSu~_|afDOxHiv-!DwoU&FQr8qXQKneLgUnxB|?OC{@O>vLRE zGRqn=>&-vRN6kUYZA+@{jdj1RwQY;7k*(0W9Y^)OWuv8`rM;z?Wt3%&Wr$^urJS{e z^_2aCeQ?o@BBQIVTkDzQQTi795(E7Lr;#k0ADkHU@Jw(F-tA6&5+4k#4b>?d^cS(aO%sUEo-^t2U^%L+A3U3)SjGw`PX6gqG6%om^jcKe?jr zbMg#bbKNW5QC(G?P#4txryZ{ygGyRnJ1X%-VxGE*rj>eyI!oPK{X&(Zo`+}hs4A)l zX+~)tCZ0=7O!}1=NIaUjG^ttAy`))5G0i5;F;!hvj&iorfLEwenU&pD9nl&1RCZN) z^%m79RTb5A)d8hQHC*{YDN~MA)>14~9FrTs=cdTVpqH>tnCwS0oBVs)l^!P~25#G#)emRN!$XDwv=WAMFJi%%~g--~5H zycMaJaa z`xj~3bohO0V84$dIh2a5=H35aLT>6DW)Mg4vlD)- zASMnEfD3m%($H7om;XCf1aJJ@=su|2CowZ-KavTG$j@*Zrm}9p+HFOy>LFa=98y#f zc*k4gc@3BXav1YEbKonufhhysFhBYRl3LH;p{9ky1?RB#8}NGkm|ngX^Y@BJ_e6;3 z#Yh*-97%wWb|5DB2$3}{hP*3IX3_ix(hsHSS0@8_!JV=}geuksolaI)0q?p_& zZV1mf3+LGxavZq@zG$oXf%p#j5;qb|>)+H^`Z>LaNoM8jO`!OST$nw;jp9sPCU=bc z!Or5|aDydTl9iG>oSh3x2x(JUZ`p6M*|I}2kLLsyrk= zDt{xdq3EXgp-54-QeIX*Q*>4Km4A|ZaKFE#64`TT)TuIutckR`>=d_3QUmxq%B_`L z;@U`3&;`t7_A))#QS4@P2Y;|Ku-U!YD74Xi>|Ay_)0pW=3+a*6Z`3ak(@l|@){sre z`@q#o9H)!OK}(5dL{F##p8{u5)9D5$!5K%^I zggv1i!Ct}N0<8l{;9l$d$Ahgl`OEm%`nUN~{k^;r-%d{p@4uev_|kg4p03_eo~NEh z?xk+hRmIiR*~_W1*RlU;{oCrZBwLFt8CE|k*kx;N+j?77`#4*1yU%jodf$A|(%#(L z^23yGZer4!C8iIi$>v^`R+a|V(dG^oi)pR-fvLPX%QVe&-H>YZ=?58f#@)vHrfa6r z#x0mSlkwmNoD?I z$T57;U)HxWbTw=-JTv?U%$ZxbtFVzF(a^x$%Y58&+%n3XX#Q##XIP_ep^xZa>JJ!Z z8`c{-7^)b~=ocCUhIPgP##g37(*W~e^9)l4>OK87U)mu4TX-v*#-X$}xJ4GEE|MAKJhGz6c53OW=EdgyW3L7Pz*{gyU<-ccgPB3!Wv^ zLsBISFXL9^`yONZ-51QGn?wFiwx=pm-O+FMWd2|>@X0(cxhYwJe)poJpQN;Gx700L zBAX(sEptidNe{>#OFt_Da#p=uwFKJCgru}2UGfdBR`)6Sqwc-#rB0IaHo2^Bz4p8I zmX=K(iQgPe(WTZ-*^<&iH(%E=Wk|}W)DNjkQ^%z4Pnna_J*81f)f8n)UtOy1Y4XNo zgD$K)kn$@a*&G zs+($xDo0VMu*X;ypoqv+?EfOACPU34VIF!97%s^Z^>}UNGRT7whnuY zb~1}-Ase70Oa{ZTD)iznX)CZq1-1=(@=DBh<|&xk_tX$-CixO>9fshAl|?0ly+yT! zHAO!J2ZdDy!$oT0Lh(3a6S*6!g_k%X-bt9nB1{8}A|=xq({k>M@}ZCTknQ}2+~XT~ zN)ICi+J$gHx8H~=^90+_hzg1M&Q#ZKa+cspFlz2Hl3h&hEN$bBS5 zwIt7x_r+1TR-R(=;Q_q+C*eKj!az9WLVPWC4&lFz_{ z9HI78dh}(v)Ea6CU4rhyD3~T}9b zZlV^DpOF|+k*|qzEy3?TQ|0mePsOPuLvVx! zS)Q4~0dUWC7Pk>CB8Cc=5Zi<^#m7W<#kr!R#Ao3dQKHZx8YEm!<~qVTvFbX{?GsyQ88-u@2F_v5L_&1c5`CD2r=uVDeTtGBX;_$K^*V!;ji1 z_A~kt^9NobZ(ao4oPreJDCE!AA@M&MjAt%#fPW&*Q66)6UW5qDlF7zIfqR(NQzkMq zoE7O1N(-L~j(}FXKHN6aI{GXujou1HF}rMVWK?8!v}&YRG!jaSYz{5N6dG6fRAhe? z*S1S&hh*|4;a_lTXA-mEg-C>_?ySf_ToRlR))SP5lkGATjf>zZrV`^s>oIYuNIZc& zMmb0ceVIH61=>n&pnA~%(p{Oq*&;T~uHmk*gTd;a<7Cni(g>7{%d+9}!_qmZZnx#X zt0d~H>e-s^s_N?J%B!l9>acpRrd^^`vrThQSz6Uw@vq{eqPWtB?m9&^OtnmLOEE=0 zMZQ$AL9tF{QT?axpt-M3*R)k{RS(bvG(VEAX%}jnCD%{7m9$+`JaL#>rdCK5N6W5BR!MW&N8C)hHUl>sl3^#|s!65ISgrm;vg;Jil=<-X9f6NQLe->liCW?- zq5(vK=m65uzlcG||CR+`Gc49Ep&vY(Sx6*x!Hn1yygH}~>_4> z&+AF@u5_<)zjb|gU3S@BLtGbJ1BzA^Ep%>l_H}M?($0xav;8;6M{CIHHm6vMn|qso znCzy2>7i+vsg3Efaka6~xZk+k6gDQKGL|;%(N`~gTF|iYX2DGTyTV_EjSA1^*Us;m zFV6o`u)JVrVfn&;3X%)z=U>Sym47^Me!eU3d0wf!GI>*TkL3NzCGwSdpYq=3uFgM} z_q{+}aHsHHL7&2&1&s^F=I0jlE@-O1R9Id=q3~ECRk*saaiPBOa^Y%y1^r`v6a8qt zP+voDDO{j0tqt`7}`h$kw46_X7fHLbD3Jfz1jg4NzM&nW=X(|CEJ=Ijr^oQwy z$#1fm$D!(GT2BI7H#OZhSxuYGC9Josb8J6t`>k&4DNDNbwyl|=Ua+*_Lw;HTRamxgPT{=5`UTkq8}b}^ zioDKwJ@YE(W#m5oweH98A8WoX`nKg;gKzTh&AxG8Q@*tRJoS_5ONB2he_a25IQQDG z2KkNh3JdcJ!e+wEIm+1^ImV**Ib=<>wYFBVx3OKfJ+K}!A2GGZEBIx+WjJaYW?W%v zW^8Wq7-pIn<3P)M(+XR0Yt)`&b2(2s{&RIGD(1Q68tz--oz8y?kfDrVZ8*s4g=>Up zq-3We6S_$B1WAD}f+NInQFnSbwVZV`L)bG+FLnYGk|-s+6lQt0(y2&Ryp+#WG*ZNr z|0(}c)mODvwpDJBzm|8FKZfpEQTbAts=A^QsXJ<(X&NWJN*bU1F4>e)BlUY)*Yua^ zV~b@MGZu>$n_4_EBPU~6=IE@?nWn6=nVPK18C5bjXE-w-W)3frlQA)CLa}ghWBQ=t zvx^HdXJkmTx)&=dJ}@ocTSs;(oOqF7f945(}~`sxtdu?GPOMMZ)G2KMTK6; z%3jMA5|^|S*Fd_29V0B=wj!o(|)07&=$jP?!e(@DbE_RZo2$MLCFp77G z#)!uV6Nv&rOVMdz5Q&Ap;sQ|@@odp*%n@2HK1Uv+Mj?xEmncb2C)OkP`mgvNSlGUT zxxx|&9<0**_(HxxJE^T$;qmj^VnqelCG4CG9WCleUl=q&3h-Xrx-XLME3tlC_te zl2(9OY!G^@Gty$xC|6EWlUobTZVp%hiF}2uqx6|1L;78E zMRrhjLbg%1MKW7bfvd$0VrR31n7^1>w3a?Z9S0J>hdk34@-Tc>@6cJ)rM6NJfTrsZ zdx@BEJEj-DCJ5l>sPLPpBCetMA{s=_=>lAB!$q%R_Y+n{x?m-@(=X?;P3JqFgyJNCgm*kSBI{?!S4=k z2p)w8s1mS`E;Jx`EO>xF20eFa_z_Zq!C9P%yOj(p$EuSDAs@x+_RL@Xa z)OFEUmR0?u@u(UlEzwL!-kU@l=`JQ)w3K#G@@lO^R}2?~tfxDx^(UQ3%1^wYeVo)cd4$%c&C+((?n;`d z-ItWEt)0|A=}_W;q`bt6+AB%LwL6o}Cw5N!k$69GPtyLRVo51UiHT(s=csS1YpN$f zicV9PQPxl{f=_Uu?7D1#^l#}L^zu`vqo|Ek2XZI5hNvj6FQSRY zxW>S2@G>*N?^E#DPC*xyiK`t&qZ?xkCJ*e0w1{dk6<37L=LK??TLUS4O@19;n?J%o z^KT0r^&a+-$lDJ0)%B0?ZT3^1P2O8Y&D~>*E~A$2bX9PycJ{FTYfH5JVSR0WX)&3e zn`avTG>O24el=#B_JL>3F*P!$nAe%>nYWs=&Bx8(&DAaCEz2yFrH`etd9%5J`50~) zmJz1=rcs8&hEn?8`jLfe3!4-)C`ik<<|X7`&TEBD*bA)pEUV2Oj8hCXjVldnO?Qp6O}mXR4Prw>!=L(w z#!N$w@x0+G_~qTEbH){xab}S<*DL^QEV8&wU9BA~3#_*+o6Tm_-${6`s;1uNZ>AG? zWm$#?`j3VIhTl<}V@AR>*YKyo4|cbmp^jmaezJa8L6d@{{BT~syu*3-fG?S!6@Fg- zdH?5#yf=Bh@|6XTarIQ`?_?;5}B`i6ef`)&WXnQuqE-S+Cvs~0anzI^yP z!104qeY-bAqL@^`W)UddgP9dd+$g9`^dyjn;{F z(s9{d*>T^(Ss#E=zGd&==uuQqRK4g~(JA{V$7{!O=Sz2A&jcUs5BskLo&@^y*~n~l zz(nY4f+oU^Nb(Dy)l_FEvSP^*$r4!^`7LDwRifsTrjDj>Vs_%pq>j3(DKk@7r!`M~ zmR6LyGksmLK=C!1-?GMLI5ID#w~yPCJ->JMBYy z+4QEx-W9u8yj6xevu#G(%w8GuGEZe*&-$AAFzai^&CE9$dowF!eah;Z#b>?1HXaru zi{H$6olz`nU{-R*l#G*U7t=bYF=@Zj@1!?OADsR`_e&?#+LPWT{Y>hTbT+9c^xJRB zlB!fiPlZp`R=x#3jsZ}Wdcc<(#OLoKX6_h}L-|frA=1eK;>$?5^blnWrx86wIpk%r z0e;)3M3JaF(Mse(wVO#^6JN))yjPf2uuy2lOo`iI{qk{DvyPbX>&NwPl(8^0HhZ)^ zI^IK3CblR#0G#1=xL{6To=Csg-Dr!1x0uo~B=#@7u*cy8uY_%$qPkHNsM27Adr?}Z z91~Unh>y z)JV+MP>D^|5w&08SKgN0l=ISSvQ<#y8%i2WYe7w}NB^MjQ4?u1^@x5?j;A_+<9$SA zV6yFFB$a!B&5RO<+6;MxQH z>m7J1cJfYs7qT9e{R8|ry&B)2-sj$L-sZlm-Wt9}UZ3}>cb1RzZ}mU&cMi<^-I-Q<@vk`XzfMT_u$z zwYY=qzwAe*1UH)Xabr0znA_%znog%{Q7hwHA<2xIVL$I`9+sv(&*;&Ty7fI30|f`E<>^h%EKfkNSnDuTnX7*X=mvP z$s%qen;`8fnJq6ZPgbr`L=`0!Q{@&o_ljhf6)P2ORNs`_l(YWF(OH1CxiwoD2qd4z zU2!i^cWBKcSwf!Mx;W_wbG!%0ndqKT86B~)&f(h?i+z(V`dC)ES z54nh31AYB(AN_#z1ys@6X-bVLE&muR~#d2=7 zZLwapez64G@7g*z_>Q@@1-2Jvt+~K5)athX zX76ZU?cC-3>bm0+IE?lMHo0w(qt3q5#c~C?jyPA@OKd*&e{EZxU10aDcUC#Go%>xk zT)ECD7sWBxk!KqLkN@Mm<*IT`ww<#%Or@qD=3nLqcD;SL7psiBCtj{ z=R5y%{&eoNZL%G-{<7Y)HQOdQk{nYVQI55?8@3Efpmnxogk`KH&>}V^nC=-L8kNQ| z#;cj4%oa<&MQu;De|L>?*|Jw>j{!}Wx(qKzD@#CK^|&IuDy3>c4GPY>oLXlMwf0(d z*P7{dg>~gX%q(jbga7=V?^)@N;LO)@&I7!?`d|KRJJWtFP&lH~! zkCCKGwn#rogJl7dBa#ZiZ{Y{wSkX0!UV=!HBx8i{gl&amgl?Fi`iT|dgMt*n9M}P> zx&LxK?B{G3OkVW#+4QN5pY$K}Idm@Z1(y-$aRuWseIBDbBbRsv%AhNFJpDKHsZ!`Y ziAsDVM#28zZ}3%QG8stpBz~fK=t0ba&4q^hJ1h!6k5nP4Xcz2XJc76dr}Zhc8?>WT zUz!v-jr5`Ir;P$FP-mdaJq0~ZU(g5fK(G8iI{R+wKU5= z9`u)Fe|jF-!nneSW9?#|V!9ZQpkr0dTE{A54}?x}e>RIvWuN1`djMUW{J^RjsZx!K%m?h_uLTf{vA{k0D4+iWX45fouY&N=o0_Effl zy^q_Sdy^N}#C+6FrUoS`-Tjs8n1H^F7Sk>Ugbeh=VT zOn}6#Ex=FQ15?S~Kq?>5^1YenHxqbCLO<{S=R^)~>EdVeod!ufoq*f_zr@(%psSq- ztXSYb_@0FM{b!(c#ejx;B>0UxgC58X9`JpTi3bW+An3FKdSjaJJ5V-_1I`H-`X5Ir z!zp@T58Qn@(Rfzdy}QuMtl;+!rKy;i58f5yU|Tp8S)4{i3VT;u%p-x z)Qc`h-XImAmeHfz(Y2@u6{FXXbd-YDKu`J(@{V?l_J_KLc8l5(SwPE2rbCy0JCGBC zX{V3@$Y^vd+^;A01@^22+JCe;Fx6g6Ta0w1#?wT=lqmCi2NcLGU^LcIP5}wp2Bd=o z$XU4xY`-#KrY!~bbVuJTNL12-(&}al>37EW2e6GRAYXv#x5xJy%wTJvk3S8##_NGz zcpAKLJzx%Pgao;v)N!Cu`$dh0GguLVq8z+6eu=n9BoKoM2EGwL0SaCZJ&rMnag7m3 z-%cM-o+Q`OSI}QEZ!!O})7d1)ha+TXv-YtkvoG*2@~#UX3i}IVAbQcxj~B)Yhl_`a z?}?9#ABxV4j)@M7o``RXC6XrbAy5tu6o-i|qA#Kp(IwFz(Jqll^h$V3&{MFGSIZsG zOXD8n`|}UN-spnP?Iva`=2(`9)s7R+@nK0=X@s3Pgzv{c5t#%*3@2_O9z+#Dr>dzv zX+iK@ln1P$z5^CYPvBu|{X0QEl8jmd{+TJj-OdFs?`p`QGXeW@0eH7JK+4Y^AnmX6 zIoQ(QXA{iJAAzf|47B6mwge(?+5~PnFIo%>w^SN?BrALD{XcS*0^dlZty3e=FKw6j$6_biMdo@#LbUB3VIA{_Xtp z`P&K}6x=B=7TnH%kUuSNPM$1paNf4Ot5C@;$aQ3EvZJ$uvbSYV$ga$OoINv}n;nv) z&pw*lJ$DUEH)C^uQ>?E|O4AtgMzhgYV4Liy zw{Nh%wxk%D#*ECkOsPH4_QQ70YJu<_rg5C1!CYkuws*8GF$bFu8nzk2OpeR|o6Bl2 zceKnjcnk}TAB=If|7^`zBP?_5OvhU1JXZ&2t#i74kA0tgi~Wzi+HSF` zt)pyd)?)h#`!2^E2g7c$8Li!|Irj1Pj!x1!&ynaTvhA`JST9-ETJKn2SQ;$nEC(&C zE!!=3&223=O$sw^Vwi@QrkK{5a!g646Q)y{lQQFtb%w$E1^WBCe|3q*9K%o3b<<}P z-@L*6#C+Pm$i6mfV^-JvoB1pA-{zMTG!#rJeOr2@a!6HR^^58?mHNt-swdTo20`O| z@V;_E4-pKU;C`@1orJF9F<^y-10OpOXs%0vPF@Y~=O*CP{YSe+-1)69t zyb>GFtYj$g+gJv56BT-U_zliwPCmPVeVQnSzL1zPi_3x2(|BzK*Mzl_jxw(ZlXT&Y;@{w}6}*wirS+l+@jskvoJzKXy_R3go4_-2BN;aO zQ8<&|AQAEay$!t!o`CbvLZlDoi$&r|padF&7h>!1pZG{(F+P@fjJmNN*f9J!v61{s zc*!Vy7SSFH!X5ZdqCcHr&=|RlFxY?ZGMX5JS?^iO zlN$`4kpPyIl?ZFeWKb<9a}1mvtR_|rqk@seILQ3)zaBKqf`VZxB>tc3iWA5^!x3|B z?0Zn}^@P1^wctPgNWoZc6s!_2dEwCQ(uhWh$MA8%biReZLHu4EB)uS=BB~X+`JDtO zVICYPxglu{TB{Q75Z-0Jk>5vfQE-%7$-TjLvT^P}?jvpoUMgz^djiADC}f^y)iNEd zN5pZmD@MV)69{>ToJfzxoY*m1JhB4iVnRFxUxJpR9l;wIf-Xa6;fL_O=x%fkG7+gj z)#yWP4`u;>rJE8QAfZ&+(gX> zN0tJnybGXLe3{w<*iHL^8n_<3W)V~)q|XIGUt$IjiS|Ga+!MbZ;2He}XU%d*mm2|8 zlViY%Dg&MNLTYWmMDSN91AYGz)H`cIGqeQA9oIl_uohAZ&d_GjGJ#h#nCKVFuO4xu>LU9LCw>FHI4a;^NWr0<-AG!<2;N%gvSs} zY6QLx3_(VSD< z7vN-l&OXjg1=U^;)=c(x))m$lW+-D1T})mgx)4`^M$sD&!S10z^q{=~N7)kUM<8-d z49Eh_kH3Ee5Fy6_k#r}JvrC|>J{h!MhryMSw3eop8<5g51TsOcHLh$ZG}+ql11Rlx>-*L(sBfw}Tc1$hzy538yEHOsAc zeRdCZvpgH!TRmoXy=R_heAYeh*X;VNM$o;IFv07Vvo3p3&f@HEF!$V$mF%T?Ii3NY z{_YwN#q$^Txvt((9;PSBeautf)`8Y_GiY81dviTw+|918o+9^FZx`<|_bhjiv)HlS z8S1pScDt;u^R8#kan9u~#HH~5_Q<`TJT&(n*8>;ZmFW5G9_Y1sKDqn5AG%^(kKJS4 zb3A;H)xFA{?b_ptb62`fz~>w9=DWXvb|=v-aj$^dZ-t$0ud-dRjdw^L>+Rj_yUg9p z&oWnJUNX-%t1O2tnWmklzlN=bBZe6Uwdt`b)`D5Co6ecqmCm8G`&|>rHj#Z(9PA((f+4- zqRCa;)i+efRQ=V_>J8fU+Kc)(`sYTAai;m2xxM3r|eaJxTJVyiBI{F^7zW9l{V--IcoaXegpUa8J{R%tnt0ENm4v@@vMn7{=XiN2);gWlGSzQ7dtT=WN;N=rh1Aal|ESQxIy zmg7N~3bUb4u<3XVffK2O0&m2BV6Sl&o{ujex{!&a3^aLN=y&L=7}3mZW*1g1tBCoL zH3qbJ4AuwM6qbNJ6mF-{FEdz-B5<4BVDjiBqk-_D)98)#B*tLoCi*eP3;Y{_;{@@R zculS%{vq$c%Dokt53{#=>=?e4IEXJN(y(OwBsfaM^a8q=nF+2yBWM-wv3GDraj!u? zxSIc0Fiwyp>@Aokj28|NrHU-#$>NieXJTLJM#*Q`iR`jBGM-|YT&W-xeFJyNFDUj) zFUdYhUP?bpcgfnza%HjhcDZGsG8XZ}LLeSW^c3;X{#{#e0F-dX+v-WYxqpDL*2-QhpuCh<;k z`$IP>n>Pit6GHZ1_J7RhEG5&xQm~}#IgEdp7Tli<$NLapi4?M)s30f8ZDxxDw`Gn&-PyQGx9I!fVZLwG#)z% z6~uPXPTmGh#S}aqoC!yXH=wlKM6U-urH+)5|B!>|_vqUgWegeP1mipW9|D@M1@v5c zHF=1hK>i`O6Jqis=rZQhg^U{F40!`@P5i)n5=%%kOgaB0Z{y|o5a@x?z&8|0ULrRV z1q7W)Chih(Z z8c-{8A9)AYXz=u&1`nnkvbR@*wlosvvTdm6sUa}C-9+L0&w||5VrnlM37Is}z=k)& z{(T?(Ld*b*|9MCRQ$unhH?nV_4QOIxQEfB4VFq5v(K3Cgx^l8`c}R{$Va*H89(@Qwl^s?YpzB{it z_jKNl+&+1Mxy?CE*`KrjX3?{2ya`!U*r8nR9CwB*+kMA{dOo?fxQDs8?kwkV*C^*j z*GA_(_gmLy&pG!7PqKTMXT96zN$`C1%DmIF-gqZwsl5ZT%-%s+Dc%*{Z=Q2rlV@ty zUhfz0ch7ClPPf!k;;Qwi-E?odyQkahG&=V>PC5HIhB=#|WBAtkz;f0cWNB-rn|qjE zWEL8G8&4QE8@+~`nP}z;qthTZoY${2l$?^Hp{7Hqf zLZXOLbfDxxNvDdb700SRSB;d80{Gz zMkV72vpvhjTF;7SS1@<8CbRUchwOMx821Ht0QV2~5VwT;lrI-_6s8ML!(LG)+z5(T zqj&@KQCq}p>1Oc_$#)4|79`s%XUP{RV&uye3+2BQ21Vzf5kYH%`vi9j@dW)1?h=$A zBndtiye#Bz$n20$A>V_@;F`e5AX@P7;Pt^p!G8lg2mO@CD{AD|6blr?0?o4Ta*HHG zdQAL6vL6(-Z0Ow%6fP0%7km}=6dV(t;(rxrc?J9jTs`jy=QTH-tp!iZdkzNrA`w#v zx}&wst<2#pCaaLOhcSh@5%%*A^a|L~+cRH)`^KMqO$HM=P`A8?7BLhfhc>A*O+fKM8uxJ3#Mz8@N;N;JmW}BY@O*5dRL}uP-ndccUY*KG0PZ{I7$E z%tJ|-pp6DXd^>mz|Dq;=Vzv)*l&YcX0+`UnyGI=al~f>YA*`zD)Y;S$>KY*1Kc)(3 zCh7uOG4&{I6S!BC;QoE!k>%7Q;JMjNwNZt%-$)dk&`x4g&@1pNeu+|vB%%ZTD*XWT zdBb78Ub_fQ+wCk<#y3iwjEoMmc z;$xzoqDtX;;Rvyx_=b3bI7Va@1_*z{Va7(bPl&+E$T z!RyZL3cja7oC3CkQ>&ZF-jQ;C7ZbLeHRz@1H|8e&cF@1 z56aFET0Qj$@I5XgvyepKqJ4q1)CiczzV~nMPobRl+YA)WA-<1&dioxRob3HAbx<+s zfRgG5w17Ln;QS7`AHnr$b&i_tKnBjMrqo!f%PMbHR+R55-%`#fUr|n!hn8I^-CXje zxOK_OVyt*@(ffkB{7L!Gz$>TC>zkjPcPytYds$YKmz(`4YdzF6b>8(}w&y>0zWai^ z$}`8))4R{}3hJX99;f@TH`&9^s`U&91?MmKE7urzq^p|;b9eHzcVBY<=X&n`;HvUe zxVL6q^g6w?4#}(^(;!JnYTomUc=S@ckMymL%H>Yirvg>r-pKCEF5jv6=^%3rsUjS($?~{Y=v{yP9riel~_1e;SyEOGcex zQs%$LS4M$xrtz&I1)}G*rbts9>{Ru}GseiwN5(MI!_0vu%=E)_&t!x;=0s*_=AcZE zv7MJzA?&;zhFL z)oo?9vYqmWs#JMUZBd12E~?F3j;=~STE8MwXFOpowyd+K z*k0S~ZTDSe&iI_I+3gAr=65JKnqQeeDxX!nw&;0zLiwqxE>-WVCRasQKdrh{H@L2M zb42q(-#$RTJ?s|xvEbbu1FzLybdo-kXh%?qYCMtN zlg?*0FxE4sFiz6<)7JtsqlozjUj3t(d+0~#3+adG21X&n!N_AM$r2KQm2Nljo=}qw zWHu?GH-Tdyl^9Ik0#DIcaM$mlKLVG%k)Fud!8po1$L!B)!>VCcF-sY>ps4uF7y%X1 zQ1JEtU|nS0WNrtw^&w_=m^cmL^a29FI8GxwpWTfU2^!ED+&5e!cLKaGeb`#oZ15xs z`4RlV{I+}#=LV>;=WtpH6oL(+MWRQ7ZGtDf)x11@3cru2Be)!$LLEPipA8z)cB1Yg zRMJW!lf+2;C1qk8%<|%8gJqrNW99SY3*~B=L$*nFUuKi}%X4HkvSit7*)Ewyb`d_F z0z5BVI$FA1QYcv@MP(zUzoqZQq-2+-s$xw8FXd>aml;N;LH%eW`OK3{1^l0$TtUA~go5i+N75J@ z!4>(CL5EtTJ-LO>oKK&P6 zOGg=tfH^mm>`J=nZqQ!FGM?! zuvf)n+xhfkBBF6axgiWSVdfc z&%1*BM9v|VAd_n}1ub_sygAK+} z{65|e|AmKvr|cZeq0-Tfpf*LZ0wf8DK`+tuSQVUvP`nP#G8wdD+AE|UnT(~vj(Q&7 zhd;&Vf&X(EILv1Nw<#HFV_)WQ<{E~Lp@Q04&fdW`b3(a$I7;YUcYx0HA>MCchR`bX z2xWqHf;xUB-z|JCye{4-7D!0RUva9qQ1nB@6l+96NtHN3(pr)s{w3Z7(;|cTC`^); zisuSv2pa?%L87Qc_*J+~*h>&D7{yQEoA_t=k6=G}&OZ;`w|BfFysf;y+!Ss%Za?lD z&M(eGaO^VS^*)6?hmCL|I62^r#n}}sgk8-tu`AeGb~$?kYcXpuGl3~%c4B5R;+bq< zF^TCN>9^N9m-eRfW8 zb#irf&>Zt&vNOz9Z@pw&Z(C}cY3pm{TW^DMz-qZ;F<8`=;g0FNQKs8C$(UqJ zH|PxcdY68JZkVn>+o;{6y8}^+TwRQ|yLO@WfcC0Rt(&7eq03iOHEtDE{auY|rfBYH z&Z*z3Lsao9w@R$GYkF!=YE9bt+TGd*8k>fxU9C;gh3R?vk@_cEy>_DJoF-9wLtC%w zr|+N}txMN9HD|S{+By1j`d<1``W@Qs+Ns(l+HB};P1X<5AJ8t-zJ1p7Aq%QTnd5VQGKT4yW}@ z&qzy7lct^fJN9ov8kW{SgO+hX8LuqPxR}wb{I1Nb^-JY#?VgFe2@;Hj#`E^!mWMamA(!$g|5UxnDhjYT%s>=26y6{;H=mTXV4#X zE+d9KPL{wtrikc4?jrT%Gm=Ti$eCm@AtPfLiQrfbWR7S2%Q(&W%BX?L!NJUD&V+To zgcZd8$^OAU#oi5-d@Q>ydkuRL_YAikc-NFrL-=tS+%ddKymg=e_6lwYP@akVhx3rL zg*TA54c=p?p_e_9Bj8*D9?J-B8K;oLa(%+#@r7&kR%V4b>4h#|#Sp;4J1Kmz% zG7=bV7<@)0{VniDc0e_Il(`!^u@3M_cV~V9*W-NFELIy12f9}^(6!>QAF|E>d#ITG zh&zY79{io@+{@e_pr>>3Ht@RhY+NOGKern{hqq7QDJJ)gmk z=ec36mvF;*_1qACI#0`6&l7OXoCok)U&HCnxxpO<`%p*D7?zwhkrl>LviE`?^Axx> zKe6_L=4L1m5^eyq;VJ76OTg;EN?>(jjbsgB{Q&3oZ$=|y1>+d}L~)ExaB;$%Q$^x* z1wEENn4Umy1v^6*@J5UV2JjkSuC}k zlavaXtkZ!_(;BkeGaH^aAoaO*BWs7&3ToIj%c^=;HI%26#{r*r+cF%WF$}iwpjqY58dGY58V8 zW{xr+HqlKi(~ivTnZnE-nPldAV^?EGL%RN#evZCMm#RCbJ*54rxuKzH)tUlriZ)rh zR(nRXR-;v4RJT<(s)ndLsd;Lq`nqbrYMpAb%A+h$K2@eE?RrK zqo2t?SNv-ItLEqPpS@G#QjdK<@!dDo_+$Ui_CJL`OH)_=Jo$6*uX(?Q{0aZl`1k8y zK^mI&E8Ug;MD;}FuQO}ih7`kt%%_=?EbA;ByV!mY^n1#z{H%Vt8*_UWEC-)7ySToj zcd5K$X9cBtW;MNjc71r$k*2$lNA{tKj-b!P-Ez`WMG> zH@*!U4qfX6b;M&^7IK#{%tLdG{a3B(HU;ur9^$#PN z$)X1`4guX~7$cGSo|(eRWq6o#8RMAoj4sfx?aGQ`_u_nJf8d0`d29t|KX*B29hceG02h=Ao(UETeT0vNzXZpHF9io+HGePsA)F+Vi5x&GDH81w&J;z! z39h5aE9x)yl_*8q#UY|kaMoWX3KwOFo{6S{j(V`TOtb>Dd{@OSqH}P%LnOb%eIy&< z%sx`gl?;i!h%B8p{+v;L;npO6~+jA9kx2GefYkx3E{zESz(_;SA|uC zJPYj;!VT>oLJP%1o`i%1zYFdZXbHS5AEfwK-c~V2{!RW)+EHc}?~&ArUVs{@Kzv%% zBx(yf!dJX${A;|nd<(BTe8LbSiWWmx1o@1L(pQKxaQ62s3PC9j!Mlg62yb zLKD#j)AE6|IhHyN@~BJ0S{Fl|whR_LYB8PgayV9lG&SjjlUcmh4h zDCSCdEsbD41AW9Bra$Zw?br&Oe`HF9gXX0D$53Ut#4K}DSkyt}#F zzTAtz&j|s2+Bl$27IH2D@tn>vvcADeKZw=LddAw$9>snK-``H=I_N)k2aU-mrVr~Q z(*&;kFHrY6=@;mG;r!p0(M)&2xnD(`Ak@S;qBBX7FQD>H#wX(hkhogmzIYM#6_l^r z&3J^sw}afxcK)f9*%T3ErY-mR4yrv8E<2hJH)VmI>`y&f z-y6J4TWcw`18YiwnO#~DQ*ol)3ohW#r9(^ml-w`gR1{wnT-d5`PytbZ=I_c&%I%h$ zpFJsupS?4CMwU3M-XrpOT&b?j&e5&_P!(Tx(4BAWYWo^njQxjoh&|Oh(0 zZAC0&tnbaEtuM@KOQgBX+{@I9f%v6KRC^0-S zRO&(uYjr&hdvp{-Kb=b#q7BxrRJ%2u)oEIWrcB#a^GOq~wyTCJBhTUtD$RYB zM7NTn@nx5*_>Ncu_%IFM+>Se}p^#oO++NgSxrt+ zQfw8z6R*O3h_ys-GJ`x#@5boHa6^5)kBPE0EI#xzP4@yjy$Ro6f@`NR#>%!Bb9iq9Qj(Q;}5LZZS;&KTSv|+C$kEO}dL$Wv0 z6j`$LUztbhFP|yfDBmHwDZeKBD?cxLEk7x{ELX@|DLTqmDL%+3iX_=`d9kRC`4+7Y-tC^E2pkV(-P7!=q&h!+$Q%m@+& z&kmdu#D^VY5IB%O2WACL37i#Fr1%whOK~8Os}KgV6^g(}g(T1}zo+2JOXTBZN%FQb zsk};>Dod1NvW^m#bd7k1L@3S`-xQ4&$AWrnpRk|ED|jIsDPRb*_@@QW_!9(Sd;@d9<+AE~7N`Ga3zy

BGx*utaZbiDI$Km#D$kTB`THyo8 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 q8c~{pU;)NcR%Yn$J)+;Nxw*i@M3r$ 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

Example scripts for running the training on SLURM cluster + +- **launch_job.sh** + +```bash +#!/bin/bash + +#SBATCH --job-name=source_separation + +#SBATCH --output=/checkpoint/%u/jobs/%x/%j.out + +#SBATCH --error=/checkpoint/%u/jobs/%x/%j.err + +#SBATCH --nodes=1 + +#SBATCH --ntasks-per-node=2 + +#SBATCH --cpus-per-task=8 + +#SBATCH --mem-per-cpu=16G + +#SBATCH --gpus-per-node=2 + +#srun env +srun wrapper.sh $@ +``` + +- **wrapper.sh** + +```bash +#!/bin/bash +num_speakers=2 +this_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +exp_dir="/checkpoint/${USER}/exp/" +dataset_dir="/dataset/Libri${num_speakers}mix//wav8k/min" + + +mkdir -p "${exp_dir}" + +python -u \ + "${this_dir}/lightning_train.py" \ + --num-speakers "${num_speakers}" \ + --sample-rate 8000 \ + --data-dir "${dataset_dir}" \ + --exp-dir "${exp_dir}" \ + --batch-size $((16 / SLURM_NTASKS)) +``` + +
diff --git a/examples/source_separation/conv_tasnet/README.md b/examples/source_separation/conv_tasnet/README.md new file mode 100644 index 00000000..8c64ac6d --- /dev/null +++ b/examples/source_separation/conv_tasnet/README.md @@ -0,0 +1,44 @@ +# Conv-TasNet + +This is a reference implementation of Conv-TasNet. + +> Luo, Yi, and Nima Mesgarani. "Conv-TasNet: Surpassing Ideal Time-Frequency Magnitude Masking for Speech Separation." IEEE/ACM Transactions on Audio, Speech, and Language Processing 27.8 (2019): 1256-1266. Crossref. Web. + +This implementation is based on [arXiv:1809.07454v3](https://arxiv.org/abs/1809.07454v3) and [the reference implementation](https://github.com/naplab/Conv-TasNet) provided by the authors. + +For the usage, please checkout the [source separation README](../README.md). + +## (Default) Training Configurations + +The default training/model configurations follow the non-causal implementation from [Asteroid](https://github.com/asteroid-team/asteroid/tree/master/egs/librimix/ConvTasNet). (causal configuration is not implemented.) + + - Sample rate: 8000 Hz + - Batch size: total 12 over distributed training workers + - Epochs: 200 + - Initial learning rate: 1e-3 + - Gradient clipping: maximum L2 norm of 5.0 + - Optimizer: Adam + - Learning rate scheduling: Halved after 5 epochs of no improvement in validation accuracy. + - Objective function: SI-SNR + - Reported metrics: SI-SNRi, SDRi + - Sample audio length: 3 seconds (randomized position) + - Encoder/Decoder feature dimension (N): 512 + - Encoder/Decoder convolution kernel size (L): 16 + - TCN bottleneck/output feature dimension (B): 128 + - TCN hidden feature dimension (H): 512 + - TCN skip connection feature dimension (Sc): 128 + - TCN convolution kernel size (P): 3 + - The number of TCN convolution block layers (X): 8 + - The number of TCN convolution blocks (R): 3 + - The mask activation function: ReLU + +## Evaluation + +The following is the evaluation result of training the model on Libri2Mix dataset. + +### LibirMix 2speakers + +| | Si-SNRi (dB) | SDRi (dB) | Epoch | +|:-------------------:|-------------:|----------:|------:| +| Reference (Asteroid)| 14.7 | 15.1 | 200 | +| torchaudio | 15.3 | 15.6 | 200 | diff --git a/examples/source_separation/conv_tasnet/__init__.py b/examples/source_separation/conv_tasnet/__init__.py new file mode 100644 index 00000000..9c31ed25 --- /dev/null +++ b/examples/source_separation/conv_tasnet/__init__.py @@ -0,0 +1,6 @@ +from . import ( + train, + trainer +) + +__all__ = ['train', 'trainer'] diff --git a/examples/source_separation/conv_tasnet/train.py b/examples/source_separation/conv_tasnet/train.py new file mode 100644 index 00000000..691e7f34 --- /dev/null +++ b/examples/source_separation/conv_tasnet/train.py @@ -0,0 +1,332 @@ +#!/usr/bin/env python3 +"""Train Conv-TasNet""" +import time +import pathlib +import argparse + +import torch +import torchaudio +import torchaudio.models + +import conv_tasnet +from utils import dist_utils +from utils.dataset import utils as dataset_utils + +_LG = dist_utils.getLogger(__name__) + + +def _parse_args(args): + parser = argparse.ArgumentParser(description=__doc__,) + parser.add_argument( + "--debug", + action="store_true", + help="Enable debug behavior. Each epoch will end with just one batch.") + group = parser.add_argument_group("Model Options") + group.add_argument( + "--num-speakers", required=True, type=int, help="The number of speakers." + ) + group = parser.add_argument_group("Dataset Options") + group.add_argument( + "--sample-rate", + required=True, + type=int, + help="Sample rate of audio files in the given dataset.", + ) + group.add_argument( + "--dataset", + default="wsj0mix", + choices=["wsj0mix"], + help='Dataset type. (default: "wsj0mix")', + ) + group.add_argument( + "--dataset-dir", + required=True, + type=pathlib.Path, + help=( + "Directory where dataset is found. " + 'If the dataset type is "wsj9mix", then this is the directory where ' + '"cv", "tt" and "tr" subdirectories are found.' + ), + ) + group = parser.add_argument_group("Save Options") + group.add_argument( + "--save-dir", + required=True, + type=pathlib.Path, + help=( + "Directory where the checkpoints and logs are saved. " + "Though, only the worker 0 saves checkpoint data, " + "all the worker processes must have access to the directory." + ), + ) + group = parser.add_argument_group("Dataloader Options") + group.add_argument( + "--batch-size", + type=int, + help="Batch size. (default: 16 // world_size)", + ) + group = parser.add_argument_group("Training Options") + group.add_argument( + "--epochs", + metavar="NUM_EPOCHS", + default=100, + type=int, + help="The number of epochs to train. (default: 100)", + ) + group.add_argument( + "--learning-rate", + default=1e-3, + type=float, + help="Initial learning rate. (default: 1e-3)", + ) + group.add_argument( + "--grad-clip", + metavar="CLIP_VALUE", + default=5.0, + type=float, + help="Gradient clip value (l2 norm). (default: 5.0)", + ) + group.add_argument( + "--resume", + metavar="CHECKPOINT_PATH", + help="Previous checkpoint file from which the training is resumed.", + ) + + args = parser.parse_args(args) + + # Delaing the default value initialization until parse_args is done because + # if `--help` is given, distributed training is not enabled. + if args.batch_size is None: + args.batch_size = 16 // torch.distributed.get_world_size() + + return args + + +def _get_model( + num_sources, + enc_kernel_size=16, + enc_num_feats=512, + msk_kernel_size=3, + msk_num_feats=128, + msk_num_hidden_feats=512, + msk_num_layers=8, + msk_num_stacks=3, +): + model = torchaudio.models.ConvTasNet( + num_sources=num_sources, + enc_kernel_size=enc_kernel_size, + enc_num_feats=enc_num_feats, + msk_kernel_size=msk_kernel_size, + msk_num_feats=msk_num_feats, + msk_num_hidden_feats=msk_num_hidden_feats, + msk_num_layers=msk_num_layers, + msk_num_stacks=msk_num_stacks, + ) + _LG.info_on_master("Model Configuration:") + _LG.info_on_master(" - N: %d", enc_num_feats) + _LG.info_on_master(" - L: %d", enc_kernel_size) + _LG.info_on_master(" - B: %d", msk_num_feats) + _LG.info_on_master(" - H: %d", msk_num_hidden_feats) + _LG.info_on_master(" - Sc: %d", msk_num_feats) + _LG.info_on_master(" - P: %d", msk_kernel_size) + _LG.info_on_master(" - X: %d", msk_num_layers) + _LG.info_on_master(" - R: %d", msk_num_stacks) + _LG.info_on_master( + " - Receptive Field: %s [samples]", model.mask_generator.receptive_field, + ) + return model + + +def _get_dataloader(dataset_type, dataset_dir, num_speakers, sample_rate, batch_size, task=None): + train_dataset, valid_dataset, eval_dataset = dataset_utils.get_dataset( + dataset_type, dataset_dir, num_speakers, sample_rate, task + ) + train_collate_fn = dataset_utils.get_collate_fn( + dataset_type, mode='train', sample_rate=sample_rate, duration=4 + ) + + test_collate_fn = dataset_utils.get_collate_fn(dataset_type, mode='test') + + train_loader = torch.utils.data.DataLoader( + train_dataset, + batch_size=batch_size, + sampler=torch.utils.data.distributed.DistributedSampler(train_dataset), + collate_fn=train_collate_fn, + pin_memory=True, + ) + valid_loader = torch.utils.data.DataLoader( + valid_dataset, + batch_size=batch_size, + sampler=torch.utils.data.distributed.DistributedSampler(valid_dataset), + collate_fn=test_collate_fn, + pin_memory=True, + ) + eval_loader = torch.utils.data.DataLoader( + eval_dataset, + batch_size=batch_size, + sampler=torch.utils.data.distributed.DistributedSampler(eval_dataset), + collate_fn=test_collate_fn, + pin_memory=True, + ) + return train_loader, valid_loader, eval_loader + + +def _write_header(log_path, args): + rows = [ + [f"# torch: {torch.__version__}", ], + [f"# torchaudio: {torchaudio.__version__}", ] + ] + rows.append(["# arguments"]) + for key, item in vars(args).items(): + rows.append([f"# {key}: {item}"]) + + dist_utils.write_csv_on_master(log_path, *rows) + + +def train(args): + args = _parse_args(args) + _LG.info("%s", args) + + args.save_dir.mkdir(parents=True, exist_ok=True) + if "sox_io" in torchaudio.list_audio_backends(): + torchaudio.set_audio_backend("sox_io") + + start_epoch = 1 + if args.resume: + checkpoint = torch.load(args.resume) + if args.sample_rate != checkpoint["sample_rate"]: + raise ValueError( + "The provided sample rate ({args.sample_rate}) does not match " + "the sample rate from the check point ({checkpoint['sample_rate']})." + ) + if args.num_speakers != checkpoint["num_speakers"]: + raise ValueError( + "The provided #of speakers ({args.num_speakers}) does not match " + "the #of speakers from the check point ({checkpoint['num_speakers']}.)" + ) + start_epoch = checkpoint["epoch"] + + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + _LG.info("Using: %s", device) + + model = _get_model(num_sources=args.num_speakers) + model.to(device) + + model = torch.nn.parallel.DistributedDataParallel( + model, device_ids=[device] if torch.cuda.is_available() else None + ) + optimizer = torch.optim.Adam(model.parameters(), lr=args.learning_rate) + + if args.resume: + _LG.info("Loading parameters from the checkpoint...") + model.module.load_state_dict(checkpoint["model"]) + optimizer.load_state_dict(checkpoint["optimizer"]) + else: + dist_utils.synchronize_params( + str(args.save_dir / "tmp.pt"), device, model, optimizer + ) + + lr_scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau( + optimizer, mode="max", factor=0.5, patience=3 + ) + + train_loader, valid_loader, eval_loader = _get_dataloader( + args.dataset, + args.dataset_dir, + args.num_speakers, + args.sample_rate, + args.batch_size, + ) + + num_train_samples = len(train_loader.dataset) + num_valid_samples = len(valid_loader.dataset) + num_eval_samples = len(eval_loader.dataset) + + _LG.info_on_master("Datasets:") + _LG.info_on_master(" - Train: %s", num_train_samples) + _LG.info_on_master(" - Valid: %s", num_valid_samples) + _LG.info_on_master(" - Eval: %s", num_eval_samples) + + trainer = conv_tasnet.trainer.Trainer( + model, + optimizer, + train_loader, + valid_loader, + eval_loader, + args.grad_clip, + device, + debug=args.debug, + ) + + log_path = args.save_dir / "log.csv" + _write_header(log_path, args) + dist_utils.write_csv_on_master( + log_path, + [ + "epoch", + "learning_rate", + "valid_si_snri", + "valid_sdri", + "eval_si_snri", + "eval_sdri", + ], + ) + + _LG.info_on_master("Running %s epochs", args.epochs) + for epoch in range(start_epoch, start_epoch + args.epochs): + _LG.info_on_master("=" * 70) + _LG.info_on_master("Epoch: %s", epoch) + _LG.info_on_master("Learning rate: %s", optimizer.param_groups[0]["lr"]) + _LG.info_on_master("=" * 70) + + t0 = time.monotonic() + trainer.train_one_epoch() + train_sps = num_train_samples / (time.monotonic() - t0) + + _LG.info_on_master("-" * 70) + + t0 = time.monotonic() + valid_metric = trainer.validate() + valid_sps = num_valid_samples / (time.monotonic() - t0) + _LG.info_on_master("Valid: %s", valid_metric) + + _LG.info_on_master("-" * 70) + + t0 = time.monotonic() + eval_metric = trainer.evaluate() + eval_sps = num_eval_samples / (time.monotonic() - t0) + _LG.info_on_master(" Eval: %s", eval_metric) + + _LG.info_on_master("-" * 70) + + _LG.info_on_master("Train: Speed: %6.2f [samples/sec]", train_sps) + _LG.info_on_master("Valid: Speed: %6.2f [samples/sec]", valid_sps) + _LG.info_on_master(" Eval: Speed: %6.2f [samples/sec]", eval_sps) + + _LG.info_on_master("-" * 70) + + dist_utils.write_csv_on_master( + log_path, + [ + epoch, + optimizer.param_groups[0]["lr"], + valid_metric.si_snri, + valid_metric.sdri, + eval_metric.si_snri, + eval_metric.sdri, + ], + ) + + lr_scheduler.step(valid_metric.si_snri) + + save_path = args.save_dir / f"epoch_{epoch}.pt" + dist_utils.save_on_master( + save_path, + { + "model": model.module.state_dict(), + "optimizer": optimizer.state_dict(), + "num_speakers": args.num_speakers, + "sample_rate": args.sample_rate, + "epoch": epoch, + }, + ) diff --git a/examples/source_separation/conv_tasnet/trainer.py b/examples/source_separation/conv_tasnet/trainer.py new file mode 100644 index 00000000..af4e7153 --- /dev/null +++ b/examples/source_separation/conv_tasnet/trainer.py @@ -0,0 +1,165 @@ +import time +from typing import Tuple +from collections import namedtuple + +import torch +import torch.distributed as dist + +from utils import dist_utils, metrics + +_LG = dist_utils.getLogger(__name__) + +Metric = namedtuple("SNR", ["si_snri", "sdri"]) +Metric.__str__ = ( + lambda self: f"SI-SNRi: {self.si_snri:10.3e}, SDRi: {self.sdri:10.3e}" +) + + +def si_sdr_improvement( + estimate: torch.Tensor, + reference: torch.Tensor, + mix: torch.Tensor, + mask: torch.Tensor +) -> Tuple[torch.Tensor, torch.Tensor]: + """Compute the improvement of scale-invariant SDR. (SI-SNRi) and bare SDR (SDRi). + + Args: + estimate (torch.Tensor): Estimated source signals. + Shape: [batch, speakers, time frame] + reference (torch.Tensor): Reference (original) source signals. + Shape: [batch, speakers, time frame] + mix (torch.Tensor): Mixed souce signals, from which the setimated signals were generated. + Shape: [batch, speakers == 1, time frame] + mask (torch.Tensor): Mask to indicate padded value (0) or valid value (1). + Shape: [batch, 1, time frame] + + + Returns: + torch.Tensor: Improved SI-SDR. Shape: [batch, ] + torch.Tensor: Absolute SI-SDR. Shape: [batch, ] + + References: + - Conv-TasNet: Surpassing Ideal Time--Frequency Magnitude Masking for Speech Separation + Luo, Yi and Mesgarani, Nima + https://arxiv.org/abs/1809.07454 + """ + with torch.no_grad(): + sdri = metrics.sdri(estimate, reference, mix, mask=mask) + + estimate = estimate - estimate.mean(axis=2, keepdim=True) + reference = reference - reference.mean(axis=2, keepdim=True) + mix = mix - mix.mean(axis=2, keepdim=True) + + si_sdri = metrics.sdri(estimate, reference, mix, mask=mask) + return si_sdri, sdri + + +class OccasionalLogger: + """Simple helper class to log once in a while or when progress is quick enough""" + + def __init__(self, time_interval=180, progress_interval=0.1): + self.time_interval = time_interval + self.progress_interval = progress_interval + + self.last_time = 0.0 + self.last_progress = 0.0 + + def log(self, metric, progress, force=False): + now = time.monotonic() + if ( + force + or now > self.last_time + self.time_interval + or progress > self.last_progress + self.progress_interval + ): + self.last_time = now + self.last_progress = progress + _LG.info_on_master("train: %s [%3d%%]", metric, 100 * progress) + + +class Trainer: + def __init__( + self, + model, + optimizer, + train_loader, + valid_loader, + eval_loader, + grad_clip, + device, + *, + debug, + ): + self.model = model + self.optimizer = optimizer + self.train_loader = train_loader + self.valid_loader = valid_loader + self.eval_loader = eval_loader + self.grad_clip = grad_clip + self.device = device + self.debug = debug + + def train_one_epoch(self): + self.model.train() + logger = OccasionalLogger() + + num_batches = len(self.train_loader) + for i, batch in enumerate(self.train_loader, start=1): + mix = batch.mix.to(self.device) + src = batch.src.to(self.device) + mask = batch.mask.to(self.device) + + estimate = self.model(mix) + + si_snri, sdri = si_sdr_improvement(estimate, src, mix, mask) + si_snri = si_snri.mean() + sdri = sdri.mean() + + loss = -si_snri + self.optimizer.zero_grad() + loss.backward() + torch.nn.utils.clip_grad_norm_( + self.model.parameters(), self.grad_clip, norm_type=2.0 + ) + self.optimizer.step() + + metric = Metric(si_snri.item(), sdri.item()) + logger.log(metric, progress=i / num_batches, force=i == num_batches) + + if self.debug: + break + + def evaluate(self): + with torch.no_grad(): + return self._test(self.eval_loader) + + def validate(self): + with torch.no_grad(): + return self._test(self.valid_loader) + + def _test(self, loader): + self.model.eval() + + total_si_snri = torch.zeros(1, dtype=torch.float32, device=self.device) + total_sdri = torch.zeros(1, dtype=torch.float32, device=self.device) + + for batch in loader: + mix = batch.mix.to(self.device) + src = batch.src.to(self.device) + mask = batch.mask.to(self.device) + + estimate = self.model(mix) + + si_snri, sdri = si_sdr_improvement(estimate, src, mix, mask) + + total_si_snri += si_snri.sum() + total_sdri += sdri.sum() + + if self.debug: + break + + dist.all_reduce(total_si_snri, dist.ReduceOp.SUM) + dist.all_reduce(total_sdri, dist.ReduceOp.SUM) + + num_samples = len(loader.dataset) + metric = Metric(total_si_snri.item() / num_samples, total_sdri.item() / num_samples) + return metric diff --git a/examples/source_separation/eval.py b/examples/source_separation/eval.py new file mode 100644 index 00000000..c8ab87ea --- /dev/null +++ b/examples/source_separation/eval.py @@ -0,0 +1,106 @@ +from argparse import ArgumentParser +from pathlib import Path + +from lightning_train import _get_model, _get_dataloader, sisdri_metric +import mir_eval +import torch + + +def _eval(model, data_loader, device): + results = torch.zeros(4) + with torch.no_grad(): + for _, batch in enumerate(data_loader): + mix, src, mask = batch + mix, src, mask = mix.to(device), src.to(device), mask.to(device) + est = model(mix) + sisdri = sisdri_metric(est, src, mix, mask) + src = src.cpu().detach().numpy() + est = est.cpu().detach().numpy() + mix = mix.repeat(1, src.shape[1], 1).cpu().detach().numpy() + sdr, sir, sar, _ = mir_eval.separation.bss_eval_sources(src[0], est[0]) + sdr_mix, sir_mix, sar_mix, _ = mir_eval.separation.bss_eval_sources(src[0], mix[0]) + results += torch.tensor([ + sdr.mean() - sdr_mix.mean(), + sisdri, + sir.mean() - sir_mix.mean(), + sar.mean() - sar_mix.mean() + ]) + results /= len(data_loader) + print("SDR improvement: ", results[0].item()) + print("Si-SDR improvement: ", results[1].item()) + print("SIR improvement: ", results[2].item()) + print("SAR improvement: ", results[3].item()) + + +def cli_main(): + parser = ArgumentParser() + parser.add_argument("--dataset", default="librimix", type=str, choices=["wsj0-mix", "librimix"]) + parser.add_argument( + "--root-dir", + type=Path, + help="The path to the directory where the directory ``Libri2Mix`` or ``Libri3Mix`` is stored.", + ) + parser.add_argument( + "--librimix-tr-split", + default="train-360", + choices=["train-360", "train-100"], + help="The training partition of librimix dataset. (default: ``train-360``)", + ) + parser.add_argument( + "--librimix-task", + default="sep_clean", + type=str, + choices=["sep_clean", "sep_noisy", "enh_single", "enh_both"], + help="The task to perform (separation or enhancement, noisy or clean). (default: ``sep_clean``)", + ) + parser.add_argument( + "--num-speakers", default=2, type=int, help="The number of speakers in the mixture. (default: 2)" + ) + parser.add_argument( + "--sample-rate", + default=8000, + type=int, + help="Sample rate of audio files in the given dataset. (default: 8000)", + ) + parser.add_argument( + "--exp-dir", + default=Path("./exp"), + type=Path, + help="The directory to save checkpoints and logs." + ) + parser.add_argument( + "--gpu-device", + default=-1, + type=int, + help="The gpu device for model inference. (default: -1)" + ) + + args = parser.parse_args() + + model = _get_model(num_sources=2) + state_dict = torch.load(args.exp_dir / 'best_model.pth') + model.load_state_dict(state_dict) + + if args.gpu_device != -1: + device = torch.device('cuda:' + str(args.gpu_device)) + else: + device = torch.device('cpu') + + model = model.to(device) + + _, _, eval_loader = _get_dataloader( + args.dataset, + args.data_dir, + args.num_speakers, + args.sample_rate, + 1, # batch size is set to 1 to avoid masking + 0, # set num_workers to 0 + args.librimix_task, + args.librimix_tr_split, + ) + + _eval(model, eval_loader, device) + + +if __name__ == "__main__": + cli_main() diff --git a/examples/source_separation/lightning_train.py b/examples/source_separation/lightning_train.py new file mode 100644 index 00000000..a0d61e8a --- /dev/null +++ b/examples/source_separation/lightning_train.py @@ -0,0 +1,465 @@ +#!/usr/bin/env python3 + +# pyre-strict +from pathlib import Path +from argparse import ArgumentParser +from typing import ( + Any, + Callable, + Dict, + Mapping, + List, + Optional, + Tuple, + TypedDict, + Union, +) + +import torch +import torchaudio +from pytorch_lightning import LightningModule, Trainer +from pytorch_lightning.callbacks import ModelCheckpoint, EarlyStopping +from pytorch_lightning.plugins import DDPPlugin +from torch import nn +from torch.optim.lr_scheduler import _LRScheduler +from torch.utils.data import DataLoader +from utils import metrics +from utils.dataset import utils as dataset_utils + + +class Batch(TypedDict): + mix: torch.Tensor # (batch, time) + src: torch.Tensor # (batch, source, time) + mask: torch.Tensor # (batch, source, time) + + +def sisdri_metric( + estimate: torch.Tensor, + reference: torch.Tensor, + mix: torch.Tensor, + mask: torch.Tensor +) -> torch.Tensor: + """Compute the improvement of scale-invariant SDR. (SI-SDRi). + + Args: + estimate (torch.Tensor): Estimated source signals. + Tensor of dimension (batch, speakers, time) + reference (torch.Tensor): Reference (original) source signals. + Tensor of dimension (batch, speakers, time) + mix (torch.Tensor): Mixed souce signals, from which the setimated signals were generated. + Tensor of dimension (batch, speakers == 1, time) + mask (torch.Tensor): Mask to indicate padded value (0) or valid value (1). + Tensor of dimension (batch, 1, time) + + Returns: + torch.Tensor: Improved SI-SDR. Tensor of dimension (batch, ) + + References: + - Conv-TasNet: Surpassing Ideal Time--Frequency Magnitude Masking for Speech Separation + Luo, Yi and Mesgarani, Nima + https://arxiv.org/abs/1809.07454 + """ + with torch.no_grad(): + estimate = estimate - estimate.mean(axis=2, keepdim=True) + reference = reference - reference.mean(axis=2, keepdim=True) + mix = mix - mix.mean(axis=2, keepdim=True) + + si_sdri = metrics.sdri(estimate, reference, mix, mask=mask) + + return si_sdri.mean().item() + + +def sdri_metric( + estimate: torch.Tensor, + reference: torch.Tensor, + mix: torch.Tensor, + mask: torch.Tensor, +) -> torch.Tensor: + """Compute the improvement of SDR. (SDRi). + + Args: + estimate (torch.Tensor): Estimated source signals. + Tensor of dimension (batch, speakers, time) + reference (torch.Tensor): Reference (original) source signals. + Tensor of dimension (batch, speakers, time) + mix (torch.Tensor): Mixed souce signals, from which the setimated signals were generated. + Tensor of dimension (batch, speakers == 1, time) + mask (torch.Tensor): Mask to indicate padded value (0) or valid value (1). + Tensor of dimension (batch, 1, time) + + Returns: + torch.Tensor: Improved SDR. Tensor of dimension (batch, ) + + References: + - Conv-TasNet: Surpassing Ideal Time--Frequency Magnitude Masking for Speech Separation + Luo, Yi and Mesgarani, Nima + https://arxiv.org/abs/1809.07454 + """ + with torch.no_grad(): + sdri = metrics.sdri(estimate, reference, mix, mask=mask) + return sdri.mean().item() + + +def si_sdr_loss( + estimate: torch.Tensor, + reference: torch.Tensor, + mask: torch.Tensor +) -> torch.Tensor: + """Compute the Si-SDR loss. + + Args: + estimate (torch.Tensor): Estimated source signals. + Tensor of dimension (batch, speakers, time) + reference (torch.Tensor): Reference (original) source signals. + Tensor of dimension (batch, speakers, time) + mask (torch.Tensor): Mask to indicate padded value (0) or valid value (1). + Tensor of dimension (batch, 1, time) + + Returns: + torch.Tensor: Si-SDR loss. Tensor of dimension (batch, ) + """ + estimate = estimate - estimate.mean(axis=2, keepdim=True) + reference = reference - reference.mean(axis=2, keepdim=True) + + si_sdri = metrics.sdr_pit(estimate, reference, mask=mask) + return -si_sdri.mean() + + +class ConvTasNetModule(LightningModule): + """ + The Lightning Module for speech separation. + + Args: + model (Any): The model to use for the classification task. + train_loader (DataLoader): the training dataloader. + val_loader (DataLoader or None): the validation dataloader. + loss (Any): The loss function to use. + optim (Any): The optimizer to use. + metrics (List of methods): The metrics to track, which will be used for both train and validation. + lr_scheduler (Any or None): The LR Scheduler. + """ + + def __init__( + self, + model: Any, + train_loader: DataLoader, + val_loader: Optional[DataLoader], + loss: Any, + optim: Any, + metrics: List[Any], + lr_scheduler: Optional[Any] = None, + ) -> None: + super().__init__() + + self.model: nn.Module = model + self.loss: nn.Module = loss + self.optim: torch.optim.Optimizer = optim + self.lr_scheduler: Optional[_LRScheduler] = None + if lr_scheduler: + self.lr_scheduler = lr_scheduler + + self.metrics: Mapping[str, Callable] = metrics + + self.train_metrics: Dict = {} + self.val_metrics: Dict = {} + self.test_metrics: Dict = {} + + self.save_hyperparameters() + self.train_loader = train_loader + self.val_loader = val_loader + + def setup(self, stage: Optional[str] = None) -> None: + if stage == "fit": + self.train_metrics.update(self.metrics) + self.val_metrics.update(self.metrics) + else: + self.test_metrics.update(self.metrics) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + """ + Forward defines the prediction/inference actions. + """ + return self.model(x) + + def training_step( + self, batch: Batch, batch_idx: int, *args: Any, **kwargs: Any + ) -> Dict[str, Any]: + return self._step(batch, batch_idx, "train") + + def validation_step( + self, batch: Batch, batch_idx: int, *args: Any, **kwargs: Any + ) -> Dict[str, Any]: + """ + Operates on a single batch of data from the validation set. + """ + return self._step(batch, batch_idx, "val") + + def test_step( + self, batch: Batch, batch_idx: int, *args: Any, **kwargs: Any + ) -> Optional[Dict[str, Any]]: + """ + Operates on a single batch of data from the test set. + """ + return self._step(batch, batch_idx, "test") + + def _step(self, batch: Batch, batch_idx: int, phase_type: str) -> Dict[str, Any]: + """ + Common step for training, validation, and testing. + """ + mix, src, mask = batch + pred = self.model(mix) + loss = self.loss(pred, src, mask) + self.log(f"Losses/{phase_type}_loss", loss.item(), on_step=True, on_epoch=True) + + metrics_result = self._compute_metrics(pred, src, mix, mask, phase_type) + self.log_dict(metrics_result, on_epoch=True) + + return loss + + def configure_optimizers( + self, + ) -> Tuple[Any]: + lr_scheduler = self.lr_scheduler + if not lr_scheduler: + return self.optim + epoch_schedulers = { + 'scheduler': lr_scheduler, + 'monitor': 'Losses/val_loss', + 'interval': 'epoch' + } + return [self.optim], [epoch_schedulers] + + def _compute_metrics( + self, + pred: torch.Tensor, + label: torch.Tensor, + inputs: torch.Tensor, + mask: torch.Tensor, + phase_type: str, + ) -> Dict[str, torch.Tensor]: + metrics_dict = getattr(self, f"{phase_type}_metrics") + metrics_result = {} + for name, metric in metrics_dict.items(): + metrics_result[f"Metrics/{phase_type}/{name}"] = metric(pred, label, inputs, mask) + return metrics_result + + def train_dataloader(self): + """Training dataloader""" + return self.train_loader + + def val_dataloader(self): + """Validation dataloader""" + return self.val_loader + + +def _get_model( + num_sources, + enc_kernel_size=16, + enc_num_feats=512, + msk_kernel_size=3, + msk_num_feats=128, + msk_num_hidden_feats=512, + msk_num_layers=8, + msk_num_stacks=3, + msk_activate="relu", +): + model = torchaudio.models.ConvTasNet( + num_sources=num_sources, + enc_kernel_size=enc_kernel_size, + enc_num_feats=enc_num_feats, + msk_kernel_size=msk_kernel_size, + msk_num_feats=msk_num_feats, + msk_num_hidden_feats=msk_num_hidden_feats, + msk_num_layers=msk_num_layers, + msk_num_stacks=msk_num_stacks, + msk_activate=msk_activate, + ) + return model + + +def _get_dataloader( + dataset_type: str, + root_dir: Union[str, Path], + num_speakers: int = 2, + sample_rate: int = 8000, + batch_size: int = 6, + num_workers: int = 4, + librimix_task: Optional[str] = None, + librimix_tr_split: Optional[str] = None, +) -> Tuple[DataLoader]: + """Get dataloaders for training, validation, and testing. + + Args: + dataset_type (str): the dataset to use. + root_dir (str or Path): the root directory of the dataset. + num_speakers (int, optional): the number of speakers in the mixture. (Default: 2) + sample_rate (int, optional): the sample rate of the audio. (Default: 8000) + batch_size (int, optional): the batch size of the dataset. (Default: 6) + num_workers (int, optional): the number of workers for each dataloader. (Default: 4) + librimix_task (str or None, optional): the task in LibriMix dataset. + librimix_tr_split (str or None, optional): the training split in LibriMix dataset. + + Returns: + tuple: (train_loader, valid_loader, eval_loader) + """ + train_dataset, valid_dataset, eval_dataset = dataset_utils.get_dataset( + dataset_type, root_dir, num_speakers, sample_rate, librimix_task, librimix_tr_split + ) + train_collate_fn = dataset_utils.get_collate_fn( + dataset_type, mode='train', sample_rate=sample_rate, duration=3 + ) + + test_collate_fn = dataset_utils.get_collate_fn(dataset_type, mode='test', sample_rate=sample_rate) + + train_loader = DataLoader( + train_dataset, + batch_size=batch_size, + shuffle=True, + collate_fn=train_collate_fn, + num_workers=num_workers, + drop_last=True, + ) + valid_loader = DataLoader( + valid_dataset, + batch_size=batch_size, + collate_fn=test_collate_fn, + num_workers=num_workers, + drop_last=True, + ) + eval_loader = DataLoader( + eval_dataset, + batch_size=batch_size, + collate_fn=test_collate_fn, + num_workers=num_workers, + ) + return train_loader, valid_loader, eval_loader + + +def cli_main(): + parser = ArgumentParser() + parser.add_argument("--batch-size", default=6, type=int) + parser.add_argument("--dataset", default="librimix", type=str, choices=["wsj0-mix", "librimix"]) + parser.add_argument( + "--root-dir", + type=Path, + help="The path to the directory where the directory ``Libri2Mix`` or ``Libri3Mix`` is stored.", + ) + parser.add_argument( + "--librimix-tr-split", + default="train-360", + choices=["train-360", "train-100"], + help="The training partition of librimix dataset. (default: ``train-360``)", + ) + parser.add_argument( + "--librimix-task", + default="sep_clean", + type=str, + choices=["sep_clean", "sep_noisy", "enh_single", "enh_both"], + help="The task to perform (separation or enhancement, noisy or clean). (default: ``sep_clean``)", + ) + parser.add_argument( + "--num-speakers", default=2, type=int, help="The number of speakers in the mixture. (default: 2)" + ) + parser.add_argument( + "--sample-rate", + default=8000, + type=int, + help="Sample rate of audio files in the given dataset. (default: 8000)", + ) + parser.add_argument( + "--exp-dir", + default=Path("./exp"), + type=Path, + help="The directory to save checkpoints and logs." + ) + parser.add_argument( + "--epochs", + metavar="NUM_EPOCHS", + default=200, + type=int, + help="The number of epochs to train. (default: 200)", + ) + parser.add_argument( + "--learning-rate", + default=1e-3, + type=float, + help="Initial learning rate. (default: 1e-3)", + ) + parser.add_argument( + "--num-gpu", + default=1, + type=int, + help="The number of GPUs for training. (default: 1)", + ) + parser.add_argument( + "--num-workers", + default=4, + type=int, + help="The number of workers for dataloader. (default: 4)", + ) + + args = parser.parse_args() + + model = _get_model(num_sources=args.num_speakers) + + optimizer = torch.optim.Adam(model.parameters(), lr=args.learning_rate) + lr_scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau( + optimizer, mode="min", factor=0.5, patience=5 + ) + train_loader, valid_loader, eval_loader = _get_dataloader( + args.dataset, + args.root_dir, + args.num_speakers, + args.sample_rate, + args.batch_size, + args.num_workers, + args.librimix_task, + args.librimix_tr_split, + ) + loss = si_sdr_loss + metric_dict = { + "sdri": sdri_metric, + "sisdri": sisdri_metric, + } + model = ConvTasNetModule( + model=model, + train_loader=train_loader, + val_loader=valid_loader, + loss=loss, + optim=optimizer, + metrics=metric_dict, + lr_scheduler=lr_scheduler, + ) + checkpoint_dir = args.exp_dir / "checkpoints" + checkpoint = ModelCheckpoint( + checkpoint_dir, + monitor="Losses/val_loss", + mode="min", + save_top_k=5, + save_weights_only=True, + verbose=True + ) + callbacks = [ + checkpoint, + EarlyStopping(monitor="Losses/val_loss", mode="min", patience=30, verbose=True), + ] + trainer = Trainer( + default_root_dir=args.exp_dir, + max_epochs=args.epochs, + gpus=args.num_gpu, + accelerator="ddp", + plugins=DDPPlugin(find_unused_parameters=False), # make sure there is no unused params + limit_train_batches=1.0, # Useful for fast experiment + gradient_clip_val=5.0, + callbacks=callbacks, + ) + trainer.fit(model) + model.load_from_checkpoint(checkpoint.best_model_path) + state_dict = torch.load(checkpoint.best_model_path, map_location="cpu") + state_dict = {k.replace("model.", ""): v for k, v in state_dict["state_dict"].items()} + torch.save(state_dict, args.exp_dir / "best_model.pth") + trainer.test(model, eval_loader) + + +if __name__ == "__main__": + cli_main() diff --git a/examples/source_separation/train.py b/examples/source_separation/train.py new file mode 100644 index 00000000..a969d33f --- /dev/null +++ b/examples/source_separation/train.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python3 +"""Launch souce separation training. + +This script runs training in Distributed Data Parallel (DDP) framework and has two major +operation modes. This behavior depends on if `--worker-id` argument is given or not. + +1. (`--worker-id` is not given) Launchs worker subprocesses that performs the actual training. +2. (`--worker-id` is given) Performs the training as a part of distributed training. + +When launching the script without any distributed trainig parameters (operation mode 1), +this script will check the number of GPUs available on the local system and spawns the same +number of training subprocesses (as operaiton mode 2). You can reduce the number of GPUs with +`--num-workers`. If there is no GPU available, only one subprocess is launched. + +When launching the script as a worker process of a distributed training, you need to configure +the coordination of the workers. +""" +import sys +import logging +import argparse +import subprocess + +import torch + +from utils import dist_utils + +_LG = dist_utils.getLogger(__name__) + + +def _parse_args(args=None): + max_world_size = torch.cuda.device_count() or 1 + + parser = argparse.ArgumentParser( + description=__doc__, + ) + parser.add_argument("--debug", action="store_true", help="Enable debug log") + group = parser.add_argument_group("Distributed Training") + group.add_argument( + "--worker-id", + type=int, + help=( + "If not provided, the launched process serves as a master process of " + "single-node, multi-worker training and spawns the worker subprocesses. " + "If provided, the launched process serves as a worker process, which " + "performs the actual training. The valid value is [0, --num-workers)." + ), + ) + group.add_argument( + "--device-id", + type=int, + help="The CUDA device ID. Allowed only when --worker-id is provided.", + ) + group.add_argument( + "--num-workers", + type=int, + default=max_world_size, + help=( + "The size of distributed trainig workers. " + "If launching a training as single-node, multi-worker training, " + "(i.e. --worker-id is not provided) then this value should not exceed " + "the number of available GPUs. " + "If launching the training process as a multi-node, multi-gpu training, " + "(i.e. --worker-id is provided) then the value has to match " + f"the number of workers across nodes. (default: {max_world_size})" + ), + ) + group.add_argument( + "--sync-protocol", + type=str, + default="env://", + help=( + "Synchronization protocol for distributed training. " + "This value is passed as `init_method` argument of " + "`torch.distributed.init_process_group` function." + 'If you are using `"env://"`, you can additionally configure ' + 'environment variables "MASTER_ADDR" and "MASTER_PORT". ' + 'If you are using `"file://..."`, then the process has to have ' + "the access to the designated file. " + "See the documentation for `torch.distributed` for the detail. " + 'If you are running the training in a single node, `"env://"` ' + "should do. If you are running the training in multiple nodes, " + "you need to provide the file location where all the nodes have " + 'access, using `"file://..."` protocol. (default: "env://")' + ), + ) + group.add_argument( + "--random-seed", + type=int, + help="Set random seed value. (default: None)", + ) + parser.add_argument( + "rest", nargs=argparse.REMAINDER, help="Model-specific arguments." + ) + namespace = parser.parse_args(args) + if namespace.worker_id is None: + if namespace.device_id is not None: + raise ValueError( + "`--device-id` cannot be provided when runing as master process." + ) + if namespace.num_workers > max_world_size: + raise ValueError( + "--num-workers ({num_workers}) cannot exceed {device_count}." + ) + if namespace.rest[:1] == ["--"]: + namespace.rest = namespace.rest[1:] + return namespace + + +def _main(cli_args): + args = _parse_args(cli_args) + + if any(arg in ["--help", "-h"] for arg in args.rest): + _run_training(args.rest) + + _init_logger(args.worker_id, args.debug) + if args.worker_id is None: + _run_training_subprocesses(args.num_workers, cli_args) + else: + dist_utils.setup_distributed( + world_size=args.num_workers, + rank=args.worker_id, + local_rank=args.device_id, + backend='nccl' if torch.cuda.is_available() else 'gloo', + init_method=args.sync_protocol, + ) + if args.random_seed is not None: + torch.manual_seed(args.random_seed) + if torch.cuda.is_available(): + torch.cuda.set_device(args.device_id) + _LG.info("CUDA device set to %s", args.device_id) + _run_training(args.rest) + + +def _run_training_subprocesses(num_workers, original_args): + workers = [] + _LG.info("Spawning %s workers", num_workers) + for i in range(num_workers): + worker_arg = ["--worker-id", f"{i}", "--num-workers", f"{num_workers}"] + device_arg = ["--device-id", f"{i}"] if torch.cuda.is_available() else [] + command = ( + [sys.executable, "-u", sys.argv[0]] + + worker_arg + + device_arg + + original_args + ) + _LG.info("Launching worker %s: `%s`", i, " ".join(command)) + worker = subprocess.Popen(command) + workers.append(worker) + + num_failed = 0 + for worker in workers: + worker.wait() + if worker.returncode != 0: + num_failed += 1 + sys.exit(num_failed) + + +def _run_training(args): + import conv_tasnet.train + + conv_tasnet.train.train(args) + + +def _init_logger(rank=None, debug=False): + worker_fmt = "[master]" if rank is None else f"[worker {rank:2d}]" + message_fmt = ( + "%(levelname)5s: %(funcName)10s: %(message)s" if debug else "%(message)s" + ) + logging.basicConfig( + level=logging.DEBUG if debug else logging.INFO, + format=f"%(asctime)s: {worker_fmt} {message_fmt}", + ) + + +if __name__ == "__main__": + _main(sys.argv[1:]) diff --git a/examples/source_separation/utils/__init__.py b/examples/source_separation/utils/__init__.py new file mode 100644 index 00000000..0d9f0206 --- /dev/null +++ b/examples/source_separation/utils/__init__.py @@ -0,0 +1,7 @@ +from . import ( + dataset, + dist_utils, + metrics, +) + +__all__ = ['dataset', 'dist_utils', 'metrics'] diff --git a/examples/source_separation/utils/dataset/__init__.py b/examples/source_separation/utils/dataset/__init__.py new file mode 100644 index 00000000..45eb6a78 --- /dev/null +++ b/examples/source_separation/utils/dataset/__init__.py @@ -0,0 +1,3 @@ +from . import utils, wsj0mix + +__all__ = ['utils', 'wsj0mix'] diff --git a/examples/source_separation/utils/dataset/utils.py b/examples/source_separation/utils/dataset/utils.py new file mode 100644 index 00000000..a53cc123 --- /dev/null +++ b/examples/source_separation/utils/dataset/utils.py @@ -0,0 +1,89 @@ +from typing import List +from functools import partial +from collections import namedtuple + +from torchaudio.datasets import LibriMix +import torch + +from . import wsj0mix + +Batch = namedtuple("Batch", ["mix", "src", "mask"]) + + +def get_dataset(dataset_type, root_dir, num_speakers, sample_rate, task=None, librimix_tr_split=None): + if dataset_type == "wsj0mix": + train = wsj0mix.WSJ0Mix(root_dir / "tr", num_speakers, sample_rate) + validation = wsj0mix.WSJ0Mix(root_dir / "cv", num_speakers, sample_rate) + evaluation = wsj0mix.WSJ0Mix(root_dir / "tt", num_speakers, sample_rate) + elif dataset_type == "librimix": + train = LibriMix(root_dir, librimix_tr_split, num_speakers, sample_rate, task) + validation = LibriMix(root_dir, "dev", num_speakers, sample_rate, task) + evaluation = LibriMix(root_dir, "test", num_speakers, sample_rate, task) + else: + raise ValueError(f"Unexpected dataset: {dataset_type}") + return train, validation, evaluation + + +def _fix_num_frames(sample: wsj0mix.SampleType, target_num_frames: int, sample_rate: int, random_start=False): + """Ensure waveform has exact number of frames by slicing or padding""" + mix = sample[1] # [1, time] + src = torch.cat(sample[2], 0) # [num_sources, time] + + num_channels, num_frames = src.shape + num_seconds = torch.div(num_frames, sample_rate, rounding_mode='floor') + target_seconds = torch.div(target_num_frames, sample_rate, rounding_mode='floor') + if num_frames >= target_num_frames: + if random_start and num_frames > target_num_frames: + start_frame = torch.randint(num_seconds - target_seconds + 1, [1]) * sample_rate + mix = mix[:, start_frame:] + src = src[:, start_frame:] + mix = mix[:, :target_num_frames] + src = src[:, :target_num_frames] + mask = torch.ones_like(mix) + else: + num_padding = target_num_frames - num_frames + pad = torch.zeros([1, num_padding], dtype=mix.dtype, device=mix.device) + mix = torch.cat([mix, pad], 1) + src = torch.cat([src, pad.expand(num_channels, -1)], 1) + mask = torch.ones_like(mix) + mask[..., num_frames:] = 0 + return mix, src, mask + + +def collate_fn_wsj0mix_train(samples: List[wsj0mix.SampleType], sample_rate, duration): + target_num_frames = int(duration * sample_rate) + + mixes, srcs, masks = [], [], [] + for sample in samples: + mix, src, mask = _fix_num_frames(sample, target_num_frames, sample_rate, random_start=True) + + mixes.append(mix) + srcs.append(src) + masks.append(mask) + + return Batch(torch.stack(mixes, 0), torch.stack(srcs, 0), torch.stack(masks, 0)) + + +def collate_fn_wsj0mix_test(samples: List[wsj0mix.SampleType], sample_rate): + max_num_frames = max(s[1].shape[-1] for s in samples) + + mixes, srcs, masks = [], [], [] + for sample in samples: + mix, src, mask = _fix_num_frames(sample, max_num_frames, sample_rate, random_start=False) + + mixes.append(mix) + srcs.append(src) + masks.append(mask) + + return Batch(torch.stack(mixes, 0), torch.stack(srcs, 0), torch.stack(masks, 0)) + + +def get_collate_fn(dataset_type, mode, sample_rate=None, duration=4): + assert mode in ["train", "test"] + if dataset_type in ["wsj0mix", "librimix"]: + if mode == 'train': + if sample_rate is None: + raise ValueError("sample_rate is not given.") + return partial(collate_fn_wsj0mix_train, sample_rate=sample_rate, duration=duration) + return partial(collate_fn_wsj0mix_test, sample_rate=sample_rate) + raise ValueError(f"Unexpected dataset: {dataset_type}") diff --git a/examples/source_separation/utils/dataset/wsj0mix.py b/examples/source_separation/utils/dataset/wsj0mix.py new file mode 100644 index 00000000..89d59f3a --- /dev/null +++ b/examples/source_separation/utils/dataset/wsj0mix.py @@ -0,0 +1,70 @@ +from pathlib import Path +from typing import Union, Tuple, List + +import torch +from torch.utils.data import Dataset + +import torchaudio + +SampleType = Tuple[int, torch.Tensor, List[torch.Tensor]] + + +class WSJ0Mix(Dataset): + """Create a Dataset for wsj0-mix. + + Args: + root (str or Path): Path to the directory where the dataset is found. + num_speakers (int): The number of speakers, which determines the directories + to traverse. The Dataset will traverse ``s1`` to ``sN`` directories to collect + N source audios. + sample_rate (int): Expected sample rate of audio files. If any of the audio has a + different sample rate, raises ``ValueError``. + audio_ext (str, optional): The extension of audio files to find. (default: ".wav") + """ + def __init__( + self, + root: Union[str, Path], + num_speakers: int, + sample_rate: int, + audio_ext: str = ".wav", + ): + self.root = Path(root) + self.sample_rate = sample_rate + self.mix_dir = (self.root / "mix").resolve() + self.src_dirs = [(self.root / f"s{i+1}").resolve() for i in range(num_speakers)] + + self.files = [p.name for p in self.mix_dir.glob(f"*{audio_ext}")] + self.files.sort() + + def _load_audio(self, path) -> torch.Tensor: + waveform, sample_rate = torchaudio.load(path) + if sample_rate != self.sample_rate: + raise ValueError( + f"The dataset contains audio file of sample rate {sample_rate}, " + f"but the requested sample rate is {self.sample_rate}." + ) + return waveform + + def _load_sample(self, filename) -> SampleType: + mixed = self._load_audio(str(self.mix_dir / filename)) + srcs = [] + for i, dir_ in enumerate(self.src_dirs): + src = self._load_audio(str(dir_ / filename)) + if mixed.shape != src.shape: + raise ValueError( + f"Different waveform shapes. mixed: {mixed.shape}, src[{i}]: {src.shape}" + ) + srcs.append(src) + return self.sample_rate, mixed, srcs + + def __len__(self) -> int: + return len(self.files) + + def __getitem__(self, key: int) -> SampleType: + """Load the n-th sample from the dataset. + Args: + key (int): The index of the sample to be loaded + Returns: + tuple: ``(sample_rate, mix_waveform, list_of_source_waveforms)`` + """ + return self._load_sample(self.files[key]) diff --git a/examples/source_separation/utils/dist_utils.py b/examples/source_separation/utils/dist_utils.py new file mode 100644 index 00000000..380358b8 --- /dev/null +++ b/examples/source_separation/utils/dist_utils.py @@ -0,0 +1,86 @@ +import os +import csv +import types +import logging + +import torch +import torch.distributed as dist + + +def _info_on_master(self, *args, **kwargs): + if dist.get_rank() == 0: + self.info(*args, **kwargs) + + +def getLogger(name): + """Get logging.Logger module with additional ``info_on_master`` method.""" + logger = logging.getLogger(name) + logger.info_on_master = types.MethodType(_info_on_master, logger) + return logger + + +_LG = getLogger(__name__) + + +def setup_distributed( + world_size, rank, local_rank, backend="nccl", init_method="env://" +): + """Perform env setup and initialization for distributed training""" + if init_method == "env://": + _set_env_vars(world_size, rank, local_rank) + if world_size > 1 and "OMP_NUM_THREADS" not in os.environ: + _LG.info("Setting OMP_NUM_THREADS == 1") + os.environ["OMP_NUM_THREADS"] = "1" + params = { + "backend": backend, + "init_method": init_method, + "world_size": world_size, + "rank": rank, + } + _LG.info("Initializing distributed process group with %s", params) + dist.init_process_group(**params) + _LG.info("Initialized distributed process group.") + + +def _set_env_vars(world_size, rank, local_rank): + for key, default in [("MASTER_ADDR", "127.0.0.1"), ("MASTER_PORT", "29500")]: + if key not in os.environ: + os.environ[key] = default + + os.environ["WORLD_SIZE"] = str(world_size) + os.environ["RANK"] = str(rank) + os.environ["LOCAL_RANK"] = str(local_rank) + + +def save_on_master(path, obj): + if dist.get_rank() == 0: + _LG.info("Saving %s", path) + torch.save(obj, path) + + +def write_csv_on_master(path, *rows): + if dist.get_rank() == 0: + with open(path, "a", newline="") as fileobj: + writer = csv.writer(fileobj) + for row in rows: + writer.writerow(row) + + +def synchronize_params(path, device, *modules): + if dist.get_world_size() < 2: + return + rank = dist.get_rank() + if rank == 0: + _LG.info("[Parameter Sync]: Saving parameters to a temp file...") + torch.save({f"{i}": m.state_dict() for i, m in enumerate(modules)}, path) + dist.barrier() + if rank != 0: + _LG.info("[Parameter Sync]: Loading parameters...") + data = torch.load(path, map_location=device) + for i, m in enumerate(modules): + m.load_state_dict(data[f"{i}"]) + dist.barrier() + if rank == 0: + _LG.info("[Parameter Sync]: Removing the temp file...") + os.remove(path) + _LG.info_on_master("[Parameter Sync]: Complete.") diff --git a/examples/source_separation/utils/metrics.py b/examples/source_separation/utils/metrics.py new file mode 100644 index 00000000..03859aef --- /dev/null +++ b/examples/source_separation/utils/metrics.py @@ -0,0 +1,204 @@ +import math +from typing import Optional +from itertools import permutations + +import torch + + +def sdr( + estimate: torch.Tensor, + reference: torch.Tensor, + mask: Optional[torch.Tensor] = None, + epsilon: float = 1e-8 +) -> torch.Tensor: + """Computes source-to-distortion ratio. + + 1. scale the reference signal with power(s_est * s_ref) / powr(s_ref * s_ref) + 2. compute SNR between adjusted estimate and reference. + + Args: + estimate (torch.Tensor): Estimtaed signal. + Shape: [batch, speakers (can be 1), time frame] + reference (torch.Tensor): Reference signal. + Shape: [batch, speakers, time frame] + mask (torch.Tensor or None, optional): Binary mask to indicate padded value (0) or valid value (1). + Shape: [batch, 1, time frame] + epsilon (float, optional): constant value used to stabilize division. + + Returns: + torch.Tensor: scale-invariant source-to-distortion ratio. + Shape: [batch, speaker] + + References: + - Single-channel multi-speaker separation using deep clustering + Y. Isik, J. Le Roux, Z. Chen, S. Watanabe, and J. R. Hershey, + - Conv-TasNet: Surpassing Ideal Time--Frequency Magnitude Masking for Speech Separation + Luo, Yi and Mesgarani, Nima + https://arxiv.org/abs/1809.07454 + + Notes: + This function is tested to produce the exact same result as + https://github.com/naplab/Conv-TasNet/blob/e66d82a8f956a69749ec8a4ae382217faa097c5c/utility/sdr.py#L34-L56 + """ + reference_pow = reference.pow(2).mean(axis=2, keepdim=True) + mix_pow = (estimate * reference).mean(axis=2, keepdim=True) + scale = mix_pow / (reference_pow + epsilon) + + reference = scale * reference + error = estimate - reference + + reference_pow = reference.pow(2) + error_pow = error.pow(2) + + if mask is None: + reference_pow = reference_pow.mean(axis=2) + error_pow = error_pow.mean(axis=2) + else: + denom = mask.sum(axis=2) + reference_pow = (mask * reference_pow).sum(axis=2) / denom + error_pow = (mask * error_pow).sum(axis=2) / denom + + return 10 * torch.log10(reference_pow) - 10 * torch.log10(error_pow) + + +class PIT(torch.nn.Module): + """Applies utterance-level speaker permutation + + Computes the maxium possible value of the given utility function + over the permutations of the speakers. + + Args: + utility_func (function): + Function that computes the utility (opposite of loss) with signature of + (extimate: torch.Tensor, reference: torch.Tensor) -> torch.Tensor + where input Tensors are shape of [batch, speakers, frame] and + the output Tensor is shape of [batch, speakers]. + + References: + - Multi-talker Speech Separation with Utterance-level Permutation Invariant Training of + Deep Recurrent Neural Networks + Morten Kolbæk, Dong Yu, Zheng-Hua Tan and Jesper Jensen + https://arxiv.org/abs/1703.06284 + """ + + def __init__(self, utility_func): + super().__init__() + self.utility_func = utility_func + + def forward( + self, + estimate: torch.Tensor, + reference: torch.Tensor, + mask: Optional[torch.Tensor] = None, + epsilon: float = 1e-8 + ) -> torch.Tensor: + """Compute utterance-level PIT Loss + + Args: + estimate (torch.Tensor): Estimated source signals. + Shape: [bacth, speakers, time frame] + reference (torch.Tensor): Reference (original) source signals. + Shape: [batch, speakers, time frame] + mask (torch.Tensor or None, optional): Binary mask to indicate padded value (0) or valid value (1). + Shape: [batch, 1, time frame] + epsilon (float, optional): constant value used to stabilize division. + + Returns: + torch.Tensor: Maximum criterion over the speaker permutation. + Shape: [batch, ] + """ + assert estimate.shape == reference.shape + + batch_size, num_speakers = reference.shape[:2] + num_permute = math.factorial(num_speakers) + + util_mat = torch.zeros( + batch_size, num_permute, dtype=estimate.dtype, device=estimate.device + ) + for i, idx in enumerate(permutations(range(num_speakers))): + util = self.utility_func(estimate, reference[:, idx, :], mask=mask, epsilon=epsilon) + util_mat[:, i] = util.mean(dim=1) # take the average over speaker dimension + return util_mat.max(dim=1).values + + +_sdr_pit = PIT(utility_func=sdr) + + +def sdr_pit( + estimate: torch.Tensor, + reference: torch.Tensor, + mask: Optional[torch.Tensor] = None, + epsilon: float = 1e-8): + """Computes scale-invariant source-to-distortion ratio. + + 1. adjust both estimate and reference to have 0-mean + 2. scale the reference signal with power(s_est * s_ref) / powr(s_ref * s_ref) + 3. compute SNR between adjusted estimate and reference. + + Args: + estimate (torch.Tensor): Estimtaed signal. + Shape: [batch, speakers (can be 1), time frame] + reference (torch.Tensor): Reference signal. + Shape: [batch, speakers, time frame] + mask (torch.Tensor or None, optional): Binary mask to indicate padded value (0) or valid value (1). + Shape: [batch, 1, time frame] + epsilon (float, optional): constant value used to stabilize division. + + Returns: + torch.Tensor: scale-invariant source-to-distortion ratio. + Shape: [batch, speaker] + + References: + - Single-channel multi-speaker separation using deep clustering + Y. Isik, J. Le Roux, Z. Chen, S. Watanabe, and J. R. Hershey, + - Conv-TasNet: Surpassing Ideal Time--Frequency Magnitude Masking for Speech Separation + Luo, Yi and Mesgarani, Nima + https://arxiv.org/abs/1809.07454 + + Notes: + This function is tested to produce the exact same result as the reference implementation, + *when the inputs have 0-mean* + https://github.com/naplab/Conv-TasNet/blob/e66d82a8f956a69749ec8a4ae382217faa097c5c/utility/sdr.py#L107-L153 + """ + return _sdr_pit(estimate, reference, mask, epsilon) + + +def sdri( + estimate: torch.Tensor, + reference: torch.Tensor, + mix: torch.Tensor, + mask: Optional[torch.Tensor] = None, + epsilon: float = 1e-8, +) -> torch.Tensor: + """Compute the improvement of SDR (SDRi). + + This function compute how much SDR is improved if the estimation is changed from + the original mixture signal to the actual estimated source signals. That is, + ``SDR(estimate, reference) - SDR(mix, reference)``. + + For computing ``SDR(estimate, reference)``, PIT (permutation invariant training) is applied, + so that best combination of sources between the reference signals and the esimate signals + are picked. + + Args: + estimate (torch.Tensor): Estimated source signals. + Shape: [batch, speakers, time frame] + reference (torch.Tensor): Reference (original) source signals. + Shape: [batch, speakers, time frame] + mix (torch.Tensor): Mixed souce signals, from which the setimated signals were generated. + Shape: [batch, speakers == 1, time frame] + mask (torch.Tensor or None, optional): Binary mask to indicate padded value (0) or valid value (1). + Shape: [batch, 1, time frame] + epsilon (float, optional): constant value used to stabilize division. + + Returns: + torch.Tensor: Improved SDR. Shape: [batch, ] + + References: + - Conv-TasNet: Surpassing Ideal Time--Frequency Magnitude Masking for Speech Separation + Luo, Yi and Mesgarani, Nima + https://arxiv.org/abs/1809.07454 + """ + sdr_ = sdr_pit(estimate, reference, mask=mask, epsilon=epsilon) # [batch, ] + base_sdr = sdr(mix, reference, mask=mask, epsilon=epsilon) # [batch, speaker] + return sdr_ - base_sdr.mean(dim=1) diff --git a/examples/test/__init__.py b/examples/test/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/examples/test/test_interactive_asr.py b/examples/test/test_interactive_asr.py new file mode 100644 index 00000000..40867a0f --- /dev/null +++ b/examples/test/test_interactive_asr.py @@ -0,0 +1,105 @@ +import argparse +import logging +import os +import unittest + +from interactive_asr.utils import setup_asr, transcribe_file + + +class ASRTest(unittest.TestCase): + logger = logging.getLogger(__name__) + logger.setLevel(logging.INFO) + + arguments_dict = { + "path": "/scratch/jamarshon/downloads/model.pt", + "input_file": "/scratch/jamarshon/audio/examples/interactive_asr/data/sample.wav", + "data": "/scratch/jamarshon/downloads", + "user_dir": "/scratch/jamarshon/fairseq-py/examples/speech_recognition", + "no_progress_bar": False, + "log_interval": 1000, + "log_format": None, + "tensorboard_logdir": "", + "tbmf_wrapper": False, + "seed": 1, + "cpu": True, + "fp16": False, + "memory_efficient_fp16": False, + "fp16_init_scale": 128, + "fp16_scale_window": None, + "fp16_scale_tolerance": 0.0, + "min_loss_scale": 0.0001, + "threshold_loss_scale": None, + "criterion": "cross_entropy", + "tokenizer": None, + "bpe": None, + "optimizer": "nag", + "lr_scheduler": "fixed", + "task": "speech_recognition", + "num_workers": 0, + "skip_invalid_size_inputs_valid_test": False, + "max_tokens": 10000000, + "max_sentences": None, + "required_batch_size_multiple": 8, + "dataset_impl": None, + "gen_subset": "test", + "num_shards": 1, + "shard_id": 0, + "remove_bpe": None, + "quiet": False, + "model_overrides": "{}", + "results_path": None, + "beam": 40, + "nbest": 1, + "max_len_a": 0, + "max_len_b": 200, + "min_len": 1, + "match_source_len": False, + "no_early_stop": False, + "unnormalized": False, + "no_beamable_mm": False, + "lenpen": 1, + "unkpen": 0, + "replace_unk": None, + "sacrebleu": False, + "score_reference": False, + "prefix_size": 0, + "no_repeat_ngram_size": 0, + "sampling": False, + "sampling_topk": -1, + "sampling_topp": -1.0, + "temperature": 1.0, + "diverse_beam_groups": -1, + "diverse_beam_strength": 0.5, + "print_alignment": False, + "ctc": False, + "rnnt": False, + "kspmodel": None, + "wfstlm": None, + "rnnt_decoding_type": "greedy", + "lm_weight": 0.2, + "rnnt_len_penalty": -0.5, + "momentum": 0.99, + "weight_decay": 0.0, + "force_anneal": None, + "lr_shrink": 0.1, + "warmup_updates": 0, + } + + arguments_dict["path"] = os.environ.get("ASR_MODEL_PATH", None) + arguments_dict["input_file"] = os.environ.get("ASR_INPUT_FILE", None) + arguments_dict["data"] = os.environ.get("ASR_DATA_PATH", None) + arguments_dict["user_dir"] = os.environ.get("ASR_USER_DIR", None) + args = argparse.Namespace(**arguments_dict) + + def test_transcribe_file(self): + task, generator, models, sp, tgt_dict = setup_asr(self.args, self.logger) + _, transcription = transcribe_file( + self.args, task, generator, models, sp, tgt_dict + ) + + expected_transcription = [["THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG"]] + self.assertEqual(transcription, expected_transcription, msg=str(transcription)) + + +if __name__ == "__main__": + unittest.main() diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 00000000..068f0918 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,3 @@ +[mypy] +allow_redefinition = True +ignore_missing_imports = True diff --git a/packaging/README.md b/packaging/README.md new file mode 100644 index 00000000..f3ee62e6 --- /dev/null +++ b/packaging/README.md @@ -0,0 +1,95 @@ +# Building torchaudio packages for release + + ## Anaconda packages + + ### Linux + + ```bash +docker run -it --ipc=host --rm -v $(pwd):/remote soumith/conda-cuda bash +cd remote +PYTHON_VERSION=3.7 packaging/build_conda.sh +``` + +To install bz2, +```bash +cd /opt/conda/conda-bld/linux-64/ +# install dependencies +conda install pytorch-cpu=1.1.0 +conda install sox +# install torchaudio +conda install /opt/conda/conda-bld/linux-64/torchaudio-cpu-0.2.0-py27_1.tar.bz2 +``` + +To upload bz2, +```bash +anaconda upload -u pytorch /opt/conda/conda-bld/linux-64/torchaudio*.bz2 +``` + + ### OSX + + ```bash +# create a fresh anaconda environment / install and activate it +PYTHON_VERSION=3.7 packaging/build_conda.sh +``` + +To install bz2, +```bash +cd /Users/jamarshon/anaconda3/conda-bld/osx-64/ +# activate conda env (e.g +conda info --envs +conda activate /Users/jamarshon/minconda_wheel_env_tmp/envs/env2.7 +# install dependencies +conda install pytorch-cpu=1.1.0 +conda install sox +# install torchaudio +# and then try installing (e.g +conda install /Users/jamarshon/anaconda3/conda-bld/osx-64/torchaudio-0.2.0-py27_1.tar.bz2 +``` + +To upload bz2, +```bash +anaconda upload -u pytorch /Users/jamarshon/anaconda3/conda-bld/osx-64/torchaudio*.bz2 +``` + + ## Wheels + + ### Linux + + ```bash +nvidia-docker run -it --ipc=host --rm -v $(pwd):/remote soumith/manylinux-cuda90:latest bash +cd remote +PYTHON_VERSION=3.7 packaging/build_wheel.sh +``` + +To install wheels, +```bash +cd ../cpu +/opt/python/cp35-cp35m/bin/pip install torchaudio-0.2-cp35-cp35m-linux_x86_64.whl +``` + +To upload wheels, +```bash +cd ../cpu +/opt/python/cp35-cp35m/bin/pip install twine +/opt/python/cp35-cp35m/bin/twine upload *.whl +``` + + ### OSX + + ```bash +PYTHON_VERSION=3.7 packaging/build_wheel.sh +``` + +To install wheels, +```bash +cd ~/torchaudio_wheels +conda activate /Users/jamarshon/minconda_wheel_env_tmp/envs/env2.7 +pip install torchaudio-0.2-cp27-cp27m-macosx_10_6_x86_64.whl +``` + +To upload wheels, +```bash +pip install twine +cd ~/torchaudio_wheels +twine upload *.whl +``` diff --git a/packaging/build_conda.sh b/packaging/build_conda.sh new file mode 100644 index 00000000..0e6aae53 --- /dev/null +++ b/packaging/build_conda.sh @@ -0,0 +1,14 @@ +#!/bin/bash +set -ex + +script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +. "$script_dir/pkg_helpers.bash" + +export BUILD_TYPE="conda" +setup_env 0.10.0 +export SOURCE_ROOT_DIR="$PWD" +setup_conda_pytorch_constraint +setup_conda_cudatoolkit_constraint +setup_visual_studio_constraint +# nvidia channel included for cudatoolkit >= 11 +conda build -c defaults -c nvidia $CONDA_CHANNEL_FLAGS --no-anaconda-upload --python "$PYTHON_VERSION" packaging/torchaudio diff --git a/packaging/build_wheel.sh b/packaging/build_wheel.sh new file mode 100644 index 00000000..0ca814ca --- /dev/null +++ b/packaging/build_wheel.sh @@ -0,0 +1,18 @@ +#!/bin/bash +set -ex + +script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +. "$script_dir/pkg_helpers.bash" + +export BUILD_TYPE="wheel" +setup_env 0.10.0 +setup_wheel_python +pip_install numpy future cmake ninja +setup_pip_pytorch_version +python setup.py clean +if [[ "$OSTYPE" == "msys" ]]; then + python_tag="$(echo "cp$PYTHON_VERSION" | tr -d '.')" + "$script_dir/vc_env_helper.bat" python setup.py bdist_wheel --plat-name win_amd64 --python-tag $python_tag +else + python setup.py bdist_wheel +fi diff --git a/packaging/pkg_helpers.bash b/packaging/pkg_helpers.bash new file mode 100644 index 00000000..4babde63 --- /dev/null +++ b/packaging/pkg_helpers.bash @@ -0,0 +1,302 @@ +# A set of useful bash functions for common functionality we need to do in +# many build scripts + + +# Setup CUDA environment variables, based on CU_VERSION +# +# Inputs: +# CU_VERSION (cpu, cu92, cu100) +# NO_CUDA_PACKAGE (bool) +# BUILD_TYPE (conda, wheel) +# +# Outputs: +# VERSION_SUFFIX (e.g., "") +# PYTORCH_VERSION_SUFFIX (e.g., +cpu) +# WHEEL_DIR (e.g., cu100/) +# CUDA_HOME (e.g., /usr/local/cuda-9.2, respected by torch.utils.cpp_extension) +# USE_CUDA (respected by torchaudio setup.py) +# NVCC_FLAGS (respected by torchaudio setup.py) +# +# Precondition: CUDA versions are installed in their conventional locations in +# /usr/local/cuda-* +# +# NOTE: Why VERSION_SUFFIX versus PYTORCH_VERSION_SUFFIX? If you're building +# a package with CUDA on a platform we support CUDA on, VERSION_SUFFIX == +# PYTORCH_VERSION_SUFFIX and everyone is happy. However, if you are building a +# package with only CPU bits (e.g., torchaudio), then VERSION_SUFFIX is always +# empty, but PYTORCH_VERSION_SUFFIX is +cpu (because that's how you get a CPU +# version of a Python package. But that doesn't apply if you're on OS X, +# since the default CU_VERSION on OS X is cpu. +setup_cuda() { + + # First, compute version suffixes. By default, assume no version suffixes + export VERSION_SUFFIX="" + export PYTORCH_VERSION_SUFFIX="" + export WHEEL_DIR="cpu/" + # Wheel builds need suffixes (but not if they're on OS X, which never has suffix) + if [[ "$BUILD_TYPE" == "wheel" ]] && [[ "$(uname)" != Darwin ]]; then + export PYTORCH_VERSION_SUFFIX="+$CU_VERSION" + # Match the suffix scheme of pytorch, unless this package does not have + # CUDA builds (in which case, use default) + if [[ -z "$NO_CUDA_PACKAGE" ]]; then + export VERSION_SUFFIX="$PYTORCH_VERSION_SUFFIX" + export WHEEL_DIR="$CU_VERSION/" + fi + fi + + # Now work out the CUDA settings + case "$CU_VERSION" in + cu113) + if [[ "$OSTYPE" == "msys" ]]; then + export CUDA_HOME="C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v11.3" + else + export CUDA_HOME=/usr/local/cuda-11.3/ + fi + export TORCH_CUDA_ARCH_LIST="3.5;5.0+PTX;6.0;7.0;7.5;8.0;8.6" + ;; + cu112) + if [[ "$OSTYPE" == "msys" ]]; then + export CUDA_HOME="C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v11.2" + else + export CUDA_HOME=/usr/local/cuda-11.2/ + fi + export TORCH_CUDA_ARCH_LIST="3.5;5.0+PTX;6.0;7.0;7.5;8.0;8.6" + ;; + cu111) + if [[ "$OSTYPE" == "msys" ]]; then + export CUDA_HOME="C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v11.1" + else + export CUDA_HOME=/usr/local/cuda-11.1/ + fi + export TORCH_CUDA_ARCH_LIST="3.5;5.0+PTX;6.0;7.0;7.5;8.0;8.6" + ;; + cu110) + if [[ "$OSTYPE" == "msys" ]]; then + export CUDA_HOME="C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v11.0" + else + export CUDA_HOME=/usr/local/cuda-11.0/ + fi + export TORCH_CUDA_ARCH_LIST="3.5;5.0+PTX;6.0;7.0;7.5;8.0" + ;; + cu102) + if [[ "$OSTYPE" == "msys" ]]; then + export CUDA_HOME="C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v10.2" + else + export CUDA_HOME=/usr/local/cuda-10.2/ + fi + export TORCH_CUDA_ARCH_LIST="3.5;5.0+PTX;6.0;7.0;7.5" + ;; + cu101) + if [[ "$OSTYPE" == "msys" ]]; then + export CUDA_HOME="C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v10.1" + else + export CUDA_HOME=/usr/local/cuda-10.1/ + fi + export TORCH_CUDA_ARCH_LIST="3.5;5.0+PTX;6.0;7.0;7.5" + ;; + cu100) + export CUDA_HOME=/usr/local/cuda-10.0/ + export TORCH_CUDA_ARCH_LIST="3.5;5.0+PTX;6.0;7.0;7.5" + ;; + cu92) + export CUDA_HOME=/usr/local/cuda-9.2/ + export TORCH_CUDA_ARCH_LIST="3.5;5.0+PTX;6.0;7.0" + ;; + rocm*) + export USE_ROCM=1 + ;; + cpu) + ;; + *) + echo "Unrecognized CU_VERSION=$CU_VERSION" + exit 1 + ;; + esac + if [[ -n "$CUDA_HOME" ]]; then + # Adds nvcc binary to the search path so that CMake's `find_package(CUDA)` will pick the right one + export PATH="$CUDA_HOME/bin:$PATH" + # TODO: Fix Windows CUDA builds + if [[ "$OSTYPE" != "msys" ]]; then + # Force GPU builds on CPU runner, when `torch.cuda.is_available()` returns false + export USE_CUDA=1 + fi + fi +} + +# Populate build version if necessary, and add version suffix +# +# Inputs: +# BUILD_VERSION (e.g., 0.2.0 or empty) +# VERSION_SUFFIX (e.g., +cpu) +# +# Outputs: +# BUILD_VERSION (e.g., 0.2.0.dev20190807+cpu) +# +# Fill BUILD_VERSION if it doesn't exist already with a nightly string +# Usage: setup_build_version 0.2.0 +setup_build_version() { + if [[ -z "$BUILD_VERSION" ]]; then + export BUILD_VERSION="$1.dev$(date "+%Y%m%d")$VERSION_SUFFIX" + else + export BUILD_VERSION="$BUILD_VERSION$VERSION_SUFFIX" + fi +} + +# Set some useful variables for OS X, if applicable +setup_macos() { + if [[ "$(uname)" == Darwin ]]; then + export MACOSX_DEPLOYMENT_TARGET=10.9 CC=clang CXX=clang++ + fi +} + +# Top-level entry point for things every package will need to do +# +# Usage: setup_env 0.2.0 +setup_env() { + git submodule update --init --recursive + setup_cuda + setup_build_version "$1" + setup_macos +} + +# Function to retry functions that sometimes timeout or have flaky failures +retry () { + $* || (sleep 1 && $*) || (sleep 2 && $*) || (sleep 4 && $*) || (sleep 8 && $*) +} + +# Inputs: +# PYTHON_VERSION (2.7, 3.5, 3.6, 3.7) +# UNICODE_ABI (bool) +# +# Outputs: +# PATH modified to put correct Python version in PATH +# +# Precondition: If Linux, you are in a soumith/manylinux-cuda* Docker image +setup_wheel_python() { + if [[ "$(uname)" == Darwin || "$OSTYPE" == "msys" ]]; then + eval "$(conda shell.bash hook)" + conda env remove -n "env$PYTHON_VERSION" || true + conda create -yn "env$PYTHON_VERSION" python="$PYTHON_VERSION" + conda activate "env$PYTHON_VERSION" + else + case "$PYTHON_VERSION" in + 2.7) + if [[ -n "$UNICODE_ABI" ]]; then + python_abi=cp27-cp27mu + else + python_abi=cp27-cp27m + fi + ;; + 3.5) python_abi=cp35-cp35m ;; + 3.6) python_abi=cp36-cp36m ;; + 3.7) python_abi=cp37-cp37m ;; + 3.8) python_abi=cp38-cp38 ;; + 3.9) python_abi=cp39-cp39 ;; + *) + echo "Unrecognized PYTHON_VERSION=$PYTHON_VERSION" + exit 1 + ;; + esac + export PATH="/opt/python/$python_abi/bin:$PATH" + fi +} + +# Install with pip a bit more robustly than the default +pip_install() { + retry pip install --progress-bar off "$@" +} + +# Install torch with pip, respecting PYTORCH_VERSION, and record the installed +# version into PYTORCH_VERSION, if applicable +setup_pip_pytorch_version() { + if [[ -z "$PYTORCH_VERSION" ]]; then + # Install latest prerelease version of torch, per our nightlies, consistent + # with the requested cuda version + pip_install --pre torch -f "https://download.pytorch.org/whl/nightly/${WHEEL_DIR}torch_nightly.html" + # CUDA and CPU are ABI compatible on the CPU-only parts, so strip in this case + export PYTORCH_VERSION="$(pip show torch | grep ^Version: | sed 's/Version: *//' | sed 's/+.\+//')" + else + pip_install "torch==$PYTORCH_VERSION$PYTORCH_VERSION_SUFFIX" \ + -f https://download.pytorch.org/whl/torch_stable.html \ + -f "https://download.pytorch.org/whl/${UPLOAD_CHANNEL}/torch_${UPLOAD_CHANNEL}.html" + fi +} + +# Fill PYTORCH_VERSION with the latest conda nightly version, and +# CONDA_CHANNEL_FLAGS with appropriate flags to retrieve these versions +# +# You MUST have populated PYTORCH_VERSION_SUFFIX before hand. +setup_conda_pytorch_constraint() { + CONDA_CHANNEL_FLAGS="${CONDA_CHANNEL_FLAGS}" + if [[ -z "$PYTORCH_VERSION" ]]; then + export CONDA_CHANNEL_FLAGS="${CONDA_CHANNEL_FLAGS} -c pytorch-nightly" + export PYTORCH_VERSION="$(conda search --json 'pytorch[channel=pytorch-nightly]' | python -c "import sys, json, re; print(re.sub(r'\\+.*$', '', json.load(sys.stdin)['pytorch'][-1]['version']))")" + else + export CONDA_CHANNEL_FLAGS="${CONDA_CHANNEL_FLAGS} -c pytorch -c pytorch-test -c pytorch-nightly" + fi + if [[ "$CU_VERSION" == cpu ]]; then + export CONDA_PYTORCH_BUILD_CONSTRAINT="- pytorch==$PYTORCH_VERSION${PYTORCH_VERSION_SUFFIX}" + export CONDA_PYTORCH_CONSTRAINT="- pytorch==$PYTORCH_VERSION" + else + export CONDA_PYTORCH_BUILD_CONSTRAINT="- pytorch==${PYTORCH_VERSION}${PYTORCH_VERSION_SUFFIX}" + export CONDA_PYTORCH_CONSTRAINT="- pytorch==${PYTORCH_VERSION}${PYTORCH_VERSION_SUFFIX}" + fi + # TODO: Remove me later, see https://github.com/pytorch/pytorch/issues/62424 for more details + if [[ "$(uname)" == Darwin ]]; then + # Use less than equal to avoid version conflict in python=3.6 environment + export CONDA_EXTRA_BUILD_CONSTRAINT="- mkl<=2021.2.0" + fi +} + +# Translate CUDA_VERSION into CUDA_CUDATOOLKIT_CONSTRAINT +setup_conda_cudatoolkit_constraint() { + export CONDA_CPUONLY_FEATURE="" + if [[ "$(uname)" == Darwin ]]; then + export CONDA_CUDATOOLKIT_CONSTRAINT="" + else + case "$CU_VERSION" in + cu113) + export CONDA_CUDATOOLKIT_CONSTRAINT="- cudatoolkit >=11.3,<11.4 # [not osx]" + ;; + cu112) + export CONDA_CUDATOOLKIT_CONSTRAINT="- cudatoolkit >=11.2,<11.3 # [not osx]" + ;; + cu111) + export CONDA_CUDATOOLKIT_CONSTRAINT="- cudatoolkit >=11.1,<11.2 # [not osx]" + ;; + cu110) + export CONDA_CUDATOOLKIT_CONSTRAINT="- cudatoolkit >=11.0,<11.1 # [not osx]" + ;; + cu102) + export CONDA_CUDATOOLKIT_CONSTRAINT="- cudatoolkit >=10.2,<10.3 # [not osx]" + ;; + cu101) + export CONDA_CUDATOOLKIT_CONSTRAINT="- cudatoolkit >=10.1,<10.2 # [not osx]" + ;; + cu100) + export CONDA_CUDATOOLKIT_CONSTRAINT="- cudatoolkit >=10.0,<10.1 # [not osx]" + ;; + cu92) + export CONDA_CUDATOOLKIT_CONSTRAINT="- cudatoolkit >=9.2,<9.3 # [not osx]" + ;; + cpu) + export CONDA_CUDATOOLKIT_CONSTRAINT="" + export CONDA_CPUONLY_FEATURE="- cpuonly" + ;; + *) + echo "Unrecognized CU_VERSION=$CU_VERSION" + exit 1 + ;; + esac + fi +} + +# Build the proper compiler package before building the final package +setup_visual_studio_constraint() { + if [[ "$OSTYPE" == "msys" ]]; then + export VSTOOLCHAIN_PACKAGE=vs2019 + export VSDEVCMD_ARGS='' + conda build $CONDA_CHANNEL_FLAGS --no-anaconda-upload packaging/$VSTOOLCHAIN_PACKAGE + cp packaging/$VSTOOLCHAIN_PACKAGE/conda_build_config.yaml packaging/torchaudio/conda_build_config.yaml + fi +} diff --git a/packaging/torchaudio/bld.bat b/packaging/torchaudio/bld.bat new file mode 100644 index 00000000..6b31d431 --- /dev/null +++ b/packaging/torchaudio/bld.bat @@ -0,0 +1,5 @@ +@echo off + +set IS_CONDA=1 + +python setup.py install --single-version-externally-managed --record=record.txt diff --git a/packaging/torchaudio/build.sh b/packaging/torchaudio/build.sh new file mode 100644 index 00000000..e2b53ddc --- /dev/null +++ b/packaging/torchaudio/build.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -ex + +python setup.py install --single-version-externally-managed --record=record.txt diff --git a/packaging/torchaudio/meta.yaml b/packaging/torchaudio/meta.yaml new file mode 100644 index 00000000..c485d368 --- /dev/null +++ b/packaging/torchaudio/meta.yaml @@ -0,0 +1,61 @@ +package: + name: torchaudio + version: "{{ environ.get('BUILD_VERSION', '0.0.0') }}" + +source: + path: "{{ environ.get('SOURCE_ROOT_DIR', '../..') }}" + +requirements: + build: + - {{ compiler('c') }} # [win] + - {{ compiler('cxx') }} # [win] + + host: + - python + - setuptools + - cmake + - ninja + - defaults::numpy >=1.11 + {{ environ.get('CONDA_PYTORCH_BUILD_CONSTRAINT', 'pytorch') }} + {{ environ.get('CONDA_EXTRA_BUILD_CONSTRAINT', '') }} + {{ environ.get('CONDA_CPUONLY_FEATURE', '') }} + {{ environ.get('CONDA_CUDATOOLKIT_CONSTRAINT', '') }} + + run: + - python + - defaults::numpy >=1.11 + {{ environ.get('CONDA_PYTORCH_CONSTRAINT', 'pytorch') }} + {{ environ.get('CONDA_CUDATOOLKIT_CONSTRAINT', '') }} + +build: + string: py{{py}}_{{ environ.get('CU_VERSION', 'cpu') }} + script_env: + - BUILD_VERSION + - USE_CUDA # [not win] + - TORCH_CUDA_ARCH_LIST # [not win] + features: + {{ environ.get('CONDA_CPUONLY_FEATURE', '') }} + +test: + imports: + - torchaudio + - torchaudio.datasets + - torchaudio.kaldi_io + - torchaudio.sox_effects + - torchaudio.transforms + + source_files: + - test + + requires: + - pytest + # Ideally we would test this, but conda doesn't provide librosa + # - librosa >=0.4.3 + - scipy + {{ environ.get('CONDA_CPUONLY_FEATURE', '') }} + +about: + home: https://github.com/pytorch/audio + license: BSD + license_file: LICENSE + summary: 'simple audio I/O for pytorch' diff --git a/packaging/vc_env_helper.bat b/packaging/vc_env_helper.bat new file mode 100644 index 00000000..94101356 --- /dev/null +++ b/packaging/vc_env_helper.bat @@ -0,0 +1,39 @@ +@echo on + +set VC_VERSION_LOWER=16 +set VC_VERSION_UPPER=17 + +for /f "usebackq tokens=*" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -legacy -products * -version [%VC_VERSION_LOWER%^,%VC_VERSION_UPPER%^) -property installationPath`) do ( + if exist "%%i" if exist "%%i\VC\Auxiliary\Build\vcvarsall.bat" ( + set "VS15INSTALLDIR=%%i" + set "VS15VCVARSALL=%%i\VC\Auxiliary\Build\vcvarsall.bat" + goto vswhere + ) +) + +:vswhere +if "%VSDEVCMD_ARGS%" == "" ( + call "%VS15VCVARSALL%" x64 || exit /b 1 +) else ( + call "%VS15VCVARSALL%" x64 %VSDEVCMD_ARGS% || exit /b 1 +) + +@echo on + +set DISTUTILS_USE_SDK=1 + +set args=%1 +shift +:start +if [%1] == [] goto done +set args=%args% %1 +shift +goto start + +:done +if "%args%" == "" ( + echo Usage: vc_env_helper.bat [command] [args] + echo e.g. vc_env_helper.bat cl /c test.cpp +) + +%args% || exit /b 1 diff --git a/packaging/vs2019/activate.bat b/packaging/vs2019/activate.bat new file mode 100644 index 00000000..6f607ba7 --- /dev/null +++ b/packaging/vs2019/activate.bat @@ -0,0 +1,44 @@ +:: Set env vars that tell distutils to use the compiler that we put on path +SET DISTUTILS_USE_SDK=1 +SET MSSdk=1 + +SET "VS_VERSION=16.0" +SET "VS_MAJOR=16" +SET "VS_YEAR=2019" + +set "MSYS2_ARG_CONV_EXCL=/AI;/AL;/OUT;/out" +set "MSYS2_ENV_CONV_EXCL=CL" + +:: For Python 3.5+, ensure that we link with the dynamic runtime. See +:: http://stevedower.id.au/blog/building-for-python-3-5-part-two/ for more info +set "PY_VCRUNTIME_REDIST=%PREFIX%\\bin\\vcruntime140.dll" + +for /f "usebackq tokens=*" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -legacy -products * -version [16^,17^) -property installationPath`) do ( + if exist "%%i" if exist "%%i\VC\Auxiliary\Build\vcvarsall.bat" ( + set "VSINSTALLDIR=%%i\" + goto :vswhere + ) +) + +:vswhere + +:: Shorten PATH to avoid the `input line too long` error. +SET MyPath=%PATH% + +setlocal EnableDelayedExpansion + +SET TempPath="%MyPath:;=";"%" +SET var= +FOR %%a IN (%TempPath%) DO ( + IF EXIST %%~sa ( + SET "var=!var!;%%~sa" + ) +) + +set "TempPath=!var:~1!" +endlocal & set "PATH=%TempPath%" + +:: Shorten current directory too +FOR %%A IN (.) DO CD "%%~sA" + +:: other things added by install_activate.bat at package build time diff --git a/packaging/vs2019/conda_build_config.yaml b/packaging/vs2019/conda_build_config.yaml new file mode 100644 index 00000000..358052ec --- /dev/null +++ b/packaging/vs2019/conda_build_config.yaml @@ -0,0 +1,24 @@ +blas_impl: + - mkl # [x86_64] +c_compiler: + - vs2019 # [win] +cxx_compiler: + - vs2019 # [win] +python: + - 3.5 + - 3.6 +# This differs from target_platform in that it determines what subdir the compiler +# will target, not what subdir the compiler package will be itself. +# For example, we need a win-64 vs2008_win-32 package, so that we compile win-32 +# code on win-64 miniconda. +cross_compiler_target_platform: + - win-64 # [win] +target_platform: + - win-64 # [win] +vc: + - 14 +zip_keys: + - # [win] + - vc # [win] + - c_compiler # [win] + - cxx_compiler # [win] diff --git a/packaging/vs2019/install_activate.bat b/packaging/vs2019/install_activate.bat new file mode 100644 index 00000000..3c38253a --- /dev/null +++ b/packaging/vs2019/install_activate.bat @@ -0,0 +1,30 @@ +set YEAR=2019 +set VER=16 + +mkdir "%PREFIX%\etc\conda\activate.d" +COPY "%RECIPE_DIR%\activate.bat" "%PREFIX%\etc\conda\activate.d\vs%YEAR%_compiler_vars.bat" + +IF "%cross_compiler_target_platform%" == "win-64" ( + set "target_platform=amd64" + echo SET "CMAKE_GENERATOR=Visual Studio %VER% %YEAR% Win64" >> "%PREFIX%\etc\conda\activate.d\vs%YEAR%_compiler_vars.bat" + echo pushd "%%VSINSTALLDIR%%" >> "%PREFIX%\etc\conda\activate.d\vs%YEAR%_compiler_vars.bat" + IF "%VSDEVCMD_ARGS%" == "" ( + echo CALL "VC\Auxiliary\Build\vcvarsall.bat" x64 >> "%PREFIX%\etc\conda\activate.d\vs%YEAR%_compiler_vars.bat" + echo popd >> "%PREFIX%\etc\conda\activate.d\vs%YEAR%_compiler_vars.bat" + echo pushd "%%VSINSTALLDIR%%" >> "%PREFIX%\etc\conda\activate.d\vs%YEAR%_compiler_vars.bat" + echo CALL "VC\Auxiliary\Build\vcvarsall.bat" x86_amd64 >> "%PREFIX%\etc\conda\activate.d\vs%YEAR%_compiler_vars.bat" + ) ELSE ( + echo CALL "VC\Auxiliary\Build\vcvarsall.bat" x64 %VSDEVCMD_ARGS% >> "%PREFIX%\etc\conda\activate.d\vs%YEAR%_compiler_vars.bat" + echo popd >> "%PREFIX%\etc\conda\activate.d\vs%YEAR%_compiler_vars.bat" + echo pushd "%%VSINSTALLDIR%%" >> "%PREFIX%\etc\conda\activate.d\vs%YEAR%_compiler_vars.bat" + echo CALL "VC\Auxiliary\Build\vcvarsall.bat" x86_amd64 %VSDEVCMD_ARGS% >> "%PREFIX%\etc\conda\activate.d\vs%YEAR%_compiler_vars.bat" + ) + echo popd >> "%PREFIX%\etc\conda\activate.d\vs%YEAR%_compiler_vars.bat" + ) else ( + set "target_platform=x86" + echo SET "CMAKE_GENERATOR=Visual Studio %VER% %YEAR%" >> "%PREFIX%\etc\conda\activate.d\vs%YEAR%_compiler_vars.bat" + echo pushd "%%VSINSTALLDIR%%" >> "%PREFIX%\etc\conda\activate.d\vs%YEAR%_compiler_vars.bat" + echo CALL "VC\Auxiliary\Build\vcvars32.bat" >> "%PREFIX%\etc\conda\activate.d\vs%YEAR%_compiler_vars.bat" + echo popd + ) + diff --git a/packaging/vs2019/install_runtime.bat b/packaging/vs2019/install_runtime.bat new file mode 100644 index 00000000..e09a5ccf --- /dev/null +++ b/packaging/vs2019/install_runtime.bat @@ -0,0 +1,49 @@ +set VC_PATH=x86 +if "%ARCH%"=="64" ( + set VC_PATH=x64 +) + +set MSC_VER=2019 + +rem :: This should always be present for VC installed with VS. Not sure about VC installed with Visual C++ Build Tools 2015 +rem FOR /F "usebackq tokens=3*" %%A IN (`REG QUERY "HKEY_LOCAL_MACHINE\Software\Microsoft\DevDiv\VC\Servicing\14.0\IDE.x64" /v UpdateVersion`) DO ( +rem set SP=%%A +rem ) + +rem if not "%SP%" == "%PKG_VERSION%" ( +rem echo "Version detected from registry: %SP%" +rem echo "does not match version of package being built (%PKG_VERSION%)" +rem echo "Do you have current updates for VS 2015 installed?" +rem exit 1 +rem ) + + +REM ========== REQUIRES Win 10 SDK be installed, or files otherwise copied to location below! +robocopy "C:\Program Files (x86)\Windows Kits\10\Redist\ucrt\DLLs\%VC_PATH%" "%LIBRARY_BIN%" *.dll /E +robocopy "C:\Program Files (x86)\Windows Kits\10\Redist\ucrt\DLLs\%VC_PATH%" "%PREFIX%" *.dll /E +if %ERRORLEVEL% GEQ 8 exit 1 + +REM ========== This one comes from visual studio 2019 +set "VC_VER=142" + +for /f "usebackq tokens=*" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -legacy -products * -version [16^,17^) -property installationPath`) do ( + if exist "%%i" if exist "%%i\VC\Auxiliary\Build\vcvarsall.bat" ( + set "VS15VCVARSALL=%%i\VC\Auxiliary\Build\vcvarsall.bat" + goto :eof + ) +) + +@setlocal +call "%VS15VARSALL%" x64 + +set "REDIST_ROOT=%VCToolsRedistDir%%VC_PATH%" + +robocopy "%REDIST_ROOT%\Microsoft.VC%VC_VER%.CRT" "%LIBRARY_BIN%" *.dll /E +if %ERRORLEVEL% LSS 8 exit 0 +robocopy "%REDIST_ROOT%\Microsoft.VC%VC_VER%.CRT" "%PREFIX%" *.dll /E +if %ERRORLEVEL% LSS 8 exit 0 +robocopy "%REDIST_ROOT%\Microsoft.VC%VC_VER%.OpenMP" "%LIBRARY_BIN%" *.dll /E +if %ERRORLEVEL% LSS 8 exit 0 +robocopy "%REDIST_ROOT%\Microsoft.VC%VC_VER%.OpenMP" "%PREFIX%" *.dll /E +if %ERRORLEVEL% LSS 8 exit 0 +@endlocal diff --git a/packaging/vs2019/meta.yaml b/packaging/vs2019/meta.yaml new file mode 100644 index 00000000..94a0ed4d --- /dev/null +++ b/packaging/vs2019/meta.yaml @@ -0,0 +1,24 @@ +{% set vcver="14.2" %} +{% set vcfeature="14" %} +{% set vsyear="2019" %} +{% set fullver="15.4.27004.2010" %} + +package: + name: vs{{ vsyear }} + version: {{ fullver }} + +build: + skip: True [not win] + script_env: + - VSDEVCMD_ARGS # [win] + +outputs: + - name: vs{{ vsyear }}_{{ cross_compiler_target_platform }} + script: install_activate.bat + track_features: + # VS 2019 is binary-compatible with VS 2017/vc 14.1 and 2015/vc14. Tools are "v142". + strong: + - vc{{ vcfeature }} + about: + summary: Activation and version verification of MSVC {{ vcver }} (VS {{ vsyear }}) compiler + license: BSD 3-clause diff --git a/packaging/windows/internal/cuda_install.bat b/packaging/windows/internal/cuda_install.bat new file mode 100644 index 00000000..fa4b97a2 --- /dev/null +++ b/packaging/windows/internal/cuda_install.bat @@ -0,0 +1,235 @@ +@echo on + +if "%CU_VERSION%" == "cpu" ( + echo Skipping for CPU builds + exit /b 0 +) + +set SRC_DIR=%~dp0\.. + +if not exist "%SRC_DIR%\temp_build" mkdir "%SRC_DIR%\temp_build" + +rem in unit test workflow, we get CUDA_VERSION, for example 11.1 +if defined CUDA_VERSION ( + set CUDA_VER=%CUDA_VERSION:.=% +) else ( + set CUDA_VER=%CU_VERSION:cu=% +) + +set /a CUDA_VER=%CU_VERSION:cu=% +set CUDA_VER_MAJOR=%CUDA_VER:~0,-1% +set CUDA_VER_MINOR=%CUDA_VER:~-1,1% +set CUDA_VERSION_STR=%CUDA_VER_MAJOR%.%CUDA_VER_MINOR% + +if %CUDA_VER% EQU 92 goto cuda92 +if %CUDA_VER% EQU 100 goto cuda100 +if %CUDA_VER% EQU 101 goto cuda101 +if %CUDA_VER% EQU 102 goto cuda102 +if %CUDA_VER% EQU 110 goto cuda110 +if %CUDA_VER% EQU 111 goto cuda111 +if %CUDA_VER% EQU 112 goto cuda112 +if %CUDA_VER% EQU 113 goto cuda113 + +echo CUDA %CUDA_VERSION_STR% is not supported +exit /b 1 + +:cuda92 +if not exist "%SRC_DIR%\temp_build\cuda_9.2.148_win10.exe" ( + curl -k -L https://ossci-windows.s3.amazonaws.com/win2016/cuda_9.2.148_win10.exe --output "%SRC_DIR%\temp_build\cuda_9.2.148_win10.exe" + if errorlevel 1 exit /b 1 + set "CUDA_SETUP_FILE=%SRC_DIR%\temp_build\cuda_9.2.148_win10.exe" + set "ARGS=nvcc_9.2 cuobjdump_9.2 nvprune_9.2 cupti_9.2 cublas_9.2 cublas_dev_9.2 cudart_9.2 cufft_9.2 cufft_dev_9.2 curand_9.2 curand_dev_9.2 cusolver_9.2 cusolver_dev_9.2 cusparse_9.2 cusparse_dev_9.2 nvgraph_9.2 nvgraph_dev_9.2 npp_9.2 npp_dev_9.2 nvrtc_9.2 nvrtc_dev_9.2 nvml_dev_9.2" +) + +if not exist "%SRC_DIR%\temp_build\cudnn-9.2-windows10-x64-v7.2.1.38.zip" ( + curl -k -L https://ossci-windows.s3.amazonaws.com/win2016/cudnn-9.2-windows10-x64-v7.2.1.38.zip --output "%SRC_DIR%\temp_build\cudnn-9.2-windows10-x64-v7.2.1.38.zip" + if errorlevel 1 exit /b 1 + set "CUDNN_SETUP_FILE=%SRC_DIR%\temp_build\cudnn-9.2-windows10-x64-v7.2.1.38.zip" +) + +goto cuda_common + +:cuda100 + +if not exist "%SRC_DIR%\temp_build\cuda_10.0.130_411.31_win10.exe" ( + curl -k -L https://ossci-windows.s3.amazonaws.com/win2016/cuda_10.0.130_411.31_win10.exe --output "%SRC_DIR%\temp_build\cuda_10.0.130_411.31_win10.exe" + if errorlevel 1 exit /b 1 + set "CUDA_SETUP_FILE=%SRC_DIR%\temp_build\cuda_10.0.130_411.31_win10.exe" + set "ARGS=nvcc_10.0 cuobjdump_10.0 nvprune_10.0 cupti_10.0 cublas_10.0 cublas_dev_10.0 cudart_10.0 cufft_10.0 cufft_dev_10.0 curand_10.0 curand_dev_10.0 cusolver_10.0 cusolver_dev_10.0 cusparse_10.0 cusparse_dev_10.0 nvgraph_10.0 nvgraph_dev_10.0 npp_10.0 npp_dev_10.0 nvrtc_10.0 nvrtc_dev_10.0 nvml_dev_10.0" +) + +if not exist "%SRC_DIR%\temp_build\cudnn-10.0-windows10-x64-v7.4.1.5.zip" ( + curl -k -L https://ossci-windows.s3.amazonaws.com/win2016/cudnn-10.0-windows10-x64-v7.4.1.5.zip --output "%SRC_DIR%\temp_build\cudnn-10.0-windows10-x64-v7.4.1.5.zip" + if errorlevel 1 exit /b 1 + set "CUDNN_SETUP_FILE=%SRC_DIR%\temp_build\cudnn-10.0-windows10-x64-v7.4.1.5.zip" +) + +goto cuda_common + +:cuda101 + +if not exist "%SRC_DIR%\temp_build\cuda_10.1.243_426.00_win10.exe" ( + curl -k -L https://ossci-windows.s3.amazonaws.com/cuda_10.1.243_426.00_win10.exe --output "%SRC_DIR%\temp_build\cuda_10.1.243_426.00_win10.exe" + if errorlevel 1 exit /b 1 + set "CUDA_SETUP_FILE=%SRC_DIR%\temp_build\cuda_10.1.243_426.00_win10.exe" + set "ARGS=nvcc_10.1 cuobjdump_10.1 nvprune_10.1 cupti_10.1 cublas_10.1 cublas_dev_10.1 cudart_10.1 cufft_10.1 cufft_dev_10.1 curand_10.1 curand_dev_10.1 cusolver_10.1 cusolver_dev_10.1 cusparse_10.1 cusparse_dev_10.1 nvgraph_10.1 nvgraph_dev_10.1 npp_10.1 npp_dev_10.1 nvrtc_10.1 nvrtc_dev_10.1 nvml_dev_10.1" +) + +if not exist "%SRC_DIR%\temp_build\cudnn-10.1-windows10-x64-v7.6.4.38.zip" ( + curl -k -L https://ossci-windows.s3.amazonaws.com/cudnn-10.1-windows10-x64-v7.6.4.38.zip --output "%SRC_DIR%\temp_build\cudnn-10.1-windows10-x64-v7.6.4.38.zip" + if errorlevel 1 exit /b 1 + set "CUDNN_SETUP_FILE=%SRC_DIR%\temp_build\cudnn-10.1-windows10-x64-v7.6.4.38.zip" +) + +goto cuda_common + +:cuda102 + +if not exist "%SRC_DIR%\temp_build\cuda_10.2.89_441.22_win10.exe" ( + curl -k -L https://ossci-windows.s3.amazonaws.com/cuda_10.2.89_441.22_win10.exe --output "%SRC_DIR%\temp_build\cuda_10.2.89_441.22_win10.exe" + if errorlevel 1 exit /b 1 + set "CUDA_SETUP_FILE=%SRC_DIR%\temp_build\cuda_10.2.89_441.22_win10.exe" + set "ARGS=nvcc_10.2 cuobjdump_10.2 nvprune_10.2 cupti_10.2 cublas_10.2 cublas_dev_10.2 cudart_10.2 cufft_10.2 cufft_dev_10.2 curand_10.2 curand_dev_10.2 cusolver_10.2 cusolver_dev_10.2 cusparse_10.2 cusparse_dev_10.2 nvgraph_10.2 nvgraph_dev_10.2 npp_10.2 npp_dev_10.2 nvrtc_10.2 nvrtc_dev_10.2 nvml_dev_10.2" +) + +if not exist "%SRC_DIR%\temp_build\cudnn-10.2-windows10-x64-v7.6.5.32.zip" ( + curl -k -L https://ossci-windows.s3.amazonaws.com/cudnn-10.2-windows10-x64-v7.6.5.32.zip --output "%SRC_DIR%\temp_build\cudnn-10.2-windows10-x64-v7.6.5.32.zip" + if errorlevel 1 exit /b 1 + set "CUDNN_SETUP_FILE=%SRC_DIR%\temp_build\cudnn-10.2-windows10-x64-v7.6.5.32.zip" +) + +if not exist "%SRC_DIR%\temp_build\gpu_driver_dlls.7z" ( + curl -k -L "https://drive.google.com/u/0/uc?id=1injUyo3lnarMgWyRcXqKg4UGnN0ysmuq&export=download" --output "%SRC_DIR%\temp_build\gpu_driver_dlls.zip" + if errorlevel 1 exit /b 1 +) + +echo Installing GPU driver DLLs +7z x %SRC_DIR%\temp_build\gpu_driver_dlls.zip -aoa -o"C:\Windows\System32" + +goto cuda_common + +:cuda110 + +if not exist "%SRC_DIR%\temp_build\cuda_11.0.2_451.48_win10.exe" ( + curl -k -L https://ossci-windows.s3.amazonaws.com/cuda_11.0.2_451.48_win10.exe --output "%SRC_DIR%\temp_build\cuda_11.0.2_451.48_win10.exe" + if errorlevel 1 exit /b 1 + set "CUDA_SETUP_FILE=%SRC_DIR%\temp_build\cuda_11.0.2_451.48_win10.exe" + set "ARGS=nvcc_11.0 cuobjdump_11.0 nvprune_11.0 nvprof_11.0 cupti_11.0 cublas_11.0 cublas_dev_11.0 cudart_11.0 cufft_11.0 cufft_dev_11.0 curand_11.0 curand_dev_11.0 cusolver_11.0 cusolver_dev_11.0 cusparse_11.0 cusparse_dev_11.0 npp_11.0 npp_dev_11.0 nvrtc_11.0 nvrtc_dev_11.0 nvml_dev_11.0" +) + +if not exist "%SRC_DIR%\temp_build\cudnn-11.0-windows-x64-v8.0.4.30.zip" ( + curl -k -L https://ossci-windows.s3.amazonaws.com/cudnn-11.0-windows-x64-v8.0.4.30.zip --output "%SRC_DIR%\temp_build\cudnn-11.0-windows-x64-v8.0.4.30.zip" + if errorlevel 1 exit /b 1 + set "CUDNN_SETUP_FILE=%SRC_DIR%\temp_build\cudnn-11.0-windows-x64-v8.0.4.30.zip" +) + +goto cuda_common + +:cuda111 + +if not exist "%SRC_DIR%\temp_build\cuda_11.1.0_456.43_win10.exe" ( + curl -k -L https://ossci-windows.s3.amazonaws.com/cuda_11.1.0_456.43_win10.exe --output "%SRC_DIR%\temp_build\cuda_11.1.0_456.43_win10.exe" + if errorlevel 1 exit /b 1 + set "CUDA_SETUP_FILE=%SRC_DIR%\temp_build\cuda_11.1.0_456.43_win10.exe" + set "ARGS=nvcc_11.1 cuobjdump_11.1 nvprune_11.1 nvprof_11.1 cupti_11.1 cublas_11.1 cublas_dev_11.1 cudart_11.1 cufft_11.1 cufft_dev_11.1 curand_11.1 curand_dev_11.1 cusolver_11.1 cusolver_dev_11.1 cusparse_11.1 cusparse_dev_11.1 npp_11.1 npp_dev_11.1 nvrtc_11.1 nvrtc_dev_11.1 nvml_dev_11.1" +) + +@REM There is no downloadable driver for Tesla on CUDA 11.1 yet. We will use +@REM the driver inside CUDA +if "%JOB_EXECUTOR%" == "windows-with-nvidia-gpu" set "ARGS=%ARGS% Display.Driver" + +if not exist "%SRC_DIR%\temp_build\cudnn-11.1-windows-x64-v8.0.5.39.zip" ( + curl -k -L https://ossci-windows.s3.amazonaws.com/cudnn-11.1-windows-x64-v8.0.5.39.zip --output "%SRC_DIR%\temp_build\cudnn-11.1-windows-x64-v8.0.5.39.zip" + if errorlevel 1 exit /b 1 + set "CUDNN_SETUP_FILE=%SRC_DIR%\temp_build\cudnn-11.1-windows-x64-v8.0.5.39.zip" +) + +goto cuda_common + +:cuda112 + +if not exist "%SRC_DIR%\temp_build\cuda_11.2.0_460.89_win10.exe" ( + curl -k -L https://ossci-windows.s3.amazonaws.com/cuda_11.2.0_460.89_win10.exe --output "%SRC_DIR%\temp_build\cuda_11.2.0_460.89_win10.exe" + if errorlevel 1 exit /b 1 + set "CUDA_SETUP_FILE=%SRC_DIR%\temp_build\cuda_11.2.0_460.89_win10.exe" + set "ARGS=nvcc_11.2 cuobjdump_11.2 nvprune_11.2 nvprof_11.2 cupti_11.2 cublas_11.2 cublas_dev_11.2 cudart_11.2 cufft_11.2 cufft_dev_11.2 curand_11.2 curand_dev_11.2 cusolver_11.2 cusolver_dev_11.2 cusparse_11.2 cusparse_dev_11.2 npp_11.2 npp_dev_11.2 nvrtc_11.2 nvrtc_dev_11.2 nvml_dev_11.2" +) + +if not exist "%SRC_DIR%\temp_build\cudnn-11.2-windows-x64-v8.1.0.77.zip" ( + curl -k -L http://s3.amazonaws.com/ossci-windows/cudnn-11.2-windows-x64-v8.1.0.77.zip --output "%SRC_DIR%\temp_build\cudnn-11.2-windows-x64-v8.1.0.77.zip" + if errorlevel 1 exit /b 1 + set "CUDNN_SETUP_FILE=%SRC_DIR%\temp_build\cudnn-11.2-windows-x64-v8.1.0.77.zip" +) + +goto cuda_common + +:cuda113 + +set CUDA_INSTALL_EXE=cuda_11.3.0_465.89_win10.exe +if not exist "%SRC_DIR%\temp_build\%CUDA_INSTALL_EXE%" ( + curl -k -L "https://ossci-windows.s3.amazonaws.com/%CUDA_INSTALL_EXE%" --output "%SRC_DIR%\temp_build\%CUDA_INSTALL_EXE%" + if errorlevel 1 exit /b 1 + set "CUDA_SETUP_FILE=%SRC_DIR%\temp_build\%CUDA_INSTALL_EXE%" + set "ARGS=thrust_11.3 nvcc_11.3 cuobjdump_11.3 nvprune_11.3 nvprof_11.3 cupti_11.3 cublas_11.3 cublas_dev_11.3 cudart_11.3 cufft_11.3 cufft_dev_11.3 curand_11.3 curand_dev_11.3 cusolver_11.3 cusolver_dev_11.3 cusparse_11.3 cusparse_dev_11.3 npp_11.3 npp_dev_11.3 nvrtc_11.3 nvrtc_dev_11.3 nvml_dev_11.3" + +) + +set CUDNN_INSTALL_ZIP=cudnn-11.3-windows-x64-v8.2.0.53.zip +if not exist "%SRC_DIR%\temp_build\%CUDNN_INSTALL_ZIP%" ( + curl -k -L "http://s3.amazonaws.com/ossci-windows/%CUDNN_INSTALL_ZIP%" --output "%SRC_DIR%\temp_build\%CUDNN_INSTALL_ZIP%" + if errorlevel 1 exit /b 1 + set "CUDNN_SETUP_FILE=%SRC_DIR%\temp_build\%CUDNN_INSTALL_ZIP%" +) + +goto cuda_common + +:cuda_common + +if not exist "%SRC_DIR%\temp_build\NvToolsExt.7z" ( + curl -k -L https://www.dropbox.com/s/9mcolalfdj4n979/NvToolsExt.7z?dl=1 --output "%SRC_DIR%\temp_build\NvToolsExt.7z" + if errorlevel 1 exit /b 1 +) + +echo Installing CUDA toolkit... +7z x %CUDA_SETUP_FILE% -o"%SRC_DIR%\temp_build\cuda" +pushd "%SRC_DIR%\temp_build\cuda" +start /wait setup.exe -s %ARGS% +popd + +echo Installing VS integration... +rem It's for VS 2019 +if "%CUDA_VER_MAJOR%" == "10" ( + xcopy /Y "%SRC_DIR%\temp_build\cuda\CUDAVisualStudioIntegration\extras\visual_studio_integration\MSBuildExtensions\*.*" "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Microsoft\VC\v160\BuildCustomizations" +) +if "%CUDA_VER_MAJOR%" == "11" ( + xcopy /Y "%SRC_DIR%\temp_build\cuda\visual_studio_integration\CUDAVisualStudioIntegration\extras\visual_studio_integration\MSBuildExtensions\*.*" "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Microsoft\VC\v160\BuildCustomizations" +) + +echo Installing NvToolsExt... +7z x %SRC_DIR%\temp_build\NvToolsExt.7z -o"%SRC_DIR%\temp_build\NvToolsExt" +mkdir "%ProgramFiles%\NVIDIA Corporation\NvToolsExt\bin\x64" +mkdir "%ProgramFiles%\NVIDIA Corporation\NvToolsExt\include" +mkdir "%ProgramFiles%\NVIDIA Corporation\NvToolsExt\lib\x64" +xcopy /Y "%SRC_DIR%\temp_build\NvToolsExt\bin\x64\*.*" "%ProgramFiles%\NVIDIA Corporation\NvToolsExt\bin\x64" +xcopy /Y "%SRC_DIR%\temp_build\NvToolsExt\include\*.*" "%ProgramFiles%\NVIDIA Corporation\NvToolsExt\include" +xcopy /Y "%SRC_DIR%\temp_build\NvToolsExt\lib\x64\*.*" "%ProgramFiles%\NVIDIA Corporation\NvToolsExt\lib\x64" + +echo Setting up environment... +set "PATH=%ProgramFiles%\NVIDIA GPU Computing Toolkit\CUDA\v%CUDA_VERSION_STR%\bin;%ProgramFiles%\NVIDIA GPU Computing Toolkit\CUDA\v%CUDA_VERSION_STR%\libnvvp;%PATH%" +set "CUDA_PATH=%ProgramFiles%\NVIDIA GPU Computing Toolkit\CUDA\v%CUDA_VERSION_STR%" +set "CUDA_PATH_V%CUDA_VER_MAJOR%_%CUDA_VER_MINOR%=%ProgramFiles%\NVIDIA GPU Computing Toolkit\CUDA\v%CUDA_VERSION_STR%" +set "NVTOOLSEXT_PATH=%ProgramFiles%\NVIDIA Corporation\NvToolsExt\bin\x64" + +if not exist "%ProgramFiles%\NVIDIA GPU Computing Toolkit\CUDA\v%CUDA_VERSION_STR%\bin\nvcc.exe" ( + echo CUDA %CUDA_VERSION_STR% installed failed. + exit /b 1 +) + +echo Installing cuDNN... +7z x %CUDNN_SETUP_FILE% -o"%SRC_DIR%\temp_build\cudnn" +xcopy /Y "%SRC_DIR%\temp_build\cudnn\cuda\bin\*.*" "%ProgramFiles%\NVIDIA GPU Computing Toolkit\CUDA\v%CUDA_VERSION_STR%\bin" +xcopy /Y "%SRC_DIR%\temp_build\cudnn\cuda\lib\x64\*.*" "%ProgramFiles%\NVIDIA GPU Computing Toolkit\CUDA\v%CUDA_VERSION_STR%\lib\x64" +xcopy /Y "%SRC_DIR%\temp_build\cudnn\cuda\include\*.*" "%ProgramFiles%\NVIDIA GPU Computing Toolkit\CUDA\v%CUDA_VERSION_STR%\include" + +echo Cleaning temp files +rd /s /q "%SRC_DIR%\temp_build" || ver > nul diff --git a/packaging/windows/internal/driver_update.bat b/packaging/windows/internal/driver_update.bat new file mode 100644 index 00000000..00b43aff --- /dev/null +++ b/packaging/windows/internal/driver_update.bat @@ -0,0 +1,25 @@ +set "DRIVER_DOWNLOAD_LINK=https://ossci-windows.s3.amazonaws.com/461.09-data-center-tesla-desktop-winserver-2019-2016-international.exe" +curl --retry 3 -kL %DRIVER_DOWNLOAD_LINK% --output 461.09-data-center-tesla-desktop-winserver-2019-2016-international.exe +if errorlevel 1 exit /b 1 + +start /wait 461.09-data-center-tesla-desktop-winserver-2019-2016-international.exe -s -noreboot +if errorlevel 1 exit /b 1 + +del 461.09-data-center-tesla-desktop-winserver-2019-2016-international.exe || ver > NUL + +setlocal EnableDelayedExpansion +set NVIDIA_GPU_EXISTS=0 +for /F "delims=" %%i in ('wmic path win32_VideoController get name') do ( + set GPUS=%%i + if not "x!GPUS:NVIDIA=!" == "x!GPUS!" ( + SET NVIDIA_GPU_EXISTS=1 + goto gpu_check_end + ) +) +:gpu_check_end +endlocal & set NVIDIA_GPU_EXISTS=%NVIDIA_GPU_EXISTS% + +if "%NVIDIA_GPU_EXISTS%" == "0" ( + echo "CUDA Driver installation Failed" + exit /b 1 +) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..316bd5c7 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,20 @@ +torch>=1.4.0 + +# Required for Windows because it's the only available backend +SoundFile; sys_platform == 'win32' + +# Optional for torchaudio.kaldi_io +numpy +kaldi_io + +# Required for tests only: + +# Style-checking for PEP8 +flake8 + +# Used for comparison of outputs in tests +librosa>=0.4.3 +scipy + +# Unit tests with pytest +pytest diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..182c9f7e --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[pydocstyle] +select = D417 # Missing argument descriptions in the docstring diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..c09961cb --- /dev/null +++ b/setup.py @@ -0,0 +1,119 @@ +#!/usr/bin/env python +import os +import re +import shutil +import subprocess +from pathlib import Path +from setuptools import setup, find_packages +import distutils.command.clean + +from build_tools import setup_helpers + +ROOT_DIR = Path(__file__).parent.resolve() + + +def _run_cmd(cmd, default): + try: + return subprocess.check_output(cmd, cwd=ROOT_DIR).decode('ascii').strip() + except Exception: + return default + + +# Creating the version file +version = '0.10.0' +sha = _run_cmd(['git', 'rev-parse', 'HEAD'], default='Unknown') + +if os.getenv('BUILD_VERSION'): + version = os.getenv('BUILD_VERSION') +elif sha != 'Unknown': + version += '+' + sha[:7] +print('-- Building version ' + version) + +version_path = ROOT_DIR / 'torchaudio' / 'version.py' +with open(version_path, 'w') as f: + f.write("__version__ = '{}'\n".format(version)) + f.write("git_version = {}\n".format(repr(sha))) + +pytorch_package_version = os.getenv('PYTORCH_VERSION') + +pytorch_package_dep = 'torch' +if pytorch_package_version is not None: + pytorch_package_dep += "==" + pytorch_package_version + + +class clean(distutils.command.clean.clean): + def run(self): + # Run default behavior first + distutils.command.clean.clean.run(self) + + # Remove torchaudio extension + for path in (ROOT_DIR / 'torchaudio').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 _get_packages(): + exclude = [ + "build*", + "test*", + "torchaudio.csrc*", + "third_party*", + "build_tools*", + ] + exclude_prototype = False + branch_name = _run_cmd(['git', 'rev-parse', '--abbrev-ref', 'HEAD'], default=None) + is_on_tag = _run_cmd(['git', 'describe', '--tags', '--exact-match', '@'], default=None) + + if branch_name is not None and branch_name.startswith('release/'): + print('On release branch') + exclude_prototype = True + if is_on_tag is not None and re.match(r'v[\d.]+(-rc\d+)?', is_on_tag): + print('On release tag') + exclude_prototype = True + if exclude_prototype: + print('Excluding torchaudio.prototype from the package.') + exclude.append("torchaudio.prototype") + return find_packages(exclude=exclude) + + +setup( + name="torchaudio", + version=version, + description="An audio package for PyTorch", + url="https://github.com/pytorch/audio", + author="Soumith Chintala, David Pollack, Sean Naren, Peter Goldsborough", + author_email="soumith@pytorch.org", + classifiers=[ + "Environment :: Plugins", + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: BSD License", + "Operating System :: MacOS :: MacOS X", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX", + "Programming Language :: C++", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: Implementation :: CPython", + "Topic :: Multimedia :: Sound/Audio", + "Topic :: Scientific/Engineering :: Artificial Intelligence" + ], + packages=_get_packages(), + ext_modules=setup_helpers.get_ext_modules(), + cmdclass={ + 'build_ext': setup_helpers.CMakeBuild, + 'clean': clean, + }, + install_requires=[pytorch_package_dep], + zip_safe=False, +) diff --git a/test/integration_tests/__init__.py b/test/integration_tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/integration_tests/conftest.py b/test/integration_tests/conftest.py new file mode 100644 index 00000000..66adda5a --- /dev/null +++ b/test/integration_tests/conftest.py @@ -0,0 +1,37 @@ +import torch +from torchaudio_unittest.common_utils import get_asset_path +import pytest + + +class GreedyCTCDecoder(torch.nn.Module): + def __init__(self, labels): + super().__init__() + self.labels = labels + + def forward(self, logits: torch.Tensor) -> str: + """Given a sequence logits over labels, get the best path string + + Args: + logits (Tensor): Logit tensors. Shape `[num_seq, num_label]`. + + Returns: + str: The resulting transcript + """ + best_path = torch.argmax(logits, dim=-1) # [num_seq,] + best_path = torch.unique_consecutive(best_path, dim=-1) + hypothesis = [] + for i in best_path: + char = self.labels[i] + if char not in ['', '']: + hypothesis.append(char) + return ''.join(hypothesis) + + +@pytest.fixture +def ctc_decoder(): + return GreedyCTCDecoder + + +@pytest.fixture +def sample_speech_16000_en(): + return get_asset_path('Lab41-SRI-VOiCES-src-sp0307-ch127535-sg0042.flac') diff --git a/test/integration_tests/tacotron2_pipeline_test.py b/test/integration_tests/tacotron2_pipeline_test.py new file mode 100644 index 00000000..3b81b184 --- /dev/null +++ b/test/integration_tests/tacotron2_pipeline_test.py @@ -0,0 +1,28 @@ +from torchaudio.pipelines import ( + TACOTRON2_GRIFFINLIM_CHAR_LJSPEECH, + TACOTRON2_GRIFFINLIM_PHONE_LJSPEECH, + TACOTRON2_WAVERNN_CHAR_LJSPEECH, + TACOTRON2_WAVERNN_PHONE_LJSPEECH, +) +import pytest + + +@pytest.mark.parametrize( + 'bundle', + [ + TACOTRON2_GRIFFINLIM_CHAR_LJSPEECH, + TACOTRON2_GRIFFINLIM_PHONE_LJSPEECH, + TACOTRON2_WAVERNN_CHAR_LJSPEECH, + TACOTRON2_WAVERNN_PHONE_LJSPEECH, + ] +) +def test_tts_models(bundle): + """Smoke test of TTS pipeline""" + text = "Hello world! Text to Speech!" + + processor = bundle.get_text_processor() + tacotron2 = bundle.get_tacotron2() + vocoder = bundle.get_vocoder() + processed, lengths = processor(text) + mel_spec, lengths, _ = tacotron2.infer(processed, lengths) + waveforms, lengths = vocoder(mel_spec, lengths) diff --git a/test/integration_tests/wav2vec2_pipeline_test.py b/test/integration_tests/wav2vec2_pipeline_test.py new file mode 100644 index 00000000..012f960a --- /dev/null +++ b/test/integration_tests/wav2vec2_pipeline_test.py @@ -0,0 +1,70 @@ +import torchaudio +from torchaudio.pipelines import ( + WAV2VEC2_BASE, + WAV2VEC2_LARGE, + WAV2VEC2_LARGE_LV60K, + WAV2VEC2_ASR_BASE_10M, + WAV2VEC2_ASR_BASE_100H, + WAV2VEC2_ASR_BASE_960H, + WAV2VEC2_ASR_LARGE_10M, + WAV2VEC2_ASR_LARGE_100H, + WAV2VEC2_ASR_LARGE_960H, + WAV2VEC2_ASR_LARGE_LV60K_10M, + WAV2VEC2_ASR_LARGE_LV60K_100H, + WAV2VEC2_ASR_LARGE_LV60K_960H, + WAV2VEC2_XLSR53, + HUBERT_BASE, + HUBERT_LARGE, + HUBERT_XLARGE, + HUBERT_ASR_LARGE, + HUBERT_ASR_XLARGE, +) +import pytest + + +@pytest.mark.parametrize( + "bundle", + [ + WAV2VEC2_BASE, + WAV2VEC2_LARGE, + WAV2VEC2_LARGE_LV60K, + WAV2VEC2_XLSR53, + HUBERT_BASE, + HUBERT_LARGE, + HUBERT_XLARGE, + ] +) +def test_pretraining_models(bundle): + """Smoke test of downloading weights for pretraining models""" + bundle.get_model() + + +@pytest.mark.parametrize( + "bundle,expected", + [ + (WAV2VEC2_ASR_BASE_10M, 'I|HAD|THAT|CURIYOSSITY|BESID|ME|AT|THIS|MOMENT|'), + (WAV2VEC2_ASR_BASE_100H, 'I|HAD|THAT|CURIOSITY|BESIDE|ME|AT|THIS|MOMENT|'), + (WAV2VEC2_ASR_BASE_960H, 'I|HAD|THAT|CURIOSITY|BESIDE|ME|AT|THIS|MOMENT|'), + (WAV2VEC2_ASR_LARGE_10M, 'I|HAD|THAT|CURIOUSITY|BESIDE|ME|AT|THIS|MOMENT|'), + (WAV2VEC2_ASR_LARGE_100H, 'I|HAD|THAT|CURIOSITY|BESIDE|ME|AT|THIS|MOMENT|'), + (WAV2VEC2_ASR_LARGE_960H, 'I|HAD|THAT|CURIOSITY|BESIDE|ME|AT|THIS|MOMENT|'), + (WAV2VEC2_ASR_LARGE_LV60K_10M, 'I|HAD|THAT|CURIOUSSITY|BESID|ME|AT|THISS|MOMENT|'), + (WAV2VEC2_ASR_LARGE_LV60K_100H, 'I|HAVE|THAT|CURIOSITY|BESIDE|ME|AT|THIS|MOMENT|'), + (WAV2VEC2_ASR_LARGE_LV60K_960H, 'I|HAVE|THAT|CURIOSITY|BESIDE|ME|AT|THIS|MOMENT|'), + (HUBERT_ASR_LARGE, 'I|HAVE|THAT|CURIOSITY|BESIDE|ME|AT|THIS|MOMENT|'), + (HUBERT_ASR_XLARGE, 'I|HAVE|THAT|CURIOSITY|BESIDE|ME|AT|THIS|MOMENT|') + ] +) +def test_finetune_asr_model( + bundle, + expected, + sample_speech_16000_en, + ctc_decoder, +): + """Smoke test of downloading weights for fine-tuning models and simple transcription""" + model = bundle.get_model().eval() + waveform, sample_rate = torchaudio.load(sample_speech_16000_en) + emission, _ = model(waveform) + decoder = ctc_decoder(bundle.get_labels()) + result = decoder(emission[0]) + assert result == expected diff --git a/test/torchaudio_unittest/README.md b/test/torchaudio_unittest/README.md new file mode 100644 index 00000000..dd6249fb --- /dev/null +++ b/test/torchaudio_unittest/README.md @@ -0,0 +1,148 @@ +# Torchaudio Unit Test Suite + +## How to run test + +You can use `pytest` to run `torchaudio`'s test suites. See +https://docs.pytest.org/ for the detail of how to use `pytest` command. + +For testing, please refer to [contributing guide](../../CONTRIBUTING.md) for +the installation of the required and optional packages. + +For running `kaldi`-related tests: + +```bash +export PATH="${PATH}:/src/featbin/" +``` + +Some useful pytest commands: + +```bash +# List up all the tests +pytest test --collect-only +# Run all the test suites +pytest test +# Run tests on sox_effects module +pytest test/torchaudio_unittest/sox_effect +# use -k to apply filter +pytest test/torchaudio_unittest/sox_io_backend -k load # only runs tests where their names contain load +# Some other useful options; +# Stop on the first failure -x +# Run failure fast --ff +# Only rerun the failure --lf +``` + +**Note** +We use PyTorch's test utilities instead of `pytest` frameworks when writing tests to avoid reinventing the wheel for Tensor comparison. +Also, while we recommend using `pytest` for *running* the tests, we cannot +make `pytest` a testing dependency of `torchaudio`. As a result, you should +not import `pytest` or its submodules in the test files; Use the Python +`unittest` builtin module instead, or the `parameterized` package to +parametrize tests. + +## Structure of tests + +The following is an overview of the tests and related modules for `torchaudio`. + +### Purpose specific test suites + +#### Numerical compatibility against existing software +- [Librosa compatibility test](./transforms/librosa_compatibility_test.py) + Test suite for numerical compatibility against librosa. +- [SoX compatibility test](./transforms/sox_compatibility_test.py) + Test suite for numerical compatibility against SoX. +- [Kaldi compatibility test](./transforms/kaldi_compatibility_impl.py) + Test suite for numerical compatibility against Kaldi. + +#### Result consistency with PyTorch framework +- [TorchScript consistency test](./transforms/torchscript_consistency_impl.py) + Test suite to check 1. if an API is TorchScript-able, and 2. the results from Python and Torchscript match. +- [Batch consistency test](./transforms/batch_consistency_test.py) + Test suite to check if functionals/Transforms handle single sample input and batch input and return the same result. + +### Module specific test suites + +The following test modules are defined for corresponding `torchaudio` module/functions. + +- [`torchaudio.datasets`](./datasets) +- [`torchaudio.functional`](./functional) +- [`torchaudio.transforms`](./transforms/transforms_test.py) +- [`torchaudio.compliance.kaldi`](./compliance_kaldi_test.py) +- [`torchaudio.kaldi_io`](./kaldi_io_test.py) +- [`torchaudio.sox_effects`](./sox_effect) +- [`torchaudio.backend`](./backend) + +### Test modules that do not fall into the above categories +- [test_dataloader.py](./dataloader_test.py) + Simple test for loading data and applying preprocessing. + +### Support files +- [assets](./assets): Contain sample audio files. +- [assets/kaldi](./assets/kaldi): Contains Kaldi format matrix files used in [./test_compliance_kaldi.py](./test_compliance_kaldi.py). +- [compliance](./compliance): Scripts used to generate above Kaldi matrix files. + +### Waveforms for Testing Purposes + +When testing transforms we often need waveforms of specific type (ex: pure tone, noise, or voice), with specific bitrate (ex. 8 or 16 kHz) and number of channels (ex. mono, stereo). Below are some tips on how to construct waveforms and guidance around existing audio files. + +#### Load a Waveform from a File + +```python +filepath = common_utils.get_asset_path('filename.wav') +waveform, sample_rate = common_utils.load_wav(filepath) +``` + +*Note: Should you choose to contribute an audio file, please leave a comment in the issue or pull request, mentioning content source and licensing information. WAV files are preferred. Other formats should be used only when there is no alternative. (i.e. dataset implementation comes with hardcoded non-wav extension).* + +#### Pure Tone + +Code: + +```python +waveform = common_utils.get_sinusoid( + frequency=300, + sample_rate=16000, + duration=1, # seconds + n_channels=1, + dtype="float32", + device="cpu", +) +``` + +#### Noise + +Code: + +```python +tensor = common_utils.get_whitenoise() +``` + +Files: + +* `steam-train-whistle-daniel_simon.wav` + +#### Voice + +Files: + +* `CommonVoice/cv-corpus-4-2019-12-10/tt/clips/common_voice_tt_00000000.wav` +* `VCTK-Corpus/wav48/p224/p224_002.wav` +* `vad-go-stereo-44100.wav` +* `vad-go-mono-32000.wav` + +## Adding test + +The following is the current practice of torchaudio test suite. + +1. Unless the tests are related to I/O, use synthetic data. [`common_utils`](./common_utils) has some data generator functions. +1. When you add a new test case, use `common_utils.TorchaudioTestCase` as base class unless you are writing tests that are common to CPU / CUDA. + - Set class memeber `dtype`, `device` and `backend` for the desired behavior. + - If you do not set `backend` value in your test suite, then I/O functions will be unassigned and attempt to load/save file will fail. + - For `backend` value, in addition to available backends, you can also provide the value "default" and backend will be picked automatically based on availability. +1. If you are writing tests that should pass on diffrent dtype/devices, write a common class inheriting `common_utils.TestBaseMixin`, then inherit `common_utils.PytorchTestCase` and define class attributes (`dtype` / `device` / `backend`) there. See [Torchscript consistency test implementation](./transforms/torchscript_consistency_impl.py) and test definitions for [CPU](./transforms/torchscript_consistency_cpu_test.py) and [CUDA](./transforms/torchscript_consistency_cuda_test.py) devices. +1. For numerically comparing Tensors, use `assertEqual` method from torchaudio_unittest.common_utils.PytorchTestCase` class. This method has a better support for a wide variety of Tensor types. + +When you add a new feature(functional/transform), consider the following + +1. When you add a new feature, please make it Torchscript-able and batch-consistent unless it degrades the performance. Please add the tests to see if the new feature meet these requirements. +1. If the feature should be numerical compatible against existing software (SoX, Librosa, Kaldi etc), add a corresponding test. +1. If the new feature is unique to `torchaudio` (not a PyTorch implementation of an existing Software functionality), consider adding correctness tests (wheather the expected output is produced for the set of input) under the corresponding test module (`test_functional.py`, `test_transforms.py`). diff --git a/test/torchaudio_unittest/__init__.py b/test/torchaudio_unittest/__init__.py new file mode 100644 index 00000000..22ac65a3 --- /dev/null +++ b/test/torchaudio_unittest/__init__.py @@ -0,0 +1,4 @@ +try: + from . import fb # noqa +except Exception: + pass diff --git a/test/torchaudio_unittest/assets/Lab41-SRI-VOiCES-src-sp0307-ch127535-sg0042.flac b/test/torchaudio_unittest/assets/Lab41-SRI-VOiCES-src-sp0307-ch127535-sg0042.flac new file mode 100644 index 0000000000000000000000000000000000000000..8ef93ecc90a213e011464b63538a1d3d9ff28c33 GIT binary patch literal 67549 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 literal 0 HcmV?d00001 diff --git a/test/torchaudio_unittest/assets/VCTK-Corpus/txt/p224/p224_002.txt b/test/torchaudio_unittest/assets/VCTK-Corpus/txt/p224/p224_002.txt new file mode 100644 index 00000000..a3d5413c --- /dev/null +++ b/test/torchaudio_unittest/assets/VCTK-Corpus/txt/p224/p224_002.txt @@ -0,0 +1 @@ +VCTK Test. diff --git a/test/torchaudio_unittest/assets/VCTK-Corpus/wav48/p224/p224_002.wav b/test/torchaudio_unittest/assets/VCTK-Corpus/wav48/p224/p224_002.wav new file mode 100644 index 0000000000000000000000000000000000000000..66ee46737e6fb4a50e41780fea3c0b01b45830b6 GIT binary patch literal 84 zcmWIYbaV4zU|L!fNF0&}04a|ce*gdg literal 0 HcmV?d00001 diff --git a/test/torchaudio_unittest/assets/io/96k_0_1ch.opus b/test/torchaudio_unittest/assets/io/96k_0_1ch.opus new file mode 100644 index 0000000000000000000000000000000000000000..df95474ddb67499f9223537f0081c689b0be654a GIT binary patch literal 8259 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 literal 0 HcmV?d00001 diff --git a/test/torchaudio_unittest/assets/io/96k_0_2ch.opus b/test/torchaudio_unittest/assets/io/96k_0_2ch.opus new file mode 100644 index 0000000000000000000000000000000000000000..b8837e81e26b579d984f2b871523322d4e8b5399 GIT binary patch literal 6740 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 LtBkx?x;Fd|2;dDu literal 0 HcmV?d00001 diff --git a/test/torchaudio_unittest/assets/io/96k_10_1ch.opus b/test/torchaudio_unittest/assets/io/96k_10_1ch.opus new file mode 100644 index 0000000000000000000000000000000000000000..56b170d380376604f308a28cb2ca92ed1f1481cc GIT binary patch literal 15033 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 literal 0 HcmV?d00001 diff --git a/test/torchaudio_unittest/assets/io/96k_5_2ch.opus b/test/torchaudio_unittest/assets/io/96k_5_2ch.opus new file mode 100644 index 0000000000000000000000000000000000000000..007bc813cea92c6555cde19b2ff0f8e779a59614 GIT binary patch literal 10618 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 literal 0 HcmV?d00001 diff --git a/test/torchaudio_unittest/assets/io/generate_opus.py b/test/torchaudio_unittest/assets/io/generate_opus.py new file mode 100644 index 00000000..e6b99c47 --- /dev/null +++ b/test/torchaudio_unittest/assets/io/generate_opus.py @@ -0,0 +1,50 @@ +"""Generate opus file for testing load functions""" + +import argparse +import subprocess + +import scipy.io.wavfile +import torch + + +def _parse_args(): + parser = argparse.ArgumentParser( + description='Generate opus files for test' + ) + parser.add_argument('--num-channels', required=True, type=int) + parser.add_argument('--compression-level', required=True, type=int, choices=list(range(11))) + parser.add_argument('--bitrate', default='96k') + return parser.parse_args() + + +def convert_to_opus( + src_path, dst_path, + *, bitrate, compression_level): + """Convert audio file with `ffmpeg` command.""" + command = ['ffmpeg', '-y', '-i', src_path, '-c:a', 'libopus', '-b:a', bitrate] + if compression_level is not None: + command += ['-compression_level', str(compression_level)] + command += [dst_path] + print(' '.join(command)) + subprocess.run(command, check=True) + + +def _generate(num_channels, compression_level, bitrate): + org_path = 'original.wav' + ops_path = f'{bitrate}_{compression_level}_{num_channels}ch.opus' + + # Note: ffmpeg forces sample rate 48k Hz for opus https://stackoverflow.com/a/39186779 + # 1. generate original wav + data = torch.linspace(-32768, 32767, 32768, dtype=torch.int16).repeat([num_channels, 1]).t() + scipy.io.wavfile.write(org_path, 48000, data.numpy()) + # 2. convert to opus + convert_to_opus(org_path, ops_path, bitrate=bitrate, compression_level=compression_level) + + +def _main(): + args = _parse_args() + _generate(args.num_channels, args.compression_level, args.bitrate) + + +if __name__ == '__main__': + _main() diff --git a/test/torchaudio_unittest/assets/kaldi_file.wav b/test/torchaudio_unittest/assets/kaldi_file.wav new file mode 100644 index 0000000000000000000000000000000000000000..66ee46737e6fb4a50e41780fea3c0b01b45830b6 GIT binary patch literal 84 zcmWIYbaV4zU|L!fNF0&}04a|ce*gdg literal 0 HcmV?d00001 diff --git a/test/torchaudio_unittest/assets/kaldi_file_8000.wav b/test/torchaudio_unittest/assets/kaldi_file_8000.wav new file mode 100644 index 0000000000000000000000000000000000000000..01a5755dbadf129f6e8af650174a4b765c8d5234 GIT binary patch literal 16044 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 Zeesh-VfPngGd682#8t}M)@!}m literal 0 HcmV?d00001 diff --git a/test/torchaudio_unittest/assets/kaldi_test_fbank_args.jsonl b/test/torchaudio_unittest/assets/kaldi_test_fbank_args.jsonl new file mode 100644 index 00000000..91d1b57f --- /dev/null +++ b/test/torchaudio_unittest/assets/kaldi_test_fbank_args.jsonl @@ -0,0 +1,88 @@ +{"blackman_coeff": 0.0939, "energy_floor": 4.5062, "frame_length": 1.0625, "frame_shift": 0.6875, "high_freq": 1841, "htk_compat": true, "low_freq": 479, "num_mel_bins": 5, "preemphasis_coefficient": 0.84, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": true, "use_log_fbank": true, "use_power": false, "vtln_high": 1832, "vtln_low": 1824, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 0.166, "energy_floor": 1.7875, "frame_length": 1.125, "frame_shift": 0.5, "high_freq": 4999, "htk_compat": true, "low_freq": 1740, "num_mel_bins": 6, "preemphasis_coefficient": 0.29, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": true, "use_log_fbank": false, "use_power": true, "vtln_high": 4587, "vtln_low": 2289, "vtln_warp": 1.0, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 0.2215, "energy_floor": 1.3444, "frame_length": 1.125, "frame_shift": 0.75, "high_freq": 7468, "htk_compat": true, "low_freq": 87, "num_mel_bins": 5, "preemphasis_coefficient": 0.17, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": false, "use_energy": true, "use_log_fbank": false, "use_power": false, "vtln_high": 1700, "vtln_low": 870, "vtln_warp": 0.3104, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 0.2512, "energy_floor": 0.2607, "frame_length": 0.875, "frame_shift": 0.875, "high_freq": 7380, "htk_compat": true, "low_freq": 4471, "num_mel_bins": 5, "preemphasis_coefficient": 0.76, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": true, "use_log_fbank": false, "use_power": true, "vtln_high": 7138, "vtln_low": 5172, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 0.2834, "energy_floor": 1.7885, "frame_length": 1.1875, "frame_shift": 0.9375, "high_freq": 5385, "htk_compat": false, "low_freq": 2579, "num_mel_bins": 6, "preemphasis_coefficient": 0.82, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": false, "use_log_fbank": true, "use_power": true, "vtln_high": 4782, "vtln_low": 4492, "vtln_warp": 1.0, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 0.3188, "energy_floor": 1.6288, "frame_length": 1.0, "frame_shift": 0.5, "high_freq": 6258, "htk_compat": true, "low_freq": 2043, "num_mel_bins": 4, "preemphasis_coefficient": 0.57, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": false, "subtract_mean": false, "use_energy": false, "use_log_fbank": true, "use_power": true, "vtln_high": 5274, "vtln_low": 3268, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 0.3637, "energy_floor": 4.7928, "frame_length": 1.0, "frame_shift": 0.5625, "high_freq": 7671, "htk_compat": false, "low_freq": 2385, "num_mel_bins": 5, "preemphasis_coefficient": 0.81, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": true, "use_log_fbank": false, "use_power": true, "vtln_high": 6881, "vtln_low": 4659, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 0.4702, "energy_floor": 2.668, "frame_length": 1.0, "frame_shift": 1.0, "high_freq": 7231, "htk_compat": true, "low_freq": 1515, "num_mel_bins": 4, "preemphasis_coefficient": 0.92, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": false, "subtract_mean": true, "use_energy": false, "use_log_fbank": false, "use_power": true, "vtln_high": 6506, "vtln_low": 2549, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 0.5988, "energy_floor": 0.8014, "frame_length": 1.125, "frame_shift": 0.875, "high_freq": 3663, "htk_compat": true, "low_freq": 1941, "num_mel_bins": 6, "preemphasis_coefficient": 0.59, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": false, "use_log_fbank": false, "use_power": false, "vtln_high": 3373, "vtln_low": 3354, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 0.6421, "energy_floor": 2.1404, "frame_length": 0.75, "frame_shift": 1.0625, "high_freq": 6031, "htk_compat": false, "low_freq": 57, "num_mel_bins": 4, "preemphasis_coefficient": 0.03, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": true, "use_log_fbank": false, "use_power": true, "vtln_high": 5417, "vtln_low": 1170, "vtln_warp": 1.0, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 0.674, "energy_floor": 1.3778, "frame_length": 1.0, "frame_shift": 0.875, "high_freq": 6623, "htk_compat": true, "low_freq": 2402, "num_mel_bins": 7, "preemphasis_coefficient": 0.5, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": false, "use_log_fbank": false, "use_power": true, "vtln_high": 6491, "vtln_low": 6262, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 0.7979, "energy_floor": 1.4223, "frame_length": 1.125, "frame_shift": 0.3125, "high_freq": 2534, "htk_compat": false, "low_freq": 810, "num_mel_bins": 4, "preemphasis_coefficient": 0.77, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": true, "use_log_fbank": false, "use_power": true, "vtln_high": 2494, "vtln_low": 2015, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 0.8161, "energy_floor": 1.2937, "frame_length": 0.9375, "frame_shift": 0.125, "high_freq": 5030, "htk_compat": false, "low_freq": 966, "num_mel_bins": 6, "preemphasis_coefficient": 0.03, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": true, "use_log_fbank": true, "use_power": true, "vtln_high": 4652, "vtln_low": 2559, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 0.8873, "energy_floor": 1.2866, "frame_length": 1.125, "frame_shift": 0.4375, "high_freq": 5558, "htk_compat": true, "low_freq": 1464, "num_mel_bins": 8, "preemphasis_coefficient": 0.77, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": false, "use_log_fbank": false, "use_power": false, "vtln_high": 4613, "vtln_low": 4001, "vtln_warp": 1.4073, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 0.8997, "energy_floor": 2.8795, "frame_length": 0.875, "frame_shift": 0.5, "high_freq": 3383, "htk_compat": false, "low_freq": 259, "num_mel_bins": 4, "preemphasis_coefficient": 0.08, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": true, "use_log_fbank": true, "use_power": false, "vtln_high": 1175, "vtln_low": 1038, "vtln_warp": 1.0, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 0.9113, "energy_floor": 0.9909, "frame_length": 0.6875, "frame_shift": 0.375, "high_freq": 7562, "htk_compat": true, "low_freq": 3978, "num_mel_bins": 4, "preemphasis_coefficient": 0.03, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": false, "use_log_fbank": true, "use_power": true, "vtln_high": 6483, "vtln_low": 5671, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 0.9312, "energy_floor": 3.3768, "frame_length": 0.8125, "frame_shift": 1.125, "high_freq": 5824, "htk_compat": false, "low_freq": 1366, "num_mel_bins": 6, "preemphasis_coefficient": 0.28, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": false, "use_log_fbank": true, "use_power": true, "vtln_high": 3917, "vtln_low": 1620, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 0.9472, "energy_floor": 2.4134, "frame_length": 0.75, "frame_shift": 1.0, "high_freq": 7959, "htk_compat": false, "low_freq": 1770, "num_mel_bins": 6, "preemphasis_coefficient": 0.12, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": false, "use_log_fbank": false, "use_power": true, "vtln_high": 6874, "vtln_low": 5861, "vtln_warp": 1.1718, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 0.9483, "energy_floor": 4.0177, "frame_length": 1.125, "frame_shift": 1.125, "high_freq": 7854, "htk_compat": false, "low_freq": 4793, "num_mel_bins": 5, "preemphasis_coefficient": 0.47, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": false, "snip_edges": false, "subtract_mean": false, "use_energy": true, "use_log_fbank": false, "use_power": false, "vtln_high": 5868, "vtln_low": 5848, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 0.9631, "energy_floor": 3.3222, "frame_length": 1.0, "frame_shift": 0.5, "high_freq": 7662, "htk_compat": true, "low_freq": 1833, "num_mel_bins": 6, "preemphasis_coefficient": 0.71, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": true, "use_energy": false, "use_log_fbank": true, "use_power": true, "vtln_high": 6204, "vtln_low": 5887, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 0.982, "energy_floor": 1.668, "frame_length": 0.75, "frame_shift": 0.1875, "high_freq": 6788, "htk_compat": false, "low_freq": 1968, "num_mel_bins": 4, "preemphasis_coefficient": 0.14, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": true, "use_log_fbank": true, "use_power": false, "vtln_high": 2114, "vtln_low": 2024, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 1.0183, "energy_floor": 2.1572, "frame_length": 1.0625, "frame_shift": 0.375, "high_freq": 2018, "htk_compat": true, "low_freq": 317, "num_mel_bins": 6, "preemphasis_coefficient": 0.14, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": false, "use_log_fbank": false, "use_power": false, "vtln_high": 1118, "vtln_low": 947, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 1.0269, "energy_floor": 0.3681, "frame_length": 1.125, "frame_shift": 0.5625, "high_freq": 4897, "htk_compat": true, "low_freq": 543, "num_mel_bins": 4, "preemphasis_coefficient": 0.57, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": false, "use_log_fbank": false, "use_power": false, "vtln_high": 1226, "vtln_low": 960, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 1.0298, "energy_floor": 2.249, "frame_length": 1.125, "frame_shift": 0.25, "high_freq": 2031, "htk_compat": true, "low_freq": 257, "num_mel_bins": 5, "preemphasis_coefficient": 0.65, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": false, "use_log_fbank": false, "use_power": true, "vtln_high": 1731, "vtln_low": 1582, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 1.2222, "energy_floor": 0.0582, "frame_length": 1.1875, "frame_shift": 0.8125, "high_freq": 6633, "htk_compat": false, "low_freq": 1117, "num_mel_bins": 8, "preemphasis_coefficient": 0.96, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": false, "use_log_fbank": true, "use_power": false, "vtln_high": 4191, "vtln_low": 3264, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 1.2251, "energy_floor": 0.4403, "frame_length": 0.5625, "frame_shift": 0.6875, "high_freq": 3192, "htk_compat": false, "low_freq": 599, "num_mel_bins": 4, "preemphasis_coefficient": 0.75, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": false, "use_log_fbank": false, "use_power": true, "vtln_high": 3183, "vtln_low": 2975, "vtln_warp": 1.0, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 1.2278, "energy_floor": 1.4848, "frame_length": 0.6875, "frame_shift": 0.625, "high_freq": 5785, "htk_compat": true, "low_freq": 289, "num_mel_bins": 4, "preemphasis_coefficient": 0.58, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": false, "use_log_fbank": false, "use_power": true, "vtln_high": 4062, "vtln_low": 3715, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 1.3199, "energy_floor": 1.1137, "frame_length": 1.125, "frame_shift": 0.6875, "high_freq": 6702, "htk_compat": true, "low_freq": 390, "num_mel_bins": 6, "preemphasis_coefficient": 0.54, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": true, "use_energy": false, "use_log_fbank": true, "use_power": false, "vtln_high": 4426, "vtln_low": 2811, "vtln_warp": 1.0, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 1.3325, "energy_floor": 2.6552, "frame_length": 1.0, "frame_shift": 1.0625, "high_freq": 6444, "htk_compat": true, "low_freq": 759, "num_mel_bins": 4, "preemphasis_coefficient": 0.67, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": false, "use_log_fbank": false, "use_power": false, "vtln_high": 6065, "vtln_low": 4599, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 1.3426, "energy_floor": 1.5712, "frame_length": 1.1875, "frame_shift": 1.0, "high_freq": 7444, "htk_compat": false, "low_freq": 1986, "num_mel_bins": 6, "preemphasis_coefficient": 0.46, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": false, "use_log_fbank": true, "use_power": true, "vtln_high": 4787, "vtln_low": 3163, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 1.4359, "energy_floor": 1.2709, "frame_length": 1.0, "frame_shift": 0.5625, "high_freq": 7657, "htk_compat": true, "low_freq": 1017, "num_mel_bins": 5, "preemphasis_coefficient": 0.93, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": false, "use_log_fbank": false, "use_power": true, "vtln_high": 4228, "vtln_low": 2903, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 1.4621, "energy_floor": 1.2891, "frame_length": 0.875, "frame_shift": 1.0625, "high_freq": 6324, "htk_compat": true, "low_freq": 408, "num_mel_bins": 7, "preemphasis_coefficient": 0.09, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": false, "use_log_fbank": false, "use_power": false, "vtln_high": 6163, "vtln_low": 5973, "vtln_warp": 1.0, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 1.4751, "energy_floor": 2.3567, "frame_length": 1.1875, "frame_shift": 1.0, "high_freq": 7115, "htk_compat": false, "low_freq": 4236, "num_mel_bins": 5, "preemphasis_coefficient": 0.65, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": true, "use_log_fbank": true, "use_power": false, "vtln_high": 6523, "vtln_low": 5708, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 1.4883, "energy_floor": 4.1237, "frame_length": 0.75, "frame_shift": 0.25, "high_freq": 5670, "htk_compat": true, "low_freq": 766, "num_mel_bins": 6, "preemphasis_coefficient": 0.29, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": false, "use_log_fbank": false, "use_power": false, "vtln_high": 5479, "vtln_low": 4173, "vtln_warp": 1.0, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 1.493, "energy_floor": 1.4719, "frame_length": 1.125, "frame_shift": 0.25, "high_freq": 7805, "htk_compat": true, "low_freq": 5052, "num_mel_bins": 4, "preemphasis_coefficient": 0.9, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": false, "use_energy": true, "use_log_fbank": false, "use_power": false, "vtln_high": 7300, "vtln_low": 5299, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 1.5718, "energy_floor": 3.5447, "frame_length": 0.625, "frame_shift": 0.1875, "high_freq": 6777, "htk_compat": true, "low_freq": 938, "num_mel_bins": 4, "preemphasis_coefficient": 0.69, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": false, "subtract_mean": false, "use_energy": true, "use_log_fbank": true, "use_power": false, "vtln_high": 4540, "vtln_low": 3168, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 1.5786, "energy_floor": 1.9016, "frame_length": 1.1875, "frame_shift": 0.75, "high_freq": 5812, "htk_compat": true, "low_freq": 3000, "num_mel_bins": 4, "preemphasis_coefficient": 0.14, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": true, "use_log_fbank": true, "use_power": false, "vtln_high": 4930, "vtln_low": 4316, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 1.6134, "energy_floor": 0.6389, "frame_length": 1.0625, "frame_shift": 0.8125, "high_freq": 7384, "htk_compat": false, "low_freq": 184, "num_mel_bins": 7, "preemphasis_coefficient": 0.08, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": false, "use_log_fbank": false, "use_power": true, "vtln_high": 2759, "vtln_low": 306, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 1.6312, "energy_floor": 2.6556, "frame_length": 0.625, "frame_shift": 0.4375, "high_freq": 5589, "htk_compat": false, "low_freq": 1049, "num_mel_bins": 5, "preemphasis_coefficient": 0.8, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": false, "use_energy": false, "use_log_fbank": false, "use_power": false, "vtln_high": 3816, "vtln_low": 1550, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 1.7515, "energy_floor": 0.5964, "frame_length": 1.0625, "frame_shift": 1.0, "high_freq": 4349, "htk_compat": true, "low_freq": 702, "num_mel_bins": 5, "preemphasis_coefficient": 0.36, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": true, "use_log_fbank": false, "use_power": false, "vtln_high": 4168, "vtln_low": 1531, "vtln_warp": 1.0, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 1.8179, "energy_floor": 1.3295, "frame_length": 0.5625, "frame_shift": 0.6875, "high_freq": 4510, "htk_compat": false, "low_freq": 122, "num_mel_bins": 4, "preemphasis_coefficient": 0.56, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": true, "use_log_fbank": false, "use_power": false, "vtln_high": 4365, "vtln_low": 3721, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 1.9387, "energy_floor": 4.7991, "frame_length": 1.0, "frame_shift": 0.375, "high_freq": 6123, "htk_compat": true, "low_freq": 740, "num_mel_bins": 6, "preemphasis_coefficient": 0.21, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": false, "use_energy": true, "use_log_fbank": true, "use_power": true, "vtln_high": 3970, "vtln_low": 3355, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 2.0479, "energy_floor": 1.4296, "frame_length": 1.0625, "frame_shift": 0.6875, "high_freq": 7818, "htk_compat": true, "low_freq": 1628, "num_mel_bins": 8, "preemphasis_coefficient": 0.27, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": true, "use_log_fbank": false, "use_power": true, "vtln_high": 7749, "vtln_low": 7478, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 2.0809, "energy_floor": 1.9752, "frame_length": 0.75, "frame_shift": 1.1875, "high_freq": 5933, "htk_compat": false, "low_freq": 666, "num_mel_bins": 5, "preemphasis_coefficient": 0.72, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": false, "snip_edges": false, "subtract_mean": true, "use_energy": true, "use_log_fbank": true, "use_power": true, "vtln_high": 5348, "vtln_low": 4645, "vtln_warp": 1.0, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 2.1098, "energy_floor": 2.1356, "frame_length": 1.0625, "frame_shift": 0.9375, "high_freq": 7825, "htk_compat": true, "low_freq": 408, "num_mel_bins": 4, "preemphasis_coefficient": 0.37, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": true, "use_log_fbank": false, "use_power": false, "vtln_high": 5297, "vtln_low": 2747, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 2.1463, "energy_floor": 0.3422, "frame_length": 0.8125, "frame_shift": 0.5, "high_freq": 6892, "htk_compat": true, "low_freq": 65, "num_mel_bins": 4, "preemphasis_coefficient": 0.47, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": true, "use_log_fbank": false, "use_power": true, "vtln_high": 4178, "vtln_low": 2891, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 2.1768, "energy_floor": 3.782, "frame_length": 0.75, "frame_shift": 0.8125, "high_freq": 7063, "htk_compat": false, "low_freq": 2703, "num_mel_bins": 4, "preemphasis_coefficient": 0.99, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": false, "subtract_mean": true, "use_energy": false, "use_log_fbank": false, "use_power": true, "vtln_high": 6819, "vtln_low": 3764, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 2.1902, "energy_floor": 4.9973, "frame_length": 1.125, "frame_shift": 0.5, "high_freq": 7066, "htk_compat": false, "low_freq": 1699, "num_mel_bins": 4, "preemphasis_coefficient": 0.95, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": false, "use_log_fbank": false, "use_power": false, "vtln_high": 5452, "vtln_low": 5271, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 2.239, "energy_floor": 2.9557, "frame_length": 1.0625, "frame_shift": 0.875, "high_freq": 7615, "htk_compat": true, "low_freq": 4707, "num_mel_bins": 7, "preemphasis_coefficient": 1.0, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": true, "use_log_fbank": true, "use_power": true, "vtln_high": 6790, "vtln_low": 6501, "vtln_warp": 1.0, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 2.3199, "energy_floor": 0.8311, "frame_length": 0.9375, "frame_shift": 0.3125, "high_freq": 6738, "htk_compat": true, "low_freq": 1787, "num_mel_bins": 5, "preemphasis_coefficient": 0.83, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": true, "use_log_fbank": false, "use_power": true, "vtln_high": 6635, "vtln_low": 6360, "vtln_warp": 0.7856, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 2.4071, "energy_floor": 2.7889, "frame_length": 0.9375, "frame_shift": 0.8125, "high_freq": 6598, "htk_compat": true, "low_freq": 2373, "num_mel_bins": 5, "preemphasis_coefficient": 0.2, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": false, "use_log_fbank": true, "use_power": true, "vtln_high": 4565, "vtln_low": 3464, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 2.4586, "energy_floor": 3.3176, "frame_length": 0.625, "frame_shift": 0.75, "high_freq": 7380, "htk_compat": false, "low_freq": 4248, "num_mel_bins": 4, "preemphasis_coefficient": 0.59, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": false, "use_log_fbank": false, "use_power": true, "vtln_high": 7263, "vtln_low": 6361, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 2.6039, "energy_floor": 1.1619, "frame_length": 0.75, "frame_shift": 0.5625, "high_freq": 6578, "htk_compat": true, "low_freq": 551, "num_mel_bins": 7, "preemphasis_coefficient": 0.16, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": false, "use_log_fbank": true, "use_power": true, "vtln_high": 4974, "vtln_low": 3139, "vtln_warp": 1.0, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 2.6068, "energy_floor": 3.6411, "frame_length": 1.125, "frame_shift": 1.125, "high_freq": 3078, "htk_compat": false, "low_freq": 1003, "num_mel_bins": 5, "preemphasis_coefficient": 0.12, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": true, "use_log_fbank": true, "use_power": false, "vtln_high": 2920, "vtln_low": 1121, "vtln_warp": 1.0, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 2.6192, "energy_floor": 1.7209, "frame_length": 1.0625, "frame_shift": 0.625, "high_freq": 2275, "htk_compat": false, "low_freq": 367, "num_mel_bins": 5, "preemphasis_coefficient": 0.27, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": true, "use_log_fbank": false, "use_power": true, "vtln_high": 1293, "vtln_low": 771, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 2.6578, "energy_floor": 0.9137, "frame_length": 1.1875, "frame_shift": 1.0, "high_freq": 4898, "htk_compat": true, "low_freq": 886, "num_mel_bins": 7, "preemphasis_coefficient": 0.57, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": true, "use_log_fbank": false, "use_power": false, "vtln_high": 3704, "vtln_low": 1013, "vtln_warp": 1.0, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 2.7083, "energy_floor": 2.8806, "frame_length": 0.75, "frame_shift": 0.9375, "high_freq": 6605, "htk_compat": false, "low_freq": 3759, "num_mel_bins": 4, "preemphasis_coefficient": 0.9, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": false, "use_log_fbank": false, "use_power": true, "vtln_high": 6542, "vtln_low": 5821, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 2.7704, "energy_floor": 4.5251, "frame_length": 1.125, "frame_shift": 0.875, "high_freq": 3819, "htk_compat": true, "low_freq": 787, "num_mel_bins": 5, "preemphasis_coefficient": 0.23, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": true, "use_log_fbank": false, "use_power": false, "vtln_high": 3368, "vtln_low": 3286, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 2.9255, "energy_floor": 3.4363, "frame_length": 1.125, "frame_shift": 1.0, "high_freq": 7660, "htk_compat": false, "low_freq": 5020, "num_mel_bins": 5, "preemphasis_coefficient": 0.09, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": true, "use_log_fbank": false, "use_power": false, "vtln_high": 7470, "vtln_low": 6783, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 3.0009, "energy_floor": 1.845, "frame_length": 1.0625, "frame_shift": 0.75, "high_freq": 5812, "htk_compat": true, "low_freq": 1287, "num_mel_bins": 6, "preemphasis_coefficient": 0.22, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": true, "use_log_fbank": false, "use_power": false, "vtln_high": 5573, "vtln_low": 4642, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 3.2195, "energy_floor": 2.9858, "frame_length": 1.0625, "frame_shift": 0.0625, "high_freq": 6899, "htk_compat": true, "low_freq": 4117, "num_mel_bins": 6, "preemphasis_coefficient": 0.85, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": false, "use_log_fbank": true, "use_power": true, "vtln_high": 5077, "vtln_low": 4977, "vtln_warp": 0.8739, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 3.3208, "energy_floor": 1.5569, "frame_length": 1.0, "frame_shift": 0.3125, "high_freq": 4556, "htk_compat": false, "low_freq": 334, "num_mel_bins": 5, "preemphasis_coefficient": 0.02, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": false, "use_log_fbank": true, "use_power": false, "vtln_high": 2831, "vtln_low": 696, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 3.3976, "energy_floor": 3.9462, "frame_length": 1.1875, "frame_shift": 0.5625, "high_freq": 6513, "htk_compat": false, "low_freq": 3398, "num_mel_bins": 8, "preemphasis_coefficient": 0.38, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": false, "use_log_fbank": true, "use_power": false, "vtln_high": 5827, "vtln_low": 5388, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 3.5842, "energy_floor": 1.2264, "frame_length": 0.9375, "frame_shift": 1.0, "high_freq": 7744, "htk_compat": false, "low_freq": 195, "num_mel_bins": 5, "preemphasis_coefficient": 0.62, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": true, "use_log_fbank": true, "use_power": false, "vtln_high": 7667, "vtln_low": 2993, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 3.5889, "energy_floor": 3.3559, "frame_length": 1.0, "frame_shift": 1.1875, "high_freq": 7354, "htk_compat": true, "low_freq": 997, "num_mel_bins": 5, "preemphasis_coefficient": 0.98, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": false, "use_log_fbank": false, "use_power": false, "vtln_high": 7088, "vtln_low": 6494, "vtln_warp": 1.0, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 3.5936, "energy_floor": 2.1701, "frame_length": 1.0625, "frame_shift": 1.0625, "high_freq": 7407, "htk_compat": true, "low_freq": 3649, "num_mel_bins": 5, "preemphasis_coefficient": 0.65, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": true, "use_log_fbank": true, "use_power": true, "vtln_high": 6878, "vtln_low": 6036, "vtln_warp": 1.0, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 3.7002, "energy_floor": 3.567, "frame_length": 1.1875, "frame_shift": 0.625, "high_freq": 4479, "htk_compat": true, "low_freq": 2240, "num_mel_bins": 6, "preemphasis_coefficient": 0.73, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": true, "use_log_fbank": false, "use_power": true, "vtln_high": 4084, "vtln_low": 3955, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 3.7078, "energy_floor": 0.3892, "frame_length": 0.8125, "frame_shift": 0.3125, "high_freq": 7876, "htk_compat": true, "low_freq": 2830, "num_mel_bins": 7, "preemphasis_coefficient": 0.46, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": false, "use_log_fbank": false, "use_power": true, "vtln_high": 4726, "vtln_low": 2918, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 3.7585, "energy_floor": 2.9425, "frame_length": 1.1875, "frame_shift": 1.0, "high_freq": 3277, "htk_compat": true, "low_freq": 2244, "num_mel_bins": 4, "preemphasis_coefficient": 0.76, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": false, "use_log_fbank": false, "use_power": false, "vtln_high": 3158, "vtln_low": 2865, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 3.7772, "energy_floor": 2.8211, "frame_length": 1.0, "frame_shift": 0.1875, "high_freq": 3747, "htk_compat": false, "low_freq": 1244, "num_mel_bins": 4, "preemphasis_coefficient": 0.64, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": false, "use_log_fbank": false, "use_power": true, "vtln_high": 3640, "vtln_low": 2770, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 3.8514, "energy_floor": 3.7933, "frame_length": 1.0625, "frame_shift": 0.5, "high_freq": 4136, "htk_compat": true, "low_freq": 1010, "num_mel_bins": 6, "preemphasis_coefficient": 0.12, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": true, "use_log_fbank": true, "use_power": false, "vtln_high": 2408, "vtln_low": 1892, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 3.8677, "energy_floor": 2.5418, "frame_length": 1.0625, "frame_shift": 0.0625, "high_freq": 3496, "htk_compat": true, "low_freq": 309, "num_mel_bins": 4, "preemphasis_coefficient": 0.47, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": false, "use_log_fbank": true, "use_power": false, "vtln_high": 1490, "vtln_low": 645, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 3.9033, "energy_floor": 2.677, "frame_length": 1.125, "frame_shift": 0.875, "high_freq": 5699, "htk_compat": false, "low_freq": 2960, "num_mel_bins": 7, "preemphasis_coefficient": 0.52, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": true, "use_log_fbank": false, "use_power": false, "vtln_high": 5458, "vtln_low": 5400, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 4.0371, "energy_floor": 3.7559, "frame_length": 1.0625, "frame_shift": 0.8125, "high_freq": 4280, "htk_compat": false, "low_freq": 1207, "num_mel_bins": 4, "preemphasis_coefficient": 0.12, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": true, "use_log_fbank": false, "use_power": true, "vtln_high": 3686, "vtln_low": 2010, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 4.0757, "energy_floor": 4.7442, "frame_length": 0.875, "frame_shift": 1.125, "high_freq": 6363, "htk_compat": true, "low_freq": 1524, "num_mel_bins": 4, "preemphasis_coefficient": 0.32, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": false, "use_energy": true, "use_log_fbank": true, "use_power": false, "vtln_high": 5178, "vtln_low": 4628, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 4.1248, "energy_floor": 2.5255, "frame_length": 0.6875, "frame_shift": 0.6875, "high_freq": 3527, "htk_compat": true, "low_freq": 1701, "num_mel_bins": 4, "preemphasis_coefficient": 0.43, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": true, "use_log_fbank": false, "use_power": true, "vtln_high": 2884, "vtln_low": 1773, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 4.18, "energy_floor": 4.6907, "frame_length": 1.1875, "frame_shift": 0.5625, "high_freq": 7316, "htk_compat": true, "low_freq": 3483, "num_mel_bins": 8, "preemphasis_coefficient": 0.61, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": true, "use_log_fbank": true, "use_power": true, "vtln_high": 5820, "vtln_low": 4635, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 4.2251, "energy_floor": 0.5, "frame_length": 0.875, "frame_shift": 0.625, "high_freq": 7515, "htk_compat": false, "low_freq": 1751, "num_mel_bins": 5, "preemphasis_coefficient": 0.64, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": false, "use_log_fbank": true, "use_power": false, "vtln_high": 7486, "vtln_low": 4238, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 4.3011, "energy_floor": 1.4663, "frame_length": 1.125, "frame_shift": 0.9375, "high_freq": 7804, "htk_compat": false, "low_freq": 1208, "num_mel_bins": 6, "preemphasis_coefficient": 0.18, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": true, "use_log_fbank": true, "use_power": false, "vtln_high": 7421, "vtln_low": 3707, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 4.3252, "energy_floor": 0.7732, "frame_length": 0.625, "frame_shift": 0.6875, "high_freq": 7389, "htk_compat": false, "low_freq": 2071, "num_mel_bins": 4, "preemphasis_coefficient": 0.08, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": true, "use_log_fbank": false, "use_power": false, "vtln_high": 6900, "vtln_low": 2344, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 4.3693, "energy_floor": 3.9073, "frame_length": 0.875, "frame_shift": 0.9375, "high_freq": 6107, "htk_compat": true, "low_freq": 3905, "num_mel_bins": 4, "preemphasis_coefficient": 0.86, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": true, "use_log_fbank": false, "use_power": true, "vtln_high": 5001, "vtln_low": 4046, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 4.3926, "energy_floor": 2.0617, "frame_length": 0.5625, "frame_shift": 0.0625, "high_freq": 4253, "htk_compat": true, "low_freq": 1367, "num_mel_bins": 5, "preemphasis_coefficient": 0.84, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": true, "use_log_fbank": true, "use_power": false, "vtln_high": 2112, "vtln_low": 1445, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 4.4706, "energy_floor": 1.7516, "frame_length": 1.125, "frame_shift": 1.125, "high_freq": 7645, "htk_compat": false, "low_freq": 225, "num_mel_bins": 6, "preemphasis_coefficient": 0.8, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": false, "use_energy": false, "use_log_fbank": false, "use_power": false, "vtln_high": 3717, "vtln_low": 304, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 4.5385, "energy_floor": 2.1519, "frame_length": 1.125, "frame_shift": 0.0625, "high_freq": 5610, "htk_compat": false, "low_freq": 1239, "num_mel_bins": 7, "preemphasis_coefficient": 0.87, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": true, "use_energy": true, "use_log_fbank": true, "use_power": false, "vtln_high": 2231, "vtln_low": 1432, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 4.6337, "energy_floor": 2.902, "frame_length": 0.875, "frame_shift": 1.125, "high_freq": 5072, "htk_compat": true, "low_freq": 826, "num_mel_bins": 4, "preemphasis_coefficient": 0.37, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": false, "use_log_fbank": false, "use_power": false, "vtln_high": 4253, "vtln_low": 2427, "vtln_warp": 0.7049, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 4.7468, "energy_floor": 2.1835, "frame_length": 0.6875, "frame_shift": 1.0, "high_freq": 5153, "htk_compat": true, "low_freq": 943, "num_mel_bins": 5, "preemphasis_coefficient": 0.94, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": false, "use_log_fbank": false, "use_power": false, "vtln_high": 3287, "vtln_low": 1478, "vtln_warp": 0.9406, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 4.7855, "energy_floor": 2.1377, "frame_length": 0.9375, "frame_shift": 0.8125, "high_freq": 4123, "htk_compat": false, "low_freq": 587, "num_mel_bins": 4, "preemphasis_coefficient": 0.92, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": false, "use_log_fbank": true, "use_power": false, "vtln_high": 2588, "vtln_low": 2346, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 4.964, "energy_floor": 3.4931, "frame_length": 1.1875, "frame_shift": 1.0, "high_freq": 4235, "htk_compat": true, "low_freq": 1036, "num_mel_bins": 4, "preemphasis_coefficient": 0.43, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": true, "use_log_fbank": false, "use_power": false, "vtln_high": 3706, "vtln_low": 2840, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} diff --git a/test/torchaudio_unittest/assets/kaldi_test_mfcc_args.jsonl b/test/torchaudio_unittest/assets/kaldi_test_mfcc_args.jsonl new file mode 100644 index 00000000..7808a244 --- /dev/null +++ b/test/torchaudio_unittest/assets/kaldi_test_mfcc_args.jsonl @@ -0,0 +1,114 @@ +{"blackman_coeff": 0.013, "energy_floor": 1.8509, "frame_length": 1.1875, "frame_shift": 0.625, "high_freq": 7999, "htk_compat": false, "low_freq": 4330, "num_mel_bins": 5, "preemphasis_coefficient": 0.38, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": true, "num_ceps": 3, "cepstral_lifter": 8.1048, "vtln_high": 7497, "vtln_low": 7397, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 0.0487, "energy_floor": 1.3641, "frame_length": 1.0, "frame_shift": 0.8125, "high_freq": 7892, "htk_compat": true, "low_freq": 1904, "num_mel_bins": 8, "preemphasis_coefficient": 0.26, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": false, "num_ceps": 5, "cepstral_lifter": 34.0918, "vtln_high": 4400, "vtln_low": 2737, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 0.0577, "energy_floor": 2.4313, "frame_length": 1.0625, "frame_shift": 0.875, "high_freq": 2922, "htk_compat": true, "low_freq": 274, "num_mel_bins": 6, "preemphasis_coefficient": 0.48, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": true, "num_ceps": 5, "cepstral_lifter": 21.3007, "vtln_high": 1352, "vtln_low": 280, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 0.0718, "energy_floor": 1.3071, "frame_length": 1.1875, "frame_shift": 0.5, "high_freq": 3159, "htk_compat": true, "low_freq": 759, "num_mel_bins": 8, "preemphasis_coefficient": 0.04, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": true, "num_ceps": 6, "cepstral_lifter": 2.6493, "vtln_high": 3145, "vtln_low": 3119, "vtln_warp": 1.0, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 0.083, "energy_floor": 2.1607, "frame_length": 0.75, "frame_shift": 0.75, "high_freq": 5872, "htk_compat": true, "low_freq": 708, "num_mel_bins": 5, "preemphasis_coefficient": 0.95, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": false, "subtract_mean": false, "use_energy": true, "num_ceps": 4, "cepstral_lifter": 24.5097, "vtln_high": 5231, "vtln_low": 3888, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 0.0933, "energy_floor": 1.577, "frame_length": 1.1875, "frame_shift": 1.1875, "high_freq": 7519, "htk_compat": false, "low_freq": 357, "num_mel_bins": 4, "preemphasis_coefficient": 0.5, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": false, "num_ceps": 2, "cepstral_lifter": 88.0941, "vtln_high": 7042, "vtln_low": 5298, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 0.1048, "energy_floor": 0.5013, "frame_length": 0.75, "frame_shift": 1.0625, "high_freq": 6426, "htk_compat": true, "low_freq": 3613, "num_mel_bins": 5, "preemphasis_coefficient": 0.96, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": true, "num_ceps": 4, "cepstral_lifter": 73.5838, "vtln_high": 5816, "vtln_low": 3997, "vtln_warp": 1.0, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 0.1447, "energy_floor": 4.7142, "frame_length": 1.0625, "frame_shift": 0.4375, "high_freq": 7629, "htk_compat": true, "low_freq": 3498, "num_mel_bins": 7, "preemphasis_coefficient": 0.39, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": false, "num_ceps": 3, "cepstral_lifter": 19.4145, "vtln_high": 7169, "vtln_low": 6751, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 0.1473, "energy_floor": 4.9154, "frame_length": 1.125, "frame_shift": 0.875, "high_freq": 3631, "htk_compat": false, "low_freq": 1229, "num_mel_bins": 8, "preemphasis_coefficient": 0.04, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": true, "num_ceps": 3, "cepstral_lifter": 34.479, "vtln_high": 3390, "vtln_low": 1536, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 0.1485, "energy_floor": 4.275, "frame_length": 0.6875, "frame_shift": 0.75, "high_freq": 5222, "htk_compat": true, "low_freq": 311, "num_mel_bins": 5, "preemphasis_coefficient": 0.31, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": true, "num_ceps": 3, "cepstral_lifter": 48.7991, "vtln_high": 4833, "vtln_low": 513, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 0.1829, "energy_floor": 4.5358, "frame_length": 0.9375, "frame_shift": 0.9375, "high_freq": 6148, "htk_compat": true, "low_freq": 455, "num_mel_bins": 5, "preemphasis_coefficient": 0.59, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": false, "num_ceps": 3, "cepstral_lifter": 48.6724, "vtln_high": 3138, "vtln_low": 2247, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 0.2002, "energy_floor": 1.4805, "frame_length": 0.9375, "frame_shift": 0.875, "high_freq": 7621, "htk_compat": false, "low_freq": 2232, "num_mel_bins": 5, "preemphasis_coefficient": 0.03, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": true, "num_ceps": 5, "cepstral_lifter": 69.3653, "vtln_high": 7087, "vtln_low": 2800, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 0.2107, "energy_floor": 3.475, "frame_length": 1.125, "frame_shift": 0.8125, "high_freq": 5701, "htk_compat": true, "low_freq": 1629, "num_mel_bins": 4, "preemphasis_coefficient": 0.09, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": true, "num_ceps": 2, "cepstral_lifter": 77.7066, "vtln_high": 5622, "vtln_low": 5544, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 0.2523, "energy_floor": 0.148, "frame_length": 1.0, "frame_shift": 1.125, "high_freq": 5833, "htk_compat": false, "low_freq": 556, "num_mel_bins": 4, "preemphasis_coefficient": 0.66, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": false, "num_ceps": 4, "cepstral_lifter": 57.0398, "vtln_high": 4519, "vtln_low": 3600, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 0.2959, "energy_floor": 2.3729, "frame_length": 0.625, "frame_shift": 0.5, "high_freq": 6757, "htk_compat": false, "low_freq": 1744, "num_mel_bins": 6, "preemphasis_coefficient": 0.2, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": false, "num_ceps": 2, "cepstral_lifter": 99.871, "vtln_high": 4957, "vtln_low": 3549, "vtln_warp": 1.0, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 0.3642, "energy_floor": 3.5246, "frame_length": 0.4375, "frame_shift": 0.9375, "high_freq": 7942, "htk_compat": false, "low_freq": 3282, "num_mel_bins": 4, "preemphasis_coefficient": 0.52, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": false, "num_ceps": 3, "cepstral_lifter": 74.4735, "vtln_high": 5601, "vtln_low": 4966, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 0.4891, "energy_floor": 1.989, "frame_length": 1.125, "frame_shift": 0.875, "high_freq": 3219, "htk_compat": true, "low_freq": 973, "num_mel_bins": 5, "preemphasis_coefficient": 0.91, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": true, "num_ceps": 4, "cepstral_lifter": 85.357, "vtln_high": 3181, "vtln_low": 3129, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 0.5428, "energy_floor": 1.2368, "frame_length": 0.5625, "frame_shift": 1.0, "high_freq": 6700, "htk_compat": false, "low_freq": 749, "num_mel_bins": 4, "preemphasis_coefficient": 0.86, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": true, "num_ceps": 2, "cepstral_lifter": 16.2782, "vtln_high": 5573, "vtln_low": 4988, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 0.5495, "energy_floor": 2.9502, "frame_length": 1.1875, "frame_shift": 1.1875, "high_freq": 3873, "htk_compat": true, "low_freq": 1564, "num_mel_bins": 5, "preemphasis_coefficient": 0.05, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": false, "num_ceps": 5, "cepstral_lifter": 59.0075, "vtln_high": 3870, "vtln_low": 3750, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 0.6457, "energy_floor": 2.0199, "frame_length": 0.875, "frame_shift": 0.8125, "high_freq": 6510, "htk_compat": false, "low_freq": 1482, "num_mel_bins": 4, "preemphasis_coefficient": 0.26, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": false, "use_energy": true, "num_ceps": 2, "cepstral_lifter": 49.7663, "vtln_high": 5461, "vtln_low": 4039, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 0.7031, "energy_floor": 4.038, "frame_length": 1.125, "frame_shift": 0.6875, "high_freq": 6433, "htk_compat": true, "low_freq": 2336, "num_mel_bins": 8, "preemphasis_coefficient": 0.7, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": true, "use_energy": false, "num_ceps": 3, "cepstral_lifter": 18.0061, "vtln_high": 5902, "vtln_low": 3191, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 0.7197, "energy_floor": 3.2075, "frame_length": 1.0625, "frame_shift": 0.25, "high_freq": 4448, "htk_compat": true, "low_freq": 378, "num_mel_bins": 4, "preemphasis_coefficient": 0.31, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": false, "num_ceps": 2, "cepstral_lifter": 71.591, "vtln_high": 3497, "vtln_low": 3331, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 0.7238, "energy_floor": 1.9087, "frame_length": 1.1875, "frame_shift": 0.75, "high_freq": 5457, "htk_compat": true, "low_freq": 1775, "num_mel_bins": 7, "preemphasis_coefficient": 0.48, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": true, "num_ceps": 3, "cepstral_lifter": 29.2858, "vtln_high": 5349, "vtln_low": 3987, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 0.7507, "energy_floor": 0.2754, "frame_length": 0.875, "frame_shift": 0.8125, "high_freq": 6405, "htk_compat": false, "low_freq": 1972, "num_mel_bins": 5, "preemphasis_coefficient": 0.83, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": true, "use_energy": true, "num_ceps": 2, "cepstral_lifter": 52.5962, "vtln_high": 4597, "vtln_low": 4417, "vtln_warp": 1.0, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 0.7954, "energy_floor": 0.3451, "frame_length": 1.1875, "frame_shift": 0.625, "high_freq": 4078, "htk_compat": false, "low_freq": 796, "num_mel_bins": 8, "preemphasis_coefficient": 0.47, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": true, "num_ceps": 8, "cepstral_lifter": 42.3128, "vtln_high": 2299, "vtln_low": 1094, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 0.8432, "energy_floor": 1.3765, "frame_length": 0.9375, "frame_shift": 1.1875, "high_freq": 6004, "htk_compat": true, "low_freq": 2302, "num_mel_bins": 4, "preemphasis_coefficient": 0.6, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": true, "num_ceps": 4, "cepstral_lifter": 74.1116, "vtln_high": 4129, "vtln_low": 2898, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 0.8549, "energy_floor": 4.8924, "frame_length": 0.3125, "frame_shift": 1.125, "high_freq": 5643, "htk_compat": true, "low_freq": 956, "num_mel_bins": 4, "preemphasis_coefficient": 0.32, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": true, "num_ceps": 2, "cepstral_lifter": 76.0384, "vtln_high": 2672, "vtln_low": 1762, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 0.9502, "energy_floor": 1.9738, "frame_length": 0.75, "frame_shift": 0.25, "high_freq": 7773, "htk_compat": true, "low_freq": 1205, "num_mel_bins": 7, "preemphasis_coefficient": 0.5, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": true, "num_ceps": 3, "cepstral_lifter": 62.9038, "vtln_high": 7460, "vtln_low": 7174, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 1.0759, "energy_floor": 1.9132, "frame_length": 1.1875, "frame_shift": 0.625, "high_freq": 3529, "htk_compat": false, "low_freq": 227, "num_mel_bins": 8, "preemphasis_coefficient": 0.26, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": true, "num_ceps": 4, "cepstral_lifter": 4.1559, "vtln_high": 1976, "vtln_low": 972, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 1.147, "energy_floor": 4.3972, "frame_length": 0.9375, "frame_shift": 0.75, "high_freq": 6393, "htk_compat": true, "low_freq": 2451, "num_mel_bins": 4, "preemphasis_coefficient": 0.06, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": true, "num_ceps": 4, "cepstral_lifter": 67.4571, "vtln_high": 5460, "vtln_low": 3654, "vtln_warp": 1.0, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 1.2069, "energy_floor": 0.4607, "frame_length": 1.125, "frame_shift": 0.5625, "high_freq": 5864, "htk_compat": true, "low_freq": 1512, "num_mel_bins": 7, "preemphasis_coefficient": 0.17, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": true, "num_ceps": 3, "cepstral_lifter": 33.0194, "vtln_high": 5438, "vtln_low": 3920, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 1.3369, "energy_floor": 4.2619, "frame_length": 0.6875, "frame_shift": 0.375, "high_freq": 5307, "htk_compat": true, "low_freq": 666, "num_mel_bins": 6, "preemphasis_coefficient": 0.19, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": true, "num_ceps": 4, "cepstral_lifter": 88.3186, "vtln_high": 4677, "vtln_low": 2590, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 1.3699, "energy_floor": 3.0236, "frame_length": 1.0625, "frame_shift": 0.75, "high_freq": 3720, "htk_compat": true, "low_freq": 1980, "num_mel_bins": 4, "preemphasis_coefficient": 0.13, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": true, "num_ceps": 3, "cepstral_lifter": 57.6793, "vtln_high": 3441, "vtln_low": 3396, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 1.3763, "energy_floor": 1.6574, "frame_length": 1.125, "frame_shift": 0.8125, "high_freq": 2816, "htk_compat": false, "low_freq": 1021, "num_mel_bins": 4, "preemphasis_coefficient": 0.91, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": true, "num_ceps": 2, "cepstral_lifter": 45.2819, "vtln_high": 2547, "vtln_low": 1123, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 1.4097, "energy_floor": 4.1523, "frame_length": 0.875, "frame_shift": 0.375, "high_freq": 6164, "htk_compat": false, "low_freq": 987, "num_mel_bins": 4, "preemphasis_coefficient": 0.06, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": false, "num_ceps": 3, "cepstral_lifter": 75.0589, "vtln_high": 5873, "vtln_low": 5807, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 1.4295, "energy_floor": 3.7938, "frame_length": 1.125, "frame_shift": 0.75, "high_freq": 3382, "htk_compat": false, "low_freq": 471, "num_mel_bins": 4, "preemphasis_coefficient": 0.42, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": false, "subtract_mean": true, "use_energy": true, "num_ceps": 2, "cepstral_lifter": 78.588, "vtln_high": 3299, "vtln_low": 2540, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 1.4677, "energy_floor": 4.0728, "frame_length": 1.125, "frame_shift": 1.0625, "high_freq": 7698, "htk_compat": true, "low_freq": 569, "num_mel_bins": 6, "preemphasis_coefficient": 0.5, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": true, "use_energy": false, "num_ceps": 3, "cepstral_lifter": 76.8484, "vtln_high": 7453, "vtln_low": 7251, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 1.4979, "energy_floor": 1.1705, "frame_length": 1.1875, "frame_shift": 0.375, "high_freq": 4474, "htk_compat": true, "low_freq": 1123, "num_mel_bins": 7, "preemphasis_coefficient": 0.09, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": true, "num_ceps": 4, "cepstral_lifter": 38.6407, "vtln_high": 3043, "vtln_low": 2934, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 1.555, "energy_floor": 1.8728, "frame_length": 0.875, "frame_shift": 0.9375, "high_freq": 5191, "htk_compat": true, "low_freq": 2262, "num_mel_bins": 4, "preemphasis_coefficient": 0.24, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": false, "num_ceps": 2, "cepstral_lifter": 11.982, "vtln_high": 4607, "vtln_low": 4483, "vtln_warp": 1.0, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 1.5626, "energy_floor": 3.7117, "frame_length": 1.125, "frame_shift": 0.125, "high_freq": 3008, "htk_compat": true, "low_freq": 534, "num_mel_bins": 5, "preemphasis_coefficient": 0.65, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": true, "use_energy": true, "num_ceps": 2, "cepstral_lifter": 75.6661, "vtln_high": 2592, "vtln_low": 1621, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 1.5707, "energy_floor": 3.0409, "frame_length": 0.75, "frame_shift": 0.625, "high_freq": 7441, "htk_compat": true, "low_freq": 1554, "num_mel_bins": 6, "preemphasis_coefficient": 0.95, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": true, "num_ceps": 4, "cepstral_lifter": 80.1563, "vtln_high": 7152, "vtln_low": 6151, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 1.5966, "energy_floor": 2.3442, "frame_length": 0.5625, "frame_shift": 0.5, "high_freq": 7944, "htk_compat": true, "low_freq": 1616, "num_mel_bins": 5, "preemphasis_coefficient": 0.49, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": true, "num_ceps": 2, "cepstral_lifter": 80.8779, "vtln_high": 5720, "vtln_low": 4080, "vtln_warp": 1.0, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 1.6229, "energy_floor": 2.0519, "frame_length": 1.1875, "frame_shift": 0.3125, "high_freq": 4871, "htk_compat": true, "low_freq": 1567, "num_mel_bins": 4, "preemphasis_coefficient": 0.79, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": true, "num_ceps": 2, "cepstral_lifter": 42.9569, "vtln_high": 3483, "vtln_low": 3287, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 1.736, "energy_floor": 0.4063, "frame_length": 0.6875, "frame_shift": 0.0625, "high_freq": 6475, "htk_compat": true, "low_freq": 4439, "num_mel_bins": 4, "preemphasis_coefficient": 0.23, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": false, "num_ceps": 2, "cepstral_lifter": 30.0984, "vtln_high": 5450, "vtln_low": 4909, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 1.7411, "energy_floor": 2.0918, "frame_length": 1.0625, "frame_shift": 0.8125, "high_freq": 6107, "htk_compat": true, "low_freq": 2523, "num_mel_bins": 4, "preemphasis_coefficient": 0.69, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": false, "num_ceps": 3, "cepstral_lifter": 92.6839, "vtln_high": 5085, "vtln_low": 4771, "vtln_warp": 1.0, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 1.7439, "energy_floor": 2.3782, "frame_length": 0.875, "frame_shift": 1.1875, "high_freq": 7669, "htk_compat": false, "low_freq": 4499, "num_mel_bins": 4, "preemphasis_coefficient": 0.81, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": false, "use_energy": false, "num_ceps": 2, "cepstral_lifter": 95.7035, "vtln_high": 7521, "vtln_low": 7417, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 1.7611, "energy_floor": 4.2965, "frame_length": 0.8125, "frame_shift": 0.6875, "high_freq": 6607, "htk_compat": false, "low_freq": 454, "num_mel_bins": 7, "preemphasis_coefficient": 0.35, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": false, "num_ceps": 2, "cepstral_lifter": 17.8265, "vtln_high": 6387, "vtln_low": 6105, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 1.7893, "energy_floor": 1.8005, "frame_length": 0.625, "frame_shift": 0.375, "high_freq": 2791, "htk_compat": true, "low_freq": 617, "num_mel_bins": 4, "preemphasis_coefficient": 0.96, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": true, "num_ceps": 4, "cepstral_lifter": 93.405, "vtln_high": 1751, "vtln_low": 1690, "vtln_warp": 1.0, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 1.8392, "energy_floor": 4.3711, "frame_length": 0.9375, "frame_shift": 0.75, "high_freq": 6978, "htk_compat": true, "low_freq": 453, "num_mel_bins": 4, "preemphasis_coefficient": 0.48, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": false, "num_ceps": 3, "cepstral_lifter": 42.9768, "vtln_high": 6315, "vtln_low": 3995, "vtln_warp": 1.1059, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 1.9561, "energy_floor": 0.8419, "frame_length": 0.8125, "frame_shift": 1.125, "high_freq": 5308, "htk_compat": false, "low_freq": 1471, "num_mel_bins": 5, "preemphasis_coefficient": 0.62, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": true, "num_ceps": 3, "cepstral_lifter": 51.0514, "vtln_high": 5221, "vtln_low": 5071, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 1.998, "energy_floor": 1.6949, "frame_length": 1.125, "frame_shift": 0.8125, "high_freq": 4678, "htk_compat": true, "low_freq": 2340, "num_mel_bins": 5, "preemphasis_coefficient": 0.44, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": false, "use_energy": true, "num_ceps": 3, "cepstral_lifter": 70.1988, "vtln_high": 4041, "vtln_low": 2424, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 2.0106, "energy_floor": 4.5392, "frame_length": 0.6875, "frame_shift": 0.1875, "high_freq": 4776, "htk_compat": true, "low_freq": 1297, "num_mel_bins": 5, "preemphasis_coefficient": 0.14, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": true, "num_ceps": 2, "cepstral_lifter": 37.5837, "vtln_high": 3995, "vtln_low": 2991, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 2.0835, "energy_floor": 2.8454, "frame_length": 0.9375, "frame_shift": 0.3125, "high_freq": 7496, "htk_compat": false, "low_freq": 1207, "num_mel_bins": 6, "preemphasis_coefficient": 0.76, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": false, "num_ceps": 6, "cepstral_lifter": 4.5734, "vtln_high": 3935, "vtln_low": 3932, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 2.2801, "energy_floor": 1.7051, "frame_length": 1.1875, "frame_shift": 0.125, "high_freq": 7958, "htk_compat": true, "low_freq": 561, "num_mel_bins": 4, "preemphasis_coefficient": 0.9, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": true, "num_ceps": 2, "cepstral_lifter": 60.4649, "vtln_high": 7218, "vtln_low": 5709, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 2.315, "energy_floor": 0.4964, "frame_length": 0.5, "frame_shift": 0.4375, "high_freq": 6582, "htk_compat": false, "low_freq": 1010, "num_mel_bins": 4, "preemphasis_coefficient": 0.98, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": false, "num_ceps": 3, "cepstral_lifter": 71.516, "vtln_high": 6157, "vtln_low": 4430, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 2.3236, "energy_floor": 0.7825, "frame_length": 0.8125, "frame_shift": 0.25, "high_freq": 7488, "htk_compat": true, "low_freq": 1363, "num_mel_bins": 4, "preemphasis_coefficient": 0.3, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": true, "num_ceps": 4, "cepstral_lifter": 68.9678, "vtln_high": 3555, "vtln_low": 1851, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 2.3805, "energy_floor": 2.934, "frame_length": 0.75, "frame_shift": 0.25, "high_freq": 6076, "htk_compat": true, "low_freq": 80, "num_mel_bins": 4, "preemphasis_coefficient": 0.85, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": true, "num_ceps": 3, "cepstral_lifter": 31.0805, "vtln_high": 2257, "vtln_low": 1533, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 2.4091, "energy_floor": 2.8812, "frame_length": 1.125, "frame_shift": 0.9375, "high_freq": 6086, "htk_compat": false, "low_freq": 1210, "num_mel_bins": 5, "preemphasis_coefficient": 0.59, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": false, "subtract_mean": false, "use_energy": true, "num_ceps": 3, "cepstral_lifter": 96.1612, "vtln_high": 4840, "vtln_low": 1905, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 2.4134, "energy_floor": 2.6379, "frame_length": 1.1875, "frame_shift": 0.375, "high_freq": 3318, "htk_compat": false, "low_freq": 770, "num_mel_bins": 5, "preemphasis_coefficient": 0.6, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": false, "num_ceps": 3, "cepstral_lifter": 73.9427, "vtln_high": 2044, "vtln_low": 1481, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 2.5228, "energy_floor": 3.1056, "frame_length": 1.125, "frame_shift": 1.1875, "high_freq": 5422, "htk_compat": false, "low_freq": 2825, "num_mel_bins": 7, "preemphasis_coefficient": 0.88, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": false, "num_ceps": 7, "cepstral_lifter": 4.6719, "vtln_high": 5337, "vtln_low": 5243, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 2.5577, "energy_floor": 0.7393, "frame_length": 0.8125, "frame_shift": 0.5, "high_freq": 5291, "htk_compat": true, "low_freq": 1445, "num_mel_bins": 5, "preemphasis_coefficient": 0.01, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": false, "num_ceps": 2, "cepstral_lifter": 5.8944, "vtln_high": 4338, "vtln_low": 4330, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 2.5854, "energy_floor": 3.2219, "frame_length": 0.875, "frame_shift": 0.4375, "high_freq": 6924, "htk_compat": false, "low_freq": 4024, "num_mel_bins": 4, "preemphasis_coefficient": 1.0, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": false, "num_ceps": 2, "cepstral_lifter": 0.0578, "vtln_high": 5707, "vtln_low": 5025, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 2.6674, "energy_floor": 2.777, "frame_length": 1.0625, "frame_shift": 0.3125, "high_freq": 3129, "htk_compat": true, "low_freq": 1706, "num_mel_bins": 4, "preemphasis_coefficient": 0.91, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": true, "num_ceps": 3, "cepstral_lifter": 50.9241, "vtln_high": 2593, "vtln_low": 2198, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 2.6816, "energy_floor": 4.0548, "frame_length": 1.1875, "frame_shift": 0.625, "high_freq": 3182, "htk_compat": false, "low_freq": 157, "num_mel_bins": 6, "preemphasis_coefficient": 0.04, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": false, "num_ceps": 5, "cepstral_lifter": 31.3652, "vtln_high": 1203, "vtln_low": 1174, "vtln_warp": 1.0, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 2.7879, "energy_floor": 3.3482, "frame_length": 0.6875, "frame_shift": 0.375, "high_freq": 4262, "htk_compat": true, "low_freq": 150, "num_mel_bins": 4, "preemphasis_coefficient": 0.68, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": true, "num_ceps": 2, "cepstral_lifter": 92.794, "vtln_high": 3276, "vtln_low": 1685, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 2.8683, "energy_floor": 3.8162, "frame_length": 1.125, "frame_shift": 0.375, "high_freq": 6620, "htk_compat": false, "low_freq": 3389, "num_mel_bins": 7, "preemphasis_coefficient": 0.83, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": true, "num_ceps": 5, "cepstral_lifter": 82.2365, "vtln_high": 5365, "vtln_low": 4579, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 2.869, "energy_floor": 3.2618, "frame_length": 1.1875, "frame_shift": 0.9375, "high_freq": 5646, "htk_compat": true, "low_freq": 491, "num_mel_bins": 8, "preemphasis_coefficient": 0.89, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": true, "num_ceps": 2, "cepstral_lifter": 59.9812, "vtln_high": 5397, "vtln_low": 2639, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 2.9211, "energy_floor": 4.144, "frame_length": 0.75, "frame_shift": 0.375, "high_freq": 7210, "htk_compat": true, "low_freq": 3666, "num_mel_bins": 4, "preemphasis_coefficient": 0.93, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": false, "num_ceps": 4, "cepstral_lifter": 94.5907, "vtln_high": 6682, "vtln_low": 4979, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 2.9464, "energy_floor": 0.6798, "frame_length": 1.125, "frame_shift": 0.0625, "high_freq": 4445, "htk_compat": true, "low_freq": 323, "num_mel_bins": 6, "preemphasis_coefficient": 0.46, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": false, "num_ceps": 6, "cepstral_lifter": 7.8133, "vtln_high": 3755, "vtln_low": 1137, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 2.9633, "energy_floor": 1.9565, "frame_length": 0.875, "frame_shift": 0.0625, "high_freq": 6835, "htk_compat": false, "low_freq": 649, "num_mel_bins": 5, "preemphasis_coefficient": 0.77, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": false, "use_energy": false, "num_ceps": 2, "cepstral_lifter": 80.8871, "vtln_high": 6691, "vtln_low": 6581, "vtln_warp": 1.0, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 2.9697, "energy_floor": 2.0241, "frame_length": 1.125, "frame_shift": 0.6875, "high_freq": 2170, "htk_compat": false, "low_freq": 180, "num_mel_bins": 5, "preemphasis_coefficient": 0.28, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": true, "num_ceps": 3, "cepstral_lifter": 95.8111, "vtln_high": 1266, "vtln_low": 521, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 3.0358, "energy_floor": 1.7295, "frame_length": 1.1875, "frame_shift": 1.0, "high_freq": 7222, "htk_compat": true, "low_freq": 858, "num_mel_bins": 4, "preemphasis_coefficient": 0.16, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": true, "num_ceps": 4, "cepstral_lifter": 64.7537, "vtln_high": 6220, "vtln_low": 5229, "vtln_warp": 1.0, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 3.0421, "energy_floor": 3.3343, "frame_length": 1.0, "frame_shift": 0.9375, "high_freq": 6477, "htk_compat": false, "low_freq": 1402, "num_mel_bins": 5, "preemphasis_coefficient": 0.99, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": true, "num_ceps": 4, "cepstral_lifter": 26.1743, "vtln_high": 6381, "vtln_low": 5017, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 3.0919, "energy_floor": 4.5103, "frame_length": 0.625, "frame_shift": 1.0, "high_freq": 5323, "htk_compat": true, "low_freq": 937, "num_mel_bins": 5, "preemphasis_coefficient": 0.95, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": false, "num_ceps": 5, "cepstral_lifter": 82.2405, "vtln_high": 5130, "vtln_low": 5086, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 3.1463, "energy_floor": 4.5068, "frame_length": 0.6875, "frame_shift": 0.3125, "high_freq": 7587, "htk_compat": true, "low_freq": 3542, "num_mel_bins": 7, "preemphasis_coefficient": 0.78, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": true, "num_ceps": 4, "cepstral_lifter": 28.5808, "vtln_high": 7478, "vtln_low": 7326, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 3.2416, "energy_floor": 1.0604, "frame_length": 0.875, "frame_shift": 0.5, "high_freq": 4730, "htk_compat": false, "low_freq": 968, "num_mel_bins": 4, "preemphasis_coefficient": 0.11, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": true, "num_ceps": 3, "cepstral_lifter": 60.5751, "vtln_high": 3542, "vtln_low": 1943, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 3.2698, "energy_floor": 3.0361, "frame_length": 1.0625, "frame_shift": 0.75, "high_freq": 4870, "htk_compat": true, "low_freq": 1281, "num_mel_bins": 7, "preemphasis_coefficient": 0.64, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": true, "num_ceps": 3, "cepstral_lifter": 28.536, "vtln_high": 4401, "vtln_low": 3315, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 3.3078, "energy_floor": 4.9217, "frame_length": 1.0, "frame_shift": 0.3125, "high_freq": 6758, "htk_compat": true, "low_freq": 760, "num_mel_bins": 5, "preemphasis_coefficient": 0.98, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": true, "num_ceps": 3, "cepstral_lifter": 97.4694, "vtln_high": 6022, "vtln_low": 5650, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 3.3206, "energy_floor": 0.023, "frame_length": 1.0625, "frame_shift": 0.5625, "high_freq": 5744, "htk_compat": true, "low_freq": 3901, "num_mel_bins": 5, "preemphasis_coefficient": 0.94, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": false, "num_ceps": 2, "cepstral_lifter": 47.6031, "vtln_high": 5741, "vtln_low": 5524, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 3.4022, "energy_floor": 1.2172, "frame_length": 0.875, "frame_shift": 0.375, "high_freq": 7737, "htk_compat": false, "low_freq": 612, "num_mel_bins": 5, "preemphasis_coefficient": 0.35, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": false, "num_ceps": 2, "cepstral_lifter": 65.1166, "vtln_high": 6852, "vtln_low": 5820, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 3.4339, "energy_floor": 2.6197, "frame_length": 1.125, "frame_shift": 0.1875, "high_freq": 3341, "htk_compat": true, "low_freq": 1275, "num_mel_bins": 7, "preemphasis_coefficient": 0.41, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": true, "num_ceps": 2, "cepstral_lifter": 76.6062, "vtln_high": 3005, "vtln_low": 1680, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 3.4369, "energy_floor": 3.9198, "frame_length": 0.9375, "frame_shift": 0.125, "high_freq": 6218, "htk_compat": true, "low_freq": 904, "num_mel_bins": 5, "preemphasis_coefficient": 0.47, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": false, "num_ceps": 5, "cepstral_lifter": 92.8036, "vtln_high": 4870, "vtln_low": 1901, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 3.4557, "energy_floor": 1.5553, "frame_length": 0.75, "frame_shift": 0.375, "high_freq": 6642, "htk_compat": true, "low_freq": 1530, "num_mel_bins": 4, "preemphasis_coefficient": 0.72, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": false, "snip_edges": false, "subtract_mean": false, "use_energy": true, "num_ceps": 2, "cepstral_lifter": 28.8828, "vtln_high": 4490, "vtln_low": 2980, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 3.4753, "energy_floor": 4.7166, "frame_length": 0.75, "frame_shift": 0.3125, "high_freq": 7637, "htk_compat": true, "low_freq": 4992, "num_mel_bins": 4, "preemphasis_coefficient": 0.92, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": true, "num_ceps": 3, "cepstral_lifter": 2.5456, "vtln_high": 6925, "vtln_low": 5486, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 3.5134, "energy_floor": 2.0285, "frame_length": 0.625, "frame_shift": 0.1875, "high_freq": 5229, "htk_compat": false, "low_freq": 595, "num_mel_bins": 4, "preemphasis_coefficient": 0.65, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": false, "num_ceps": 3, "cepstral_lifter": 30.6069, "vtln_high": 5090, "vtln_low": 3467, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 3.5212, "energy_floor": 3.8251, "frame_length": 0.875, "frame_shift": 0.3125, "high_freq": 4092, "htk_compat": true, "low_freq": 545, "num_mel_bins": 5, "preemphasis_coefficient": 0.09, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": false, "num_ceps": 2, "cepstral_lifter": 2.9422, "vtln_high": 1634, "vtln_low": 1000, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 3.5261, "energy_floor": 2.0251, "frame_length": 0.875, "frame_shift": 0.625, "high_freq": 7926, "htk_compat": false, "low_freq": 3916, "num_mel_bins": 7, "preemphasis_coefficient": 0.45, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": false, "num_ceps": 2, "cepstral_lifter": 48.8818, "vtln_high": 7889, "vtln_low": 7527, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 3.539, "energy_floor": 1.6456, "frame_length": 1.125, "frame_shift": 0.1875, "high_freq": 5425, "htk_compat": true, "low_freq": 2326, "num_mel_bins": 7, "preemphasis_coefficient": 0.72, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": false, "num_ceps": 4, "cepstral_lifter": 39.4555, "vtln_high": 4290, "vtln_low": 2715, "vtln_warp": 1.0, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 3.5643, "energy_floor": 2.2424, "frame_length": 1.1875, "frame_shift": 0.875, "high_freq": 2140, "htk_compat": true, "low_freq": 59, "num_mel_bins": 4, "preemphasis_coefficient": 0.98, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": false, "num_ceps": 2, "cepstral_lifter": 36.7118, "vtln_high": 1463, "vtln_low": 1358, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 3.5959, "energy_floor": 4.8866, "frame_length": 1.125, "frame_shift": 1.0625, "high_freq": 5150, "htk_compat": false, "low_freq": 3697, "num_mel_bins": 4, "preemphasis_coefficient": 0.46, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": true, "num_ceps": 3, "cepstral_lifter": 74.0966, "vtln_high": 4277, "vtln_low": 3777, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 3.7223, "energy_floor": 3.4282, "frame_length": 1.0, "frame_shift": 0.125, "high_freq": 6601, "htk_compat": true, "low_freq": 1923, "num_mel_bins": 6, "preemphasis_coefficient": 0.05, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": true, "num_ceps": 4, "cepstral_lifter": 19.2839, "vtln_high": 6596, "vtln_low": 6594, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 3.7376, "energy_floor": 0.2093, "frame_length": 1.1875, "frame_shift": 0.75, "high_freq": 7830, "htk_compat": true, "low_freq": 4448, "num_mel_bins": 5, "preemphasis_coefficient": 0.27, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": true, "num_ceps": 5, "cepstral_lifter": 5.5865, "vtln_high": 5459, "vtln_low": 5056, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 3.812, "energy_floor": 0.4393, "frame_length": 0.75, "frame_shift": 1.0625, "high_freq": 5917, "htk_compat": false, "low_freq": 1272, "num_mel_bins": 4, "preemphasis_coefficient": 0.97, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": false, "subtract_mean": true, "use_energy": true, "num_ceps": 3, "cepstral_lifter": 91.4723, "vtln_high": 3532, "vtln_low": 3056, "vtln_warp": 1.0, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 3.8613, "energy_floor": 4.6574, "frame_length": 1.125, "frame_shift": 1.0, "high_freq": 3399, "htk_compat": true, "low_freq": 1576, "num_mel_bins": 5, "preemphasis_coefficient": 0.71, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": true, "num_ceps": 3, "cepstral_lifter": 29.1497, "vtln_high": 2440, "vtln_low": 1852, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 3.9117, "energy_floor": 4.6803, "frame_length": 0.5625, "frame_shift": 0.625, "high_freq": 5009, "htk_compat": false, "low_freq": 2542, "num_mel_bins": 4, "preemphasis_coefficient": 0.25, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": false, "num_ceps": 4, "cepstral_lifter": 65.8362, "vtln_high": 4734, "vtln_low": 3050, "vtln_warp": 1.0, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 4.1851, "energy_floor": 3.5211, "frame_length": 1.125, "frame_shift": 0.875, "high_freq": 4768, "htk_compat": false, "low_freq": 562, "num_mel_bins": 4, "preemphasis_coefficient": 0.05, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": false, "num_ceps": 2, "cepstral_lifter": 36.961, "vtln_high": 1982, "vtln_low": 741, "vtln_warp": 1.0, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 4.2197, "energy_floor": 3.7252, "frame_length": 0.9375, "frame_shift": 0.8125, "high_freq": 7453, "htk_compat": true, "low_freq": 1561, "num_mel_bins": 4, "preemphasis_coefficient": 0.06, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": false, "num_ceps": 2, "cepstral_lifter": 44.78, "vtln_high": 6612, "vtln_low": 4074, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 4.2736, "energy_floor": 4.9552, "frame_length": 0.75, "frame_shift": 1.0, "high_freq": 5145, "htk_compat": false, "low_freq": 1705, "num_mel_bins": 4, "preemphasis_coefficient": 0.33, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": false, "use_energy": false, "num_ceps": 4, "cepstral_lifter": 70.9332, "vtln_high": 4857, "vtln_low": 2223, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 4.3762, "energy_floor": 4.7209, "frame_length": 0.9375, "frame_shift": 0.0625, "high_freq": 5564, "htk_compat": true, "low_freq": 712, "num_mel_bins": 4, "preemphasis_coefficient": 0.74, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": true, "num_ceps": 2, "cepstral_lifter": 39.2887, "vtln_high": 4353, "vtln_low": 3521, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 4.4229, "energy_floor": 0.4222, "frame_length": 1.0625, "frame_shift": 1.1875, "high_freq": 7822, "htk_compat": true, "low_freq": 4837, "num_mel_bins": 5, "preemphasis_coefficient": 0.04, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": false, "num_ceps": 5, "cepstral_lifter": 36.9181, "vtln_high": 7261, "vtln_low": 5703, "vtln_warp": 1.0, "window_type": "blackman", "dither": 0.0} +{"blackman_coeff": 4.4663, "energy_floor": 3.5767, "frame_length": 1.125, "frame_shift": 1.125, "high_freq": 5844, "htk_compat": false, "low_freq": 799, "num_mel_bins": 7, "preemphasis_coefficient": 0.37, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": false, "num_ceps": 4, "cepstral_lifter": 34.2098, "vtln_high": 4554, "vtln_low": 1148, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 4.53, "energy_floor": 3.1492, "frame_length": 1.0625, "frame_shift": 0.375, "high_freq": 7706, "htk_compat": false, "low_freq": 3813, "num_mel_bins": 6, "preemphasis_coefficient": 0.74, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": false, "num_ceps": 2, "cepstral_lifter": 71.8337, "vtln_high": 7672, "vtln_low": 5265, "vtln_warp": 1.0, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 4.5474, "energy_floor": 0.7883, "frame_length": 0.5625, "frame_shift": 1.0, "high_freq": 7283, "htk_compat": false, "low_freq": 2418, "num_mel_bins": 4, "preemphasis_coefficient": 0.68, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": true, "num_ceps": 4, "cepstral_lifter": 70.0635, "vtln_high": 7277, "vtln_low": 7265, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 4.5663, "energy_floor": 1.127, "frame_length": 1.125, "frame_shift": 0.8125, "high_freq": 6069, "htk_compat": true, "low_freq": 167, "num_mel_bins": 8, "preemphasis_coefficient": 0.68, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "use_energy": true, "num_ceps": 6, "cepstral_lifter": 1.624, "vtln_high": 2148, "vtln_low": 461, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 4.5896, "energy_floor": 2.7617, "frame_length": 1.0625, "frame_shift": 0.8125, "high_freq": 3851, "htk_compat": true, "low_freq": 1115, "num_mel_bins": 4, "preemphasis_coefficient": 0.03, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": false, "num_ceps": 4, "cepstral_lifter": 49.7637, "vtln_high": 2897, "vtln_low": 2701, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 4.6128, "energy_floor": 0.1203, "frame_length": 1.1875, "frame_shift": 0.9375, "high_freq": 6901, "htk_compat": false, "low_freq": 3577, "num_mel_bins": 6, "preemphasis_coefficient": 0.25, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": false, "num_ceps": 3, "cepstral_lifter": 70.5509, "vtln_high": 5962, "vtln_low": 4190, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 4.6262, "energy_floor": 4.1656, "frame_length": 1.1875, "frame_shift": 0.8125, "high_freq": 6147, "htk_compat": false, "low_freq": 1684, "num_mel_bins": 6, "preemphasis_coefficient": 0.58, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "use_energy": true, "num_ceps": 5, "cepstral_lifter": 54.2056, "vtln_high": 5259, "vtln_low": 2363, "vtln_warp": 1.0, "window_type": "rectangular", "dither": 0.0} +{"blackman_coeff": 4.6741, "energy_floor": 4.3867, "frame_length": 1.125, "frame_shift": 1.125, "high_freq": 6273, "htk_compat": false, "low_freq": 2481, "num_mel_bins": 4, "preemphasis_coefficient": 0.15, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": false, "use_energy": false, "num_ceps": 4, "cepstral_lifter": 75.6122, "vtln_high": 3701, "vtln_low": 2992, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 4.6765, "energy_floor": 1.2644, "frame_length": 1.125, "frame_shift": 0.75, "high_freq": 5204, "htk_compat": false, "low_freq": 276, "num_mel_bins": 4, "preemphasis_coefficient": 0.04, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": true, "use_energy": false, "num_ceps": 3, "cepstral_lifter": 96.116, "vtln_high": 5148, "vtln_low": 2541, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 4.7216, "energy_floor": 2.4818, "frame_length": 0.8125, "frame_shift": 0.375, "high_freq": 6723, "htk_compat": true, "low_freq": 2352, "num_mel_bins": 6, "preemphasis_coefficient": 0.14, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": false, "num_ceps": 3, "cepstral_lifter": 32.0303, "vtln_high": 5598, "vtln_low": 2579, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 4.7919, "energy_floor": 2.6435, "frame_length": 0.625, "frame_shift": 0.5, "high_freq": 7971, "htk_compat": false, "low_freq": 1812, "num_mel_bins": 4, "preemphasis_coefficient": 0.65, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": false, "subtract_mean": true, "use_energy": false, "num_ceps": 4, "cepstral_lifter": 27.7648, "vtln_high": 7735, "vtln_low": 7419, "vtln_warp": 1.0, "window_type": "povey", "dither": 0.0} +{"blackman_coeff": 4.814, "energy_floor": 0.468, "frame_length": 1.0625, "frame_shift": 0.6875, "high_freq": 5252, "htk_compat": true, "low_freq": 569, "num_mel_bins": 6, "preemphasis_coefficient": 0.85, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "use_energy": false, "num_ceps": 6, "cepstral_lifter": 56.449, "vtln_high": 4397, "vtln_low": 4332, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} +{"blackman_coeff": 4.95, "energy_floor": 4.5916, "frame_length": 1.125, "frame_shift": 1.0625, "high_freq": 5044, "htk_compat": true, "low_freq": 617, "num_mel_bins": 8, "preemphasis_coefficient": 0.89, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": false, "subtract_mean": true, "use_energy": true, "num_ceps": 8, "cepstral_lifter": 23.3238, "vtln_high": 2732, "vtln_low": 2677, "vtln_warp": 1.0, "window_type": "hanning", "dither": 0.0} +{"blackman_coeff": 4.9663, "energy_floor": 4.7867, "frame_length": 1.1875, "frame_shift": 0.5, "high_freq": 2424, "htk_compat": false, "low_freq": 350, "num_mel_bins": 4, "preemphasis_coefficient": 0.39, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "use_energy": true, "num_ceps": 2, "cepstral_lifter": 59.4319, "vtln_high": 1202, "vtln_low": 1063, "vtln_warp": 1.0, "window_type": "hamming", "dither": 0.0} diff --git a/test/torchaudio_unittest/assets/kaldi_test_pitch_args.jsonl b/test/torchaudio_unittest/assets/kaldi_test_pitch_args.jsonl new file mode 100644 index 00000000..9844bd6c --- /dev/null +++ b/test/torchaudio_unittest/assets/kaldi_test_pitch_args.jsonl @@ -0,0 +1,5 @@ +{"sample_rate": 8000} +{"sample_rate": 8000, "frames_per_chunk": 200} +{"sample_rate": 8000, "frames_per_chunk": 200, "simulate_first_pass_online": true} +{"sample_rate": 16000} +{"sample_rate": 44100} diff --git a/test/torchaudio_unittest/assets/kaldi_test_spectrogram_args.jsonl b/test/torchaudio_unittest/assets/kaldi_test_spectrogram_args.jsonl new file mode 100644 index 00000000..c44245b6 --- /dev/null +++ b/test/torchaudio_unittest/assets/kaldi_test_spectrogram_args.jsonl @@ -0,0 +1,109 @@ +{"blackman_coeff": 0.0016, "dither": 0, "energy_floor": 4.668, "frame_length": 0.625, "frame_shift": 0.25, "preemphasis_coefficient": 0.82, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": false, "subtract_mean": false, "window_type": "povey"} +{"blackman_coeff": 0.0121, "dither": 0, "energy_floor": 4.9643, "frame_length": 0.875, "frame_shift": 0.1875, "preemphasis_coefficient": 0.98, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": false, "window_type": "rectangular"} +{"blackman_coeff": 0.0378, "dither": 0, "energy_floor": 3.777, "frame_length": 0.5, "frame_shift": 0.625, "preemphasis_coefficient": 0.76, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "window_type": "blackman"} +{"blackman_coeff": 0.0545, "dither": 0, "energy_floor": 0.0732, "frame_length": 1.0, "frame_shift": 0.75, "preemphasis_coefficient": 0.81, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "window_type": "hanning"} +{"blackman_coeff": 0.1005, "dither": 0, "energy_floor": 0.3739, "frame_length": 0.5625, "frame_shift": 0.625, "preemphasis_coefficient": 0.19, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "window_type": "blackman"} +{"blackman_coeff": 0.1088, "dither": 0, "energy_floor": 0.6933, "frame_length": 0.5, "frame_shift": 0.75, "preemphasis_coefficient": 0.51, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": false, "window_type": "povey"} +{"blackman_coeff": 0.1777, "dither": 0, "energy_floor": 3.8992, "frame_length": 1.0, "frame_shift": 0.3125, "preemphasis_coefficient": 0.96, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": false, "window_type": "blackman"} +{"blackman_coeff": 0.2384, "dither": 0, "energy_floor": 0.308, "frame_length": 0.375, "frame_shift": 0.25, "preemphasis_coefficient": 0.98, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": false, "subtract_mean": true, "window_type": "povey"} +{"blackman_coeff": 0.2669, "dither": 0, "energy_floor": 2.4329, "frame_length": 0.625, "frame_shift": 1.1875, "preemphasis_coefficient": 0.18, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": false, "window_type": "blackman"} +{"blackman_coeff": 0.334, "dither": 0, "energy_floor": 0.5962, "frame_length": 0.25, "frame_shift": 0.5625, "preemphasis_coefficient": 0.38, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "window_type": "hamming"} +{"blackman_coeff": 0.4268, "dither": 0, "energy_floor": 2.4431, "frame_length": 0.5625, "frame_shift": 0.0625, "preemphasis_coefficient": 0.95, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "window_type": "hamming"} +{"blackman_coeff": 0.4774, "dither": 0, "energy_floor": 0.6982, "frame_length": 1.125, "frame_shift": 1.125, "preemphasis_coefficient": 0.27, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": true, "window_type": "povey"} +{"blackman_coeff": 0.4992, "dither": 0, "energy_floor": 3.7665, "frame_length": 0.4375, "frame_shift": 1.125, "preemphasis_coefficient": 0.42, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "window_type": "rectangular"} +{"blackman_coeff": 0.544, "dither": 0, "energy_floor": 1.6641, "frame_length": 0.9375, "frame_shift": 0.875, "preemphasis_coefficient": 0.13, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "window_type": "rectangular"} +{"blackman_coeff": 0.5785, "dither": 0, "energy_floor": 2.8162, "frame_length": 1.125, "frame_shift": 1.0625, "preemphasis_coefficient": 0.17, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": false, "window_type": "blackman"} +{"blackman_coeff": 0.8072, "dither": 0, "energy_floor": 4.0404, "frame_length": 0.5, "frame_shift": 1.1875, "preemphasis_coefficient": 0.74, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": true, "window_type": "hamming"} +{"blackman_coeff": 0.8418, "dither": 0, "energy_floor": 4.1771, "frame_length": 0.3125, "frame_shift": 0.25, "preemphasis_coefficient": 0.48, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "window_type": "hanning"} +{"blackman_coeff": 0.8431, "dither": 0, "energy_floor": 0.0728, "frame_length": 0.75, "frame_shift": 0.8125, "preemphasis_coefficient": 0.1, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "window_type": "povey"} +{"blackman_coeff": 0.885, "dither": 0, "energy_floor": 3.9292, "frame_length": 0.375, "frame_shift": 0.75, "preemphasis_coefficient": 0.27, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": false, "window_type": "blackman"} +{"blackman_coeff": 0.9625, "dither": 0, "energy_floor": 2.5481, "frame_length": 0.6875, "frame_shift": 1.0, "preemphasis_coefficient": 0.06, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "window_type": "blackman"} +{"blackman_coeff": 0.9826, "dither": 0, "energy_floor": 0.7377, "frame_length": 0.375, "frame_shift": 0.6875, "preemphasis_coefficient": 0.7, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "window_type": "rectangular"} +{"blackman_coeff": 0.9854, "dither": 0, "energy_floor": 3.8819, "frame_length": 0.25, "frame_shift": 1.0, "preemphasis_coefficient": 0.54, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": false, "subtract_mean": true, "window_type": "povey"} +{"blackman_coeff": 1.0303, "dither": 0, "energy_floor": 4.4583, "frame_length": 0.375, "frame_shift": 0.875, "preemphasis_coefficient": 0.39, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": false, "window_type": "blackman"} +{"blackman_coeff": 1.0743, "dither": 0, "energy_floor": 0.4642, "frame_length": 1.125, "frame_shift": 0.625, "preemphasis_coefficient": 0.39, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "window_type": "hanning"} +{"blackman_coeff": 1.0788, "dither": 0, "energy_floor": 1.442, "frame_length": 0.1875, "frame_shift": 0.3125, "preemphasis_coefficient": 0.53, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "window_type": "hanning"} +{"blackman_coeff": 1.0816, "dither": 0, "energy_floor": 0.205, "frame_length": 0.1875, "frame_shift": 0.6875, "preemphasis_coefficient": 0.02, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "window_type": "hamming"} +{"blackman_coeff": 1.1385, "dither": 0, "energy_floor": 4.738, "frame_length": 0.625, "frame_shift": 0.3125, "preemphasis_coefficient": 0.23, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": false, "subtract_mean": false, "window_type": "hanning"} +{"blackman_coeff": 1.3142, "dither": 0, "energy_floor": 4.8914, "frame_length": 0.875, "frame_shift": 0.1875, "preemphasis_coefficient": 0.34, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": false, "snip_edges": false, "subtract_mean": false, "window_type": "blackman"} +{"blackman_coeff": 1.3189, "dither": 0, "energy_floor": 3.683, "frame_length": 1.125, "frame_shift": 1.125, "preemphasis_coefficient": 0.88, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": false, "subtract_mean": true, "window_type": "hamming"} +{"blackman_coeff": 1.3235, "dither": 0, "energy_floor": 3.8538, "frame_length": 0.25, "frame_shift": 1.0625, "preemphasis_coefficient": 0.07, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "window_type": "blackman"} +{"blackman_coeff": 1.3389, "dither": 0, "energy_floor": 1.6152, "frame_length": 0.375, "frame_shift": 0.5, "preemphasis_coefficient": 0.21, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": false, "window_type": "blackman"} +{"blackman_coeff": 1.3887, "dither": 0, "energy_floor": 3.3198, "frame_length": 0.375, "frame_shift": 0.125, "preemphasis_coefficient": 0.14, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "window_type": "hanning"} +{"blackman_coeff": 1.4127, "dither": 0, "energy_floor": 2.6264, "frame_length": 0.875, "frame_shift": 0.375, "preemphasis_coefficient": 0.69, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "window_type": "povey"} +{"blackman_coeff": 1.5178, "dither": 0, "energy_floor": 2.8631, "frame_length": 1.0, "frame_shift": 0.8125, "preemphasis_coefficient": 0.95, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "window_type": "blackman"} +{"blackman_coeff": 1.5403, "dither": 0, "energy_floor": 0.0133, "frame_length": 1.1875, "frame_shift": 0.25, "preemphasis_coefficient": 0.59, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "window_type": "blackman"} +{"blackman_coeff": 1.5754, "dither": 0, "energy_floor": 0.954, "frame_length": 1.0, "frame_shift": 0.9375, "preemphasis_coefficient": 0.2, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "window_type": "rectangular"} +{"blackman_coeff": 1.5959, "dither": 0, "energy_floor": 0.9033, "frame_length": 0.75, "frame_shift": 1.0, "preemphasis_coefficient": 0.14, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "window_type": "hanning"} +{"blackman_coeff": 1.6923, "dither": 0, "energy_floor": 3.5626, "frame_length": 0.6875, "frame_shift": 1.0625, "preemphasis_coefficient": 0.27, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "window_type": "rectangular"} +{"blackman_coeff": 1.6972, "dither": 0, "energy_floor": 1.0863, "frame_length": 1.1875, "frame_shift": 0.875, "preemphasis_coefficient": 0.86, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "window_type": "rectangular"} +{"blackman_coeff": 1.744, "dither": 0, "energy_floor": 0.5308, "frame_length": 0.5, "frame_shift": 0.125, "preemphasis_coefficient": 0.33, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": false, "snip_edges": false, "subtract_mean": false, "window_type": "hamming"} +{"blackman_coeff": 1.7642, "dither": 0, "energy_floor": 0.4833, "frame_length": 0.25, "frame_shift": 0.8125, "preemphasis_coefficient": 0.94, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": false, "window_type": "blackman"} +{"blackman_coeff": 1.8072, "dither": 0, "energy_floor": 0.8085, "frame_length": 0.5, "frame_shift": 0.25, "preemphasis_coefficient": 0.96, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "window_type": "hanning"} +{"blackman_coeff": 1.8836, "dither": 0, "energy_floor": 4.5145, "frame_length": 0.875, "frame_shift": 1.0625, "preemphasis_coefficient": 0.4, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "window_type": "hanning"} +{"blackman_coeff": 1.8946, "dither": 0, "energy_floor": 4.1442, "frame_length": 0.3125, "frame_shift": 0.875, "preemphasis_coefficient": 0.73, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "window_type": "rectangular"} +{"blackman_coeff": 1.8988, "dither": 0, "energy_floor": 3.0931, "frame_length": 1.0625, "frame_shift": 0.3125, "preemphasis_coefficient": 0.35, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "window_type": "hanning"} +{"blackman_coeff": 1.9501, "dither": 0, "energy_floor": 4.3519, "frame_length": 0.4375, "frame_shift": 0.25, "preemphasis_coefficient": 0.61, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "window_type": "hanning"} +{"blackman_coeff": 2.0137, "dither": 0, "energy_floor": 3.1007, "frame_length": 0.625, "frame_shift": 1.0625, "preemphasis_coefficient": 0.67, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "window_type": "rectangular"} +{"blackman_coeff": 2.0175, "dither": 0, "energy_floor": 2.9099, "frame_length": 1.0, "frame_shift": 0.5625, "preemphasis_coefficient": 0.28, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "window_type": "blackman"} +{"blackman_coeff": 2.1114, "dither": 0, "energy_floor": 4.5618, "frame_length": 0.25, "frame_shift": 0.875, "preemphasis_coefficient": 0.61, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": false, "snip_edges": false, "subtract_mean": false, "window_type": "povey"} +{"blackman_coeff": 2.1472, "dither": 0, "energy_floor": 0.2, "frame_length": 1.125, "frame_shift": 0.875, "preemphasis_coefficient": 0.58, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": true, "window_type": "hamming"} +{"blackman_coeff": 2.1947, "dither": 0, "energy_floor": 1.8065, "frame_length": 0.875, "frame_shift": 0.75, "preemphasis_coefficient": 0.45, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": false, "snip_edges": false, "subtract_mean": false, "window_type": "povey"} +{"blackman_coeff": 2.2457, "dither": 0, "energy_floor": 1.704, "frame_length": 0.75, "frame_shift": 0.5625, "preemphasis_coefficient": 0.98, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": false, "subtract_mean": true, "window_type": "hamming"} +{"blackman_coeff": 2.2893, "dither": 0, "energy_floor": 1.0286, "frame_length": 0.25, "frame_shift": 0.5, "preemphasis_coefficient": 0.8, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "window_type": "hanning"} +{"blackman_coeff": 2.3371, "dither": 0, "energy_floor": 4.4192, "frame_length": 0.8125, "frame_shift": 0.625, "preemphasis_coefficient": 0.3, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "window_type": "rectangular"} +{"blackman_coeff": 2.3831, "dither": 0, "energy_floor": 4.8325, "frame_length": 0.25, "frame_shift": 1.125, "preemphasis_coefficient": 0.34, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "window_type": "povey"} +{"blackman_coeff": 2.423, "dither": 0, "energy_floor": 0.6363, "frame_length": 0.875, "frame_shift": 0.3125, "preemphasis_coefficient": 0.77, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "window_type": "hanning"} +{"blackman_coeff": 2.4378, "dither": 0, "energy_floor": 1.4617, "frame_length": 0.9375, "frame_shift": 0.375, "preemphasis_coefficient": 0.53, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "window_type": "rectangular"} +{"blackman_coeff": 2.4454, "dither": 0, "energy_floor": 1.936, "frame_length": 1.0, "frame_shift": 0.9375, "preemphasis_coefficient": 0.66, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": false, "snip_edges": false, "subtract_mean": false, "window_type": "rectangular"} +{"blackman_coeff": 2.448, "dither": 0, "energy_floor": 3.8782, "frame_length": 0.5625, "frame_shift": 1.125, "preemphasis_coefficient": 0.1, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "window_type": "rectangular"} +{"blackman_coeff": 2.5164, "dither": 0, "energy_floor": 2.7455, "frame_length": 0.875, "frame_shift": 0.9375, "preemphasis_coefficient": 0.55, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": false, "snip_edges": false, "subtract_mean": false, "window_type": "hanning"} +{"blackman_coeff": 2.5316, "dither": 0, "energy_floor": 2.3286, "frame_length": 0.75, "frame_shift": 0.75, "preemphasis_coefficient": 0.61, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": false, "subtract_mean": true, "window_type": "rectangular"} +{"blackman_coeff": 2.5487, "dither": 0, "energy_floor": 3.8457, "frame_length": 1.1875, "frame_shift": 0.9375, "preemphasis_coefficient": 0.63, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "window_type": "hanning"} +{"blackman_coeff": 2.6121, "dither": 0, "energy_floor": 4.3165, "frame_length": 0.6875, "frame_shift": 1.1875, "preemphasis_coefficient": 0.19, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "window_type": "rectangular"} +{"blackman_coeff": 2.6988, "dither": 0, "energy_floor": 2.3417, "frame_length": 1.0, "frame_shift": 0.6875, "preemphasis_coefficient": 0.38, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "window_type": "rectangular"} +{"blackman_coeff": 2.7457, "dither": 0, "energy_floor": 1.3662, "frame_length": 0.25, "frame_shift": 0.875, "preemphasis_coefficient": 0.74, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": true, "window_type": "blackman"} +{"blackman_coeff": 2.8577, "dither": 0, "energy_floor": 4.1431, "frame_length": 0.375, "frame_shift": 1.0, "preemphasis_coefficient": 1.0, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "window_type": "hamming"} +{"blackman_coeff": 2.8693, "dither": 0, "energy_floor": 4.3801, "frame_length": 0.75, "frame_shift": 1.0, "preemphasis_coefficient": 0.95, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "window_type": "rectangular"} +{"blackman_coeff": 2.8888, "dither": 0, "energy_floor": 0.4078, "frame_length": 0.3125, "frame_shift": 0.625, "preemphasis_coefficient": 0.25, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "window_type": "hanning"} +{"blackman_coeff": 2.9074, "dither": 0, "energy_floor": 1.6849, "frame_length": 1.125, "frame_shift": 0.625, "preemphasis_coefficient": 0.79, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "window_type": "blackman"} +{"blackman_coeff": 2.9303, "dither": 0, "energy_floor": 3.5172, "frame_length": 0.5, "frame_shift": 0.5, "preemphasis_coefficient": 0.04, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "window_type": "rectangular"} +{"blackman_coeff": 3.07, "dither": 0, "energy_floor": 3.5254, "frame_length": 0.75, "frame_shift": 0.875, "preemphasis_coefficient": 0.96, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": false, "window_type": "povey"} +{"blackman_coeff": 3.1297, "dither": 0, "energy_floor": 0.3513, "frame_length": 0.4375, "frame_shift": 0.3125, "preemphasis_coefficient": 0.2, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "window_type": "povey"} +{"blackman_coeff": 3.2523, "dither": 0, "energy_floor": 3.5376, "frame_length": 0.3125, "frame_shift": 0.25, "preemphasis_coefficient": 0.46, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "window_type": "rectangular"} +{"blackman_coeff": 3.3896, "dither": 0, "energy_floor": 0.4666, "frame_length": 1.125, "frame_shift": 0.25, "preemphasis_coefficient": 0.05, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": false, "snip_edges": false, "subtract_mean": false, "window_type": "rectangular"} +{"blackman_coeff": 3.537, "dither": 0, "energy_floor": 1.7032, "frame_length": 0.375, "frame_shift": 0.875, "preemphasis_coefficient": 0.17, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "window_type": "hamming"} +{"blackman_coeff": 3.5378, "dither": 0, "energy_floor": 3.6594, "frame_length": 0.25, "frame_shift": 0.625, "preemphasis_coefficient": 0.54, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "window_type": "rectangular"} +{"blackman_coeff": 3.5847, "dither": 0, "energy_floor": 3.6357, "frame_length": 1.0, "frame_shift": 0.3125, "preemphasis_coefficient": 0.79, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": false, "window_type": "povey"} +{"blackman_coeff": 3.6057, "dither": 0, "energy_floor": 1.6902, "frame_length": 1.0625, "frame_shift": 0.6875, "preemphasis_coefficient": 0.65, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "window_type": "blackman"} +{"blackman_coeff": 3.6498, "dither": 0, "energy_floor": 0.2005, "frame_length": 0.9375, "frame_shift": 1.125, "preemphasis_coefficient": 0.37, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "window_type": "rectangular"} +{"blackman_coeff": 3.6648, "dither": 0, "energy_floor": 4.6742, "frame_length": 0.625, "frame_shift": 1.1875, "preemphasis_coefficient": 0.88, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": true, "window_type": "blackman"} +{"blackman_coeff": 3.6701, "dither": 0, "energy_floor": 3.7451, "frame_length": 0.8125, "frame_shift": 0.25, "preemphasis_coefficient": 0.19, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "window_type": "blackman"} +{"blackman_coeff": 3.7232, "dither": 0, "energy_floor": 0.4912, "frame_length": 0.375, "frame_shift": 0.875, "preemphasis_coefficient": 0.34, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": false, "snip_edges": false, "subtract_mean": true, "window_type": "rectangular"} +{"blackman_coeff": 3.7605, "dither": 0, "energy_floor": 1.6813, "frame_length": 0.25, "frame_shift": 0.5625, "preemphasis_coefficient": 0.27, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "window_type": "rectangular"} +{"blackman_coeff": 3.7759, "dither": 0, "energy_floor": 1.7002, "frame_length": 1.0625, "frame_shift": 0.6875, "preemphasis_coefficient": 0.42, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "window_type": "hamming"} +{"blackman_coeff": 3.7921, "dither": 0, "energy_floor": 3.4087, "frame_length": 0.25, "frame_shift": 1.0, "preemphasis_coefficient": 0.54, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "window_type": "blackman"} +{"blackman_coeff": 3.7954, "dither": 0, "energy_floor": 3.5651, "frame_length": 0.5, "frame_shift": 0.8125, "preemphasis_coefficient": 0.06, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "window_type": "blackman"} +{"blackman_coeff": 3.799, "dither": 0, "energy_floor": 3.0026, "frame_length": 0.625, "frame_shift": 1.0, "preemphasis_coefficient": 0.82, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": true, "window_type": "hamming"} +{"blackman_coeff": 3.8659, "dither": 0, "energy_floor": 1.7487, "frame_length": 1.1875, "frame_shift": 0.375, "preemphasis_coefficient": 1.0, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "window_type": "hanning"} +{"blackman_coeff": 3.951, "dither": 0, "energy_floor": 0.3903, "frame_length": 1.125, "frame_shift": 1.0, "preemphasis_coefficient": 0.41, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": true, "window_type": "rectangular"} +{"blackman_coeff": 4.0045, "dither": 0, "energy_floor": 3.061, "frame_length": 0.625, "frame_shift": 1.0625, "preemphasis_coefficient": 0.74, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "window_type": "hanning"} +{"blackman_coeff": 4.0187, "dither": 0, "energy_floor": 4.8148, "frame_length": 0.375, "frame_shift": 0.6875, "preemphasis_coefficient": 0.68, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": false, "snip_edges": false, "subtract_mean": false, "window_type": "hanning"} +{"blackman_coeff": 4.032, "dither": 0, "energy_floor": 2.2019, "frame_length": 1.125, "frame_shift": 0.25, "preemphasis_coefficient": 0.78, "raw_energy": true, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "window_type": "rectangular"} +{"blackman_coeff": 4.0627, "dither": 0, "energy_floor": 4.1729, "frame_length": 0.625, "frame_shift": 1.125, "preemphasis_coefficient": 0.89, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": false, "snip_edges": false, "subtract_mean": false, "window_type": "povey"} +{"blackman_coeff": 4.0736, "dither": 0, "energy_floor": 0.9155, "frame_length": 1.0625, "frame_shift": 0.5625, "preemphasis_coefficient": 0.82, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "window_type": "hamming"} +{"blackman_coeff": 4.1131, "dither": 0, "energy_floor": 3.9204, "frame_length": 0.5, "frame_shift": 0.125, "preemphasis_coefficient": 0.39, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "window_type": "hanning"} +{"blackman_coeff": 4.1816, "dither": 0, "energy_floor": 1.665, "frame_length": 0.8125, "frame_shift": 0.375, "preemphasis_coefficient": 0.37, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "window_type": "rectangular"} +{"blackman_coeff": 4.1897, "dither": 0, "energy_floor": 1.2668, "frame_length": 0.1875, "frame_shift": 0.625, "preemphasis_coefficient": 0.74, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "window_type": "hamming"} +{"blackman_coeff": 4.2217, "dither": 0, "energy_floor": 3.6775, "frame_length": 0.3125, "frame_shift": 0.125, "preemphasis_coefficient": 0.01, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "window_type": "hamming"} +{"blackman_coeff": 4.2785, "dither": 0, "energy_floor": 0.7201, "frame_length": 0.8125, "frame_shift": 0.8125, "preemphasis_coefficient": 0.3, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "window_type": "hanning"} +{"blackman_coeff": 4.3304, "dither": 0, "energy_floor": 1.0538, "frame_length": 0.875, "frame_shift": 1.125, "preemphasis_coefficient": 0.92, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": false, "window_type": "povey"} +{"blackman_coeff": 4.3942, "dither": 0, "energy_floor": 3.9813, "frame_length": 0.75, "frame_shift": 0.6875, "preemphasis_coefficient": 0.27, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "window_type": "blackman"} +{"blackman_coeff": 4.4432, "dither": 0, "energy_floor": 2.0441, "frame_length": 0.5, "frame_shift": 0.6875, "preemphasis_coefficient": 0.77, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "window_type": "hanning"} +{"blackman_coeff": 4.4459, "dither": 0, "energy_floor": 0.5135, "frame_length": 0.25, "frame_shift": 0.1875, "preemphasis_coefficient": 0.29, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "window_type": "hanning"} +{"blackman_coeff": 4.5486, "dither": 0, "energy_floor": 1.3248, "frame_length": 0.1875, "frame_shift": 1.125, "preemphasis_coefficient": 0.91, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": false, "subtract_mean": true, "window_type": "hanning"} +{"blackman_coeff": 4.5535, "dither": 0, "energy_floor": 2.1772, "frame_length": 0.4375, "frame_shift": 0.875, "preemphasis_coefficient": 0.21, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "window_type": "hanning"} +{"blackman_coeff": 4.5835, "dither": 0, "energy_floor": 0.3781, "frame_length": 0.875, "frame_shift": 0.875, "preemphasis_coefficient": 0.04, "raw_energy": true, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": true, "window_type": "hamming"} +{"blackman_coeff": 4.6297, "dither": 0, "energy_floor": 2.49, "frame_length": 0.5, "frame_shift": 0.25, "preemphasis_coefficient": 0.03, "raw_energy": false, "remove_dc_offset": false, "round_to_power_of_two": true, "snip_edges": true, "subtract_mean": false, "window_type": "rectangular"} +{"blackman_coeff": 4.6749, "dither": 0, "energy_floor": 4.8853, "frame_length": 0.25, "frame_shift": 0.25, "preemphasis_coefficient": 0.48, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": false, "snip_edges": false, "subtract_mean": false, "window_type": "povey"} +{"blackman_coeff": 4.6971, "dither": 0, "energy_floor": 1.3632, "frame_length": 0.875, "frame_shift": 0.9375, "preemphasis_coefficient": 0.44, "raw_energy": false, "remove_dc_offset": true, "round_to_power_of_two": false, "snip_edges": true, "subtract_mean": false, "window_type": "blackman"} diff --git a/test/torchaudio_unittest/assets/mat.ark b/test/torchaudio_unittest/assets/mat.ark new file mode 100644 index 0000000000000000000000000000000000000000..d50ef9d5797b76878b01b21c7f94b7f7e39b3568 GIT binary patch literal 112 zcmc~!tu$0%aB}liU}0upU;q;g3=Q@`wgV6Wv4JCy2I2r9UID}#fOv;vc50;&roJCQ ZIR+=7F+i*W#2P@X1H?W+900^2P5_s(55@oh literal 0 HcmV?d00001 diff --git a/test/torchaudio_unittest/assets/mp3_without_ext b/test/torchaudio_unittest/assets/mp3_without_ext new file mode 100644 index 0000000000000000000000000000000000000000..e4d4e6973e1cd0d8262ad3c88edbcc0464bf4663 GIT binary patch literal 15453 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 DHUU%3 literal 0 HcmV?d00001 diff --git a/test/torchaudio_unittest/assets/sinewave.wav b/test/torchaudio_unittest/assets/sinewave.wav new file mode 100644 index 0000000000000000000000000000000000000000..93182c4eba4e6349555d773fecfdefb5a47e0926 GIT binary patch literal 256080 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%0Djod@8BE{+k)r0 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 z zj`1A&uI08DmvX1hRP&yd4Ue`gH0<4HZHDI^vEt?dKkt<2$`8Ho$+)}wk(2AC^a2mF zYS_Pe3-@a=+_U+i{&cA;!&4_t2$ky0$ zlmz$q!t;4(kh2=^SP!`|a8929er^wQqkG{>^6#%2_7(I47J?G!-zy{TC)EZ0V{E>% z3-)fG13wog+A&u_f9_c)X1XQ&;6<|}DA!Mh
8;2)2HpKtb+(d*MBc)P-fchQN_ zF!0No2)1$E0D2bq$B)i|bdLu1RMtvaAMg(w^VpNB4!UM#WIBk3?6guIwJ@8p5BRwW-+=Ex z{<&X8De3V=%dR`X)B9M36b(7l1m=xJFu$7iDF=s{s!%V?^S{frR1jOrrHQN2hKPn4 z|GaJu3y(GpNoW+3o}fR^1Ab1zJ()3&d`bUMDgFWce4a-j0{uBngnpK+^GExm8CLKY zWR~o9=XH8V(Gci=jnU?!__xNAj9@*D`clKqtt>~!l8Ts9z|Yy~kUwx0&PL!LW$;|v zjLAVe!RM{8Mh)3A`TV!-#Y|mu1wTYsm*u~-8OOtZd-szT+!Av;X1cXA)4TyZ$AO=p z6iLw6GJl*8`;Cy(M%saYtezsHL(WUs@FpMbR-^}xws&FN7<=y6BTL()5EF&Zp%&J` zr=DInt)gAll%TZZ1@yuJ4IRBhi5Kt4!NAXHM}FKpm_rEOsz{&7dA!Gc=n1OIL;)E( z@~eH5-u`e~{D8p%yymer_o`gP^aXzIcGwFYdG3d4yOe5s1RxCj1M=*!)GB}mKbEqc zqy3nj{$Bid2RHUJ@bdv)*70tq%=X$F4Wj1djobs^=e99r$YE3=+gi!v#w{xP4D@gZ zz!xwN`1$NhIb>3XlDd{_$SDiR{|5i|C*UXe{&BsiXa82Z?2`$nBDNj8$Wx-5XDV@(hhYD{2WPG{!v>c zK`x+Iq2E2Y*d!N{bH`qE;ZI@MzBeX{tj?{p<)3=I9`xs$+7e`DD8PsFHMq+YC232` zK^^?O5A5&S`sSho+g0518nE3DFC=ZCr%5uYNW0=PzXV@o9-(c*$NjE=MI29sS2T?Q=K5 zk*C1V6NF8;{I-sXC@sV9Zxu3yVR>{K%sX!Z|44`VRfiAfJ*Xe5ZA#pDOv9c2yMQ0E zs)Q-EtP1%UT>o>`=@$9Mz9utX0Y5(s{9Nwr!dAh&wWY;}xLuUck-$G7Pm_ki{OZ*m z=tDr~bWbS3m%I9)xHV#~ueq}>uiZ9b_A!eGT`W+EYHo8xT5~P;rM`mwC!v^Cf6S+^ zVII8HQHf-u{^RGJfPef1ejfUVnx6VQQ4Q?tYF9 z6J&H`l6JZyW0W^mf`9vS*uQ$c20R1cykVJF=O|CA7rQcV)3&|1uT-`K~qnb9e*S;#JL>!hBp2rNtZiLH>EG605<#{R-S;6)+F+g}M4*;OFyz zpLbGe8IRIZe%85aUao8qY33Tn6`Pu6HSD#L7yfXdPmj7XXC8QxL?`e}-XO(2@?>Za z)Q`(hKNd^{?;_ymX&ww44*s)eEZzC2C64UMa2syUCqdcK?naV&vMJ=_@EYdL+;SWY z`tv)fdH7#DHK|;eOBnE(0)9@S7Ua;lu}b<4bbPiw3m7Y7G0u#yWcD7f(}}EGa#o#0 z`t!igzaO>Dl^b)_!*#p1QUY#kLo-p}h*ov*8-FFAVm-ppGqq&%?K7ahDQ>m%Bc``KN#7(_k=bhp%x<@`^=LH>I}>R@f$9dNPpzhV zK*xFVUoBs7n5V@5C;u+KpWj%Y4J!;ru;hWR`!?T1;EeGK>rH-oI1t_pbX&WmlrY2J=U9B zu-P3AZgNBml{R5+3(Q4(GL4!44ALuhX4f!}%gdR7V?}(7AfGOqrlu1w!mcIsAHdIX z^q3sd2lVH8wQBw@aKA}AiR)YkdRBXNFNINb2D3K&LLjRw)OMD zNsba+SmjTK{KwB}FW`+2B4lV%s)ToK^dZCl@<4SiE{f1j_RN+KmWP}IOv3soTHx%i zXN;%{r9%Gs%;W<40{q+g%}QdrBL{Wx^VLv4ei@&OcfC?E-$7Tr`9UF6D_J_5!I^vDnmz$F}aBXLK(n2Rs`&>rNE=kx^4Ae=)pU>?E=1F)P*(cwjY zxQo;a4Yzkg&XYxo*&f#1iNj{!q~PD~wXBh84$-kIqRaRzz|XTQASW95`EFm}4d1f~ z?5aEU!o(h}p^Z5vA!8$jS^aL?GnGeO*rne* zka3DH?0`z>)SWVH3Ui|5xq&!3DS-5Y`f=$ogBoAKK5er*Y24t%aFBn#+GwsgG`F4q z_C=3IM%UsW;NN}&xVT4bJ}F97<1WC@oxA>zpQGncKi1^u;+9l3(<>JCvz&^-Bd;Q2 z-qP9-!xz2sx7c>Y-$rxvD$bU8Y;@wLnuz(B58mYYbB27l>rW$re{}G31L!|~-z=jx zRT8#viZA``n+Hzc@4`FXwhxg-3J-CwP1v{|txUw7dS){Gd(%ow&~e!d9m#|Ypb-M6S1Kmgo7z|VKc%M?C6bfV$Q8t;W04EVQ`X8dcZH9i_C;@o}R zI8kRW{v!M?o8kBA1$z#VS5MBs?^e?tw8O}sJM_YjPCDYnpF8A+o@zzu^|!5O6bB0; zz8-0lr&%S1J+_o=cX&{$4CRM>ach=;&QalzmvkuSzZ; z8LLasNvkTwW?6lSD!GN44L1q9_`#CS>ST{xU0vwmg&zDb@Z8P={W+R0Lj(Tf=cpg> zz2&ecvdvM#U+Ce(?3Rm}-Db|z(P*0#pJ<^y+tMzKTiDG0Y^r4g>nfNJo1lOHkWbzN zKkwwC#D_=cVBqJd4&JwT;O7BQ5lc~)|Mjs83E^RZuy{9t%*4=8Og z3-7V-E_W2Zz?;xT4E_r5d9Sqr_!pQvECl`et!pxTxwn-0dfk_M3+KZ$2UlV?+=0h> zVb&VfG^FTht4JT#zzy}RW|uE2C5cNx8#s(74v_z0`Ys0nKc^l1{B%U)s4NCdyHFGR!7y)dxZ7rEW$}n%bm$KDmY)9$n6z_b#Fd zz|VEIYCIeC-8pd2hkYmV1GvN0iAwSV^yia%6)?Noi)r4L%8=f>>q1SMn=R*^Lh?QR zEYr^<*&*XWXHM8C=C&sKkSTK{G+HLZk3e_c;a~C)>c|S%#~L?V!gW6avye(LSH06& zckQTM+NM61dv6~`S#hW(YhXehP6d9xsIi!-?E(D<@bfuil_Ul}M@Jq>pdlNzK>b+Y zq-H;*=FzZ#A}WX}Pro*$MihLZ>Fw11M%FG{z@>!SkX_*4?(ObQqo961@nA?-;2+nf z2cUnTzV3kUDPAo@!E2;2N-?~_SS<&59<9j*n22}{Rg#~ifI17a(vI8FuXS!#N7@PL;p@)E< zAFlSNQ4jvd&q)#7*ZRT!RpdDdn^*6{X2E^V*Tw}c4zLe8_0DqHw7w>~564>MJEZmC z1yO}}FD_w3pg(_jNJBotJ@MG~9MqvdzYX`tr{i;J#yb_$7M91B-YO(Z!RP3o*E-!l z!X{FDv@K#=KS5UgU0|`GKX*UjMwXuNVxMIAp=rR+v!Q+fKPQd@1E~+xkDU*tWOb|` zJqG-I7vvTReivm~cCk*le$Z@Cs?MMoEN@Kj*Ih@H^UL_~5rxdm8lH~atfI&lJno_Y zD1r0Q67(Nd&z1OMzJ^IR)w1p2-~KSADr@|``sj_lT7AcTH6dN2glzjAdv;%v3)iId zV7mf8HwXUFv`vOi!~QPtbJCOy-W+iMPXiw+>-XO5ZOC=XUG60R5NrGMfWbU6bx!+? zEwAQZ(>Rj>!xJSqS?wc+-L{gB8Z(LT>xJCK=5oh4+ zpg->$Q%m`8dfQ=f?RsQwk)E`~mcH5KM1Gix`Sg$8=o#?yR;XXEj|ULo=jg6kAbJ4& zLslt4cYpKci@tbp4$wa>x@4a1A7MVVP9?7P8IJ|#dE=<3OV0pW%5%4by$nEJ zI9|YpL%%!8ri%3OsSo<~P>Ya;n1t5;vl6``DEbd+os5{MfLyq zImx*jfL=nMecndGo`t?UXM>ninK&zk8En%J9kw`sN8cXmG`pDBOK51}}D?tvlZs>ljft(WYeYJHg(^!A3qmxhbSIq6R;P z`A;nP5DvPShj)t9=oRquK`q$?_&FIcKL=-zQzCVkhDq*SfNe}l(4lRWIv zrFUWOw4}df#L9zqBog%Jy+D7Sd%_1L06))y{VUOb`~%O2Io1b189FvY!Z|1V5JRmP zjo#rbFWzGpn$pYi%G?x`w!_$rHXf*BW^aP|RedotrAq;Rf+1fN=4TdBIdnXH9>>;f zatP*Y6P(p7eiiG22YK|za1}ZPbH>%cKYl>n|LbE8y$$+vyCFO=TcD-amz1(=0;~DY;~JQ_ z53Mc^z|R8>LX)&Qk9EhfO{k-XM-Pf4*eB)dcg%X z?EV6?vMXlyt*wY?Ur{TZ{y|@UB&uChVq>A(wAL1_-R#6p2hP&;3Hpzx3=X>CkAs1K zbm-6Dfqu{f{2d?ZBy0lk^CLe!_{Do%KcRk1ljc&-1QpJy=lOlW&tJxtrE|S?^6*WKU8SgPM(5LJ z5t{<7xyacfyxY-@MFC!vU+jk*fPY+rIpe8;fp`k+J+M6jsMaWDO&<9%XAgSO9S7Xd zm^@L$up8DSXrLhB!11<-Qq!i0MfY`Nb43|Syj#eGjLjoXZ&bKD@biqqY-*pHP4l6j zbpifSnh05_Cm`ElZ3!}m<*?wOdU>}aE!J0inq&$yEkzbmd(>Uz!i|jbV2&Am=+Fxi zESoCB-+<=Cm4g;0kq8Byj%tp;pg;H5 zS1<#%6?6F+`M9B6P2V~xNe;YUpwp$ca9>LS?r1U#JTGnY`M~2vIH%FI%%l{@TF50e(Ie_<3lwmO0a(r;AKf_%ra=W|&`j z1OM=D%%LZMpZ5fQz8v`Z*UC~p>0&h=UEVN=TVRl15}N7uL;iWpR|jn1PXXR*TEY(BQJG$K zv<{86XmL+XM)D%y=fe-%A=`bzAkFs{+~Qn8dOAojVq5}K}Ub!bW@B;7bE)Pt3Vz;!G_f@bEN-TyHla5 zH_i28uy&9?Ju)MJ0zXH#P-mT?e%x3m#REisC^y}cE@JVk);V3i*|z9q2glZ}%PJNdh1G(z^qtC=B>{5bzJspOfPF0NNk+ zG7c?d`0z+?YP#2*IoaZfYId+1oE`?dpiW9$4g7r2yL|5Do?_r^mFcT2>cU1XX+CinAv!6=BK?Ay9V2vf z=C`gF(=B~`AX8pKJHfqdC-4u*NuXmZKm!KvUC;#yim&xym?#fo4!H{DV*44(o>;C} z5Bn}{hg-1Hw;s(FRPh(rmXP?w0(2SnW2SBepCWkQkHNjM4epN)<8sON53qMIDUZ4T zq>#B(Q5F&n{Cu%(Q$+B=wyYTg1t{&IHE}Kzp??mz@fonEv-O!D)f=T~5u883&++^I zFb}Z`z%7rZ3|{ERJNbH%zZ~6A!c-A&=VKijbIj~sO}l}7T-lg)D_Dn40zZ$1b5C5& zH~(|@Wz|;5Qy1{o1G#C zFSXSTGPRgIFRDGF(+55GBes_MEvsOT+d=;Gf_!2H^`j5y&t3n2|H=dAR~5j|UC*iM zi|Bl^)eZV@;OC#?YLfnI59uX#mwN(-YA1Ic>W!Kd=mJ_ zdgzCO&EW0`{QP^h1Pz9Lu}=SbP{DDRtcTa^LnKp#e=U7yGUIx1EB_|Fo)6wzg(AVX zWkOp4I$5B>WlxlJG3;LfKgS5_M-K*=8u0UJIv(qQpU;Xb!+-VFvAL@oOYBVBX1aj> z{7=|>ldvM*$JvdY73f7*$M~VVCsNYGI)DN{C)L2u3&Fq4*3_S$O#S$jU0&dm@5b+g z{PP9r*7BR(1^8@I8=hm`ly%{*j%xDDNVhwMyk&SE>6xjb!(s2D68hH;{dx4R9Q1jo z64xFDFV~X={L>XBbdW_AW74G_9Xr(`Jl@+Rv;2*vBFo>Ne06l;w#@NB@0&p@0Q@`y zo@2>>{2WVwpD!r&r`N0`xCr?9`sHxvwQ$y5G_lhUOtuJC*0)DIjBLho(4Vhvtl<53 z7c(AN`Q%Bxnmn*ql8yiIbDRtNy8|J2XTtAlo)PA=AI=ow~9i@?v(HK-ro(qT`&uax-S@}-AIdvg0c zUD?}%!0)J5m^EgeY1W_*t;|`tk2-+lYqT`S8Y*wz8EU1R8^fT4$$(#m-VU9s|)Y^<&bi_&*{M~`~J?YAfy6?%yJNh9| z*WgP~Kc>N42KYJ32A?Mt@Q)jw3}@Ejg#*FA-F2y>+bX^aleB-Gmx^H9+ z`YW=WI~WMr@8|RAeS0;`GhkQW@IQWz%z%G9?BM5E!@LhEAa;ThE@4w8d6rZc+Edsv z>QD;Oy#;R=M^m-9|v7^k1`!-pPg2r27_tFwkU(VS<^uN zxlqlDD@xh=pIWx%2#-z=QjvYoCjvjG{`a!+4A2jbGga~ZfS=EiYf;v+QZ`*&EgC<% zfxq**RZ+auRH0}T%45L~trYUlcK|=%d&d_a8zdzc@5*Qt^sghJk6jlZfH$mzT+KL! z8s~cRMO)l)sos(AqqY$@Mw`bkf&BAbL-gshzSPj*!g8WWDx%5IH_x1|CKcen8w$@e z@N;Ab{G-}UiOXu#%!c>*BzJo;K37x03~8^`-&xSS+|w4N^*d=nSDV{$v#p(J`vx(3 z(btEb7D@1cDt}t=|NMLt+}EbV{?&l<5`JWp5A#o?2hW0kyFFtc)cDd8H3gc8To1K` zSo+lCOQu!q{-q`Cnd9JDc0_}cwyJ2t82_!xDqc4D;7eW2#;O9Gle=tx#f`FgP zfS+&n_o5#i+_*QtiI|>V*0N2BX2KQq2C7@$m~{jAxp_<(FC0~f8!LHI3;e?o>c>go zAD6Fy*Tbb8G&MtsJS zMyr6+*iZ9iAAI*9V-zlmFTl^+<_Z7V_QnKdc5WrL|I~9Uc2{x1P3$7U70(>$3RUdT2qF4IMVu4a+E@yLP}Lxw!XEWK|RYsAkL zv3tDTm)XA-^mu8=SJMJ3AG_m4I6 z2J+9P``Wloz|U_$9%UHJHD6yXM0{8to%~uw)xbZlLH{}ho}<&y&+bovUC=`sJ|wAt zQEn*VbdY;_OIpuvI^8nc51Y`B?<|>=0DFEl@bjQ(53ar5hj?C;;K*q*v>56K@N;_b z9%z{>{Be?=@Lu2=>0tIyDX1i9o8J@}Ulg8A`Ot9)d0vWP$3QXcXzUo)e1fZldNmXXmcHfNsa z*q~btj=1*$cWO4@n?8zVsK;7=%&!AqP`Edq2mUbv>c`GLQrvjM7j+%wNyMM4J=6~|%(K@0A>rmF`mo2U#OUBI zXLjuYJFc>urQ~=b3jM5a4zbu>N5`+N7xz06qZy*+ZCLwX0?L=Mzg4`b)zsVO4+Hg4H!wQj z=i7jv9|J$+dC5G|4pk8w*uVOck&Sl4@AehyhdcQ1jt72zcd3@0v!aw*`&Xw2j%i>{ zziE~IUrFcw)YK72@x{at0|F8Zk4U@;0jm`SghJJtok~ZFSXyPQilvtV!z(b+b_za5 zc2pF8L6lmNhrN~-rw%$;tuhK_u_`_Wi?5=DkV{GkMGO!ygon_t?Z0qm@1EWLe9t+W ztT~L%A!b58bjO9j&tGlyV~2sCw}h+ksbAH&2JRnk`MG;OWK8A-kXqntOM$bE7kSeL z>jKJx|9pP>+`R|y&QQ%NN4?fGg3Y5Zsp3>0mj?Vi7x;M`=tnlZ$36i4$ojYwpYiS@ z_r|-~rZyAz`B4ix2mW)B!`5GwIpLivM8y9G5z)-JvR4FBZeM{9+ZyUmsun15zcqlk zTn|KHFn<*Wd2`RUsL`gMmEfiFXV!k@iyK|N$a;la{hl`G=c}Wf=!MghI7~i9><|(n z*7Y+zCqVPdjL^{s`gN$2X25@+5Bd=b{GTi=mmnM}sG zZ1zB>RT7Wy$^<)$Y|}`(+m1&@9nZNp3rAr zC$TX0R1-DME1MlDT)EE6y z;uW&I&W*}b#HJPCt4|G`!tx7aB=$JuCxY+(1E*emtIvqSTDoW)=*J59KETiMN5Idu zM+^)c!RepTX6P>&C^bWVd+cs|%$Ditr-{{qkam@XU$@zVC=@bmTq)--Y8RtIsv;3C zfh2lO5K005F$Dg>L(?jL$?uElM&RewCuI0quSbZwSwb@-gpK#Xe;)hB&b?|JrgfG9 zl62Y3UxRh>^&JEHBvy~s^}T=$=ofhYkxp7&*hK`VjdVZo^9OnT+)t2uP^=zf(@H0W z-$Xgx_3m)4Pg~-~E?VftRAl;+@M(XN0qfTS^H?6|a7%bR!?xSAv$K7A3st!2nx zz|SN022${!qbm6Sx5FIGxo{PFcuB$DiuObC?hD)Beczo#ImE7_tT~*ad5US!yyheQ zhOid$E%>Znwsj3pX6G14GR$8U!u_Ke_(u)QUrD14q!9Kg+R@9G06%|vacDT9`*q#p zl<6wHP{8X;=IK_px^qe83t35{AKNH}oKoN)O`sn)VP6b_^R+D@kRDzQU2;-}x4n_W zd#X3B%~^mmG%gJ}ZNeYV2G7tNmE-8f<`H(Q?j`?dSs$r6(?bTqf1a4H$7J#a$_4#+ z4%{Iqt_$-UjAYxYzxmQx3;qZCIX){Jt)DkBk?`as;>#kP>G|^c%*|pc<8JriYb*Ux P`3@ysu2j>eXMz6$KEllY literal 0 HcmV?d00001 diff --git a/test/torchaudio_unittest/assets/sox_effect_test_args.jsonl b/test/torchaudio_unittest/assets/sox_effect_test_args.jsonl new file mode 100644 index 00000000..2a223df6 --- /dev/null +++ b/test/torchaudio_unittest/assets/sox_effect_test_args.jsonl @@ -0,0 +1,88 @@ +{"effects": [["allpass", "300", "10"]]} +{"effects": [["band", "300", "10"]]} +{"effects": [["bandpass", "300", "10"]]} +{"effects": [["bandreject", "300", "10"]]} +{"effects": [["bass", "-10"]]} +{"effects": [["bend", ".35,180,.25", ".15,740,.53", "0,-520,.3"]]} +{"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": [["delay", "1.5", "+1"]]} +{"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": [["fir", "/sox_effect_test_fir_coeffs.txt"]]} +{"effects": [["flanger"]]} +{"effects": [["gain", "-n"]]} +{"effects": [["gain", "-n", "-3"]]} +{"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": [["norm"]]} +{"effects": [["oops"]]} +{"effects": [["overdrive"]]} +{"effects": [["pad"]]} +{"effects": [["phaser"]]} +{"effects": [["pitch", "6.48"], ["rate", "8030"]], "output_sample_rate": 8030} +{"effects": [["pitch", "-6.50"], ["rate", "7970"]], "output_sample_rate": 7970} +{"effects": [["rate", "4567"]], "output_sample_rate": 4567} +{"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": [["sinc", "3k"]]} +{"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": [["vad"]]} +{"effects": [["vol", "3"]]} diff --git a/test/torchaudio_unittest/assets/sox_effect_test_fir_coeffs.txt b/test/torchaudio_unittest/assets/sox_effect_test_fir_coeffs.txt new file mode 100644 index 00000000..903a607d --- /dev/null +++ b/test/torchaudio_unittest/assets/sox_effect_test_fir_coeffs.txt @@ -0,0 +1 @@ +0.0195 -0.082 0.234 0.891 -0.145 0.043 diff --git a/test/torchaudio_unittest/assets/steam-train-whistle-daniel_simon.mp3 b/test/torchaudio_unittest/assets/steam-train-whistle-daniel_simon.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..8977e7247c21f587c1e4a52f7eab87935c28c33e GIT binary patch literal 166720 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

oxtnR&xzm79FO zq!J8g7on5ux>!kHtTcWS6W6FCK&{tmIFy}iPc;O)^hr+5n1l+uoxg1D_en;;P^6FHdC3@uB)0Yt$Tnob_B~oB{f46IZK7#>(#;EAR0tmnz zZa7&{GNCUl)NqF0c`GnHNq6WpM%4yaQ9lT4+4nyM$L`3D^NERo40nAt%`^!=H&}5T zNr`gQ<66Vxy2JaEna{JSxuN8HLHr4lID?~R(Q3pWwtl5~2htnW%*FWrS{=S@7h{$y zKH|{pe&Cdc9BOnZT`^)6anK6)H=?_~8Wn@&*)MXdtmJ~3*DfNL(`GBrf!dNHwHmfM z!(YvAbkg75Zf2K;8DrGpcqMg6T)v5y@f=7~hWDcdO4`EG#6w}892~PW;9)hbiN?(u z@V$7_abgon$p^pJ62GW~FmyCVt95I)vP~7ecS*^{%BqvPSySmU8CtQ2}%{$=lu3iICEf6PNfzRnv&+3A;0?lHN7e^4zirZ05U{5DYkv&49sfb#HAFZ?ykVK<|0=i6;nWE$s1{;qJd=mPbVzH z!M9t?Lm34FaWMu^(0sg>8q~pp_8l6bvD#&n{Q6#sq=nk!5(P9QRB83pLOk(ZKeWg! zhr4Iqw9H^EOQ1L3=aq_r!=c6~m*?Uw8SLT<73T}Sc&z0cK5`BpW8?S9^3|A@f3*x{ zfiW;1uLXsFkQ$W70>GD>P$vhp#_aP`pv6FUy}m&5;__XMabwl$*eU5*Iy;=QUX)xpq zH57=7_!l$mC2qKDYPJ1a1SNZ_SojmoSUyc!N66#RAQ%VlxWD~nk~%(qu3Q*ud8PZj z@t;@hk1xN--z+`cp#$*%z?_L{A2=c)BLOC&gg|VdA%jeykst$N8x)TgUi{(}$%GPC z#oJ45cA;kEwgOHWCngR3^B+peNR5t<34|iR^cR9Re<2-~lQZ)qgsgK%eqRz&^DD zu^_k+W2oWzPNO`@oy~& zzZ+L5_twpr*iFCv)$DLY*QR|=xEvpX0jVV=sWG&bTt(7YSV&~2%Zgm5DDe5F&_M}H ztL4r3wYbE!F2)Ac6in0z?85B?@$Q1IEHkT;tp6FeG7e`G(cbBMFyW{EC)o-DFH6IqZuRtgo6U zNJ$V{s%9NZ((c4tF_#zO9Xxzas3`HYy5<=iZYe)HoQAxtklS793lX>H(HBtsJKFnj zQag6@2ewjkWe)m=wqa2?9;wh+?fIbm#>z%0Xdd-+)?iOfUcNGDqRY6@ATHd=xK6~} z!GGeh#+;!u-f``B=4HzX=iZ0wn(eFk@850nJ>I1@67sh3#9$|u4{iT?sREH?nvo?1 zhyg%Y)KUaA)q7YA@(?m_Z*c>`p%QguhK+sCcmz+&IgOf5D~4hd8x1rfTS}MA&d0j= zGSB`~7oyo2O|!>aIhSB1GdROwf*d+jefoK6OtKB_wo1)w)2z?sSsUgp8d_!99V@lt zIEiC0RO6f=mx%R{#3I}VIN#jcXFt#1Irl$EzJKW!xlVZYP`gmy=p0XPRe+UIiq`tW zUF`UCBLUC{J<+QD0ssh-Ng?%E)RaX*Ux=bw$z5XX17r}IFhd9*p(33?xRx;ZK!Ie5 z;?>0*qyZc;!QK1GX#pHt6_tEwkzmC_-qSx=UYCGPt+yQrDm@)8N=(>0sh@Qr%9fTU?Vv?A9S#m&WjtkjJ$e`L9zieu!BrVcfd*Au8kb(1_V0h%k(e-Jij#Qeb|Azn%o5P?BGL$ObVY3UP7dPqyV_YGaN)( z7dadNeFsPZZkvorDNFOe0Cr^eu_%$HHY$2|GaEXLa#grkch@hENMo1{nxOD|r3HQT zmUi!PqNF@>10+Z$mSg)m1(H?~Y=UHRT1X5Z9d|?nZ5$85uq~wMSTA@(g$X+p=br>V z4ne?oTrhW#G6_N!f?8$q_`eI4VjDSw7?>S;$}@V@HN+U>g6QEl1YEO{uE$@coA`e$ zx6&WHaiSR?UYx{Ks%Y%EDE;UXtQ(>Bu0*Wu-emMe?QQ6>EfA|56nJd9-a?a0CNvxm zuvto~BTh?1^0EsTh7R~E&Rsy{o_Ge*QEF=av!2uh5QXIbcB(2OQAX$^q|I<@M^^U3 zsgPNL*`tYRiuFn-DRlg5xVK+*?^jZMg`dxELd1JK*QH?l30T*YCXQci;D#i6a>UTEodY~up{Aa0j)$7)=zfR2{@Zz zLJ)f^icw&^*obQDsxDsdyLw2U1V5c>#>7?K83`vN`#?ofw0(<+Wd!-Wo()yLm_iPH z5SCe@8Le7J3a_j>Ie4V%EUS~XrZ|&9o#vY0=)>69oJNMgBA6nKZbCg0$bBwyc=?-M zhDdjsQ)q#@<*-81Szh|I&d>kI(d+#|A7Bz7rr+IH%6SNc2gU|tC6R!E5PE!dw~!kC zx#bl>Ic{C=pY8=xW#I;uQgjF>mh3)BU2KGECOJK=pCFPi$15#H&d05$U_!8+I5c3c z99eW;)Jw)?`?#-F-F+kv5*Q*!t-naw=-IjEd9vrpGZG4mh4J{VoE`vb08~u|$U$V`{z>4M z0C-ZfrO$FCyV)`NP|imju2THJstt7f-{_4u48mnNxhR*_bWpah0?#d zlh(FBFl-Uj9an#$;osN)W~!Y&sRtY_Bn|ALu-lmK?!P$nv5;OC@sGu3%l?hU01lqz zJl?dF?K_Yq%)`*UdHrJf6C?_tELu;-h-QGS>Du%o#xS>|Kwy{z6TzTgqT=#=LsUSC zi@f1=OgFxCGzUXX4vgIfmUQVjTm`N01h8Z|2wFjPDptLpe`NMGfYGphinprN zHgk_tR4W{{higNQ)ajPS-6uYItmWy%lmLGf26Z7-Emzea49TX}N5ac(07T89fC3_3YC+etb?2$W^I)aS!rcyR zl+=|lO3`;&fxiae#7D)3NUMaJkBXiNRU$3cj%pZnfz*y}xH2CX;mS_bu{m#)ybfD% z2^H&)8PI?1sBmv94to92C&jwRw_yK_T zf)eO#$wWWNY^qocR^!1(LQmYHIAJ9-bo%_mw4z{0BJ76%_R)Q>?P>Y&lbpc9y_L~A2>Q980laeqgFus!Wh!Ixtn>F0Bi{^ma5_a`M)F4_%BUg>*Ue8Vg z_$D_|@d1BvFqmi}QkKw-L5=Z5HY-@u*mh!ZJJ{2|-S%A4Hi5=VqI3m?!*K7vt>oAA zzbQ4dES28v$-fl&YIayc-+yJfX09%jLq}!+5Tbf84Pk>SNOmk{pqLf_(!wAmN32TJ zfsGXvQ$n+#_7&N0NrFks3=%I&aZsl+nP*42=PyMYp-f0hoj>g=)svGLW~xrw+K7!p zSFUhxWlAeLszSE$3-D_;n(4u?{HE67)toXCesyLmF!Wd%t?!7PjV~H$9;f$|e8H4r z-XF0gQ1f70kmk$6DH>s9U-Jh}ZJOVy_tLR9k)^96`R+c*KWE5P^~awjM^^K%YjH|= z(*4OnVjq&MNd_ar0;^4jjAcC<#bV>2!H)l3PkYy{c-FF_Ds=Unpz*YsXE!(kw9O2n zHJ3v?iXTf3XJ>VuRwu9LYdUl#d%q}5XNiYV{gtNHhP$VIVwEv^1x^B#aFD9^$hYnw6-f8~Rxo)7u4!f3D1gx|r z1gQ{G?wn)vd9@_xeYa&+zx9Pp4pGV{sdID*f^XxxM0A-VA^r zx7N6K;QkpJp+IgMT&X>ruQJ`RC{9!XZ3keK+CHVKMBBdNjeAwBuj){gA2uHS`1mUD z%+fFf`UJ7P{vP#}XdTo3wGrOpf|==iC*Kv`UV0K2+IC#)uQJ|VIB@QKaoWfuQAI!8 zwaKyr2~fu{Gwow}t*83*S*D+9f;bH;NiUnqM-N<5`!WTLO4p(v(fPelP^v~*zKh{{HRI)5RgAL(@Raoj5piQELTx{O^jB2@OKRO5mUblo%by zaganmo=?J)Wo7Ka+n`=E`UE#cMIf~PpG{E+tKbN|>bR6F<`;DkAsc<(Vomtp%`6T3 z;VNO1FL^JDGRdSp9J~i`#2fzpV|lCEj0Ay&WMmvE8OmtnP>R%+I{mq{ans zXmt~lhD~UmY1S!a28`Pneo4dA2fqi74Y=sPl6~yXu3u#K%8jsHDUGkzVh|&EijtP6 zIl1xENO@D0sBv?&oTtKp5cQ)u7e{rcNN(P(rb#}!3` ziDzzX713U6GY({NWPHh#b9u>*NMNI$bWCagQ$Cu3$%OE+DTFw;n&!lo;y-$Q^7OXk z?4aBZiti4qvF=B!%#x9zhRy7=PAf+yo`Rei>aHssF}6kwMB|nIL^_E-S~8ZB7ycE~ z-vIbovn4k}GNCT=75#D9!eP)0#FiI$~F_~(1<)_X6<4aV=nQG8!; z|7cB_RAYmmfBg70GFl=?;nMu_xjyujdvmz6bN-jXN9~mk%FeN$F$SgG>!(k$v`ORa zY^c%p!^_d!-KuhetvyVF)v3A{0OuxC`?0L3mQb51RqBB;mKU%bUY0v57KIMC%TW#S16u&Ux1%`qd4Zv zbT#?HRFo`aQO1NbpLDWpauoElKjN(lr>UDNr+`c~yl0pz5nIgFwfL@uJRJvhZLK~g zWd2ZQl?|1m`LWMUqFmI*W)X7efk#fE{+!n)M%%Ncu_YGEfstwt%cyBOHry10uX=VM z(bxAz=FuDC+h{9_#to!UNt0vO=7&T6j3CJ}XO>6;!P;eS>{IDLUa*#1>KcIKLfU*} z9v>`9oEtVi^HPs&GSgs_-o3r6XBSe8=$S^!7>Re z#zAUH*CLX~TGz&>O=eUK!|GR`!7ZQqZuRN8HuqYj`=lIi3A!tir105w1ulwD)4Z3s z=e2e5*RApSl!KFt9Y1rydS=thdvQ|}9{>c?AYIMAN27{_qX>`_RfXVy%=-(A5fU6i z=4`lso_5BI1@<)!94~aO^$&`nR*{E@GA+33P*WD80Q=g@^?A{3MJw(chni9A5qthU z^Opede*XZnQgfA;c7C?BRk*j%F_V(zAfHAD=o*5fY$J}r*pDcrU^7xt|9La@HNiFu ziy>KJCnU8GojIP=w1hn`YNj8gg~N)+T?DL`bQ*M38#A+gYz<@3@U-`Rt)uQJZMomnj^rlk=N5CrQC~p984o-~?NJ^gy zkL4~+^(HGz4L5{Ev&81$^S3{Z*6{;SMzl49JEmJ6olSs`VuV_p&hlbv{4r8ncOOf_ z=gEGcF`=mk1qZ2H)`+~n;j_>_{LDrsoY&<77wKnOPW`V(!LD!Tjt|4U=Pf@h4RIZ+ z<`^wS%09PCWA#8JZyfHdK$GENSP6_8J$1bqkyibqE8Eb)0Nt3WA%BeNqar^T$wK*J$3b6N<68og9xu&jfqyC$|7o!jA zZwE~Kj*m|+u|)<%YEt<4U2>u4bzSq?;bFg=(bpeKyWc&0!R6;F_nqkUeB5FW*Asu5 zr;iPC_N0U&mc@7-uxTHsz==Do&w8G4HT*!dp#2qQk23)UA)(LA+TemI;}nJzRPq$j zga@vQsZzxgvjj=mjt7Uv^RW~q4tP$t?pw*WUdGy`8R~XXHbpS2CbwUb19eP76$gQy zfO k%jf4#k&&LS@nyPl{pRn7;l=MG8u;np6$8_@a`l1s)PC_ z-}RsETmYy~7JC~5CfE#aUJaIvnv)YmqSl$55ur-&ORI@P1jd3QqslpgV7tA_i8zt= zI#6y8!@@aGWRgqb&@G0-qr}_qs`Qi7QtKiox?7;^&Od^B;b<(57Zb=kt(7gyiKGA1 z3E>DBcF@UL9+RYTXs3&|Qro9mKDH5CoRz7jCyj_&T(|}$nusG*l9`&KG@cPfO*F8w zlLx!{9)Y@B^G=~9(V5@5;KJcJyjpj2=mred(zKK6TY%V~y)sHZQxdq>d%bOMj~o`; z=|v6Xf?h1(>uLHem$ko}C+$tei^*eYnT{>L(DIT5UQ0KWej$u_0E(bXIH{ z*<(y9Ts%U-2V6=+#0L3}24A_}D~NW8z7hWyIjeKg`jOe__#508R z70PW<8~~=g*)~6V{XO=qwsyi8pW4XVn{d}4RDaPpG-t4Fr!lYzD6T*qLy)EP;zI__Y0cA!k}S3RrwZ>#7&8c!1cy!Z60Igfn3l! zslH2-5vz#_azMc*wVkl{)?|#PqhOVP}dpO`N&v%+d!SdaGq2$EBEh z4Zkl>-w$HNrp(__N+(ks=DX(ZN<%mK94nZn#Ynxzc9QT#-jK5ha@q+IP?)jaQ0s3{(=EJf9}5TkV)G!P>fc?!qRt z66#tktEIrqzz-M@-~5n%tqgZv0xM;u^IzNK^sCemi(CkDQ@}t={GYh{R+_|WW=Pt| z)D@hNqlsW-ZrRi}$`9_^xAC5l6fwP~haV)IW&_8`Y1nGI@>n=wDeZ$zlj4}qds80% zaia;%i?a-=+<{Izw3}v?n15NWp0lE^oqiwswZ&@gF<#YXuQ(dp$h>oWOMhuS9a);58s8u7&dQI*>oR}mvH+r3{TTb+vMj}cnII)K%%yf}EtX!Z9BX`T~4GX+( zDOawa*Xv&K@jdzX03-+iz(PP2nT8RNhJq3}NGndHEo&+(R%VTbKy49(pFluqwyTii z&HZgq`V+O(P5AoaXDLAU)bGC)30SCESTbt;vV{x$93LVYLM-H43z{w^8l_K4$}Lyh z{KNkhQ|RRE%0HdJGZ#A+tzsLNt#efTZxc5Nlf9;=7^CF2`Get*k{7dCjJQ9(!Oa{= zL(->6g4X1^^Mk1@*72&WFIP<2_554sR*owviPs2-Z#ap{vq*RAu5e0oO~+3Ry}dq9 zl_S1eJdXmbwdKWRv{6mNS7bKE6^`5}le*MjEC`r{s01963~^Mk6z!_A*(I^J(+$d7 zvl3ggeb(v29v%G$NL<1=ojkf4SE%z9-RU$Cy*%u6)=n`!L1C z{P2n7A~3v#s>!(Gt(9!TjLIZo+lQI((O!5fh-AoiLY+3CB(JG3>QI5D8AbPK`KH|S z(+=wES8|WJ-?&SBAD!!an%h(yr9MPdmzLD}k^%m`gt(WX8Z{_mK2S2Ej-a8RkpEj8 zWsxZwjBi9>ypkgmF+u zu@mJL8L!q}f}|P$PQSJ*@0rb*&1bhsX!w3#I2;l|J6@YjORa732MA>d3ZULIwtXf$k2<>!bU>`-Q)l$y`tvMJyp!BR2Zif?{3PJYHkeb6EMr7UY za~~Z23_jL(;$414Cg$$ataUAnG(Zp6Fmr7Pq(J8Ra4M@{pn=`eK zh#BFQ*{cx-L725K=Ci2->Br|+#iwDHGA zAHG>M2`5IhQ8K1nsn9M=Bl0#SIAAhey`Q!~H(UXL^=OC@O_3Qg0XU!%QcxfvU9u)Z zFz@3#CUNNIvox+af2GE!;C^wv(rgH>kADW*GxfDF$_gu`JCLdhH`W(2~Cre4&`Bi7mdG;8X_qNml>nuyp@cq6#UEWZ7>oFRF*8UY;W6f! z)f84qxN*z6q@oqb|vB0`f1gCyfU*8hF7luWw*V-Xu1Y?P<#0 zkt#b@4Kaq{r02drWJS_=T*ZV6Ujwtl^11l4vBAV!N$#JBL2L`qN zYX|%W!2L8^GKWsY6kWbRH*vGDV`bE0x+jBd8tr&evIqn;_%3-#toqE?tJXz%%D&`W zD|}TjWCrN*ulMO0X4@qsuVEwTOWD^#>JjnPRA4Ru2DzwGtv3r=YbPI`BlEozDGjehOG z3@;9t;wK|x>U#f!k_xqAq07Vdu;n~ek%6c}91@Cx$sl*!l)q#m{bLEO9U4+(1((#~ zr1Qvb^43SE)V@pvEVONtOj^1RQxQn$apTu){XZx8lxIIpQr1^h_P0KzUJAY|__oIE ztX^lDM#gwu&|9cp(a-6&r-MQh>j+#FUA1uZMwTTn=ER} z4kBQ+^l6hFG|SbvB?C2cc2jQ1%-Li0HTo>RxX>g4sgMOvYbqJ}{>v@ddH1U()%}8G z>dRLB&Dh_p!>m$iJoGcGhpCpmWH1$^wv}T=f;Z+`PPN^r@D5aWX|+mA(9EuwK5d?u zi_$%LcvejzE44e_%2rjv`Mj8-aT%uxK!QUGltdB`m?9}H6b*`wMEp)k8%`)NN`~yPI!HXQ6`&7+@rxhWQ$lO*j%-q)*k2kx|yiA$= z9B1H^jb68%<#9iptl)90#(g_5+M9A~lW@7Oz^1i&|0Xt*g#{)~=BWwQJ%U#DP&Pu=uI^0W3w>%^hBtnD zLpHyN+A%(|2q?=5X~n?C?^i_1NhOHDS{TQcKo+jcJjshHfBIsLYT{YfM$Xxvj~E>9LF)1*hW6_U9k$kSj%%Se~`tcH&q$;j3RE03U zNo`S*h$AXFx8B~+>@YRpCJ=hoe?OqG91o%QI?J_Cfs;D%NmB$pH?xMQQGqk8r2f;= zLMF(08vHbaXJ3p!O`TdG93icQOR8f~O|rtMTQA|xd+T|I8@w$c7mpOJXIw`tiFFieQJDo{ZG|`- zr)A+{UjO^RM9csLq+iryEF4gui#mK|CW;k7olA|d(LzzJ?7fB#HKLTOe@EwSI-g%A zJw2ruY#ZZkpxQ$zBpWJIa(vaHZ8tp1Nc4AGTkfXV)do|`JXW6l@syH}320x7$1It6 z;RpKcMtN7lt5Hl#3Gs~MB8<;7c2;~NkRAeUgef6=* z@E`yH6_YeB02K^hh9qubW+7nnAy+vP09DHI83Jl)7=+fGgleo%1jQYgv3z(kL0nn^ z?!w6!47&jIU{8FuO%9BlWn!DFFSJ{L>hKuk5kG+in8cP*hY>805$Q=JktXR}T9LF1 z14{R42WYn-dPIl{ncpuDUY}Q+iquTVF5J3%$l_lv{_AhwuMUpy``@>##<~VNOs8r(A#_ zLlIC!$(hk;qj8wi!y=`RSCVf?FILZrTwA=z@aFS6}kiyT#103SWgYBGg zla=*HA9!}LhNY?|_PlbXGMyARrCT|#O>V>qe$=zYvs_g3THrCG0%q!;ljNX=)qfI~kQh*o=3r5O?%sGPyFvBhYwF(C?+(`+Uf@1>( z0>8u~g8{b51c$;#8SJs<0_213C)`j@GjxU}Sb8=t6+xnNfeOe)8EG@8w{j+;DR4Yx zcv!qk)*^gi5@bMv#Sv4&K2O%QF{yzeMi+%8M@fq>eS;n54vO%34)AUbp3^m{cYDQ4 z>4ikCo8&)i|NFp1?gRyEW711(G(e*a`hIDKh85+DNsTbkLW(hI@sy5uoHZS)|9@%S zx5wL@*PUyXVjdrcpth085;Adyq=`qap&1Y&K-geiv_x={*W`l~K*{3Jxiln+ z>7;~XDGJEUu*@ckDB(CVnN*v4+bF9PkL;r& z4;nKO1F|lX7Kp=O>YW;*3^BxEw!h%EB@V|HifEeRBM>%9(HQm#hZSfPM{u^C1}<)i z5u^QVVGXJ-D}E{_aCsBnfns_fM$%7m#C}E-G&{QMhnh=jsua`ckf!-Xp=C&gfjV9Y zsz&3C%ISfAnK3chV|<4Fg9M$gf~5{-%LXTd8ye2PUH~9S00Oo$T3isAcrk$t zA{y=*WhYn|9df2+>g@?-nt~cb4S3;E*jwV+B(!okoIMbrts0!C+t_)6k0u5anPdFr z?U542W+;i`ZL~^`Cv#BTFsnTc!K@>JqK3TfM(s3PUXxnChltp5nvTA|)413Pix6izPFPjiD1hG|732a-s;kssHq&-~$aHJ-6Z* z=9Dva1|=ODIxS-YjtS5OYA^s+jBI2G@F&n{3eQhw?;g#fF$x{RdXpE`lF26Y-uC96X{1$jU>|omb0m`wi-}WRdX1i zP%I$NKnw^4c+-x8iwne-E0$fS*1bl|hOd4~6rZh$ab09Al!EnZnLApd!OuY12PKlR zR;DJircSV(28w4}s5)3mW*{TScf(PTbNq)mOYpIZ_j#O&|nF@EoN>s@9X zMJC88+}ZoDu5Met<9Pijz5BI_%*M@TN6uMI_Fw(N!PDj6R?YPMy}SUx00OI6F*8IE z6d=(7Mgn6_L0X5&JO~V(9#g1MR^<7l(sn7U(C^oQA*WuGKnMhRx=f9c5JBirt6;ll zk)UOBq$5H|sIX)Qjyj{Hi8Ccy>Nv+jq9BA@o_M!08WC@lFV?Esee0+QxOpb++=ps-)lIZSDfg~yz^4#(`{>GMmg|Zq^b4ietk`DDt~AHsKdYn z2><{Bf~W_qfxqsSgKX%Z3@l2cDUh*bSvmN`}; za#-9KWz~#AS_BtKEO#Ng8X#?Gn@E*lVj|E|A+dFmO9&XYiPng;7EqOhDLjHj(@_Hn zsnn%01lHEao0JiqN(y8Vw=ZfgteU9!0xW_t;aZ#hdunS$6LkkcJviqq`pgBj>vEVA zBPq5^mdM|VGrgCOYr|fjE3)YJWEWc4<0f7=jNG0H;nw-H6L*U`No`zl%3xoUP#mhBMNl4Q) zUQ=Io8?*_lX}4wCMF0E1MBD@gC1+D(DKxOVO!`b|Cdd^%i%X5L(L!yo>G7nFXyeQv zvmAvin9EoZ5?W;tUMcFkvtp7So8C2Haf2eP;|9hgMijb(5jFdCvMw`G`?Te|gO^Y9 zJ2f>|#@^Ex{^!WePju!Q$A;)YFXtIO^e#@|0|1f&i|pKtV928qNJ3~REI0|5FeccW z$v~tTamWFWFn?4Mb(Jt#WAOn)Y|``2k||;*)WZhJY=!KM55!{aJ5LM|S;WNl*pqCr z&6`NoMK!Gik;+kB!h-xd?LkPef+08%QGkemMHsqd5XOgNd?t!|!;KRE2U2V+V1f?_ zFF@RLIRlx_Lxx%(ZKaT8XCOk5!VU_(1oSG90R$idg3zd;hmMSgpdd&LC^8%xV&mgr z#!wmx9x8>yL;*nHRR9MRxW%@TRfq*aKx~O1B-3^vIu1p%1=&Hdkf}mdOQ8~@Mv@Fh zqZWyN-Fcw^!U+z|oeb2GrW|2CDC~klrWGi$LwU+O91mD*&BKFFM`DnJ63UN}05st^ zIoQ&je29SvKy?7>!UWi9k3)imSnGs;Z_~cqf zeAxv7E(Vc!F$Y1mkD|bvBYA4`NOmK4+b0dOUG6?e!0K~&hfNb8F6h{^OP~^{jb&hs z5-r8>`NL%gmnhcFYfm8<+m%xzQ}bsNZj&WSo7h4*b2inmt1rzeEL(<=7UF?z)^%+# zkt@OVJrCNRgKQ9|7|e-CxeCNN*7gjwIKhQbK9ocA4De$%+*)ZExLotw16;_7+wtK#uN z5em(tKm-5)0|Qc#2Pz0yP@<455`%?ZCmiTv)J`#{RoQt038Rrf=om>1s$Uasjh-Ha z5|2PyGD1dypP`ddp1MLTisOHWbtEL`%3-d?bhxN}8i`W-rRBJCrYn|Ej*BXse^tjK5BVZ1(w@FbF1TwI=Wmy2S zWQNycb`!ag{K9TZ!qIr#`!;4Pq9Jy<>@ulUA!-(Dg5@s8|xHdHxzVz=GS*q{`}6s01yNO3N1jO zVGo$;0+ufzT(n6b^xh2F3^+!{*VsUIY{D!ozFqG`$F}2`y=qd@q%OFd`~r0+?tZ_#;<@fYYlgw~0em1xS()OJ1Xg>MbU7 z)(?e<8itNGAqt$zIxrwVF;1?!tkSHeops00)Z~*b#UT10wLC|qX?WGO7Za@2Drdpd zcNqyBu%8tdlt^)xt&>P(AXZ=h`@lry00m8AQsYTApsI{YePxJ_6gi7cjU^L;f-x#} zl$tprEh%OFVnr{J^N=!V<~A5ekoA{sOV+ye_oZF;%PLs5CXpB@#T@Lf+m+RN0R)01 zq|=miBLoD~04N10K$;?f9|6)n5qe>`HV-CUoWv8=I-m|fVsIEnQF9$c>_85YcO| ztclR@+FClByxUYHSNeRj?$ONpT{AQ%9W4}O#927#wd8m~{&VxyZ8OcxC0T_fJkLLh zm;nU=5D=TcMFS+zkkN-CLR@sZMt}r|)h4VYmA`V`5{J?9fQ?|kPi(%rr-6iI0|KK4 zW+^9)u1N!`si%@$av3BC30Q{hOnnrxDQ3YOnaNG5uyKS)Oq@o>riRpVI|?SV3*j^6 z49&{shx?3bZBE?1IVWG)sQmPxjc1z`p09g5|My}Lr!MD8#{Rpv@#`SW?$6simf*E> z#BAH_`nBDy1e&ZZ(m1BUazgHf%>)irBvlg-Q;E^zR~Yh;I=vPNc5vKP73fXljF1%# zC~#jPxtbC+rqOn!Rxjmcc^JzWe+(u%|N)@tcQm=|P1UXHT zXMF$Jw?BXH-*(F?{%f|Q-XW9b>+@s@nU|~}Lm4i_T1tNom8L(VL1mzumMnnNXiqR8> zhrsQX$3`GXeMKb^#u9o{H@uW9<5((Gjb^svFR==?VL^Sk?y&L~G!swR6;_#$9Tv2g zr7yai`FV5IQ!lkzfuz%BrG2Yr4vtp&#l^|r4IxzbKzx#kB4u~l%GV?@ZE{*qZ7$mK z?OCmjSKlS~!4LOo>*72K1~*lBzg7SHutfC$1rcJ@b4fICnCwe0W#(NLQJ+bTu+c)R zu_>{(8Za-29E`va001Ht3;-E8c#K^;00Mwe)K?Lf0Kg2i5Fm6!0P__>K|;`%1rV2| z`gaDQh*G_H;0)9vNbqe^Qg!x?qFr1}#c298^|LRfdE=7WrIka5`w^Ku(89*v<0)Zz z#GN^9VwiE~)-I&Bs&%B^LJ5_OZwE2}GgREpLylHzNp zxPgX3{@weklp$=5%x2AOZ*W+Spy^_y63RAsQ|QUW00000Z~?@u0VFU0j2d7fP{iP! zL<0ax1%tN-DQJ@yeaMBz60MjumKS0jnW-Unm<#(UCm*n&X{h#n0GuF_^rgw`5&fi$ zdbyU%B~?atmQNW{U{w!RBV7WtM~Y5izFnu9q8FFe&NpS9fjOuss^s^c&1q~Ma#G1L z=Z+|{*AJb_sEbNwQT>l%zIprevI!Ui1)S8dfI&`T6u}rehk#HV5Tz4q*I`9RKD%j< z0YM2ZE7^rMV}%&d2#jINm|&<686XFoZh!)Xi3n|1DnKbFSc9y6<+q&!ez_t*Y$6NB zO$W)zqR3K~CSf#ViZn*4W|YNIb=6f0|NFp1&wvFHV$)+uMBuOLI!kGWsuQtiPmLsV z0>iOsakd&L%q2@?1v9vF>i~h1gcIZ`M2B)H5&E%}j<~}Ts2kI~IsocxMGC4C1r|)T z4gf+12#YmuhQ_lYkIqfvnRX69F#*N`!gvJAL6Cgc4Lkq&hd>Ar2p}a0%ZwmY6c7-u zU}NSG8ej0KLBxj|@PIg8Or@ltVB&>zObX3FKn0TRxjhPWF41weaqi96hGV)=`Q2-ELXz`B<2|@kw1|PNqk`wis8kfm*|?EZY=J6ET(ybC%&K;W#9+ z<^>DLMj;e{%G31|;VBX=IORHW>|QK`1(3&`+g8QzwlfSD_2%lkdZhb@47zuVldb$^ z+m>9RwWv;WC-$SOcW{P(7}xo4gni4i+&tRkh!F@uTgVF(76b_gLNP;O<^CaJFBpsH z&}D?|bfL(M0!OGk6z#@fNqcK8&8e{{yUQm)9tANts)2G9gbS+5yEb)&;RX?-b~~>s zO~I+m+^21|bHtxMZ-DafRkx4*Pr1nMs`@lrnfCYtW(qn8iz@ZFUd}$`A6$y(? zjWE*!e6%Wcl#aQ-Pqv$NC_M7RlJ<@ys@~?-K{gpYrL)Ol0y7P^ea`7s=SI`Ubp-sX zD_uLoa+aG;FLD_YJX z!9vo$k^dq_`h74ZhPf7u!;svHt!Ohm?J!412%8fmc0@|W3@}>DnT?^fUWuYp*26~$ zrhx~BGQ{qBPZz|n#d{;4A#aJm~N>WpoAcS18&Q;Wv(ehk_V%)#lMz%uf2B1yE-gVS z@WtHtDN0Ti6_~2Rq3oLP>@gjT8Jf^KJC z1<#2?NlIet)ogLQ$uL-0m78g7ymGMdOo2nd1cE>Xnw9a4>hQpY^`@V6VW?bP$%Q^O zBQB?$G+nzwD-tDP4pI4p*R-sSJ-~<2w1~B212NpGe zpa}S)*@=l5P|A}&W3X70J}d>JnSq6k9y7vXlE9d;S|>;-87x(_l`L#NP|VbV|hwoge#jbWW$|S z^`l9Ru+u^~sqB4(nSIuiH(9&yd1=;^X5ZvKv&`aqkkskVi+F8gJB)#>vZK%!)B=|&v z+D};0(`O-h57#S7V$5Z@GAgsFV}D8-C?At7?*Qi%jv{`)wd}!Tbp{X+y@ZUng8{~- z^6tFFn8d1`l2yb+)30$7OvQV%l#f~dDAJFDCfla*?;l=zjPOPA*$>cmPdzylk36L#k4PV`E#n*79{FpSt%UQgx?f{SuNNGtJ6X5{>#Hni6`&Hlu00;oo7J@E2)u}Hx zHir?WpmbMKLT*u5(-nz(xnhOwBD6n~fGu=$6w8*w_jGQS~MGlkI zVrRRAOPWfhTJXENo0)Iqd^2N(dRKL8GS*&05kxV{4AIFT?*)Cq7DcbUw2kT1vTo%(+$ zW~T1laOKYPh69KOkSGH%8eJqpTt^~B#E?-?@D&rw|NFpX%>V_hX42zKG{BH5`!8uI znO6~yOO0VEf;=Xy{e+sO^5Ul=X%lgxQ9<-a7?Q=FE7S-3C?r5m3d~G^&TcZvk8C~X zV)$pB-rc7ziZjcskGq!5bn)Hcs@Y9mr8zB%d!r|v@~Z0NnEd+2v*9WEe+4mE)Ru*+ zAv_2)v_-Wnce4^@Yb8OFnd5Kvw{T7m1vT905Kb1%1BCZAb8y_XI9D=ZE;(iS`UnaH z%LbhTRg@w`M+hHSXC)kpnQIrhEdA{rNrq^c$m?pw%aXS8bVFmONR^(+{3L>!n@^^= zPOd2Pudd2%%l)BV2-0kx)@@&FUhMt*joalh{!E<}qML-6+3^F9o$a2(jk!!P1ZK6B z{6+^>WoLfYON_-t-7n#_NL-RYRYX`KVw?0@exd{@uSk-FA<1X6y7Ku$f1XB+vVUzN zn*?hT#58>iOtwNbA`F`+eq9U_Jy#)Qya=Y=2o-9T7>g>FkSjQZf<>%A!Hi=dMuo5o zG7Y@LuL*(Yl(;rSu~<6%brNo<5w9MDH^Q*yQP|NRq!m7yV6bnFjZwXMmh6B800bZv zfWRV4(;-1K10@lV;v6m#ToD#5M5Rr@kZn8}lHE~cQ+}YBA2y{bh||cVGQE}{L=vE! ziB$p#Il*Ww#0fR+U zSa`GH%EDW8HJe};KnMT;pe;6VD3Or?7c!HgQXdTEwUC}2dWvbvQk zg1x11w%~RY12ch9bnuHP%$bbfX=v1J`gKn%(y7ByTOoYA8+hpyx6e`Q+>@T0J0H2s6|sp)jj+)|sj(?Bq?$0w&QKGd1Y&#Zwqg3o>|3L|2r-P#`CqSV8MoifW;7i# zv;soR7C!V~I{b4xY`g$~0003|od6tQE^y0)Fu+VoHiiL$dO#h}=uZNJc>#pd1rwW8 z&~=jbR{{W{08MhDKunNQ$gHuj5CudmAT}f0GO8C&Jczau&s5hMIK)zZqg1A|LhMZ! zy^R%zJSg*d)>L}CPI6}nMCKvCUpX4EXgopkzsZ@rgsJOg#ZEoAOJyHG@)|@EJ$*}P!#B7WW+^5 z5+=5&ZlMMS3OEB39Tg54AqEElg#!?^t*$l;1u%FawREVpv>`k%K739cONFBG9C;x| z_&dR>_p^^lGSe>J(EO1 zoFg|iZIibz@86CO6=HCKltxZc=r+x#5auOWF!Al3kRpQ3X;PK- z(Ic6Q8Q}r}+(3%v$nfG5!!XE7eUxZi>d^<}b?fE~H)rUvBnq6-^@q0h;eY@D0003N zjC^zgG&~|;VI-wE0F2CQ6vk8yG##xX^GOh)sj7hy9Rjh0z+~?L1_El*b8Iy5 zhzlDZWh0GPNvlWAq<6wOBy7Ew8a1@SncKk;3=^Cb%&5gfaFhv%$8t#JgVu!%k)uN$ z%wKXGyG83Adz>t{AO@_b5VBa8+yAEA6~MXWWVVPNzjMPXHK1S3*PqH>@hGc(0LVup zMNWqz%?0kcI@)+}_iwqmfby*FVyqyZ0Z zVNKCSo#J!8XLg5;?^@fW5WFCGB&#I!(5o^=Kh3v0myzqQcq0uZJN(Z4oT)t0+Q1oq z>V*IV001D;O*qMs8aZZcBIyJQBqRk6SdIcrBwZ&80YE~4RgFU$^IT{V&$Bk;O6^5Z zMKXv`GX+=j9I>Ck0ifSbo zTe_k7_ReX~u6uaGI%DMx>J4pv9Q@1Ww zJlgC4!f8CH!GnbrL}2or93ZJil9IyhonkTiqEcyfP>Kr7gxhD}#K*3z;@B9*8h{{j zC971Q=u;QVPupM^iln+Bta?pUj|wiRo#F~&i*pcyJaGy&H0B_U&LSShj&)F|p4X=7 z)F1#50T_hIX+{VJaZ4h>Km!6$0FW#^VMyAeHU(i+jg@amrZl)&)OD21z-)yL8pPPG z#$6;KWo(#n7>Ei&7a|C`0yLOBBN}L;Bv^F0NzO}ND=eIpRaEcCn<&{EGjnRnW{Hv` zW>I2?M3FRtuB;Y+D~;Bb!xWi%ab#6M}cNY5kTN-~~1^34dBE154;JUa=rPO$LWy(J-jAn)9gQ zvgq}3s+bFETe0qt>{LO4QY&b%F-u4-?PXh49M!ofVXU+jwJmdhZyp}A4mqQ1`boMq zubnRPfEZ2L7Cv&3(-G-m))Hv%XQ!E^L)!s38*6X*p32tBKPxr-w!iiNY6JD&zn}gu z{_30o5D0{uN=UoRco4&e9eGtqU~?G2DLCoXF<=EmqhpX}f}(mISR&zS@wxjF$Az25 z;WoO4)oW|W5%EZzv+opYWG`X{FnbOGwXTW z|JSE~?9S6jyyyROKI0yja5ty>@om5bB%sow%=B2n0Z<^MSP;W9;|iP&VF2YQU|fCaf? z((6n_;FPPn|7C`YRW*xEjWExm86oSvhon8x0?$O=7Eug+ysGE2mu7tU(^rQXHc-#0 zGxDFrZ{5tIWXOoiI0$~^e*f~3CIHNgt!K7bFz!gsq!~ukIybOKO-Q8!iK7-Fx?x0N z;h+$g1p$;|qKSq^QOKGxpNe;Donk7eDTWr_vT|C`=~%f89&j}!Et73F$@pV0+PSf} z7~}8KlsKvhzbO(&wl|dc%IBPl&J-bt@|EB+>T?p1gwRcFVZQs(PChg@cO_|JO>qJ68Q8PZ4tcaR6vFE zU5tzte9W817s%r%-M0x;+RtuCU_k;F5J*Gnu4xd34Tsic88H+Zn1EKPImd zsX0bfKQP+RVM()Luu7Yub4ddrDh1GDq|SGm`*@kqGf0ZO_8aOS610Fq*%|W4 zm^6qWg)pdLln7DD)dGz4>5)m9sg*{SSr^wZF3TRX5JD$wqvOm>hf6GkL;w50M9qK% z#$wd_3^RJ5Oj@61BcD@QlUwX0(W-7S>wTA){j^=uL`OuTea!sBty8HZD(sZ>;x}~* zc8C9KnbF<-+S_+DY63o$A$m%M zn58!XK|nQG4YPn7q-3LMsf~pJIYk`A#Tdy3XwMj*By`UG#cDv*#=@Z#_EU1C@hNBo zyHa7LQ!+qs;zP?o)JUe;JiiuPmd~rU%&aHy+X_j^Bqax)cguK_P)BXBtbeL|S$T5U zQar@&HyL|3z(buJ%Fh|`w<|oI|Bih&p|3vaSV&~W!c~bNIA(@ze%jr_sXP=k0M`Qm zO19)k!0n{SMLbI~OeK=Ul#>O@Fl z3odCUrzokVk;PF;(zFSnW)nG+Y74BQut{)~&nBf7S~Z)BmHraAPg`8zjNmkW)<^{;zAUPWpLsulnB@!f4E6%g>x zZrfWph^E9DT-ia}1WA2`LJN>EA$A~O@j~E6bbNi_7-_LJ=1dzW1JVkJ$HYmtu`w{L zfd)#;t}mz5QVZ9_%rZ178#dN2l+D^PB7_2n#i=C3B%-C0H0E**4mHW0MYl3dc_&1I zB|Y}}c&#)y#`i5Tns!;NfoLAc{g=C$m-^05_M>VuWVOppV@xfwN~g`kCEX-3|NF2+ z@c;!FXw~}(M0lZX8n0z0n-o2dRqQ0us!y!x{e+G^4t8;(5&%)1RL#jGNy1bMNKgYe zOfi8L3TtVg3T~T-FkgTG)Wv8t_;Qi(ha3?p7~?@{Y&E7(6B}tIh0U_!c{golYHJd1 zB1~oPW^zlc9#(Dj-ic&%va;6L#X8DI&r7V0wXml`8Zg43tbwv^iY6;C_9#?Gk}O#j zDWTP~D!Ye{2g3z!k8k8+D_BBqx}3^DNzB^os@xN<{>bEMx+4fGVH8=5B4Cn*pt4P% z+F6E3LkaMM%Z$D0SeRfCo0^wC^J^Wtw6fVXS;6Syq|AuYLlco{aPEmBbC_qkeU_R^ zTq-Q~7tg)f<*v0EjrvZ+dTAQ++P>Q45Lc8-gDudJ*jUmvty3Gl+?jEczPEFCsk|P# zO!dkKl)r{%;u8oX+8Z0@C3^?l|7WviZ0}-mwlrmyV?;`$ka-|FWvNSdy32)e5|9sd zhj!h0#fwVhu{Q;%NTu)`I$#2~3wq^0%(=stJ}6_aQV7r?Lm%oHJC>@%-w{iRktUGg zrk@hTsOG{L$)u$#YFVpM6qJHI!RfsQ!?83Uk$MmY^y8U~p-03-qo zIuGLIS0#=@Hrg%P&gG1+jz5l@J6Wr#b)r*QTYMWXSz(hyPF*CLSU{<^Q@Q{9 zuw>eR1t4bAYb-R{ii}F1Wh0$cjgwLAFw^Q2qv}1A4sGpy0m-uYaj^xtyG7w`;o%=T zOoy56Hu%_r;mqdD(Ijt9DI#@uIshjXQr~T)l7KxBhH9!XOf_Q(`nIXEOcWBT#0^a4 z;HekVYO`3&rD578D2%93)+m9Ao@V{C-!f|@>K6pf%H$HODMyeB1_9}XmIO?GwO@r^ ze)OrCr471DqeTV-j>s4=tNpYBJaZ$2a3A=ug8oUM4*v)g47LKa0zCm;?Y$z(jo}J0nt*G)W{{zq;eMhKaGwfg6A@F8JLtv zkDm`+kdPK9T)P{F`*?L4Y>MW}TCPp-uQ`-uXe{{}G+qcB$e~F@CZP~eH)SbGcG=U6 zCPz#Ax=FFANSDxz`KLO{e({yC;>p93BwS+tmV1yVcI1`wCCpXqq+Pn(A{tP(oN9Bx zMerbQsG9&7Mu&}e)!9eTWBKp`L8b3ymaNB_I|HEtW5a%iG0>7?(oqG(*d^cze5`IyDyaT9 z%259*9p{|3IUHv-)N#dpsG^WvHU}hm#<`MeCp1A^^c^b38NIbs1dIAs#D^Op}MP>J!54SD%ileil`goCZb2A+C$+5Gn{}bQp zYek@sy<}O3!Tq+eB3xFUp%wscBo`s&JPUz*)-ZJpg^-#|N9kxGk(p#Int>SfoNKfW#%tcd80$^B-7#uuW3D$jx|n*!Kg+N*VOKLUIqQ^ z?(C6T-u2k)FfwCNNtCz0He{TMl04aW-}SPdJ^0M4o?(d@Rhc)lwaneAtqBTl~YpTR_>YN|AJR-R|+8+sEr|FGRhWO2Z&R2rX4F0D!E9uU*)AI z*UFcbeW)fVBu41D4U%lu#bP4E)45$nzxxy#HxN)P38tM{>*6dH^LOWmzO?B>*VE2}G6{TZpU-C|Pql7@B!{28pKw zAY*a1)fD5fLqmLNT?jgz$`yxDgF{ckfYSu3h#FZ55L^-q33|;i@T^!yML=k}2`3jU z=8-01ax1{*j>s=AheQ6b>ql2%Aqn8knP|grS zzQQwFaJ6{?F+t_nX&TcTIcJ_tn>aT55)iXa@<^MeepYpJ;AId=YjuL= zXI<(^X8-%JWc+{yS!YlCNi_P02%1l6Bac;qn?LO&*J>T3Xg#!!r8RBV&%Is^>-zJ} z%IevI8`v^i5{qXMFqqPFA#iDiwmugf$Ehpv`y8psrBKa6kYbz0S4ezFhR0_Pm@w#W z!(J3O`%lkZbaCBfhe(_;Q>Zcv*egr@8q+ol!0guqhG=(_xJ!oX=|t4N54F7mBDeW` zi90C(P#<9;0NQ?VE=Q6Eq=?5CB@W&ghO~)zS@wb)ILhN#6#HO{wdYq3vJapX zisi*sbS+v@+we8Ea@MP`W?ueK$CyhzYjBAYGm!yF%Ak_rD0=+@6hehpN65=&sgXbo%gk=B<}W5dJijen)Ercn?ygKGL|9JK~ckOo0+F9afD zMHI?GO(~|ACQC?AP3G)dI3=7i5)R@BU1BNvR~Dpo`8Y#rHawcb$Up!vIvfGSyP1KT zi2)1)2%IHlgUU?-8jh_kR^!e*&L72Iff1vn&hDx@mgU)s5s83G3YjqBp%AAK)wacXl9`xV}dJ`41o+gWx4_o=%^CHC_Ehus;QJAZHq!u zm)It}bZB8hA`$ZS1f-;rfcSJ${(h=S%@&MGV>r^mc54}vw1tLUX_%)z=jfhxmc{7# z{RPZbRZBR!wQ=ck$_G8IP>v>EB2|}ERc+g;MvnH@RJw&%Ng#m|=xZj|{}H{16i(Ky zl1gr-qcBAO`>YwRh>hMt8UQtxJNepq=zs3RsLafF+;)5%t+QiL|~Fkm1C z2^a?jRu%&V1LwkxG&RB+b-L-hP|Myu)@tvii2?;fMq7UNX@*`gfekQmQ5kBGKw-?V z0ZJf3LBcmOQ4;yTM}%275~HBGEf&$j!toDDMouLd>Z!vUP2^v8^@Yh;vgtpzHdVv- z%bKHriP?$nxxu6Rqu0RyFNGXPTEiu zNK#?L832P!L?Mw)R9zPG+DOQTG21)%|F(Cr>}gbHqo2>0*ZG-piIB-~kTxhRPEPvl zW0dFUkryj0Ups2)vS9=i#L=wV+iq;;&|@qKK5Z^;%2Y(mB+WI0808Hbgp`q379`Ce z*PTuw+=++8b%EJDJVbn;)X+*Vl9fQVtq4pm!a2u1E1E@gIQ0-WG_%L9Gh)K)JZhk< za7$esU@kp~($)&)rT2xU_IVtW-boBjEH$8F2_S)ztecx|#gtggnMQ?o!KR8Im-O8O zNGn{bQPxgG3mG*X#IqkT*=~yt2j;U zzbaa~72`gmQi^C&mfV`Y<47tKm+qM~FF*jM1d8{&TQH=9W(yghDU*YvmP#5D8pXUl zUHjK8b__>+*Qa7l^?vM|8DLcCp`*U2PWX5u!=2rG=MW-)#j?wynlauM|9dL z$_b!|DiZdjshiiF`vdH0TeYmW+BhTh3 zRSqq!)4*&}Hzm%9()lEBj&eD-E*BFi_Q^JPY`<=rJNjxf&jt}EkyhrmW%p_AH(01} zS4u%jlvBvY4N45NMdft5?@LqlO^p&nOPLwA%0#N^36i2hG}!T9aebc6n&vKjdCOqE zdY)DX%o!zYx&lNYO(=mXD>T0UuHY3;Pyw;N3cyMd#dB-bFftb|Iv>a4;?&A3R?jF> z-pfk5)+O%dtYpoOmwRc7m+txrXt7Z2c#wg0aaSNr;Q0qo@q<|YO-pkodOBfB6?B@q zF_f5@8}rU&b$eq43?g1Nt-SV?_^pq3o$NW^%B10vvu^wV0idfJc3k6!_Y`2L=0*Sn zGz5bILd!-fLKP|=8w?g!wh&*$uTuwUTOY;)HpSLuX6Xy%hw^i#Wowp1mBvf*0`QAx zht?xl+n)EWvC49&^jWX-AC)OP`*NaEW0-~5m3AGkeJr%?3Sek4Oq{zOMG0f8uWc`>rWrgqU**qh zSI8l$Q4NJ>y!lZ$wAKk}Yft_$<`MI%hGzcpyZyj8by4j=8KMv*6=!}+oODGj?1U6* z5D~&n*z8t6a`;^m3XdFwMbjcsnpXEmhRY2pE%kdFp;qoeloclg9qhCsM8Tovy z-sme%GS@3ErDY+sE^`g*P)SX&+4sXYj~;g$QDpzxrDbDFt7F&NpLY5C-u@c2 z<*3?ux_nKn(@|uN8kV0J$EIysW@~)wjlr-< zg24tpc`&mUgad&FDnFe~&Tw<^vXsgo69^o}iNZ&Kz=42-&>)N~I13Wk6gl$XC4};B zA^}&hvf;zTQ_N&hVgLKkMAU!{=hJJON^9w==oSkOUdC=6^Mi0G{9z zN?z<10}+;9IDQR4#93NRVZXlrHpqJtyye4doy??x7;1`_W>}f5(bQxqhX<;TsoT4U zZGyN9frmiuk-l8+DN_s3b)!+o{)^X$7k`;YGp zW6gi-_xrt`JpO+5-dgAOv%UCX|IL4Xy_f#?c)LE4C`uz~GGG7zEF>s$@W~Q;B>LLi zB&I=Y3h&Tbz#LzTR zF>nPWU1eO;{o5Vs=+Pr@y1P?Gw{&-RH;9bx?(PohMrrA8P)d+Sq(R`B|7S0MFZVg$ zE6;Tfo}lTHRfBdaCMPsjW?&v&CPr{QIybHiqA6Z$_;LGSi2x~EQe*BE!3bLxwprA1 z(NXy2Vr%K4Dsh$XFi-d4eu&qg;#4O;Y%B*i?PxN`z)kI&F^T_ru1?g@1p08Xd;8&P zwH409%^V&QUdnr&voCzVY|E8rxN&pOS^b6cN81VSYt}Z4W`&yHe(>D1>S}#9C-gt` z&;8L=|MqHf_tofE5IX1Ei`nzb?%!V=e}jS?vVu0A#Bv_C7uYl4mys0?HF@^^M2be>TYYspL<^ym>K!vkn!>Y@&wz4i_u|qk2WO;=~hXCQhRPfQk zMQ({`k0Gv!VDfNVBF7|-;gPtryor!0&8(TI1R{zWDa(Nh(h%^dsf4MitF4<4I)^0- zhv^V9DlsUlIE3QyrwR)iMbaoRhp7w`&Q&(WGz)_WQSvhb2yp}vSt5=H1%Et5Pfh^D zglWuy!XP> zpGoeCJOdHJB}BLKLoSiY|k3NK%H^49zt z5&aG8+2`xqzu#*){A8P;bNE1}?zU^0!&Tz{zCJqQQ^gv6PU!e_BPy)?#|#002s@Ai zP+-!^!URO1Ve!Mk@xGcvN(pTuJ0s$7MMY5L_n;;Qw5%pkP3F8$`D8IH~PC zd-v*nZ;#$Y9Z>RzAxL2A(_Nj%e{Zv23*Nr^XJ(n1zuB?wMmh)bh%#pk+x=X~Hsrr%8q$Xl`Ob>K2A^I~>*Dt9RL!Y2Zt5>EkR)F_7>3E(VY3I@TT#$7i-$D0q zp5Z6k)soj8S0B|SH`~6_!##3oq8vAAFBSW7FgC|p#J1;k{Q8Yq{}H7nSq$^v_4a z=jynY@SN7_?n(u-8p`~u4}VJLjWnlKkEo)Gk3{v@-U)H+V0R5{Aktg;1h8xv#AU1X zdNvMvv42um8_u_L*6iw`lnTz2v1CCWO+r#+F;#fKrRJ^TNu0lVr)R=XY2>w8;NNw| zK9GZ5G{TA0eyubWkE_r)Xrb5Eg9H6V=YGsE_Yy9bQr~JvjhO z*iGRY(VZ3q#Kuako}7%MIHh!OUx>ntq-}|b?BErEdekNIMFR$Y$4f2};}#)o{Iuim z*P!Ega2op(v}1B1drd>5*px|hq+QtQr?ZlA|JQzIy-j48vnMLc#`dPT&YSAU8l=-@k}trPd3GQVZLVXUMj( zwMM9TDBs}fL~fOlBiJ#fa9lJO$e~#dPiAJZ%$Cthotv@m)E<-)V?RMwWx|z_kwOa| zv<`(@##qq$SIvjW9OF(DWjRAn;@&0r zbr4E7Xs*XpS{{W?&p4n+wOXS5{5iG7h}M#llyqxN`*a~<)9fyuH#tVh5d6J_V?}k;Jn8^^7#)s@nwp2*sLvnwQT$<<;VA~)tYA0*F{&e4GDkc%lhx>%w-o&* zkNki80@Ghg&N#M6(+bd)KSz{T5RkY|qcUMuEmRc@>+Gt~X+%gyoU~CBun`%k24)L8 z>nCyi_=b@L7~rj)=P<+o=|Xw%k_-`L%p@S@rC1>b=GbOn_`mUR;y8i#R46ts@!9>^ z=sHqvXq^B3=k`O^uqpziv1idVxoS;Fn(oi+oHS7N0m0#YH za0bk7{}KH?-w^J2eTekvk(smk*9*bCB7fK}I!2j#4~IhW}bHM@qg(sPxiD?-7SdLccp-fsdX16WJ0%9%RxlC-~xoF6Vr;1kGa6JMEe`j0aCG1 zOCxY&fY>NnL{g_XwoN(4#VLxX8Pyz`+H#gsn*Q??;}YENCA{GYU`TZ47<$IwLyMz( zeJ7K_$5#YiO*@tMfkqLIO`qQXqvx&zkgm)ZVBNQB90T#CRcJ0jak?VxLal^@zE-p~ zhi4flFX17w5^pgL4XK_YG(x3S-F|Lfg3I!FQP?{le-NbCya-dKA9K;3r1r}%A!Jy zN>kPZ$|2_NsRUuaDj7LDY)TJ)n2)=$4vdW^ndQ}UkuxKod!D@gB{n{@P3u4O3!_u2 z8NPOljhJ5nQ_O!k2E@R_XYjt;AQ}YUY=t@f4Gy1Z9T^8bJPob@h(l9RKSc?AH>Vrt zTCaQL+ZJN!L@>kBS!KYDA`nX>oWMM1DjQPab9f=G`BP+tZ^-2P(?fK72rqv?zP|2~ML-98`>oUCZFapH z`L=i6lA1AQYPa=Ke>rw`dB!>$M;j1|BM#i796V7^LZy;!CMsbJLu4R6wW{ZQG*Nz^ zLhc=4ea@#g?xjnGE|3M5`z4&9&${62)iXg51OB}nOOHaSqy+j|F13G7WsR68HEJfn zY`D$KT6lbh>@3-5rH2rX^Q5W$zyh{LFOZOEHt`UM8m6_5&`-9`2&ah|`OfCNP!nfq zZ(uJOg;Pw-lp?fQhNsCog>Kr6CX-b{2d_<+!X!&H7B{utm)U}}$dprz)IW`$njmNn zq(@AmrqK-Molj_X|1>RwcokCV_$c#5`YLZDCuvRMis}1=;U89(9JQq-pTH)*sE&|9 zaO>|An=T}RtB_Na0sOGSwFJT`0`)2N#v<9m?e#Ivdfj2=PrqkWD`@t*UtvBL3QecmP_0DMAPP8~{ z`@#~uG}jH_skyYltr?pypgyHVqgV|Vzn=;2zi^!3?V0(ux`Fj!E0YKDkw59GWgJR8 ziQmadS)Tg$sVf+X$IMO@#g)=&rP^MGNu-B`eaz3J5|9~q*ShR18KZ!{wpRusEu6Mn zS!+^X{8e4L^S;cUX~D;CdmfCEqM796|0(W{#M<47>9DRE_H5PqLU?TCaaKeM%E<>5? z&5MRIB^{AWML|&(5sF@pzgnD6{9jVkYNN$Jg<~GcSrLCf(>$IZ)T9x_ZFNMJba??; z^G_eH{aF9gr*03*#Yq3L61-sF5t2wjV<{7{;Y7EKf?vpqLcVof;T2o6uIbg7=V30zoW`a)@L4!X(Dcq^6qVAmlv;U4}Ip z3+_g`#d?e2Dv3A4NdUkU06#SjfxTB_4!e1HYyd=Fm4#DULKzq#UjO}^fCaYg+3PFi zJz4~cb5jJ_Co^-YYC>yN``E*MqtcX*Rg_CuBvfyd?W+b-gPb zRh;}Uh6~+i0}m}9XE)tZ6-es!j675HbEWg=VnnItcZ!+o*<#f;H9jBD&U@Z&v@9(C zGx=K>ZykO9aM&VWZPy6TEg>DNN9s9YNJ1Q+p`ez|TryMSH^nAZwzR+}LnG$3d&n}# z@llT|6@`<9{p_Gt!Ft#9%IRqA7fuX_cHf8`m{e3;R1_MGu^3~G4;1Zfir6$7P?Wew zhg`e3r@IMX>=w2H`N%Tn@q+K1doRzI*ZBWV2ix7@{Cr6tVLUH*4$7l`+IR^EAi?f! zv>dn=T1=I(V^gAtvegpE;4+oF=8gu?cgjvX*J$?;_tYK??@D^4OSSa1T_*_twx<$X zi{(|^@M3EEk%Vb{;BodRvsHc4l5y+O8qusRcI`!4s#2QH<4wP$wrW2<#drDKbLTVb z#wGjt6wG=W2cw?0-@9yymc+is7Y_VO-`h~6ID2&qd7j4TXyR?FhycBpYT4<7=s^R8H>Jr+SZeS7Hl&hLE$!>P`@fM z3$;%nR5kpJI1GnD#zjYLoRrSuLZ`7RqbC@5&RU+T)FRj$nHYc2cDdRD7nO)Btwyz0 z8bdEt5ZjDQl(lH2e-HMkRDX0fYV%fc;vLx~XjlHyyWg&9=_1wb|HEEmU<&^N?E4Om z;_Xge9(|)>+4tXd8~lPhE+K%Tn25BW(X}c~P{5o)JYu5411tGm5}_`HgcAx9pel|) z1magNSSt4f4pN# zQH*b~Rb!rSZ(TC>hitf0tCcxa6y4|@6RXoR^C^3XN3~h@whb(+dns61O>PBKx1>&p z`{sG9!3aPYsyoSvQ=T;`wGx_zmj`Kt}zSzvp=Q9*vgru}j*IOWnE&Z|Wq_q86 z{|97uTgDT;<`sLDUSa(hG?FC;-jSseUy)Z3h6W6!65_;9FD9T`OkkT@bajDP2g4rA)K z=`n-IHa1qzlrC%O78v`Mb{DHPGm`ROFZ2h1{N7$|Kbnst{Fs^DcBqz+GFmYnCLc!l z-x(}@JLL){skn?RzYgmCskIO@V31@a(vE;85+ney{mCmf5}s?7T`4987M1VY&x}Kq z;ZsnjKg>d==$@HuZc;Lg&v@?5HR_$<<>=H^nfm`;>?hVGVt4a}D+U&;jw>7;xTVt^ zez(amYF6j$B9&L5ZJS1LLJGyvB{Gf28%|4IvgXzm;H3wlQ5*+h?#V+#^3U%yUr=R~ z$gT8+YN79+ywC1@e8esfpA~*nP|%((0R_6vqL4s5sBLUJhC7%$cymQwd|u>k#gpju zNgL5Zw`C3la32rg+e0V!sms-Hp``<1I0K|NxOkt5Sj-D*@doVL&{IA`f0nbmdOgzm zTE-wlk^x+DX;L6?OUh)9(%Nse{sEFU-B`Y5L$h1_i+kf!`$Kj>$DxMHgpctUPoUMgjJo#Hyjq-uCLI{h=W9jw89AbG^h zS(TlGe1*`CDZiEZz_9V{hJ2N3_vAOnjBw=I)`0kyKZ>eqC1<07+2QIY%3900$IRa} zEQ_K=Sf<`DSZMZQ;Tw@{TJUV0Nt$&`bMr1WyXol92JHW^S%r7$a{In-w6C-*wrcWV z%$Z#{J+u3IMYV9Q7SO)-+=tl?MFzmp>rXXPfRn4o|LTt%96YU3-hD#dc|&ZJF8x$6jq-3%k!#>i1sO=_%P{ z^9pqvJ*195eqaAbNgHMG)aJ>rh3h=`@77@fiUc%YRFvnL%dC64G>Zt(G)w(wQyUV> zY1dRibd>F~|NcjMI>)JxtP?s@9M|Sy`aNH+gTZtlvxmvonT^!;hEM1B9lh%h&%W<1 zd&P9U?Y`>#wJ1UaB4JyP$>7mTE=bCds)f8yq~n>N!Es4M3TEqmS10fO)dLKMVAkS{ z!-b@#?+;-7)q>N*2`dX=i(Tw(Z74aVaAxg}YAUDK49WJ@MBqm=QnNDVwO>W zw=Fj+`?eyf4kVYG>ya)q|MEBLVYcZ!$AFKwaQ#Qz#Hxik=a=-Vvz{6L=j+cFUa!Wd zVz$?HA|Djs^jg2FyvA$&rb49EAEu1!6sM4XqE@#?50(tcEN@`y%G!nGH;Q+m(=w}7?vu`M6|{UNAwGFWkh{gEg=i;F_e`Nbw#52g zQ{DXdGM}ZJyXHk(DvcFA(!A-hv9IU@lc<++oG{{0fuZ9VXFn0d7B+@Kv|16VFLmq4 z@P2%IsHAF~R@xUdYcBG|3I?31^1Be$gQwVHFTOTO4!AN0#`;!)0@1BCGs_VMA=2nxfZUc5xA{l*3ng z#=HdjRaep^?D+|Ee3vC%d6)_UwnLx1ZcLo?>UgduVg#p*Cn+OPb(#a8_)Kibo|M~v zXMa(v@H0%0g=bT|#G+Nsv=`UvUh5*86Z#iSFQG`S78;65JachC5_YTO|8$XRVKSqu z5{#U6qG6b=M7iKfi&DOTUgqaP*1yKDYV)3##@{pFQVpL~Qy4ED=(K+$j7JG(3-dE4 z&%kA26n|i<9X%R1x;xD^_O1R;rYD?xK+?m2hGe!1CqGiyEjk8Vy24aUl;1Qrr_rKY zhu<25M25+GF&ax?9^2U}i1{A5*L~y*-g4 z?f~Wx85WA^MHb3zY26br3bxd$rgXBPi!g&6;=ATQ-4!j8a}0VoQ=`(QCE{1rJXfW$ z4z=HTO8_?IV0EGB3bom+n|1!UZ$`hqB#%0NlNgMb$Sy~a%COifaNNGt+pM+8Jf5D) zI;i_C^CkXsTAK50lC7l64R1G5PMGBh&La6I`pzx#^rU&GOHLs!j2wZmN{>MB@e+vJ z%G>+dvXjvliu(kevuqJl)L{J+qWL0pI!bDJr9_RL-j({K$~C~)^ZjFba6_V|$s*pd zuOSy%IR+46dH}WW549UPmvU#R|McCpanrhZ$t#f%SdITb2~oTSAH^Rm5|+UJFN1Cy zerNbZ87dC!vkBpkidQaW#AhcR&lhbR5M+1&wU{;v1*VkkAy^fIi){>o&8Rm-LFCrh zU!{p14YL%MGo@|lbO`?Gn>!#vK*+#)MDFExvQnppus=y->MSk+p^+J=9(PPCYtN^? zys2k+zn||@sOevnWgL3o@w?cl#C_EHuhR3s%5)BZ6bCZ{bL43Zu{D61f#qxO)~ZZ5 zVGmMJ)xmn9{CW)PW#g7GuD&$RdvOMSZ1SyT!+8a%Xq>&nQ*CbhAH5J96W6{4=|x2p zkjJcWAj=0Oi<3RALpAnJEO`HIhs1pKI?Jz3ZZ4H1LSbm2iTUr29jKlYxFsMsPLjnA z^;V4}_;xCP%~8CjQQefz{hlAIDGU6BCpks zz=pe2l9jid8?@*~v&B+#PyMB6ck7w}p|8B%qCSiw+-g$AVeQ zag`-DHiH8{iz|YvFmk$`O83H-&I;b9kXHki=;^k5zQ6X82;Zu{+tlk4bOEl?v)kY7 zI=~PS3zOfjJZsJ;5BB`JeN{c^qoPJRovqk+1M>%}o#JMH{EBE@k(9|bxHD>j72%;s zaoEiZuF9ayRLAuX)k%)di1l>9^x6V^=IKcg5Kd%AdQWqti(itl5<2H zO?}5b`vz$c(a+RdfpxJaNXDn@cfb1Nw|;t^9EA=3b(&y;HeP5TVutayKi*mD`N(tKKTN%WqLP68vioe)X8qYRQ9pQox~g10ln)_|>(Asy7%a5HS3OyAVQ z5&>jEh6>3QKu-lKhDiD6{o)u?zyf)}o8_HUijlf+DFy$l1Ar zc?ND4>jKXF_LKikbiz6)iCevpU?SZr9?kyYI8c=CTIV6)Jsp$VZix6;3#;YejNQ{y z<}%XqX{fN!=Hw+kbBe;2Fb7TrDbOl2N81FssX~NfjgV14zEtOQ7#V%jo>8~|KPI4M zRetmJ^)OY}@#3fNC)S^nHU#wPSE_MFdY8FbKHj1#wol$oeM>m69Z`QrmoBNKR%xks zjKZHEbiGMeyK8y1t*3M@=qF>{>?dF(I`LyqJ{=1qo{=Jd_>FX!xw++NS{g~F%IR%m zdt32Tj-qtNw~-!7@JXBUI8*t;KF6U$q}c4CePz!tLdP zCQpxhjD_3T%G$8ZL+YurkT4qc(mCwBzVh84{_0V9Li`G2EkLt#xx$p}DN%fzm=8Xo zpqjH4*vdTafKg4Ff3eJfAPNJrBPp$si^$ZhZ1E+{E4YQFrr%S+Jc6QHnv=8O_g;2m zNk%lC5uWPm-Hyj*PPjEXFgn*8QnFuH~>+G%`#@aQWgxNZ+TP5 zYwm~^T7D6w$#M@Ke(Wrt-M27wy%ud_OcIg_%+P{V_c?qP>xha3DgXQhTmX#BZC<@G z<1AVc>K%cXvXw>&94Zo={c9Ku_&kI6EbvR0Y(}y?r1y2oVA9+C#J{|opA+PZ9)s1H zsL0{S7WU5d)AutHqn^g<`_pm z<6p-*Z}}WOPD7sj$L(1ic@bStf5`AsSzpSa$5o1h_#Ra?q&t>!jBR;s zy37YClC$Kn1$J7@Q9=(>Qd8-rw#4ZNH>ZfHv1C(uBG017DW+9TGwW}cmLyVl&g&C+ z8Th?MjVexChD8FObCy2~BITa%ofut{9{ih`UICCk*uzXiNq-k85f@rTITJkozuv56 zE3;r)!bj#Fx>3LOW0?{27%aC9I^8eil7#{2LfpWbg%8+N6woY)(s8;WX_Z6;_UCFf zEzY2jmi*ai_QwnAvMjsY&tW1``+zj zL`sG;vvK4E$-KCgc8}4H_lxD@R^DFwgXYPC2V1+UdaMps!$N8SR}6>@+Kv?kvX7sN z%QZEFpRIENtybN+@JaY)I?YYz6c1tuY@v%v_@!!~c5onL!_laP^Gv~o-I4->UE)LL(}OZ*3ixbH+QwSf>8#;~3*>|V^AB^32FZ44u&BWg;d z&k{>!9cD7Yy}PWLZh>>~PQobe=1T!~d=z<#6G~>Gc|=a<^6^dJ31(_X z*pl6A*zrT$m)(c6qjtP^2?fwB0}-}Z#P0ca)dQSE#fp;^lx2RsDSZ7bFQdEilka|` z?z}aM*8AGUXzn6)O{`?rz{o1Fg0GhR)9>B(1 zQjWrRSwf^b0Gp4;h{_e#FeN?*+$$d9{z|w!yu6rkUq37g?#b<03}8J33|(f80V6V$ zr@x&WOuE@|h4y1y2SxbYJq+MBiBD0(HOWy<#5AlZQ=93{tkka!si(L(%X^#HDB@HH zL(Q;JG;!#RA`F&TRH!DVe!2;2Dk5QX?1)pNhFSkAQR^QJQU2XbnM?#8-FuZ-8@GD@ zk?T{a#D7N)o{Wz@wlDmByK4Y(0OGlx83@3n6{>)w2M$KbuwlzJr59vKT2biM87n#* zoX$VsvBh>kq7wYo{El5}ld~~B7!y$*%~HU;r*dSU`;BMukT|Ty-!Z76M{!hFFpX?Y zixW4)hW08A3F!iaZn*J|#`T5nC<`Y|z1~1$9a9tS<(!Jl@71# z3{TW>aJseeVcMqjiiCBka431yLls0B=%Fyz$^nR|n z@`apUU%%(GtYp6Fm08VjL}a@Sb*K|k*I}{W5Ii&CIzz8gk~c*PW9@AA(jU~oyie_) zh6{xsBTM2J3}E7j^5447(A{@Sedtw;Zh0kn>@R)1Ywtw1yS9TaeKpO>`%b=X*jk}~ z3&ZS$ZMOh;POEfyR9Hx;2w_u=stoYvs1=P&9=oV#a%`Kp97U~aN_VQWYOt~^8s0$_ zQ)&4YP0Ou@Xe^4GrhrCQ-~EDG#ewPH1Zxp0En~h2lSnjebJB;IhQ<*v{=e&31Tzy% zM@}H*HX6lVvm$r-7e`D&N}@1z<0isl$&KNNNGdg#@)K%lzKabc3hmbLd%CiLq?prn zW?b96PB>wv9wGa&oxc$tcDCE`Zd^4_H5>2Nil>$S$tT_5ScilK?+h~gaXzj}Y>~UT zPi<{M08VC|EF767RYNro9P}|{`=W><=gP8yVB)@mX*mguHd`mRb)$xRWu(Xw3^JpA zT_a4{DjYx^DhRRYFPpb+;2E7<3eI2ZwKc*N+qQ~*PZfvb)pzUE%JvBo=(x?{2;b48 z!*RP=aib{+GBTST>Hlj#T`n*geZ=T1h|SjAa&JH97Cw%U6#ZIdp5#f#Sr&feng@nQ zgBw&ZTTaT>+I)%K? zN*x3=8f$z)EXA56Y&e5bJ?73aODFGB)zCtu$?aKaqPZ|q`m|i}a#DlKqm&1##Paar zqPoJy@*AzQesv3fOdiQ*|B=7su4(*`A;Gl0#Gh6Nb>I43>O8|ZtwG|iN10+s-BoV$ za%xty&AD7xaG4FS%d|)}fShS^a}i|0_7@P063y$Y2N439wqT0XC?Uep#esh7L4H@O z-%nDcSw?6ibCGBrJ)KWFJLVJvcjDgZQDG!zQI{%$!4eM@v(BfC-f{^s@}ZM=Iv)aQ z_u-%14N{h|z5u`eH~KzFbU|uI5yU_xsWyX%T;*7E7{)O6!)Wz`A#=AtllAo)ft-Us z1ErovPuinO#%AWLB}@dBgM+c-`Aw@R1_;2mp8|rIkS8WrxZD@|2S)MGKj7GuBL){y z@VQ?Vv-;2~9BLuCHgh5v*03KYrIT*JbJ%Qd@BKhKUj4==e?B|aNG7|3Za zSeBEd8_#6R@hcVI!TaHOS)YkT{pW7H4AadZs=Ai2>r1)7D#`LU-ff?{V)y2z>4Ck1 z(iyK$E4Y8h0$+^ZriTD-F2JgqFh=e*U44)9fo^k3G`mefK;=^@Ua(0sKbwS6BD;hj!P0 zSNk&US7GlrMsg-{Ht6fM*I!uf1F$@VlyY;zPn1YR%Oz)-0wPD}^%l6G6(#Gr!AZbn zJgFqfx`BNzbDPK`7ruP=sF@FnXj34B3WZ9E%=HSuA7T%gP!2}Uee91RmN+tzmV^S| z_~$ZEX`djf$)H|p5}-h^uHB!0_kONe;fxH)^UP0#rz;=R{MnX)RTPtTjJ(H{YWl4M zy2933E32$TbfS&{j9;&_Pl^(ci>-8#%C}oi`*Wd00BE`n=cRaYq&$VMuI*gO4HB4$ zisl3`n0nAqQw+(|Oo_l9r4PkPvRya+p)NYKT*iyK#gYRnVSlX{mJIe z%<*Nx`tUPuit#$#z>hA{YJnB!x!2M(#GWowN<5wzyy(eF zLG(#*jA3ABc&-;oI?;egOtG+DdVF+5k8FLAkl1ZWWHtB5QsOgRTZe;>>Gz%w>W6N3 zvCToE52gA8wv-qv4VQv-t0!pp8~;k+a{!W}1*`<-;mDfOkFyK?gtr^5aV|0opZX8l zC9d2~rxy&W61Mx%NbD~`_pLC!QUIuQIt!Ua%{5DvQW68PZD;G|P5ZUOW+4KWiUBms4rC;l?9%S=5}F7Aay0m?W1^{O za9vcRS>P`!B&hUWuQUYho6_K3YZ~gOG*qyI{1|kN23Ycr03SCwVhoN>j|P)SlH&nS zQB^nE9bQo@&s?ocMGr|*gnXG31INE%4;xUY_C<9m&Fs&KZ=OM{)_ICK*r_#%5er*- z8+9Mi^}b6xGU_I*iTT~#6{8PM_c%9}hM|O!&OV}Q8X;DVFWWDK?P{^EhHfgjG9D+< zW??xN7GNZ2Y3U+p*!W##TYC-z&;tKHpJmz%GXX3ml1Nm#i=Nlz(C|84i?`kbOFIbB z3)sVOD(*n=wdJfxBZNbh!lT^?5i^%P)km~k$tml%yY4}TwT1Y(4u;dM1T~{>;;TIi z={q~k(3WbvON~pNO_Z*;S%|tOkHYU?N%!#r6)QPBBDHQ9=Qu zsK{j-JDOpLhzWKP3ppnb5dv60FkNG1G)s`jFhpQhzZVeZE;Q*O@hjBtaJ4q!BjLew zBTJ;9xvTA}*R-Yx8f8rO*hI2SN1n#5VS?l5(#& z5tINX`_ERIm@^k#If_{9_8TXxL?*Jr8K;DnU9@3b97^5hTvnKU9U!xEY;w|lKn<0z z_K4Y^i)@bs?b4&_diT6rhX-M<#X%QvxI2-=#fC@Kwaw3(cXBlS3^a_2q#`u>SJu>2 z(^I=OH-KPB*yIy+LKF_0extnx2z!ldbaa9Ak8KPON~ZKA%lN^#(CWe-R)XYAfOX-4 zbtp@Q$f7NX29KUENpr#1jfz`XJKV~yn#tGT2YV_*0#$wHv?GHJ(qGCu*4qNqU@L#%ldua{oK~Qqdux z-Wgq)`}TKq2lirU3FdDGQdA^4(Aho?BKo55;`mT-p7NS(1osCM8hC3F@a#Yyr^Ot} z85}SXQ0>3z#vx44O4ILKj?I_5F{DT-P}zq^8$g;J8^?9YdFmq%!@tXMc$eYeG;`B1 zhFXIBD|kt{3&IksJG{cjho~crqA+iXaJx7y@dgfny!S{c-PHo_yBX!llnE6QsQ6T) zjW0oO9E~uYQ-HXZ4+;fG7@cF*FxLSf20kRdjvHYty68qscL<3X`H;Os*;=6S2wEl5 zj4WPM16u@Emjz2>M4u_cC$KT7E##qqim`FP`YK_o!$9Q{dAv~muhv0q@00loN>aIC zeRPdgQ~BMHY%1rqqyjCXn>vGQEK>kYT$TrNUL}&Mte)c6?)DGP`Zfjz$KThM8%kI| zlV%p~4=M7xCI5l{`t`46(Z68&8!C1C&s?0BBS#4qOsRA5)+5zm9IV07R@f4kxDyJ% z7^xwUiw2a^;7RPnafJ25w8^=ytPUphZ$~xbv9u$!?CiOTs#=^I?+&=`55e8#`}_oT z3Ib%fBTrR3S2wq1OlOhb%oXgLEe)-1(U#xX>^zxjJ|>V1RVIj>o^h|EDR}2Q9J2T1 zojD)wJ+;}ZIa&02PF~b(6gBnO?2ifc{Vt-&i52?HiDGY;azJQ!@pW;^gJ=ScsN+wxJXC4}fY}nu$ z1qJ^ZN(z>Tr&K-D+Hi=@nfT6)2gVv#84C+}vV^D5?pz8l@Q=t-J>~BORomWDLc9Vk zZRe6SwH5izVhi&!=F-OP7#vtdlvdCP?RH1O;fu1vy%p|AMCE32Ml{X=iRD5D@3Jm} zShr5y_b-I}YT}N0BJnl zqfVu0o!g4Cc6pppQ<=!H*Ce5{l&Fw2O&dG|k8LWz?Vxlp@2RJ{}386**SSSfw+CVHj5QS237c+LaE&Pso5A zdE?9>6naDa*n*W!W(R*0FDWT-9FS2cjsS3Tu{%35WcT{!Xf1GJt~|o2gBOYvbRM9R zQ0*o0H&5o?^{q4u&i@5dnBvWV!@s6$F0m#S%W~*5(ZHqRbeTa6RlQ>%Y@V=8%!Ysy zHvHfeoF!U0+V1Uq*B*L1xrZhMs zA7*BMIJ9)+bj zld5QlWLy=RNC~^|U)|kl<)kT`v)$fu@0YT=i>=4T+N|#zyh}lh^->0S+w} zL}X5C_|k!~m;?m42o&xQ$)qa_Xd&vV)D32X(eHvch8T?0Y}3R?;}obdPXffx$t%nVhadRaUnjZo-Y~?kx<0UW3;b}dQCmIH##W~u&(vP;|)|fu@=#DRai;g0J zvv82HKa`R++PVkrEy;(*FUSOs(SbYl4)W2RaK>j`^Fw8zUwZ8uH@L&T&qF?wC)VIluxH+W}I}#PHiVerr$3lIe?eH z`ki7R@4S5U)jm<`zou&gO0#XI=^;Rsd9u zlcA^sa67iZC13DV*aw*!Zl~Z)~tLo@9r1%aAkn$W7-5F(qKqN zrl1~9YD`)Av^x#r2l`y=8!cMf6TS#_sWfI2WS|q^^5fXzXrZ4d7k6H` zNnxr1Qv+jGZ=0Fr`J{T4PpZ47M3BwEjW`WI&LFzJ(9t>qMGY&aB{g0SYmb*m!=|l* zWAYWFS*k#)D07F5)Apb=Tmn}qmZxRK7eDKP4CCBG>(;$Vgi>D8Gwo)WPzw3zXx-|= zTOqZH?AwhYgaQBuDJw&zp$3ApU`Ig+1LBK(kjq4&s5m7C51t{rKlo|ivs=3hsuq~_DH~Au@mP9F!G!N1yG?wy>wU{4CJwiHqg#QDKr~FjJulPfAg$esoiyZWn9J@nZB`hexT0X%T>PRWSi`dQjJe#2 z=8b#BbIcwO1=(`@AGCi%k%(+GPg%I<@}`*Ttzh$nt7zB~*K2U0$X$Xi0#(BhnG1EC&nIQxMZu4;{3>`tlAcRn?^${N)-R zeVMvxVm_t>vFIXddg#U~eg-JPL#zeV)VT-PJyo-OF3=ljr5+Zq3cJ+{Ptp+JPev6*{a`{{45;(Hqh*aR^%K48BrKB7TnC;)EF{QpdFRBpDMQi6!7Bsj#5yH0lNYCaqJL;>R^UY zW74A%GQhm$Y|(ETxRz1N_Uw3?FIs9HG0GBWeJQt!Sh#F5hL@JS_&8RUAk5ZkT+fVk zzxE9Uws?2P9Q-}A9`E9i>v6gBT7mA#59ua7ojC(4#NwtvR0h zt72acz44cFFR%_7Iap&e(jAIAe&IL&sj#o~*&NQFHib0$)Q(iDDChrJx()46 zF&ndXiP)pDM^Q7xs@S{srbexzHGYe z?#V8%{-crJvawZqW#eD0+qm9Wo7vA!SQly7*K25AWwz#(XG~Ow;VEL&nz)encaL!a zirC>ASKb8cBZ({YfP#e#9~Juc(bVBo*Cdt{{y$%Odg}#jiuq=draHC-#!!XD7}S;f zD*B3Gy3)vdD*MKS+g9T(#L6hRKWM#|@^kRmeVIRyO&$YiguT+y!-1*jQ$WU9lj$zw ztUA3D6zfFO7=^e-gX>IMbGyg#d>T(~B|i&3x5~Non&5AKPOoY*>@uO0MIkC z@+UZX=!Ek9SMD!MoPXsFW+^9Ul$bacH0f^>-1aBSgYO89Nm-ce&wH!rWJFb3b}1Z{@DJ zU1C@7Kg>GfIRAK`k?~{b?oC%hUXJva4t?+8V?6>CZPESBP&N()AQp`S2nTUjv&e_x zATc#;yIl0m?9g2L0shpkYyA^%l^1mHP0Ux7@rHb?bs<7*O(YLB@nN`{nOpPX7}R#^nVn@oWgsqO0e zP$%_6S>&<$8ZKWDT@tFCRySXH7WBz>xFeCKE}H(f2TL`;r7{*xo=cO4wkZ=5hiL*t zm}MqYVX4ZfhkKKFxcD07sj40B2)T4yQ-BcdihOJ8Y9GZY6NEE3}tWm6N)XeLRNN+ z3)4+H&br#VZ@0r5PnK?1CFI_C&c+*B2>(}c-vPkiHu_i@0(N1j{uawm(BI0!@)KqT zorN*ImGmoe|F*3BO?ruVH6}0dPnA8qLobn_BI?$L|yrN*{nS)91in+Xz5eC%xS(2sr&UQsoG7^aKsfe2W%k2 zp+Hi0+4<=~l5m6ik7@wJYpX6!l$GAf6bk8eNGF-B$Gl_7;e4nRVJpoo-j-?i+IaRe zYUgu3u^uH?FuQZ0Xerw~NefG^7^3QC;-tA=cxd{z;`A}24M(LfU*JTKZVK8W!SJ_&8D2!c-=JC= zHIBWg_hx4qm>dUrbq*r7)lZoHX)S>srm}4}<$dc^*t+~6=>Wm40-rMMuAhfzqb~3a zjpa$2Cy3of*;M+{Z$5WFFXG^svEKIk55bxR;CH*~?Yt1#40oD*Y|WWK>zwf{p>b{y z2zBp`Ab-g$FapDHcSwiTIUtYQ^{;2~yZ8L-M(%^eHBI3yVrJh1R65$-pDs=Y@e#N8 zuUHmGn8*rDxA;oBB3zP;XJJ1Gu$hgE4ThnyIk|Q;qL;UC+P?C?Tt*Ri?EUDiaaDN1 zF!`3(jAPsCSju94UVf^nLeh=AwK}wA-cgf`a=NOZjkYC0gUgr0g9J|e*crCRs>Q%Y zJmdx3k^zcM%dPR&I@i{7YBt*H4y>n`5)k4^5a;%Wc9H{OL)kL?YI2^bk@9;(<-XsG z-pq9ZNI_tAL}zjT=P{7B;zk}pH1tU_9+fa8Y~GM1Sh2rrJU*tW?UpLk3WhUzLp|y` z(e80qp)}VwyM1ltcif`gZCm*}BQm}Bu}`a{10y$&Q_HyU8WoROyjd?U+%L7I#DP+u z1sJ07+m>7-i{-?Khw`_YNb%<#L-*qyj~4BdU8b1lPxhWXpLgDImOcI87ji~fyi}?9 zPvcAeG?rqrE-`}x6-TI*Z=xxGWVyuxUx-9Lo<(jOYpT&u9(=1{`*tjSL~YV=Tyco_ zUf-~})pwplz2^O?B#c+8+y1Qy$4Q>54ZbHP4A)o?jm{_wm6mme9C4Q?h^Ia~wN7_A z|G4oKG_3JJwba`#-JyLAqkFhC&0TJP+WYsedAbkvck#DBX0EZ?p>Qqnvi@v)Pl7@2 z*99->_xykvpcg^KeaoFI02{zgWU5U8hNKZBMCfZ;CwNSjMg8y@2@b}?|*-^vq?Nc`HvFCDWx-@Yf z^&U?IW2t>BZC2$YfWD>y2^|iHJfu~`31L|{O(INg6+9dva{>R#2`b5_#glxZb2 zQH5tVIbXDsI~6e`Prh&G7wIw$1>=MQh+(i&6q>FdrJ|*29B(XX%%q9_s4*!=jqjqx ztuHiZSw9fm{!wYr0!pLHx4)4?c82?-l}1`uN~cln$(PUj6$}#K>?iethCA%lMtyIe zmRy`Q_%bIth#zL8^M%Fgqa=0khH#Y*bOv(cRE2rJW>7ol11q%GICcl^Eu z95w3M(styR`l*`98=C0>{9sxqI=9VXmuH_3)3Gle7t$0BA_9@|TMtG+!{MQ6IW3Mo zVSC^tXWbo+l2Gw%qC$D1Y-&=yP7ZP$AOpL!VFh5tlNZ6i`PW~~+R|a8I@s_-Erl4R zN_x1k2F&vMCn1WEGa=oiWS%chTUME9xk-9=0(@}%tj!(zCNXA(k>Z)ELvgyrd(|GE zg5)2dYq~54HD$}Sd#B@xqE%j4Ne{zGW}@{(7>6D>1%>Qyl0ElB*4y*?C9WjB zhiHT#MPqCc+aYMdu$KZ$tg3{%%83DnOrtV9di%zeZwO_7XIC9b{j;A<*KGOc&VRe| zo!!UzknEtN=M&Zq#)4@?Ezr>=-tF0Z`${k7F|kv$#(WAlX4n(VVf*I3;T#~3$7gWp zV$&@9>gajdN~DUg5iZEHT9hxe(Z#N8B5cMZxwWANZe_=4LBD4L4VV-_kZ_Q5^Qwa` z#j4b6Lp0@6l*h6Y5a}9ypqhOUZEjGc|9`X4r%-$(Hi21FRHvxUwB9}{mNFquFTUJs zFkHk1yAI$!hJKt@q3G2xGaM)L@U^3608zz|P6)2_sym^|pI_6>?&;z_`4G@TFW6eL z@%e?5wE5rNJ}dOtLL^Vq{r!Xt8UP5TfM%vgsE6v~;o6qck_ul<(xfj)esXf6Jz;zu zw_i=K*Tk;&YMu)`jM)vt>ky>MbR|q91+?T zkBtIJK~C5C7j?n66@0Bfr*s+U{_dNo!W6m?1 z3!rf_J-YTF8Y#bm105$6qZmgzen64a>Z#~!dw2qmY#d=mYVS`?Q8qc^0X&#|!At$z z=aD+^JYIXyOLA7?*@;dtMjE7#XyzujUmQx1WmyDHEz5o%Ijb*VA`KHCYS}&g_>{Z9 zcQk3GkkRB{7Fp4gFTrmXj~5p|(Y(ofck}-C6aWQqfRV`J8J~W6un(*W0#!G(3QH*m ze-_+_v3H(8dfY3OG`gN}7>$N!3me{Lgbmp{hv82d@BfKaF5$kea8k{WjpmyjT51`a zpG)b`?Do4@Vh*qKJ)=B5SixHy#EGP|DeIB7eXG;7V+pyg31_vJtV@O80Uihx?jfza?$VeDfzsd)FmoaQzF3kc}%9?v$ZBBa!2`l?-5cTx)1~o~@E1|0N3T zCJ5#g(NSuoiU80I4vv3q#u`~|p!z=?#3|Y!K?JV4#I9FY0^|xDMaj#d-u0%ZBA!;Y z5$?htkKW!6{5xn{zi}E!0|sW2D2}apaLnBLlF)>2bq$;x^V7XhEeTd zRoBJNxWO8;6Fm4CUL*y_e9h6plbmu@MvtX4M^?Ju^tw9-cZh!(t$e47#q9Y< zSqI&PSDkFrwB~T;>|ci7YAlwHW+EQ8LB2mv-rfFjO}%*2x9h7Wsh#xmU-J(D1r`e; z){klo^nyPCeWL>qFcH}xtH@tHV{N9))gy*~V-HCu1A>b8hH7sA zJi7n3(2DK(0RRx`Dwe$<#czDvKN>&d976<+Dq~QfqeJAkj;^I^m| z-4|*8MQ=BM|JwL1d`SMI8>LfvqxI+~QZAtK*n=Ua&`M6Iu9^pR0|cqe8U27W35u$% z-ln;nlzC>W{-NA5IM(LpI!#_)(Idz$qmM<^J>zb}D=wk%+xtJVr|Y}We;0*4HwDw9 zj+H01zm`&Cr9+K3wG^ew5-KE~#6C9B_}`d+2f(LtnBPH&!Ybp4-JBy5X|Ch2dxynA zza}i%D^U{rk0A9Zh$1qQ72L|pJpzCLMRjDox&@&;)k2db^;%`~d zB2N2ARFXj}0RwEj6*p47zR}svB(jWF)ACR>Hch5P471*odgN=jFIr__u%4c-PCmw4 z?ek^+k-eX41(8Dr^u+P_BZA zl_mPO#2Wy{f`k+s3P_?@h~kI8R(Q=`SgOe9P9G-tI{dL1miEB zXVIVmdF~SUK2>c=tdxGWck*cW;{wh}O-?~yMb4wlMD31IkGEgi*E(u3A!lpjgCi=X z>QztQ-Q6T(YZl-_N$jW)iKt_ZM?}GJ=xE{a;DJNL?gMvcWCnN6U#|}cUZMqSBx{fc zHF=SuT|7OaMJ-)eJcdZnx*+MYNnTpVtWGrdK%ss&Uj_GOAtEMp_Y5b z$k^Wxj)W^@03aZ)L5NPaJbOjlYjziIz{`|q43O37Fo9bBmCGb$Fq(>%8Jt?D&@h*X zCN74`^NB~YrAupLhHF`=Nhr#!2DuKJDEAjQ;JzX@0yQK0O}A`Hg!MWYtr)?TI3c0) z;OzZm#F4O^`>};4#@VRLaoZZ3Fww8P917gA(f@(1xWb8UY@B@*4SmV81P+SuI@rPQ z@>0ppT3WTN$1%2QP;=dM%&rC}64&n`{Y&%^1bN`#?D9*SGNU;7!6Ts%75`Zv3E9#8 ziDV`Kz=|!@Mav5ZhnHb!a(1`@=po2%BM}S}{t(M6uD%%8oUR4(!~^)SzT?6h3`c|x zgtLit*lU;Q2sc`lgM4^pKn%d6y~dmY##A6vE)`?bADu<0OuCSlEbVtiMqbFn&AJ|AFjBV3@| zY8rMmL>zMSrNvY`uw!YhaQ>w;sWBhN&*so@0J^5?Mtt|EMcAfoiib3PEwy!>p7b04 zbj>i6iV`%YN_m@vV7M{Qz5W>@D)sIvkW9m+=4h$KzFxXuP1)hi(W6?@B8%>N^OV)+ z&qJy$JyKSLnF@RC!0>nE>M^FYQ8u7bO5R1Y&urs0k7{Axvo}TYBTd9 z9$|pUu`o0J$7(m!$IThgc@zFV;p^uqUBaCU7**=dR#x^N6*%P)@^YUzxb6Q1I(pqdHW=l>;o>2k@sDhcnWltCmSeQ!`1=%#r){> zx_^dho|${Jbo0}gIPQ1s$~IUBZmdA|7=>SGKaW+|SfOheAJ|8{c^H_WHQl&8sL1ob zC2r791CQPv$A3E}hnS);US)-3n@(VVs#a(7IL6+T6ieCUds62&RxrxV`b$9%hw7Di zE&r*RM`~Mif^ku`Zg#0aC#Mi!Y}Gn@~)}r%wq{FcYe_NwZNPX65*z40WZhndVsN{K5p>CF|-BbwDXzLc2f?2Lfc7FjuR2% zoD5%jRYwe;wv%~jhYap;i>+Y#X5&kJCqw+pQnWrBlhYuu`aJ}B#Bl@?e0E~Av)x7A7`#7^F4jP}jefUJ^rx(kMUb$Uez8+RiO=`F^ zW0~e|sZloBPio$z^aa4=0qP4huyt z_><#h0`0jR%TXV~#c|Ows^DxCOuL}J!O)}Cp)rLoqM2lHuJ&OX(H@_RnXeJ^FihmM zt(}!8H-=ToN~$rO&UiC&;>wm%+4w50&;XwZsw|VZ*OuGl{Ws^djzi&xH-3CQ!0kG- z<+QG-{~-ufo~kt*MC*RY28!gg>Ka@FM8b4I0;cmRTk<{#+q2tWl}oQ(xa0J^o2&%L z_qVA7!mJv{^DX*=t~_-fk?11;cbx_>Ri z9n+>$i8B1-{dPN{y9CgyrxI#*<7DXQs^2xON@m+jk-VAYFar^8SWb_4RhK%oUsR^S zgRl2@zUf%KA^`VSJm4a1dqj=(P(A|3n$2Eba_>>BVdNmZs0<(`vy>V);Bqh?&9`8h zj>5{mBS_Ok=f;3m$&CEQx|y;qZWC=j7|a(zJGn#bz(B4Gu9cCKbb1F;RZ0Gpv0d_C zG+>*t(wx3?`P}RfeC*&e18<3P3@8a~J(Ycyz~UWUKps z*<*{N7j6mK_oK{_;@ckk_rwa16%4=~AmX$%#{~<@*T63NCknlSKhC-%$JD$7DSCVj$c!SlqD29VG>p0eo2U8jpkYB}dxp$VQ{bZ)Lh)coED51SXH-3K9Z&-)w~nz|fKTyi7%*P@d}KHu>>F4WAeBc+v0dF*C&{_s`6J3z$gO zz3ZbPgX|_i5CDuWdVBXjh0T9lZ9_+FX)E_Pk*=D%M`csT#3Acpv$!oy|MdyQFSSB9 z4@vM_+8`gfq^%j5HK#+8N4Vl6V(O~}H#q%z#|7V}{K%>l`}3ulAlK5v;lzr zP4`c}21vi+qF)=BfQKw^`r@ku>)v>$Sy8bl$szWhboYh&K8HF zouE-c%A;&N6Nk8krc}P#kb&gQI-+VZ+@EJvbqJ4pJilfLUxB@DTv#HFhZE}|mQ}qo zmg-c*gq+4V|3k)V$u%lgQIcGzG^h6Vw`b*azSB$5JY-p&dC94{)HhO&;}P2FijJESVBMcCt&&_ zr?JzUN2iWK+EIRUONoGlC{+aE1-5Z|{zh>C(oY1Lo>d61v3(wG%#TOPW~U+-EPAK-`;I{$u zlHK~eL|V7~bE5LLLXNR79|LbNWq;(0M(U~8m}c4YI$4b`r{0dcXK^FPiLG!dLI_%R zNrZqf0=iOXIMBUn&|*h=ADAwp)uLc;upI>!w*8vv2m;IV!>^q+Tllx*X2e#E&I=g{&Db zO#Pr2d@eZ}u|0zK#h~Y4J50Shd3Xa<+aJ)e(u^M&2YOglocF{7fyDr#rB+={m zYaW01A@cb=rGgsU9g|XHmiE{(Yu%ITuL+Yf-VKl6c*XI`Yx@Ff*h1EVlLRjY1w%0> z2&c)a8)WNl#_1ot1U8k=Dj?I;qrH!nF7NocJ4h{dHI^%?kRqHnZ1xWNcdCyF`dA0@ z($Oc}%3fCKEHwSyJyo9+5z*AIz%=R1r#{;syah`GDC)NOq_TyKLPuzo0VZ zSd0Q*_$2yrRRSDSRhtZKGw^)+^eKbc6Rwg1;B+Bk%Q%WFakZp{il!=#Fx)A?^Rn`x z0VtnH&DzkboRY&4oi&Dftctt!oxxO@?Nz>b>|RB$=<>;sN3TWO!}QTniBf{-pqfDs6j?_>JZ?VmE1>0V$u@#G`;v;J!DGCiF$wbm4`2>1C@fLOR zCm-HuDLjD~yyV$(z7bt8P&85GcrBz%I8L+>vBlvK2HgWWIiLZ*IO+$Xgu|YyFY)BL zI$xDVR+`~X{o8n)KIXFh%@mUr*a1q5ja)rK?qjDOoGEh%tTCd*8N_jDKqLgPT*reI!DaYnE?rT{U`ppT;TIvauIrRsVsJd6* z2Kzq|T}dY8<<94+$iT;L)ZUV&GSMvNge5=%SQpR{Oo7*#4||}x*!+vR>v6qDJI`cO*4>?7 z;T_^qWM1`6rylR(*&HN0wUjIHPa?y+%hdJ%;WRss#iH@B(1XfDLI;-Q z!Lj(U&C=riEi2mxs7uCirLk4KCLH_^Q<(+@m^_H%%d>aJ{;aSc5U&bF)n)W-35ok7 z$w<2lxmu?r{_!KtzXRU}J?c^U3G}#7c#S|^hT(F10MUt%QSK7-Lm&?DXKrJ z3iP`rDCPXdL4U11c;)?R*k_{I3LaS#ce8k2gcsR-I=9zV-ykkRiZAQ|Nc(9 z(8sL$H!%N`Xx1k)$$9>gC3bT3G%lL?Y((9k(9(3c)|%@s zrN$L(7UL6`QSm|w3EcBr!p=glu!~mfHtyqMWPHfM+w;;iUkw}n@<)Ly1dLA-Gs$>x zvg0kq-aQTZ?eXh*;=Wr*$bZw_uTcCbr-dMd=y?JU-!o^<921oq#B}*`tZGBuU3sFf zZKWhFCl=qifRoRoIeGYZk%KLt zaJuX#0cVC|41P>(TCh#p{Xf|mc`Gb@_Kb~IgjmuF5Wfi+52vFcC2S2DClWNW3IzoM zidSE$O#Z6Wo_aeZDF`Ll(s$uzUg{Nyu2!HNH)wgecWR6o9~rh9O5hoa9F<6*rj1H` zWpJGSCC*~c-u#N}>9dfD{^+@hjaYZLHRXxU#R30{gnIql%t;#G?++H2gfy9lmO#h6 zHP*_riE<`o8C*T>j+0GwGHTTfKG~AVjX6(SE>bSB%sv3lb5(gX0foXmXN!WTDZydM z_kKS7Lt;V~iegylTf=nR#EAN_be01>*_4vsoxjx5=_wy+>QjOlMM0^5@Hi;A;aq~1 zEf|Q7Z(J=@1vKY|Nt@}z2BiGYOODoN!3V)~UR7s`1!oIsn@Y6ePq-9Qgj_<(b9ENI zlQ`y3(W&9_nAHg-*3s%wie77qOxw-lbXlRiwve@gXkQGE#<&!hNRFq&iD6Bf@X+3r z-6>4>VKe4BUNOl3$@pib^>mZ@MEIkS)$dU;5efQ(8JNT<{Y!aV0D)R6LYqF*D%rt& zyD?k1QVpS)Mo!A%+5SQO!`9fv8{_6p!|Mo7Ht&GkFNt+lZj$fng{)lYI0M33kZcJM z#pM3d{d7mk^z3Ha@HW8RdQ6y@w-!jei72SPU@HB(g}LLI!|Ggy@Xxoen?2OV1K$Sq zIcm4(K5lAzpphrJd}h3NSg}%Hv|y^uYhAVU^QY^_1H49g5Xg{;NafS(PuB($6a*^D z{?L?ndSG|`x8Pi3v95X@Cn!SD8@J@P1^-re#I)ktM8@*6WY!e-C3;|FSrkd}V<*c) z7xYcDnPgJ!LmD^@Oy=Uf#5bZi2g#I@OefDR<7^62V`7ul9~3?Au=tB2X_&K=sU2}_ z0MB6D{HB6`D)(&Hh{DDAeABcze>A9f)c$)hWOlQ3^5YToH%+pD>lgrprU1txB#9xU zP;fe?&fXOVhe*g-T_r*=;NvMYBZqjI$xOH_v#La2ED@sPR=l+KCC+X*c`ezsfpJ%wcxV z?pzL#u>2ad7id2p?7A=eu6@=2k=baG*{XkE{zY6t&{%3dxyA!gB66Jh`+x63u?rQx zXJ{1~;+$D^2Y?@Lp_OV5-JYFS5y}b&7Vs{a7{n$B_!7*-0?y}QTCyv8mA1pH1^Glt5&QiGZX;LXiEcv zNMR#cU={pFupNaKEszqQ@j$p4Ow=iWVc3wXiHlyenaQ5yAZdH6aY4POghJXSHu|d& zT@7xjuzYK>4QiPd6t_gup!O+})$!bpKjhm+AOwGEvnoz5DaE`==!TcqU^8^5~>i$r0dF znvS0sW31*9d-LUh@hiJfK)U@vdA9QS>|e$)>mQWJS5#IQ@J%0Y$qNtBgjTN_z*lcC z9td>c8bDiU;tfhNRtn#92GRd@sH_pRZmN95)=-6v!bbyACXF1Bx`fkulC3;P-G%kG z2Xh~m(KW{k?%{vp`do`+-BTmQ`Bvb2OFtOZ1f!--?Z0tR_`!afwvl`vW#7I^*1h2T zap(Bw^uArhRi@3upB2C9VrS{~>cUi+nr#j*e=OR|fz2os48Xz1@Fwd}i{Nkp@kQB{ z2(x+YGbPFNP<@YkgdwEU^iSCRC~w3#y=&IU1Ez<5Z@JWc2~e|1c2m{=HK*(Ahgkke zBuEy9QcP8fkFeljk%s zDGA|>FBd&~>m!b9lSXpKH**c0d-!#00Vr?!5);iGeWcENZ3&2hs1ZT)N#aCU!v+8q zQB*ie&5b}Rc_4?0Q<{NMGX#8^n!iH#;6aP3UwNcU_jIxoSp5rLTnPtf6nRxI^kT|Q z5VIgl83mazaWu+B!xb4jG=^ppYFT_H-geWDtlsYpSwqd zfJff^S3s!CmA-SoB%FGZGkN_{ynyu!li-XB!5sSp*C5HZOp+wQZu>Et83G%?EyYVi zja0fcNX@)vMZQ^UtW4Sdg-?ZCp|<;t1FZS4Ultcs^W8!3mt7mdN%uPvyYG{IUv32s zj+6~0KIyKeWaQy*JP*8WbM37Z*a-Z35FhZ3Xd&KzEuj?v;bdCiINi2%|ELX>cr z>ec~^VNiy=Pvo3Gyi6t-a?y3Q`yUSoDleX=3PWvln{>9R*_8dAz)kG&J1cPESiVGH zMVzVVDz_JWL)p^;hx!Tshf3XW)6060BfX1}my;w1+fec=olVotI6jum+=hZZu zH+>N#_!E_#tqT@@4D?yZ6ws}E7r*+>l0rX6v|PocGuBzc?9n4nZ?!ypODbyb!pCeLA4`Q~oip=mI2_z< z__J53T{LI6Q`^cZmpCJnCByvitD5z`%yl?p1V>v(+Hc+W92>F=H$I-`SZoh#`EXH0 zjm5gPAobQ-{!M+^>*ag?ykq@tS;*A*?C@7hzJjQv9!PrJ&1T;mQj@$M! z8-(h1w$x7)b~AP=)#kG;4^u$a#D5gsLaNVkdUfUViV@Acy;(U@ zMqg*C=QNL^gz5V%}hH8VWtq`$j3{U?1>IbxgHl<-cR zLD@*kYu?-_zpR&u{MoPL@BR#qBjzmRsh)Um+;sh35!RyeaMMsgo2EDF>7Uj8@+)}6 zz~$Av;m#sr&6Rl3dqxo6RT3&-5c7 zHN6?lG%VIL|65~G{B&-N&o$q9)|*6%RAzhZBmJq7eU*J&+j=SiAqns5It5;(E{PI! zi@>8^HmzM{CapMPA$B1;$V^cUWoAZtRJ4EV2w%*?1 zlchaFPBuyAXUYkArnPH`*K-!Lirp=RI>A=L8k42zdv-tHD+ua}3k7Z*& z<;b~p2sqhrmTJb?mL@TP? z-O~(Z>tb$V%dCtQXHNGu9G*u1H*_5W@Ee`=k2ICfQEIi1>?4v4>(j6+wOK_7hns8} zdHqxC_P5>>MTSU=l9SX;9UcXoMs58EzM5Mj=l6F2dI)BeF{axSKqqR&{-KVT0?a+Z zdUI>mq{rDM#Sp`zYoKgHIikm&IQKd7X_F$;W)HX4fcAVM#Q+-a$NA>#;<{V5DV3qs zv=cd94n1%#nRmXF?9QBVOj{$MJaI2gc1&3*0xzPXG9gcz2kz2ez4GGU?(g1zSIj=9 z1p34gbgp|}?_vyku)BwFTx4i0CxAPhngy3acn-XUCqymRnyemA40&kpn#~~D=%mJr zj{+#^N`70b{5Mv)H71ACfdVXwMDJKDdOCGJp+HE=o71NWw0_Mu3!R}wv^vDJt0)~n zA`xZB?rnA4j&j07K9$bgoL5-QRU3DKa*BABW$yXJWyZ06%IX_K%uikoqPIkqYsR(3 zpAk+|sqJlUm=f!;!E`gh12ZoA!B~H}L?ilZ8uG)A^q4~|JSuSAb=;`sv1SshjAEodwg9QgKKaIl9wxY z2cHg2{DZhdhm9)3aJA-)4`uQY$JHY$pY}zzjn15B!*eaJ3C}RR^p40xIK$ibzbxWd z2tnior-C!v>$@BgQ8d%#m{L?7OK^FE1Xtl zB5JSUFfS;_O}}3WR~FEb_4w|ls8eZYrq?|tmtdeHlZa15!Wu+jw-?Ptv|X9#1NbB{ zxw2@Yf%sZs#z7l2xK;93g-cvNckabx!B5}4Z-1T28}8!t?v|e_ee_#?cmLXUPh2{? ze%qTj3syp^eZk(An;eSmOaVf%l(v!CU~I~Q2$?O2rzo#ZIU*sf zE=>%vDfNx3&wb zvSX6?d5+)arfB$?-`LGu8q#G5ZpK1Cm&ZG}$w2==Y#A#UA{3BGDr}m&zH8d-ye$Gp zB@Ig{0$e@uPD#cm{3l;x^&pc0`PK5A&I5`L{6ebtUw>giZa{0C=lqm12&Kr1iwPwX z_+_S(t~#ukw~5r-WE~0bEXLmj$HVPs*v(5JEy?z9%2*Nf@NO=}kUJ4Xc~pQ|LmA2% z!7FPLqbA^nKoJR$msQucGZzCofGMR4{_9M!2HvhNb2|v;nc_I>P*XFzJPY0D#PQbdou?P@5@yJs~2U7UJ{ZGLIB)ta{4;AKbx^ zW?%}EdZ5?)X|nQgKtFXAp@i*xd}CuLG`oOH)R_=YW#vdB8(Pi6Km>Prp30?jEmS}v zz7CzKT0Bjsf0ACVXGmdTo~2$|Zoaq%sz=yxI25-2Jl;)a447E5JACb$AIYW4f_q5gRPh42a-CMqAI|WkC3OGek4OD}@4K|Rk{SK2O z*Hq$a*lUD76xTzn{b)mux^{8O37)@_*JQ9Ts+`D1PorJ3rb66bd>0gXQu_Nn-z0U= z7kXt^&4M#+=Q&%@Ni|G$gp+8lXZ_x$S_I~)-*RN?-f39J`~{^J_*qo1;fJhg@)5o zJ!Rw-;o=YJ6wpho@K+-2@CScPW?C_uVS3$@>h2@z4)c;th)rDjeAMS z1{~Z`#+{<0o)!v=Py{3qhmkWoZ>cHY`m*?{^>6*XXwo2WhqDvbS!O(?j-wTfi~Xwd zsj~WX!tZD>jZm*zzF98k=`*WZl2v!bn+HoTbk+My^`IV7_qBPl`C+K1!XzCZTM3R` zV@yeuL}I(EBOmZsXPnxt6Q{TMbor*otu*(Iv%!H7x!d}Zng4dOxC5RO!hqp20?KS| z7H+ZPu_8BVtXwgX@*vt|8tSt>)Wp-&&H$4H zhAvR#g@@AI{&bWf9CXwLm=&X0UvETUK&n=qaMn~ru z{i~~pgfZAbjevhrGvY(6FhvH3@`cE^>yH#V9zm8XxJ!ZV$jpeHLi-gbxN8mJ#r@s= zNuNga`fb-&mM`AdzIQvE(~K2#IxmIA!b0sMWwLwF&^pGvHC=Sk0h< zClpD+$)h7fiScCn@4t64L7(BxHGt)XU~`O_oBUK11{a~5&K!xvBuR0WYMR;!-O=A< zbF3Bi8RzdPtf_19zz(4XZ_?EoK#f}Uob zrO+2jJUzL;XMCTu_U8Kj-+{_j_dfCIY5R+p9k7czF}o|JF&4{%kIL-KFN)=LS&eKm zahj=TfHmwKRa2H`g?_C zR{{V{SwJlIqf`l06N0L}vbBO+X1_jj$RoCafG&wCRfnMbw7^#>bWDJWY!ao{W#B1@ z8EsKy|7VMl>>~;P-}$oMpC<;EMji33Hp{rzm%WNhDGpgco$C*AC>2+181>gk4$&Ej zBGlw=CS?Qhc2d+ioZ05d}1zN=DRx-9xc=nc*2o-RJq7S zR^uVf+f75t!1vOm`q((LVofj zl}~MU7&RfgLyk(kI=s$dU|+=7m-DAE+*FBLDDb&k3WMv`Tw4}V*9Ia01 zhSV;1&t7b%fy13)03_9auy zgZk5&bUYmn1*51u*8}(Azw#gV zU;oD5iY4{6lYo`CbUAcj)pmFP@VXO2Z?NQcAec6E@IysO8=ZbrsvrPhs~vWM^H~6> z{t3FI7V{&i#!p{BUFmg{o$*yoL!EU3qJ4P`*b8W;mHUSVbB+T7qw+VVW3G`eP3BR<~ZZ6+T zCvTKfM{QF2qBh;Zi6?TDn0Rhj?aC-~av`8bPiR`9C_JF^$kNc|7N!i{Lc^kQui!2FGBAk-RqhK*_u#Ww50Y!E%qo=Q@REOJ|PRz9hL795^arpgLd zShC-<)}m;2IGe|7!_#__s87XQI5JFrYPv52DLOp#eO=CCl?av#HikVqaB+n1CAl@j1c4}mWZ&wqcm3echEA`QGT~88v>C+U#U$@i5 zUrB~ZF(S8!J>e3L}KoRM^xI>3Gjb z03Xv5%6^D7nvI?&WVCYNx-ZQ!=no}O_|l%PF?HaD*7LogqjkVAkd?y(%N2urT#E4-}eHJQRndPQls7ne0CMj7u2^u5P_y-y6$)#(0KeYII7zn=Ua)i zx3uk*xMATqam}xv94b&XFgo4ve?QvOy7l8GbNPINR6l$R*HOVvx#9kw+&KUQ2G!aZ zQCkUbs%<$asD!;Iwz3v{@h$YmB2_Sq)KZ8Q zru{}oYlD7je@=_l9RIf~eibC;Anz;l;@_(4WUrC_Dcuj)z2I??b9iZ~!id7+29PO zBt+@%Zt3on?gr@w=?;-nk@)TNec%7!KKGe3pKE5WkQK+*k5P*)Sr{zfKKoUYZFZx0 zYNw}X7lEj}hW(8LvN$YhwU90qni`T)3I41cY2M;z?c73p}rY#eG9PByI*>DDK1 znQ#CC92@|o#yz0Pr%xye26GkB5nyq-(p12Yf!{$AtMr01;_<&9S14!l4F*T^cgnuP z+2Y+sKNbib)^AmM@(UA3@syv_o*#Ja+n}KL+c6~NGGmugL0Zi0nq*OscEaNGH+X_G z6$Q%9dH9L+nkW=8_H9VZmOfX=@!`elMZ4#o^H$%Uw(+L<8Z3*Ye1J>{wAL-Kj&Lk| zONdcqmeLyG>#W;IIbt#?S^VtU@}QNdQh9Zl9edk5>1OFxc+tucU!`3)%MFm1@&BAG z5-OQMvX}_LolFka#lNDTxwYn$59JUx??-(il;c0=+8wsF>Mq5*#=kIL_M3Jz#>7=(gN&znih zmLQr<)q)e17Vqlt?l(1EO&n<_J`0HRwZ*!2izj)tEa4p4MOU;x=B6)E)Z6&D8L zKwhxwXo-UmjUJMSEzw815Jr1Euy+f5WQEqO0@vT2BMpzPqG4h1U;*9$%+0|45{DKE zNMK39536Fl!y@ zX=zIjWpgkx%(7jjaxfZFA9_lsa1XRlTWlzRidtJC)l?+x(T)O%iU$p2hCnd;3~uRa zw1!HD)OeW3xIo=hJ)xyucg9M3&BnT}Of6gJ-`G*j9vfyB^43{DujXg3bz+t#k#)zq z>!QM=$ON@osNxSpkrdtSbUCEnpEa$2TRTYGT9~bM&p+E_Sp`}lSR};F_Vn=f1;5Mt z$A0e5kjkUCJ;Z7JV-Dn-az?4JT=hC0`{p_=s>{evMMGbW2gL zu5RIZ%kCm1H|6c%%iH)L7)%4O_-3d(B=H;oDs=ik4vG6ng3pw>G!a50FfD4SM}u4^ zLTb`4Yo`5B;ssfoQ$BsN^xDTZI2sN2Es!$9klIngMFiZ3jVTF_)3QoL(;bErWtTe= zSe;XHIR#tX$k2|OX5}ek$53`gU;~R<)Lhw1l3(In#BgCy^#mj5IdnmShlZLrR zsB;GZt#d)G<+*2od4*1-$Z!cv-nk*aM~VB$TqVtTxUQZJp}=V3afX6~e2 z<>?hI`N%l`4_?!cbeHMKo5L)PbWy`v6;Y*;5VNY3>_}~PCpy2uSooOQ$6G+C2M@!vRu$V!ITTkcg zq{z(dlDnY~E=o(AGQYqP%9Wf8--rIyv|z}Oo_S;!AR>dnH<1&9(P{nNm-8i?E7z6m zo$T2I&P$NBXq>{n%2$v6$4{`j2t>q$#V)`!uepBYZa_(rB^~0I1>I|sWZxIqvhEK>OJ^0O<=!!f+l}=~* zOM3Bj#x^G14CyZ4N*jlt91MmngYt;pg7G7aZrVsafqw;%AdPR`72oNJ+;J6Z;{4$$Qf7JNj_MBxF5OY3yZ*c%qxL^c7J#)^H6IP;~=zY@RsehQA+q ze!lqs6wdi}oMS=44xjV7b2bNZq@Ze2`Mn{0WI3q;v_kf2 z09cRW-NGki#P5_$TIO*ZwkRZ#`TF!!Y!U~+k=e=7qGo}#47Y%EuF^>ASCf4NvoX3l zu#H}gsbyQefICOtXCbO>VH=9}{`+_LO0_zs9cSe`49!zfz8lfp=QNRby4;dcFLz9_ zU3Dno^he@9LOCKz$=i*NL;k}FutX7Ri)DWWe)S@B#e6%4Xae&DmAD3vL61gz;bJW6 z1~UkCqKssY-t$72=hUeW0G23?FKLKR=qTU3uUTBHPihV{Az2K(r=_+EEQ$kHA)s7P z473Cb1w2*V@cNE<2Pa3pWK14C)B<77vZ_zCHzQ{dcDYoh4_oxy_{$q$(q zb&p1y&2$Q>6YspD4DZX`o8R+9q-1wBS`}x(%SrA|)}@(h8alK%{lS3)eC7Ay8+0e; z+EX8}2(*-`M@)h1f^SDhZz_;SABmO){WWzg6|!jBWDnR2+i6GgW;D^|EbUSq&>=}` zDc9cXLkzYon-Vw`FLwHG=z^KiIKhT4W%AuJN5#)pLzDRP5t?yepTSga7MT6fX$@ux zwF>op?p5BhK#i_5_7XduLFb8hdt4-rjDNDQIH5KVc@JDLPnAHSa?xC@!!U0cu^}Ya z!&a4fI0zYAAzU}Md|Zz2TG2i*W{5g7v(Pnhe}(t$Z&?Ah1U8%kZgL_^Y7O!!wnCvK zTu4YnruuI4;PRqzh!O=Ywvs-AzWkB^m#~iy_-jLKi`s+A?jD@BU6Sa{{pA@3Yx}G* z+1SX^c$uxMGk7Z*hpD$U!&53UK5Ut9Ls}yvps@DyfakdP??~g7 z>eWdXqg`uW@9WUsDmV)zZpw${%S>|Wn_=&YU{!EIV?Ag{GL>8Y?KREs|5b${5-F*f zLU0-iCaMszP+*cqNK4k)z#bR_zxks!kf#44vVG6YQ_?AaBAQX7SYn1E6W&}PSc5Cm zA3L^USBep6gI!EWKT_E;uiS(bJu-ZXTu|3970zVSGhQ@?Hl4NO6$4z|C*!ZU8?(q+5&HGOQ70R3%l+et#gtMOK*g1LxnD$^%c36&kS8 zs0{GI)pKA;=dYX>9`dauK}$Yuz0Fc5fEd!urFLz2;p*^iPS;@Tw0?LwLD$o>&@I}? z-RFv@1um=@}H?m;-Y*n_$6;n%*QfVRBNekn!oxJd{b zfw+OAz!TU@_sNfyGeyH*O>&~AgR4DSO*odL_FKyjTvfwsqSD_LjJEq4F&=F@)ui4{ z5=mY$W#H$}*X8a1(>rVhA_~ADFih_dDVGCt3eV9cxYC#*HkM+gV;6%tg7tn zD84(o<;SuV(Vl7RpKA9DZ?_fSb^cIGUrbK5wr$a&llo|d;O9MiB>y1@UX)=D0+Y|8 zct`LDVX`RUSg|2Uzst;O$&gJNeYjXiqgXe@&-;#7F{sL>wf3AN5rWR_6!ei6Nm8-8 zohMo)SvtVuH!Uuvfl9D#N~`psO;M&kk~>AhR_x}Na_SPuC2HW7wtqi+47+VLTq}y7 zv9t=i;vYC-WZU+*$3H<72P4 zW&G+x2<6X-$kF5UBoP)&e$V|tTF&?C$@7xH-0`yiX-{wZL@<|Nbu!UM>`?aKSeP4=V6eOiH_)%pj zXh;^oCtuQp|A5^A2~ZHOj(1YzK9}&HGL6wdT~xqlICgHSP0L^`viCEn;}!IEHIW{p zL-KPPU16{7dGq~!aq=HdxDG^+`p+qx!u6{YOD$|whW8Q$>)MNhL5(bKFdk$26X*W zgav|?i9i~5u60v{Q4(`96ofhKm*lA;p)kb5KJSrJ>QmcaG*daN8J9OL=1qkYzqwE~ zYDI$?%tuS+M%kHX6rLAHi1sG`^5VgQhyPV|U9Rpwp;)LiKZ=`v@vmr;y=SppwHjt3 zrnI%SQRjW>TWo=`!hwMeTBpQ5iv_!fJj&{DmJ)f00(KD$O?vO1Z{ zs?EI3^a01=4)*)w?uGGv46E33dIQF99(mha#qR0-AHSQ_Fq=WYn|o)o+9$?afO z^0=w{r9csqkiD_cp|tdD!9P~97e9*coQx6tXPvnV{dQ+KOdYRUL#h5fdkTr#9bv(V?oh zI7{iF@biF(r5BUN&gyLOA^%h}BF)0o8JR^olWaFpkR-X}E#x9V;UN>+S*1N+NwwHUA*u)&2&C781&6{&;osQa8QIZw;>}oUUV#^aJ-E(=OX6*(rb3nfSeAx0 z*h=No1(e8Ai1F&%4eVl*%HnMeDw~(-z_&*pvN|2AZ{~6R*#3-fSZkSlesb+7sjQ==L>jWm1@ixVdu>Yu;8Fun*Cd1j3(V2n!fVmL z4Ku`#bNSQkJ7`ywRYrjLDB{_8a~m3tRp?WyL9yyVfg&W#N;Lw^nYriVnk)W})m`Xe zxK#&Wx!tXKzIdn{X7~q&;CD@}K1DKU+V;st9LC}2d#R8xyd{1viCt=V>VcPjgz?tROFKMlv{ zm(PE+zJ6X*7Xe z!_J6v`Cu8scyC<-ev*;tUf13U1A(^mwq)ZDLGMTGUQ?W6uZ;N3`R_?OiUp}360ZcR zWIfNLXkI@U;H9vq7^2yJY@J~CtTR-3ci31K`2I1N3Fu zL0X)Z1#)M9C#gu36O74-rq>rBy5-RhYXW6{;=Urf$v z3l$Cvh?zfsTciKiOt%9O2>y4(mB@26fHl*2-0e8+OYlOpZNr6cY7xbchu4hH`ORMz zvgxnad&}=uC$>8TR5*@Dxur#jEl`1>LRIMmOWD${(q87k(Ni=`0(QN@z>FZ;G z>G-X106RGxTr7OyccbFhxKu5!^|l2z+oeJf{lrI&cQG*?wveyYtkF#J@*N|jK4VmL z+>#o1@x;v*u)Ezjk6gCKU+cUixC7E7nJOO2qleF{B>@}C@?n?}LQ8Graa{Y*)pTd* z)!&u96$YHTKl$8^Hj$9_WLp3TKtt16k3HgP>w!R{5FJVw+i*%5BpzcBy1INNf!brA z^f4~~y%skSA}`XIweo!Ps}fNo@DQq`7n-;+D?!VIk$1kNoIEk3v`=q! zkIqx#)P&Chj+d7|L9W}t@QV`&_>08cm3C$r6Bz@96wMses?sED!80I#_oikm<#u*k zrIq%c9Cq0{U)W6H%I}jWeo9Id4El>iI%#7Z zeEzLYQ(l=rB2B`RZ7>Ah>{m15MNJ2EJcU7oHQRpS&}(u5#HYN~7#=nc&Z$YVFWIAD z#${+i5w@la%d?s1iz&UP%^fqf#AdG6&>SlEU*BXAY*(d^nf|<0^u_%B)Q#583)i2a zz4jcL5RloV%+~FoU=J#nfy>*ACZyohv67-rj;T_AH*lnYMWQQ^F0eoxqz*@@p+Lm3 zP#fK&Tvt*iqOs?-i@Y?_5wLZ3JxJfuPsf&mL!DUwk%Y(Li+UDkk`O0C&!pTWrGAb9Kn^>G}&^|kbSf4=fi6(AC#EE1=$2|e%wxkq|VCV%Gdq~RRp zQT}YjHbJG~-^O>|9sag#PhHk>dDFRUL4~RWS-M10nZ4Sd#;KoK7mvM|w|1i6eHVkD z$jg3wJB9HQ&4O9xXmbQo)gWN_Y|<8X$gQvfirMA21Fzwhck|yWYablc7ktLkibPxOHOJTUl1I^ zRJE&j1L$`+J1P$*fpAE%$k0muC5nL1;wDZUrY}r4V)l^8;GJra@NNb@{0M7lF-EBk z-UKnLN+C+dV^;S1TFdS;RVyqJhNcO&lVZpC|Q=jm|p3N>c6-JxcIv!T3$it`Q{{K)3QE+G+I3QUXawbMT@<95#2(^(ScJ5aPc`XFxheYg#O5|T|>IG5o+&pSauARRi@hBY< zOc6c37sI$L+F*1`(jtHUW>HO<-9ZOdg;|1fRq2|VX89aT-1u{Ql}Mi93)w0%qBT@A*Z>~yM(R0OJN zs2q<~pUZpoBRNGMp?yBlUBCRPkQ^!*E9os(6|*awuyFv?Oghcfy9+p6HY(;86WK-b zhZYm$S{5HhgZMLXsSP8i#LZ{uC;FVJ!jgki_%YDLKTxtbx$?0F!9!xR>EW%9RT-9s zP7l?@@MNpF*-G20^`;=omCVguWsCOu#<3HQB*)7I=kvs|6^Hi6_$!6OW+L{Fxw38W zu3vI#N>i%#YSI`=BIH}rI+6b>|7PJT3n}E43$FF8e|xMdgNborl13sy4LZkc5D_+J zB4c_#2;Izv5w$@Zf&>hbGl1|QP_X-Pq-!8xlQv}3NZ%5COFLJPgzn38vB52hP&FCR zyLAghJKmiyT_{>w`7jo8N(%XE_X^iF+oXf5e%@Cb1~c}&_|jm!nm=(~&R%-WL77e- zzA&2C{ItEyUj7zh#HbIK^^?v+T$ak{(Xo4TJCtVUDrJ5v?P=u&Q|}e1 zm$aeN%h6zd#K&k_(~)J0bCroI3o^clw{_iynCegPGF;oN(`JWruT!a?+X z^8vGa54Qz18wc$3bELC|;RLJ4K!kSN#XCp7-K6u)_jYtSdgd_yggxfL_q7f^CGldm!p_}Z)2)=% zHGxriu~+^o|4^qUozKO*PO@yy5)G;V)YGAf3Pq{p=$?|9xU_OXvgnafmSrp1b4kzw zUkGtrh{RVWvC<|xzD_9sTx=4phJ_b8LyNn{-{+;?OAF;~?Rn4XE4As}3$S`{B7t3L z)RM%KC4IDR2+N3$`#UDhsO}Q2kb0>RCpe%i2V)8N<7Y#ZoQZY!?x)C)SGhB7ynHxv z8z|?NhJsDOUG(1oFiMTt0<{EB2`CJJ6YJS9FsSS$f0prmDVK)CyAFSsTceQXa273) z;mpVUafY>RbwjCW;AC%nVR63DA~@=spsc6Wr$ZGos%4K$VfO_-hM0O1YZJ|O`@fj& zC{S=5TQA=f=J~HjgHbFZ;WQh#)}q_N<2-W>)S^7&v04f&yy9(33g=s-nFQ<-gFiCL z)|pD@?7G{`tO-^y&YMP6>v~6OIFUm1Q+eB83zY`wd>g8gKO&_-Sr7om^?(D#Bt->A z!yy*{n5c)tC4`Lj(iKT#eY9*O85M4K_)Z&$&_JFFi6I}BJ1uf(+~|E%e$UoA+}S1S z?o`fb(BX#|i1+xpp8)Y@YPQ=AQb0Du5@uHpQK3i^{L&>~9Wpt|FnHfw{0vHxDhOn0 zavq3;A9JSgfSPTCrg+?VXe+e%p}*tp@VzFrWIlxC2)^e&!M2Lp0CBisztq2&p4|gR zwe8+OiTYAJz(gbzVlJ~3Nshrfv`;LQ$jnb|!` ziWB=dXxu^^|25OuP=qCWsG*|_%mLJ(-Y)zzUS*6%9M~6_2(w8nBl&YV5%exRO0~#9 zL-w!?2ORC*g2K@GGg+g?Pa{MqjUSD!Ta!p-?wq=4{YmARxk4{oss2uPU0^%@%Z&fN z^uqaIa7jt69GQYW4#TJO!+&Y--%rcOIjDE$IGmZjg?Gaakbvl3P^kS-PiP2!w zE`&D3i+B{FN#S=D%wVbBD4LuA*~(g4(#(&6XAhu@;m-Fzfi?}4PqC$YJP^R^8 z{e{SnxE9J6*iy45AFf7p^Ei%e4i8#S1b!r+y7^GYxm?zQoP#ev0Ab^&uye&xorMzA zZ8DEnI>j2vNE+Hyk~Y!-b-dcBrM58O=tISdTE%~NA!OQ&XbXFj;Q;B?IEj?qI&9bg z*#^BruA9cYV2MR|2GT#aGpCik~~lzbbEqF@JbGuUb8XdB0-rpi?f)tsbC-57X?JjOqQ zP(6Y|>*m%u@h&cs%C5)J=bf}y`6@RQLqw)m0o_kOx#4l3)wI!#*DTq-mpxTy^|L`k?`oA!0R6yz;h>;zU z`NJWys7kOP@n$%bP)wny8%k&njEsAsvvH#I^(Zg(@IsV0Qa2Cn>(w<85NXtqW4CQl zj!PxDq^mu_<6e6a^LlgoxOD-=EQ6$_a{SI{bhH?`%33BDN(_JO0oglk+6s~)`cZ{r z1Xp&9+|lFrhcw-iD=(t5NvDR&x)B8-FKOw0^el_P|By_J8lkQKe)%;z6afEUQOMxH zXUbQ`2fwr6OB}+%-it2JRTplGXHTv$FlrfM#86*rUV<=x{ zU@YN|mr?JDscX5HnlmR?;T-d$$sC>i<$ms7RwWJ?7SN9?Gt)%soaV9)8=h%9#DGs%}=2K_HQId(ScXD2R>h|kO z->Zk;Z^07t8$D!3hI9g~1#SlV^Gy=@zx~9po*Tw)ZdEcK3vkmsLIwfQ-|=38PYal! z33fSAk`4VVm4MPN!|z#J+A>=9kdywK8-02ir9E%tfdjt{0z~FavS5^!F#Oq|nGV~8 zn&~1LL5>iJLvws|yq%XOOO&GuYE{K^f!TPXbzK~FtL2upu|EEI?xs-Mq;gb94}K}j zN%@?`inYrQ z)GSEJ3#X>&3?!^bjYsT@|I43`tE20-w;Eu#WX6iWvkhw_&oBS<^?e`8$(&ZB>n<`r zL^AxPNbkA)Z=Y}*Xnkn`{SPOUPXEVa%*E4yEoVh%fUey3+$BYNS8tZ}KR&r!3v^u_ zUQ@G8RL2EU*1aa2wV6p9E;IA1%OR z?gZr9C>gyXdah}8Lh=I`Gwtjv z90xFi+;5yw#$Qjq)J0>%0Rh+w%B{eD^ys-!gm7gpPz64Enq)t%-FO6=!<|ObT(nvC z*<6JgG3D(*5xnADiKHN3MLKx6B*?sFT+fDyL4~5c#JUm;(|3CjtBCq*@>VZkIYOsf zQmf^YYWC2Q*;Plsx=dyaHj*YZ*O0`EpP>$aV7TgOo(x#zdEa}bI`e9!{nkhE#NIHB zmA>(3>1nw$KAkr@4KES}C?N9oXEX`c)Bp%J4ami8pc-3C1o;n=z?ei1gi=8(NW^%| zc-%~LA)_0SlDu0!0q90u!L|y@xm@a?ru((CvCNdCb&T_=0?n1h-}40*Psc4ULS>i~ zqNq`yF|$W%oGg?n^C#re3k<0v`*H#@wD3Ti-nS z6=B-<=AYrWvi8CLT;zS#H#?djY!uwUN)Y$X(K(2>_+3q6>Ha&;slp9*$bDEa>?fXevii^Fn8xZ0fh_9j&L9#Fy5jdEbjvomd)Nls|QV@7G zD=#S#r!t|c!fwp8ZW`pJ)+Y=ye%TbcU`DdUWP17EgR2r9f)tNVshbL%6fAoFQWZq_12(|0F^#mIg3c1&ZPT&y;nhGaB9uNA=qY)kX}LZ>Dx z&Jou;j4tXur{b;j#x8BkaspilmiE62aiFtLmqsCXK|olo&M`M}~=fW-YLAx3fqmN3No^{0$q zjvD2he0SFP1fO?gzf_-Z>oT|}SpU5FddD`eV6VCb^?vmpYCi0iwZ%=ooo-iVJj~8czZUy?t~rzH(Rs)uJxg@VpRl$Jl@=EQxP# z8Zf63*!KbEA~R!gGT5x3)R~j$QvH%C`Pf5^?%3CPs5)j+b&yp8P=uFNi1|qt$VmXY z0ALa-6o)9XYD?bO04hn~*rHGd->)|b;wz51rScoBkfG@9g(`3h`s-gGDzVF4P3_wY z|5w_Z02zz_IeWPlI6VEwh#|DAR+#}W0%7|E?$m5E_y>@^QWxt%LjB~-41bF7$NN8# z79A9;B_(sgN?r@Mxf=LTdvHcI)kajvUQS<9up>fFXLuHh~tH7J&_;To`+SKA3dk3$N6&LjP(oTI2JhADUY_G|P1Wb6fg)R_Egi zy5{`j(z{hcGIbj`yHC$gsZA%jX8m{HWv$}nYep>SkLoG$!}W@{mMl0~60}69$&^2A zqOH$TW-x%K*qv9A89d24NPjh|VR7G)@wDCIW#_WK?VO}*9F1DaP5dJqh7Jb1_qz|D zfxZ~LcSDtj5$?DEAVUMU#({x-Zm1?EKG#h#IwwLtS(XHoEcmRO0iYZi+RwTdLF-q^ zDKxDVyiG}hTWCbihJ{B1dxPi?249UOV{4$*)zTSTeT0M;4R?51tk}C2zuuRnb}LzD zKh;W*CW8cN!4j(zL?Zx3RtxMaE}hsoJ-b?2JT4kmS{sIm)B> zxh>VLdDO42NC*XkT$B!m9FI~@|Ax8a92ktuhFaRaILXSNA@rDMA817GZR zCJ712{rHk(45tzhorvSUg^k8gBK5|ni$S%&V$(dzTpV;_E?S;JE%Qmq@g;qXaU9{% zNe*ttJ;u|L>0hrrb`TmTmJu|d)>Y)3onO9u;T0%pxjvM+Mf;kR|75k$b;JF7Q?d1t z{EyL*1OPh_AQhq?3$Uav$EF#-SWuU+lBHMY-0K%*p;r6&Pfw;vw#W z_WsIO6lzctsWVpbnWSb~RHgeNGPUeb+d6O*)~HAgJTIdaYp!iA1jf=E^D&B)I53k% z@$XmA%Y0(%i;pwdFnXz1RfVe5o2>_-lxIqAUhqYp{ovbF>S`+2Z11@0W7)V;)mDO) zc7xS%Ac8B*X9?!p99|s*lk((g7shDJaLr4xl{kr&k@Be_U46OZL$N>*;y^)_z}lWQ zzKrwwn@HQ$^{*}Yl3KlDx?6(!+uNhFcf97dmYS}{u&4ezIEwZl1Nn&xySYFEgbAd= zg{JHC3Qqg}fgcBIO;gpMeman=F#5cLm5NO!&S6scXPozFYH4JxE={-F!O1z>?E#Ui zGoSy~oqUin^q0tJm>1DMXJ(R+8sNH#6b-`h=)WUu=|&(kcwsGj2K2)=H;chi4aE5U z#l&qu&=NXjKU*WBCMG&EgTP6l=tvj?LxaNPN=mfe9hYu`In>>a{RE;F9olj*6xIya zxL8y9MIV>eX)3dN*#?oGA;Y+Ij8mJmb)@t{(90{UbNioVoF^ODyY^pg>I$1fo_R?< zO|jP^VcT+5?3CC`wWej{gMur6nUL$>?2aFp=9)V>aARrQ3Pj)kitQWDn!d6=zwJGl zw28AGiHeYtIEJ+rcHSqUBYe~vAT%KIy>7q7h;2ZE=-7!c3C9zIc7_cVUQ`n`S5xDz!-qhxy1 z4MArcoy;>Gj@R2&F7#=J_or^cy8pB`yLorEmUC}XKi)56Z0eUEjm>;sc}rE+GxZ4A z`S4GiR~LXiE|DBcE-FE5HdJOIZvkJ{maDpA!x)Z9Y*xq#i#Nch1;PP{sI}}ql`e|W zs4~Q`xa;sSi~jgR9{IQeUR#jT-znGT#yk2Sb$1o)l!o!67MJHMy)r62+s+zt!(1@>VTauS^L*CzjDAq%gE*6jJu zp>W9dg5s~jsRVYTv_3aQBJB2hx5I=B{8 z9Mbt}iLEe}NTK27c{EmNKUttwN&d9b@Tkju@^+i{WAmZQ_uE-yjDwl^X3w`YhMN1N zZu_OyyMTJlp3a)}_0P*KKef*<-G|aQO-#FoHrXrWKe~YU4C0jQap82{X&tzAMYstk zu+5=I#oUtGE3@Ac=P403GTC8p2hA_3-!IrOYbu^9-gHk6>T(5ebdwtD;HwRcWGFkV zHm{eYkh1lZO1xuYAg>k*(+6H4@2h;vKHx8t)G8Ut(_uN{4~kW9`r`LRxim&9%HC}; z+=4HsfaNxX((qEvx`X#zX4W0eaxU=qE*t=ejG(McO-@c~MHhmNAl1*66<3iHY(AZi zb2R3*#Y?NmMF;$5b(Xw2u0LfS+L^jl+-eeUBYrMjUtIMDp+>eo?&HH>Ub2J zrY9#@&ns5ovk}y!ml&|l4$-Aw4r3^~R2Z2Xd9pJObzh~oD6{^m+ksye#^zWA+KN5U zux`6$$NR6e&j1L!wu=WM=5sOA{!~^&)p`h7|69%y(_X>MZJo9po48rtt0rf;EL-9t zMz*AsnN)GBv5-}2^)NrkFXg{@niD*BGyXDX_9z`>UKmpNpbI8r_K8GtUD$Z3Sxw_+ zXvM?Zw|7VAGBljAKvv|h56|^hzUfS|^VF&GlinXgOtAx-%c9{x{aZ~3(Jg#pgK)%m zyj8>$v{3Ht5R;nXfoXQB*F$cO)Ts#=tnB5UFVi`)qxV6ZRp``YaJ%7+Cc9!1WbzXK$AdH z1KV@wtR7a5p`vWwNAaZGgEu=w$^L+*H5=UZ!;zp+ag`IGF9 z?{MHx<)3Z;lsf2b+z_QFcQ_JL11Kjb&^ZPi1kr#MTcpa1OdKlQ7}80(Kd+{aB&{*GPPtUa z@lPV&GyRPsnQm)#_+s`m_#^z80q+t2_H*BFwWm&d036Iq46X{9*ijA^*pMCeoeU&R zHys*+pf%rxz^JmkRAwy^hNw|TJ7n@CWZ%=lj*gpoee94BCC8b)RELwc!eTuXIcY4f zTbQ9+k&b4Qn&%YPTG46IyTc%KM-g6IQJplojG-M8(@M2^c1_u5!Y}^E<-3W(7%Flr zGG=BN4XadX0=Z~r5~7vahI~I@j3DdcEh*MM3+gcb@GSAd#6nkyRqH9^$KH+RXOE|K z`L^8(i+gUcf{D!kSwc4*`P{?GHchS1sI>F`!wDXPER5_u;iME{;W3Et)R;;+&A5#Q zc!F%2mND6rrec>Q)4Cnh~)dq7+}eD}EAq7_IqBPJT6gpElKH!)Leo1zJ~od}PV6 zzm44b{_f5hF!}ZdS^kC{2!=<{Yru&GX3DFd$@6JVzy%X8%qja!E`~`42iB|?C5Dv- zBZ`O6jGPoCCIkx!iK}0(AQ~2tDPqTjg)DOs;k$>$X4rX=cLa(K4(CmQo>Ad<(S;m0fm&iWRi-Rq!ZS*?8$#BF12nIj&3~ zXT-&pV@wWe$$UF%tu$WwSSr@b4Bxm!%BB!FRY26Cup#Ck=Y12zSJ zI;jY4y?cKCOBO~$!78nBU}QfLq>e}B(_zz|Z=r%{zq|-;+|RVxwkO;US>n5FkVN0`%-8@L>*6DT+sRS zu+fu?8TdbB3P31=(F+|Rt|6m2@6E$wbeD_OW*Qa;Y1nIC!Z9B3&<*qb`&tO0no3sd znmpV8emK1T`PZj3(~@FiA3uA-&15@*w-EhV*eLCQm_?XZ;m&zWgx^Xi6GH&IqU83E z)~haaRq0*T1LxhA`$-gTx?D-fL}t&FxMjL!v5cX~%{NP(SwcCe#f+WExKlyesloV- z9(5tNBL$a)m4bTno@eVfNW@!FK_DjaqYKJ zlOL)#Mb*x%>;*Iv0JHz5);AMk;@XvvNG2#n3?U{xV2tEqZvy2R5G`?#$e2P#3?^`! zOK{8GYG%Fh7z6?W3vmhygf1|W3;ATj$ehYh91U%li$vKJ5{m7hLdmohBgOZjtW0Hp z9&ChD#>#vnl@+|IRRb7k_;PwSUA7e7ez>gMuM)hz%rnmS?wl?EvKivL;C-B#x&9fM zqy6=;n=FIAK#ullo6C_5WyAf$<@$nt2>>4y2n0KF=nJ5R#;W5EriVk!zN68}ld_{? zr;sHeL)g96%8Jx?IjEFX!VB50aPU=&%qp^JlY&E2Fu!uLv06P9m6^VOnK-d9XFd=x zotnCI^VMjpS+-jT5xbUg=wOG0GP;&jc9Br5kC29a*G7EU(41xX*JARmpk()2r19jN zF$VhN4*_%}*FD9;!u*C++EEf^CPD^cqm({qU(OO>9T3{`Pl@YzI50Vgmo{forc6Hk z_j<^id*zSGU7IWGv^3O+#|!+}kW5RvD}`wik(!pyr)*q7$Izr>@CsK{E*9UUrcH73 znymJUxt~q<5gJzjWNEN=;?l}mEGf?{rxrg!Go7Tvic2=5V7i>6^ZldLdNstEXi|PI z_V_#T%oHvMbk+Du?P0kB07nA^KG zeS%{Iu3lpIdh>R#np9vEeS@1&-{-m8^E{UTw9Azjj-5(S6}zVM@dU+A8gEF-Q@47l z$LBTXz>Tk9RISaCyHTMhp4OLQCqdttA-D70cGy}r5Brs04_28XC^d=vxR|*P>gkd2 z=#`+s!^_N(Rassk19R$F=QQm5!FgfsvPqKdxj~4)2Na<4tR(fEr0ruO7e(5_1A5CF zI<BIRf&C@y*yIHtOs?7)&i$ z^X=+er)N)hSo$0Wv_OOgB*Qd>-OI-{`qOa74mOzArG`@d8dCgcR1&_qVY{`#^Pe&yqj5_n1zn zI?>%czw-Th?TV8_D#xbqU*qR8d%5%q@(TYu%x0YcL$&AM*8x9fo1$N)`{u$&R6b)Z zN-zI`*~b8Kv zw(nwOKCZ2SKKK`y$S2pR1N1PxJcbxPW|O~{cIw*DyZDP{$fFH?m0s`1p6i2~SOvXG zPL9Fce(7{At9S|gsPT4!(C$Itr^laON$-r)?AKh-%ePrY+^Y`MX0FPfZat+9#V@Se zbA`gE^dn6QGjb^Ll&}}ifPrfeGK$Bze>df)$kXqdrr2vI%L)b4WhQ=TFl+tW0)s&- z250F@Tzx;7Fj8e{H?7!EnkgyNDKT22nW*?y%oW5$-WU;B1VU4lglH9L*YvHxkx>=< zfrVq;nKK_I_!Ys)iMdLWq4ZIwd)bF8s6z9=w6ADfhQD5ND|?KOt-R3V_mF!H^fu#W zN6;wqLQo|Ii6HU-ra%C&=!V*J8D-kjs$V5KBpi`RRLbJq?6JU)JC=3|_X|1{mj%vj znMkklK+Ys9jh~fwT?{u!;aw1|`X3*5=0F-n_~I6$a7Dc25mH+2MAultZ0EE7!TJ^{ zUSxeE+5cvt`9OqHOW5Tc(lHk&EgEYZ2O z8_v(LMkUq#2*;z;Y4Ui*tf;$Y|5t`RV29bTGjxD&jZU+jxRD!DOo5|!S$G#@_X}9XuW$dBxP4GO9S2pl1v&YaoTZo} zdQDkr!2pMxJVWGIhLH}H#yVUY84m*iekaLFoA1%YlyRt;R3r^03hrYVD2GCatl@I;? zkEFAHX!8Bu_!t95j}g+2(W6F(h{S-=-Q6MGV2vIr-6GvcD=FO}-6)NefFcHO7mkqsz(qvo-{Rq| zzn|3<^t^6fBf)IQLyb30ABs$}+G`;F(8pitsh%%TVMKaEE4k~3YQ`%{xxl8d0P&JsTv zoH}ecM9bqqY^*e?jU|ti9@SR?K)As>@l%9+2qDA;j=nJ)@&=Chian=6*^eq5UA}c9 zsx{wGX!p#XOuPV-W9=R{Y8h^W0L+RS#Mi(@?^-g^Bw zeZnZqvZoBbcRH=P#xm(v{>7o|yjkc=s+P!M*W1?6O|58++y<esey;<@W7Lne|3WE zw-*>0FhKV)2?*-vfXczM7cEnF)oe;JonxRT5zlHAuR!w@iM93ffpR9de)Qnd?cY%$ z6y?p-@|^}-c|=vR^}eZtc>BwTsLU2U16N%0HZ$I5XwP*Tv`^6^O_g_eDu{Dt;fx^8 zl5w|W<`LdWT{7do0!|;yjN-}Xf&Qnm*i6>#WS+miAqsA}6pgB}Z*92c5}w#Ct;#QX z-JKueo=Ri5o2l9LB?G;y2mw}a5K2Zb#atCIU~iy4lcvSnfT*v)ph*z)8RrAWby_T& zj#KB~#+}HRD9=`LsQyz&|GLLQa;F;aIY$hGengy!2d)7@e`rvvOme${|v0ltQW*zf8oerhX1ne{#wK_5O)RCoceyiST>hM0=%$ZM+uD>sbpIcs-! z{;ZOvxo6K88UA2jX}nz<_QVf4FCvi#k15;nbJi@=2U?!6yZJBB{gzr57VRkK@Z}K! zD?~XMQ~Pqht+cpuo^#H4tMu-p^=luG3ikE-CGDkIht)i?47s1@{4W+9|7+pp*NVwn zKGoB{kvC6#v5>wBpn?#?=LJpk`=|gacu}yYXvH%ETM9?=5`k8_uRuhC%))A&$fWyf znkvjqt5nmLL>Ehi@5nJgw7Cv}&jB6X-i*IZk3Z3)PWtgFxi-d0RBv37)gWo2`ZDHs z`?7I|$~lu>6(?SvpXt{1zseLB2*zP=u;;;Sn}>KHZCB9&NVh- zF-^`kb|RgjPa<}pp!G4RGk3D}Mu|a~U&Gz{P444ys~4~TcZ|t==tF( z{W}@!&>Tra60TP$3TY+Y4J_ItUdQWgmA_^Cu@|Ko8dsLoh6Qda+Y?V&{Hd798ckm` z|3MvS66|X%hEE5wZp9bc7%RUG&Z`9AL2+Lz$YtsTMD`u-C{#e?G1B?Ap(gqMrHZ9& zcxN(*BJ2r;A}vE%@b)!>q^jNX!-bu$A~WThW%va}^8H4H20kiP|OkNiO{?0lVodt@B88ga-N zg=$w$w3I^$GtgVls-@APjUg?V9Zcs-!iW?8`(y&2N#6HL9XwM;faUuDLvG%~x(WCq z7{e4;5f3bp_$68JFukbp5WI-5nBb&og^di_d?fJD`azG*Uo*9wg&gm)G8UNt=RvIU;Lhv$*W5OFB$N z&acbb;cl~M^w-Vp&e9P@PDActp;hraJBs#lVAhm@SJVVYD!V59y}%jDwO2 zJs1^C7DWI2({IenvAbZp?|D+A{u$LAldYv!UZs@;Lrf}nVD*NdcB`m+G}qS_Qq+oU zN0=P#-OHqmgUO|@*E$;gC~^*+M~t|A@$-D5IrqfZO6)&Axnou7zFh@O``j3g)G3TJ zU~(Cz;3>K+v0{p*Vh+mZFLeVMG%+LPYuqgQ!@dk}9P;H1 zxi`**LSN~<^CkB*{NB7jR(0qym;E`Mq>P_G-9kyOVMJy<=jh`B52CP)m*TfQ3J(NO zD7P3l7M-BCnqWZYhJPkI>Yt67QB<^?ELi*&Y<=LNu@;bL*awK0Y!GhxMIHB&4V$vc zK2D>?i3Y3F8gYr`8w@#KB>256N~g|~iPAYMm zs1poc9<6C{-wD}cb-RCc!Y>Sg32wHkCbpgXI1dNrEP&1_8BLY>4n*+Ogl6Qyn6QR+ z;3id+3diJOxSBZ{zk05uZ%mR<(d&U$7Fi97=c&$)3KdnOEoP!BN7Luqx#)t#ejH7^ zh#f7c9-bzlnVoVdq7hMlu&18oEC^1p>}#s5F^fyTYW}pz(#!Dc2~+!8L3&g0Ma9i` zzk^sg(wOx*YWz`!W?-r@u?ofpEwL;>(Q>!9l#8=0;m}$LAT!c_N*D#?Y){qo!$)?r z%8wQB$N5(6Duyqx3T7%IlYIBf6XZTjN_ClMuBM(m32sm#1ZWWOj3sMvq{nCl$7e(= zM&>VCr;lsn7wYlSD1^p2%M@%ma6S-nd$DEq?Z4ZgtL9l{xQ0mLlQ6j(eUZat1+gz7)4F6teY6;+}832RJ27i)W|go`1D^4wd$?^eQM$UM+qt!Z9SWMj6|MEwq_M&fw1 zT_-(4wXz_ zZ6B&=xUJ#f!Rwi4ANPM~SEpM;rOMVhiH`==kLY@ikGD5|WU{{DYzALp!@6zBzU$5z89af^-NH zNZbr*vubXOneqCj2JWI%rf> z|J*_#Cu%{ga_>BN?B9vo#egpC7X4LNwzJMG?%Q!?8VaQ9O>@i+cnBjxD=3iO-KX-x z%Dqy|YE%!BUNrm$Z(?u=FbG^gGA25@CQHLc1!MlV!4Lon@Le^ z)g)fB0>h8*KX_jwx;O{|xHLGMrP0j7jsR9NP@@LElL}Z-JrxwMHH=`0Go)Q@cea#t zdHAT6cTe`1F&9?!v)-XrFwXyInwU*YBq>$@L@Uaf7`3<7^cg!VuZ?PM{pz@{_hui` z@1h`O4fYTf)BS-dB{@W0Y=6ndAj>OqMp~8Y(=BNc z_Fm`NA0b)Mjk|>hIsbgLKrkmak&C1yki!)yzZQC+!V`N{nIm7Cda#+1EkMNx(o z9f=>QZ{V2|GltTYAZ4}fAbYdC0ZMe@1C`R%|p&_zZ;f#f;RuV)w!$>5c;hINVs#}ukr}}K>H-MGIaW@L9X$q_lWNw zbHBH4asp(M0oCOI0^F2|7+p;!ylwhD7LSOESeVVQHQdLskJ^@sc53%M2RkjkUS<>b zbJqRmAS0d|b_fBbU{N#~uT$PaO_K-je4KTJ)n}-TPBycV3~GWa@Qk^YPe_zjo1XDA zvKINE+0=t$>08g6f7kac2DGiW_Jmh#J)bPB!HFx4`YO)ooReD79C_LimHbTnP*8w{ z^qq~e^oxe2xszQ=-$6lRO9dHn&d+GwyLA27z#$6P*cLV^)rJfQG@z3aTwe3HIvr0R zsi?-;#HbDe%0C&1l*n{XlV<~@P}2T>Zf8Y;z5acOU^f95Vqul?`o-r&qYM+zI7JMZ%2i9au_cEnW6`#;X!%IN+SnI?EYhy#c&h=+MT@Lm=H&DQe59{)Q zkfHY06Iau@=-BY#$+v!lMix|1i5RFq;QU*3$C!m|BfZy6t5Yxv{$3-E8I@8R@zH@nVG^_l+O`Kvur} zk~mOPw*(&)F5mZ0Ouu75P4-5&yvzf+XX+oE*pm%=5_FP~a}k+!C%DQ~7a0GNSR`ve zK30CTk}wMKBbQ;82Y z9%I_CG($#Dz+np;k62sO6$HOhLr#+Bq$SYl@dnM^FgcMBX`o|95tH#+(T*pIQrvNo z*IY26tvND{=+d8Y*bPsm#LgBuNs9BV8{=gnUg|3c#u3k7Rk9<}+)#*DLsxx61rnws4S7 zGbD@a0v%7ypu|0t%wyo;mcz5acCW$O^*cdG#r{T}N$GIM{^NK-ha%$}x%X1pf zfe`DY(16%atTq3{^dp7~#>s96bxH~y+ znEbwG3s^2S_J<~)-IJFTu3tw7OYxc@NU=sU`Sw*c{>Qg3epxmG;BY+UvWyf%h>X1i z-bmvm46zEMaqN2pW_m~|hH_KCv0`*RxBC#?=%Z~KP8Ua2w8BnES2#BSQf&(%+Mpcr z0taF#`$$=|<%;-+AZez1%^roN0+X()@Yc7A#UGBs^nCR4AKU~7p>sLa@^u7SqPy1U z6~ChS0^jkr=CSzNpf@}vH@CJ%Lq6p{>oQ(enzpEb$h219{fNfhpGbhSx!5={l&&4~ zx#kkNSV5TnNLiKcC8HnXEj1O^Y$C-Lc=S9~>nQKVu8@=dJ)KzN6$rhY3r*VM1LPYS znU;n}r02U!#r9YGooC&r)1!%|7wawe-iS_{{a`fxO(%$GsCmrqHu33CP1xA2p|C*v zWKVdWC#7NR&HY?+_rdwFU-Z8jl%PpZ+<$Mjh{I|9r|r_Kkj2SqVKY&M;rEwIcQh}7vk%#PSUwbzaDL!*6t%B<3z?>6eCP2cA_DiwvL28pAVEhX-sO26*#I)@&qu61=T zG3P~K%}Vs~U;Go(8;tTp+*?SK`Lc9MCK<=Kpo#vsML0hY*MyM9!Gi95d3Y8~=$0h^NS{L9c(1(#>Y_&E7__R{ zps0F-bgR5E>jf5>j;&TX&kvg~RrCGVn=6Y2PB-)G2_HWT>Ulv#oI}9Ot?BpZN3i7t zl8{4DWy6z`YW63AHy*YPG#NWlrpCqJJ=xEU8Or8#oSE;l=-@NA?{^a=5KVoe;WVZ6 z&{ujg&^p_)dE%kach>W7OOoY97XiO7pA2(|R-B50RqaKhILx9b^%Wdvq-7#iBOrh> z6Wag_brf~UXyMUqr|#Ci(8<7~LzoJId3xnr9V zpJv`swjeIu`dvI>MkI@k&7h0emwP5+FNM^U69s0Ki1au--3L=1=V#u>w#vDTjXJt6 z=Jvd%t#rTlr294>sIvU&IL%WTHMO_-MXWc}CGgQjXK&$q$zUB4k~+Qsn-?3WV?6+T zOnAgWFqQ=$0F;928FPtc+o@4509|oBiifdf$k9LcqO^5)gT{C;enss+jO6zv)?PfQmvpcwPjCuGd-m%Zwu)zDd zzd`sX?|StXqdb2u%ky91!lFLC=(W_vt@wgT_$&(9$m|qjSP@)q%J_X)d{xSJI*J2r zNijCJhB^VBInD;lIMiYZ%Y7t9+kKxlPj$kaQ$4AYE*~{0P?DGOj(ZB)k7cep?x>Ja z832lr@m1v{SLySz^AXh)cw$$E)*?F2_X&W22s}l)%euTWX_zJz zbV@KmZ&o;rv#)iR&)v_Iits~vBiwz6S{_qNFbvey9 zXg^LWkziLIP26u$GC(j;4D_|PvSdg0y&^EDw^P{Wq){3n0)bJ1>}@nRmZ^nUWD>to zHI~qe=FhA}hCbip;ZT9&SCaRU=>8bB<^0~e8hQ*)K-J_k3aFqI`s^eYgCw@DRn6Ru z+o_OrrRJ12P|?FJw?y*P#$(wXV9`|JJry(HZXgTl?0Bt+T_pyuSOM}1Po*ILIcZy_ zVNDABG3&YKg%!mFJHJ;C>jSZ9xtix>id1Uk9Wi81@gHa0{0XJavacIt3_ZOnS4f+y ze>l*DtjOrqw@Dk8Z$70K3i`4+bUz@JMg$KJwh;5~#T|2b3IGWK5K!5|{AR*QC@6tl z$cRWb`fm?-6ZOCur3K1|qJ+o~BhOj+ikdhwp6a2(fJ@FoFw+iiz~ zVWa(+!xL8itn%mWCeOduZ)aYajaq6%$Va{tC;}2-XK_FCpuns?2g+%)yzicZNZ{*LFYc)^+m=j)HMsF4(%6;jOe1|G287*ki7&2@91oy1v%FK0iiA2Pon)#? zs}vEfr*Z-z#&{2+H1MVD^-x|!d8!X1S01NqwPVd*29l4jMRmr$f;f@lt!Z&5Tahfr zJUVkSxxraCBY*v}b71w3kV2dRYcfpDfQWRHwX7J=c`Oa;(K24Qwrp6Hb-@CGb z=nNnr!(p5H!!qjp-|td}nqIVeOHf@j+T1q%!rxXA7F3@P+|?w9`J~5q`g^mHP z#%8UJUyU&@~l+mdBq+?;fKjd5a&i4eq5mYpMsE==9s)XQN@JEarII9^%U-)RUUI-f~Fx zKvhGn{S`GKFsRANKn+`hvu01!R0N#ud#p(sNO8KoK%3)&ne$D9}`#TUV@Zcvv=!r&VA{Pm@$y;5G z5%HMzViDf(Ar%LpGuWQJnFo@LO@RtxAAo1oG-XP$+`a@DLkI`i^~Xq+_+jj)o!;6DA|{9k)gVo8#8t*_ z731yL!hL;`wQMzn3Mf$A@zEJEDjGWT@7t1w#|1rlqOK?EWaF>H1H7cT=^v_QRcdcp z-uOF6hrJY9yv+Db)O+8en<4*NP~G_deajHeu1&@%&Zp^8;%Duv+#z8 zZoiI+#m;TM##}?bRj380gtT$UYZBriF#WGRYd%gN1pF&x0dlk~1Tb}+sM>CW6=DJO zRBjW%C~hJ=DdZPI2=G5%99No4hbl={$ibo;+-Qn2R3rgTvU7z;#T5xjCv&3930dvN zTp~?fhgjRS_JF_$U|chS>u2OEYb#U(^Ctc(AKI__IKCxxQ1^1Burkm8SmiFM$D2U3 zDewU?-L>0|S#^45W+n8V=(x9fDbw!B5$o$0k4`t;wnIK9<{AU-4B01)Zu~kiFnIuj ziI9))>?IZqAc2AC37F~e^rrFh(Q`q1F?xjb*Z~i&;upj5uMlo}mg@MtQyL{ee|I)4 z1>CVYCnTg(yt^hgr`&&b)LO<}eIm1;rZ3cJxZ=CN?SV!LebM0;{w@>F-T%4%_Avx` z|G(T<&%gfElPiZdFjFU~(RTpb@~<-G@uR3YPjyHMghzOSBhbfj+T@1XU`;9Ju3)wA zz!+1Fe8DGSdu%7cD*_+^zdW#MG@46gD^i3#hL zN%&vjG0q%Ajw*3Gn!8L)`tcDgtnP$Om7OUkEhfG=`|c6*W|)_EjEP6m739xyjdxHD zB9JL~x%U*o$nzvlgu>1S8DHjLJQ*Sy#BX>?SM5`Oou5skRs0RSKd zB$Rs2I4YOcQDC_Zq_Bs-x6_-g%&~X^p z@_<%~YT2gX(EBoYwd6hR<&Tcddc|C7PC8F!r9mZ5(42j?$CgY$ia{lkjYS8Jyi z#F4OYyEve3wqfyv@p=)o^It?CSsxJm+F;7jvTvr3KuM8Hn9P0$zE}b=m7kRv$g%B* z{JhK;rQp<#;pSH2$TNrB8_0aCz!Jl1coYCn!RJqSn~q!**D?a-!u28|N=XTbu>{d8 zC>U>mtXcmDpru&8RMLZIi9h52gKA|ZyLGVL3)Rjj_{`HEHTV3rsOs8rM~QtXN!|CZ z*1vBPo`~ox@h{4yD-KypoECor_gcRVdY!)e=hW*xcYSa0qwBSgDHEf*L#d%^dI zVxrsx=i76T4yqd)&TIxQCmMSZ_#>ESm8ir$37usv&^(Maz$L>C7z=wmi%Rx9`S3ndD+iG#QK4s@8Ny8{X}S2$gfW2w%CiGA z5vyxtrcGwCY8KU8xPj>=Uv}2KXxGXN&CVs^?%d+;0qKV;-eGiElI<_vNQ{@ z=7BV*S(w)`R-J;j$CjvNWtpXqeYp;x8h9Blr;R3pf4cz+pM!pGjnl=833vA2E&0<1Tg2Va$AO{ zQK-hla(WeAmNzph8Eb6YoIC<8RhVqCpnB`oVlw9%&% z;Ju5om5N>ruZi;g8`1`=ahwuZ=UW{~+E}m@ZZMfk{!Pu_9RHrqqGc|6|F-I1UM4*5 zw$T+BHOSv)hbPz-%_ye_jMschEbvTsYr&VA3S|(t8vCiztaK|(WMEbvWf-pLDr@bQ zp%>E8t=mDu6X~zuK|DKDyUS!&r8-xp4Ay{JT`KE(SA4Lb@sF8PW*FaLhKkQT>b9uJmw$H z0GQc}1VmH~f$%^?>J$ldrnWOQuLYHaS*bneMF%w^KFOkgQOYyKPifL91XRq3v)WL1 z+jPV|TW1t|$zJ+c{aq>Niw!mwDH2<>h?7 zMcud)@9N93jVCJ&0(rxgihDT$<$(=D`-W|+XTG&dS;wTKeD=oftE=otsuG;5A*BRd z^;sui3cV78YY&sV=e0SOHc6$g?==%wOiNo-6ZWI4>X1_x?|(T|Zh&T&D9q%A2+O ztV|&7OjULEfGkWZ)ZLAPqzem1m+2~MIaWFe2CFH=Aw{lIF89Y#Ef)vS749(3cgWs~ zpw{VEKU*o9G}jo+UzKitommrNTZlg}?!JigocZ)+zhLXXmbvMMk2xiXFHr?PMxX9} zzco|=V6`OHwdBd|>;SbCpoT=e$pIv0-hMP!0a4H4rfPf-ED+c&hRl63$eqIKI{Q_c zSuF!hMUBMMm^Re)e4p>k!>{+(3;od__(@qmSbP5U5RCC3nREuZT2+e$o7%FcIIcug_((YY7bty29bZF-dB z$usV5RFP?7Mpd-4lVqieAN%Tc9;EsGjD(%jww|SLjgyi}4AcK$LAdPzM#sflUa`j4 z8YdS{T=@pR(YOwl|3r$&iFjFwYXOaoyXPBK{Uy#+C-0-CpnK_uc_txy9y|1Gl(`Bn zDzKHHB5FH9T2uPHdsT$JE$iPh2j?gjM=E^NF+ZOODENK)@8`cun2K114M7Y90ob8z zh@BjtL$b^DJvuQQhx+@YL458m-{^jF;6C*8D7UroOyqV63;Xi5ChWeTV^ujH4z1ag z<%+{3=s-)P@!LytY#fti@~U1*OeyL$4>IU|Kx?T=3%P%n$?{A`e4u)}B9bDRrxw~N z^U6$1U`*`k!s>6RqOKaXch#@pC^j{uV2KoSBG z@9y3YiUwSEV&>pEs`3Lv-tGiA{3!0lM5CyQHf>}}v*+e2FuUh3)^}6D=qy&$EuPLX zCXR&LA#CjcS+6c)O87n!z4>in)7)V`A9-a(pjUErHShnk_-1B9RyLf?RY8tF*(D@*OnN&bwvVWpm-@ek3|iqH$Ul|SBkp*9aIgrhgpJwC88mG&3`03|?R zf?CBkgZ%)^Nk5Taml)}zq6oYr@)4^s?X2rV9yCY->VAZtwxNH1SmSiu4j-%nbWoDG zIi>hzge&S@{uayJua?;s`H=gar7}G#AHAFE@Mfs%NA3H3ZgY+Q)d>KC0rz1EXC&uER3KZr&+#Z z4M=)d{Nma$_~DrU18Gz*5dLE^=KA9WBC@8mEKBO3Z>l(yc&ddE@=;_7K!d^!JQ1rO z+gc~of6zG{xiUCWJu?kbiQ}3?7BkL5YF6hClqO--Q!Mbao?K1XGx2a**fHGAEkhxJ z!TX3gOi=5=Jo3_ysF*etSfj>&C)nd!s(=On0I`JVD6xu(f+C}dL>+Y@7;q{rERv9e zlfYcT$7h>!d!p%aLn$O{Fm!7H z$ivZWgftH#-Z6yZ0@V9Sv|Fix03#$|iX}icE|p{|K0o_3*7p$3<<`sWNmWHWY)foaT?$F2` z!4r6O;l7eN3e1me?={9ZiP@M>iES}1TS(5@5x--tgY{=#ZgYwEGAJ~!07sbH> z+EYX!p{b=HK@NSg#;=NG`I-{5h0cuyO{x!{%FY3Smo>FU%P$?L=2LewjaOQC82x2S z8r$XEB+Om(;QP<;UWl{pDRty1$4`izYZl zzg?HK>7FTF|Gu`N8O=y~y7lbP>HbjaW$ln$$=JPlzCC!5CuNc@9IeY!SW1FVsco(} zq#X`LtMLl=P}DwMP_;=(e)d5G7S7D0M+k9M*;Q?G{=k5xhgi}TBSHY|004N+9MXzp zkEg*9#J@tKK|s7rfPx8+ZJXrNiDFH+jIh80+r6`7&_rN_Nt_uYGNnpFJA_<}B2SDD z<)|P7&Gyipr1uJ3OjVf?SyR@yl8Va^4I_lS&3mJEWnKw=V6CCX6O0N2Jah=zRbpd$ zS&wu~baOI0-Q8^<&Th?<6fzL9Ec!?1!OQCF zTNGy@L0I<7sFzrV%NN`3^nd)36GdFAeGlA_brq7X72m6~@SXH=AIz3q0zo~Ml0rn9 z2ps6UbPQ*PysJ+`*AkZ}NV%4P5N>8Q2*cXAj(x|-g!r))VI41ZcFpL_j4?=m6;Th{ zryyhHL(u_07!x8h=c?%(LFFDxfBAh zIE9>`zzW-a=%!Z>$dRH!4RyD?to0Aw@gRZxk!?+hxpE4Ll7-C1-l)`bR2pk5Td)%~oCS%cm=6~QHcAo4-zP)6!# zE7CHsmrp=ab!22{`Sh{qqm^gKuB2D-E)_<%V?A9PA#L;?7e71}*Ipcdxgit~H?sPD zl{tskhfCg><4FgRwYdhljIc0Z=t=5)QWye;%?@dzZdvd-R3)%dc&s+`!0z0dp7}+p zVdQ1aFwwYE%#FRROZ1he znZ{}VcW}SgEG6UgQ#pdAsih5dE@yC`{dH!pwO_-;X8w6l`^_5VC;P2A*SjS{Rc{@J zq=M0@maWR)#)|eJFd#lbjh_FfUx|hoT+s-x#P@S?EQG*_vIsqQN`>YJ&UU97JLvii zbj|)5!m~Yb63TcPXHu@o(rxl+iK4fzu(&XPtFzDWM`jUo!{XHQWvr&+uYK{xKJnoc zWxo?*#|9-DU-{@7M7u)}z1%Oxsu@uR_GBMrqbr)|vG|l1#y?;;)+C}boG;y7EHl){ z3(08U;pkl#^;*@=yN~DcU;qZuMxY);#YIR4rPa36NdSllYXLBg%@8mzVKPWqKBN5a zvjNOvrbJPek=1b1Hky%%3y1Iu`Qj-cSmSVWSgNy)ktVVhHltoN@Hj! z@c@v+KH%7h0d`~(&(f@D&y{^T!8ar-^YzyUy;JT>8MdL zmxg%GF%szd*;Z|92bjh@$KNk4v0|O@;71oF7M~SMuGU0~&Q80E zaWfbXsKg&p5{1NR6|#bl8buw})|pL1hS>HB-E$}0J6MK$kqmh3V*8$)su&taY$V{;yCdT?iz4r+xN<*W;Ptagmrh5Wa=|)C+)jeFVIo$YD z5rka)&Y6qYGT{E8XsBW0qL`GtFDG47m!1|Nc1hDY?qo)tqn6uqHdd;)G zSQXi9dVlM>m7DJIrqw#oJBf-gM~8+bujO~+=>p;fLP}zEW0cgx38&md@a%b=`BRAP zo&prje{KU@`;dhD)oZmuF7xBjU{~nxDqtlK_sHd&l34WRNFb`dUQUa&^IPoaylA_i zRe#rpdVk+w%UlYT^o#_nPC?59jr`^C&-T15@lPXy8qFIR;@Dn3-g=7o+G0KHpiE!k^0mKJDQ#G!G3{SgEp0du36-52Bql3UH_tD>E}{``Vrne@}sYa3pe zlZ?t(In-!e&bA3e=FbbjwOr(@TDIqjQ%Q2*rr^|osWg?`2Or6zIt0cSJTUW19(f4>p_y)a9tOmc+}_)Zj*04%oFPGVeL=vr@E+FB@0XIs1T3@lwxX1*Ijaj_vkk zruqw-K=C&fipBi$1G!|x$w_zlXWwf~aV{wUKp{_cAWjAO`71RYJ>+sG0pfv23f51I zEVWG4M|5|H9?a44mnSR#wD4JQ3_m!l$!o5I7IU#0vc%K4>O3^5)ap0#cxB#}qpDF? zBcs_x{Kwfw0cI@;q=~_w4Yc6ny;jOpgviu1%Oljvd&eY>foUg$Mp(Xu|UWK$K|~d{j`|bjZ@; zbV2}+DS8cKYpqaH#I9bbS#CeRF~J?JxLIxa>}O~?&_4O)iG6_nuBD~V?$6i0TN^{w zVU8D#?-%PJO0Et^UmD-rpYjDBY~J!E)W8xtn9Ooud6Je7f?phutvf#mQm6StD@HuT zZS~jTy%Oz{*!+BPWRyXZ^@iKK$0J9-e$#f*C;vC@oqqb1Mn<1>=JpE!M&OPFVIok@ zgjA6PIrM{YO<{Z)f75LUHqe!9>ai(rT&3+-Hw9}WNL)iAk4Lkb;U6rgktCp0 z1v(Yl5tQiu7R{nmPL7$fu z!wyx|Sly_7>)D*L%Jm`c$CG9Iv-o}#Et(8WCsp+GpO~IU5unWGzp07a7EP(Yc8kfQ zQBcxIavU9a-1HdNoPAd8liI(drK*W%`Y<5$!O8DJ1!B#`x%M=*Inx-UcF2K8BBt1x zjU9|8MNfQ0Sy_7Wmi6jrx)pUUTQM{4ig3ql3YKccou49F$D+~;hBTA2IT8=EtcK!x z+A9fqs?y{9wnr6z!I!eq>UMP_iH^;6qscweRFe}Lo||juvtbfvYimdeL@Du10^Q2D zT1bTP>5^~eRNS+!)L2i**#>x{j^;KYc4r>5Rz@k9F^)71(?pmsh>-^dkOEcNnkALU z8-h`0GCpu9T7F-FrJc77U7DIhpsVOak`A!o8o;xdBmtW`elCKdWa2d{oYf0I_%9OV zY0QvK$u@dAx~G7-%i~4lw_1dpV>peiyu~*=eAX8}ID0O}-|3ylIzE67rFKZ|_HG>8 z|6!wTmjSI(FloS@j>B?LiuO6@TCB?{{9{1sXBfMT1L>^!_Or{5jnP7Db9-b0z z(dP#%tEXMFq^Kz!*7eD&y>?pkGg33%TIAHGd`Hm_FWKSMsE||&7(b%yRKF0i{=Mw+ za3{{}nqU7!{k>OtX#CRjEgh->2E?b_LF3cs<>Ud16*7tmSxn1KlfHO`<^T1Who@N{ zh+xyl19BOJUq7^AYE0wpc{nX;9on&(28}bE>kbsM(S4^>Jw_ExH7(7grogD0EP)SB zI8zE`FeSBj(VfufNjFz8m5wxwT#pQlYkm9P$|-?WSN=ZdaY^eCmy<6~-Qi@(x|@bx zzlRYOBlfVh^C2RZ9p8<;Mv}wdT&svasIxi*z4%W|=K!cfZ3722vH8-(6Aib>JSZnO zt}^wni4k&Z%E&{;lzh%qADiQ?Poj_9K~KbuJ4X#2YKQJ7DL6f+Vc}aZZM-#&xhO-{ z(sl0^eNBA_fR=p7nZnm01UkY9Tw`fE8C&Ivv5?O-P62{?b?)cRlVl`p`SQLcp9zW3 zQN{4#AuOX&Dm{aIf>^kSn4^>Z2bwjK#wauAim+rMDN0Fcb`@Spzri^fF_b_Pp_3V+NN9P&0X+un2MIA43P_T^UVdU~ik#MI^4D2JF!f%2bPwVP%E!xL`0^j7*VmW!3CZTV{n6B{k}JM)$bF zI{k!}6o1ncRVLD5;PzNLIIdrV){v|JdYY3Wqr9A~*_bm!|A8YUS36t^F&HEODi#M~ zb(@o#-{?AliuyZdcsaSNBBCJfGffQ66E~xyCR%~2JIhcL3!dnWF3tpVj*^V z5+3X%vvbD=(ZFm!UPNr=e21K>zJe#tux+kIJh$C`*7!alH*CcWsi&V5`oL39Q;6j$ z?UMzw!>@E%yw^>HKz77_=`KMH3865Q=>#PpY}_SlH)uyg1y6NzFALMHb|%L1o7Hh9 zBFaP`E|YzH$!|y(#dMGHTnQgsBqv@3tJYht7I^npgb-8gMx z9Mw8OnPWyW%$!S}c_Fe-bSe#RI6mzz@BDqhP1ZH3or3Ytc?0}#mkn{=_*RwUvcUAW5BDZ%Uv z>0c9$bHw0QvW1Th{=qmHjlThDvss#OSILG$9ppb>W`G3+MWNdSeUrQZUPXu5+Whk({>IN68#w zWK`pXq|DC0N4$2;XcTknt*gehT$J1A!l8F8?v(27sOahhyzN1$DV_`qmFO(jUGk1x z4$A`rh%1LZy4+my#gCAm|b|FRGO`=G~@Q>_J?r7``JjUuo>fv z_LZ&o&ZRiqYZ}at?#10FSKMP0MAt-tu-w6;X5&!V0mO>;<>7zCW3f zh>iMZy(vufUAEgJXgd?WBY<$b>LNSp-&bxej8?PHv^7lJC==5WF`ldGtvv zYhTkwhA3v5(!_=f!9x9PVOKVSvI$cOkU_a_cK`dpMAZO9=VR0BFEYTLENX0JBbpRJ ze^1P$bK;S#sO_YVuo1#hNtlt?996J0Z3qqnEP+~fQ%LH;A`#bN89f=Bp_K=9V8=XG z9x}lUGyxD6um9Dn~YFZugE|rwu zUOV~Y{-Vpqf(a%BmSLj@Uf=$U-~xg|L~*p!a50ft5%3{l!4b6&$^jTDrYO096x2jQ z3kr}stl|5{M9ty3x%hpyl}=NtG^#X^j)`%G%8*s8t5P{42GYzrK^72Q(iys%)a5@l zm5NmbkjgS;hvW-AcQWBC7({f01~ZYN0;PowR7%8~6AqHRl*A5HS`S{L*a#yF z5@A+=ZVt5wD?@~@cxwBaj%$Q08>uP0&xIZ(hXy%qVlS*TOP3S9t!5IJlM9YSxs;k!>o}~4+MiU}ELfDb`a7{{L;VqpLo#eZeH1UwD(f` zHCerydbvAF9?_b&J%;)?jTe5X(KLpD2;R6IhyxfjL|Ha*xkODZF;oBqkghgc0C5v} z9}8F=TW%T{E-2J8m~YK45r?Dt>mZ*9R);k4)cE^n`_y4sHTpAU?I^8YFkFcN{B*MQ zYrB&d{4d|Bsp#{|-uuyuwKSve&=!$_Fl8AdqoM=OW-}mcVjYhpq{dkt!!?90fkJ{t zBKMuz2Od{fx zq69f0VL+H>d}~ZCsi|n1rRtt8f!7*QkEn&3>94xJt>2}_J;N37GHHCScwX1= z#%wrmejW`XglMt?l4@j7+!V~zZ8V%MKxjk)1<{N?mjnO%uteu?eVX-}Tw`ut#Fi+DIC6w0 zU^qbm!z*Qm!S{?_LWx|KxzWWJ&B910P;fOIpMo$xib&^Al&a;S>(cUUwxrpVbm6t1 z_N%y?r$QIoOuFCwCrtUwZM-j%-_Ahp?CQwW9vN73UCb%roh$gNwhH_sSB$qINhm%M zMH!4_3)%}2A;iQmvnY^J^@5xdp-_!J5+*l6A!H3UR|4^RBZ!m^WVQ^CT?xs@1oxpO zk_X~qQ!~S~Pn%(}?YoyWWdfyzf9aUFGWGtYnv|H^4@SdOT^(iBjo$l^=QGa7nC12R z)l-0yD%T@mR4b5HaZrRS;o%nxGCnL;hyN(Dsu$^=t27VVTN~6{OCL6d8X(XZl}y}? zw4Evd1);I`A*MN-){9=LI%H#Xq_t7A`dZ8}nj2B&@)x%7d-#S|I=(i#>N zEG!XZA!HGO<)+2ggBh~m`f=!#ix}o4vn)MU-6BlLjoIik(C$Id)fI@0Q}Ro2O4cn^ zZ;0C@>axN@;Ta#+HYYDBZ;nz^WCXCJW~H>e67|P-cWfXmYGh?5i7paO1hr4#wo1Zt&V+2urq(g0CTCVc&(2v13?+&=sNr0HK%$uqq%&Co599Q0*lcp7nGeEFA zLGZ9GGcdPYwUtwCAoa{s5eJEeSQM+<_Yw~ogn|iB%{0qO|NF2+=KuuuXw>Tr9Qcqd zde30Pt5VH}QSBskDpfG*y@nbURjdtp2^}#B(4GdUmlEJ1so;g)*Ac};*>r_r+=|+SbLSfyg=rEaoi>jhd=x6`|N}S9&`3;}3$KpTTqsYB0hHnU-C$Z0k#M zn>&}AOwQfsq#Z3@+lOa0qmx{C>C?klP7!o9 zbD^NEYKH(f6e1|)NR-Z0N|Xc&W28_ix&RaLAFZJ(MEg^5m6xK{xiVh0l5U#`F&t}_%6w_I&XRawnYh3g;$r`hp5rn(2=`!8PqF6OBwoV!L8;cca zPa>UCHj!|^ci_Cj)&#*GfTLHy=AxK6fMG?JIE*XaTUofn_o&B!AdV&gfVenCG$Nc@ zMZ^uJmq^13*YIv_Dy&k~HmS%X%B_@e>l1ydF*dd#pzqh2^rq6KJ{55(N5_7XMNX`wt9at+g};_ZCYHmY(mPykEj3putea11i@s}dnp`x znaujHVI!SV)rU>2B+;r|vgvh%jy|=%a=krqhe(PwQUMak4!=oUS@HTrWnSxxc!vil8MwQNfwl7 zM5h~DTUG?DA62_VvY5=ZhLb>+&roorG9!V8*M^v;JPE&QXyDLD~9mLe!svkXFU(6pkkpkyP55+pHo zOn@%8R(M`G2}N70e>YSc77LR(qe3;-L}`;lf$)%#3n<-0>j_iTwRe&)yHG}{+oo2@ zue80XY&xN^E}T&*6^*F}UiPfX0weM(k@;2>>MCep+d8P_S71Y^Y_1l91P(@OVkvpN z_{|n1hZUp_rUxa8Xh$RkQKV?(8vv|AD!N+>!44Fgp~@amP-hH81RMmp&)|>-#-$9s zQMs@l+?r>S|;X=V-Q)G2OaeC}X|NF2+*MJ1OW6|p= z9D0?j`b=pfpHiWVL#!lqN@THUv6Pw>8KCqvX-N*dIdqmu-GDAg6c8vNk)ofGfD}20r_5=#!fGmPphY;Ob0QScv?NLZ&Y3`p3<#P6 zpf3*@tCo|Nd>V+sqQu=#IY?tgS#_;{CL+Jxoyz4IEgZ9_X$Wn3D)^I94M$o;CNzM1 zkTA;fMw2WEAW{m;*O?EV!obyk8-sLgU8bL^K1xb%#(!VEbqOiLWALcS?5eRyT-Z#JDyl zQu_;VTE&1$R%M4qg;ao`=2HDl0h}=!q?M#nhOU63&~(9GS_upQAqs&3S`4aS{G<)X#|1d?0kjesfY6DFL=eV|ixtUGp*{cmutd;+1dn3T zYY837kV@(eWW%;oA&*7un0G2eG3YUq4!t6J-j=he6lFUE1UpF&{(}-`qG84dbA>CS z$_UvqYD8UToB{~~C|EEPG%(~4q_7fyppYOohOmgO0Kd!Gpv$J8nFKKz!WywJsk2E0 zhP6#{7Y9aZ1lfy|outz>@h+E4gM!KaiD|3}a)GB9lbAt%Gsezga^EY~WBE#|i0(oI zEqwaRIBsjn)Luhupm30g)~DXhkP-DbibRsbc%($OlS{J}4L9jT<5?mFHZnEA zWkx6m3Cq~c6$B?wo zk!>AQP!?#iXq;?CD7Zn5UICUY@+md<0<_qoXF091vq?e0k~^wVX6r|D4kh8UJ8SJf zhsX81U;ddJ)K7NY#E{y=OSPwed0!tm0CVQuc#OzhlqJb2!E6NG6@;vYs=)7G$cefN?iQt|3)DdhF*<5s)on90fdj>S`JpxEDI%SAnD|NFp1(SQWL zRM7h=G)k9@S}$cIk5b`pKddm(YGAVG^@bWHu-U+yr-juW?ku+}ZnHC?ttLO8seC<8 z+9CvmL6{;6R)T@2AyV0C#!)I5?2Vzv6g4qM>5Rfekfc=DS@M{gFh8xuEEvO;H%?zI zGN5L6$e&LVO;d7r)>NJLIMhwOS~^lB?KyjH?JFgTQ1PZZG|% zX(_TE%|s`PSpp<9LuTtusY0-|Ga++M!UPfwKpaIW3_udtXu5ZdzfIL$uF&)_5=OEN zL^cWHStR++;YH#_103LHvjY(-Wicq>Zst!wzq2_xDu-lxw4&m{&q)4Uom{!rSqPn^ zq?I3T@rM5pr^a@d&k+(`p3^_>K2`@v^>^E3HxXr*h|S(gFBAK8qxZdsw~j^Mh$B+c zNLe`nE&_^C6q|s+ia<$1g@B3&bQKKA5WWHt8=#PCl`?@szpOF|O6ePGh{>=m0gl@V zEG*1WzC!J8q54r7;_f3eY{3*_hNgyEzG`S#+0P_~7i#SsM1?t3wJ6kXGk8;sXet9W zwJqW+tV|FbH3%#)x~gP=`k?siq8Q~v$hIwh$0R(tvuIX8L3Pu0FYz&Jopci#X-8UU zxCTr9G(q46T;U*q1mf|PLNX=*y~hTrUX#>O;!8{7X<@M|GSdV2PH{=KA3$Bboz-D3 z>qm^~&b1^gKyw#%ij&N8lDwCLO`$OcTBzkQ;9pFq)-4c0ZU|K7ZgN|w)~7LudT7t; z&)VI2b0B3tVhy^vxK0N+k+Jx^1i4^Uz8LL}c=1vJQ*_0%C+K8$%oa`>;g) zfCRK((0eR1s*;PDy=5bgQx%6htUS%CSu$t+gpNE}va*nZ$|&;Kfn-SBoN?+fU$#~S ztw@nDo;jSzDzkGYZ45uER&w1#ABVG6)tHFpSb*a|Va*d9TLd-ZJ!Vrj4<7{q9)b>- zql3S4f#)H-+7hAXk1SZv(Ks6&SW*NcGA86HCk^pKAW(qK0W4etOAQE-kx{CW1-m>e zbz*Wnv_&npG}KO5c7+01@Tiq#Kb@zy3HypHxmin>w_CSnOLc0I@3nNx-4xcTP+nq?g~W2geoza1cBND#93{Wmes)zNk)+(((UM+D)Dk$jPveh4g{x3V zD;`C&R|{I)j1mGOfa6vp@0T|;i|0z!aDY_B3OeX`P7q|Ex*W1$!DjSo(TP%xM#>4) zxHG{DqyUTvuQ*Ea<&!al$dUI0{{E4 zMEHONn^Det3>)f@EP35wBac#rhcm1saq3F0=Jk^g9p>e4^h*eBG9C>2>G{=dAwy}~8%^4nA_kd4;i_SDh^U_+(;~o(1V)8TEHqkW zE}8U>7ciiL+onsF9;^dmQP!chS$%Xvp(sm?A!Vwjz@G{!(<0c?=;n;F-DROM4W$>f zA!FIC`Au6iTN^hl*Dzisxh=VkQjc!AoSbUCn6{&~$d#FX3w?uF)X_>)q+icRALjf; zx(Z0pR(>H^-|ze#CF$lWwoGdP8q3H6!!k?go#i74V`Nsg`67}TPbmkngSf9uZN+`0 z6t%9%CT<)&2pkl1lL}zkT2U7-k5?j+a%$*m$fEkrUG6jORF-YP=#^B^Kjvj)F+Pc8 zF_dJ(d5gm?HnMn~PHN4!TaL{ow*jLuD0)KX(LIDPEI!Hu zf}j|kkt2~2hSv}i6E5Ws(I%o<52u?}3pnKzYUGJ&G9M@ihXp2OF3r*>n(l%iv~?h6 z#*GQZbfGcIvocJxHAO?|ULH`A_68cs(Z5y*VDJ2R`xO3KYx|6;|;wvMiWf1Wb` z`>;g%00g30&Fc&_I*n+#iD1d8Qguf!tR!*jWGJS+mKr^<_UP?~6_pXQoP?2UCl<&a zS7v&Po}?Crfgvt~7TZ@_yAL5ez4y$k!t*j<{VQbbg|;T8URJp^CJu5oe)lU|v98tK z%>TP#X3VUs!&`GTIoMDC{xQ0H+0{rR%Vh$hO|{9OTK0$K@3coCd9$OsMrdfHm{ zFI!`JSQEnI*=10go7!TMh!3+wR7o}#VRIO;k!x z$2#hYltvZ;^|;B{`O0^+jYiX)(WtXFOw^|w5-UgR#nv)-yjEY-(35Xkm4#I?>VHzt zxWc0>zO>rJq(jLyX3nD#{iub#P*>*r%0aTPi-hQvm5`M6rirBlGZ7 z3^etRLYY;SiIkaQwwi842(w78k7Lg}gOC33^fG?w?v-nQ4TwE5Q=Y&afEUwf*a ziM#&JB8=xr(bm{tW$2}M*YKUe$uqeXki#_Cjnybe!Bbq1S z23aHYYY%QNDD6-RFmeT#RKOJr4H;Cb#W;)a(~T%#5b~75Aw==)Nl1O`T51xZn#1CF zivRntMEQUOwNy)M2{ibXO1X_;Bd-z}M=z`~G2$4g<#C1@J-p5cUPJtNsf(0Ggu`{L zPSML_H;U{>Ju{Q`{dKdW5_2OvR@R+z`~Rd`Gko*goOw%?QBOto%X2+jN(P+5X^O}5 zF-%pq?6;n`$zIjn@lnC0a$SD6Q5O=?C4`0zgj_0Ms~a*`6957jluZtb?X0FGA{7SS z7X=6nkr)>q69tt*+7baiIcWlA#`=s@BpKUAh(3fws-k(xGC-Qj6B4FLC?b-NUFg|R zJ;z+^j9Tu*+_IU?qPfoYeJ8)FSm85f2sx3djX5JwM)jeM0bbVbZ_ka_;pwXa>`aG| zC%u0KYEI?eED_QQrO+Bn{5m0IpgqVI698y!RnuPx9=dbAo42sFd#l;-X)8nvf)?L)}jU>lfq$0pZ1EC!U!v}Pe=*?7Y4kZ>j12pm8F zsH_c4-FC#i;Y_Yu*y!bA`NRjdDX|EBtac9xG=;T@b-V6OX^%<#TE*j>!w#XAn zWCtekg}WP#mR0tY(p~~WC>u#6-n{@xCfpztXp!>@Fu@5V0cgMs9R`&NhMMqOnxZc> zo<}8V@}a?$+6R_>#PyRkaAe_jQDZD3iBA*juuPEk5U(Tcryjf-LkZ>9J;-81 zqN8*F`=CVs00do6OzR0WdW7mZTw#chQb{Kg=zviEN{2;Qtq>9@;M zWm-hsE}5Gdwsd@+f@!uGH)FXqhIf@ zngvG!@fAS497wu|DUN*%*B>&GcEYnG#lI*vU^noLwxUB%KY`X1Zx6hyyCCw

<-&IpN6f$XU z$w#2N$5LOVyA7l(l@g7|NXeQoRzhM)sZs|<1$lsGBB1q?l1H8 zvdnB`tByv%A_@#xYluoRMGB-4R!$JI@RSu;*4humVyxM@YSaRp@YEt+XO9LHZ9{ED zEF~vFNB{evMBRV{07Xh;3^aOz2$@M?Bact{6)CJF(dq@HWwnHkbs|1WGM%2bW6;3> z>$Z`gaYBklEVcj0r%THmfMp8rNhu_nHmOdHHFr9%LzpHZLQ$9I1j|YM@=_1#?tFp` zv66dss$}N3-PK6N833l4jlyRX10))71(PFFS&?=LvKq-2L;@NDmi3&-k~&~K z+L_Q}(UId^V)8Mleje5-JX%URE^X|p?%iTywkX20$@v$x6f}mp+erD^XUCt*#H9ji z#p{CMEg9kB=ZS7vL=5Y&hyoab1P%~937aUdq(H^6_1FNPOvf_>vAL!LTb00wf9_r~;H2X(nj+gai)Ys;G!`T#c71K#Wi( zZ)r5+j_h^2Tg=+V%+=88;+$pM*D~fo=rrZ%;)6zlK-3{VhS4u5q+Cdb$um{*`oWje z#TC+woKD53)tu`96EZ(w2|jgjba$Ds-lGASBBq*NfM0F8sR<1^H|K6h1=4*?!qrI=Z zmRf2haKlCICvK0@hn8^DT%zQGrLvw8Dor(h$oAt)#@}e7<7ATcn3y!;CiU!rwmi!y z-*Wy4HooZp`>RwLu@XZ>032i+i6d*GqL^W)#YKIvjwt@-KNVBV+iFx8a*DN>)`s3E2vz2G z-0-^aRa}X<;zX2j{~YQh!%0h-Q6>(x<4m68{zBpN_; znAw$#)F#?_qUtwk@PT0G=5*F@ALyK&s=5qJ!

!z$uUX>i4{Au$2Bwg~3BxCaKqz6f(lTIY{ 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

    rfnDS>*@qM72%ZOLJ5AUY({Nq^hOQP~O&eRld{h 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

    |Ed~N9g45X+mrezCrJgenAj~2 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 $ROJ=y4R_B~h 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

    D2=qG~9gc-t(|H3TP4#jtgKewFW*?x3oRHM z{`M?){%cn;p?r3EP5Hln9V)MsudaSx-mtE?e2V5kc~m#O%4)h@OSg5c|8B3 zultTzumQ34Y*6HIhYP(&BZpBg*^l6f*xul=L|Zrm+b8xA-#lp`1H?@QTTlnp zUeQ>doKNJ~StIDz81u<@X_L?vY!-n;S_>`&1GpRb1GrDv#sCrXDj`hXOorq0lH6!2 zG6A+nEy3xr%;5F-W2gqX2K`Qqf)*u=fs#axe?lTFa1luaro|6IjyM8UBd3C{*nOBB zZ55dvxf;bs*T3aw%&7xq$VBG?n?9 zG=k0`f1);`B%unj|q?2+IiICkf6}eHg zDbXHY12&Cd@CPHG09SZ5W?lFkWykh`x@& zk$UuVZHlys&5E>(J&x>0DYU})?l=;gAKMb|8{3OCi`OFI*c!wdd5ZLeHy~|8LlIhd zNFotAp4b|BoDfI4Bo~KUVj6}m$^XLYgfIL%;SFad(!)S9fi7Do?2f8db+c<_b4dEl9^hi{;#k=O0K;U&8C-YVB}uf!ekm|R^w*IgEu#7%Ma z^(ftwd_K3%zY#rYc;gux-01rYZw+9h^8+1YQ=#dJFHonXF8BiDfU`j$`izv9a8fq| zN9lMxiTN0O$x;wEaQ2b~yalukylKn{{IjeRddaXDH;62l-|4o*+TA2=@L#m zX(o3EN{QbrD&^c45;-ux8EXu;CwdYVrKHp9Nda`X`x|G#y-!@g%tD!m9Z`PBmQAkC2ENTxf6uf=bJn<~?z8c9b%k+DRXZbGIY&RUs)Zh^tkK@9 z%+fBd+^ic?u|zkavYWo9YMpUO?R`tNdakXm{;hqep@ZwU(e2WjYCQ&PFG%2&g}H7h z`p*L+R=+v9Hk=2{NX){`!G#GzatG>3>OR_4#$Co~{z&dX=^(+Elnx?+YOfeQb&#FQ zsFCMq?nyCZUQw*ioTr?WIbT(kK~YW1?4`=jtW;K~|4f;X)>Zy2WvFziWW0nfAPLrU z=CN$dr?kJ+v!rK~%fwQW5nqSnCXOa+;8igp)HgiKza+@>ZV0q-hrR8c8(pJptL!X` z$J)x=&5D?;rZJ}PdV;y3=A5x(O;??#>VrmC^;(@%v#q{uO}TDl%{t?xnwh3Z^#Ef{ zO&epgI=X41X1-OXg`5rb9PcKR)pypm0Ajn|go?fA!*_xk;~F?CQ5#9c+)vnXTX5|N z-6$_9MXU)-0dF$rq3{T=OqwSUsQQY7>9=K#GmBD2W=>HMv(Bm<*>}zhRk;)j8_Sz1eM5-?FLF{uz^n z#mWpGMb?BB5N1=Sb92Cs3}AF=>8R@2me(IbIvB@M;@X zy0tGV$vRQZWqocP+i*@jNzbp>>4xY|>OL6|t;I~!=i09sUbtJB>jN|GKAP|S z73t+`fb0k^1r)JGxQax3upiKlgawaK`jPqQ6zn9UA@c)c8FMddI_m<;zH|vv1*0T& z!VA)O!fVon!tGKs-zf$8-DKDKr=&~x3#AkJqa_o0&xL0>FS+Yk7(1GXT-+@no^*DE8ViK3sByJ*`(Ejw1_=SirK0Wd|Mvl^B?csvR1c(uSAAlkw z{oZJEUsklgZ%}kT`jYxJ;eq~faG4JS6VU5N1yw})Ko#q05^IG31;JS5#s1Sn5|S{e`hMw?B5=v|2dbWM(O zo6^_t`Rr$c=iJ`n9Q3nw6daLv6K+X)FSIIJioPfeB8swHqE&$MHYsQ1XXStlkdejD zM7xAOUO7*|`Ndw%+`@W-x+R`b__S~2Zzzox53a_QU{7O8FvZBrcxF@!XTw2%G*IMO z;9ZSQ>~fvAZ2#GMTQ6G+EJG|qjRQ?V-Be>AZHcjCz0EvEv)(#Y?YDHOlUp>ks9UT~ zjWQ7!2Bt=fGO72XDeShorMe;dW`^^IdQ$__D684j!~WRfaA54)-4zb0_m%UsZ+xEo`at`d%?M!0&qU3J@|oLjW1y(@h9jPz*p4n#Lv`^|>jK*$Zue1wbrhAhVYM3&%>BY1E-vJu22N*D@U8TxBm6QZIMY zJNF}Z3(syJ)&~W8`#V4wXjJf0C<%+hk0L;HLwrTdh^XQX5p{evLPXjlhQuXg0Z^Kp zfpuc0;^qOzz!F>rsfN&;%APcD;USv9~eTegWiPKl<|+hkv?58pE^r$ld@ZI zfV@F?fOJb}BQ_LvB1Q$N#EybuLPtIue8s(igISG%Y4o#5Bifeu3W`74h_De}hFu+8 zlz{xZqYr%n)D%?(HTT8>%RR&V{k?B|P5fJZ>->Lwt$Z{674FV{i%aF-#Y20>6 ze?hTyu4uh%z4WboxO`yB4tc4fR4!50DvXMLsXoQZv_nd5IxV$%Mrvw$##5Cl{ju_% zijqQBNF_6*bv%`54(mD3PAy~xz;nb=7zJ=MzAt_*oF0h;nxVe?R-s4UeE%lbOn2D6 z&)(en#jG;DHYoID{ZH*K?O>E!az?kXZYJ6X!fHpU!FZE9}$YOk={-1}Th`~;sMqz(Q? z_wK&fyJP@Uj3*L9AdkGA$e_%jJ!ZUOr}7>NpNqz$?aLdgpxl?cMCTTdqceNC#YF?2DCdoKA;Yb_FW6U z^fV2DnI5ZPED*P_kGp$u`MMdXKUKcJaUe$0kJ{jMy(596^(8BuV{PAWsMKF z{o16yJ+T?F!-ZyvcB%h+-)3#I%ocr$b~c^d0M}?o*01c$wBf1C6|ba+#8dbtF2q>K zc#TdJ&r%*!SK+r1Rwj$EEuwg2T<`$A(BCQuxtl^-J0&p2)YIERt9J%9h-0*Rx8qfH zz4dhEX5;IMsoEnIc1^f~u1Qv{SMRB%YUbCGG(FUadbzqxy+_?va~XYKtZu39r8%OR zRewc0O#45>Ani3%ny%h5#b~i-SR1%E*;ji@?A6}!_SU|`cE6u)9~CUMcM6xfGGcGM zcjK=DCS(iT1JfWT#obLn;Em*A!Z^%b!bbdL(sNRGay|8bltBy}bvS!6t(afLJR{o2 z+APgyOBGDsQPq55rK+LmkLr!Eg~}`#sl*7V%D+5~qJk?_r0{3T)(FZa4AB?SGVveY zI&nYlZ1Fx0Ni1MZ64lYV2omJJ-1o$`>_LPx%$>MQx-q$!vLI1J%0>RevykuDW$`tb zoXDnFb*K;g96|yKf7nmCrjQd21d0d=-xE{=Lq2fjcfKROUugZJzb< zO0PUw7-$4^2pO=J$Yt>6o5a+ER_vz2-ZctahItD*KIw594v_o%ZeIkeTZMbvvt zGR?`EO3xOIVO$a3WqcKmXY@hyo}g$Uqmy7IV;0Xv`;T*r@`;J&0I1h-oxtkkx8&DY zZu~{KW269%!6cXg?+EP)^bh>>Uh;nRtoDs}i2@hwy#vE61wM!IzQ<(D@%}Jg@q9Nu z_8c>_yvxnIysa%9|2j)w|99(G-v?*ZcgR~1*z3Cr4fk&j&4XBIn&L-vWUw-NIJhJt z4*d%s3}!{vqHXW9ft!(4sDtmAZwX96J>nC+^6=uog2+W^cJvb@k537$PJD*nVVZ@% zWAh^x{Df#e!4xkbuz>r-4!E1tUpOb@Fg}O*6uihVla?{&&>zqPtY7q1+_8)j{zyiq zuoH8HXd$y&{DNL6okH6#X+u3JK1LCX_fQ-H9;G9%A>}&zHrdDMLfT3DMrcGG4yq`X z_(sIRcst&YyN|zv`-mFeZ{t%iacl{~0bKD$m?MZLu_OKlNsH}@ABnz%O8Y9)RcpOrwWXzTwxxsVx7lj)nMRvln4*T0rc;Jf#(#!7Bh{2*&a~joZVSOe zwZYZ}Xt&m2pW@u$bh^<|2HHoS75EeA7kUzG9xV=^PppY9!7h!G2&ZEn((uGuGBxQZ zAHuYuzQw`RVsH|z8?h-3A!VbV=oM=QEy)5H0`^Q6#A0#`?1S8O92)NlyPQkl#Cen0 zTAmZ#i|W`pqD7pW;9VQK_`bas-O%jT{ zLb6EmM><-tLZspzS>9R)TllsD%M44dxej%qpVhrK9@Br(uhwg{Gj*%$S8CJi z&(&|&AnGBS0qRUmA9anog}Sk3r24sLp!#8b1I=8W3cW*#^gd%_(;Ukr+bY`=M{h@O z_fzLH-~U|I{I04|gE7U@Ij6wf5Bj;7$sAW|~nL8AGt z7eTz^qKAVftI4J+OJie^v9WP-y}IrB^`^Fpbf&HeKzq@5qfa7pM%UlGsO*kmB+CL($3XV>Ug(Hc(u~f`xpcwlO z{Dc3GQb|(M(kKr4Hri57HoKO$fb&JLjI&3y5>y=$3b;=jg%gTJ|?3BO4>1ZC(DE0{~qQ=6RLL&b$FT%RR zIZjVSxi)X87x4j-J$ZwW6>pEb8&1WP1n(oI{%B;D`&am@Qx@W*_N#to#M8;R*||uc zYul-VO(Jb`{VR=6U0Wxu)z_G+SJcd^&a5q~+EI6}+p z>ySxcqi758DO`ph2LHodhYPWX;V|Y~NSz!P+MbjIGm^6c%tWz29~pwdmhE_)DXI0^7}pJ5&e1bOZM+bRBmv^Z~yww1GGZ#*y+OsHY~nhA=MnlF$O_ zL|U4>PMMCap-upg(z+4S=ue1x`Uu)2))-b-P7QZCPb4@aSR<5(bfRlwh48Sr3BOjf zjyF&!N9jcEcv>_`sOR!nt=MF`g+Zcypp{Y{kav^h#FOASP=wXvPA28pv&j2oGsKq2 zkK>Wykqgmz@c&}>{8;u5VvDj_AJ?;RPAt6G? zCf9)Y*5Tk4tR z`s0S2eNo2Y29#lO%Ol@-f22F>ThwjH(McO4AAtHbP6yaxKAAvV;$BA#dir0h~nbI(zt9|N{Ql=qObD3;=HPp;&b|%lxbPclz3KqB|H0;@*Dd8 zWF}o&mXRaM$Y?3NmEKgaCZmLZG=s&@PhZXbtIFd9Q$WsB*%sDeafteizn8?~j3Dl! zSArc${qQ?+mDuILH|*%dX!LZNfs_ZAMQ-{sLMCVDz#UuC8!~TpKQb(Ge$kfOGwLVV zNcE6)Q9Z#nUqiN>Rc|n2)xY(RYwzmr)yj2O)E#v8`U!f0j%GNc=NMZV+L;a+axG^~ zmG%@gPolS*d^cTr!8-53aChiTY<+kw=5_2Y9*-KLiwU2p-AV0fJt#-%#f%f|L2QIO zihn`yOSD*|m3|jjr;Lz&RGdngrQj(BDJJ$BA=&wFdzPz?wg{tO3#oy}PmG(Mm)gW!}>OuOPx~67t?JJ94 zeck>^XLDaP`}{XuC&HKgOB1_8H*q6kWRe2NrS~UD*lS~n{79}`X9*32}ZotfAY-O~ge4yJ2 zBd8s47*a6l!$snZipYg%7%SMAW{v@tMJu$!_76*ptz#xGM-7lq45|UojuR68sy2 znmC4Vih7ICm-&DcVNatzMCS!0{xcR;_>HqdY~YqkFY&KR#|Z{XLjsBPqfn3T9oI^G zh}%kO;%do$!4NTpw?=q_`;V_-(|J$mop?Dk8TUHnH}ejuH5EtbO=tmp$K@g(pgN*W z3=4IN%?fl2rFyHqk2I6K`v-ZsruY;SHqYM){0VaJ-~R+=f_G~GDEn5S=N zJXL?p*jp1b?$8W1<<%cBwb168Q*@^-8Tw+o!C-f)O^00jZTsBqorm37?pdBO-X^|f zzIq=J=n*Iil|Z`K>Ck^b%jiP_GZ7#+!%U>j!Omb_B@o#*N*=eENk(}`2YKf?{|TAG zP2!G{A7V&$Od?WrmiZOeWK?CgbfO|(I#h8`u2SYIILc9qJBqu?bR{X3q&$~qQ%*@g zrW};MCuK@T^OSDsm9oyMmC}5rT5>?%Ty$HOFYF^7$^R$%!!`&WGavFU(7&^FloAG> zoKG1HvWTyNGx+7nHW*oKK>`lni8J7t;W_^4kl$_bV4XV0dD~8F2kSPo+Va?V$TC>J z(b7$4wOrADvwYUhvsP&vTia{9TL#u|G>htM&1>r++Z)Xz$1!b|bFzlf&&A%&=hDA?1T!!TLag_>4EX#tbhi- z;y(*dh9V&(v@=u=PYAt$o5DN8lE{xpG8&DN5Ee2VX_?R>?UJ)GslZR*CD01j5ZjxO zh5Jh`#LH!x zuMydtS0o1uOI^%7M6od9SzJ-LV7RCB}N_P3F`or#hyj& z&u&9q%;`vN!lhDWyvNj=TmfwiCyP3o{hK_Gp(6dE4kPTMHUi&~n}f9k5y1wYC0Ota z2zXo@LM3pF@E%h@G$x-CBZ9)G?!CotAG8am|jL92a3g1!BMkT5VRv^#Ja{Z_w*&IFsn z?cwziOL$u>7TzBvMP^2n(PoiRQGSFQof_#LErUdY& z>MGp|&rr`g_i0aicZEmmHlSDGSoboY(RaX~0af{b2FF0*$h*+Qcuuq)>4(h4Tuz37 z_dqpPfGfpm@G|@o(qC`~1qM&j&VY}ZLBb5qA5vSv73w*03H?mUbylOaecTh7r}@fU zjOax!S*~*j4QGYszbsdvgzE;!Wk%ACQhEo#t{b4USp;Zm9Y>YgBQma2BHxR%DRHQ zeLb%nBT;LM*!;)PTsJ~@rfzY4MWwgy{a>j1)~`#I;qMnKCw@<_y8Vq?b?M93%J!e9 zR`&YRr^@)Pvc~trQpftWRXy>~d5!$vHPqqE*UhM%Xh>C$G_vba#-Sd~z!@jm@0ugd z4z>Z_?~dVtS)MOJO5jEKcxW+lH1Y&vj>a*2kh9pIz->^6&i>94_fU^fn=q!Z?5xqe zLHyprec~O`)6(b4sj~KIBW03|*0QEKx8*HSrtGe~Un+IptJF|VnQ}|EB_)~3mX)Rd z5Wh`*Alj&i@ntC?UO=kmR0!WN4xs6EGgD8jqx``RBRCN!5DZ^MrUohDAwFWDm20Sv z>uiaVVJVJZ#unB_`t7D3+O>vW^)2;Rv}7%y_i6qZ4rzLsLYhA2?D`kxKbp>#y!r*U zTblWfXx8V(hDazcn_X{e>IA~ZSxMh=8pASIFB zn0?3woE%6cruwy^acN#Yc*wqHBxl!+URe+y7k{O z&5yD|ZLhLQ?ZvWHy0Ws#`jvkr#`5x9%Z4h%zP09|dsE#Yzq#&vfLH$m+H33yx3X48 zFb)oK*3%s`Cs>NZ#)cDTWBOB;;D=Cu66d0PrC&EQ{Q6!5BPYVKreHaDAsVJpZF znC(bc7{3Xv=mJ7x8j(;+c>~TP$-puq5x;`y#Wp9r$F>BO*c|*eOmFnOY#COY=!C@~ zjd0qy5Wf&<4X#Qs2uE7R3pe50Pn!qcDVQ3y(*Vk!*CHxG5HijEX&p zfU%3=2eJF%+IS>-JB~>_h)u+ti?zlI5igFF#Gub7zrYVb5y6hr6N?CQsI$O9jOE}@ zR#%dr9j0{WoTF~!o}q5y1!!U3D*An1Z+cVSBKk9w{`HQ#k7naerz$vaC>gAUR67$# zbucPO6KH8fJmm`h1EC1r2ibv+xZOY|nlOBxOiB3S)>vBXe9Rr05!1k9qebw{NGbdX z{u%mTXm99Q@ImO<|2aC#@HEo44Kv6vxNCq2#H~r{+qdrS+iu-;>+Novx^3Ov-QC^2 zX_Cf0BoN$TfMEvs=J*bd{K*gg1Sa?M+}CxUzI=aIFEOygoLu3Pqe_fE$aFUvW_cicJLH^6lQXRv5Iuf2Oc+hK<9xc@tDLfDG*!Ffc( zgQKw>h!>j>+8C}1<%MtIl=4}T0a$jV45MR`NMq<@WXS{rq?R8r)>? z)QiEHUL*X|HxlP{r1`T#tNduF0&fRx4v7ik@Dboics=o0gh07VxIir+*3&HHTxJXS zH@g7p&il^TE_lr534d|B2mwJ~;R>NjR4j@SOT||40?~NsCUIDjBiSZ-A|5HL7VU~D z6!ufHMYB}`@k3ROI7jtWazZ&wmZ}^jAEG=h3n^bnYL)Sl%Zgu8pFCFXmQIze7H^f< zh2O>h6Fv|(3$}}P3N{M{@H+6`Gmo;nLMp}^3WK_zI0N_=xr61P`;apKX`G%|?R9!n z@buqM8))xpW?Myuh-s6Zz^kFAM^V} zZU4d>)w06V)ej0qHA{;|)jEo|)gLZd-ngP-fbMuT!BAL}Ymn6+F@V|vL%Hs%X|AcC zeWgw8>F?a)Tk7o)$PexadC+PMz_$%U2@+r&(L=O=J)m}sIM!#*DQ-x3Pxyy?n9Qz{ zEB{uj<7vr5H7iqQXyBCR>Y1r!YH!LIb$Uuu{IrzZxUaY~MwgVX=$rVTbb9HF^5G|!7@VWtG=*ieAN-yLP5QH~|%iZ^p{>~rpXU95ki=)9a1n&Xe zbX@eja$ImJ9J3sAY@=*^OR2?b9&Y-}oMWstdJT5N&6aog&2VR9w*IAYR*T-$*7(~z z$vD?+G0M&TEK|(|R@7v%dn`Gw_70Cb&9wn;^vw4sdXERbd)uKE{>kCD!EVuGp)G`o z;eUvuiDZf#Tn?d-fjNski?>R2M^qG3Egq$8kv>&L6t5Dl#|=t)uQ{CROg@+qpXSVZ znbs?NQ`(d)WyYAyty$6Zs!nO|r5lqJd?v0Z z>82P2D#WXR-GVHFin}$!gytXsa25Uw;ku*lv9=4=h_OZATDP(x(jcw*RkyWrY2Ej- ze`{}*g0-VcderEOoK*#d9jX==uB;6GimP1tYiz~pUnOPdehn=fQ#h=GUo@}ES7@tp z6n?ML6|t(X{=QMusq|ZIw0ut8)~dbrIW_f-+v_juIyWCS4KzHrPcb$+cbM&N+=317 zg|{GQk-{)7%pqL`5}_)Jn|_jZk~x&+Vo&5A;VK19+&O}A+$(~CyktqTV0(;J*h=*R z-^(4Ao=|U?B>Mwv8tq_szXUzRQHmqRG*Xjs7ezNWwQFW@@pJP z*+He2`mxPnA8VcNa9jI0L-s_M**U{?(zC|Z3m)PQ`a8Mh0h*^t^z3@9uKLmy?&>AV;&bDrGAsFu~z zweo#*Q1*@H5Q?Bl{MjIG$fC4i-6i7fjc3xe?D$dyMc`Ksgcd_HDr3=bLefbEjkWq36&rhLo(zf^4h;8 zpoQo8*T8?llRfjj5l1i2T6>eL8lPofIaWGj9q*jU_D!zd_HC}!_BqZEwp$LHb)Msr z)#Es3{m)fzCAv@9{H|s8VvoTw1OCq)2n@wd?^is%g6-kUNT$C$bUL8KsMx|N8r?y> zNopj&05hp)p~WMk+0lo(XwrBq&Jh z3U#G_f?m-U;@`?NCH5Wp_zo;k3uRsm40Hgqopcbdg z$bkpclZ0{5rDz}erWmX4w5Rv&7|Am z$)t%G2u#PuN5t4Ev?|mA>5WVb@PZz{+5aHGg;xZ#eFKnKUmEhw8x8v09fL+^a^R_b zu78DPHoVVx1OA|&>aS?->tEV*5ALVE1FvoD0q<`(>&t6U!-Pf$yr-!tVA4%S5)B_i zMaBu(HPcb_hGlms)w(7mvn@kYT^ZrozKzi)KMI@(B$GY{Bc#XZpOiDvQv5D7j5qK1 zF#e^^Wu2wgvi3kBRy%rEc31iymWTF`xso1Y%%nq%FHkc*kGhZX4)>hgq;6*H#ygxh zp@no58c*LzBhatV=Fr+hFfb2JP(Nm&IxAQN%^y^0LrM9$t6a&!XeAf_bzLbJo? z(61q9XeW{oT8p#_d2lP_VWc0j4(Sz~h%EMZ3p(JW02$r{zx3Am#(QVOpM2H+!~Xff zufd8?Ax4f4j#Lu@kxQgR!gA_Dpabm^C55GhvUz9dCk6MJH$+JsSYqP|<<$a#JXx%h zeU*e{`BIJS4_ShQA>S!-$*&1d$S3d<0^1ObeH0*Yw~U1&XozXsXT>@>%A=VI3wd?zy6JIs^m3*z_h1u(t@49xOxLRR8) z?fLL{?0Do<_GApUO#9yQ|`=XVo@T z{-`Ucbk>Ke@*98E3~5%>FEBXjDvip9IpzxOPiuoNU!E}Qjd3rsl~qM35VWIol-+>3sP-_&sT;VC^&{(BHP9Q+H6GD^Xew_;bjuAa!vqt{ z7;Bwkx#Rd?uk!43U-pyz)ZqTWUStw7KV%LC&~>O2cU*7)Mf3ux2k9q>fFUS{E}<(~ z8yHHT1Snqy+aX@I|ZST>vkyFJrjNI-VyHZn-Z;quK{s^SHyk@hb#>*rfiCS0%?S0 z5C*`|f8-~$k<<^gE6{cN7+M*lA0v~oo4JIM%yu&%ZW?nXN5$O3`j2slwVqkV-phW% zZe(v{O=53gYFWD&SC}Hk66Rt?f95%QAI4@XO81afF*XwCGub2uE1$fRJ(S{Rn#oh? zC`k>CBGRB!z+LDsAVN*V{}V69Tkwm4b>ta<1t=sOArOI&gzbb_!sFDBq60%cBcsr)unNc?0!t%4h1oP#9t}=P|~z z*RX%G3prmoeYk7+A9xwUVFJJKso;jFP@oYV6+ps>;3K{Vvj_%=<_T${bs~-ElxQzb ze!DBI7k%U%6clpr3Vv}G@Ee#J?31`xZ8&%g+Cw@3b^^RaLbNQx#5-W$a33TW84<$# ze}qJSZSc33;6Lkm;G2Wn!E+odUHcq^o%N13&J_-Z%VIBec5>czR=K9SnC?Q?V|*u3 z>z(4u2#g4H4Gj)4F>#oUwu%l4jlyqx9|Q5B)p+_ilkg(cf$$Jr0bIm(lCrQ;@)c|> zxD01Q=8(el7nGB%dg@!wVp=|*MvD>lgz|(AYNLKPSUi^reCuZX8YbNK4;0%5mS;6~PaF5fPe}J`^bBFn!L7+E+T+mINMSKyZL^onn z(RV=uepkVPcfe!4dhegkpRNX5YiB#_HAg42$9~v2!P?v+G9k@M!&Gf@%Zx_4erjWm z?oVxx=K0zyO{I-AU9h2({zAhS-PgvE&FM`sx~EN5dbH`8K1WAtX|0>0U#rvVyXobI z>-xUNJI1b-e=N5x0h`7;8n*{E;0@MHWJa(yT!VWhegRJMY!V$>O|da)44AuvFA?vP zh2{B5R_vL$P&}a4X#Pq3p13wym&{64rM6Cclvit;m*Sv$BR} z_0HIr(Lb#&`B1W2U8G*5+L~Zftco+pyC}Mfvt$PZGI2-l62So0S?)RdX_lB8rg2EC zsqLa3@(~ zU-F#t*&MsP`SyX{Qk&d;-&$|KYx!&)W4dYGZCq=Un90_gW`d=&`Gh5E%CiwIZS5DV z9qnp+wY}aJwNJO*!RhF^t{)Der`bjHo%75NKyZENY+y7t2RR;@g#8=s6TU$h6N!*c z6Kr5A=`!6&eat?@=)|AKZZ9mv+gwD6Ogc!qR{mOgI3`Y(ujnri#^$MX@rU9p347u< zCeDq2m)tI{b81y=zto|zYg5m}j!7M&%1W83$W897C`(F?S(fCH{+ak%vP^?W&L_-} ze2M)e?h@k>I7MGLZ+HV(a`tiBU1pHFpYEi@g0BfLfXr|N`v;jHOoOf7qfV~#sIkCu zx`|@i*f66dyVj)}Q9V@Ws65}es5G_i&*BF)jYah8A4R7s<`z9J^%agNSy|lp`}Xfo zze`K<4}0 zol{|j`;A`&3(@T0j<7s*HTn#50t4_Y<}=b3%2!YdeW2~2$FY{N-mrJF&vQO;h6(2J zw~9Lp`^Z#cP>~}UtSpl}Q?ccXV*4v5#0HeK*mxyPHA+57xm7k>RVv+}`XVk?Y!qcG z?hDSwY~;<4`I|?Hq4HYG_HloTJK^VmL#+G!6y`8q8@ho#ngX)^Aw}rvq-l^1@56>7 z8lnS(aK?NjGBLOfdk1ept9|W4mEJpki!2juuYMs5&hVrL_gPziP?I6hR3=aWwc26`L(+3uwQrTYs4 zx!;8Xu1=`K*&CxdW!Q691U>J$7`g7P2DZZ!$fpAHDOZC($cCVegu}mqHpo6g1gVHp z(ACkzP!^#VGBcVIdKlIsJy96vAQc6~a7MuC4f!AVUc+vm-KU3d`tJCnc*A7@Ou=bk zY4~>67KDR;qP>uBSWK`bT- z-K4+x2W2-!M`gc6V#y$3A0f(r$KSI_@9x%}owu4+rOYn!Ff@xijz zZnWk(X%2-);{F3RdCmpy`zN5E5E|heHjLtmj)f3H3N4;8gK4Lgavm~f2q?@9@o#2# ziI{Uix{RAE&*kOE^x}(Sz6++xhvE6)-GV8S4*Xd{Cr$zB#7*b7=f<-Kb28{1@D|k~ zY92U=6i@z(kPJ*B{6i2#2SzHfhv>}UQ$*lh9N6NF!UOF6y%|=en`7?k`ehpAnq_M1 zEHM?^kC`g$dyIFi7mdTsSB-Y#U#9WKj>aLzH7(nWf3&nWUpBOuJ{oHc-Hg7LQe%-} zq3OAaVYOKXJ zaSS{A2B$B-jWAzik)D-YR{-+3I6!$2w~QsL@5Sv)EKc~Ed?)dD%EqKs>7A3OWZF}= zWM^fDa!RtBa+l=j@;c`B$*ay8p3^(0L#yA}TQmQc;Y)2uu1pSVxEdPX?%1FV#SE0y zh`$QI@fqA-94=kP+Dp+eLGn)MYowg$2*B8CPg3BF{j>X>S!3JWl5Wgt4m59Qe4_nc zTh!RTy1cOi&RT0%zM@WCQeR^&{#<>uNL018P+ZxVmfw#b0a2{nl1Ej0DV>qR_q3pb9jigdUl!ip)m~oQ0*tvLroE3DsTgOll&Cf zNF0F*3CZZ8$SGuc^o6Fue&mYJjWFP!$SvGEk?#8qe{iqGz41M~TU|y^f^)Zbu$|+3ZRzP9 zXkO`8EJgH8H4{{P< zt;^sq_IwH&;OWQ_VPaXce#BK$|*N*GT*P2iA50XIqc z#59VE%mkJA-CC4XO`Sm@(7!|3J^E*J(*qFYX{QLVLhYR5qvrn{glSBx)b( zOK1heX8eEzEHV8E`!)R$JAvMYGm*ZCN4$aHH3T?p|4f%}jq$*k#VGVVCD+zPZ`=|Z%>GWVWGfGy`;%#)LjlyHiD!0~8;iB9Fy8diqE+`6Z?&7ojzz-3XQVS8y<4aIkyS7#I?n z6r79Q^s7T1y+eYC>yTgXB*H{jcWeCHBmCll&$#EoFc9os>}a@l<|xa$0fb{ZwLxJ?Tl>n8c&012x@Kza(r( zU63$5^=CXUIV-MB!ZOvr$|o^t@~x7v1QK)>E@F?wKc8;u7_yi2HhP{|6z&{ZhCB+a zh83O*ZjJ4V6*c4b!^^6Nh* zm5u#wFXepSSUUaN^-{^#9pzuXOs^RDRadd%+w97R--%U|3Q}w5|IXIxN>}Q3mH(p; zR?gN(YfrUQH1)7tG2C@7uvfzG-R}{qcUCCi=R}^PvBdMyJCvV9E`2qa%1WW-bFR^& z+if#$pWADWuh#wPQ z9KSq1KK@MHfOsZOF5VSGh@T=`uS%0YRbbLH8USTHj1)mZ^b92t5fC@~P0>T%bJzxVR`8N@yLYs`*m2vo**f0#(>%|*!hl${ zy6v_@`eC-PUTy2p(qx-p@Ywg7;_WXj4*N-bBH3=e?{HXa91Q0UhXQv<{SNl^|AnqX z4o0YG1rfwXQw$Nj!bO@%PoqYet!d}jg|rQ9DXSy5g=gpO5Q+FVB^`Kq^7*`}3V{$G znk6xDf5_g%m&)fS?2b{YGh-$sJ&6&f&Q^+2SI6e2#Klia9vZ(UDK0)c(HFZ+O;GJs zmB~FapX8^cLOfWQCk%6TaZ4E`%qdU}G@ByB4ICSYkI;C+7e9>6@_s><+K2l#n+>iy z;{;b{J<*<})#1I26k~nm%;s5mre^AIcg>5UvDM2916AQ)qpPwDA62$0YG3uZSX%X0 z2~@2snN@?8F07ebURJZQVnS_c#kD#@WxIwq)uBduU1k%fen9h|4f!oG+L@+(&1Wq) z^&I;;BgMtDAf7iiH@w)z$5W7}gS-9b&?89K$eoZYq6+H)E#VDrS1qO8q+e!bvDI7; zceX$$_$awA>JXD9ofNxEo}YlvBkJ|BPmh041DtngV@Y8mYM!EtT#FIBTYZ_N^@CDNBs9f^J3$4 z%O=xx>nig*tH{E!S6V+hHao)3KV5uxfg5(o@%JC)djpRRp7)25$9^t)G|)TDMlM9& zguX?eU^3$V$YxSe)J{1;@KJjJ)2S??20DP-wUp#-^u@$I%z3~R);8c7%RuPDmJ*1Z zGQvRiO`s!-46J5a2$hWYKpyh}@dooJiNw^AwX|oHvrsc-AY`Y^pw0w^loaYA;teX^ zX`yBUmDDfMZBR~>3#CO0!D-doGN) zXugnhl6SEq*^_TCbd9qgc64$;woT5P*2(VgR?sub+RL-h_Q>1TA%v&6cEI~R3ja8- zHqaeE)dK#*;p2EWDi9nW@gRZ7th6n#Ea8zs}BMDA?B+huN>03-KND49+{&VK&5$u*#WT+4~v&*lfmSRxNcb z>pWSD8*&hKHE^1n13cr82KEau!WTY|K<2jrig;&<3wbw4_c+7Jn^{B1o_)V{jh`FDs);iow zwjZwZ~BsaXZ&|P)F9wFkNoGo7%KG* zL%;jN*kqUy{RodE90;@l%*YVp^bnmiA1xr}WB((a2nWcgBMwS@lnYV`-N8Qr6Q!9* z03~D%wJqfr^%}m9O$R?fG;ky|5cGgI@Xysn+7F77J{NpIF9P|D!PN82G0;=iaA+-S z4D}eRD|I8M36%2+$+LI@5|O7RzTk$5iQFk911pvsW*#FAWHkW8SOb8^jC8tfa;0m1&JPExB z^3a3GZ+v3>E1Vd~CA^9rCh>@GC?%w?AV4mMR*)VsT1Xw(T_`KLJn9PGGU{ud39Jxg zK}<<++C7;A>ZiC$)ho5sj;af^I@KJyE_NtmLV}ywC2=5oQQ~#>b4@yDwT8jDpKywO zF1DUs5+h|_6VGSQOWQy^){=563042j53adZ_zCNc9g#E?}Qol zN^G=cH*(GpfLAxUU2_{I*-qDgwY1c1FukwB^hH&NbQKjb&8)J=T4I?>d$4SFW2iKt zAym4wo>acMVR}VbBe!z1_IJhh#s!r}8#vW-8m`s!X$aL0Y*03=YM?bSw4a+FYlV7d z^P`poz0Xjhx0(`6&#VpBzK(kPcbCqEJ4$>wdkl9p#i1F92;YNv37x`~q&5*TMNb$= zU4Y;8Orm{eePP|=+xd+WqGUzPvlyZ3cYI3x)1=hI#OfZL%x*!u#zf3KNouEZR131xiQxE&`7nX8+X~e8@t(SjWcZ$(*=tee+G}!4>zsQmm8XO#+F{XB`v?T znwFG?i7nanlMR>aiKhC7m*&-t6Rd|CZ`=NC9N>s)8sN&%xAxQ<*7!D<<$*u#Pl7!6 z9%Q1Y03GMw6&a7^Gmgw~=DaqUDOVmksbUAyh=nZR%U@?opH!-T&bLl+hJ?a74 zSyCr(B*6yE56=(B;=Gwp2pJv|xavOVyJ5#WceXX|i`F?#wOQ=wW-wZp=nBkBn@^cs zO$Q8=rn|c9jcLsfwa1(8HZN*gsso#T>(d))E!XNJI%DnA=FhmBWI(l~B~)EwB-FOI z^r^dO9p3QM=4`xU+uc-QJJ9mn5pVhG>SjOTIpqrby830o85j@!Ojs1@K^YAg=sI#G zn*(j)ex@k_Db8&A~ILSN?Nouc=q|%e$rd1`+OUp?4k~%ykCykyG zlU|!VE?u8QPlb~vC2vh>Na~n+Ch1f1>cndrPQp*zx>X*Zspt~FNd7a9BEO>QBKf6g zhj*dJ+V>;^-U{T%M$U7=UG=jrcykLu^b%$5=UxrWRBY}1%vz+8n0 ztc#GN*7iu$Dnp*z*})^O3V*s647`Td1nUC-AZLR=kw1dEP`@A>dxt#4eCXWp4lIh@ zLY3hXR2T`Ocf%*JO3Z`x#M)s-v`zSTXkp|pG!pq4?i;-lCPYc${?R$%)&woiMV%de z1rUG-!~&w8^p5<2Jb+S6SxeamZlSIR>+!p=DRde6481$f3>Zk>#au&9W6LSsI5x^i zZV~v9*MZ97U!gwZ{iJ>6-ex?)y-Qa(2ic!E1nv&bYOa;Fg5183@E*s z0lwGbZ}xP>H=Zxxeclv!llKI?)7$JH3HL+p1s0(aq#tGvE)7$H{iCab3xV&UPDCJl zfcR%*KZzNYQ+&}WR0ePysw7BhU4dL$HgP5`k2ID6gNZCAJ(KkZ{V!G>eI{GOe91n` zvaptMb}}Y#0a`xqDfJ;A12^;gQ@aUj8M5zlnZYoWOo3 zV+dXf#6d%M!7?N>FgDYbfR z)A;IFO^G%4nkBXGbU2IDb7|8O+bn|>?@~Ne>?{QZE+sd)Zo)_S1g{4 z5U$`8SSIZl1T(0t9QI3If38CKnYUi_m`{>c3ckrRgl}UIp;&QP+@OHP8f7=hDdlbP zZQM0=LB5I?E4|3-FHE6t;)cKhEED+$<1aF-rC0?x863)wJtM8 zZT*ca9V?7c2hEi1%ro_ILS~2aqon%tc`4;8~{>iW^x{@6}UiH0sSu~k+mpJ!;4Yh6Qrnb3t|$}L^qP& zN&`t@#e|fuaV4p?_`zu{n$cOiEN}|iFBg;r|^XMFgGCB!X7O+&uYi3VAL?pIQ{-S$qxL8_K#T6 zqak+C0{`t@?HT5}<7C^HIgXo$IC$pP)+5FYqo*ZFPtwzxA2tF_-nx>;E43L7k(&Lr zooZWGf2*BQ<*#j1Ra2W=l~A8kC2hD@wWYCd&1jvV?vx>+-e~IHFxa|YJH&oV*V_56 zrP$TpP~v`S`rAXaQ@tv8$XDP$7kr92uwDdLbPw?nP(T?%{*Pt@W0)UkAj`|JGLJE5 zvW~M#*-Kf=xPF#^C*=ltV%~dx4evAWEiV~=&F3_7KeNVjPqL=s=juV+_Vj5S0c{cM zZ|a|T3)4>9PFB+zh-r|Q5C<-eB$C%+kBHkbB5?_p0z}XqQ2{z5vMG2t{025*hrJKc zbWc(!-!(pX#ktNea!vI=b@Bt-o$dhUv<3}M3R3AT50YK}px8-71~|qA*W336KG}Br z@na`^&SCWLa`*#k7Z)k^5<-)M>d+M|9UBmJM~4Hah(_`%@=d50*p~Gff;kk%My`-) z<$q*<63pk;3a)W>i&4%+=|xUg**G>w?q~ItzvuidkLBmZz`VIJ&vtEJ(=5tmYa{-gdTukqR^X67T z8=!AsfI5Y8nfd@I17jn9Q;uVM$R6}8iGxC&G&Tj7P;!Yms}HkTb)I4S4Ys_#y%o&-x`6dEgyU&%OqbN&g0x}68m9YT_Q7tqiC+Yt>imhdce zjaZLurwk!h0lo$P!haA$6Pr}Oq|*30`TGQxB2En{ zLkUh*SIzu5RMRiMcVc`(=friITJ@@wYwGFgUiF)d1ogj}?bZLwe3$SkqdeZ4{xWV) z+Fj-Q)Fm;x6s|lw*(9z?6o?ijhDGIS9H*Ed6eFruLP*Zy9u(ha<#Rhi6^y|oh~WX; zU^!+W0zrDTvu`UBb_|3amO1Wo#)z%I{)LI5y`g_rOKcujJ*;V3RcYgpilVv+Wy`Ai zl%6Tg`+cuSR{XMHPhnia*n*6L6F++wTr7C^>ryeJXn(1uczM~2l1Rnt@bcEAtz9Q*{%rcAd4;`5f57vfahCt1DKjv^?nLIhpN2i&R{$OPLERqqvde%e zA}Mv8)Xs>Lm-D75Geotq&qXid2FT#}HcCT6ax6)GEqXBA$h*$QrcF{h4d$y z(zHhP&6EdmM{(}Jp``E1KANj?e_WC{PdS4hC#~c#`8e~B1=Hq1-NAD5H{uuq7G936 z3K6h&2oQYXKjysxC%L7*{tlY!wpC`kZ@z|~ts6{;USPc0tk;id{-R?xuh7kHs&DSo z#BcuGq-#3bw7=pE zx+2IYoegyd@1rnuE8?Wp0J+Qw)MU;ahK+Na@qt5OpW_+1GGUU?Dmo~=AswnvD3I7r zv11Zl@vl=f8bc~KiJJZ_=~#MQ(()`->a14PX*D_i^ozO93|?;QjGj5NjD4Bw)5RI| zGRtTVk@*7Ucu=iMTDARCPh(9W^T{px&EuA#qk(C}~dG+7wQDQOba{!AT=h z7HTdhc2Ohh12{12hw_DTg}jxLE|~wjCIkoUnCp|dHbtO2#wG&l& z@}eufRYaBl5oI3Y19zYophMBK^drR0%!lNkct*U4KZP+_5X&+O2XlGSRl)+fP*SBZ z$+jx5%U8#plPy)VWlD9j^riZ?(QK2A{LUKKrIiv*V# z7dU39oFNALPzy=L#Ph`J=y}|Uvk$i~RAYam?qCv97kCTz2;_Udz#SbH&v;ANwaQfH zdTN;J^66vU@AduN3A$d+Puf`yv@z%yRxfZ~t<80Qs9Ep4Uo*~iqi%@XUAM^ntYMgY zk5=rqXqUUq&6hpYmYF`l^u||hz3YxOCN}q?0rA|bjgD*l~NLTQrWf1KR;Fl?c_K_?CgOC~(0Xw5~^3CW&YTrl} zqi^IsYftzNdjY26{*Cc?qp_DfcKA5&S2&9&CCK=XNTUTiC~Dy;d{F%YJ`+?^+wu2M zo7gRoojHLvhUue+naf!$u8Z+O(1-p&c!wV34Pf--3}T+(ykx0(pIQG3o^wQ^b3CE& zHt(qb5zG@z63*&@`1uOYp{vA9gcbqkV|0jbg$YvZ7JfSZWoT4WSOf(%g8Jf)* zOL@ie5Z5zYQHbtAS3qUR6pAdk5t#0uhfVVgM)DoC!3Xw_zGREYb*ts7ZCZ1Md9YS# znxKugENQscLafU&j;y1YR@TFY;SFj-eB=LG;D+0}{qNDr+eg^_Ba0sT(=Gx~@)oy;dar4^D#u|84;u(yL>*m9bY zdyKhOK;--o_vO)~GjQ9gTW~_QLVQzkL*6xZf&z$}rOb{?Q7wu~Pkg-BL(ZQwRC+7<6hv<`P>wS2S?8qCIAG&|u`+no+Y43oq@7`K|c;8(t z#NKZym3@*`_4yjC>HDL1!^B^eO@m63O-U7RY~3qs>^*9JxYHU&`u8{A2<4f-N7LL% zAHBna(PmdrZSzB#?NwRcFx+AvpCz5 z_jjwoZO^vKX}>6EM#pnG6FZK`>GsE^+%Es?m-n#qi@fU2oV>~Z8=iCckDBbM9l(qM z?N_F3&&$y)%QnR>%knD|QzyxOCG-?+R<0HZW!pKI1PVHpRYM*}WfRT-E78{2Qh%Rd zXRpe?&hym0*Y&{G(Gg?0V{J5aF%Hu$(tXedwP-_L!`S-Kbz|zr*HP-u*4(UnSoK%^ zo~kQ#Z>kp6MQgs-9jr6gJL~0*rp6xHCt6i=QPX$bSY3*tyK$XSVd-j0weqc%jz;@D zPo^{ED{-9<=se8O6?ht^53!<8qYr?#6d&anokYhI_zWHQJqHwb6E;d&(oxD+ig$6| zSeja!@K95qa6Rct{FxL*Yv3x<&9r25Vef)rm z!&w`%n`V{_p}gc%i7}i(QG_)ta)7ow^qaCEkWY%jU!qi>29tV=0^i*;yenN}@qJQ1 zYhUMXliEJm*weOEudwAem0MGqu3LX-Y1YFH<>nuC&&`VZ-qu_7=WXc?ukBMBF57?C zpRw1~`|RlrooxDsd6ur)Miad`*1R0&!O(U1rBKUK8{H_ud!fIrsm=+utuBrY^!Bx9 z!Z)3n!4~%ebhme53)v&JW2=;Yhhn zrc};Ru8$oOw@@&drGMh{#`AA!V_GBs0o-&y5opL(b0*;ARQ@cm< z!4Nhb_q#3z=V5gq88`5?#r{Vw3{4}o4YdarBg^qTL;3Z42gSbi{@;gt#-ZB{&*P z3?3(q4+=<<;0a)C;8wId+%+8a^be6et&kYp2>!<1(Ld0w$MZWo+-IEAon2j*9Q)i? z?SQx3-pae%dB^k4*~fd+*~7Qm^%$ml(t;85YjETO-xBe@^ z%akABV8{)=r@y55=u5~aY3XD!{T=x@BbEG`nM6uv1&QsMxul(p$;7erfj|VB0Gxqd z07c*(;xByfH;5!9DF8L;adaecaP&K&OLSKxGrA=50Jp1sk9-7rN4Ejlgnw}_;S(Z} zdWm#~evaIQ{SRd}XE!y2pU;>mI>w<(?YtZXk@ry5g*!HGDAy4?m#0@g64WbVL{%}B zqME!#`rxYfAA?{0dJZJ;Jy-)*eZTU2Fx5xEu~!~S3x^SIts3m zC2bE+Ar^)|MmvOG;MvXZSOgWJgV6SfGB_mo+Sly2yPx_6?tm}N^&4l%uW+rf7u)5w z7Bgb`ZYVZiZ9z?s@&4Tq?OEfM`U?h89nr9xovr&) zy|`&U-r#Di8L8b+=WCuG7Y_LN#9l}VMTBXfE0JmRA3z5E5ZIcTMmxZ%pkL+v#n~iSD1<~> zv0n66JXD-0OsY;a zB#utLmiRWQP10n|jwEN?IL%Vk;kY5nEAq`T8nIQfo&QGgkUf!qgI&r#%?Q&X;8Hw` zei0ZEejdJ$@I$1)QanXk;(6p*=&W)&tw(JW%?`8Q)NIZ(4lzG$8E+cVGS2we@TcjZ zX^w5Qd4)6Adf9yfx8Qmm58$P)UIB{xSzw;`cyI%}1nC#Ji*!ONk-zc$W?J-EI1B_M z|3}d|M#-_YZMeIu8r8OYy2nN)I4#`9P=kxpzJr7ydN zTFRP3&tcQ)^Es*Xdz>VOjC+nz!YN{8^VZP){B6`EK`!+KFM;A?y(T*80{lkuSKLwJ zOzciVJ;*rTg=oiX0<&Or|a_>MUtou@E*!2dPYJ9s8OBGMX@7O%(T;aX#3a2A$^EP^o9 z^@K6>`Q&@-0W_QN2s1xnH&-gZ%v+nt6ho=KQ5xbdhjSPzW#N6ve~z!tf1p>)QQx8XBd04g-MY#vGBt4?&;_mEH7Ct`)k5o#d88N9le##)2LC^oGyGR-$F-_ZTG2e$6^j`mH?G-uRP>dErg`Gmn5 z|J>+~kS|V+&Bva>u7fT?*9Z|p9I+uOWCD3B`wc9OorZeqUhq97zTa(Pk;w_ehow@2KK_g+}bF$j(`C-FDZhI8DM zM~n&N5%m8MF{KAVi&p{%!*+0<@uEj#&BATNGlJ=XOMb{R(Tj1Ob`P~f?n3Jz*CkZt z{An@Rb1dtu)g~U=#Q4^-!WcnK#tqgC<0@2c*l1a3JZqU?I&NW^QOj*hJM=v2wQRHw zvyQahwl&(!_JK~3E5~hcTfA0JAkZ9i3F^FeBL{qI<4*!u+}g-mTsYPho{sHFgy9ar z^nH}tne-1MoBW=!o|44Opck=zFh;QAj41m7;|OmeZJ2;fqlsoPqoN$nc9E97M6{6k zL%f0UKvYZ*3mT~(_*v8l$4q|A96?+`?*+G{flfPlFVvBA2x=sB!i@$Nlg;>nkQ43& z_k^#&ui?&kAKV;lX|ebM{4GK~!Av?tSdM%nR3n{kdZR-5%DNs0XJFNUN|;knrM)`x#X;TzqDSyQnpqxT=qmUO!h(kPeNGcm5ZeF z6&=Mj37JB^Y@gt=Bum&@EES9uZsffZyyd><%Q#NX1E!uCr?sYLkp)Bo;WCtmGsG9i znuNQBp9IbUCP9Voq-Tonj2rQ_cW(B4w|8^hcbst~IF~uDJG(e^paHPYDYX|l8ID5t zV8FNo#TN$_F%y8J{jEQyBQoCoD#emP76|F{X+ZVIe?h@ zGaQd=qQBxJF>K5;V4co@Y1n1><J8)(xh zR?0?7HN_4R^PebdkO7qS#2}>^aW2(IC7z#cRT98C=;of!Q{*J!QE@k_0@ zo>FZys~Z=Y8XJ<$QyN;E-vJisg$Br2tW45RQ8w4DY^>H7sTUg3v_DPvbQLJ1r&v$w z2ctwo3W_y8Ld9m8ZMOBZ)8opND9^E^AFy#jvB$LK&nLH!S2NxF!tD1}`Je+@`U{>y?oW+^?^e@RpDg70>$-CuU6Rt_F3D)v9vga}G zQU68qL2mj3?jOwHSVLqv*wb`$YrR)(Ugs4w0vH|>>@&@)(QU@9rndU;`btf)?wfk5 z!Q43C$ZdG8->o!i|2F&we7^$i=EfJAZw-2NiSoXBmoit=wtk?xzCKCyq+wB`L)BkZ zrGBhxs@|%0s-J3W)N;LBy&rTT&zR3??wB{}Ct7Bhc3Qui^X(FJlXJTx!86o5+tV?$ z)Ymb(Ghm3e3HHTyh!oCT`NC>$QvsV>&PUic_#(!79-aD~ zm5m5#ID(4Q1N#=f6EVfb0l;$iya$GKiz{F)aBQ~zwq3PoEgLMY0d;er=`-kq&oBhE zIr2&R z(7nLz^7ipjLw|hDBRl<1VNm~@=v42au+IerJ2)D=AFM506w6Zw&HTnT#3ZsH#v!IKapBnq=_crv_a~e+@CO398zH3}<+|~HU@IhtNXK4o+Sh^_&vVN*TX&@N|7QWev z4o7QjJ8UzY=N;Lu2VkCNdB1?)$6?{>@Zl&PGXq10C*qidKG136dDu-p0(@a(iD#Ir z0o#m6W^uMsJ9C57&YZclEOtKaJ?j*80(&rZJcmT7XE{l1#u8#v>S*F1FbnrW9H8aW z7pEm&#H=FJ$5H5Y^a18>q#|}O@^55Q^h4-0pt8)5EsEv>cjd)cN%Ro-gpR1>CK_LilVb|O&A13^7JHBD z7u}2d9=VQ7jI6^>3s=YIhn~mI2R}w>p)z11Xcb=&u zu@l%&(HZe~;j~y@=s+M|%Z29zAO6XObJb8#d~W>6`2BX>Vyq zYX<8!XqxLw)CYAZ)f2TvYO0Q;Ni?j{9y2)fOyeix3!~fE)O5%^-x5QY+m!Y}4zg#1 zYir=4?^1Yg$Ql_Rofw-C?}g0(<&dVRj zUZosW%+9=*{3xe9tss9_=DUJUS&;%^)`0?M*3bOptWEjPGMD7F%N&xMm6ev=JM(Vp zos9cQ2Qu0xuS$QEOwO2*vNt^=#h9v1+?CQ*-Xl3fdM)vUFp_}d{}uOT>-jI~Y1|`} zi42GUQmwcg%0Wz&a4^yUvb)>jR*+g5?P%vlEhBBKjK$_mE!FT|$<%bH(Kj@!I#WNP z(o!V@{?cE?pGvm=PWwP5Yht`@i3%;Lhj3-|fZCN+y_Ir%DX=^A!>#cxvCZ=KvsSq7S_U}kj3z6he~UgCUK<)!6!u%4e0M>JrNzRV^$TSHb^FD;NBsl~Vs)DVF}x7RP@- zD(?Ds)!$ad`G5Nr^ZzPJzx{1q{;Akpv7qE{_0p0OaIksn=2s3@-mYHOumITEFE)16 zN!9uKiMlis#zeGsMtg%4jNjAUyESA8T#r8r&xV{aEnx|+1vLY&X3QaASo4t=+}YIg zqB435X%iMIYtF_f+HglCO&6fxMoXT0Cm}sepV%w)cZw*bD9x1IEYq3f&YGBbEHgoV zEzKd}q+S<&OujBCNS@2PmxSlEPB_KLlDwx~7OkOf=3z*7)&?kt@jq-o%CPuQx^4B<-fxY&B{F$~TUcXi4-C(I{LJamowl_kObKQ!dpgYIxILVvlFtQGFjc8;&aVG5jdY6IxYt;#I8N3zRu=OoYa+b`>oM&v<2AJ%Ba`}v#v>caDTJ3qBXj~j z3+%nIczQf1axSX&S4AFp2S)dLuSe?Ly&?_H8{wyp)}afw>47XO-M0xX@r<`rx%*hO z&hFOT_M!GawsH2aw$`?mwmrZHbipb?vutlI>+B_#VrOTI&`mI>xek~^jw{wj_TF}! zZIWY{o$GAp`UK`6uj9Bo%~{~dasKB)o%{R;+^a!1kP~|5dltPLaA5ibM`8V;We_e> zivJc76BA>4#24F7`Ct4Hxh3WuvJH0&@L^^|;M(hX; zC;Y}~@qn!eKfoc_8mtY2V0%MDa02{%Xe_=Ryb}K}p$Kk@c%dVdop2>}7`_?xHz7{; z6ZcTeL@6ze@QyYUoZ#-5Mrv35cSBaH){;w*b)pQURLHiP{eIr=>+53pgj)hW~kT9)RlMycDPIc0RI8K%RU z&8A1%y_Wqt2U=}RbM!<%x!+nJd+s|VfKc8kwA>$$Y>V{8zQa_*J8?4!IIv;=NjO5| zk=)FOnbA-EirVn?A1Rp&CxD5Zq_JF zGc*TG9ko@!iTBrJ(f_eDH?}|}mK)aXwvfHR-puK8^zfJ7`H!@($r+$) zbU*KCHo4$YPELU(XLbIl+!c9Ea<_v7hB@b3=GpAg8D}!%X)n?ODYKIvB;QMb69
    n0d<)lBFQsb|WC5+#DOS=BaEAjjhlt9Hdi?d2@ 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

    iD?HHCR%#PxP8e=YsZ^dns+>9R;p@;{g zHYPBlS`to2yAo34ekGSAj83B_>(k6h)6y_0Ny&3lJ|~<{J{JEm@omh=xc-q1u}8ww zqi==o4gbKBOIS2ln1IX^&n9?;1W3<+hkebt5}ZnJM*PGTo+=P@p8`j^wg4TRcaZD0 z6c5{ivHvhe8CrEbRe@@oyjlLNZIb+M%W=6|ny&aQ9VWlfII)e}D3G0L{M0(5VZ1D~ z@j@%EDWr9C%b!-HZMQs3eO=wb&`FOq)f?tp-kE1Q`Z$VRr(L^|aBmNW75ta3{ee;#yuZ;U(HiWzo5q7o@)dEWzl@$AzGi ztR%!1*zV5v_I6dc%A8sDNXKn+vgN4WZ&;-ou9wJyZ4HetTSFRqwLWe* z(adh_(WGl=*VwZWtWTF7sO2^-t4nO1S*L9!){SB-bIE3A)nE6@bb3J=A36-Y(8;;vvW5UluCS_FA4 zr-|klY~|p@FNHHDv&C7F%J9uG?8w$Qe4IJ)d2*MOs~JnumUX~pMs_l1p6CL#``UF{ zyJg)Uw@c`LJ^gl5{x~X=!+7>j*fVZQl8pNl0!(L z0q!LJPNt5#gfWu+jryJJpB&Y`sEcJgKNsj@X~n_HH&7B!h#PfPE}#!IKk&o%Z_ZfxAC?B2*! z&uzG-CO5oM8XNa0m$iIR?`^~BHYyX1KU5{=dQGKui~feAhauBB(6rCl86_&Ou*Q0_ zY}9oq;kpC6dy@Lo=8%Yy1~22EyM?;FGN3KB4IoJ4f35F|eFO42~!2D0)u-;L~Z80jm)1uknF47+K^w)Ps zGEIenFslWKaxTGM@K%BP;7OD>agk61KZU2#Z&M8H0@`qHJbevsD}91sG<|+(6C+wu z#`+x5pYtthIDbY|{}5)>7*Skwt~eMyP@EToj-I2FM1-iy-xYTyoeteim?t;~@_2E$Y23vam~$(*g(VMsqAv)r zXw?CLT0RZqIQ!WLTQes5a=zh!>q(w0I+ zr)G!TE6tYU8>h5Y*H4xu*FR}(s`a#dsEKX;Reh%osp_e)R(loxT3D4yC6G0AKHzp1bd-Vp8Xg(_!83#LNfu- zSd@OG0(6icD1a6MtvDXA13M`=39Ag$VG4r1F=;>#;0kOi?TEF+!oEh{}a*851g!{d{qq!GQp zI@k@sV4h(Vz!6M$Od^I4X)z$&6}Jn%0 zGi4uf7iBVW6=f!20OdG-DJ7o3q4XmP$yJ2qq#FD`cm_U!=*DaDTL>|PPQ=lKzrr z2Z+KL=H0HJ=6Io-ZY|Q~S$}DMSq`gCSOzG2S}T;Q4SOMy*KB+8VKtJ2`Rz z?_<<=0Wr2oWQv;;o|P~*ikT1|Gb?^;{OGt(32)-&#hs25#`t1Q5ijFBl7n$mC4{&U zVaKD-h@OR?7Lvv5Ih#Yf({n>Z$q#ujp26IOSxs4pEP^jPP~E9rf`4VfW0WQ<;xdXo zoed{kUOmk*%W&U{H-wu_x@Ln~SE#$IXK49`wd!s9R7I6`97^@3xAatPYg(hiN$;p$ zH1ttUZ&)c$lOAom-?C92M2%uE<(Cw0MUE0u>r|_?3$#tT>w1gilk^hFfLNJQ=Pw;>jE_ltI%bUO>2_(Gzp%8~Ce#e?FDrT(-xye=w73`nFbF81j zB1T7H2JI5>Av};pgf>yXvue3% zt#ZHqcH4ICcbQf-R94tl+g#i%Zipq$#A<*|@&udBc?2K*OxMzEWxZ zpT@<~!P4+1uu0OA)v~6IBIn6x$^WURsva4h>5S&Z<{5UsesxnNrHlW^E5uu#&}kZs&*{A&ThoFjY(<%8k1nSDdI$yqCgH%}=XMU!49gGe4c% zenk4D_O`Tyj&sxdbpDeX)3Gigrej>()($tLu~~&t!!ox zD=%uFX@aI+M!jQ}?W$MpJRX3(1F_e=i*TcS_Xux+O6oZ1BeM^@f!CG#OmvXZ5Z=iC z5w%ock3~Xv#ixcvCB6>(A4li>7)RT-;q}g}ZN+SwrfF*TscqZsQ`=0R+O}=GsgX41 zCY!a*S%33>KO}!ZvO9C%*L9x9AmIa)8qbnzP5k${;^kkCpgx5UpS9@S+295WA3fqDZbBs7PJ-; zg!_eYF?{R-st7R*zXkmhn2S|`4m^@{AH{m7che8f4xeaU~$e<^$^ zNEMO9lO+RXClU>bVDgTnqe%~v7A3t(`Y%xrd(|!`6iHr4vn6+d5NMU$?@S)c_>gvtE<%4Xwiel$65s!#KpfJ9hl#S{}t;Bd}??E0*K=!cTl7s9I)b`vD%yIlg-V$L?VVbyyxJGh7 z(nfYojF3SUA_v9k&j~WNuzJ%Yj4Z|s=4R#z=6Oap#xdGu+CCbM z`hw;m{h+5%_Au|$HZff=F=GvF38RdBoxvcEVzwo$Wwr-F<{rF?-T^zGz6W!ZnvWSs z-i>-fW})7af1yT^_~`jy0+NY46HURGLa&hXgDv9^0{R%*cR5z*=@4$~{sql-g#(*i zF5g&Z#8cv!*Ee1@A#&oy9FPi_eJL7ys-zMJyuM(faplshLY0WWB9bgcs?E87{O$~ zbUKd^vJswj-vdq%+XG*~2Y3f|E*|cm;-iQr?6_DjEH-=yCxY7GZ}_L- zHwW$kAE65bQ*a%@9;^XQLvQhXNQXy<>HuA|37?7_gCn7jV*RKUm~W^9s2F+{@-t=v zauvoImtnwI1S5#{!Inn`VAh3iqh1F~k-wnx$R5yZ#J><5!Gn_HGoW7aS%IS1T;I<~ z2VX7RG;IO@m_u~my5LtIFF3VJlD`q}!``pXiqA~s0B@TR3-KB4xm0sSzep0NUmGd_`Tst({cGf&8Hh%D3OW|s=du6 z8m0=+GqnB9=M3K*1mhX!FO$LD$@;^8&VDaA-CYnX_144xx8KOIk@u+2@i&-@nDM|2 z{31d&fFiabd?)5n&X7+qTGIBj2Qs4E8LYef5_V6%m5t*c;M@@SxdVlFxkCg7_6`06 zR!81_h8<>o(%EFn4cbP+cya_F6AJJ`d>B0tpNGoCdE)mllVWEuXQKB|H^Z-E1B0~i z)Iff4vcEkv)rW$6#Am#xe3h;Z-bB|%x4`|(RRP~5c%DzrxNCy5qXX?YXkX^Yb6jzr zwoh;zw>`AVt*K_F{gY{&qpP`(Bf*^IkeE+7E|@R5UR!>9H8vr1(p?-Z^C83ceeI&H z{N3VrpbscpbO`KqLSyHmF}M?$iGTw4m>>jSkU8Y7G&DoQ{J^=yql#~fY)QXlw2V2) zf3xPKbj)6trp*#$A+zSSXq8ox^QVP5CzeBMxj2W_@^6a^Epl5VwHVRjO?GxRDZ4iF zNcP;!#aZW5Dd|_?qfnwKLDG(!$DK&)ML!JW6U*b>usxwb?1;-9q*?n1pPC2wF6i~P zN6nXwOXPPnbL%E5v9-4vZ&uB!)s`tMs!D>TS;e>iPAc}7JTF$1D2fLb&-?@ZHvaPc zp89*nA8g6!Vs;r=vaRgDzx&Ew{4FXwRu-iws954EZk%iXARlJUmP5v04d=Cc z>p!dW8!}ZlJ&^mkI11Sq*EQJmbAP>^^b!I>aS2uqt~Hzdh2g7l{JoOCVx*4ip$istfa`6Jn4 z?k9RRV?N~!#Y5UlJ_xQL{=`lKaxgb>GSoZ_E#3p^33ZMzppF5wA95FY=Q%&RI@)I1 zew)IUv!+d^)dsxruU2O4tXZ!=tzzmXsNQNPtJ>*GRd2MzRZh(q3amao0dn%TRNi4Y;KU@qS0RZ=d(LRM)L6~`%*{K z&ZXbym^0T3Z)ImBT**`iJkrWY(|lj{%J<;AcCs;ThCyXqPPuDzm)~knM&34)#s{rH-C~OU|^w zMW@qmayb17jvj$0jza%Kd!g@$b-nkB^@7`Jed7FR9pzYMhi^-+3-%|j6}EuugY}{3 zvaQ^Qci=-a9ZW=L$3sl6VV4u095c7esg#OeopKxo{C%!xR9N|?Wk>F68a*3B{m=X3-=t&07}qdk`Vuu zwv_OVP9yGRa0s^;ZNMm92r}qapfzO-{tnTJeMr24T~Exx4kRqX9LIB!hcWbs8C3vT zkdytrk;VR*h%3IW@muc4u>;QP=wrvOsMe7i6*$X7Qytk*f7?6%D(g7!YWr&UCigq% z6+hoT59(&^6HKx!4V9Y3;VN@w7=X9yO_tjDB-<|(U}s`_I!>aRJqJ=e-fx6LTMN3-(`C78RakDCyP6x#xAZupNEg=@=(05Tb=j&m`Web)x)-X?+W*wuHDNVV z8Py=0iQ0oohISSFt&OPB+L^lcx-I&MUZtO8d~Y~o>IZWtPFr?ZOjeT>aJF}--8If6 zXuI1GHhQGdp*}nU@}EH?!zA1YL>t^A^l01yY=2xM_9M=NYmXlazQXk;@5iSw_5nB8 zVuFRciGbwyBkthWlb7(;Q6LUPLGVt{4)Zyzw}K#dimqTb?FN7n3fS?X{f;$cUnW;w_X=)^!d_E=vx`b|^snE#SY(FXl_-6aZ!~N31 z?vtLCE|e4PSZy6>e`9)YryKotu3@-+haP8JYkX{dZc4R|w1%t|_IEa?5(HGdD~n^jPn@s{tmF2h}`hHTTDdz#iX?$`EiXi#jaE2+<{*4BtBaw_+gb}R4s z_hYHGmZsgh+t-m6ol;wzi8Dd3pi~v zmI=>ggEDH%ZmGLkv9osOQ(DT}F3USu0JN#iU)`od>qtH&?^c`Mt=6?^mHRP&Mef7a zPg}}bkIy-ldn;>awmtPiMss2mcH$jPJ}P{dlq2v;=W@!06xL*}pRS|dBvq2pzX`L{dL8Ff|*s8`OW) z&lNjVcN#;}Y)=|VJCn*}JW3zQzLoKtcRcMXe{;%2LEGf_qL8d!vIc%v^Q2Q` zi=-KnPQtiw7kj<{Pn*VXAb#PA!1?S-%tq!zl%IYJNu`X9EhX^7vvI@x|DyIgyitd# zZRoW2y8pRyf@`8eW_?^g!E~|KZp^9Oq7POwHSP*ibH~c{O>L@b<%6qV)orNWRAZ>E zsd?1+qPAXjspht(bqz|hqIRBUR9%yzO*jspA4uY@THyuJp^x<%PRLp{!i9C$`hCL2+Abf<0Q`;!D^h&0M9T|sO z?arudq0jJVwaNf8wP|^&TT@@k#3`FZ$fQJm2kAl94|qo)fS<3O_{&LSIWeq_m5kiU zI2vhBD-PTswQ_d`PFsnXH^xjvopwN6tU<-rs5Q_BOpYO!hzp*c>%eTy{ zzp5W0KdSj%e_VC8&eVLgZh3Quy77vob(xJP>x~V9MtTFW`E3JNbyvP#UD=4$FK;H9 zXQ{fue;20bvqtM(ryb@mHLM9ythD$9*JC8YSAY)s4r0Ii$AjIWVj?EIkvt*(f&LKr zgE14FBPN&lOWdT8D%$7SzStH?LNwBI|y z_}v2;K;I6%(>GpU;;Yx)_oeD{{RZQlfW>+;kmY0r+|K=hd#)h#(0dyi1zm&&hPwoJ z#)1$DksMMX$l>4U){%ucYd8U)7XtCqLoI+Yq1lAt;bijra1j{`d&fS6m89R{Mdag= zGvw@eYsw_V2+BG5=O}7DG14^X(Wtgo*)RARB!;J9r%DYk^sA+&n~lZc7Hadg8f1SU4 z$*x(xXRf!NO4l4$qKoXJI}4mq$1=Oc`NXDh4!31HX|{ECi>;>(W;)r47Op*F9&Ddy zzUbgv{=lsOoI7Y5?RjlN`^TA|1Vk2h;ELrF^xN7ZG|{FHW!u_>PFh)^bek&J)_y)X z#r`Vjw6_e6bA1eRd>g_Y{G);#Xk&13$Pvm3=Y`KjKSqwmM6u!Vir4~F0ZNM%!wFGnqpzptBhVn6;c6?Dc|DPC)R8b6BvG*Ce3wb43EdanX1| zg`his4fhcL30uiiv)=NW8Jl=J8TFj^^qVY@reJobzGti_U!qPWlF9c8-v~bNEuj*y z5m3M|(2rr@79x+MI>vt>pG3oPS>#0YKBNp^_Vokf;im+#~ebIhA2i*_sj!q*KVIL4y z<5v<_fPJ&!&@1j1b8+3+!> zCrCj}3$8@ohXy0o_|syaJ&VGruFKF(#{yqpo4`HB+60?Eu9!aUuQas-3AQs--r6scWe` zDHmuo3WxrYN@~8R&9ySk6%M2QgD1ru^bhv_4A1g5MeqANA#9--=19C7Fa)h5JjP{F zGl=6@ZK(_SOPPBmZ@G694vNcCvJ>{DTN6)Y9!neCYGT%qHb--;1i;dFzurflg-Nmj!C z#Gy&tv?(duGtH^3vY6@bv(9DOvlN-rvnOS~$r_R_%5o(=g{Kdfk`{|=rFGn>=wJ30 zo|EpOwh;eXw&=0)T-|y(QQNt(m)a&DsJ2$7xv}33CFWTL0)4BELymmR4T5d}}kW-N2%}z<4ok5VIQ-AXd5-+ld zlIK*kU@^(T*5fOw4^bz<*OBvRm#;m-=9b0RyG{f%tdqRM^*tRglt$x`#u2*n_1{!~ zsznM()%J$uij>;U<=Uzx<-aRg<+&BC@{OfyO0Se;lp#tEl;1DeQ;sdktFRX*R&6Xf zP0SoaLNSg>}DAnZCbcmkECUQ0iI`hv5~7_%EUj? zE=eqDGZJ=Yd`zg#7@R;z|5sL+(kRVNHcIhHXQkD$a&fbGBYy(FC)dlNbH=d`vDVXb zS;NVrn4bu%8E#-A{VUE#9gO)y!6A!D3u6y~tD#Z2*8w%w?_Y^_daod_da7dIohQQw z?Ec^#d;5UUZuIKyLC-~p&VAIe+C_56T_fQ($rD#6c+bOg%=B!tay+T<4g0Zmly|U| z;+5)7j$p^tSEtO|u6BKKnpup>t<&lzVSf<+%%|*rn(f5C=0N zoQ~B*K4QN_E!aY2F5tt&!0&h(sT1)A^#^4rb1~D!-pS44l?eV73=w}9rb}RYg|w~M zoA6u2N!%?ANsbFvimW`Q@E=~1U?~s5Tgu7h^o6|?qbN)1&4eYio`frui=d477RUi_ z;!feVVVtO~s6KHn!W^bX=7#14mW1kj^MW*gKIHN(gKqlz1poLD!4AIFfu5ej0g3B< z;DW1tpxna?Z1HUH6}qRuS%F&5G?)td(z(;?b*%7Cbu_!z+djESRI~xueV)hCh z!#PlQ#hG9j=zL&cJ59zmjy%hF+c(Q&>vZcr>s#wn>rd-7+fh4Yedio#!MiHVpIo=B zQ(QOg2b_fty>mEh$y?;RblgQm^M~Oe_DWoLEPr^XPH1IO> z4nBc19zTn#!vE&v;e^~{xNF=_KpFQI(4Ti6AL59yr`fYmec4}-Gnp2oh4v8f2)=L6 zgdee+@I!+l+!iPemFV9V`Qpv;9dPZo2duBnQR8LfP5mSNYAr(Z2kvTpRxVL=R_;{1 zRc1GLRn;_mRZkT-&Hct78b;GY-LmGVhV!aAQ?d4tCC^lB-DqjFwX$7y%y9SgFrak- zL1ceq8Nz|sjA=&W@y{`QIOFk@I0a86F9ko7>H&y&5C5L-3P zV!!KNZdZEV*pGRK*<8LB_K<&>b0E~#^*p%HwIVbdHq8!&{VrR4<-x`NoUko0Hrx(+ z5j+~e2fzF01sz^p@QPOymiT4x^mJ9^L#P}+bF~lCqo~MrL_?$>>Otfj=6?7BMi4oG zEsQ<`r^S8|IEV#AA8IveD6W}w3OG-(fGg>ri8oki$_~zY__W`T_J@_k=nmV>=5yL{ zy77ARKM013T1#3>lM}xu%u3cJKSB9J`lQmcKf=UbSz1Yp!Ra$wqSDUgj!FL6 za(2Q$*{!5^(p!s1rS=y*O{Q}3Gw|o&kJ>+;@3;P_e_Z?1{nwk4FTYr2EsL2|1*P`dI=JyS zwq~~?u|BFCBG;;^%@d6i)EDfR^?dl8`y#sEz7(0{F2b68e1a@AhO#-fi8&0hlwE}E z%~9aG3uA<9l6$0w(o3|3(uth6lKz7J(!(N>tXwioW{@tE;9)O=TKZG0mOT>5k|^S~ zNykKQ5=M$z${zBmQZHwXWGXu(9?pCrzREZ)V$;V6-_!aDI?Y@T?j#}MSOa+Poz_DQE0e-cCZudgF5TNd6Hd~HoX0) zX{8xsxUWB}-K)E*>8`)4&e0EO_G-0_n>7;}3pMBDe(kZwCjFMCcKVA=OLYaR!NwAO zXZs|R$<@J(@YLHL2SE4v=n?-#^!n&cP>EtwhvM06KDoD0O|M9p%MPY)dm~-8O?c3S;1DFbKd2y&Z)=&TL3NiE$TBzw>XsgB)doQf-Fi>c1AZ@ zB&ATeBq6|-NZ`tqNK1Xg)sfc0rst33b*PI#zt}jeJDiHPg*HcD`VoP@F2McNw$Bza zelfFk$;RFqtCp+!p>`^i%IYSK0+Jh>Udm&QTN*U-&UM4&j@tTqaV@W|uBM>&W6dP^ zH@Z4lcdxQn!|AFfxuizh7^t>3>1+I|w{aK=$-xV){WOT)XHq{3P^U!aH0)$}a$R1Cr;nvuH}L zo5tdMnFL`W#~^yfTPGeMd>~#f8YS5w87#e*uqokg()Og?DKnChX;+h_>FVUN%#$g@ zvS+0lGI41qGmfW_(q|{7rC<_{BrFnd5!VV938wOP^Q$>~cq~pL`!Mq&{TZCd%cBMe z6#yCk9@P<380!}Xml`?vYFxtF*h`xobD+Y+bRlH*tcZ%eo6IF7i^FPwVE71tn#%F|?T^iFW*`h2besH108_=vYOy3scWA%lJ4 zhr`3suOdz8XVF3|DXzg?j6cM-M;*kx!Ct@|!y&PGxRKatxT{ze9)~@EXJWSFAEWyM zEinY}J(dpGFrDzH(TjjZI3t)vI01r040sD%0ebPfz>a_!>`6FJfCwnUIO1jS0P#3{ zQ~M0JN{v7#vKg2~UWe~ST!lXdsPTOK0-OuoAA1{FhH)ZBqtS6Ta(1*;tSmGlT$GqrV=VY+{;+1jC&9~z9s zp|hIN@TY6N@jvrHqrkkubl0>F#<$(Db})9e#thr6?~D=K8q-$?+0qs^e^&fAL(7PZF=eE| zSeW5~>&DRC}B5Kce96592izm zcSD1Ew_L2lHYp6}n`c=1tBH1vHo@7;xXOLlQs-6JH~1I2`UZx(`$8u@6`=xOEG+QD zJ}o~lKH6tO^!L9M_BCY9vfSuO$euZ^3e0KVTUy19u9$0!zg{!F@H>+;wJnFzlNzn;4uA>I!tGzA3G6o18axBck*};D?}c|dQm&ExtM3LNL!6v zgCB~S3T#L30=D9e_*CFA@Eur45D@)DFWE@aQ&*8E)7Db<(w9+}Fn&@6&GAx{g~_9-F<%BZW`fZuj%R8uwXfpXWEk^zVu+3fzvt1kiZb&_%R8JRjRWnu|Lb z#{(NteF<~Wtw}r4*<=xRF0BlZumRE`?sW1B-U?bTeoJ--!Cn4Nkxv|!+GNWT7bR{? z8kKxD=~(K?q_gQ@((TOm3B;^TvRRprrG`v_>_gTqSxM#@SSpSRFE@|D4-YNs3bAWYo{Ogwp; z<*IcJaejbzu3c;+Y&NUWvea5^`DPYdJ!XdGkmZzRuc@0k-H>e9tDm6%s?X40f?0SQ z^eQ9Xu*Q7TSZbMMd1f17@8+^NhroWTIZ%_&6sir-Vn?Bwab{>?{6XYj zuSHgZ6=(?VX6pz~u|n_wz6kJvX9-$DEwLqWDhVQ@sVB$>>7OVi^nH|X)YlXy`3Ge` zaXV!U$e;`bK+;>Fj<5+BMw|%rAWj2xgx$btFcl~Qmg7$mHsIQkYOsG`@8lY=4Q4#{ z6-t776a$f8A{*j8BPSvgp_)(=Om%+jYw!Q<%l01iZ*c$PU+iqW3Pf8mShk#wZUfRn6^`h0UFnFO+AS zOH^%@Y|S)HTsutfHQ-D+)&&-hbCq?dHx*{+Z1O;nlRi_lC=iTyiM+!eL(C>hu-~aC z3B#BmZ3AZ!Ydilf@3TlOwMfg8hb4DRJ)1T?wSD^EbZK@|W{2EaS>5tpX7|r8$#J(v z=2o;?+wx4y8!a3;f-GN)s*FY1x(sLL=X6P0Lh7@`A&CUZR!Iu~if{nCjJJz%n*EyM zVtfbt(wsOfc{1)SaVLHm=*P{$Ove00%tb9gBq8GQmGC+4NN`sG<6q`gc(Ob*U6@Qr&wyv(@HjFdf+RBc%oU5pyR43-!aixXuD*nFoW8U#!*U~@q9DY7*NdA zZ&g6*4UMB!uKIk{r8PbE{#(sfd8 zG$rfJa0h+Ady+#D80#TM{ek%?N0P2FrC9CTe65N}S{079Q@Eg#J4Hfu)Y)z9IGux6wMn&a^(T zm73!=pQWRHwk^?yv}TwOm^vAnjT;PFv&8hlPBi7XdFD^vA(nt2uvG=-+Xq88?JGi) zT~lLvPj&o;?_~UWAdYB+j-wBS^YEd#3cm!o6#p8D0;Xe{@K{_Sz6U-XdmR4&a|$;W zw+sIgI~}}?RuYp?tH?W09Et;dnBu{Wpu_+j`8r`NMNWB4$J5hUbjEMia+s3ez>9Gz zge>7+Nf*g<>1yd;S&{Ty;#z6{B$O;A<-4p;%KvED^AbNw7blg8!Q@xsxNMPVzE~i- zEF3Gm%q!(L!dbH4EIo$`&#vX*TUuw#RLb?(TSAY>V;mS1qobbunA6raI8C4A{i{^E zikhA{UdpGNgmrHqX0IhF1vOgY>yD?L`<>+g~}`QNp5Nu{MV8%jS`M@r>YnPp!p zZfwz#!9WOjk`qt!pg1?YAx8>?`b_ooif~-hrO! z@Q&t8;J*J*KncAKPKCBhJ(*y zcvrJsD)H4hDn`+6;Rzc3Ox@DGL{qGuzHeb zbN15va1XK0aq~F4xC8kzHX!=T>?YpIUMW4piAtw)J4pQ;m!v00DbcW+B)94JBr&R3 zilP1|{X*7B`Vz}Ua^RY<8h9?e2har5fiB#ucoK(z>%cyWbUi=Gvsp-MyT5Y^Dq_#x<2%nzxe9%x%k1I1zsf=}X5P#>Qf9vWW|5yN)4oXF)! z*HB_)Fr*2m2KGm0_{K(m!5NNgo;%_HJo?ZHPy6rz_b=G!ULEP~d>f`X*TOWaBY`XS z_x^9zQvVAJa=iO|dwu)- z{rtb6o&Lh$j=;qbG6Y4cL;Yh{LxI@9$np4q=sF}Sx*F9lx)wD%x(oFTrX-cdNGJ^K z`pibpMz6uKvG?#ju)Dw$*eXH|)<&3vZA)y6`a`%By$;60iNKM_C;Zz;6@F#}!uZ3Z zk<@TLq9}wyj0`u$r$k%EJI0U1dd1hp;<0|wD^YUvd*oweapX~`XZQqkC-}^V2$Xr& zdSAQGxNkTqo_&sgygwYXz3I-7cf6DCJLq)#-Z*ynS2&*fC9Xrz5m$@Q1LvI39OsDO zIM>+VEqDJ=98NOz@jr?t2TZZc0Vp;)l8LN9w!+-Tw8T~8lz1O}_78$H2=~bZ@>=Q~ z$~hW?zLkD~(U#uCTtk1&G0|7565A)?ac@Yr!kI-C?}vCP4<~!eyCLE6YeaMS z?}dB;pPwg0a|(q`41!=AZ9Bg!O)B)!dx^F%TZ=-h>%t1ocEMrpC;l?7h6l^$I9`^B zSwzpEZKiG`CK2hlDvTeIjCd4VADJ9;29`zvcTHr0qf4ZR9p>JeyZSyEK(|B(*|)2k zEVG-J8x}P#SC5t}VdBs~jlCLY)q{-%br|`ps*MeAE4$0TR3$exR8CZUsR}55*QPgr zYe-WTH?>f^luB)X%?!gzO)sNYbHy}SZ?L8qH#rK;V;rMx3dd&0E04}IHi(D*M!$rP zA)m$EI5%PfI2Sd5P=yPTUJ{C^Y;qy16a5Ew9NWVC#c3^Yu`2}m?BgOdhatJfxhm<- z{YR?jewRMtzLFi{ZcqHgZIgU}t4S>2^pg14eFXWOlRP^E$*G`;S;@2l<}P9p1prFG z0r-x%{%`npyUAk$GA!6EOKwClcG5dCF+}YdS=sE3r;}`f~%zJQ5 zxH|%Fu%T}ui?Nq5Ww^Q6#ds+&hY%nvBjd?yY0s#9<`?=yrh`Rb9pJWNU**NwD*h49 zN+HA>ECK|BMZE<7ORugG)ksE)4kWx0KTD!YCMCC$97swLw@7F!qRG|>2~rIl`f0;I zE>7Ww1oK%Ij*YRNF%CX07m>RVN04&yHwY)tV}TT;3Ue-kL0^S;Y%3vmw8=vZNgb#C z+bucnd~><&y!n`MpJA5x+6DMSTNSu+mAVCDTv4|;4 zWRc{~GJ29!`c&3Q{EyfnSjKzGo56g?xl7r^o=ZfrIlz9#e)K5%5M(2jgy>Ef9{q)F z4&OvR37(5~^>age+%x=l;aTQv>v8ueGsAh?RBoRE=aPF_9yx~Era1rDS6T7*qebHAZ*6dbHl^o?RqpQs`xOV+S;3x;0Z`0c zucmVizZBaVUMQRE)oMl~TE|q4G|td$ zFz?c0&BKjY%O3M?OFy{e)8EFkr`lIKPTBuBD9(wlX|64;IkQK9DzHpOM?+8>oK)JM9dpprsQJ zP?wPwQqGfXlz9|A^sMHzJpCKVK|lbCTiq`8dHE`aAx2s*$&ux{RYF11vdd z5N!yd0~rY<6MEwa`2NTZSY2c=HVC~yZ}&YxUi7X&T=rlhYuuxuDXz7?Z2LOb6Jvp6 zpZ2(|N%P(IP&d&&+j!r;-8|E=!SciY(=r%t-LYMX?m@0GUXf#ice9P7*3eTyiy?K)JwJ zLMOsIEd_%N6Q75$UQqm;jg%EUCUqq@nfie9h%yW2(;|6mD3xp`X(z)2e5NU|U&&GQ zO|TZ(5C0A^1hXFzj;6)>hGbDe@MNTw|3_$vZ%nY+J2N2lYWzREbD-nilHdoJLAu#H zBv|TM4~aZep+a|3@TTWzFx?M?dIoC42H&D6!S@t4G?c`$z58NjUjyvZd>=&xqtO}A z{3r)PN2fvSL(ia!fHt(xpAvcPUl5rYSQ*^{H&K6wOXEf85;(vqxgz~&OzP=8eyTbC$CNL^ZeRTPTO3h;J zDrK=c(p0EUYGSH>G@Ms-s%JL+s(mJhs(aK|R^?Yus~S|EUDFxn5UEPG)eb6oQ>Q7J zR^RkDui;Bso; zc#OM;*OK#-qhbcwZpurhgXE+uiE&tH)QZxLFp4-2ybIh17Gqz-GvtpLPuz{F4{t{9 z2vo&f?n{w}4pC&eeNA|cWoBrj>1yzikrYttH+U-axz10zmDV3>y7{S!XAG;|nqHc9 zs?X|cD!2N+5~=Q^WUHgfS?WcqD=LeMsG(}ahDz-VYgiw3%{SljwX=Ht_iW1oLtImW z-Mssw`=BkDOHnuR46+T~gqy}HB+chZ>D@&d)^SO94n}5#Py8?V7ZaWd3X(qYpC|t2 zO_rtdUWoqV9~Az}pC`D(ckxg1V}jG1kG#k1g`8F_9V<$c(IXTR^(Of@ECjMh$4FnOBN-sGnmdbignx*gBwWk0OF05#5?826nJjdr z{17}yo+q>>trnt_mWarS#liuym4dO-wZfy4siND$r;DNp6?v zySl{7 z9|JbX8X|^TNBV`jhruv8+%3WmEr_dwJyEJ49`g#S#B2*xU}yRl;=cH{;col1IG6V# zcB;1v#_4&E>EZ2;vH0#IC;?LJICMDrEUbwfi)Tdu2dqT5#fVUCkpmDPVzXk%=-}wJ@bw5U zKnOqd&I;K)SAs%U55L86$y4q~aISG+EMx5BVH1kg@WdrBPI9A-tvo}G^E|^%+dTtJ zFTB;p5&nIqgn-4oGceTp3{u)Q!O6Kpk=wp+QFVZapoTglUxZ7MU84ig7$g@p1yzUs zj-cRg#SQp}v3y`?tO09r^ZKy9i#%lbud%`y zZ_F}!8V!cS%{z=@^+)4wb*`a6r_+Z_`%ER)OJ<#YubJSsm{WXhtWaRHofE2cL_;a= znUOjEhp`jT2?RcL3;90!6?s1{M}9-xLWbZSs}Zvpw-tN}cBKv>f{fKfIsF=GE2EzF zn0=mok++rCQ!ts&7fu$g691N@%JwC6NN^@5BybbkNz-Ll#RH{pg+1XD_CJzl?i^7+ zZVLgIJD>NB-I_C!@gJ)-6=ZWL=a~ygo$2GjHk3p>nb-{`J)~li(8mxw#G&|@SZ?eB z#0`hNYlH2*v!NolFfiOX$lJ>f#{w+v9dz?#e9dpLfbco_AWIue`BcuuHcttEef{h=V+!`ROga#BU}_+zA7MDr5HN^=tt zskf8t*&CA2v_hoxZnG`DM*%r&V%zK49XkBViF9a{^P|IuoUQFkTmEc2t>x;rS8}f8 zd$NzWtjv^UrKH)@ccdIjQzidOB+J%{zVa_{ajbz%EcGzCijW3VSx%!~B2n>65pHNu zAlLWIUG3I77r1UXhS?9>PMU{X?;6T171~4-pjPV+D@arNt^1>VG$lH4>ZA=K0F=)>i7* z_NTf(N#CWc+MkDd8DGPU=YM zM~%@=(Qh(Wu;T10j*Z9UGKBm1BSa)&gJ`Dklc=3|n)se*rTD3Ew760j7H<&#Ct>or z;)z_A@GJ<{aYXpIL|V@xP@mHC(kvV75;v@X&-wiIcenbzvfCblub(%W>;I?%Gqe$KYg z9&%>GZ*Y{I2+ejr4fS*PjxF=vLPY!ov?G{{{S@}%Mn~Ebw#NP=RL74HMk8*M2BDg$ z5PB6O3HO|_9GuG%kg)8tlus-@J(-PU?PhIbonuz9QWy+&j4_zgj&+55jS=BUXy@67 zD0t3y5}!K_+{>l_`{V?=f1D2nA(%*LCg+JvXb}* zzZ!97T&Kk_D(Xn|R?0!tbg~L{oPR zU&kk-$HpL3MYJ?-j1pomV!I>VqVXUx%=Z5qJPi#Ftb-2t=RjlNF_6_W7#;(}q3b{{ z^asd+;_fxjB$or; z+r3h~?@Kf#GRQ)79eFZp1Vx=FBTr47B^^L@B!Z|L zgdJ!jej~<*FUF+d^DrAQN=z6%4O5Puj2VXdgD^E`qkBXSp;tumutgCWZbJANZe1vo zuq||kWR6^?l*XH>1ql|TCb58_LrtOYMpcIQhiQuEKB+xr3fDZ>$(Ahv=2<1%_V$WxUzr7#c zT(gnN^N~=&BnSeHPL!ub;|~I_3XxN)f=^Astvk1 zH5K~Xb-hiPrmI%1ZnlGEVgOzX)APc{_kMF@{dwS}&>QGTbbVkz;!&g)t%{qlG;}ro z5x$nxL>Z3ku?rY8>B)@EtT`-*HEloR`^(A>+R%UX)OqZrPP4{*6O-GvVG>ta^+KZ<9+P3Cp zx}bTGL22z~-e{NDsBV{S2Vk_n_SD*ao;=4HuhuDsTf2w)3;@Fa@?P+7fjof-+%@#u zFOGf)f$^SUZ3+hs!JWHh7z%Jg}?38YD)=ekVfR2;%pKuTuPpr;JQoDrW;R&d1Qo zL}je;(o6iy^4+4tifZva&2L#{N}BR)T86q!Mw?_@W^rnE_L1~wxx+G_wcL|QZaE{P zpyj}fLoKhQZ_XK!+|**XvRx)qMoC+SH0Cym3)RboJ{h0eUt(i!6c1v3Mp!d#c~KgP zvxd5a_L|t6h!{@M#hBvQ*2Ki{(D;(Trr1)*8M+K)`tqGyz(clDPnqR5Fw^*t)1jYk zf3Kt4dp8no*7~lt>ve6c1+{}M{;Cn?qN>@Z;Z;{mH!J&?a8*6be=28Nos}!?r>c56 zW>nWYp43cmZfUsheAl$uC2r!nA8I+C4Z5w~?)oIKuX!Fc%-+Yh6&M-V4(o!|$R~qM zoJ%OMN!V=S8(b$^5xzHb2mUu}Bw;`Q0;RtspMFr0%b2M7hq)`In9ay2<27XnMfcNR zOAe(*#L|?m;*Uwg#A8+CMD1l`gdx!r9!9vDa~g33|I2?y*K<$O$1&6BTd5!EWKx{A zi?E;8fGel;M@IJ>C9 zQ37G&)8U`dzkQXF$$n*|L!dgeBiJf%I`|HL6rjM_{uAJFU)+1vw-|f|{{>;;TBsJh zgxJJYpa?WVjh=n}N5Ja9H8&?z=o%e9>BNPvJLg5-IA%reI3?HPHG>G9x zAE3sEmd0=SGGjx1+ai7aq0ljSTi~?!U!MmkfNuj8;2S^zRs*NKZvn6OCo(b3bxXVf zcMO;VTywv0|8_S4)$S|c65t%P5V!};0WzUy02h4h{tLY8tOKczcOb_(1KjN5fh$~% zo?EV0z+b>mV7Mm)IDrCBM^6N4Jek~!yau<)gY}H_=6XJZ-8>HvXHgn*O>XdK`O9xj8V!tWvvB=Bh^#Lo27q}5Es zBEz|Z_#o~xhlww<{1O>wr}QWHz9fl1Qqo$qUV2j+kaU*+61S0W5+l|wVU2<-?5SKW z&QX%YEffcYqvex@6Qs`sCE_)L<>K4Chr-?5r-EGW|M(NwpE+k)?^tCl8}lGLlTpG! z(=$0N`a&j=vXr@uaEUz)XJl`{_T)Umo@a@$S#&q5n(`s~gj5u2jlbbLoY?5y9gevi z{-gGENMP&h##o-%Q6{r>yP>anQu8gnT>G*K)mYKEv!P34?}pa(U7BchUTtyhvZmE_ z%BH0HBTeHQChBH4Rq9im*su0#e6i#KQd*egQ!tdvH`@N#r%UD9Xg1 zOFSavUnm#;ZvZdI5AkF|l;EPsCFm~xDin$Li5Qa8LP%O5yd+H* z{7<6h^%H$&j}UZY;`t2PAnr}Fg?*F~WUQmiXZXlW#$6JZdJngY)CsG>*P|bxbn&;5 zP0>fe3&HFD<319?8tQ?#r@p$-U|-jAZ;GQ4Xm7voeqlT7?qxH$xYiWA!F0!5X9^mX zrhoLKjQ{Dn8mH=tjN_ZtMvK0U;e&x~qL^~5eDg^AG4nI~Y|BCCNXHAH%CiNW@B0}j z4dcW6qFuxNW9_1sP*-9PvD(CT;&kjZ%3%_drl3z{h*>>Z7kCG_x22=Rdz5kMWz_)L zfs`%kVVQxn*Dc&x6SG%mf6YFT-8W}c&hYHaTy{>^R(08;RtK};TtoJ@Txm8tXH52< z+(#{>ElF7yvlHp@j9A*ml>VtLROd7XaYRPv58=09fb3c_mN|*|hcp6B#~zLEk6n$B zLw$Y6;DH`7u-nfX0*wZ%V_b!~qRuCFNb zG=8c0+Pt=Ay>VgVM@y0Jy6v6*qoa-G8}J4>|Mdkw`on%zgo3d1#>P?Dk=Vz?*5na1 zGV=rDG^Z3fwbikHirw5U3cN_J87MuJ^h16<$)Gf+Y|;!)-<$eAvnjK4mN2J%R#9$F z7J#I#qgi({H5nb#d#0LFz9a1VktwTHI!#D!Q*M>^ll2#U;`J7InWOj@8LK%5C^OlO z#96E%cqi>0<}vwd!jFF+a-gfB{c$?*SLA?msqcaHg!izS>&Y{>a-KBGZ8J;;V>=_H z`(&uso;9kq$BZAE)|)psqO3pbYpm1j?pRLMoi%6I7n_(3Xd_xX-@LP#YYQ7cJHpm# z_epyOudXW4l8J}8^qD{S>yfpQ#=0j?Q8l6GWtjt)Z%E-8= z+?)19u_%eEC{ya?bonIdEAcGhTERRnm)n+^&M;BR$nA&=@b@wIF!`t*=;MhwvDPtW zs9hL>0k|b7^fV*B*?Q+HYk_5gF{;Pu2R6H#DY`z*KCM-U(r(h-toz`_AsR8QdRJUvRQt519S1zi@R=%v;Rw1qXtDfh2EgyX40y(~|}!xiz!Zr;?AVRLTES{HysY z$xnjC?KG1_v(&o;`I>;RYf_%@hvpXlpt_LPOJ(JLmLKPDlb+{q5?gzPHSG*$Ao)Ie1m1&U;|`!;)QE&QkrF=_+Y+S(bHZmqYha*T z>~C<~gJ0UP;BoUAS1)6ob&~$HdAWYS@w~oKzg90cEHRWd+YPUE$4q}|Us!rIp=^(} z-Ry03HOL%7XJ4iN>AY@k_WZJ@LK|!&;Em2jf%ZUJIM*wW^n)kF)&^Ijq){R^5#Nj7 zj4mVMaeq_a;xp)R!V~&^N;R{D9%621^kL0s8Cg%bXW0{Z-`O`fB@8atOEq#{kYgM_ zc?90I=-g5FM4n*w6UO^bkEW>SM^u&#&r{hAD zGAx&b!HRHos8c8&;(7TiW{hr*l!Z&f!j<2qrFpNpWx_ZT4C+4|7dK~t<)DZVRYLYj@Fy&`PG*jDAn4Ao;AZ7 zNHss}sWqAgX7!-PZq?_SSfEGsp z`UPAK?1EQ^ibI7lSM(?bj}nqfalfg_WQg98ew<}sHnV?mDtYgCaX|*$We)EI>AAH0wl;Ts?84-bpw zf|nvSp5LK0!1B;x_mWTxU}ETx>xVzrRShzMCqTZ(;Z}Oqxf_9)vlXz~QQ{tI3jj^l zLtc?h4Q{ku1UEYdA{mQY&<2+UUI*;(@Ah~DE4(YidUzl5OqU%Q5d9fm9xDouh;bq{ zND8hAwLQKS`w4vn$)1QPaZ)*5LK)3^P7QO~GQ07faZ~vl1qnV!bVYbkHcq-kg^@ee zkK`Sa)+*>pwTc=IP1!#6mWrK`nFOS5)~rZ1st2chQ(a8LDKP3G(liA_gppq5^F$Lk z&v@+_I~mW&pjanYDMNb6^BQO2>KwH=Z)1eoha?g9mIs08pm9Yd#msd51k%qy< z+Sd&Os%`bptG3tuTQ#KmzpBE@4pr3^b(OpRe6FY~`%&5dH@SBCpL-3Wa+mf-#ZCR0 z%4z25mBTGXReh|Ynw5@w_2b>?+Ns_jdJ(k1QUTv{u>E&{b-@>qDpnp$MOh+~(6rcH z^pZp_j)nyZ9{d2}0+JqILfe3=V(ST$guO_O(q3e)(nPzen#X8TH?rEMjO4#cyDVIl zNf58j&X6t5Em!T#^(Ifto{eaFa?(>WE~imbC#Hsz+ov2%KAF5RX=>75)i}*lIbV|@ zHLK8)E~?p*4#@d;s5Dtn#=pssVi@d?MBoUKExB zUjn-wMtG{V*o!tF0p1y>y2l%zIrREc+uzL>tlxBIOFLbEb35G*M7=G+Ao9xcZ7kW zuVI+!z32?q73x#=Ps(cU2}&MEMC-zeQBN{QQ3~nz5bv0X{5N$TaU8J__YZb4<`QNn z2HBHh|4ZmF>taJNO`$?`L!eV)kN-^ky)Q5R*q0u?4?hYTz?;5zo_Www_fGF8rwh2? z^t)&djT>XVe&aCa4#E2$1Fe<#L_$rJS`i5xWhw1#2v2C37x0ow0(HLtjGxX{opclmKQj=^a`|evLXnnxBvp z+_9(F{xK|eKpc%-9`BF+9373pM~5WlM;=AjN6I6P2qzMZTnUYjeh){YzamK7F;W+b(6zA=yz-w?=%wF&Hqe)R7NPxoC2D18G1hkfe;A>WpO*Wci;3XFm~hmzrA zkpNsA3B%)~bYExGPNWHSGSUqnM7_sfz+50uaYX8QGM;&cah0;z{oLhlBBUlAtr2YGrS;K|t zsho1^QdVoqd}e^ugc?P=^KySxMcgVWiX|)V>+%S!^Tbp}XecD3PGM&lPy7{1`SMv_bAidA}%P_zO z8W&q`n5LVprXd!Vd6#vGxzcvPa?h@|t#JI$&U6oPoCbC}#(KO?iq`=2^-91A;C^^3 zyg1M?&=5Qr*cTB7d&jSZmn9BHb|(mteTg}dLX0I^f-i}`Bb`SdqISf;V-BT`;J0UA z5clRTl`(_^<%^{q6efA8;*3(QoT}=kx}e^n9G}Ec9!e%C`zH@pgf!a~e05LdWaVka zDcOJWaY$M&;|=6p`C_|mX@m4a?f~h|`+x-Lb9O{HH5;H4AqnzLVus$M zpy&={-_Z_p6}=skh5Zk=2>+P$g}9PRrR=87r1F?^sSjB%scYDC==r=|$Q)}oD_OFf zQ!SBjCrQTg_DaIMm68TtTS=VvQ`AEs6^#^Th#bP(!jcTyuSL63kThTsj1%X32M)^h9O3NW_WS^&!1Xk8oNe|(2#aQWN)fo9< z%_>D&%3Iaz^tz;RSrbzhWi_Ns$>OAT$QqQ+$tuX&k~uBAciMyOGbuS)g{h;`-S_$oQeR|!V&&vPd8-Y_1qI#asPFA)|{k>nLgh`CSf7cawG z!;6s|Oc;v^v_K1eE0L?jjY!<{Cy)X>_8oA=p{N7reQJZ8It$SzvNT$Lnx|N@%)`uH zgUc{l|5G1k-R{W1Qs2aF5sdyFG&i;YU_e+IX?S^vdcWbm5F#*>za zQDj|bK{6C}iQ^BFpqvij0+0QY=#L;e{xCEd<%$?^*Aj~mi(5S|OcIis>3ir~Scj2h z^hrJup|juRZWihV1H{#$*9dc+CRrx!B<&$#%H9cY%W;C~auL@nnagGgvsp{o62?!u zhPsiKNj6j27eYWI;6OwpsUWxkEssl zC3YolEoKBZAJa9lHlc~2qppxSm=SXMultYr7x{+xZa^l`37+#1AgTL!%{*L}q^P%Q1x>?O#v}c^ zT8>j;Q;FROgD8mR3-P>?IpfI!?tF4O7e%%4X4BRPLNuA^DZNsh$G9r}$#@~-Gk!@X zGj0pJGmH5iMhQ2Cd5F80S;C#kB6H)+D0>8RG;1Ss5evnXvoaXpX%FacsjumIw9(Xc zM?2_3KtuLIv?qR z9vk|NEeYPm%?|BBp9tMZln1WH;;=6)fCa&2uo7MY-v;}@TCW(|50Jrf;5o$c4)9+< z*smj;sIbmif-uV}gUMb}(BeH8{0eQ49PnwQSACD8PXqb!E8**ix08gr5LAi2ok?Fz{Xu_9?ZH%2MeJXc>FgBoENyU8n{bz4J?XsAzb7X z*a^w_?(~m_c>d3D9GNS=3>X7X0v(X;;E(VGzcw-^a4T96%#E%M_lTW~QWA|w253N> z5}OwL6f?$5i8Uw`T7@l6;INFyH%yP99)k}ILrk8_5?Rp4=prvC0=fOc40n5fihBuc zbyb2~*BtK-_kV63@Wj>KbI@7nEwL8?f7!197pzs_bL$DDpJ(?$~77 zb3MN;a~SU~<6mwvYa}1dnkqmuN`(*TNy1D6a~z}0-ED)-Y{Wsm#`?Eup3QGuVV`Sm=e%p* z>pI|~yTdNDr>h5#%zQRMJK_JqR|0}S?+_+3C|Vw4BoO8whJ~9(d`1{TDI-XzVu~A~ zwfAGBFvrlJupcs)a^A3>^HNyD1qJMtg6`Y_0zYpn?|=O6$Q*ADXBM}F{egRk^@Tf! zj^%D6U1wz@{ks6>0fT`K(yu2D(GNs!Qf7rFlR5=w5Q==y(6jwZ(W8RZ7-4V~YQ6tV z?6OZ5-i(|p_Z<45bgZsC0Vz5f9k8eX5B*1XnI4Q6+APMWDHQ{-Q53vDQ z3d(|ifEq+h$0gGqlV-3=XdL8}@(#JDoE1yCkEGe6{))TuovP)kbxBXuE0T$t|B_lK z4@_xJJ)c^iUY<5Ovm~9E^*ud5`%}h>Y+~lA7W*>LEk31bvf$LkSuN96XKqe;o#s}5 zN*W+ruG}iLN)~e$2?w)@hXMJePsyq zf#t1p{pZ+ZUt;NM9cTD%1RKw5RrSpcSL-&^@2j3y-MuQQ(p#Bd{=H1mM#5x;K%D9-@fJiM1N2H`SJU~GU2ZqWy&&5x$F1zs%;fJYbVrlv`3myhBx}Q zmKy63$52wW@td z^W;ZVL#3696bV|gU!2RW6}Do0<4quM;^YtpGRI=s^c9$m3mcyuHk^*1p?u!hXW`#=gj6wXU}8vR0e* z)_LY7)-Fh%dah}=<+ml*Hq_>|?X!=zzd#aNfPIy7x$}p+w`a3Q56y@EfwBINzG0yc zfqT*Mp^}6ub_9I_TZ!FFjNopQWB6v~QtH1v9J?=n4nH69TXMyHrFywtFiM)_%8xWCc~$!0q}3U0bzNGqikUo5xm>wHmLdHt{zo)Lc#v1XV{&F9 z{Vy3SkD5iLkvT8dzUyIN<>Qh9|LcE#qc0#8`8m<<~ij#0>F-zjvJPN zrVqxfW?OTcriD#l{j>U!HG%s3l@sc*6$ffp{Mk~yqe5MErs_x4!K&+3(yHxMw#xg} zlIq@dH|pLt5Vf49&&@TuFQ!?>wvOJ`688#wz|C68>oVK^~jAfwzV8o}b6xD{=`}N|I%lr51U6`B$Y? zNmduC!>WfWlX|fFuI7t+WpWEOGj*U6NVzAgNggNlC5@J@(r~4j>Yd`Sx=d89njlgs zPYWk2_`)RVHQpEDPWCN+E2f#dnAVf?7s<)khTTQkfr{btV=GZ@gC(&v|M=)&A1*l6 zn-2eW@A2fjrXgoBy7RQ9k9DQ#jQ+4ut37RWXt~D9rVaYvjXj!+8;rUawJUXm+HuXJ zY98yy)cj-6)SC@K?LTI*;k-THaL=JK`JL~~E!;TUbb#%?4HtWFhFZe2A`^n$Bjk9? z$Tr-&Xag}6KTG0cI#Zt!deS?PL-h6tPyQDp!H;sri_h>zNs|P*(xXD0w6mnEc%rPo z@P>StAfdnuhAP(!a+GEKrOG9Ig=#O-m7B^>QvBgLWN*0hB#*hbg`2rg_-i=DTt9Or z^D^B;d&+oA^&*W(37vpzL90jYq5O!ABo;;Uu@l2R(GL&$3P!wW7r$)6h+4g@l)t-=n1$FxaovGBonb8^)b0W zeHpzKlgwVqGIMg+V|aga3;ADo6L@mLVQy4-fjeHjoc~d>N%WuOhUkiO_1Id3+X$jW2=rfp-cC#SNii3?dD3A31w@sk)lf{wlu zf1EZIx0SXN$D}84A=)9tZM+y4BhSSf2}R_IxC4ycxLvH@`0lJv1R<-L_=hbd=kZpP zR`Ci59k~Z^8#uK%5_=V1&sdH>MPG{~Di#yY621}k;1&|zCwAjz#4_+V!)vi}|8vwm z@633m>tndu-XnP5qywv)CC-bwm)0q|8p|%7(9A^k1xvNZwK(J#ctrQ6u}(j!vCdf6 z7&f)l4m6)fDCDP`L+0MdW`Lr7?8wl*b_JW6o*Sl8FVXT1T4jCb8|~;7?Bkgj{>vAR z_=4x6+rwRAccaEcU(6R2fNw^Zk)C1BQ1_uh+Eau@^BeV^R)^`&U|`dkJ26z|8f-P= z9ey`mNA5<%Na@X)Knrp@(OPm>P`b0NBo1>Lc{k%W#Y}rZZXy#&8}XI+^=LbGSzLx~6P}E& z_b*7ud^cl#p+F=DoDkUs@*?-4+2LDoB(&dW3L&lZaPPpOkUX$A;PJQgVG*LM6h05l z0&~2tz1skdm+XlG5l=4Cb76XJ!-a@lZX38el-)VCe`cc33}P%wpZ zEJUFli49?lz_esr3HNyqsD&aHYnW8c@1?jd-l^;?PgAPZ*^2*?x628sO!?qcv#eD* zPVqSXU-_N%W3ouvS4r2DF`}88JYlNxs^FP)tFTnCQ_z>2!6UL$Ia!o$^i*6WiGa$* zpA6qey@uaKzo5{tE{bz#M&35UMD{2&i8d;>0*4AgsD**g@nilo z;hpflz+mvJPvTh!!pME700=lv0Sw!1_h<8Z*GH4udDS%3+11qStTjG#7Ms#tgN=ZF zoAH%(gsBFZJPNIN>rwk#Yj0fw1MjAMB5#4bEq9QD&(_Lb&`ya55IghB(ID$dTtxpnc98NUvWwg)+znsri=(D` zKgTWs>JScS=bzx#!%v;V;D4QNaER+ZaMh)A9&wAE``m{eQ(U`jCVNL~xwXdppS9kw z$x3RzZtkN^HxYG{O~rb>*{P3M%k&HFV-02YmBvEnV#_}F4SPG!2G;|~hgf)S`?iJB zLMI~oDCW3RAC#KG*Zq&!wKc_Zs9Ii1a? zs@V{&D>6+R%-&9)#m=M-n`&<^B>lAv+$_UDB39;BEgIQl}r<-h@Xh%;xl5sc)r9Xo+?2LQIb`>ZQ>Ajp=cd9BAmxr zA?V56!XH9M^Z%ua`NPTYxQ7XO%um?1)H&Esgt^ExqZu*q=EXe;Yw%p87Ul&@y~)rp z_jp%32h-}X78_SvpXqm5H|mniH=AsRdre%!-NvA9N#pe9VT~sZf(EG((=f*PqoKl- zr@d}FZzy#gwpu(t?Ki-gj>S-(E6X?6ec7)E?gg%Uw*+=Tu>X;-TTtQ84rlrQ7a8bJ zioEvaher4ffx&_I{xN|h|Bb*s|J~qve^%H7OCsN)GhrpdL!IO65-ad`insL>f(uLv(9l46rcj}p_!^_bq2Dfm~^sYnM3O&Lq0QSxa&ssB-< zbUcm8nnQca$z%ux6|8?mW$fAFL!7>nz1(u~Ro*SpZ@x>Y7M>S=5%v*MM1zEv#J`14 zrDFu&<>lOBd3V-X=|=kBlBM)wVF_(N|2K6M52CzhEh8tnXIK2DcVK)KygtSVsN)@jnTgS%*YSZ7Hj0~Af!>IgVrnsMakue2LLLc2 zT1eHC-ZQR~rz20wI?htYIc^bC$;)JX=X|Bl;IyN^<7}Zbxl5Qv&T`gv);qSGQOCYT zTgzNP`#>Ymo>Avf2$UW~KVFaY8pEj7m<@>+i8qlaabjqC>=wc_{s2aNe|e1HcXz7y zyt}~L%CjDr?NPh8csjW@d2Tu%dsexodbYUoU1lfB&T@p!zmTgj$+p(`)>5b+Y~7|i zZ0o4|3rQ30w7+P2W#8L)+E&`Q$qF~Evkq!LWj$d8Y%%LDN3)CVo)2FI(u2c2eZt>C zF?y758F5=EOc{ie6af%4%s&wfvU~+!}rNg<0Z@u7!3!+_hz3YhM0NeVpcYd z%K6UV@mjGVgu4Apw3=TjzA0cxY6VZldf{D(NIV$nZhw$9i7!jHNZ1mwbg1O6*evl1 zE{F<+9R(AFZ@HiN680S4WyUvlK9UoNk~PfRBrJ_am`<@_50mGj?;?{p4K_1!CEmfG z9F>7rLjfSomv9gBjC6fQT*P~v4Tx=itgF=e(j~H{y3_4?cRvTs)7st&+;5{oY=;(^ zRMDWF&N^tEs|o_$)sV$=2c8IeeO=)bfn~na0a~y{P!LWH4hr84e2Vl638I`(``AC> zai~^_t(Yj!sx#M=3H12{~eeYUgd8W7zk5fsrR!d37FtY zxVW}yj7l zM#GL^x8Q@w+sNJc*jNk9uGliXEAfbwj5$e}g)N}Hz>i|ABjzzTlh!c5lJ~I=Q`&H5 zQ%`eG(pGab87yRH4{%}jLhd^5G!Bd3olW46WWMI_U{VBHW|iPFbBcHzizh*|$BF)9 z_Y%D15co3gMy{AWi(St6&S+$urp;l7k-2#$r61!IaSh#y8%#B#-xA%Z9{3(8J(``6 zCycQh@nKOztVi@?EF&^7W)7r99{Hf40b1yfLFav5-!I=5e;rKsw}(6V#z3&I2HG#ArEu%K_^2V+X{EfA8@Sqz2H z4|fp9AmpN%J>f6N9-JIpnT7yXgK$F3xcaPvux7$r%Aeow4I zzs4`aw7@76b1?lQ>o5}o7g1Y${6rWoj6mKS!K2<$0Ug-hw*dh>I3cQMJy`4f2jtsl z!D8z+DB1GM^T_-R(3l1Rm4-ae1;bnKXG74t&GgHgY0dH0+e^LwbNvr8c&7Rmf?fRE zKnNi-`GQ@7jp0>cb?koF7Cqq z#iOwni5C$vYE5VfS{<}tjs{tnXmAjwCRBsn9odR&5u@QPu_8P!F%8E+IdE4|8qx{u zZt_yX9dZxiUz8a{28~Oa$6`=kas*T?cMbI{52mgYtfRdWvKiSTEc2o0C*z=a6GI?L zFp|X6nWKe;%wzmgMjme}{Ui50jnCdeImCoWD9$|cPVN!%Gu~M0M!t^zH~%=2t)?)tQfdkthnuX3EnQ(s=%1Se`aQ<| z%@&4v8=j9VMQ8vym|WUx zTs?gi=^|q&bv^SjZ7wUH{hc?4%Mjh+{v{s5Uw{y^{wMT{3J}Zu6aFOmcK%%DbU}ah z0`YLQTl!PQlGzp8#rg6z!gsPI!o%VY{BwdT&L2(})&^wru#B8cZcXrG|HAJ^|ASkW z2w)~gzoFNKC8(pJ)v+r4#5UbA z)w0?Fm_OPtn+xpY&4V38>j`JfI>d#uRlCydhulx?IN*i7&Vz9qykTGj)zQ}*5`{8@^CM}A%6Jh*jf>ns{5 zIxE>BnINr@w3j_ZaxVjr-S&P}iHe|UpDamDN}H9QlRhJzlHMb|E_Fk?H7%A=l}1fR zr%6(`q`XSmmpmwSR?_Cw_sShfOXMNt4C!4dQz8*>6ny3%=4@bRv0u?!F!{2Il#A;0&k)=nuzTb-or(f$x>A5%Ss& zdQMtd0AO9?>T3%*cAGc5$C){v1=bV54qMo@)=G6@Y?GZX+cC#~wkAi8{i3sreZHIJ znC^MtYyq$E_6?Q?N+Nfoj`$;#H*o@sLgy18^hhEHi|kMFyGdTcB_vZ?KpR88$i&gs zaCyvj!dI;A;u_XXDa4{HJFo(()~x^3ZP`aPM_DJ;Jl0F)V#Yby8!9Y*NI5SgQ|5E$ zkhF{qga&dAfkC`LP!j-L60)%lqUD&DiG`@#=$6>Kz~;~Z_;LV*0Ibu_q`#DkOpRPwHgX@TCu!~_Xcl0z5wcj-_w>`DI zw|2E<**x|`wo@+1w#B1x3pVsIPhtiTc1*3cY47jEg{Mb-hLSSN3CJP56cees`- zjSZzGI)!_nJ4W7PY_U}Q8T3`sC7hflBKD+nNi_O4@@+;beG>Z-o6O_$5Aa6`iv-g| z8%0IZLy~0*fuvr!Nc0(LJ^D3UgrAZqLS{;(K${#DUQGThY?pjmG&gCr=!W`lzDe1G zH%EC5X%VQnw`C>{Pr8}y5_V-8c}292?D-To?G>df=`UI@{1NIbESp@DxQ|~S-h&MU z+!%6Dh+63D9B&Cej%K_2gxWYBBXiXu-~h)(kHo0~#yW?%G0xw>R#(7t)cwU%=B@=E z0()GSfZYxZV6z`^547jFWX@l%5U|A48k!F6h4nrHJm1fUfA}}U+x*G?ZN65)7x2Y^ z0s7^;1W|l#;bs`)1EGz+_RvKCID|=T^S<;w0jd5q@Pxp3zbTX#7!xiG_(C%RT_c4F(bU4ffIdrrupr;_F~W|B8C z|4Sb#h>B-Epq*w6r4`a2P;XPpslAXn*j924xi48j>_X^?JBjh4OA=Y=4e{gX zKe1t`qDUa#H@q=6Hat7>I&>-I2=)q&3yOnhLJI<#u)xm@IebqL2B#fKX%|M;`F2KI z29x6Q$oRyI=uuQj{5_h1-hq)INm(p*C#EI#B<2G`H$h>KVY*|2=$_~tba(XT#4A)m zyj$W|>`Y7(tqs3Kj3VBk#BU4M`;vpZ{HDNzz_mcX04vbh#|x6+n_&lZGHQj6#~OY2 z<12%)*zPbk)*&`2ekr~-F+9-;bs5dY97TQysl>hb!;~L*E8`D7i93zdz+XVa2(K}0 zq7Tez5+Q4l)WRAhAH;p5Siyg#xF%?jo#w|S<9LUqulNPBn4rDn4S%)p5BHN`Ah)-G z#QlRzO1Xkb+zZ_0oHWL9W)}H9wLRet=@E7`u?&?*$chibZHO+xj*g7Q?hnnvyb7dZ zIX*P@xEGEmyG`K}_R2uWvJWPiCxU*%7~p}vn|+?4pLL<(ttG=iwhTACx0W_vw~3pj zwpqH%mPgtvmh)Pl)!US7?WKKfu521^vN!cJ-D)~yBs6&q89Jj$tIx75G5yc-&br1j z%{kj*a?7ne&}9!nbk|p?#yJp4NIVNZ2iAn1dlv`iLH`8D`()9>!Hy_@M2cMyKZ>Iy zLd1s{h`a_rfO>$~hsk12WIK2z+ydbiVL+^sEs~E^1r?2&Sqe#No^n)%K+`ibm~=KP zC)u0RIrU)fj5J-2By~adlB8~#3f2E{bQVButZf@kHrcov@dS5Dp_EcRb$4%1-COGJ z-qKTdcXzkb_S9W~QrzP1y4j6<`~Sl*%rFBpWU|Y<&vW0`bw2fvY}#7WrvJCYInTuBdJ8cKwlCLG0p_`PWr=)I?&agT7AR)RZ9 zMduilTag3;4L%8+g{EK*2Fa0K{=CpLH^Fakh`lpxQui_Q0oyRs4D%}ES>rtYF)dAp zQ=itDRn@8-)dtmoX1(f6<2Q9u{VC1I+AA7cO|81V>OXaSRiS!CRj4_q`cgAoBUN3k z6KdWyHEOzQeYye05vHEjP39R6rft8+Z9nTHIH~@1p6h}C0zF|FJU`YBO&JG)?f9j{ z95j=BgYuBoi*b*;gZ+sAio0Aeng6%wy~reKC%r1aD=Uxxt;|dOH*rB?ujKcM1Cu8v z-%jSG&QE!tx+tYx+WnOHG-~SVl=CU2Ny5}CiEC1TL{d_Id|f=RsIRrAK9egG`8P!XSkna<@BqNxNZ-ZZeoeG@9jKWsN=3%?V zT$n*oRg4~SM-Ct)%Pzg=oQ>n@VL9p^XZE>4W1PE{ zZi9Wgs?s8DoMu|nVA2n4Xrs5*?a+>>1+-slpJ?#93LpG`Pp zg7uK;rER^nz`4em>V4)d3xrUX7$3P3IvP0`$;NNOej+agG!z*=jaEu<)6JwV%$}sl ztW3g8js)Mp*#nT-53y^RRoDhb2JS7RGky=F0Dqa@9yga7!cC>z#kZ%NL!A(tP|sLb zpdGOegCVFfA*+a-#|KgH%$Vr z10~pt=nQv1juzd6TN~bvJqEAAo(T285<-UPp5U^GB9I)(4|IqsgHJF$g0q0MkRAw! z58*MGPK3_bzQjggJh>fsg6aUL(Yt|ZjJ^04jJf#NbTWPrV?LhE8UqgE1i){+)x>)K zN76V!h_qjjM!JpCVRmw^6KA4)c@`@~sHJTs>>wQ^EJv*@o3K0a-(u%+qoW6aJ&_1@ zcjP#h5xt7-87acr!iQ02^nNTgiUGjbTU=5!fP zjaSTHsw5 z>gJyq$_mbaJ_MgbtdKl36?u=;W2Qv0U~9}p@NZl#m`w-~&JkOZ_mFy1?ve+S0ZLC& zXNsA0pNy^@NO>SYG5~GK`>rSkT?IaBWJ`#5V zDTJpn8+bANp0EjdMj(fGfIY$_LdVDm(y-VbG6|z0hhm4RtFSAmF3eoY>exZbFw8Mh z2kZ#cBj(0k4ADf1b#iariyFHWcLC3mLZ0pC)4R>+H#4r!s%#lw=Of4P>m#t;`(T@>u4ImPgWhwHTj_%UF@v zJDC)}II&)KGX9FJb^J8xY58+evv9Hy!*4C1aEeh$`xOwnDtxK-dbh*;!YD*oS?gFA)D9OYAi&@ zRIAIC;P`>290vrv0T|i?$HB~)B9f1vgS`Xx1LhL;;SUfl5ML28DGoA|8KG_A-ekNJ z%x27s`^;Dqcb1hczQdX$-OKVy#jHBX1ExUiVcUd1dHaMvctnwkGgLH;Ef=0)s`VLxtj+2oc7wgi*N#x<<7+}A!dKGgnWOwe^UgSxktb-F#)xw>}NJbfpdUjN-b z!SK#WFfMfWGT-sEwVAy^TcWSb5%ab8-~wXb086ZE~z-<%4zMR1ubm#!C1SLVt;DF0D(OSqZvJW-qyN!pciE#+v+snpr2%hI2v zw@5Q(%t{)a!AykG`X!Ol=O-`7_&0f9#-HS;>9nLZsT$>)B(1CuF{wd6E(1Ln)b1!rQwug zv+1pSxy9qHx2*`wb`XLWJa}kMkdEv`v(u|0E}SIF0(-{V5II0AY7>6x|Lt^HrPS4& z4h$JDg&E*#m_@wF>|DWi&Sb$$&JzAvb{8H7&EXzoy=0$bDcDZ-5mqys&0NZEq8(@L zBU70o5}7%gEMmSVm!KV%B32$<#`M#8jJ*sd1MPaz&ofU^@1Z;6dgfB~rL?jCQ2Ylq{m1s3!sh4RI9e1Of0Ibc#Eb zavigc+9o=L?u-1*SQXt*PmDgIR)=>{q~QtF?h!8aek4XdAK{WSBU!{sWIK2TnE`x* zUq!xzu0nihg)h@*^Rhh+KEz$-D|7$xE_0haiSB`(BksBGIiA(-p`K*VSNCl93-@xD z)lG4a_6A&4-uJGR?l{j~=TA?DQ}2VEKZ5xl7D{!lM5=bu?eJX%md0L>@V6YpcP$- zCoyh;T^QHFy|f6}n$}F1Niz`1w4tbjO-}to=|+1>R?sA*PP9LSKeUB}RN5NCWXe3k z2l5R<57HL!5#9$p1twz;VHco@?equ~?hAK>Edg!F>0bkxyitFpYrVhR^|$xAGufrK zb$3K8t8DGfqpW+4KP^}EF3SrYfYJ-KX0Gml>7wSe(XJ{($ruJxxoVyzRei!{R=0HS z)~ejFZk*3#C=P&@-N7-|0(iLneI)FLVuJ&5xN*oOG~I+|<%yfXThx8vFxm;yI_50O zUhY$}mUoC!5|==mCZ5W?FX8Yavay2a3a4;^@`Lz}GD&<^A(c#$HAqF05AsRkdCGUf zYQ;8zL{1dklR5dr9z&B`a>7l);zAi8yuqZR}OtJuE(RHN795 zNx4Tc6BZG^<1x73z$i>2HZgi7HZZJ;9EXMB*6>m@y-Sl&b6*!%zrrxAd6pqb^Hl#>Q=+@8b!b26 z-y4*sWtN~tWGl2kwW;iV?1e73Bj&&5)+1efJ1}#D23&498T^dxN=PFtAPFdOw1M>X z%-)Oy)?ii)%?9jdR|!6|yGibIA4#SB0;!!}A%4UkAf^gnagE@CcpE<=KEk^pdCzMU z$9S&&&P-m+87>JntT5z3AWQehWzh8{p%iH}H96K4Odf zh+YJy02xFf4kXv(rc;&?N6?wHHLSbr&)nYvi{QN&5RX-imMu$srPL?)O=M+QlF=1; zT5;~K^yPWnjIjmE?Cip~EqAn;P+%;oFWlL_O*>nAUi*(lZHwXx`s5#JiOWgNJ)6}% z`$k4Mttz!c^7iDS_(#gHWQVwC94)R7_cC_~Q_g%u${=^a;&9oq9@x~dDB2uof<}9W zdZ(c&agMo-hD(lb2kFYem2IG-OrWXcE+fRec+m>&w+U z%qA_(UThrd+-Q64{^TCx?;GeCToF797r=Q@Nz9Ci$Ln#;L<+$`wi0jCZW7saFNwkE zM!ms0L`&fgV5||u=pEwrG8YSnaaKtp+->rNxV-ojaV-9*0{+J8B0l#mC80q zeu#gFkBFqANkW^Tlb|rJj(1;J!o|czSV_EM=3%yizM7dr9ZR!Pei9QX>j~>9EPOLD z4>Jds7n=e6j-&y9AzQGn;BwT=R2X^cD@MfrO(88Wd1FQV0wdeIrB0ptHKLUbDw?KXa1>bb$;_%!|eQMSrBPB?INlWyy;D zvR(24Qn4&5u}iy3-btp2Ln4Y8j+-QU&Cief!M(=ivQMIJkQDkZ>Hso<^Z@NB4FhK5 zw_&ny$09SZ>L4nh@L`ZqZed8_7#`%rvKEGW0==`#xP4oGptnC=`X808|P`f z#+6#TG1tI05-l}`C${&7yLPFeo%52x;!+v*xta`o&nRQDzk}&UaI(2asN7PH6xe5D z4muX%t~)>C9((75x1l`JYPc==6_P;N9GgS)0l7>azB@aIu$t9|{Efw<-{XWBW$X)V zCkMv^1YtCbcR{dA_)eH7Hj0IkD#=pmT}i3@l6bJrUP>J&k)ZH;M^x1BMTi77PVjdS6X>N+vn+8Uk zO=S^Opc(CJdm4S>NQ^yko`@cEPLCA2F_8`achM;!e=IFxiKSv>7#MX(=U|s$y8sKY zi*au-hj3G4^YLU16Fh}E1HO*kBkYVWB20@&L01Hi8ybyZ3!@9Mo1#e=LUdB>NqA}O zN7xqq6Wtr`ia8fj#WuLKE?$A(TH6I)H0N z{J>45Kkhs-12-uG;uz6xz|rU&?2=ec%oA%HF2Q^X{ltt7(y&VcTVj!bBrJ#e!w|F` z{t9KnB@hk%g1*|zpb|(F`V}mPi=d9kMMx4J7TkyY7uX4J2;M>lLvNAkFalS=F#HuM zihPU8F%irrK!LW6&w}{`ACW|IleeNIt28Q|K8${sHl69Doo5}P|77=KE=BWvAny>b z3;$n~&ed5sPGFE4#8mlY`9gU>aa4XxULZd$ohm;k87E(ZzRTJQ@)Xaw8x?)I>9U1f zhGZpolBhfTNnA1G0DlSX6Q>ge&-p;U!aPaZM?F9|Pud9{#LvMc#~P#F(3X(R_sGw8 z5BK(QTy@>Hl-lFX!|Y8)o~=PQ!2Djb-Z)6rPrsq*AKlx=zB+tEv*t<7C3QpPboInP zN7NrHMyiKbj#SU8?xkK-8&hAYTdO`(k3*+`V^tfPGt_MDQ_WbzZtVlJO;>EcVOZrT zHYB^ZoBsGuT37L46qK6n>#b zZs#*(*EtR1pUl(zEb2klWkL(aMI47-3aH3^(P@w(+88q;eq>?j8?-X`&1VmMa{l9g zYAy1Vp?2G2hE$7J51C%;4w@FIvkYgN*Xc$#Su{r*TWX#(5w!wUJ=!ncuI;8NGxX6d zwiM~*)(?gk);8v`_WNdrW2B{(lVZQ&Vz_sCn!Q_n{6ME54@wTDNAkk6@fyr-!bWT# zauselgG|ok=F?SiOWCcYH~9AwSmN_(2V_l|cj6D_v`X=}#AFob*JUnlwKQvP;ihbT zo80WWB5HO?QE66>qDeWoi*DyOwxQ)R3JbF<^CzT#$lI7!)xw-yl#vm?FloGOzwEpa z-AVBdqSUaFGz}?>_!u|~T#Ob*uZQcTL%#vjI(lXM!r{$*R zrtMF-l$x)kr6`ozgmtnnDD`xsgcJ8#_?-8I|C@c7HG;97GK>D2E>_O8&=mu`L^RWH9 z>z89Bdamv08Q?kTy6V$AFZq8tn@}=sQ2=oy1XiNfyGhPQXsk<)KmdO)SV&kO+C`j)NJ)bt=SUx8g_M!FSJZ66N7@kL8Adax4K*yXma);~sy6=xh`X>HV*&zeS>CYf95N0=nq z6w`U_CNro%X<4bCVq2g;;C!r4_gM5euTrmauhpM$A22NQWSC@LrnT17-ByRvWro@p z+H>q(9J|n2mC%98+#GW6I_D6d)42!L7Lr5Py@${QD?6+VPK$1aPN5W`g}|cNYFsT= zi~kQ?OXyB!5v7z>r0%p5N-JhDy$|OE^CUl)Jzkj4nNZECU2nHDRyc<$}8F|%60li>S=l>8V;S|J)}n{zo;##X()%P z3;2?(18$Pb0g${1*iRgeRS?Q!55ZwkHTWTt18xbYfM4O>gnYOsVJ>_NToPV|UlfG_ zQB;RL5blY+hL5ED4_$@R zgCNB49}W)m-3$i2dUQ)G@*eXHaxQmYvsOFrn19$w=2ZJq(*egBV~TUDvD#rXK5-A8!`_bfq>t`9pjh%O$BW?Z_oKCAmL-IjJAPOMF1yKq{xcCadX(DGkio)E?}+l-=B$ zWSFm_$occ>^SKCf4ev24k1ymr7u-g@mmLMu_$h)D+zMVXr;K05xyCQzuHj|!-g74K z#xUlwvGf(JZj@Jal+j3TflkDC0xNJYvFJ`W_ANFy{2S>A&p>C{Ptfzyi;z2rgti55 z!dHXc(23b^BtJMZ@+i15)-G5X+vHD<0|k7;H(G)-^pvs6MyrIzfB8LzoY%*Wk(P+RO2 zTR-m>_io?UK-WMQWL|JjWKUpY>>vLmV6cA%$OxVzrob#x3DTSLE4qcD0cLP>!RG=8 zu}-jsI#xIZ%_Q#OHc7GsDw#oeR6a+XrPw63qke-SGC*#T9hN_q`{en`F^Ui9=f-$m zyf{Ia(4<6^59M1F9nsUxC;0>UY1wV`Cq3eS1^=LPuX=Rm*`I}_%%oT0mymgwIM9Kd z!afb^Bej9}h~1lyWIFu;v6b&d^n;uowQ~Cp^;ruFwlnoWZ(Y24w6REg)0Bj6(=O{4T8eeU>;sI;U2`oeZwu=h|9qR;Uu&P~pX~Y) zp!>6sSea#&t@n z6TM9NEsmyjmu$^EE%}@^PMV*UB^72t(xS{d`Hl3o%C>1T<+tQC86`1YVwQ_V<0MSM zqPQ*W5K4>NLOo3ENy-J65!m<*xE0t}v6c}=bPl{ZTnbsC69JO{vrp|B>y6n8UGGq*GoSTBp%PzdDGDqV2X8zGNGe#+ zYHog0-lN>LIsUA#X|vN;Cn{6FDey_RCF_-+Cc%+ zY{96(-A41F>p*v89CiY-Kl&W%8F>&Kk0=A(pelE_fYA8~J>f2O?yzmP*{mZh?X0U! zndWj77t&MLMJLwkG0pfYzPNpxen?=^2z~^re(93=DNVE0s2s zRY2d$%%eSFw51NGTPVe7DrPHX7Rf|W5^dym#CzoB#POt~1RmIy&;x%5*AF)ga{>cL z2S+in_K}1r8R;6yfwqSczX>*Z|AT41-XVrBEr^o(Py<)F=e?uJv&63OknCA*tL>?~ zyPfW}I{)<^@n(1*_!fDS0}s5qz%>6wKOH*mW5PoMox*$I+0kLqe5?X%#1-OF2)Ds( z;&EaFX&@z@`WU?;AnFI&KKgdXeO43WHfKBI3hx5rJ%0{+c-(zXk+6}SDxAYU9+$=z z3O=$Q@wTwva*eD2`t_H9$E^_@;{C<%&OgM_^NU!Zpon4T&!IQ+*3x!!U!nB7^OW5j z0|o8*P!LuX{+-Z7yfKOeFeMLNoF@wKj zzrY=l99%U@zS;wgLhtvr0THyJl61-yjIQI$jD3?L!b6i&)v3F_$ zY()+Ew#D_YY!~Xc+fFwevyN?CXEiqMwN6o=vPg9GrgnzGMwRiq!EB)zk{o*sGu@jF zpFBpxYwti)l0Rfz?UR|i`5u}ddCJY#T)(a9&WU!R{l5K<{jLpiuCe`gt86VilkL|% zS4lzMj962 zqyzbN;y0YXh1=NQ;;f97{4I1m=M!T;OUMLR`x&R1T~O22ZR!ikSMpE%Xj0Eu6X6BY zl`tD#jvo;kAKMy82_wEWp?pt$;H4wk-_PdsEVbNst~4*Q)ERt+ue#p)1RX;cr`xId zTl-P#rHOv1b`Q&pG+R0YSS<>a=HIfH$Erd6C{dtQyG?t%n zl{$j5lyaH$7YPEd;NJn+*d-{9s9Sh#AOaon^b7Vw`&jd=9X)`#)c)15%Q9Uv4*gq? z8emlyU98co^41S;K3TJ?VP@6jy3)$T+PRgBYn%RDu5DX+y{1L=fLc?{m)aI}4Yf(A zJ$Oi6W3!{7yXG{S3V*0CH6)l%7^geFTP}HbJE6c`@A{zMcN)6uD-QSfZ^otsMI3(+SxIgp@?0uA%^v*;j#fa}l z*pF=u>Ye6kx*vugT8wU= zrh|Hzrb5+6y;8MG&1mkR?bs~Ueo?j8jMN-z7HBRseb!7?P0*&RF*=_5t>Lq3hY4;z zXMWlA#xlOS+_FNAv98e>?P233*E*}#z21@MbGnB^tl+SSJd_#hAO07x0Ey%`#K-hL z^l{v7yrZI?;tT~>E==Mi)TC}s?vi#hO_a{gdYZ+|K9QZ%va!X?g6;V`3m+7AEqd2( zbzywFovjH)9}7|oX0#fa_o!u`+y~i{vj%0AXRJ#fl*UM=CMn{7D~`(MNIr?ORt@}KR<*BmMb+?Mtg22wa;g%)FQ_j1URbmB+o766-&fT< z`I%CiP`aviOzEZC!=)SQ7nVC47XCTcz^b`a&#sr%k8RvtkFxn2`f7(al^V3FwpP8m zzkR>9v-6grz~?Zhg_><=5s7m~B+0WH^IsqioDqIbnGJXt*9d*sVd6^8Ny8Qb*I$H+oq3*Z(UdAl@+RvO?kOWGit~OXhC8%c?hABIP(8<-Qx)TzzCQF zG=YX_Pw;x=1b#+15BCh7h;56Ui)DvzM^eJY;Z@;(kdNuH@*Kxx_Qv#n)`nEwmT=Rb|)fU*FhNL7Q+fR9@^{p>bqeX;%c)@6ZFGOJxs8%o%N!TWf1GnI7RL}Y`xDi4vLvV`n8r3OFy3xlZj9F7 zHTJ9{7;|csdSA_B&F0#y=1|?Z#^sG&nuj(0*W3c-g>+K?rIKm3s+^iXnm9w6o@!fR z*y-Ma+5{J%)Z8rRih$I&AEE@?BK}}X6okh`2f#C9tx-q%=ty_m_2@ouF-A=!05mEF zd`^3VX7QJk)eIcX$goo%GTxx?$yVfJj08#nlSxZraanlw3*HRQrnrrq<-(4ft)ly! z5t75~PSQm7BdLZpU8-Z$hXVXUSdZtBF=}4ZeW*3TwnC#tJd-!_06#(jnA2^xD5X*vWm$hjEsBPT1FZPz#l_ zmwA-!m0_U;(K$@8F3iOW%IumW9?XS!wwgKi>HjUwnS%LDY2I@cSyXipPZtXVB1x>!{s^%@qSY$QrYVKHP zZyr@^Q5~w?r0!nlR#O^6+8xa|^y9R9bd5Tp_M~o^W{_@{rb0*8MD<(M-_h3bN9!cb z8i!mj_aLTk{*N{%G{bosdFf@N8DuejJ9Hoa4tYwrf!Rx~#JiXX8Q`5`pcF1HB5om6 zOP7c%r43RLJ;&ZtZcl*YdnbnyHm0$XCZ_L8I+I37PD<;a98J?D-bptq=citiEl&I* z`XWEgYY-i0wG;Z8{RCNz_w3J~)KX&_Xbu>5nX9xyQ%iN!aJl({QQmydl-kU-{Al`VUD(gEIF1A#A7T761*wM;g>OSpvds_#32Li#4p+BLiur9PCd=TD_DU0;MkHY>T9tA*Z zPuw5+ad0LJOUmG_r*h)XFv>(cPL6Dx;F+vi@I>Je-iV(nU6{~Xu1fkG-z#}v(w5|P zDf5#Lr0z)Or0-7ooSu%hm9C_0N*Z;%n04xVdrPIMX-= z##Ne!Jeb%Xd`RGpbz`H!6=--l^zRrL9P+S@fr& zruk1|&5z2s+98#^hMdYbs@$rNx|*t<#tyYNEZGeOc9sftBkBhDCmKk>2d2gFM*G<) z!>a(8P%pyINC(PK{9(ogvWU}{*;(+Ew^N)T?xiSIJWY_thZEW-{Yw5PV|CiB7UMF8 z=Mi(@{H)xMt+I1lwc@r22}8LQE4z?)=kvOm$6I5hrt ze3fjlQXtMzGDSnAMZ&RhyDT~}>e*<9WtBO{-r1bzDz@Z$_E^_?Z(0ZW zrrQqt;~kTs^UkyIB+rL%SAVCNCx`k`=f;2z{Q7qV)BN*rY%@;DjKc z8Tx=-i~NP37aKw9g=?lfB61ns$(>nWC^y;LY2VnZ=yvum#tY7B=48%5rjE^IhFIl{ z&CD3RnemQ338hkAr!ApRp?au$D8s1#kr~uZ1K>Lm0v3~> z11~8(v31mx*uS(uqyzmzjK`P;T%$k7AENantf##sqE`fE7=0^k18pgF0@+JDMeK@Z z$y>8a@ORitaA~YeoR(IFJxViU!(Z0RH_WtMH}tc9H}WhCQD@Hr(`>^sV~Ihme`Xl3 z`(wJRzhI3S3mrl08zH9eR}3tSJ&0AHui}=FD-?q62V2Ac_^-fj4;$|4dKhZw{0VQc%aEJaZg3at zY3Q6eKOi?9@c%L$^5*37)_Vqe z)4e&~!+yHAC^XD-1Ih5nVlv-oOrft9Xb9*)UuYe1K3q=e7v?eg#y+r$@D|ohVikKl zF`MTmQuF<%at~MBl za}$#4&;~!7U;BFMo_SVk{&UV(-M4jVT5s;&G+wu~fut&|J=8d~`f|h9DoF!Sy}Y(p z^@6IO)$1x3*50YS*3h?#p}JO8+6+{8Xw+04YdBj)YC2opuUS-cr}7tz_~+mOxZ=*#O(7~kJ5UkACI4%uu@Vc-Y2m0m|P2b+RLE7Lbr0~NEgv_x-w>r z-3{%H^+TEV;{tm^n>=Jc-Q{}kyF%;-weFuF8 zN~Bz>N3^31`PwgrUYhwvw`z!CebXoHxyHkqa8p3jQWez9RdKY}Rb6#_O``sm&Z0kJ zd}CZ^4Vh26^_HT*Bm2EjYfnk!c3>P8SU3z`~uJ zXQB_>O_E*w^|Ix{SBm@MP0AgTVajT$Nx4z>NSP_UE?+G9E?qAsit(b+0<5r>)06*z z(S>uCx}WiyIDmQ*KY%h9KbLI8X$kAG8-S*;Jk}EWj!Z=jmQC&%!F;F6hqVoGdn`8{ zlPqH#WoCx$lqq2BWt^*jVECquH-g$S(_#(FJVBFh{!4w@45!N_s)-O2G(H|1JyM$7_N$_TG5GHwdA!bjfaEh->Lx} zc4_~~1?lsZv(ncq$EE#LB&Q@P&m`Yfv`hJ_NKPH3OiYz49wxPtJyqTiGi6dCSMnyV zNt_V3TYR6_D4fo15DaF`9}lVk+SRZaTg)`V^NC_5h2a5x9}wd%zS2 z4EC41B>(;&n`_Tm#g`qZFSQOldbWl?sXFjov?IN|JA%r zwWvwe_`cz5!`}__8_zX%Zra(rwMn4XH1*PcRhJrbb!^K=ozhIxuQM&s$INq$b&e61 zZ2v}EGQ7vxBf8Kt2D8fhES4GEg&i99;a6boq4;2;97Z-HC&Pck=VKp34VWcRJ#a2Cfv`51PR7FR zD3jpH6g%9PHUSw;AAmey0PtmICwMmFP(;qS7OkPT#b6o5*d4SrnCp}}3`~(>u9Jze zxkM($i606K#*YJ@;0n(euGAkqd#D(Q)Yi z-TQEBtO=n45YiSH7=DeDN6q*pu~Fc=XcBREWErV_SWJEvIzf2_$*KL2-Q>v;8cBp+ zwd=xTVLJ90 zB8+uKR!6%=CdKlx=VAr8L~JMg9N;dVj@6)s;oI1rv4@!A=((sD$wX#^#s;?q7x~`% z05{G%(&2EG*k3v}p*iKtC<%CtX}JY9%B`?*tfdKc7_2jmL~|DN^cyW_(8N@{v6pL> z>7{#%<&E!!EeY!B*a|;%;E{dqf#Ht+Y)q@52M{7h@TD;Wp%LYvvq+`njno`UI^#8U z3bQTR&AG$4$ehI};^^rB{~P^aTq9E;%H#YiYQrlRo#WpZZsP3~7PC`CKN+hwviPbSmGEvTR-uC9I|3a(aKfkMbBeL2Sen%^rYYT&Y#L@NFpM$@G;G78rfnL2^S$PJ)$*o|nmbKPwOg9U z=ulRrp+?gW<%JKhE;47>f10K4W^0PyZND4HcfrBG{7iUy=nV1|#zdOJ?&$s~0XT%I zz>maNf@R=Q5|?y|{+(9AdC80yZs&}XczB?Uz%P(@;y25F2ohy~$90qLH9%7lKP(h1t znYcCi1!{?$lsY5Ll-VZdXLea$uWVw0KYM(u7A=SGlym%fs6DWt-KSU-G8mKYBI3_}-#n z`S(RNi@&8*w*Ru=&->4#Dr&zh_|5v>w!Hh#@ny-sZkH-bcb2lsDtB`=RA4v#$ZA8K*Wrr0R`ligcj6^ln|u{ zlSr@Twq=_H7x>>qw?!&Zg5;doE&WS|RSZ$Ij<+d!34bM|#J5Xao6tUynj}wJn4(DR zmiAcLFO{a`q|oK&q|f35Nm5~-#Fv7J%7G{|`H$eS!Y{zf6ZjO#MlM-2j%$ls&dn9J zEDveJ3ahdH7{G1rEU0V7o;YMBjuM@bf_bfWUvuXY^)y7P%2?uB*H8 zko~)6p=C((cB7(cfG$)&SOe7#Qnjz{q!LtFo9&gon;R;xs`6?Y)v5LI8eG!@)p*qe zb+Ycf-efvvL2Ml4k=5?F{iXqZ8H2#4`dM2K$^K zgEv-mPH`Vp;HD5O{a%d~j38j(Jmb{BJne>Ekh4>L?B@D*63D08h z2=XX~P=R1^=!83#87hp>0&T-n-SP;k7R@% zkVYZLse;&H#@*O%=GhpL8Ho*G+GE#P+cEVV5vCot3_FrT#((2<#SyvVaqZAkJIp1L z!#oT*n}3@O@%|<2co)bAdG|=^TobV;E1%?}_a_F(<-~!+okRjjLgSPVCRPKpMroO>V=wLz&KCQ4i2+ zNhWGn!c__hUq@Pl>j6^1AZ{6W2g@b6k-zbqg6A-PuPm15kwoS@f5S)YrqEIQ=}?2! z2mNC?4z)J-3OzP|gBaEpP?~)o^vBsUM0MrBcU)UT0BYEb`tn2J;7R0jNQnwJ-bHor zh3Lx2JWMIZhi!wq1rYJWv2$_TqA&5^A{bDP&VSoS`T%buM}gJh7r=Go79d9YVmHEj zqbjItq+6gQ+|K7hrg@*klYD=}2fSwZwnu>6@~uXxmm}a%;2N?dlpl#l_8~9fyNEDU z6Ly3s(Z7(Us14p3HK6JELZl*kA*@AB3fE%T*g%XQZSH@;nx(wZf9t2&7 z`2i2&=b%@RS7FL%kFfI?w{V-7+X!@aDfJuY5c4hfC}%iF#~;EzA?U$Q7wuq2<7{k7 zVpsM!Ne4DNc>x=ryp<(L4l-sYJLqspHzqP+Bl}8R9OsFM$|>SsXZu+vm_KRrXz$4{ zNpC@Z!gur`)MRKW{561rj0>WHtib2c3Eu+$4rkPbvJY{5v3kvKEDKFmYoVpml4pBw zO0=ttSF9%tcnj6A%6wLT$b3aBH*HfZjmp-E#`c=S28E`-ezdx$wpj(!R49*v%&vIF z+}1k^l)8_Sr|GL2s_UmF89HlQkQ(Fc)Z@jeuejK?L?77|nF9P$!+XG(9TovvfGWg9u` zI4^nqxJmq-+&{dr+>5+Q&UxMr-WZOVM`TrUCNQ1s5=IA>l75li2=-F9kt-=a+!OLB zjE?*f*^TUn-ooFDenn>ieGv1)$KjWPP0)x}8Cm2}gfVV%=!+vRFv;=WtF#?=ms{W1 z``c%k8FrGnz`n%%)IQbnzy`OoY#$vztz3uQjk-@N?F&HKTvYI#t!Xxj1z9N5vhpAM|1Qv-npWlmmT5yGm5+%|{ zh%Ynmi`%ezCe(2viN|?UB>M#`QtTp33R*l}q7#uM0`cLbL@{47NPJO}5w{_+Ozabj zg*^UqPH#4!`GAI?WfQZAGjUh2d(a<2PU$lEaFiDI4grIzpvR&=qV({TklTOEn*(-x zFF53FJrx^eOo+WFEes^3jP#es%TGHMB z)%~o;)q`^dkfB-8u(&YveIXOPLA9}Z9}wT3$a@W+rWP&fYgSb!En*rGg_Gg zm@Mu#?gHUq;lae4353)V$=3{H+U4wb+4plk=I+cxQ(_1DyXz2kBY39yAOpf!rT@ z3^x&wpkg7=;Te1p!ucM0xvpofh@IuwV^`Qk)^nCFrrn0ehW+|1!yj#lo~D_kd!h>I zCMX$twL-0ZC7-LkA^WD?(bB3N3etREw)nKAvTnL;Ihf;+f7hQ<9y7gd4Ow$F_w3`f zr(AWqbMDW&-=1m4D*tY)E%3v>IgEB6jeYn1hV=@~MeT}Q#EgRl(G|#-n1yH$ZVIjr z-ydH<8b++4I4H-cEp!}x1Ix@XurINWbJw!r{H4r+f*JIB!2&v4xQz}M6)}2?66mR- zw&d+XIRPnJMmQ|qOPDXl5C#hXd{53U!a#-`|CgMCj}k-Jqu|8t0O~(XE}{|@h95&r zgmGc|=)~CV5C9DKSwivdZT|iC)t*n58}2O2Gv{_NeRIGt%hFkEGM-g+G~8Ew(Jzr@ z>d#3R=}MckblELiv^k2|+7rrR?K;(1buU$#dY+P}9jU6)s?~dS_q9U97X5Dn-1r@2 zix-;@8r2}b?2%=L`MdRpeYj(yXNmVwfD@VobdQK2dt(e_Lu@^U2F2kr;B)ccVAt?? zcs_9r=ninfq~sQO3++8(F*6m_gEb$0mqkTuSi{igIK8nyxf}5Pc`FD?o{e}^AS7Q9 zWsyz@t`l!^r;?X*yV16Cw=l@u8LZP>6o=66%@T6=Q`d7sU`r55sAIthJ6THHCAt;upiD<3Qcl7)lYhf)#C`CQgh|jv zxXIC<*j_*lZWj>75rAr3aX1CnGbqKly}i&^Jb6f&M*%PJ{e@Th*1_q%4p6qQYxJk* zFp%zU3yg8QLUz~Q0NMS|3-g?EeRe;x^>=MHyt7}`s4O^bl4-1Vm+7`PV&Q20)(Q=n z?N!rk|5QPd0_|21x&?MMsMKvzyXp1T|gn=Mo=H&TWC`7PMJZv#!`c~ zMmfdLc}cJ2u3=8%_6CRbrOdM&6@$co$?V9FvX*np*o)a{j+n8CO`; !zC8~J0# zLo9;v8N;G6(S6A{w18XByHBYayHNLEbHF_&&He#zfH}?Wgx)7}@XHd&EJ(#-x^nOKBzP#aWh| zsX6s|f7-mt|F3OX+k5R(I=*YatN3aAbzODsOS`r&+ErZN9$tK=?W?Y1^2x>H@=kQP zoc*%kb(%DHnk16FDE?FiB<@YxTG0`SfEO=1%NoGBNc&F9B1v)m@Ck?o*hE+nQWHBC zy8>(tzx4|PH(hP~Po2-bzwGCn^Qf7)!+^u}5=9u?XBB%%yBOLr34l{7%`6U2E6{iSNeHHAK}_pt8{*l|`cT#+_gBMT+YHTD^LSOF zX|>W}c%ZmqpeqLGVT!LBiXyqytjJa9RfV$mYD>#rjZxM{_e4S0zg33xujSoL{FZL! zkJ6Er$ugpChjOgFK;6}e)z9^KETg>@&b8j|-gklLK^owQ_KV(!I->Jny`Y!id8mCT zDYgrqKQ*X`3YIw7HU$)EP^Ujn{0lM(E6WehV8Dq z%7J#hcXV{!cU*J{9ThH?v(An3WV@&Nk**;4js6uXb1wkc?)RXRxoz}C@D^lh_%ie% z*lOAfosS&|CX2k7?c`?MS4soEo_dSei@ucFm+_6Bzz8!pfJE2}jIEqp#!}t^##F&X z)*``DHbtyaRkOdANP#LFin#YiMTpO+Xx}@|8vBdV`UXzDW@f zmlaNQ_YALeoeVWP1Hm)S?9fmbB-9byckgh|@;hCX-r??%o^sb~mj@H??yDFp9j(ZasTC+COi5GCR61JEwGPxA z)IMoFs#~r~Fksc6%#*cS>`eW2_Y8x}yWjLLaL;OrhV6%7f7~{BG;kihGg66P09!<= zL%GOR1SEAceIhNwyhCfx?Z})XPUht%ZV*0_JOn*CY6&{skhVSx0q)`U=KAv_c^C4? zIqbY)S;z8vX1>d}r@IQ!>3<7ermipSmjV=COF3TvO-;!EnN-)NIKh$E5_cz8BMM|+ z5zWv1B&9c`vUzA-yGBE$xZ&}i-1

    `qsbriK|C{H#hwKN^RQn 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%0AK_?lYW!Rp^IsG^id2dGstGJj^w@@?0 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 z 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 alRBoCuk~2|I%dA6NuR_kcI#KleEtQ7p(;QC literal 0 HcmV?d00001 diff --git a/test/torchaudio_unittest/assets/vad-go-mono-32000.wav b/test/torchaudio_unittest/assets/vad-go-mono-32000.wav new file mode 100644 index 0000000000000000000000000000000000000000..277d4bb0b9b98ff6782344eb1c4cc2f056fac58b GIT binary patch literal 54660 zcmYJ61(e+6*Ty3=v)XN;K&iXCySux)ySux)7uwQN9EulrcPP-}#l5b(JF)NgB!A9# za%N{Hle~H5%5$H4lkL^9UArZx4Woa{er?B1ol_#8VHhTxv6K%@m#%f~`@5A_QBJFlFdU3{RuF~4*%=>I(F2~L^78=VrZjv#SZ45mb zN2?S0Wj5PFV;NnZ&rws@JD45}rH!GqIfdUAbJP@$TEr34jH!Gc%{AH@?TzmAu&2?% zXvOD#^nHvmh<)ApwHN*9#wdm|o?)~&gujz$X`Qjsn8$VI^LZX)nMixn=*{SG+&wvG z06iGZ=vy02jHX5dqnc61C~6cl%JQj}QNyUpt0BKNH0l}kjb=tmqXow`VozJ62Xoq+ zzrAT~3|H(nnQCo?pWp7!Q@KgQjIYqbvh-!joro;j$0N=uXJD{@-dVi z51_s2TxWqXC)5S)?Zf!GhO^g^Icdq9OyrnR;hK-7r;GSDg5Nb;>cJ#rWB{*`jK6KT zUeVcInDzGIdbL0{>V?wNo>qo4H$z!tNm6HKz9CyvWTgSpQjb}#$Lu$w^}f71aE7cmmie1Xi?eBMYS_*c`ZDesK z8c#o((wos7IT~G@AGWxdcU@x(ZEvG>$;&GGw1#)}YB7H|)2}1OA^zS$PYxg#TadvO z^k5b*$$@OuYR`R+^~q*b!EvZCteQ2IWd*FaWN zGdqgk`Z4PRIlgK`e~vRX#~>GVQhmrauCN(K_+BNCxqjWjLc?^X0vY|`}&1aK8Tjva-CMpMps7E zhB@iU*~2+!1h4U2L-tp8el2UUFx0`#c^%)!a@+*^GMrf%NPnb*@*KL(bUuyc z+_|i)yv!7&Kyx~oZ}JwB+9l}qc3!dx%lWiB{JfME+a7Lvjs0l5?9A?PB_my5#wspk z)i-m@mhkUxBw+)s%w>#P=Q(`d63WOfTHHt*cOW%)8hbceZLFlt^?cI%7NkS|W&ztQ z#ygMw8yKA|)lz<$L%;hoFY;F-nP2%KSz~Q|L#Y|U?|ni^8X1mW{!k(?z$oub&!~P zp+)aNOR{4F&?Q+ht;RS;A~}~o8_KmNg>x^dd@A6gS`QC#u4Cm^d zkcoDD7p_oW$0B!HwI#GZBfQc;j-SgK%t02W@@XlbWiN+woaAFNzbt1y^k! z-pK#Ti_5c*2xVq0v(t&u)M9MyLmBMMQ94e3xE*_2Bb8nGEM1kv%EQV(NfSn}4dAzC z%tbBcQJ!!JR}@wn!KXod?t@$l0kmeW+tSJq&h5smHe%M>F~8ExN=7;KuO^@C^SK0m zu{8Qw+_=rikERx5Pf5O4LU)U?uOOOQjw4F)Ds2=oZU)H};OJ6lcE!*R6ftf^$IEb3 zG2?b#Rp^`WN?qPtVZ-J5Tk@$T->cz+D{=MWMj@_So?|O;y~2FE)yQk48<}BS1&lmK z2E8fHRSU5HcFwBH(dkBRVDMf0EF%bFMjHFFI9nsos~r8x&z=GtU75bzO#iDe&Kg|h z7Pg{XuR7nV(u>OcBBUdWRiCkx2yK#jSC-L~4zI3xs?8adIHw`&(*kLXR!QsIg>lyA z+;+&Aq^v7*Ed-;j4Xrj~jRv#k!b?Ja5uTO)MAD~UWPv0XI!4}aG@ER3A6o1iN`WMK z2rai~&nRSD$hIqOjYQ&vK830p(hFHoVWhTPyF(~Nov?L6pR#zpXsZ{WgqnJXpM{)S za!ylT`rC+?6Uo_%TNU3bQu<95-_ob(5UHG|gxXCZ|qVLV=gYauHa z$*+6D^MxCBa^1sxUyt=(i``#O&sMX^`_B$N#%5X;npnb_YuT3bNvL!l+GjT>o{J%Pv~R??>pGHgEKbp+8*|72R%H@IJeQe=~%v1Y_gAY zndd>wqAc$iW_c)U(SsF~U2lmD48fDiQmdcpS7cpBvr?03Wl`uirsGK@M;&NqDC?wn ztS{>+8!kPV6xwk~lO#uL+@CARr%$2{^+7F6<=X_#9mO^Qi!R@{n3wRc+EV-{>@P$v z#J!mQXm)pnUR3yREo0sn&iXcf5xQCs&c&-KjJ z0pl2RB(E*+yo0PUZ7Nhd`mH`prOj!KbRBIdPMyFWX~j(XD%lbmUqL$>=Wg1$ABjJ1JY_s-Jj}PdjC*;1 zJpBF$tsh1|G%G?JtGM<)P^)n3M$VRItPR&^BlEF^wiSJC<2NA|jX}tKF)!hBA@#-d zOl!KBz6j+jUY3`XUJJbpKwcC<_6v1=ICD3I-mjpqE9kehUMNU$_eAEoH9o2zBNQqY z8k`eu|6!R0^f}VLja*HbYa8QT8=k+2-tJ*dHD8J|q%D$x<^P*8A);j*s})$wNVkXU zB2*_`TE)0F^1g_k%;xhZUK;IA+Fwr_YdL-sEhu7K&$p!JrY_V8Cx#3tHV z!*A=D*Zab?T*LXoF_MvK%!!cOh)|ct@m*2W1YWvYwALf|y+6lIVO&v+smO9}xb6$M z#x9PN42Uh6!TvQ|RTgI%?~?Ze*c4fgeOR`~*zQFFj)i^Ex@+5y3_O5L+{28m=UlC$ zEWj*Uok~lq!yYfDMMcc>*)s=8SLCEM9me}Ow4x2ZwF|2`6#F3TDSRYfA_P8%zd~jU z*`}fi!uDf1NB+4lEy!|c4ALO!&;(>>X?RshwAO4wC`02oa!e>uQQJa@6FFZIp2k0& z*2O>x>5B=N#g&Cgm-1Kpr5O{LO+_<`6eNv`5eI}4Ihx<(eP%FDt>jW>P%@MtV#(y`hO>>Lb;X|Y z=hL}bANI(Osn0??9l=U%x#Dm}q-aDjU4*v8LkgEo;f!ANq&2O#4)LEjMDb<9ehs;9 zMNo8AdeVraI?=0}4L``k{s?7u;VJ_eOFd9nJ}`G-Fj!4qwHQ@z^s|7G3IZ1|(*#6a z5M-Zaq=4*;F{W~S6V@+7`xQauH-pW~a^2>fT@(agma~>3qet--%UCCQ*FIdS5Onf| z;NRd%V1fNjBRj|m(l}atURV01m`A+Je-XK2y`JF1o49fo*NEa7#T>WNzbc%eb#29` z!hA2u6}m8g8dY<8-45&0gI_Dt`@Hl~>_??A3c8(^ZYN%-!;CdxL>Y{xD6LcpBbNO1 zvm7H+v=ec(;zU|AD`Su|S+yBRzigFwAX(b!^jhe=AFDc+l@c=V%z8~mnzo=b+gRtR ztfbg*aUPOQtzAd@DjrYhy=(Y;3s$Zj?Z_eyr3GQ>fwV4ZmF<(?78+Q}XK`_gMiis< zL$3NFbJaM%JICrsS;cvwjg*yIi}r5juVh`eVm>_*7a|!OAD%5#FD9&8*rMVLMLg|8 zJytwB=znW7hQ5mX)hyRy{yNh`>6`56Safg-t1V`J4)ZgEQK)Yb&o7It?_zBurnxz- zw__9MtmvXASCocDe3$s#zKl_`(4St)mx%?Hv~*z3#1gmQ=qUPb!T4(u|5Zc|#7QYq zSLED+zm1t=@m>{(Gg>kxG3b55(J9)NRp`n{YI2N@Z;JFtuf=GM;JTyuU4B)5r$6J3 zn655pUMKq2mpx+0l@HN+^rF3pmmbEMvb54wjaV2~_)FeR7-K$dE3%x(xE8WvUthUhkT%`6t zc6%#F3B3yQ%P&j`XH^`sWLth{F4tAev5>wfI@A%et>fr>#NNtRibsv2FIi*x)0rG4 zj#w60xt<8MPo*vSvQ7A?we&*xTlQR5RvDa$v@C{qHf=`i?kxHubT65cBwtx= z#wO;dE*7X3wnF~5CcdR+Xf>2aX~J=0G$R&B+({c+=#16+qGwIJK3NJ~~-9$4cQGc=N;6xm0}Skbkj24zpCb0zgccwTI7A9^R0 zEBROcR4i=7tSbtc#mX$ zkL*jD#pUls%4I2Kd6#n)*=b?Y*}O-xqqsv{#H4UOCNk;>%XVfRt04=@Qj0N>7Z^hC z#~?p?SFUL)b1fSeq0}jiL+qngybZF}ioe2>ox**}ZFFG8g+HY^vZhh?Ns)(m1Z8f; zy(zn*ERWVfgpQASf%M3zud z(JHQ_42IYa^>h*2Ec8-(rwCCz_y|79TM8+OnG_m{a>~-3R_H`?=2}c;N8Tl`@&Q5! zI-@7^sLX?Sr8and#hJfl^4_vLu6gv_WaKv|LM zw5ArT^3uxJgQdR-d|M23t>~dDxK)h381HiIE6=9C^}@MO-Y#P9OK_~XU{xBFW2?ax znsZcjzE|aV-r87N4_%bHvlgf=O2uKgv(4Hbe+le9Q_y$u?|8($?@@Jel-iBeE%PB$PFd z{wpf($>`*-^_O&e!^=l4uuH4c{HvO(mYZiGKS(`55ddk|Xen1?gY`WMa zVLTxs*-F*$=n4};yP_Sf}b;r?$ z?4tUtl~61$)Fug<6RxIm#nU)<3FpY73(ZB0vM{PFXGcb$2um`oY8GL?v9vaW)&}uO z7%8%aV|S6PoQ;@FBcY&pOrTju2uVz1w|%0yE$^8SX%m9 z9r;rP(}YcW*q--#;k6qhk0m*(87-<(sTX7E%UF7`O2Re+xSnDmRbF)Aw^7WNDzoIZ zbfhXp^hu~h<5D~)#Gq`7Jj^(b*72(Cl4lZM(JySXZ@5n5!WNZ}k(4MuET1mSrdUQ< z1tC;%PqOQZJ{Pbm()sPYw8CpbFR~A>tK5iU5NWJ7Wje$^P2&tPG1Bb?NWc<)iJ0#F zc%yqbYb)m+#_vb?Qp~eFwmgfD6HZoqvz5=fzATY2q1shkA&H*Em6R!u>Ja1`dNY3I z;>C#yl_)Y(U*?2AE6vZ6lbWqLD@0Qny{|sXgKZ7X1_Evs-ltH{#Qq%iYoabp-fq-UYx6X zn1LKKiP>n)Xk-bs8j3(wucYeCNXj*b5$`Y7N4h^gl=s>6d>1Hg3;ma$-N?5s^jUKz zhEnXbvX82IS-|RTLEjW>swPG?N>OcG#LbE`PzF&txQWkmIbs3ts^L;Qir>ZEFXWh| zTx~k~B?-`6sdZIzt>Uj@3^A|DNseKbgq$Nu(%cE@Y3+NU0b*b~u_;y)OAv7n;+my{ z@=`*Y${$48fHAbRguZPF?Tt{Ws>76@QT{|simFc)kFE(VkXZ3qoGrdq(xq0SY?^Yb ziaccn=dw>6kt&+{g=;FDDSV)w%GOJpJFs4oSB*e%r?f@YFwz6@qhpwx>Ab6&N!bV0 zyG*4o(pP!R!L+6rK~`HFyS%Zo^Fw(}|DW7Qt2KA?m^-mPvZZ1;CUUeayCRBd>{-N7 z>aB3M=2sX?)=xD>Vr8a;_FPWkN zB4U6vzD4ves{9!f&WS2~mFrY&Bo1{@IL1iF#&8v7h*XmkG3jDd=P(XcCu$bNnnn3% zaXjmggBkoPoi!2lpj@u9pxIt@2g%{aw=9ein`Ums1`xKR@Piu2XS&U z*}o{9N%^BG%;0kNC`T+gk?&PavZ9BkSgWpFQP@ftMpb@dBL;Au`YR0-gEo_KtDlk$ zSr4tOMm(2q;;Q=ao2-@iH^rkGuNW=m*Ho({`>D#JHb|T7t5A@zuI#+BCz0mKH?^Z* zsu2>l>4t3grX68)WtWA!G#B!iBWP0yd2}fELSw3?64RlWOP)lz4fRY{9Ksp1=zITA zUlh@4q+P@7iE|b|t-36o6J>fdpUTu|#Yb^vNwH*SHS;=$Ye#k5YC~hunW{I4vMR#w z@=t^5lWau9fyy390>phN%Qc?XRAD!b&x)NTladC>s5DmgTP%|*S%ve3yW~}Rb9MP` zA#eE?X_{&&bhRiOrqQVKSr)rDV-)WsPbp@*Ug%SWJ5^a1aYN$CRr#oOS5zgeHJ{#z z`%?{wbW+T*tgCA0{;P==dn6wyzpgxzaD(zp8in*-vML@{*iZgcx)W77$%AMGqFIZ4 zs5oj_Rv{|si1^Co=%*@XR5PIJ#l2WnF`SX-P}CqkKuoKu0VKl{`K;Iu6|t78!WJ?V28`-DqkLhM zvzKpCD~cj#GX6o#LsaJ|dn%2PPAgi`>P}~d#R^4?b%X=97Q)`no4}0$Ox3f7xOoeW2*2{3|5<{MYYi#_}hlo z6%n>)ZwToHx*owQ6VBMFdwj;aa7 zEr{U~E|m40PCK$r!#GBxRb(ojP4cArAhE9Uxp!LGJu1nu~)x;$m6wbmWO znGmj24Q6ZRq#Y4mA=nyKuT~&FtIpWU5}_AjR%)cwnUYTpE?ov2Zd&R~y=Xlj%i49W~ zgJdhJg&V<`CFhbx)dVk~b=5@4#!HfAg9e0WDsFB~3tDl-CSt+cA^~D3x^ab?Vf?D< z&)P^tGxkK4pt}1+)!`bC&WY+!hK8#z3@#L;J1J!OqKX~GIO?ldCFP|RiA3z1&{13V z|Hr!tv1^qS(TZCUk0P7bn)8%llNVIwprZDOnO6OR?jIP-RfR>Bp;?S3$@VF4CI2id zqD+P=9%S+4xs`Pl@{(_r4-c{>od)GWTXaS}+F@ zi!Z+{^rzTPtD=fJ9V3|@L%YfWiz!fru&NEjVu-zqvi~jECsv^h`r06r#wjN&fcXP`J!+1u)9T}fWe`CV+TG*-G4)p*MbkLEg2 zHIIC=vN*awODJC%daaPKq~e$e2dl0}eoJ;)e6u{6_ULF?DDhNcFCwY=e`Kt@=Q=Ev zsu{G>iipH$uMd%IRPDJ3TXqCyViz(f#!9&_c}7`fp`Z!0Ebd#8jdHrnxw2|yrFW_^ zl5Nx+%M3V$b+yVy8mSvqmVs}crn<;r-cnrw|YU$`>DOVWwxZoXkivV4OgE5&2VzeSY{{gD_k zRmx$s=CiEI2>LJYJebv%tj zYfG!TpZGRb49_Bvwq;DTewi&yew>E72{Do zfog@trY#89SCy0zx>IIN(kbLBD=3Slby7?y?U$@Xc0u+{Hep;i`>Jse!qFX@>ZLrl ztf#8R!~iJzQKo)A#}7af#c4!&Em;jkKw?Ft8$t$(TB1AnHn8pwF&}%Gna$zYR97kH zK=Gieq1CqdWNDaMP_!qzqzEs{7D%InIcKs@wo1Qfg%v>w$q#148i($iYR|b5$`i{R zRcTD+8msBM@{C#uc|W1d>71iE(9CJRC3OdoKvmA}rT^kbC3VVv>}FH+dw{jt9?q3& zufNI`ODxVlA?C4{{{*z6p7|pWchBRn@raXXR~`Sy{xqNfv~c zqpK-CRYW9hSHGh8LX2@#c_@BGaj9Oi!r~9KB4WK10g9(o)wyyGS{>yAWJ_1@T@kI2 zlcEA~M7r-lRRFS~QMO+9m}$Kgk*i80s_~p1O189ET(bC{ovg(^UaC`2)>E^u2s*Ob zD>+X+*L^|ati+kB=3Y`Qf3K<(YnT-8G`M5uYsV6IF3Z6UAN1pX)vi@vh3Q>8{gSczs0_ zgJ~hU^F*T%D<>?kDn?bE37?C3&|NPzh^TazPd#Quc~rHc>`Z;Wsdi6w5KZ~sjH?L0 z$nR*bB^~mOib@qVM7&PKGs-_H-WF06dQpv~kiGgW##QU1Dv-t;A*#V5C zQ(0u)9V-v6JIMOcd$rSp@r%n7`jwwm6+py+^yG||w5t4ZZLnKa`mI_uAz(%Ano(KX z=uYWq?v#I2G%w2_$=99YQ7%C9P=l=jdxW;c-3awZ`9s~mCKM)qsWEd^8JW_(WcjJI z3<8@v&)fM{962i*zLz2sT7s<{(O+evpwj%+ox;MS$~GvcsGONH<~8Ye9gfq=C?BRf zeCn`Osc`wCviX+MO!BlYR&zDNitleL7F9rFcAzO59Mi-pWHn@r!ZHL$j$*WEgdG@x?z>VBN1MF4Hf1)(aBV$9pc?B_j=zfZRb$+o9?9M) zu9N&Kho(9VMNqm=S=LCo4KYeR>0?diS{z|_W=`wboN;tyZxLibaY-%aQ)7s@EoH1D zUQR5VY?w4w*j1dZ=0yluC|xnoAjT@sErwII(y}hP+r2k7YzXglXlpdrFV3;LH)a?k zQr1d$I3#nGj>s$zCkya>a{=ZIyDY)obPSO$ISNzYa9pmmhhQ1wwQu3w5#bl`6S zTObcqnHen4JWAplqgf@N%*OX%_AP>rVsm4xgIguIb&a8|1lg*ZF$xkbzI)#18Gl{X2 zzm~;S?P!EL6j2G6%)s93?glX;(kwBv(l~hov4p}T3-RZwua2JkAoMJZpgYN;`>%x< zg-7MZm7&mIwW@rva!VUIQ(14_F{C?gR&%}4c+z8FstL^gFeFE{jq_+v_aLt0+b%v& zMr+2fe{0BMDu=O$3MBUi;!OEhc@R|@3gL>!6GJkR&#KDP^I8-E&!m6KFDWObtbuS|Pu5X2 zFRB!f)lr^KT)xhiC6dn)x`|kh;apFdUS%iNqgVUr`^FG4?Pcb4Po&u0S@d18knoxA zvQvf7aqe(?hQGQaX+K9Q#@ZUj0gn<5ZD9PuOFLqRw)ryW(+jfZK_jBI!#>>VD=KVpq*QfY&f~y|p*!A?~A$svRM=Kk(pT69~ zn&_@Vt(l@K^?4`99ibgP(cxZNc!9e|@1xiEaUbau;Teayb4R`2#rW^1J>5;EyHJHo zk8#ZXw7HI!73V5~+RXJ2(f&%lDXx@^snS7`qbOE)#3}AmC5GZXMI~!Feh=l^6$+`R4-)VvGjs#-(wrjV(4czG|eCX#D0T+06{Lm=%Cm|0+F{xz=5wQHEO?Ecq+lvm)JzMyjW3C<+ie)&o5ir=dD9-3h4~ zl{XY8q-f~>@4i)hrASq{Lm6MO=8Ca3(mw1J8=*VZ#7IeobU&s1sZf1%N33K>G3v%p znsjfQssd&+D&_UZhJ98JR5(T4mGXU2orkKV6@e*FCwwauEFM%SRcj*NNd1WFqlBfS z3VLyxBN(Ufmnz9r`5`v%zb8q_mME^3haXB`7cv*oJ$8zdC2QgU)eGgyg?7ac59Ugp zI4X!>68#&D6aZ%1bJHJ&wIS>4ChCdM;lgKORof%BU(Z<(Zz{P}MT#;7@@tAW zCBxC3?qZCi3L>?u_$=ZoqM80Q{GTx5gQ}sPEm{S>{$9KOCr4& zf>IQ&`Ov1-(z7R2>81)aJyAjtm0~aDj1*O9mOJpP@(+Y9#sDu#h1~DrHNwdqYS%7*OT*9Z5KT) zE2@Oj>O}P*(LIEUz+^3D*ZyOcM=|5N&t3RlF|P6&$~}vPisngAQW0KP#L$7BMO=om zY(f%>t0Uf5)vU_sNI!KCsnCV0r^LcY{|0kqRdZ-Y^h^<12VF^6N%v6^vZ!rC)~ zirU1}>p5S_Y-vuF4Xgw{E6=-nrL2wemr-4*sy`$Pk)=}RS{${w8|8QuCuy9jqf;Ck zW&6b~i-lE=S&V_us48IV+!bb+47OWkL_qW=V)5UDa_%Pn!_p)l;cdZ&?9k zE8MFXvl5t7b-IBo8qb9BB8m_YH@3qQN z7EvoMS!;oO$s+4%H_AsvRqJ)xFS%7!zxb4eHJxNEAsyGR?AgiS;t!%eqDE*>({)lgo9Nfa0S*&F`5>%M20J-C0^!PSp zrUA1MRq*v@E+qZZB;jzePO=KRBUJg+2qmbZM48`+OA`;T`_kprRkx!^M_R7hE8T%9 z{fih?A%`f>BLAiP-(|m|%(tq1n6y`VeYOQ@$Rirdx`layUk_D7ik zAqw5mrK$;`9`Vh}YpX^=BanO!Vic>;Kr*2WgYN&7 zwUXr)MiQeIWuL? z)mO>Ba_91*VvO{hqGHHYO@0yPl&;BcNBnP8>ng2OY^v%&MMKIJ3EimTM7d)zsN>LD z#i-HK1>|XT*No24Js3J)_%On#s*Y8ZEo80gHsvT(rz||K433^npxlX&vha&6!#wPV zu%FQXB5;nLH?ocx<1m}DQ>xuomP_{@D)LnI&kp*d9_on$niW0yZ6sHf=g@sN!sN=y z4q+{Hf0OuoVV>xYC!spAI?8XVZdP^N%FXGC1M+}TR3R=^nL%x;nG}{0`z-7!pQigQ zA|_6_M6)gl6UVB%ZNw|ecS{Cn6r9Wa0 zR5vdviE0<5#j=ReJswdNx1Rc>ItIzIq(|sd@+dA^w)(%S(g^8JVCGZ@qnJjKxMC;e zuC@NkIxF9xC|7t*xe7&sOZc>mIXb{>>8`i`o)Z+^CASJ&ue_A1=#?u|R3fab>U=%7 zLYwMrm$RCB;*n~j_7nL&0w;9`@!~_ozDHT@?L?;!u=jrUDOT3qVNw1~^_!bHU$r@s zwzFOg*1S5Jea(SpKeLzF)9hxpG3%O@IHt6Dn`xStjmt)k zk!}28oH9N$zBE394bU@jl)YOJN_v!Q(9;M^<6Q7Z@Kf+n@IbIJSP`rZRt3|8{z3bo zZO|#`9JC4Q1?7Vx!L32wAQoi%XZ^qYfBo~k|H*rfZv;u+fA)W6`@#R(|DOGq{A@qL zIW;-AVbCh*9Sje~2XliR!BfH8Z0`j>@_e_7p!q&g$%Uor=+1N}oKSMSK zuv%VlGB_H{V%%i{$N$W~%b)5u^lkrR@36PP>*v+-QoUc?x80}Qd)-~`GIzAw+`Zj( zob%4F&Zo|+&fCrpeE!S%*7?r4;Mi_Hw}4yBE$`NFo4UQ-dG0~?HTNgi@Je~@ys6$^ z?-lPa?-swMKi$9A|H&^F3=B30uLjw0yc3y`kBv;Tx4GZ^!c4c?S!=A9t#eipyBA}6 z$G&La7Hbz<5qlx_Pb_b|X1r&7W_)k_+4yJiGx2}pZv2)+`9$?ZwM30Xy+pG_i$udj zl|+$5dg4m_bo{yavG}g|Le41@x8oOMU&kJcEr_*`6^~uCkK2Roy!NNoPOFpUm`|At z&GzO^<|%Y-5L)m_u!hwP{Ac`eeqsMR@2EGytLxqDx$d9tJMLk3n%jWcc*R-g407r^ zw>p;NCr!t63|=;$&nJIOev|wm`A70vGRvvs40e_}cRTMpm!14>6}N@k$DQo1bdR_n zxW3!Ko95l?{ovi^xAn*P^ZlLvi^y62piVG2*c*Hw6hc?WAUUraSB)$&;%0SbYmT|w zeBS)ow5(gKqE;oVu2sjXY?b9z$EwG-hR9O~s}1iptuj_V>xTKG`LX$m`LKDA?P2o; zK0jwZW*#%Qn~TjU=4i7A@>tz$W)3hXnKR8r=6Z9hxsKl!nQJ)a4)a0tMe`H$cQa0l z{jJH?6l|_J(*Xz5U*U-h199uL$xv*+1le>gW1JgO$`HGqeY<_iT5grFPGD`n z!G{zgE3zw?9CQz=1V-?_zud2crI^VKzU+>7OSor|?B|`Aoe$ZbayB_*oYv0m&JW4E zlJk<2k~5R5l1Gy#lj%+)r=K&WH<~nnnCC*+n=)67!oT8u={@Zo@Me3RymHuuR4>5V z<@2h0UA&RrLM+~K?@8}5UhiP}(*0`wAb*$tg@0SnFW5$#e+D;!iYDU`zc6k$Czvmp z8P-7SA1yux#JQ|BhdcC*yV^ zD^VbEOTuIC(fEXTwRleK&De%mk68Iw-k8hCkK;Ys*fIN2tA+KcImS#iUj`laM$T^L zxj%2xo4)wF7wMnpJ>gC9DtKqH`!Bh#yKlMAyLV%y`neTc)BV#q$r|79JnX!Q56E^5 zH^VK8EvtxMNJqa);33Aj``nM2hl*ZTW@bO_z3RR1eeIq1Qv7m$Pk*!jk)MgbofjMk z9t&Q?JAM=V#gm6jkVH>#&eO(8o@cX-!2k0)+!&u3NMu)nrFyLxOw>~QSU zm>Vk=uNQ9>?-CyxpAlaj-yeSfzw~ze)A(2MKjPW(6ugih|C2p?;sfIa<3Gn9jm?SG zie1KQ@5d^Zw!gFXV@U&Z8{_>6>+lE&>O}A__H8%bCZAW*8|FQNHhkqR^DemU+@nq& z=f33f{;gV%!~Cecl<~EZT>WWpx?(I;V<%!VuxcvQ>@PN zU_O{?B%fvmi$N_HgMMUi@|lD1Nw1mjns4FP{xJ(#L%4cib+k9x@7bw%`FXLuv8Q9F zV@|ASyjHwTe0Y3w{KfbW@qgmi;#rB>i5`hTY(o;m61@_&5{bk&tl#8#x%h9Ogmq|M zrPw9=8GD{x#XgC5O0(|6)}I7Tmo0 zsp(X5ZgI{g-%K9H{xwbJP3GiY%gxRWa`Pt}B!?vzBsVATPQJ{Fok@NVR=AMNaH_MK zdz^2a!tOwKhx>;6x0~<^d-c3g-W}-BE&c%i7+ADYFe*3{oC;iQVs*UZ7UONhHJX~6 z%x}%xtj1OkYb2g|8os%o)rHqA>rU3?d+U;wv|Q_&^{4f*b&S0oS+`8R)*En+GBlX-DEejN7-xbNA1(L zi&o8uJ&UEQ5Fd@5`v?p79SHAy+(3)+Cn_cSB-SRLOq@!bOI%ERp4b7_yBL2kJ{Dwm zDfW5nwb(EgtlHp>^1{?|qx z;+hra-FUHknftqVJp>~A+Pr2Kuv%IZKvf&9#nw=eT&i^jv~~o%x6RyQt~TeIjx|{g(ZUod&|_4H|zVmJ)9n zp9T_t4v+K|D|L>Q%4DSqgTGrRdM5_5jY!N+EKN)UmB$kA#8=>*zK!jRO~X4?26232 zpCDo>ZJz-1|3!q-z&r!XP|-NUJk6rtsp!`fzli_7H_@}b2i&o4LpQJclk<}E1T+7n zv&)(4^l+LwMV!BqCz1z}hp>RpvR;2D^E=hhz_LzBtYc}X5Ll$TGt}AUyzaP8Q+JNL z)4d1pcFs*_waVhbR}-IH^J@9?kl-A@W-tQGu@xPEHTW$^AwHgly>X2yW(Sbo6mzyY z%bW%pTZGOXF`vZ#{AAjor7FnW?N$az`)k(gZdPli+10Fu^~-N&nKAP^(e4AV2W1Qg zMENUg^cZoX{L>t+*deGM6b#JZy#G5ol#4Yk8q{a~hJr(<)1P(0YF70A;48dk5&YJ8 zBI$34m`j4&S0g)bp#L|p01d2Bpy?m1!ge2fBhlgSpu$?Q{#e7^Ac;R?x5n$o+s1px zr^k24AB{g2zdwEtcJH3}zW9##q4?8ytCR7c<7d&n`_aTwc&$y~!*A@z?8Ejt&|zut z`cX94G4EkM{s2pq039v~Y6a)~C;Zj^NWZ>+85~!}`@mgAq~kg7f*STZtMOK&iGw<_ zE-n`D-Q@jvvI)s4$9?G{SRjVm3bteh#h$1;C1%K?oUU7jv`urg@pzx*B>^-70UD zCPJz~WHi*8XKi7g9>Qxq$op1np*6y4PwZ5}vaFx+DzBI)%tPS${#gB@W;Xo7%di*H zwyIPQC4)1;b6|=|K@TE@Izfe?G+QNDfL1}*pc{-rL)Nnzyg-wnCkS#Y+J6AM{SxmVKL&i8P*Z&ODoN;%PLO8q8+zCWF=#n7_nuGzuNYkmkvRLVGutv`(3^~7FO3~pf0M}tGId#`(2iG6!|&AqB# zLC^Gla-T+jR$@CxppS)#`%8llD-(GQLbDdRv)l>%(id+y&Ry#sb)R)lyS7)y>kGDA z4eEHwJL&!ACB1^+j`8pgpZlI)KIj8dJBU?!F8CNkXu~hHB<5WK1NI~<`IGUFaS2(s z%)qz;9{h)YfRqngRf(;PSqOBIFl{uD2NsoW@TG~TUHSb9toZZ z-U7Ae1m%ekHo!GKZ=5!AjY4K^_^FlVGv*nPqYYMSfG3`a4S&M=%=!adU&L-fTs#pR zxzm1})%=!->kJ&uulB$8RXTrjtQ@wrDt}A!suF&>4J-LCM|}wo@{IkY{W|#bd;5L+ z5%A+iBJ75w$xd4LS#yb<6V@N*XXdlS_G``QNYt(7*D!4}h@NwT53%+KKv6x3-%^7g z{YU*}em}oDam;t#Q{HZ3;Dz3NZx-u3(CY}|DB2>yo5`V8I3V#tz{K5O%bG-~d%TFafDvk|k@Avm-`0HRZcKiGN`~7Fp)aU%C zc)bVmGlJqlvtS4qeKkzWYOLGlU<+EZpGg1p;8RxX_uz8i1PO9A`NG^=^gjv2H0n97 zjq!F>Kw`zflx6UH%1VJ{7avjz9R*55{yy#3;@gR}q z6Vx)jOnmt>a+byH6+r7N6LI|{vRL{#h3?x)sc)&?Z14_2xv)~u`*x4weCnF}XcoQUWJ80VRc zsU}&|+~713&|EB0`M^b{@AT*TL;ddX8#Vo+emYwHt@k|9>q2jk*BAz+Ft4ga9QC~V za3U4G+lgm=uuQ6#>7{vb{+9F_AsJn<1N}H>pf|wl&ua+W-Zt+!V&OC1kKXUzWzY9) zB;Xb-M-#uBKY@tv3E0J7;7J3JP-c)PC=k@Zt4+dVY(blz!DjzNBxbY91!0rRv$}e| zt!iBdGah9eR9mHIF6((Tx}#Xn?pJ-Do*AdAH{F?}J4W>X5R`G(bB^mV4^fSBIV9v} z7`<||tXyqd+MNR5zJhk{g-d^kTCW#~!vDmw7_56$v$@&U>;&gL5lwo~JY^=$Jj6Lg zV7xAyg&$`3wR>`QXS;_z$R2?tw6$y51??Q9>JF~i5e_eA{bPQK zkJtzsUe%1l%-jtMs7;>wH*EKP#JB^P+dRaNZ=-Qj(7-aj>Hp?^4!iXd9M@iNlef-W z13I0`I@Tm+iL=5tgM~noGp>g^;OsSd-zzZYzlj4*B=_FZn-WJ4+M24aB3Y!Z+_Gia3F0e*zzV1@BjY zwHU#QDy#VbvHXMh)(42epT%;2Ow{%`Z>0|3l6^XLx9L5&T92Sh`kbBkXnq zP}(?iDR}KJc;JuBU(HLbd4lM+9C)A|api1lHSz5|==fLGZ|LG#>jIIKi>5=sz&BN4 z<=fj$vBxdY-#YM573}(;=K8E*Lw;+8AFp5+)n@*VzCe=IuR04ccH&&5K&jTF3swSL??hSk2?yT`lB+vRP*hA;Fcq0@ap zRnxsi-U@G{x5qmKf3u!nW+E+GZ?QRV64!hUZ(#Yi`xX5fejV(53%?T))o?7#cD(tW zp{;+H49Pj7`YXN}+)SUE2ZNA<4e0w*c;P35$AdS}`WqnG46tk+SR6f-UH_j;{~=6I zD%giyyc9~pFT{;kkbu8H0pEg`K7c3rfIQD9%!6>jd49Xjs>VS9x3J}7D}Y5TLyo8h zTuv`A``8e#Pc%ni%U6ThAHYj)X1l|D1iSu(`Dkd}KZ4~r!;E|QZ-O484I-TRuu!u9#=p{-W(K zz;~zF-k}Fu;n(-#S9X~5kiJPo-=mPjmc(`C%}g^F-sCJ%;@fQRvEF;g4Jy|-9@`k* z*U^fV&rhs(30wa%5#2rH3f2%OECy}%1bLRi*EbCP{lCb=C;s#PJ@5yL*msg+m`SWN z%kS&g^~ChIbar{yCa{8q9Xhi~9xq5`HoNHvd-M zD|23bzcso)4Ay2KnUbac4*vj>cPHB&@B$Cv^FQ@ZgQk8)7Bb*bD-qLl1Fg?QTBj5H zJQ#c#TK}{#?^pw`+!7DnmK=ITN^#uItr)bhS^Ed4Im*yX4fKAV|(uvlTJt+of zmCoAc$E!E7nxXYo@$Lo@zCSe^LQSj*UW`yp0)yvcv@37!JUUh-=gKbRBj z7GU#9_AG0c`6aCLn!pZ@`9rXUO&IZ>;BTXemDjFje{U_ZN?OP0@7G2hIIk0EbZt25 z*Nv5A(0(WLHp+b7xHstJUvqB|9sl9H;J)ph@rxV#%v|e5kizHI1vt}l{)f(hoLM(! zWS>hG55`)}Vg=*t$pUPO?}+uW-tc!PD`mfN{j(cx?iBxuH7t>l+9vf>O7)a{iEH*- zMuzu7?)%wSvzsL!_1-a-fhDS0jm)9s!M`F;{jhgB*)8X@?7wr5dIQbh>>pyYV#Vy$ zWa`Sut2KDW|@qUR7@jL9MWWlu3a)dg-nKPzh`BeHLQ7dGWHQ%(~($n`>1&#*zCXKjrLx3XFGY4 zS92S>gN<^r_VI@H-e8rRaK26saC*4My+T0?qmtRh%8d0$bWZ&s^+uwV-9NbK?slK> z)&=*O>+A)wk>rBc##+W}$A7Z_HES3ymhpyv)L3E_v*T6}H1^wpfGfc1rFyS}hU)rN zjmg%ASgFMGFvc(=_DCy_b=J%_TU#$$-QZeI#oES;#7D>K*@w(`gRcIgc+4q&_h2{w zvbUCfbL<%y=?nH%W@WH@YwnsGdvBCWjtt7!d1D>zA=Vw{n`l9*aV$vjA9Ly@cP7WV z`GT(2i1>?%S@Dh5m0*>BgpoIK*SOWZ`@IVOv|y3hiD=TW2b#x%@xJ4AA*OAGj-3lK z%>g*FH?VSj%qfN$EbzW{_j_0UQ^5uFZUw9HE^%2LOg`2h;GJ=sdsn=9`0k-tl)6D1 ze=3MD!~4>mMDAd(yWV@oe=1P5@oCteS^mA=zwiKC$fiEyUUL3Ou1$`0DtX`gijP+W zWdg24{FmjQ^H%z0f&u;_CtuE^H(tbO)`SO&Sz@8eI#e~hJ~ z>8Ii+Qu?H(rVNYCH7f-XjXUfMZ&nH^N4jLc3RgyDu>nArl zkGfkK*RR1x=8txh_=VU`>lOduK6yIlc=jvV&*c1&`!q6K(QV@m^J{>Y z&WWw{^LqoF{>cr=XPnyJczXC?P#x4;0Vf;X8=-A4Bi7_Bp!Rlx1x zl@1nzp#A_~rx{Q9&$*|PALf3VJ1betdBB_i+ zp6;YiPhXpMYue_t#p$dz^2QlCpZkiIDW)3oVn_ov;OK0JMO z`t0-v(k7=Ivx^5sopm{TvdiaGP2TAx&5DUvQa?@|ny72_^IpuUaQ(`aQS@(SMt=#;%R_>(Sa>+K%+ipXuL#o0Qf8amu_wcuR z-@EU+@GM9n9!bH=)2pNqW^?~yp1cpz~k@m%6p@+T(~ZzZlInxu?M z8JV&zrCI8%w9nJeWz5a&k-0BpP5S7xd8vL%)zp-=Z)tMm0ZOX z#`}T!p}7G>u-e>V^@_Diyq_{IH6``uM5p-u_E6Z|sn+Xu!T9^}r^wcxjMa%{+Re=R z!9nj7vF`KUS3Z?tMlExt@rQrfy_8&%J1l2P_NO=gxbY8K8aQcK?;S9rzk7GOMV#fi zC3B`_ug`9oGc7l-bDLM0esnY6GsXokdZXPpoHv{b?k{edzaz*nQ{Y;c5EYCus~Uw_ z=P!dL<`}z0{MST~GAy-7>VcFSi9(4Rv45$Qx!Z0Ys~3MQ@mp&Cj9HlnGylx^JN;DJ zk<>9M2NK5ngHl|<9sF1I`Ud?&@#`^0=uD_7|fy4hwr2^-wxg`kP?#W5nYp(r$ z`Lm0IFWhtfk@KrB48Jtv>genS&YO6wnch%ztKjuzm)Bj$fBxwCKQ7+5^7-|g?CrT< zCm)2-|0~&y%;rR=uX~^Sg4@U2>3`%MA)!Qfzmf8zn)q$+b%S3!Njg#1$_wm@p z+`p3jbI0b~oAZ9|ElxSNs8`i5M~<;_P}u+8eaktS{4qBx_gqd|a+rI2kRYB~X*3De z;D0`Vi}>94{h2($?FV?pCgATPW;5fk-_@(;HT6#h?U9dRcGFmbyyMq+kMGF)X4uWG zyU6sEu%5Da#5J}?#c8r7v(9Q z^+8tAyce@(Wfjfpko9xcqP+L#ZJ+m8R>7$N zr;q!Tb4zka_D@%jUHauh$BTO|KYwjO_N}=O=YE`fGIvLAiQH9g`+}6}Qbc3r)HNGMX z(1F#w%c<(T=2Z0Z1o_Qhta7oJ!Az}VyTCD*%%nvAlQY>weRFH+_H8 z+M9mLmy`GHtPy#NX4cMFkp5~~<+LF6v(&jMrDJ`A$;sh2+FZ@JT>a9(%VV!q&FSe_ zaNl|G{g<-WT>tG#xl1R`2WM-at#cl8fEe!7=l3`^R`{>ZG)lY5P+5jzDe`|ha6||37dpKqX9Dzgh z){s2F6MkQ+EFAcpH>g%x>GuqHs=nFLx=el97;CA?UA@6f|B5@%DUB!U4+7cZEe^_4 zeRYj`rSY(rCGGcN$qHI`n`L1pH=En7JM3|>lJT1ol~d}Z7EddeJ}hHFW>%hJd4S7# zvaxShGDc?f%lJFv_sm#Usl1i)1^H&&G~lM^@;#q-U)IPxxf!j~*QWMQsgsg7b!^(> z_?clDv(l=?|Mo9tzjJx)`MPI^p8e$fua`&OaB}~0inw{5DY-9am$=amd)DXTfb*-* zJ$r8c`KK;?aOsb$BeM%6YdQ}l3*|P?K7Rd^Yl-Vm-uNRo%Rgtrks+3tq% z@9ZKL_XIL~fcU11d|p&4KHg-#Z(byBYU@vN9jXzQ zJ8^HTe>yPCI#yRI+dc>PKW@g2H2+O!P41QKN3*}pagsf~0YO|y+1?xbid^B;*cd90 z`o^x|W!|s{!~)RSl=wsO;fY--nc%OHnI*Hn%-iCo5;r%zdGAeY@(s${AS-{KTQi@| z=#u$rp5b|yP7^$}N(U;e+2&H`GhvJ1nfW99}eh>CQVD5a=? zbf*%6ga`YHeqGu6%7B_+?z%d_)%<=JD)VP8EEqnvbm!(eJwf)#y7=iL?- zp;0gCd96>wUaF+;k(t!NOQ^F#K#fT7W6i4XHnR^MlGfb%yqn-qR>x9yULZ6pgA$ zkCWCWmrD5|<#tM?)F!EAQd3e3r?yS)o!TS|XXl@#J>Qb9#>OYc*|nU7)RX<=XEGaQ$mtc+ z^t3K%@1*~n(L2^9QPIBXjPN|a5L{?aJOdBWrmMng@Up@vh)W6Ig_DD^{z2-mcl~~W zCTtW-$rY4CN{afrdQ{mj8&Xv*DeqG{qnVy~6tY;;HZWy7^X0cnk#SIwmj(z@#O1%` zYjPPBTkX+`l+;3XfVxF-QF`Z=pGo^*DZdjtQ}bS4$XPm}+z~ zD@PtgMnnrH-A~$m^N%O@NcQMUY9-}O%AFKMXGAYW zuA7sLW!mdX3vodZxa!rIhDxXry z`^qQE5~T!3eVk=rmbBq zN_1i5i5WNk)(2`Am50)Hp-r&T9bw6_3u*J74S1UMX}zb-pS4L#WVDE%ON_MES#zwa z*0jX6c$;{Ec-}vK#ws=X3z}hWCt^1|?K(44HD^2Ah(m%pi zVXojHyYMe}hxbX4N9ZP2hjD8m*X5;~U5<5$*p2P8_9vW} zQ_f~??&D4uw=B2&Wwb5p-Tyc({q1V@ANDP0pjRVkE^L3DSBD0mD)J)Wu*f;FF++HV~oNjgZ1NSv< ztX}R!?~Py;=}cF{gTYsRDK8Tg_&YmpzvbTWmIXfw`=s(}1AUmW$>?uXFa%?aK2z(b zj#Qq=bLfFod5`oRuAew4i70M?QyM7d<@f8jvHlDKudrLk-kdleKOOIs$YUM0zO@HC zv)lz<6MrF^|F!;4C?9h8>0Ta`QEvuLa3#DboDwI&svDUbZJB&Or1Znb_kVmKJ$9&9a4{KvZFO{66*(=%Un@_Q{ni6iJv{%wA@G^QN)HXldNn+vu~jsp?0HAa@mGL2kM{7mLMTVMn3{5|#&Qlu6f6&i#2wMC1y&GN%`mn5^Dcw2MZDHJ}iT6>yzAi?E zQNb9mne&0QD!x3{F7}+trM8{n1a5z-;#Ft>-$x<%rB{$X_$8D<0nVP{be&bk=&RA+BL^dD^iHHyB)?hF*r3nU&*@!^yyjLj4zk?c%wpa#{xL=v)AS7WD%?@I zu#i{TI+C&V+2zM`A32X6J*kn_A@-Ji)cqS2s~1ei_w42T?l?QiDv~%C|2Li|u`lt! zYU)gPfAo6#p9U?%`#AFIqC9v_`bB=O^jDWD1EeRsLCiUH(VR zCKL;*dIOvS_8d#HyE~|r!>{DuG~alIpY==g4P&CVOqnmu7EV)DX9Zz6O?=nn1)y}-c=%M+Ed+7{h1Z1Xs{L?>|IbLM(uhQ3S_v=eF> zt(ZQ~s2#ZqMwv5ZcFK~J;wiP0-%Dy9og2v(*>27PW`i_IU*o@NtPxRP1Od~A&4 zwcMz8)Sqfmt+7&HJm!^1G)n9E_`L^p?|=1R%;Ve7m&7)J62H)&{?J9{NUdP%F z7y6BGTKZ9$uKuYy%8#-q?h$T-tCSBU{{WS0W#?`B!_s&$cDRe(KB&w!Z?b?)PO zEf>mzK*^L~l-t>^W6iRfJFof^g^J2Ty<%i!^lP|_73Nc;gRx%!Lz|-ZP&&vTNiY4M z8NK1?_N7GQ#HWd-RtaZ@_g7FDb;A&}sW$`NALOh_)Q=s`sFkrGV_fFnv9}W&EX%&* zjBo>Yx>p*N&QKJz*L?#vX9((s#X^=bWp4R?llIf7c@o2X`2iao(Z z9xDU19(<=0)y-0YaDdy@S`l9#|J-WrZVR4?vz0Hk_IeGyG4Ed|ggJi;E5h+X7r&5q z1zw;TwaDwvf6k9EF@3#j-cSCKV2sd3IxF{3G-ao>T1X0-(S1En6tXtkeLOe(PoAJR zj~oGc${oFH9yIRhP5FrjYKPRl$~d`$)Kgr>i}Pt1qFot^Z_nnwYK^kt3p(GQyv+j4SY3rVrds67h(5FgT>&zXAPo2|l1U7oQbIG2>+3xSOcl)6zxDsp$ee}W|QCJj5 z<656er^rO! zsJ=!mql>YZ|6`|l&Fo-m#!mGcscW#)emzzr?YE~3pD0gPJ*$wO5}S}HZf^#Snqlou z6ieKXr^SmWh9>?=m{v|JNZe06OT1zYwsP32)7lNZM}Z`kkgKWL^gc#T^SrTA-=?;Z zuLuW&zBs;b`e;tX&hi80vRVaxV=ABQ-+0}!NS}+R(PO_8<_m6ki`|LNAsDT%?JM>` zry})rXMYuF{CpUM;X(imL_HFO+A}VquTJ(@C}5*tpD<8C5eP z8TZo@Afsj5YB{r?qhcm`t|Z?;ogQ-|4KL@7L-RQ zE7h)AFKw|}PMINnD6aaydhNK-jH;>@%FvJH2}(|Fsh)29W7ddti+lxV*(S2dTy8AX z75$2KRr^;vuiezHX@6<=v{iaB^J%1R(w?NYNj;-YBkjyzjhse57}cZr)?DRfwLk8f zZrW;Xk@mGVT}#o&aH`v?N5nhcE7qhGxS7T`!}aL7)L`~>5rY4 zaLWAXW%1K+XLfQAJ8SU9TyZaZ&-~P|hA>9FCiPZ2YRinXk(x=vllnw!n$@)<(ihGD{-S71Zatb}@mlmUc%qo2(9z>CIL&!%~Lk&R-Kl7)!2kaKsH;D#`ZxW{xsTTLE zwZ|UnuJw)jHkuE7cw2Jy?dIP5lUL)@S{`_NT6Z+v1YUNh)-r=46B@Be7(s%M| z<)Rt^VP0h{Hy%^T14&`^0mz4GNytzT@K2`Rswc&eyFrS+j%|_;jR7m61 zx0Nn9&0a(E*%HO>Q28V^*;=)N&YLVgrTzD;{`02k-D4Z97Vc56uzvy9Q&F-8CgD{YPA|9s zb@oW_UtHAHaK}t=ue%@Ph&;OzB($3 ze3mmG#|JZo9=0K>vJ+H%fw*4UgYs^>@NY25OLFEW=EnwQ*2_Gb`9thPtYfT4X7h|u z>6KyM^~_T7W!5?8Bd?r46F2oVRH}thAPl4{9u^cw54T&GD~^%|a^E*llkf$uq+^OJ zL*@6S9jN@aOJAV5E-cH^Z^9uI$P)xp+9iLWcGhz1b97fvF-GX+wTVhq{Lv4o=RQ?# zQdj?`Oi)y{4XUAXkp`d@<03oQotC*OVnk0wibndImT?3H!*wIwcusw}(@?ozAJKcy zHvd!`3rn1&c%h6_Y1`A*r_ajFmgr$War(K9-1|-^XA}6s9c!pP%b9_Xx}UqnsSZDO z+G&BSEktWo*{>Lk4(p1W<>lHq^M|OEbSJXJIH?|&?uV28cHS&6&0iB%5PL{HEVc=N%bDuJdfa+bT6_jRBMt;CY#mfV+6%qu^uweb?Ztsa(pOZ#xu z9Fk7UzbOHzU}c%4kQd1?(rWoNb*}c3@rhYDQjT@WVzkgUD8G?4QBUc}3f|V9qtsfa z<<*NC!_29XtWiHQGmqUc9X{^=`VTsq&bh zlKoL;t~C9*nKnFQQoOf4n7(GL_lh?I-{Aoqhfkboe5F20*=j+xAdbquqyM9yjQZ7v zq5cX4J-uc-^f13?Uz?=`PlAPDQl2U0&Yg9W9jLS}FVMbIJt zV^p*EaJPNxd+zrz-;-F!(FrU5V|-xzF8ZSB_B?kz2xA!()Jfq8f4^JL8Eqf7-*Bh; zgN2XeHN2Az^ch-PwX`w`reTQ^(WdF=je%6&Ee%~?qSjS>Fw0XYOTU2$tc)(3v>`Bo zLDB_rurM!}>`ijkSbE}o?4#JMSd+LHe7|UA??1u#(%& zPUZx&0}OB7q>f1kqJKu_nd9jm8|n49acAfY^qu-+{SUpp)==ISOt)W(RY-4=RzH13 z=Gw#<_I))!lcbP3?(a@Jr>s+z6&>%c@Rs=R!FqRr5sd^1l+a0ZRllHfd`lQ7_LCp0 zV~tNE$0OgG_qE^T@q!!-XE#6b`UgdX@nRczggVmqVn@LUAEBt<>i-1KpbGzT?#BB6 zxt}-{z-L~u(s(gY>b0kEuD- z6Y}@0&MP3wwz5vsjs3{65K)MQWdrHr&656k!O-1j{?su2fAEe&LWq<^YSrDzA@4{keKf9XBT;4i^=`f z>soo;LUD9b`$PLy`xwuC4sDeBgj@D|wH;skQu9#awW0q1%g7ZuA2}K+8TrNB#(E}4 zYe#QKl*oBwzJ5Sk56@r1_|7O`RM+3o?ke}BtD)i*va-iIW>m|Vnd!$@*jwCXbnkz= zPn@Ps8RvVaAhk~+Z=C0NZRwp_`>&7`^Ag^*o}j8lf}C(FncR3~)yrBdqZqyDbiJ2) zPZ}kx500XLzZm2eii1Wi7mJHyQE5#;RsRkdQ9W=G9_A$}9To_-do|n)`w6}1b(qVv z#A_%avZ4;$;3na>P|<$MenGFQyU5u9evsF9gGa)8@cUnsNlKJA>P<9aMU>X+TJ0~r zw=u(LZYDm}{ncdV$>M-pzcv^8YW?VArn^hupqRC0`p??`3agBX?HNBm_ zQs2kf><5DOg28yK)Rtxj-JC`7l9^v-G|Vg-?`2JM%6TS^!;8EXf5A)ScT2fH&?f}m zCjWK3Y}JDroXVnsip%NkU}rEkTp<*bp2~aFPxR?Vc5{Z|X`__wVk3HqEU>bl1SC$O zPI^w2{xj~r7DA73H<=TE!8v}5c0M0C+FxFKx2*HAeah-;rC84rC9P{HHYU0i{bj*W z{0e7-<7mbI#$8^}J?&QWOQ9YrL$zO0Rbhz#z~MI+g?I&Ss@{5SV>0_StG-+H+B$kFNV9&2;>EXf%>sZG$5wGm_mJtHwFzgklrOM1y*?YMSD z+pBFSZ=sJ~%y?=HF(;X&&1J@C#(GpXZ$|!%{EII0l$pnzV|-zZG-ev(jTXie{R6$Q z_Mx&G-m|f@BmQ1y(~R30qheDM)9mtYMQ<)Avawf~?3l86gy-Vu?u-{{75!BsT&B7G z#w6!Nxp~h76T-zp1opAJnnUkoxW)qGJ^fX6y)=ZXG2K7me;Bk5JL3a>lSG(Jc=~b+ zzvGs#hqq)QPV1&1UfccQUMV-#sblZ6+FOQoG4UYL%qnY7aNhFvz{RJAC%_@U^-p>Y z@iOo8Zup&0R*aOk$=@rdl(|YVHp8u z`%cJ#Z>*QJ1s^$zD`9vj2LFMevRLarDy7ybu)IPf0KSb{lAQX zu^frM)(G@8nmg3p>K>;a%*hSb+8-Vq4sQwba2l1C-p7yfE}s4aQWJc@1;KoaD>)P! zFK}l$4PTl@S2hJd9aDP27Ah$psHQdpm1j=9p#HLcUaO-mK=n9WeM|G%lOIvXC20du zf4r)_ua`GQ8%>N-##qj1_Q+3>ZILOF+>uV^R^ve;kCa!K)aP!AStqc__BG58cWNO!8szi_@J@k!3}WB$fq zAc~9Eghk<(!Cmi5_bZs8fp#h_~hYH^ukRn4BhQ zLlFK5ruv(`2kvpFjqN5%CZ5EP!$tlW+mTr=^IFFIj1d`EGSV`q#cNv|;RO{h9}cQ2 z)DB~DN`DXY@)&PJWimDf;_7>uU9ykU+F#1fx$KK7Xdn*%jl81&klT`9`kfrALST|# z2@}L6(nz_1@~+xSYouS&Z_`ccbgFH5Nqd3^+-98Yv;M}%nS?{3WpO!f^BbHt~<57l{nJ->7kwTZ$!|G)HVwJJeZHsE{7k^z)mGAx{ zSwsi%C6z~sJe8cJ{^Uh`jYoX7G)&HqyQDHYjKXqfX^ME444fV8sb}I1=_Y;5%VZf= zP@9raSb=wQjh@AjjUD<_{a4<_KlC5;CVDpNo&j1X+z$=(Gbpkr7)8vd=JH6%=$Fx< zFoi23?@%qZLJ^-7`NZsENP0Q-71Ie2$_2&ui#Tn!`BigQea*?<>Da@JTpVcpz56DbZCeqD|F4)xJ=V$#=y3 z!m8jWbR#$5rZs$o=|N-W=A7fcEfZelbmyi<8i4<-cDOuP;P>}>qI}v!eKgb>WnH)0 z*)jM+!7Js9?0|W2k)ucoScfC|Uw?WqG8`!^5RXcK%H@^(^a+zt6*huDe@StaGwO%v zPikr#)IG`^c?$SNf1H2&NnIU@hwep|=3RDJXEH2Gh82V6q|$ZceYxgdAo^k75$uJCZRNIHrKDR}0l;!wK{ z+jRk6t_<$rHBw%s6%K(t;Vmy03hkf7C<=};B)q8F5VgBf1lQ9ZQn}*ju1AsrHYykp z^q}vYglf7SKl=w{61PT)ms>c1m$Wmh|Bsi$tL64}4%&U~9`+RblHJ}Z>%Qr|LQ>f^ z{}#;CDKg)#`=x@_L3Q-j14ugh3Fl=YGGj(bn{ZE8;-;&MYRsY9kX2Fr7Zu!5Sg#2r z8hnF8at99JaUiNM<~&v<6<`lN&3YVmd*MGC&|MGWpL}FKe#`s%q4TwMAig42BKA_O zbF4(X12{)hyO*8UUTZZ1yUu5gvpU$59nbyR-yck4ukOYvISlV(7WDXe@z69w58j`I znDb~5cJQW7mFA1*(N`59U19=^^f1ue3vxuM04`EPd6`_j-;^C{ZhbWB?>hKe#+aLo z7kG4j{Vy1nVrZ0V8}p51^N2Y;qDRH(b@<88qidsMqpw5{M}CP60T=w4x>z*6)w(Eu ziBALDEowiBABvrior!;J)pfq|1_$fWA67w-3>ubjJz# zEZ7$=5>oJ|~f61GQ3$ za00DtU8=LMNT>4IYYCDl#*mu$0iN_X@F*{X9e&F{g~t6u%Z?X{pNZMAU*o$HPp$oS zZs&C;zmvzgYf8ekFGBv#^>tS{j5-_8iFy z!*SQ`A?N)sdhtI&T}IHwzECthlN0hb)bXTIq5Ipbozed@?wiXZHzTtmf!P2I*9J*= zO_%hy^rQM8#$j_oBulgsYSUq1bfiyRJSiCp2fGuDHb?{J-!HY#X;%Z_l`|JnH`aUpg&^WV(fvEGSA zcA6`4zf2PXQVBW<`|!{1#21_`xJ*Cu8E8 z(CQC=$=yzVIx=5Ayn`DT=mJruzU zRkCAuyTj>dYoG(&>%QUr>+SRp;gmWj9L7hKgZ!IZ@_BBW$#k*n_?cfJk#P$NNG;^& zIBtIkA(J&>sqEk^Vo{!dmF+Xe^$K~08&b5*|)A}ynBvvez6yKECZ{KwP_TMJS`(^I0 z7q3SxlD>+N3>A`eZ~|xWIyAOt@ORX;G&JvpQ6$Ko?UxkD^H64?S#P9G0tFa?!=qJL zAs9eTY&UN7=4kpprBlipJRw!%E$XT4LRWFIG#@9x2g((Y&%beNUkmzxBBi)>QFcAD z>vAU6xC3}^r}N5cF4;2I!OI+I7z5~Taxdy!Cw#@3zNmlAOoAo z@H?hR0O*Dv}dAsy0{NicpZTT;`9scR;s)ExyCKp6GaUAFTt1vV7N%;bO5$c%h%@t~+>v~?mdZMLG5k$7yoaa#Q{Fw0r9I9(XQN{~?cB3& zNAIcEi+ru3)MrJ=#rm2q<_|cMRwNmG!P$R345<7L(U+B_s!k;n^p@}iSr^&qbJ~*O zycJcVfk)(uG*7Aui&C8qtg&#C4Bms(;N5vI>ynta7CqoARA+-pIqfJix1KvFlPdBf z()cbpJDgcg9cK+{Wy$(3@iqGJ$<|E!fOEzjLB~FW+VYwE4Q$B6|0iWuL7%zXEsr0f zvVYVcgI4+r-lEq?$jZe{ybsOzA4+pFBQr?v45;*L%Hv=HP5$=I@<3&T+6g}KS2(9v zjrWX+MhyaVm4kHJ=!27zWKh~1;=D6?z{d=R0**fxW^hva1(zK*D zNnb}ZOwo9t?v}q4^Mp4&)m>o!j9TbiqMYSfDxChC-kap9?uXA`NDhs|n>CYcl71jT ztvu5`gSs`IcrURhzA63z`l5^WD7PV6cvt96EqDfP@kZF1&!i#v^)^z&d;!vN9?s?j zweJ<&{`dU~WL|6tPoiwiCrjim9s=*ZD(_;Q)5t=o?Po_}HVJP^ZKo0W*o&O!P9yRx zU-Ooef=~yA8Pm{6-(1I;7%Ls6X8A6x5ft@%lC#&|yF;z>V=y>ujHmlk6nMqKa8u>3 z;Ep@wXS#?j)ayPBybd^>Uf)!x+zLiP{j6H53Qg!N4>4QqV$jx(oHG9ybvZR1=qkXbUmB& zYq0gY=xDSxYe$sm6z=PI^e1$g z?ajk_sOrisacMZ*pX`KB)9bsv>4J~PJ z7@wL>Cwr!KHPIyTYN88uNG*GV)5&`;c$Itfnly=N3;R$DPnR}}H$bB1l9q4@4(N08 zl#bFtSMzIf1DB`gEiE+1J@lS@1m*lBr4aoto0qI z3htAi$)`AjD)Tjz$NxyL%ITcw_F_r$K2DLm_r2GT?>rw`SmR)D?1I{Qs65&!0WQ$McD2grbcW_W>j&ybtia} zzP=%K`}Ob}p$r)--ATZjK+Z+vu8_>BVr>jsoGTVtiwqH5!^{%$kuAk^Yg? z$UCN^chE*Cy`-wb>p>^40P5BI_7T#QM>~1kpWLf%VbF_myoY0g956Mvm@KiC1d0!N zX@Byzxp$ppr0l?wS6;HO76*%97*T`_mo7$++aqc0O|& zu-aRkFY((v@;^quV$y9);FOkUcZ?93+8#~}uCb45`8DVQ#!w}mBt5SsxOX48(>*Ac z-)0KNXxSm-q6eP!U*+96l6wgsvW^}%>m$DfIUjxTG{5ryDLVC-3iLB5*Ky{H3?=LC zAlZLCgVlb9_a?s0FK{{-OOK% zIdHX)l=|{`@mN>}=f(nf_S#M%KG8*_WPeNDIM&PM|3L8oZx<2 z4}$%!*9YI&GJgqO?see~h}1WDkz9ERXuBjmr9#>o9D>*D={GWgi9=#{|xcgP-V|A-&F3G+wh!YZ`)hL9LD*;`AdUnf?2DcH_kOUt zT78Lpi4XMidP6+e%Z+J9Pop~Sqyp4pwTw3WZmhAzkj;} zS!&zXFsr83!8&10zzej*J6d^m$Yk%y|c z0-CY$2G2qvY|@K{p9&n5f9{AWE2foUqd&OqPHL?q^f=$Rg6c)_BBq`2~=<8 zSnnKq2Ysx*SpQ3R;0K$)8BIq?w}<+?g87|!$h>RbhRJVXt~Jh~uuW5w)XGX%Zok^% z^Kc&d5);XWK906}D*nY2F!GJakZVq&TrO5Q%4??6W%ea|bdHx5#^(yP(gC}Mec4)R z?X`;7f1ysBi`L{%a%eh$La(4R)xxpN=eW!(_z~4z0T8AkWDov{!~HPIxh;5v=aZ3A z3odOyiAV={L7#ZFz}Tvjq%@!IxR`hrq@o}gQx>YA zhh)EImxoDr$%!jXw&hjoraSm4E`v4}1e^Ma{&Eg4_yVCJ)!lir*mH?Pz!QY9F$(1N zzRm3?c|+hVMmhzZC-yVD7+qM@T?j^!Or^IfXhT9=2WBs15sQND{Y7SKb+Y_Vg2fi* z)<^|AKS+XMRp}GD>f&-Bi>jtQ)mG~jjG;Is>Ke~Tg{i8?v_DZNe~aR@H@c0EAlCnA z`A|h|(zDWQ{YuV`iofEqIWTfBQZ?E++8Fe!cBHjg%cz1@dziXf*@x1nm3TI62A+`@ zz0`1~Ma}1PEDOp!1D!seJ=XdT-}4hv2ItWeFJVVC!1t8{H)=i*+}2hJs~nn&+tzRP zKTZj6B%SQ1_+-v=Yo(E|f0z5yAj?=luRS>&%8NfecmO(Fj_&$CoWjRpMKWD~5%Z%d zdq(A%SAHnXL_<+u>@6%qZ@rX^RGXC=1n;~Eo#jzJ#Vu|D@1f`T1z_u50@pZ6?!F;* z5ccvWUWfB&8RTYPc1BSULJL12^+fqoLtbU&mPIM@%r;jNeSrs%xX+DE>yNS{%)J zh?_2I{$@4?3ri=j=}}}$q-JEgx!?Fte**j1L%E7BcPR>rf4$3YE4L+C`=9Yjukb@s z+Ou-1O2g^T33l_&{Kbmh!IS;2Hv%^GC8w1=**c%7mN*q(5Z@lJMwj-r{lIz0YXoCF zj~eGbsr;kCou`o|n8JB}kzH7T8^j^6+NU<#gL8H{=|=@{=4=*5kQaAAs)#;mEU3x3`?_$us8ZLowCtsmg>PNTZXBkV%A zFNZVek$U)ty?UPP#qhe-p)#)u&oMjbh_zJHWOc`|8(55&1- zd2R)7?nK4#M(_}XrxdSHMH0?iajPq+2Mk>6w?KoN2=l{@!FuYC!*tx=pa`u&MbXu% z=ag_tqZvJhAGEVK-rtRSb}2W|V!F6J0iR=mp7}Kk~zVMp};wh`l>40`B$jf@@rYOIJ0UJYFTZvmS2B@ zdS|!U2M+mN_~WmmA4ZkvQ2ZZDjn8#W8=$O^b_utFM*hp*hpxh;lkS}Dce$C?dZ*Cr z^aSx399VdA$1r)~25Qrm{wQy?`4ieyhK%JZbftiSwBkpyjZmPvZy3!<0&j6>X$C~8?;rN*(g(CPpwve@p zhXho#n7EomnaW`uQqWp}yq*Q8TIamw8tAC=apqn|T~L~A*mmM4q{Dv5C+NTtHxf4g zUmM#KcC;4t-Er`%SH0fwom2hMXczWU54zHA`CH`}$@|Ndj}=MTz$@MuB5S<#A53#e z)VGz%A9#_Y-Ho2%15Ws7+*d`xzE|?@MBpu7AZ-;CSmQaaoSj9e1fCkh(3(`wOiVET5fGF{QpJV^%6{tO~&0Q{X^q5 z^Q74|axL;!v{&?-Xt!vN=-9|EbF9!T}{ z9`|c*5RU=URnY&!xDx*HmwId5RD9a6;@@h9Pxq!jjSA>noUgC2r&f^}oR8G!@}ka4 zR1+4$etr}jp#ILb$b_Wj>SF$KR>8(8Hr z_!EBTj&4B>^ffc2lBksTQNed{WP5+&U_5vHXzW>RSiDl=R^l`3iq)Kcq%FASUiWuz zpMQtcHO?G3S4-&)X%}lz37z3KdYu*2<@K2^@H6$=?4T@+?FaC6CHT3Ih^gce9V20C z9{Q&_IL7R|3|*qn?WWTyzQ*}bI_Xk^iT!ps2cE26(v`*5J+nm-jO%B1Ni3&x`+X^U^E1G5~;p`dI*veg-8!@NQkXtthSDO%MUApX4b_Gy{!W9D7SZcc*G<{RZT71Ca5758ch>0j973St_l+DdMZ z4*cIQ`l#7BXtJ~Kma*z@(5V_P>YHE;T*{*t3=EyxRg$m&Ae(a?>AELTB}DjYJ{aNZ z=*K^xn)wQy{{?E0K}?-!NHv#+Yj8SKf78Jq$}ugZA=4`d;RN4-r|d2h2Ss!gC%Fgq zlk+Po@8V9npyX1UkX3kI&8;<{j$4F^SJkWQBlRsHs9Di%wlO|82BXNG58nEPk)Q0< zn)=V$4t13>7OuRLI6~OZnLEkn)sUNe3peHgRIq7G@mxdg9;cgJ??uu2^Q1#!i&(KX~7fInX+kNO7M|s(E7} zi8WvI{}~V`sL~Z&pl^Xu=4CZ)(2fXxqK5P>B6qxsTI3+7U^oBHF3sk?-ol*BO3d)^ znf!Nw)bM8EV?OOy&;yOdH$TU}LzT6bn`In~T|TA>yqH{_ounj-UGRV@GAbDo&Bzm< z0k-=yxZjr`yxsWyT&737!l~Yi7W+-9iPQ`nCIVhJ33j9bv!fb==oA!P>XLEn6_@E1 z43^?lft8j+6wm)Si2PF>C)9Y|a*Rp})f|-ob6rfEiCm z6cOCDEjY|P^^oeTZ{yV7t3~vNctSVArHDp8SkSJmf(t3UkA-8=uwfQ4%ix;@pM1BpTHjmJ==MN`Q zL2dD8af@Dqm-s%&0R7pH_sm&&RlEW&Qw08`CNBMn+>Zre z+{*+o*kBk-xWi|Wg+HA;{}}k}=ghSb`TJXN_YC5$>dQX+h@IV(w`UAL%?75G9OGs? zPRIU)Oz39Z`m4liVgcs*{K{Nf2Mn$?ENK_!zjPwy>m(;2Y@K^Kq|jVk*o>RH+A1T>WSlB3<=g zl(|Q(oz@C#tX1Di#vh?r&DaGi?R}0;zy3wgoV9+N(^iM`T@>D_Iy-(gjKptL!IP-H ziwIXxRW=6K&Sd3Y=KXyXf?YF*L#JnclNmQ#!Ped&WA4Mm zMV#5#-TQQZ#zmZqxGx(QcP)y%=Xp^z+Bq_^@V*Hkkw_#Gi3HwX!R6G{yx;7KDNQW7 z)Y3il@vAX0F)zgIib?rXOw6nQuq)=pm{(((>WAtNQs#N|?*;N-pwuoPW*hhHxla*- zyywp?8x!-4^5T69rC$Ii-ZHlG{Q2$(EcyKTQ}Ui}m);GXmmp{j|C{%WJpaE)ofmm~ z4*vV7-Y&}R2Jc@fTRzmWWpiJImhDOFhix9@z&yeB#ar9n?SS|n`w{T|wcHOt=yjlB zogIlpj}c`#3fR!HeU=wFdJ!5ifPbHYe@4G{M|t*D{IlnOiCPLhS0a{@qrXHo1GnOp ziY5kq%iAk3n+MvFH8`G}Kf7{WwNpOznl#0{8#6cTx;8m3q)3Wf$~TOC4k= zCRljO1zgZ~O9o3GTG$zjz?Hm)e%Shs=147!N06^HclxuV3+DSj^v78jAk=(HPT+Gl zc2av1(AZL#5h;63EnW}p)!Dt&b#)ChIVk#qGcNLp$;efqIKptiB{Yb*W_ zV;Oo7WF~EE%VlWwZ=m}=aEhE9Xx9U6JU3In5_|#Ac|Z~M%E48GtK_|mazYi)_28T7 zZ&Q^2?0E^JT0mX_?K7xJ%SeCC%f#7P{RL?HK6)@VqaA=g)`0 zO#!pipN797@bcRTFE!wmUnA`|gKq|}ESura{&j-up-wOP_GK9S80i#k&VZjLf0Ftm z;QE0so}0<5rEWE;<6~MwT18$3WgJ(>pcDKMbjL`?$QuND!T0g5%!YX$0XITA1oVUN z20Fks!eb?AA>}gRAsu`=@0rlf0}6Rp2Qo=7fls7-D)@ZfYbf7BUeK3a=$1P6{CPe& z<*eT10$Jd*Bm8r8E%0w2>{AtGt0S;~yCP7I-QXsH6=02W^Wf}5575W^D0pQj*uO67 zwg9a_CuNj#FLG@sZ6v=MzRLmiRNexQF_C`OQ^$FtEK2}o5DT2=`NH<|S@Mri?la(B zaBoBRBj8J(4}&`l?Sqs*fJ_bnN5P+@&RO!#0cXiOOZl_l&+#mr=h=4R!6)#ZK>1j} zcF%!}BTrd74$dd_wh-ERKtAQfsdx5Iy>mRA=jq@Rz+Z^kfByV5d1toUjUz85Dr32r zn9ICpQf|jK6jRo*%>ozlVxsvWz8Jh?nzcrAT;A=!seERTs-N<$jZq#g zJU0SWfMZ?=Z-u0`Q37x3Kqk+r%)?8-W%BaSmlFJA`>D*u$5Jnh*;B^q>GMR&rGOWb zcy{bA(x;2miQ_$)^3I3>{l*$`x|%<^3MV9Tq^~fBgMdu&CaI& zj!dMRO?~xO*(isc?R@3sT-DAg8~IcJ)I;?wojlhDWt9Rh30|G&<&@Q?Hqj5;R~AX& zQ%U7Zn(~~+b22z}G8=GiD?`UsT{usfB;MsUpM3dB10M@rb?^jvC&8Zq)bj-LtgD`j zI|4snkbc7Z`_y@lI-l}TfT>&=8-+=BKI)5 zrfe4RgKeCDmUU2L>tA0ZvTjp@O_lmCmVt8F!Tq>6X(2A|Kvq~TYDsb>Vr1T@lFMlt7DYKc(}GzfGY)DM_u#u zzim&waShjA>mL*WEj+7(&cjijyCadUwIyq1;XQxJtUr`z-h=K!19Xww8efp69?#1M@sd13G>z9r}zZ<-3jdMfYP6h8;?>f`U zv*WC<-wLQ(a*wTlu@SETLc`7Mcy47*9+~k zdCGJKaGe+jroq|H4bn%vf5r0?;34nI`ytPd!9N7<@obx$w6DxTx`>!52*WCB& zW4Ufr!%qY0&VElFaBo`%zMMM2Z`+N(6!u5O;9c)+tDds9ZTTGV&Ru<}6m%#9KJ{x0 z;4u@pK;Ayee+d3#(*4vqO}{RXcZ%l&;65jBFLe$>>kM!X{3+<4An!Ek#fbh>+R!(R z!3W>X-b4ugxqA`u7ol;U^42-d`$6dJC2tSUd&tut`!aftiTQx^UEqD-3qZfp`ue@n za^K<_k_?R$p2g|kOY1XWFZjc}9|Mj;_XxP-ls^w%KQIZrwA?SbKN6=MRrbzB$06)f zw9VRS{Z`kb5WCp1kEZUM-OmL(>%4MYT_;>aU%EVRe&4aG@QebATOsqff5i^kBh zu08I%)dB5{d+1;bo$KnSc3a!79nfyen|s!fALfX*;u-sL;}y=8-BI1Ph_-(F53F~t z{qpt_@FM!FAN>P+kMEzmZY6_DkNGR~M&HAA{4e+&-;=tw{}n#Ui;* z?mFeQd)m=CaN62cp4YbV=3V3cHhAslec(3l*54_&$@?wd?*d!fbr+yL2P{xux-+yn zPC5eU=V(vYD68$=1h>9jZ=E_f$dgu}r|+$;4R(H#)P882yCYDh0XM_DWv>JGcz#6c zI%^+Sq3e2LoARpv{uQZf!9C!1R4(+(^=OtneF5$DPRwQ%y!#Z_o@Mgn^$O`4@2>F^ zq^@bs1N-P0xewYIU)Mp`DrqQZ`El>ynxj1In`3AH9M69AQG4Lr&Svi9Aj4Af)J69K zwdCpdWTTJS_iwH#+J4vmBA(NMBp?|uMpH%^ zdF$f2pSFaZJw+4zHISz-*^Tb$)2bKK=;auoPPBrnCC_o!2dPEZLo7V_oBEv2CGDtt z$FTnBJMQ@U>S>7GS3!HnPU_PSfOj9D|EgTw9~fJ=u5vJrnhO}4(w_H|=NvXRu6)%a zb;5PbvB(22xOO{_8o`J0a2?gJcJJl>#`A>Wb2-P{FU9eGn!MA{(I-&vQh9d2>Hb(9 zybSG3=;ZROzonn2zuKCSwuYTrJ-a5|XGH~vh^u_YXD*&A9 z6}-!*Wd!}kI#R)B_v6kT{kikhKLrGRH-_u_9O8-6uceG~tEWx%SYMxI zVV)X;3vxOM?GoQqL=#$Lf6*yi|eLPT8+`=<7#b0PnoiXD~jH2>v{93jASk z`vHBtFQ9V}I7uCC$T{j5xBZg$kAV-V`w95Z!R;aag!=D;dyBj`d4C(czTtb|K8VUc zqr86Nt|$h)pECN4pOF6!>6=mA;7fkWd!Vb&sjv8)xZktqpYi-L^*;dK4`K0b)*YmZ{EQz7QBAFB;L8>PXI z4R%m}J%)HmOq4pOcjk2hGS8mApaHMXFR_`-doW9h*X(64sQUj^87k@_Y(Tw zr@$GXe-HR8-v7cmxi?H+g2t=pz?Zl{;?ThNfu+Is7`X#A*49C!YjmoXUpry@?ycg!lEU1br9Jn2aL@Kc<9n)h4;lX~(tM+C=S_{6wp`!DwZJvU^?s+ku@hrhmjLA;{dUT`hp4BV z`)^|{uDkC2>nPvCyX&fb&_24ij3eLkFvmrk7uFi><1=KY+&vf4r*Z9eUu%3f#Bp3- zjP*H&u0_WBT(gwFc-I5{E62p~Eug%z^ejc+$b8FYZqsqSDFW{p>oep+!#?Q4xem%} zJmnK2@L7K>?47mMAyzBT`eRQ4p`o znbOlpcZ+$~C-%&|f%;|Sxwg4>WH}O6nF`G zVw;YWzKMRn_0%ob-!L!q<$_)|k|!?cW$<0h)8A`_jy|M*oW8Z-m^r@6!MXOM-yQfX z0jJ(t#yyEXg*?ljW2|k`Hl)CdMO z;m1kUXUAUs)(3Em9Xs0!ae~03dja_hJcauj&c_fFi;4Q_xM(xPX)o#l$Ds>c7xbN5 z`ZDGR`w;9&J@_zhLrkQFeEm-MHtu)Ec^;ySex?4Vd;d=Gz4XsiJWT{6FyiC-VM1dH+PYe~56qqB^(1Uk7h2;uiQ@(7Hk0tJIO-RnicT*cHWR zf5rQEfbHF++!}Qj0r&1V!Q1v@p6`>lwM}Oe+--1odA}9mjH$afe-_2(#VL%x{yYjEzZOCGQ&P7VTV*=twgriZjU1UEnJ87D%UPV-|3a zuWaq#8t)+h3Oom}toy|&U=F-E&tjLTXDq?k!z^WN zPne46dRAa;$vu#JF7-$rcO&CnQ4B}=>deeGofYsa)RF!?FhgDQ0__)QC&b^4r6_|t zz+LKieksrMyiZ4U!kMmpUj{EI6Z4IAC=bW=4ses_Yk)D7l_)hP6wXy{ld311@a);? zZJr(bui*I+>2==K3u9o$)r|E`0JatSp^S|`gcxNvWwdp!>DoALX9j5zIBl`>-}zii z>i$mKr#&mCocktyn@ixeYY9`OrK{a&2b{Z}ML2iE7&$hM-wV-r4^r25>dCJm zb-!&aYZREIoH}7&96O;OoUwuq@~e5*#_04|+1Ak-N-hCbE)c5GyGwhLNJ=fQE8jJiPiV>>kp7&pk z(r48F9{ci?IcLAUXYf)K?^>YzDtKdi&do{WGZv-3d*>mXUl|jXFZIDQHOH@xXZAUkT#B-g8wxvDR#wi!qIoGK^=on8A{Hepcu{*m-W3U(DK8bm~l=qxU z|3g2+`FM#h5R|#eHbFM}VYukM^tf7LJNPAkvqhq~GUXb-fj z15ufv>y|f9J0R~t|DF5JMSbcJb65YgAIjw#xMe_`dSt(}+uAnmx$$PpE&|$a#kGIIJ~s93F??@!d0HVA0oI$eGC4dN&f@o{v)`5K@PvAjBA&6&~dp#o<6|C zs7|;$A`h+&#n6BIz;CGgdwBT+_5K(^Ow8X=@81LZ6n_AI4Sd79 zdi5>vJKp~c{1Z53@Q>jB42{19eg$5iFTb;G+H}Im* zqrbETpYj;y!AsG)rEhcv-qls-+Zc7U)sBmEXA4-0@Zk){b$uP2zRVWo!uk`|Snnk{ z?yJ!7?$I>)#v8|>?YJn%xoy1boubzH8kmdCsi?Xm}>4 z?y1MhZkT-62Ej8VeF(?RG1i~E0ZyO7xusmCqfemxH+a_H3wB+fI;=mPq|$dR^`q}m zPJ5>xZ5#G)3Y=r(xS57$ZMS}$W9d8yzMJcfeedL5ImrJ6`T9JI;9U#UBW0@nbnXY+ z8_vPzC?_q)+;z%%rR;<4w>|BR_at_so6-qut-gkHeUv=swROB_;2Na==K8aWJhdI_ ztbVNioAmS@125t3gL7wsI<80BPVJmLYd7q#?~|0#7HHGO%|chdRNnO^H_)M5JZrD+ zvL1#tQ~UOmxvKxHz0+=}U&=7>x=y>=$;Z(8hV&cWAJB$tlzF!0csNJ3>Dq98f?m>I z(l$W9&9y>5MxV#IVO&N#sJ^w*roM>#G2{H|qI%$7%`qDx&wecf%14{8&N&|%c-DuK zt}z|;xfz_gAMSCu4h4JKN`2QCbwFKGAN$DD2a-=|I##X;>ZbiuS3I|F0pwBNMH{Uj zCH)b2SO3=G`wF0bsNdtjBxM}aTfiggXz%n7+zZ_W7a*LeivJdTu(i*yw=wMPV(=Ma zV!o!XpsoEi@83l@{fuyq>Kt+Z>3n%W`h@g5=>LxA-$VC1z`f%Zc;{7+W6=2}^4teF z7u<`eU&=_?YCFPOqYj0$X#1+|b3Qokj)l6ZU#Lv&vwrdr&;`9<8-oqh57#I6Y^Ij- z>V@ZI#$vQ@-Z{TSemwYO+ScFIZ}be!dyP4y#%S`;nQ+FTE*W2W%J_#^KpS~seCop50>@BamnHOZ%u@32Swzr{A)J zGrx`c>Q&IWV3U-e^WXIVRX?0InL?*$cU^u|Gm75cWx7?;!v3X8(8Q zv2#%Ss=ldz>fs#EtKi*3T<2Lo(K)G|(&mQLGySmlRIZ+FdgiVU>Hiwz-I0rPKwjn9 zy}$_c^g{$~gYq;UtS&isX2IEp>x*+ioV2tZ(s68+i|bw3!-u`G^F`TC!-soz?T>zx z>!e&N=%Q zZ03*mVf0%(Gv1kV#`^^QDAxq-u5+gaT(}>hoey`c%~Q|yQM@bbybEi^?#PB~cf)!c z?ir0k&%NGoR4?q0Tx+!D^5$KmX7bcM?U45)ve!3tAL!bykFKBTz4tNb87pwTbC2A(&9^pJnd#SQqk=8+K7w*`uF6-KJKj|= z9^hP84w>X%q}(a!p9h~pInN8SNH6j3_ad?68&g-mYr(tr8e6q*uE(CYI6s|-#=;AM zeDa*H$|LkG*m%cWo2pK0tK?akx;`5d-SHJe+{gYnXWbLJr*zIc*2djE|M5&b+*S9S z$vvoJIt<=@tMfC^cK^N``s!P_gRgG6KX*)b=Kgq8KFllUmbSo{+cIUf2d;C@H{&4Q zCvzRQZ~gSY3(yW!f>Xx&;+`iMSE&K&sOK7_pP_!ZSC;1j>J)%iUy8^p<~{iI#_Ns2 zd#?54`I2&Oq<%Z?>vOw4G(~N^h&;kKBz{lO4JcD%PR@J#sNB_c=bY=WKH?J3t|dd@ zwf*wy{TuCQ4WP}^Zo4l~UamLVN&PGDn;0W&I57aQ+QWLZbGGIph7Ol&IhG5$>FnP{!Ct1$gyZ-=r8?`Y-w{dEh;h zlCK~4g06kuJ$kwwGtuAD4|cEU8HeYh;e5^5rm|0rK)LE8{eaxVxndmc#8T!W@6P!P zsh0XGQ~T-McMab0^MWl3{+)iF`gs{R&hrPPuTcJ{ z(0iHmRdBCE^OwNep)*(0?53HZ05^$T#nAbpGX zkI1tv^?X0&KPUYN{JY?ONva;a0bU+H=lvw*Z9`ji3cUL9OY(jO?&su5{}W&z`2B!= z|A=?xu_GI4e@5K{^yet)G3t5$NgmbpQ{cT9WuIcB_I5|}#X03#;l9Z6aSx--)|VQA zj{9l%6#7c~*{+kpzY1%Dx}~2f=!@#BRPyYZW(Ik=ycr*jXRJb>VIKT)1Tpk2$X#E+wb1q3b-k3b+IaVYu5qd0TpL{*%F!`3))9De4-?K8*U8%eZ%j%*W`;iMPwE@! z8+m>a_OgESANG6t5hdVv)}&hM=xaJA&Z|;r$-DbnQ`dR@7RN=L_TN4R+wQ#8zU$ir zd1&u1Q%66*xv0H&OdQW@$|!T?>t0)1qb@3ceI#{Uxnuz9gLW#8JZ*yOyla@W^+A+d zJ-7z)T-Wtg1pOuV+3v*z*J}Oa#t0wo>w8u_L0%tuo{yCx5B+6zz;Tc#_q@&{<4n@i zF58ytx8tVWQZD*?uI=_;zgqvr^)^0wb}bCwBr3CHz_l-dvg(MwkbYn>cxgIEGI&C|(LkkaOZ$i+xXKwpQn77yI~#Du3d=m zmi2p|;OA+7rFRUt2+r@Of*xoe!hN3LONVqR|g&gaU7tVfNN9D=2IoS3Pqtw1?FWfsgcl5am0OObHp7yK?n);jSyLuVo z3&yg7{a2@*=a#RI_;imP&ds`@>l!dXo_50ZLmxzYBlzAAv;x(D`mN9GJaS(*N!hW8 zMu>AYfNKPtBi0)OW&mRVi=?x_MAVl4g>gb-`+i#&&gnMb&-lO!d23Ogv6*l_`hdJ| z!F%uJ0kBTKbJz1MZQwO%+`{|0#+b;v_tcxobG~KN##qM?7QpY z?Fbj-tDLlhRe)=-V_+M~)VPo1WDH1IPJmOE%58{eZPH+b-}y#I-dq=qJ-F6{`|IvS zwS(?k?T`DyK5o`_ARA+tZG3~(189H!hH;3zLHZ<}VetB*%BCxl<T7CDM1n0BzHWoBW>R9ThDCch8T?;~t z&2Pah?_6`=9`^3;*_{8zCX9Qwfp_ld3rM>YaDGfv&$y@KR7ajR-T58-tMIM0V>t}Y zeYUYg<5}(@-Tzln&u=Q~7!zZSVQ={oeber!m;O$|UjX?H@g-B^Axo53PR>2`BE(<3 z3lw7dSE-|HuhYg2V2k%Fz%n@J!w8@}v<EcIr5HwkYR3e*pdp&)OHq()#)=%E<9iU$lAZl=eoO_ixvC z$2puaIiKbMZI5$DophgH320Ycr(I*+J7kb*oAbbFXWfTn0q%FT+pdMS*#pSG{-ANA z8QyI}`P}3E3GfKG1KbAx82CH#{(Inm0{%zv|499Bw)1b&7w^;EBkwNw8_3kLSmWLM zAktOV+N~XZ3j6=?9guTcUr#$Ui@cm0A(jl*Dm)1J9! z(I&Z8G?7;WRKk;XRi8*(IS#+d$GYmoIw0LeaK>GoFWPl&r1rqQs%yXexORsadAMh{ zLV5L1+vy!N<*fZ$iR9@0rx@yl`0*fRv{Sb4xDHS@jDfZ*oM}6*#)AEA6@BY`=u`r( zy{_xM(YmH@q&*3?wUauo4MGp)TmxJ?oj2M#A;h=C90h7dlB_Ry5l>CbyuI?^7`9Zly_gHZBGWIodQ0C zR9`)nvgas!fag7w-A~>b-rd6$1Hm`!niGJ=`#7K9rTWm)Y8fdYEoGkLwfJ;25LOqREF4I~TCGtp1crOQEMSc}; z_3V6Gfgaw6$(sN(P1*UFI@X~o=4uIZrjWHKpE+3o6yi@70VVXO1TM;;RtdHy;-VC4 z6?m%^P%I%Wgu8rjc|bPL=|E}(9lUo_rWbw&;Gh>C+Zn$m$~VD57j2AUgJ)U$rg1>nKwn^{Er8EoepqE!WaY4II}|q85sls-aXR zwaRF#g4Sy2aRX9l09QlJ5=!L*d0>ixD%x+L{T84d9=aHTZvOYdK|g={;ieB-eb5+y z?_pq+9*kq*rf74KoK5OHg3hmz_it(a*T6$syUzP6&x>#}$MYO*EI@Yw=`TZJ8A?mE zFaytH;0I`{2V8H|)+p5b!F6GmIwB5hXrmT-j(iJJZ$*EaN$Z&L74)na3sXQ|QKTse zNGyTcSx~Kj?`C?{0Tz3La8l!b&v7BPyL(p z?<()BJg>p$2C}+JZ&u)A5`KEVwA0h8JGyXifehJ8eel~w|pqqny@|Yim{4Ik{ zH8ZWA+y*f9Pz`e<7m20OYJ9|F7u0%)Ds>~94lHOpeQ%|eHe}Ha^z%GSnNj*PK!4ic zp@t|>5#@4-DrJ+OiL5WvW-6^F!$T7MB=VLBHW}?pp`VxNdn$Qp^hS;|BglorTyR@pMd58?j3(l-iP4drM(ZJ^#ycKFt=lwa|!Sn%bYt4uVXg0C#xfSa))>4^A%u`=NU$9jJzSBk5TFa*H7PvfpO$M9hIr3TrJPlKm}t{64}{h zq`E?lMJ&QpWPO&9?=oYsLb?i#b!e}Ww?c_!-j~6zpa)Cn$29sj2(%*MI`pmzY89lF zyw&i(nHIY#Hw^u^kV-yw`omJ9nykDctIucl5gl3TF zG*X(2Ad%-J>L#Os$)st#XONdo&$7_4bhu7|;}mdN@N93Zc-PXD!fgROCm}M{Y-etKwg)b0U7X} zj~pwIc0CfVMS_LYOra;|c{@j$ILap@17&m$J`VG~A8U4m*>s-1B_aPL%ATd}L27>v zH+zxPVK_R2bS@y7Smw(`IFI9hJa0+#Bnc_TBEz$c99=-n6Kj?!bT z`~^nl98^v~?I5&1hu2S##HaB21@HSQcZj?rv~i5*6G-bE{KvxmMOryat4E=9i030< zPeAib#7{nbsALv5k~UJdnpTQ{97<-0`iKZE!Uni z@P8V9&d`Sol)n)3G0&gT$`_Q_%X+z&I{Vj33W zA=8Vb4uJLt#TXxIDj_c^rf{}JdN<^34XCz0(1 zAPI1NN})gT^ye&morPER;}U(y0t(O*t$9H-mqw^PM4c`qskc=@U#sBDIxW06K(m;- zuHT8#|MQIDDf)aGd@PUw=;!Nmmrys4rz~*#ajxgRJdJ@LLbuv!vo?AgWmNS}yO`-+ z%;0`t9GJr@EHGndS(`?9>f>)W61U_i+|Oh8Rw=cH6<8u~hME(Ap6n8oHz{?8a`$NW zHhAAx!Aofd4c0Qx!owo7ZiT!hYR*DunpWnK_?4*cJS)&OR+FpLpQp?y^@p(~Q`m$x zaM$4AN<^WK+W}*^XhmC+O7}91cQzkg*x3%mVnU;O*%K?ax7f6gop_!4M;+ z#hYXfPe;9PC2gi&6MVEmV;E|4lv74avufA*v(snOf!RWaDx*FF~vxZTvq)Y|8S2JJrxcjK> zd~7499B}WG58ZNRStGQYp zIDqaQLL-l&DW{m@TH07-B_F027!|E>Jor>*R1VKZ4vHDMLZEsoLX^tF@{rOebyEP-B64e!QFs;E&7gy#uWP$znX!9d}QcXg`^jFGHu0`u3rY zcI)7!DuQlWAEd+tJ)EJ$6fjJWdnwgHss$WiZfWJU-L3+buA@`~7%gQh&qh~5Or)2* zQR>cuSpsGvnYa_JMSgl-#s_R;l(!)`>|-2_qzplEjNEbB96>&P@X!TyV`f`Qt%9^7@`H^U*|$mP>gQjh7uV^pyNp$6&(qEv94zox>$(BuYqYWn z)$6o>8!ql5hr7t?7U>nImkL&LSE<3r}N=q`SazMt^~E zzsgMCVwPWn?{zd|4IZyS`vzt2(5G9>Chev>yLq%|3T+xgOZ1D4optlx2fblnlII0r z1KqhnJNJPHw0jrWVwBc_RmxpO|87J3A+z%dGxITWyAMCNkm(ipSO!0jC71&ifn{Ko znZ1mKS->96V?maHHSEKFs)qMMFd4{L>z&7c{}v#J zT@Dx5H4_%lkgkW57A`_09Cpk0IZEXvG)su%GfLprvoD z-r%tn4r_g0&o?onGp6($5aSw2fXg zFg_Jf&Shq$(P9$svAkWRS5ns&8&NevS;=_X>p~=x12&s-1w570=Q3)$#$BSM_FNBN zZ}Jj&dNVq?TnB6-@Mbn3s)=a_w7;jp%T5@aQ*8UGrTix!YH?9u((P+BQ z%cEotl=7qAyVAG7xACBQKnv~K-vA}wS}E;nQqEH@)N;|J5~w#K{jSJPEnhKa;I)K()S4uB6KYSu{R&`dQ6?P>vfc^+b1UAKDZZwvgpu7$rfV5%5}a!M6K zDU%wBlsro>jv>e6)H?_56voduT`97wpkGz+?S7($F|8x74xZ}Z$F;c%T`Q+dDSaRHrDg;oNRzQ`zFU<8Z_Cn7y}h^}k}fPPFF?G{re4<6HKDFN(7-~trmpq|7?yQ4JK zzjDXCpE{o*kI$*UpZ*zP(32H>J^;NvwD$?=r=*`Thdztm>)2(N<8>CW3(mtYECMQc zuE#%YBG0mo&~3o?s|V_^inZYC*!kB}uaWYNQTg|2>l3(lC;lZP@dZ48M*1l z0PoVyFL{5B^mRt~E!MXW8QD*O&*<&vynl(bj42Cy8IKR)>rI~D0Nz7Vdw_jB8zJ}r z>Apo9Z_(~2Nc|x4IR%`J#!tVtnCAlIln>+~^BiQDiQLl}&s6@q`_}u)kL0ZH8W+jB z06IniE+M;k+B*+lr|Fa4`AMP$XNeqKr2TmKiKpF*k;nceEq=^My-N#k)1P;s{|+tv z66&u|?r#{izow5b)34Xz>s{J?51#bLUm^V)`tmo(^HuceJ@nm`<1_T)9pwLW`t&km z^(Ot>$G8}MGOF|ic|W87-*a#A|K;Ai_o`o`7jM%3>$LMz`u!4oya@g!cz=<8?1CSE z6YD3m_evzc!br{^Ay1=HpGMqIe*zrj+12wDJRAf*0p6tD*MK(|$M-4o1$-W2B#x09$vOi5AhJ0?pZ23u2jJx( zI(L|MkD)uKk=c3pxd_ha-!bO*Ugppzkv>FqhThF25?cnYFp5`6SAZGx#(6!;=#A5}X}F#y zokH4^)X^jAN8;_!XrXKi((wGY2ktyu?SivraJ6t(3wI5q&Cv1$*YlquBvM2>dN6H3 zKU&pCp65uOe&kXs8wur6+P!ZPB@1}=l*E0qC&2AsI)N6j?!UBhmaHSMnWtvzYX{wp zWdd2y%?FHImqWo5qnxN6+b9R?Sz8TqYJ?}xSGA@Mq^`{6;EI7la>5ygXVwGYN8qU! zI(7W_9X3LB&{t`}I_6r$fh6CuwKVmYzqDyY1xxdRN6!tGV!= z$9NhYDa6he!l{yWPa7iDMf4K z=M;P|FrW3pXR#Oq=tB!oOWg*_wNj>ITMzV7Yv^AUI%`C_g8mvAEhpbOq5bb*zBER2 z;4mDXq4x=Jlg?PBLo10g+Q{Sd;y9eTP9#!CFU(zr=TPdU@ed_k0;oA^RXEX5uX^Zz z57gVJR|l_Ev=Cx5M(Xv#)KO2IJqOHSEb{oPSJ}jq9-1*2^((|SbD*9|Tk1tJ9Ood1 zTE?pre!8ep4;Yn6rmd7{jPj{jMj3B6R7SK@NHgI^&$A8aq?S8|O86~hT--ZU({?>G zLmuVVo>e1-M)JD=cUJvS>7yOj<`(knDOVYl^;SgzWebp33FS)B2_t9OjG?=zi`a)) ze7XdDqa^G_9OHe0{C(K+kI;n=DDx3J%RTJ$_Tuk+N&c7U*FL1NAI=Xk-bdl^1iTwp z@x1i{_1vAs;cvvFC;FgC&@`f!MZ0<6Jv%F;J$HNh9pPTVB;1bD=Rs1>%Laf^o~LPP zjuvN;(j+|@Lmoy1ycaTtM8=Wp4Cy>|7wG*0W41_nVVOE>aJB~3ML3uM2BEC)IRdR2 zU;)V)`5mHc4{d4lh8WRFc%6p!F!VaX>aljwdt(UmJS{?Jn%0MT>P3?MNHKWB59#Ma z>Icu*DC#)9Hnuv(^8}vpG*Vxn{4!qZJQ5z`y_fPG>~*{FVvQnC@ou>hB;Q4yW}uPh zCUAPR9mFu(IT>x{6tj-g%o=X}RkEM1Wba&t7hHnpUc{+pAz#xMa>6MTP(GIv&ur>s z@_ZQ@X|$CFFKN)e3@^FRH!e}ja|``)XV8QMYw_m8t%3*Qg4y5n1}KVe?3E( zquwl3r^xd*Ogq?msMRoX^^8t4(26#;f@`5(Gv(W9xtDev*Ewn$FJEPJ=Xo20OV4Z7 zl5XCIpy$qJlvYB_YAT}F1Xqn{ySt)F%6ZmkTqGZUa~OjRXe3iF5xB&24(%4fV+lOC zg6Gn%H(d&7wFuCI_lz=)GRep$9ogg}&tiBjqfQxhERzq<;l|7|br;B=XH4hmt2?+2 z#=?_?8}!JX-F3LUNoss#kG54K`3g8FM`G^C-D!6K zt@PZJYb~=8?>3~>hs`m{=AD}cYL@d?FQO9e8W|ZaNDpJ_SPaweF~-S}^R#^wjAtwT zl=pna`9DI90cvzcZ=Pgnc}Af(KnZVvbw#;GXN?{Vz}Fz7Fvu!lY*pW-ic&>z>>0be zxF()D0q?kVk>3gTJy0_SFh(y%BOcm$Y9hz}+ZWfeLiltKTgGU)zpG(X>mrz^obiA) z+BDKOLa*CtU8>$6>jsRruF|_J$YC97^Q7**jXS92vy?a1um~R$Kp*dI{I`$Z9FL^?47&tqulBwDsW zpQL=1w@tLev$i|Y9N$E@uAM)l)4RUQPy*; zrD&wR&ou>~E3~{G>8G&{@5X7_T?yLhc^jp>=$+?^>cbc%hUh^Le0%Rn&VvR`Qc9i| zpf*Rl3$(e8MZH7JPZ{uV=ENefEJ#eZ|#%|QI?zZ=ppr=z~Oz$UO^is(HZXwdh>9U*))r`%z@K> z8EsLkr^qw%v4ob4Q>P6rs9UJJ7RC^>3EiuGdTC&(OO8dZSm=z`Jq%c3M`K z<-e9v)sbAb$X$UKPeq4l$&=L?>MT=wl`_llZU2mKFTvp=vY1Er=IBd^jChNu6q%Ml zuLOOmfL<-oM19vNNHfbQ?%>R;ggKhoO&K!5&`^lSQY6X|Zzms{lBhbL{} zUHH2OtU+suHs|4C0$PLc)j@BXqkej#pdZnX?sUVm{TZQ0vrt&3+!C1SsE0lDu?wuR zvwmugGm6H_jIen>bcDVbVe<5MhUX>PF;chz?h5OPas1nOWl!+XehvH)_!IDFaDT>X z{ynz$?~ucv82R7P(s#7<4g7zLEPoB}e}K;40sjE}Gg|OZXvCkfyuYE1uj$Q0`tty; z9z`(BJQ$>1&zFbc)b(!|S@a^S4$3uCu8H~3LfX#!buH z$jY@;FV)+{^V{+>VmOKHdhnEuoqLa`55Im4+ywm}qr4X1U9ziEV>C-LnSB{>m%+QY zeO(1y+j7BsQdI5}l;n1vE32=Uiyz(Z09+(&>x$ zs7jHqK8iZwon+6d1_5KaUHol^_Zo7`d3NXP`q@Hld*XWO-9+z&X}vu`^Cq!(i5n%4CP*Xj3l^y3EA8qZ zc2Qot-G^@WGY4Ma^saN@6Jb02vU&UdFq@1 zcMLcK&q?S}26E5DMy6ozE@1!80720zlvP0ARl2J zS$gYg3d*CTMkze|@+?koZ47=!kdHSa#%XhE8|2Z!{ZFxx=+_1 z(F=9&qvb52jx)a!TE>sM8DBkxZt`2OTZ-!}BeAe-G{cni=<7 zG~Ia7@8Rn^G|+NiGYa1_kG{qR-J>s8pcU2^HL8|AG$AqfR<{}PZ{hJ~B)KU(jB!pg zMmTlxe!(y&C&N*s@dUWD$V=a<6>jI@>?+b)iFD5!K+VY39a$$*)Nk?TS0z!%GAx7f z!seL&hEe&Sk>l@3)hxfr|Id8I{-61(`Tyk0(+}b45~Ju%`+hV@9r+z{{_kk}KY{rd z+WrlxcQ^GM-RIZPzTbwNLIQ`eAScm5?_}gb#aOK8*6Q1RX#N&@zo$RnQ1dn%twUuV z`5Qawqc@(|x6xi78suKtn@;ZS>-nEaJ4y5}6JF|(-w3oqgs6qG4U}z#f)TVnp1oz{ z&4)_X6Yp(n=bSV7lt^LhE+CULM95>YRH^8U-(FB>%h}y?>wEP7YBfo zQ5n}=_khMljFfm!%GJJ&-ZU~=Rpb|Ax5M4`BeZ*rau>)oVw_G}2}t53^bbS(1mhD= zyPjfYgHHwGk*%j&@}!&x0B?DfP~VfSBhWa;XkEbf@e7nB^wO`RPC)Mz^gO9|C#!c7 zR)7XLPy_s;(LJr6WAK`rDCMp=oWmDU!uu$mZh1!L&eF(f5i=wo{m;kt7qGGyG4tF# z7BQB3nBK$HBWZ!p4m7Eq9<{=?-#8l6)=ulWw4&Yplo+Q^GyFAny-0c2?yom z=fjs@TIk_if;2d8N@>8fhx;-iR{pmW;eosqGh2-tbAML>#(uhWE3iapZcF zJe88-J=-}%uaCgRX|&=3G|wY{cRil1?}w^i2Ari%B2-h6ayohGNHa5PB^lW|FN^?I zQByln3^h+>PDfOoXYQLi!FvaOik3aanuYE#JT$>WDYQJbt>lEZjg#(PPCvTPy9UaI zldD7I?V*J)kj^1WpMdINc={Cigc}K!oT_`eZ*RP_<;?=``xkIhl7lY0$16h)+81|T z-mmxM$h*Eu%-c!c(|3RA9B}_wM4r7)2mG4?l_V%9P%Dnn@~(h$-g{5(7~{~HICRUK zUdr(t+9aHXns==H!axr9g8LE<4l;JfkjhzloPf@yL)$Oj5^4Vst-TI}-`l#(vuED< z%sTH{`6XN?xfztt0^=#DH{iSv7Ym(ZwC|na!<;3ZiO%|8riItY`v~q1k{+Y4e#PLI zBgd(iK+nAWP)G}&gzlq_S9$wuu--j5N=qs9H$qkBQnghcDk3ke(U4cx6O>6 z_s`quw>RO+(IvlJIzT_)AonN0OXR%_|8FoZZ!=bJQRXf1??Y)1^IjQ$&hvY;@&@|% zOXTHjIS)s<^uLTYTyZjyl6Q)Z((gUcdynyVY)$vlgY%4h7W|h{stTz((%Ep61jGVr zhE^pD9`Z;%eN97KFHr6vPalx;3u^ovny-=X{gyqHRmToddN1k6yuSNnd<%MSQ0GnHUCMk+ zuRn+00p#QtT{X2-2@5RP< zqEUKvJ*;Z_Eyk%Qc{4Vl$FCRPMGempyve9f?cIQE#>6`THH@|ADaL#%;6=;hmp<{l zdyl~zW}Y_q)u$e~XGB+dzQtJHg31Pc@viUA2}>n3{DLps*(iiU7Id>{+tcu3T61mD z!|>jC7XLl5&xaE=LXV>XX?o|=Z(M8O$J?WxmK!Z=q+T=p`3Hh9`(?kkK`JR>6hJ?#9dJ$czI_W6{8ru@^WOdTw&MhH8)e=O;nNNv(NQo{)bc*1 zdlJ7k_xq;eh^HK=`3nZ#dGX5%tz|W>`uhdm7|@2Eg69*obB=evw9}6;>Y(4}cmCdN z90%tPZ48X33*FS~fKn$d_0yg{m8atya2mloY}!oW|0U@9wR0A;AdQjr zE5zfxouq66`FUWfc&lf|)B&|T*D`CoP1wkcX(nw)(w)r2E@pTK7P&dPop6ljv$S~* z+4$AR9@>5%PTzQ6QSV^r&J^tM_>JN{5W+&4Ay(u zx$x#noInrc^Aa*m-v-|jsqa^c_8=Yb?oJk|Uanq-b3_k22X6c>M=R#-6TcZ!hm2o^ z8JGh$J9-Yki_y#&s2!d`xf`vgZx!f|CyCiSX%kcU>xz5{%w?Wk3G6{7y$yFTml_hh`=W}L|z+Tr&y{MN(o zS4t@7%^RaqMkd2|BA)abz3qUXTGDWX&2mLR7B$rtzw*h#;`)0ZMQE1RGZPw@pqT=- z)X4s7Pr8u0C!=9JogGGky@RE9s{e14%Cv$yMio4F^fxs60MDDlnV5Ik1fyNrwgEUW z&e{v7e$~K1rsNi|!0yR7(-czh#A5+Y-51Zpktg`8{9U8X zMW~Gc9XvHrz8P5a6=>4~Q2Qgi&B zg{KjqhnB-Ve9s(uXxHzeIw|3KsxhcBTAl~ip|T0B8K@A(+3@EbgTt-^1a zMi@W)8D@ulsRQrXLK*VWYw_G5{Q5yL)ZF(wC;TlPBXzC(_4~3G#$F?&oX$)2f{Z>OSE$p8aHYC7Grq>xJLU%Hs|3$-(eUX z?T4>n_?^N^u0-XGR`~r`8?(?e*mmga1?eSuf5-DI=l9NeyH>^ct?^;cr;TsT@^^_k zS15mtJ{ap>W{j7hyTO^nJ$U*R^XWI_`IWD?zdZHR6EaSG1KoKHhhM?z*Nnke;QXHY z8`}CB&K`if30L9vgI{BNgU-__Pl%1D=n;mm(5umidV1+MLaj)^?`+(C`MW?{Naz9h zCydfP@~$&7*C=@dx!i;22hhAt$qjh*Tk}au%+QBra=dl2MybW9)NkP6pLzce@b#Z4 z@!w(-{|K!|ly_F%LNa&Z&bl|D>5a8;!_6Bb-v97!)d=nRn@VfQ!ML+_!1%m70PU(@ z#Vpg3Ut`ZOUf~Pv#Yp0Qnb_3d5J!(@n36r9m@A;=POEn!wmfzY7a?oQfdRL{&fvlL@G-W+@wER_eJ_V z6P-gnq3#1HTW512c@}>aKtcGZ#pV~ce;Ajuh-X!(skk=ygTt9}W=NI+uNU?_& z)Hdz@75aP~O1CK$#%_>4^aA}*pMdH*wQf`95oI62!#!$RdK3O!qx>e_6M`WygTM&= z8H1BCV!H#}@aO*@wvF2|VA8Xenh+ zIJRif+fG3*Z_~ePlwE<#McP=VKf*jEMk&`#f19Z9Eg5e$Ro$fr;*mhn78 zU&4w!#@=9vc$g9YDLnLfQoo}Mcgg+L8Gnl^8QCNthcigReaL?JJ_KcVm5Fej0?t+Q zJe;3~6XTT$l=Xz60GaAhc`rL3dO`vCV#cfpd=70JlTM_KL`Ex(-g$4-)0!-vU5OJZ zlZ4#EitO)V81Eb)He>9n4SxI{p&aQO+Yi5xRL&UZQa_Cr-Q$H{=kiOAQr;V(*$R(- z8|Tit4xHaB_`6$!>^V&RZ99Jzz;9)Gfhp)O!Iw8SysJJ#31c}6w4}`s=UlDy#hAUf zOZ^_DEP?@2&muhYZbly7r5L8wDav|!I1kR7N-N+uXmyLyx6t*wJbNqLvxsky#}mf& z9=v)JY>{*(;>+8_p6q**s01FpG3Hro3pBd1I)glqq62>GIYa#gtk42j-j?#VtWglZWAXGp z+zR&B;?(*sWZ)O9epRZkJ&08GvHB_LZfX>Y-WBmak|!AYY;x0$wEX3TzKA350~FJm z@rov>c-zUFDBgVXe5?u?RKtG_eE21|<;B5G zW!DZwhn!t*nKS;qUPo5;vm)hzN>L)G?8 z^6tXHJvj5;xOZD`BHjCF=3_?tUev1lwH9a@JMunSxZ@*TPqt>@Hhj&j#`{%kEq#~c zmWcLUYTiV$-eLDnlK0QuWB47zGU+H(z3I?IpStPIEaf~iza8=C30^+!yA$`f3jEU7 zJ3!fh7Oc?wqpaR33@d}N zInRA+*fkmPYQ?^GW3L97_kJ_f!))n+qBlm}<@zHU@^+T8gqOTQrSCfaqV z+=S;l)Vdp`9lYz4w;&OJL$n9I>Wh4@JG7+Nw#fT5`OZ{r_%ITkr0r#31ADs0yq{v7 z=mI>c^LJP}0KcQ2hTri0;$_~m0b?Z@z$IuU)1MT$%z!Ju$ov0@IuG!ws_gBrb4wHy z6_j2=Z=nQ84Ci=OSb`lNU$oul4Bo9iG;%f&b?635+lH<>#HbTu$qlBV} zqMWXUxQpe~esl_@6VIb@qWK~Vs>Q@hH!rEx3MkY%uJVw&g&r1;a1-NNo=LY8x1Vuy zjI_d*$qVCAdf^BobwmLyc#CmJ+00A4xsriE%2=wTqN>!(!^ainbIEX)7(N zpdFC3=Cb^d)J2+eQPWlY68E=}ATL+Pcyfl(aEfr4xV@yw{uamb@+eIbqIu}Jf5 z8u2b-c`PGYUU~KBB9dM2ZNef;PgBBw`DfMK%pt8jqzwf5x1wVDsU^3XsDOgjz|zUdXtSXAx-_N_j`N zK*M75T^<*w66AfQoOgr=kX}fhvL=w;)o}@#As<-s*DAz8COKzPryNqr>#OvM$$Ym9 z-$@|!;`{QfsPg2HGKrXuJZZx?)}Q=jWisZoD4#Tg8N8DPSKE)f{ygc&`+6&xXFUi# zd7i}c6k<~OmCm~v+{^o+JS_F2Y|>_`WVx}tq{SV;o6k-(<8(?hoAh&dyNa}nNGGJsDoVNnN!minJ&WrUBx&-CFol{-=lN{%m(Sl>)U=Y? z%j0AjnJH2@)lX}WtIf?vw#@?46WB_%^C;c#_k|W5!IHs#^$vYkC!7h9=k@EB; z-(G}N+EEynJVucGboS%xE;cKJ@25}>c~|QmYi+e`5nmg|6M04(N}UJtr?IKFPK&j< z&{*Z7q^l;Kq$XYv5rp7IR%f5YD=aGRTT=8Q0uk5B=!@3l2LgEo!?2qAx6hO zdT%$^os5pnTvu^jKt5IEH!qfFYi3V8>p@HE)CS7jiEnp>dM8j%AxD(1Ep<$w43bGp zzu235VJcF2H;uIE9c?FE+H&$yUW*wAi^z8Y zBT7gLAyL-Q#v2(YHI!jHzqfENO!;akgD`ROb+wgucEp}1Q>Pr_3h1{2(#oPch4(s> zM{B;>jQpDNtO;>VS;^E6ElJmkyjoGlmaHT#86zF&56zkk`ZY{th6%7|A2I!8MHu8{<`sY7eWG|GrtmI|J#vAYhqfHqCF|Q&`#ZHyKa=ZBk#9i zJP0KvUl{3(L1`}s#pX~hsq*-00bh`IQ`&0T%LuI|AARx{A^+6!Sy4_|hV$k?{^wFU zc}WpoG?@`E{c!@RIx_Y;#Ohg2$>c+|H{}vt)=VzsnW)29o{i#JA>|e7NXV37v`-OZ zNP5Qts9s<4>diHSTJ_;tKtBjaDes13c`7uP>swb?oPoSARD}?h@(Vtc-_jfR;eQ|U zk-x+|zSy5O$>*y>h?9nX4Br=$Viajz-Ky4x4%DbQv!@ZE3HL&z^&;miuJXep1XXX| zRKD5#7j?_~zi$aCBhM2>)T@FoRZ?c5P~>?_-ep8x3&}yAv_^41f)Z-|R2_u3lg3)Q z<_WPN?+YVYg|#P^o=f->cTc{QHzlr=fl$UBX% zWs-9rN|?hN?s_H*6j^6XB)z08^4%?43+bw~3zGa(+U7#)H;g)r;G43^aAmBr6_npm zd6JN4fB6BICQ4E#uEo{3h9@Fh8oZ z)&Y6%TEaJFdn5F(Fbhk_e--IhGUlYuo6WqO4t){IZXt7U1v6kJ<8m3b{-5+vCl$=6 z){FS|63QZppKzPvgk@#DfVjCln?bo;?1uc#$p^=9`d#!yo>Ir~bTlc2caTTpk-V*y zQMe1?UFD@i9Hj7|>W9(fDb(^L>MjkrZ2E*;OD0~Nmi&(ALX%~OFV9_t)Ir>KFVb}) zuLSZE%1a0!t+CPp3O(3~5;P|?;hW8AOQGZv_(D%|Nujivgx-WK%AZ4y`Lsk4UmZ=X z&J7SoL}OS8Cs$@E*=cV|DEz17pYm8DpFP4(saI4hg<;f1Cz(iVTFh1GES)5xpqVAB z?NN+I;TPnqEI&5ZEO&V+8r|Xv^Dga>C}P1oiYUr0MAt+ux~80EA^W9;xCv z#phm5``pD>J39CTclxqB&5fZ}pW}wzEsg3l~0owW~;Slv+&B&GK^JaYg zMrgQrefi3{NJ-8UB$w_*NgEQnb1$E28~M(1t_AdP14g%>->skw@|rH0^bAHx9;2)^ z?>D0yJqSV>X;n<6bvsaxZq#NNBUoODH1m3MZBCp#sENmtBuz6~Xx-~5;q{CGc`6nf zbpttxqkfYU>K@^wC7t>sEXY0Nw2CnrM-Sc0NlhDN+YBzIK*3cTeFLBcG)13U& zgfEM#3uiE!_Zm>9KR7W;>)<-Z&{@WltVUZ>#-BMQst%)0^Ge7!>0;6uzx8?6jH~9= z2;v8mr|@2_iAx}e_R9~lBtx138I)b9a^aTbbF(jf*_-cbXDi8=kPBmIMa`vFyr04d z(+VSrkR;2E$je7uJMxv4=|R4=E!JK$c~%84xq!Bn+(h=F(wuCdN48Oi?R;P79&F%i z>-cUpT+2rKbSu|w)MXRp)cF7|N?v;Z5^5?xBGQg)r!OxxlC%$jen|o+EkO$DQpi(& zR8t9ExsT`jjiE8(1$Ayg38SnKUN#-cT3hbh&>KoCFNeiQCvuU!Br+nq5howkMeudQ zDOUzOU~@uU;#!ffygCVkoJaocX{p+b%xfq|Gs=<5z3do!Q{K+-i}BR4CHD!$2`?<) zsr}$D`^NI!&UoBH9-HW=t)$;anp4#2JZWxYyd0;D`(k`%TX^v#W?~9T_S97~5O zt4~?isHU=X7Y0sfIoD=fs2O={koPnFP%=JU7fU9vT~A4dMD-EN6MYlX)E-28?7qY<&`{f9%Wg|%-qg*PE)VD$@4OKpQ3$tQs!zX=^CzXo6Czt8Fd=POfDpi z&fAf6c3f<9%T{zf-;)=kqvU^rbO$Mc^ak5#_Zs4M)0PVIDfeTt=*X zy~}H_@S1B#Et_Onw##CA7XRlGC$IF9j|$f?)8c2C(7oJRY8^<@s@!nK|Fqtc3dIh0lUGG!Ut1hNKOKBJBkmR|<#nA|T;;s)m zZ*Ljxv6An~8=5p5@5ki)!mHSGt8oc^r1Y< z@1m`Dljb1pCQosfNcR9`dXP4_%ovc4O!nIwh?Dj5amsL>a^1o84E=V9cHRU1+XX$^ zMSsg{LJfJ=kf)n(5q(iaxnw+7im zK1p|e z8}+%HKE8)pd5QLrSH3g6FFoNA>MvCAZqllK&eBJB(MxyH&u8gp>6Et88at`U5nARP zJ#?B;u#f(ff5BCxlYc3ppM?!OLApac*-5-m!LkotN4`QiyB6TYIp#!Ul-^g?6 zwB#929zNt}OE|&}#0p2ag=fNzZRX1x83#fnF5{Ox_6WBiyJLAKS{Zwznm42+Q-1n} zJRTmVK8Iu1<-A`_oi5{K65-yND3my9x3{ zvJ+adgErK>Ily;>AXIy7qddY>2#+O%x%`t$%c}CM;rr623*RkNMHRngS0l?FVcw;E z6O9$c7mXGESu|KHpveV@tNgI%`fSZP^!Tk0ZpjBv~roLSltI5ROzWBOIDIv~iS1 ze&~vLLtaCNa+Q}2*U!J?)a!VreZ)>mv;z)h3;gvuI2m~km8?VPbNR^nOP}#u7n!5Ryk6tE6T^2vtySc?{IKl;Xy;>y}nVIIw(ra4=ygWs>ceVoC@jNF~jxw4stt z%Y&ip=w!tr$*(NlWaXn3S`tg0f1v$CF_fv0XR^i7j$Nz0cyGz?bSE!_^1QKv5^KZ? zZz&$YwS5rkWIgp0_Id?xucXxKnXRUDg;4(7bf(uf zzQ3363Rkp|y1VvtV`A+t6y-wx6+9N-hD)(&0t+eNo`iZj>$0vSR-bF-(_i-mQwEQFrVJ7_kW@h&u z!hUKcj>YwM>iWhK3g{T$JH%Ia@rKYZ!X4`rFd-@SGG^qPRrWhIyuFzip}Muk>AW4S zbE@egQi)d*f@?bOSMvRt)Lc|tI1Qy2-4~)(UcH6%m+v0gyoxiH_mEB0ayz4SFEu+# z-A?oTEWLh)`xErI&cBg=TyZM$rK+Y*)$UKb=v9mi?I^UXo5dJ&eL#y| zO0p_{Zlag2KPdSf(au9OZz-**-KFqa8f(G}3X3IQ=c9Q~cK-4UH-$W>GX^WkUmi}y ze-{(v{aM-z%{+0s+Ko&lf9*-jKFHr}0TPFZ)v0>+(d}UORHtyO^F3LRDx|jYoO>5z12gDj}A2u8nL=~(n^&up=!URe&nywg$F*^!Et4kDdA zUCTyU^jRsDUUt1RNv|4ftzN?S?AxM>7X?e#r>tlVl^3kqA#Gzuv{nH(3N}uO%OVnnAJ=c4bnMY{;XMJm-jmnL@s* zy(9$E2uSuSuO9LYsIfhe(n_}@Ze##?KbMUqPKEYcJR$0bX4-HqZ&38T+FCpMYbG~UjnMCp{j7oiK^??`zQ zsGm-~NF~1v%8i%=|jN2sq7#;bHz@(>`) z=RVv?M=xIw@>Jw%az{`Om1hFaW-_jHM&bf$An)um__BOKiO#8Iq`TC3nnP}qyUIpx zJzv>OS>zX6JfJx5F~qqiDx`5IPY^nfHGxvfD??9GDMr?_q>vif+Iz^l!j(5`qzkDxiXdxhtqnTS!<~~O zW4%3_ugG(%Jfi9}W%-bhRlMwDgg=x=ok7I+r#1TVU*m89&xN9rHKy=uMf9NXbS@&K zfNu<@Z26Qln-)lqwZc^1mxgE>`3XxhiF=)%CY`a)w|DKkrNMRSiL2q4rv}O2bc&Vi z?z9TJ_Og>0cd}oSMU_tPn#kHA%Nt$CLA_-$D^#-96j_6eBwb02nv3I}Pwm8wiw;Q3 z>(1FiQsxmd2r03v&L(kn*0L7TK1tePAuKhIgwhdGMysymbK>>n`9qR^dH#?$3TY9v zf=Sb$^ADtrkmaeg4_f)O16Ive@mqUeov*N#IPJq1GM2<=O1r<5Z%PKXhPIcNQX$l( zdzeqqO0k-r5N=yY1bH4)zq_8I#j)tyl3}`DK_#KoUQ~!yy&=iEYj-RBn0C)==mlv5 zHt;3Q@ilx|y0OLBqkc~%l=Hg+ed82#;L_$k?`9|{cP=;`H13LX<;2}?gE!c+VsBy&gBs8&Tv@!7=V;-ey z(IR!Ajq=LXkXCI<|2L++>oYg%(hqg1?=AFg1IGI;jHR1+UWankWd&_Q(5Y$C@y6S; zJb#vF+bKg2(tj3hZcj#=ft+Z_7jK}1*OB%H+Oa-u*MxmobEK-xc&8!0)axN7uFE{V ziTCUBya9D+LA^UbqdW3$ds-!)vNmNU5_YCN_3c5q(ny~Sx7-z}ZEGa8jj3;Ac9Ja_ zhY6HDgRv{LiY&^8lBcXnG=Gx`;&HTY>ulRxD5-mz8`*Nak*z~tJ&U>xq~1O0yClX^0qNAI zW5FE^AzeECkwnOjdD*JVH|mqV0bgvw$Y{j~N}!ZN`K8d8JsJ7ks8J`rm%vz%SE&@n zkN7j8jdMspfFM+eu)vdOcTsBh^j%rTNRB3?&urchUQ5WB{*16>Mo@P~QZl1iD6azU z^N34lq{)s*XZ~bR+y0CW+3yrET7=XUH!_$~$P!6=bFGQu?q!vi!o5blFhW9c2{o0? z2vdvp;{8lodJwZ;Hjna&FRCMZTCEFHc~|=^VN4|dkT(RauH&J8vO5#MAZezoVDu-u zVQFTy2ArXk@^ycVFC3!O`)Q3m)M^{?(#cDvB(HZu&x6jzG@Y5!kMvT}B)T(?^3CTx z?Y)K8(|NhtR}J8|>_p@G7?=>aI=A>v& z$=h)4%$Isl*ItZ@ZnRQozTTOuJTa#6rQW=kK|bD0c#N=}KH8DZ4XngyCvWTr0+CbLem*>URrs;zsIv9b>mP@6;y# z2Ij|2jP3e_hRlT~pD?gjv2s&A`KflGT%L{H_$|R(m zJYWeIC{B0`_d+a7XYESghsNfX&Suu`L`ZV!zlT$1c_$l4y=7;joJR7_7-EGt98K=B zS=U*SgJYvrXVS?#usrA{6EB}wLLtgaS_VCvNy#$l$t>R0s88c6M4D!58Z9Pyg5uJ7 zzYpJ+9hGRccKyQ7$cvj=Tp9;ibI23A_D|)EEP08M-&G-R$IwDLHC}sN(UJMQr9H8b z6WaT^{){!-h7wBolcYg@@MP&M9JQp{vX__KM7uz>s8HlChwP@bFVcz7 z!oyUNyG}co9p*gJi0TLfcsCNapZhbk>aB#+Jlo5A(gZ2J(#|J$m-8OM zJCYDd10;!QUuv8|*|Y|!$K}N?pJxM@H3Rsrn}?9d`kwsJPKl*ax&m5E(mx@g&qECX$hkA;^EXFd(AiDw$6D9Rw}b`4X;GsdQo5)2*c? zRRdu|q@$QgtLWSi(dz%du1I<%6sY!Y+F9ms?L$ejh?93V*8+Add1yS!qCvad5d_IC z)qAq-8OApyCDoH+(#T3cdvy5_l3qkoG1=XT%1Q&J8LS<<&Z>5OE^CJ`IgI*Vl3mwR zhU89?bIamh_CeB>$Qz?1b3*G^#!?FTCC_o1Ap?ljsL@y(LT}_T8e~}}M85n$mhuJd z{k7-lPcNi%)w9{fNU!xq=ZC92+V5)KY8NT_ zfS#)6k}a!u3n-6}(87*NzgfgL)%(&BmGaEh<+xb&;@EuAo=Ipld4Q7KdMqW^2q}-< zx$^rlvC*iyN;WM_xc2Cx4wA=&og-_-V~<;j%L+uWT2yDZA_uMa5);DX*$&(D3fi@GkJ= ziG(zG{ywpFGm~B%08NvRzyYkDeMp(kDwsyvKCx9Lg+5JYBzC3j+F`|0nwGS32YSCd zr4vt?zvKOvM33?qS-JFJ9&x%C zHfa#Gc1MhGPtsh-;z6fWXg4dFg(#*@nwdk}$ez~qiZ0)oqhlk?^^B|0^FRHp+EY4H z*YAzwC-O3*^B3e-(AAsj4DcLAtyb~^>Q+RGk$kO;T+{Ib90^NfHP1Wod+jxsuFTv{y1};jQFf#MP9_@1QiAvLsnR-Bysd>MNOy zcFc2#TS{3s6L#^9?Sz%&DWCDO7p`J_t)ZbrkqWaafAt#e!>OArIkd8s@Klm~$y;?|o<_cljc_GR zlACDXQ%r4T=OWwra?;B$tMqu1sYvFeRZYBi4*6(Q3GXLQuJRNj`ye42R2Hop!a3;d zymh3JU3fJ?diNcqkjJVc1YP&=yoS8D@C|u0+(NHOn<{Ot>o-Vl z_TxRFs9gMpdTTp#e>byjKS6!~PLTc#J$Z`yA7QlZU{qH#>m`rY{#qUyHt>8C?YNQh zNN*^=96C>F9r25KM||)kMukSM+HnQ5Tey$aJkv;&U8?G`jylLok$gc3J1HMe@`Ioj z7)Sj_&|~tR?ygyqi%O~~?nm=TluxKp*}rN$$QQZG)yR%%8o{-(5@%6HPC5@-QfpbE zDR=GF)bct%Pvbxyc3l0Icq!?D#Y@em?DHtQxE7uNRZTsG@7&Eh2l)CC-Z?>8By&7R z+uqJNI!`;?PFYTo&jH5W7T#OMGf}abw5Rq8vPg1me`VDm8m(QGEE=W3=}*q`-z%Fd zAs{79m3@`?kurX3Pb%56PS9}A=+vn+O9`|1J&l@b+|H+L+GXox8DS%IUVwXQQ&p@j z#No?YUptt1@@q`bNvhd}e3Q89?D6j8)P+{)$(y2a+P6zu>f(5XnU?l+331Z;Y1D0@ zCgPSPVU&zn=KxmoJ)J1Ko6>J2xHw!Fr@n<}2T60BugQneW`ZmTh2j#nY76gep~jL% z%DzWFgk)8*kyhG9nQEY4n;G4!xffz&K0P3fiZr|OcO-Pi4&K+PDK*q!Gc}OkqUE%e z>o2>6Jk>*zz6$#ybgsTH`S(~xxJI^6PV&a#T2ya{)nx>r!J#AwL_rI5w5EHH!xJS!z$zL#_w*Q^*GLgxrwq8+X{^m68E=K+T+Z@y-;I$8{AHfb-w0K($w&`k+)WYeqF%V zsu+_R2|~)qAJGz0F5qpQFI7ppNii(NF=}|4lHEoP&(VrUsf|2^Z6~OP`?>CCpeJ9G0 zL`b6rl1bZz61Sn%uF2=ZE3G|^nOQLqZ^|(nRI=L(+HB+ zD%l6A_oe-qPLLmOp};p%cj0b@V-!|%C*|E4YkQq+x{zm8q*+3#g~2?;^%S)@!`g6y zRz1euar*Bh6yq4ZvztC$k92e?9wrytYxK_teEU;!`IzuNWqF6(J|Ks$>5o6i&+iKIlC>y^I!QwkoT@}R$D`7owaf{SUU$BcnA9>r`qZ}*wQ=8v3Xl+sm?;{>mXNh z<`S^Mci6r0-Z0XY@$OjCl@VX!AlGo%O*vp>c)yVR1~^y@IQuNYIc)8nql6RIfzZ-9 zW;YY(P^O>hlOL(mRoXX5y#lo1kCg3m?%$>kZ_t0Q(^~(arCy~z&r+XP!reaFW-G0=oOYTP8<(;K*#{jJnnL;+$#|>jw~g?vl3Oq5bcIFsBjHQV2KrZY zn|&1BXRk*O+7rY7;ZtG5H%YC!xcB@aCo1^RP#^^e`23dS4vdw;s?6$8X`|Qiee)~7~ z|A}n3A0k`r+sGE;Hrf}Fb;Pf>Um~kWzsACm_4WtxKXLtr_|GHD?6b%U`-F5K@y`2^ z8hej?-{<+qk)=GJOa3$Md%~}g+2)DPv%1u`RTO^$)H#Xrq!KcsYb__rJ}b(Kij7}I zl-*er)IZmy*y+`9y_Me&V=wtEc7A^)&0nztd^tMP-Y5JNosLYq5}C|o=Acd$ktcZR z^t7_dL#;e#36DRL@mq#R)C$J!MEZCtwwE&*m$R$^o%Cum_Kwp5=DRv(O&$RN3D>v+Z-wEc?+j!@{0v zc7u1Owe^Ar^@6PRuCr0zH8#%6I_SlFt#_X7;r=3VkMry$?{a(HJImhljinB{1IxG1n?1 z_~MPsxBDUsnO94g&&#ZD6wZb7#WqH#+WF`dXvYlJlliQ1(izJa-#S*54UG5A_6@YT z1@y4k*+c*Bx69DKH=%`5=Ztlsf5&=G*e1^byVJAXUh}N8pBdkc7_-@o%^8f7ecp}s zsFykJ-EMz)cUdFfK1=uQw+URg`1aa8T;K8?u-|-#t)Bmob@A`DetxX1{M9zs&$)vB zm3Gd*-2UWWWUu^=W1`-Jc{_gDQ3?Ao}6)+BD8b&9L9?s1E)Yup-3h+A)6;?`PE zo@K^y8cAFw0Y4!8P9k)Un_;cu&{xHk*mZHG_J@CxeMX)y`={H({tCO@Ut~x9gKeiD zESewtazEUpf0(uL543BE|IJro|MrcucYIUq&%Q}^#K-wmK2As=bn%thjlPNYy?44j z;hkX{z2H{7ldK+d>2ptoJ?w$!^ORc&_l-T{?Neuh-Q!HMEe;Y62MU;u7nOQH80`s^y}dW`t!&l>kwIPR~Z8@hWFa(@CjQM zK54_l2d!IptKH1_aKg~#(0a!IR(mb9-R=ub}YEp z76p%6N${Ly22WZ$e%~D2U_PFQ0~;(D*vIvZd4ji_H>m3&bAr3ff;AQm?6LsQe+ule zF9O@`{Xn(79av`X2A0^zT)z#hwci7qtX6Qd#gk`75H6JYGcO3|&-}T-^)JkqPlD4d z!0)=uiT2E$R6>4;{W9}%GV^nOh%@Y%H%CJgna5-8{ZOe{7`5#(JF5X=!;mg_d+&P6KhsmNI7NQL>LldL6cLppP$gtcc0^yaqcTziM# zwOJVwq5YE1%CDi$ra3~~GtjJW`E?C^#Le_&eW%=Raj;`|imZpjdC1();JVHkYKNR5 zb~nFYbjI0NtRaqPvDIU3XyRF9Ej@Fs9qT|h)`KM0h9u@wBJ-*fp*{DVNZW(;B8BIv zq)XvG*~2BhIOfwd0fI8Rb-EH zy-0aavW6X_?5CZH{2p!hQr?Fs=VPS(6VD%Wuq`6o%9!2jfEH)%TM2))7(Qw)VLbOk z;JGqc?|Q*wr8t``8-8ktv&BXera0ilSqD})V5*`2TUZY^vmR`8pf=EzYOd>88`cum zI0uM3YP+3Nl;@<~2`~5%H0TNPc!|6}bXM4}th4p0cMlJC7Obfio-H;LI#k74G>7$e zng=Tp51t1-^fT>RL%JiZyC*!W?WAXgouMtS(B{u`{geOE!syhp4PaW{JR*>BL)PY5q~_Ss#mu{$X9bohn- zl)JsB+UiixtMJ9|(th{TZrfAH5DI@1dobx?d$9AV#s2NJ z=t$cIpENu=p7B1`B9W2sWySV9*YlBbTgyBtV{Y_heXT?Mzr#c9h44_jJ&bK~7&#+h zXc+l0aZL!}&~W=OG}4|RoaDYb1eP!H0J;fZtLm&>@$fj3zL|GblM3?BJTo<9SR z`Vk?-@5XTVl0glE=&-W1I4lbxS4%PL+F1a=20_f zLtExq59XxyFvaxg81_2job{{~D{LBjocXL5D|v4n@oNbSNi&6cQ34(6&$D#$O61y} zx!Quc+W?w#3-i1_?>2%*Z7$x_sp866frJ}agN$cC!4G`QzVBuD;z!u~o~QH&;FD#Y zFpH5i5?)z|!X!dB##C2UDfy+6->o)0Z$ExVZ_OxZZQ^C9L znMm@9As-l}j7`aoxAFTp-?)Q3?kE0v3@W%eIvqnCBM3G+KG zHs{6j;vW@0`Z4(7H@)zD-pS_iv3B^tEc(z&`3h{UufLt*`X}EYd)+tGJ}3TX?jycj z^ZPTcwm-@05}NwaIrz~r_;W1L-`f)WcnCyXM#HNU_1rP!T5bO=6l6}}$U#+PB&Ql9^K2irfq`S9^s(4Ayk z=}m)X;Zw(phRh4DhI-xLEwT4KgYBFLuT-=_BTtFF%6hOGn%j`8GAs$EF7|Y)Jqg0|ESk2gcYVfeG*#74Rf@ zY$UJ2nz8_6&Uc7r|1y9(vM;@-L^{ zk5cZd@agTt_;v{+(I%`3<8c`t<6;<^7anG>h4bx`aGw1T&b0{l^&-Qq2eh+1GS2o# z@ZA$Bw1!di_k?}XVfIRN1bDU*=7+S4k|O7_*OV98Vou;-eP%EEA@k)csK-^t{!LCU za*Z7Lh(4^rnXG}S@C@jr7~dN_iFVP`&7SjgXJ3|N|Mm2?+TL9DYG|{(1FgMxh&5&( zcpc-w$GEOdXvlbI&+jCzd5rh5><5>6``Za`hCS&`foJc--f=K{$TIudTfyFOjQy2; zR^VptnoUI7oU=`t;EsN)^!_8SwT3D@cOS>i<&-&8bJmF>* zM_j#dEB2RdEQc_P=aoEPMA#H=YsbQ!?9Olkb0fj-4|lY?h*Nl!``5zVSbI{;7s;@u zku1vB+eT2%)sa-@Rw{CmEcU$kzKeqC#xKzhd_CR6{XaNe^T%iwGNBaf&MG5sLGx&h zeNfs*psZh77i;1quofi1M?lkN4jv4?!k?Gs-ktK)BA$^OPx=BKXEv>pC-cGlkt zx)pEFas8XWv%Lx3dK((}jz7yjf{uMj-1q*D_KUx}1^wCP#0{}paU-oBp<&!Gs~?xk zzAV*#xDbo^PstxgFS!Z8gyBRuo4Rkr= z9cusf4!4)RBkfi%9^?33OntgS4;{jDo&mNL8raHHWUn|Q;Wt5MIFl^`KmRFX}G zj{Y7-AIB4}!n?c~>dt`ll2%pr-o(VO#rwNaSTHD1?dpi{Bfcyc!h@npKONrJr zlx7ynM(&V}e4)3!8qBiCpl6qY;7_1Ymx811$>4D03EVI40oryfoMo588TJU_8Ny59bbBM5YM+La>_>tx0=_kpX(`ONvItr>u2&et zA2YshV7#U=mMa*)tE2e4;Chj9e1Ei`{R!V?Pcyb(jY4ap)9oA9nx9!~Lhz{9z!NlO zzGpD&WnZN;0PZII6@KGwV!k0R0&j8)Ygillu?_241J*SU{J{^c1Bhem&G&{(Ny9c6u zx$n<@b)enH8uJwI{*CYI}%$1vp0&)ubI_B=nSZZ%vt#Sak6unvX5MD`z!(trGk+g~k9reX%1^#x=*w{H=*NpN zA&>ikeo$Z=>+cW<5$csAI8~!fPo}TsreAqY8 zpg;V5?V7k8yFM-(*-@57;LE=A_lGtOw8#B}>>KL!yqEohx7;>EU&ga`CQ`?cXSBUcy-#qR&3f3&lW*TT1$NZIk2w5y9K833 zjFA^u_a9>no?t`^OZOk>%4FuotE^L5ks|vm{8JTkE;)=}J=TZ1;r6U8pz6b58Q`m4 zW?p^;kMTjcC;UfC_BD;|n@}@b#NO$*U?*E0Ot8AaMEi3f$#w^N+ok}gI0tg=FM&Md zE(0tfIMhZ!KjtByT!dV6La@Tpf@SbbBkga2GCLbU10Ud2zW}}>0@LhxV2V8fU-fZd zyj_L2igF(z?rNaYz9PI$+7|;83FGVoLM`$g9Gql_ke7ZKoP*4Bfn_4gEJLO_8~U;U z`ZE>T=K%I^t&ny4LRH8|mqL>`@1C?Pk#{~7nryYhAO~rSA^)%G9fRP+a@arAd*VASuqf%KoS|LEh?^MvgdroV#QCr?21^ zUqz$$JQVTo(6UdN!+#)0zkxko6Zoel&UMV$+R(g2Xrz2u7HQ8X0Y5cS4r<44h>)c7d;4f+c6 zp%1(oO>)?6LZA1r7rqDD{THVlJWf4(!Rc(z(Ppne%Rhscyw=ms(mZXD3#HP|gWyXG ztS)<_f$%wN;Bl`of8S?5``E*E^Z+kq;H75;e%)KwP#YCp2jy^5-sm^B3P#d)7C_ z&iKl0yANcHkMpRZOC90Seur;=*_&*)Q@2&z5A-HkEpIz}($mBids?suPO|Ty-REeR zk803D+_k8KEZfP47P>tbVH7u zVfEorg3zP)*tb0v!0Te5t5pZu+C)NTpqn)gK%4*QZ~y%x$A0>wyZ!b@OAG$d%)EgP zc2gk5+6S^NHvldf`nDv{(>AaN+!eq|B2ZxWLGNA+bY>6O)IJF`u@3|7_)afm(3$o% z>4Mzf9PDgegKeNuO|2?e4_S9D_Mf$&LAO}<5LQaaqBe%I>;iRr5xLX9LM<%-pLTt? zoi&HYNep8l04sGh?7DCCH15Jj<*UnQ*0NnpJxyBlDcZ`aH$nb``)vN{n`q6BuI#h(7Gj2s*L|XYmqF5PyQ-XSm)*xWN5IbkLV0htQLqvL_

    ~GMt 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 X6 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

    ^U8dd6-QWY9K*}vx6Fm} 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
    pW&Y8SkvdX(?z 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 Iwr1l00LVQ`ZU6uP literal 0 HcmV?d00001 diff --git a/test/torchaudio_unittest/assets/vec_flt.ark b/test/torchaudio_unittest/assets/vec_flt.ark new file mode 100644 index 0000000000000000000000000000000000000000..ed74a0b2859aaf888099e9b2defe417462946102 GIT binary patch literal 81 zcmc~!tu$0%aB>S%U}0tefd+dZ;Q&Mq*{PLAC{hNFKw%&ba0E&jqe!g)3U2`79gYAd C6%Dok literal 0 HcmV?d00001 diff --git a/test/torchaudio_unittest/assets/vec_int.ark b/test/torchaudio_unittest/assets/vec_int.ark new file mode 100644 index 0000000000000000000000000000000000000000..d8892ad47a33155619c66eba9e8f0d6df6d1caee GIT binary patch literal 81 zcmc~!tu$0%aAIL*U|?Wj1QARi0>sTutu%s5af3v8Km;#D$`~#s3KA6q5#m4s05jbN AJpcdz literal 0 HcmV?d00001 diff --git a/test/torchaudio_unittest/assets/wav2vec2/fairseq/generate_hubert_model_config.py b/test/torchaudio_unittest/assets/wav2vec2/fairseq/generate_hubert_model_config.py new file mode 100644 index 00000000..28bf3349 --- /dev/null +++ b/test/torchaudio_unittest/assets/wav2vec2/fairseq/generate_hubert_model_config.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +"""Generate the conf JSONs from fairseq pretrained weight file, consumed by unit tests + +Note: + The current configuration files were generated on fairseq e47a4c84 + +Usage: +1. Download pretrained parameters from https://github.com/pytorch/fairseq/tree/main/examples/hubert +2. Run this script and save the resulting JSON configuration in assets directory. + +Example: + +``` +python generate_hubert_model_config.py \ + --model-file hubert_base_ls960.pt \ + > hubert_base_ls960.json + +python generate_hubert_model_config.py \ + --model-file hubert_large_ll60k.pt \ + > hubert_large_ll60k.json + +python generate_hubert_model_config.py \ + --model-file hubert_large_ll60k_finetune_ls960.pt \ + > hubert_large_ll60k_finetune_ls960.json + +python generate_hubert_model_config.py \ + --model-file hubert_xlarge_ll60k.pt \ + > hubert_large_ll60k.json + +python generate_hubert_model_config.py \ + --model-file hubert_xlarge_ll60k_finetune_ls960.pt \ + > hubert_large_ll60k_finetune_ls960.json +``` +""" +import json +import argparse + + +def _parse_args(): + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawTextHelpFormatter, + ) + parser.add_argument( + '--model-file', + required=True, + help=( + 'A pt file from ' + 'https://github.com/pytorch/fairseq/tree/main/examples/hubert' + ) + ) + return parser.parse_args() + + +def _load(model_file): + import fairseq + from omegaconf import OmegaConf + + models, cfg, _ = fairseq.checkpoint_utils.load_model_ensemble_and_task([model_file]) + model = models[0] + cfg = OmegaConf.to_container(cfg) + return model, cfg + + +def _main(): + args = _parse_args() + model, cfg = _load(args.model_file) + + if model.__class__.__name__ == 'HubertModel': + cfg['task']['data'] = '/foo/bar' + cfg['task']['label_dir'] = None + conf = { + '_name': 'hubert', + 'model': cfg['model'], + 'task': cfg['task'], + 'num_classes': model.num_classes, + } + elif model.__class__.__name__ == 'HubertCtc': + conf = cfg['model'] + del conf['w2v_path'] + keep = ['_name', 'task', 'model'] + for key in list(k for k in conf['w2v_args'] if k not in keep): + del conf['w2v_args'][key] + conf['data'] = '/foo/bar/' + conf['w2v_args']['task']['data'] = '/foo/bar' + conf['w2v_args']['task']['labels'] = [] + conf['w2v_args']['task']['label_dir'] = '/foo/bar' + print(json.dumps(conf, indent=4, sort_keys=True)) + + +if __name__ == '__main__': + _main() diff --git a/test/torchaudio_unittest/assets/wav2vec2/fairseq/generate_wav2vec2_model_config.py b/test/torchaudio_unittest/assets/wav2vec2/fairseq/generate_wav2vec2_model_config.py new file mode 100644 index 00000000..2d651a1b --- /dev/null +++ b/test/torchaudio_unittest/assets/wav2vec2/fairseq/generate_wav2vec2_model_config.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +"""Generate the conf JSON from fairseq pretrained weight file, that is consumed by unit tests + +Usage: +1. Download pretrained parameters from https://github.com/pytorch/fairseq/tree/main/examples/wav2vec +2. Download the dict from https://dl.fbaipublicfiles.com/fairseq/wav2vec/dict.ltr.txt + and put it in the same directory as parameter files. +3. Run this script and save the resulting JSON configuration in assets directory. + +Example: + +``` +# Pretrained +python generate_wav2vec2_model_config.py \ + --model-file wav2vec_small.pt \ + > wav2vec_small.json + +python generate_wav2vec2_model_config.py \ + --model-file libri960_big.pt \ + > libri960_big.json + +python generate_wav2vec2_model_config.py \ + --model-file wav2vec_vox_new.pt \ + > wav2vec_vox_new.json + +# Fine-tuned +python generate_wav2vec2_model_config.py \ + --model-file wav2vec_small_960h.pt \ + > wav2vec_small_960h.json + +python generate_wav2vec2_model_config.py \ + --model-file wav2vec_big_960h.pt \ + > wav2vec_large_960h.json + +python generate_wav2vec2_model_config.py \ + --model-file wav2vec2_vox_960h_new.pt \ + > wav2vec_large_lv60_960h.json + +python generate_wav2vec2_model_config.py \ + --model-file wav2vec_vox_960h_pl.pt \ + > wav2vec_large_lv60_self_960h.json +``` +""" +import os +import json +import argparse + + +def _parse_args(): + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawTextHelpFormatter, + ) + parser.add_argument( + '--model-file', + required=True, + help=( + 'A point file from ' + 'https://github.com/pytorch/fairseq/tree/main/examples/wav2vec' + ) + ) + parser.add_argument( + '--dict-dir', + help=( + 'Directory where `dict.ltr.txt` file is found. ' + 'Default: the directory of the given model.' + ) + ) + args = parser.parse_args() + if args.dict_dir is None: + args.dict_dir = os.path.dirname(args.model_file) + return args + + +def _to_json(conf): + import yaml + from omegaconf import OmegaConf + return yaml.safe_load(OmegaConf.to_yaml(conf)) + + +def _load(model_file, dict_dir): + import fairseq + + overrides = {'data': dict_dir} + _, args, _ = fairseq.checkpoint_utils.load_model_ensemble_and_task( + [model_file], arg_overrides=overrides + ) + return _to_json(args['model']) + + +def _main(): + args = _parse_args() + conf = _load(args.model_file, args.dict_dir) + + if conf['_name'] == 'wav2vec_ctc': + del conf['data'] + del conf['w2v_args']['task']['data'] + conf['w2v_args'] = { + key: conf['w2v_args'][key] for key in ['model', 'task'] + } + + print(json.dumps(conf, indent=4, sort_keys=True)) + + +if __name__ == '__main__': + _main() diff --git a/test/torchaudio_unittest/assets/wav2vec2/fairseq/hubert_base_ls960.json b/test/torchaudio_unittest/assets/wav2vec2/fairseq/hubert_base_ls960.json new file mode 100644 index 00000000..7c6d7ad3 --- /dev/null +++ b/test/torchaudio_unittest/assets/wav2vec2/fairseq/hubert_base_ls960.json @@ -0,0 +1,69 @@ +{ + "_name": "hubert", + "model": { + "_name": "hubert", + "activation_dropout": 0.0, + "activation_fn": "gelu", + "attention_dropout": 0.1, + "conv_bias": false, + "conv_feature_layers": "[(512,10,5)] + [(512,3,2)] * 4 + [(512,2,2)] * 2", + "conv_pos": 128, + "conv_pos_groups": 16, + "dropout": 0.1, + "dropout_features": 0.1, + "dropout_input": 0.1, + "encoder_attention_heads": 12, + "encoder_embed_dim": 768, + "encoder_ffn_embed_dim": 3072, + "encoder_layerdrop": 0.05, + "encoder_layers": 12, + "extractor_mode": "default", + "feature_grad_mult": 0.1, + "final_dim": 256, + "label_rate": 50, + "latent_temp": [ + 2.0, + 0.5, + 0.999995 + ], + "layer_norm_first": false, + "logit_temp": 0.1, + "mask_channel_length": 10, + "mask_channel_min_space": 1, + "mask_channel_other": 0.0, + "mask_channel_prob": 0.0, + "mask_channel_selection": "static", + "mask_length": 10, + "mask_min_space": 1, + "mask_other": 0.0, + "mask_prob": 0.8, + "mask_selection": "static", + "no_mask_channel_overlap": false, + "no_mask_overlap": false, + "skip_masked": false, + "skip_nomask": false, + "target_glu": false, + "untie_final_proj": false + }, + "num_classes": [ + 504 + ], + "task": { + "_name": "hubert_pretraining", + "data": "/foo/bar", + "enable_padding": false, + "fine_tuning": false, + "label_dir": null, + "label_rate": 50, + "labels": [ + "layer6.km500" + ], + "max_sample_size": 250000, + "min_sample_size": 32000, + "normalize": false, + "pad_audio": false, + "random_crop": true, + "sample_rate": 16000, + "single_target": false + } +} diff --git a/test/torchaudio_unittest/assets/wav2vec2/fairseq/hubert_large_ll60k.json b/test/torchaudio_unittest/assets/wav2vec2/fairseq/hubert_large_ll60k.json new file mode 100644 index 00000000..a1b14810 --- /dev/null +++ b/test/torchaudio_unittest/assets/wav2vec2/fairseq/hubert_large_ll60k.json @@ -0,0 +1,68 @@ +{ + "_name": "hubert", + "model": { + "_name": "hubert", + "activation_dropout": 0.0, + "activation_fn": "gelu", + "attention_dropout": 0.0, + "conv_bias": false, + "conv_feature_layers": "[(512,10,5)] + [(512,3,2)] * 4 + [(512,2,2)] * 2", + "conv_pos": 128, + "conv_pos_groups": 16, + "dropout": 0.0, + "dropout_features": 0.0, + "dropout_input": 0.0, + "encoder_attention_heads": 16, + "encoder_embed_dim": 1024, + "encoder_ffn_embed_dim": 4096, + "encoder_layerdrop": 0.0, + "encoder_layers": 24, + "extractor_mode": "layer_norm", + "feature_grad_mult": 1.0, + "final_dim": 768, + "label_rate": 50, + "latent_temp": [ + 2.0, + 0.5, + 0.999995 + ], + "layer_norm_first": true, + "logit_temp": 0.1, + "mask_channel_length": 10, + "mask_channel_min_space": 1, + "mask_channel_other": 0.0, + "mask_channel_prob": 0.0, + "mask_channel_selection": "static", + "mask_length": 10, + "mask_min_space": 1, + "mask_other": 0.0, + "mask_prob": 0.8, + "mask_selection": "static", + "no_mask_channel_overlap": false, + "no_mask_overlap": false, + "skip_masked": false, + "skip_nomask": true, + "target_glu": false, + "untie_final_proj": true + }, + "num_classes": [ + 504 + ], + "task": { + "_name": "hubert_pretraining", + "data": "/foo/bar", + "enable_padding": false, + "label_dir": null, + "label_rate": 50, + "labels": [ + "lyr9.km500" + ], + "max_sample_size": 250000, + "min_sample_size": 32000, + "normalize": true, + "pad_audio": false, + "random_crop": true, + "sample_rate": 16000, + "single_target": false + } +} diff --git a/test/torchaudio_unittest/assets/wav2vec2/fairseq/hubert_large_ll60k_finetune_ls960.json b/test/torchaudio_unittest/assets/wav2vec2/fairseq/hubert_large_ll60k_finetune_ls960.json new file mode 100644 index 00000000..c50c8f1a --- /dev/null +++ b/test/torchaudio_unittest/assets/wav2vec2/fairseq/hubert_large_ll60k_finetune_ls960.json @@ -0,0 +1,89 @@ +{ + "_name": "hubert_ctc", + "activation_dropout": 0.1, + "apply_mask": true, + "attention_dropout": 0.0, + "data": "/foo/bar/", + "dropout": 0.0, + "dropout_input": 0.0, + "feature_grad_mult": 0.0, + "final_dropout": 0.0, + "freeze_finetune_updates": 10000, + "layerdrop": 0.1, + "mask_channel_length": 64, + "mask_channel_other": 0.0, + "mask_channel_prob": 0.25, + "mask_channel_selection": "static", + "mask_length": 10, + "mask_other": 0.0, + "mask_prob": 0.5, + "mask_selection": "static", + "no_mask_channel_overlap": false, + "no_mask_overlap": false, + "no_pretrained_weights": false, + "normalize": true, + "w2v_args": { + "_name": null, + "model": { + "_name": "hubert", + "activation_dropout": 0.1, + "activation_fn": "gelu", + "attention_dropout": 0.0, + "conv_bias": false, + "conv_feature_layers": "[(512,10,5)] + [(512,3,2)] * 4 + [(512,2,2)] * 2", + "conv_pos": 128, + "conv_pos_groups": 16, + "dropout": 0.0, + "dropout_features": 0.0, + "dropout_input": 0.0, + "encoder_attention_heads": 16, + "encoder_embed_dim": 1024, + "encoder_ffn_embed_dim": 4096, + "encoder_layerdrop": 0.1, + "encoder_layers": 24, + "extractor_mode": "layer_norm", + "feature_grad_mult": 0.0, + "final_dim": 768, + "label_rate": 50, + "latent_temp": [ + 2.0, + 0.5, + 0.999995 + ], + "layer_norm_first": true, + "logit_temp": 0.1, + "mask_channel_length": 64, + "mask_channel_min_space": 1, + "mask_channel_other": 0.0, + "mask_channel_prob": 0.25, + "mask_channel_selection": "static", + "mask_length": 10, + "mask_min_space": 1, + "mask_other": 0.0, + "mask_prob": 0.5, + "mask_selection": "static", + "no_mask_channel_overlap": false, + "no_mask_overlap": false, + "skip_masked": false, + "skip_nomask": true, + "target_glu": false, + "untie_final_proj": true + }, + "task": { + "_name": "hubert_pretraining", + "data": "/foo/bar", + "enable_padding": false, + "fine_tuning": false, + "label_dir": "/foo/bar", + "label_rate": 50, + "labels": [], + "max_sample_size": 250000, + "min_sample_size": 32000, + "normalize": true, + "pad_audio": false, + "random_crop": true, + "sample_rate": 16000, + "single_target": false + } + } +} diff --git a/test/torchaudio_unittest/assets/wav2vec2/fairseq/hubert_xtralarge_ll60k.json b/test/torchaudio_unittest/assets/wav2vec2/fairseq/hubert_xtralarge_ll60k.json new file mode 100644 index 00000000..2ade77dd --- /dev/null +++ b/test/torchaudio_unittest/assets/wav2vec2/fairseq/hubert_xtralarge_ll60k.json @@ -0,0 +1,68 @@ +{ + "_name": "hubert", + "model": { + "_name": "hubert", + "activation_dropout": 0.0, + "activation_fn": "gelu", + "attention_dropout": 0.0, + "conv_bias": false, + "conv_feature_layers": "[(512,10,5)] + [(512,3,2)] * 4 + [(512,2,2)] * 2", + "conv_pos": 128, + "conv_pos_groups": 16, + "dropout": 0.0, + "dropout_features": 0.0, + "dropout_input": 0.0, + "encoder_attention_heads": 16, + "encoder_embed_dim": 1280, + "encoder_ffn_embed_dim": 5120, + "encoder_layerdrop": 0.0, + "encoder_layers": 48, + "extractor_mode": "layer_norm", + "feature_grad_mult": 1.0, + "final_dim": 1024, + "label_rate": 50, + "latent_temp": [ + 2.0, + 0.5, + 0.999995 + ], + "layer_norm_first": true, + "logit_temp": 0.1, + "mask_channel_length": 10, + "mask_channel_min_space": 1, + "mask_channel_other": 0.0, + "mask_channel_prob": 0.0, + "mask_channel_selection": "static", + "mask_length": 10, + "mask_min_space": 1, + "mask_other": 0.0, + "mask_prob": 0.8, + "mask_selection": "static", + "no_mask_channel_overlap": false, + "no_mask_overlap": false, + "skip_masked": false, + "skip_nomask": true, + "target_glu": false, + "untie_final_proj": true + }, + "num_classes": [ + 504 + ], + "task": { + "_name": "hubert_pretraining", + "data": "/foo/bar", + "enable_padding": false, + "label_dir": null, + "label_rate": 50, + "labels": [ + "lyr9.km500" + ], + "max_sample_size": 250000, + "min_sample_size": 32000, + "normalize": true, + "pad_audio": false, + "random_crop": true, + "sample_rate": 16000, + "single_target": false + } +} diff --git a/test/torchaudio_unittest/assets/wav2vec2/fairseq/hubert_xtralarge_ll60k_finetune_ls960.json b/test/torchaudio_unittest/assets/wav2vec2/fairseq/hubert_xtralarge_ll60k_finetune_ls960.json new file mode 100644 index 00000000..9a830702 --- /dev/null +++ b/test/torchaudio_unittest/assets/wav2vec2/fairseq/hubert_xtralarge_ll60k_finetune_ls960.json @@ -0,0 +1,89 @@ +{ + "_name": "hubert_ctc", + "activation_dropout": 0.1, + "apply_mask": true, + "attention_dropout": 0.0, + "data": "/foo/bar/", + "dropout": 0.0, + "dropout_input": 0.0, + "feature_grad_mult": 0.0, + "final_dropout": 0.0, + "freeze_finetune_updates": 10000, + "layerdrop": 0.1, + "mask_channel_length": 64, + "mask_channel_other": 0.0, + "mask_channel_prob": 0.25, + "mask_channel_selection": "static", + "mask_length": 10, + "mask_other": 0.0, + "mask_prob": 0.5, + "mask_selection": "static", + "no_mask_channel_overlap": false, + "no_mask_overlap": false, + "no_pretrained_weights": false, + "normalize": true, + "w2v_args": { + "_name": null, + "model": { + "_name": "hubert", + "activation_dropout": 0.1, + "activation_fn": "gelu", + "attention_dropout": 0.0, + "conv_bias": false, + "conv_feature_layers": "[(512,10,5)] + [(512,3,2)] * 4 + [(512,2,2)] * 2", + "conv_pos": 128, + "conv_pos_groups": 16, + "dropout": 0.0, + "dropout_features": 0.0, + "dropout_input": 0.0, + "encoder_attention_heads": 16, + "encoder_embed_dim": 1280, + "encoder_ffn_embed_dim": 5120, + "encoder_layerdrop": 0.1, + "encoder_layers": 48, + "extractor_mode": "layer_norm", + "feature_grad_mult": 0.0, + "final_dim": 1024, + "label_rate": 50, + "latent_temp": [ + 2.0, + 0.5, + 0.999995 + ], + "layer_norm_first": true, + "logit_temp": 0.1, + "mask_channel_length": 64, + "mask_channel_min_space": 1, + "mask_channel_other": 0.0, + "mask_channel_prob": 0.25, + "mask_channel_selection": "static", + "mask_length": 10, + "mask_min_space": 1, + "mask_other": 0.0, + "mask_prob": 0.5, + "mask_selection": "static", + "no_mask_channel_overlap": false, + "no_mask_overlap": false, + "skip_masked": false, + "skip_nomask": true, + "target_glu": false, + "untie_final_proj": true + }, + "task": { + "_name": "hubert_pretraining", + "data": "/foo/bar", + "enable_padding": false, + "fine_tuning": false, + "label_dir": "/foo/bar", + "label_rate": 50, + "labels": [], + "max_sample_size": 250000, + "min_sample_size": 32000, + "normalize": true, + "pad_audio": false, + "random_crop": true, + "sample_rate": 16000, + "single_target": false + } + } +} diff --git a/test/torchaudio_unittest/assets/wav2vec2/fairseq/libri960_big.json b/test/torchaudio_unittest/assets/wav2vec2/fairseq/libri960_big.json new file mode 100644 index 00000000..3add6113 --- /dev/null +++ b/test/torchaudio_unittest/assets/wav2vec2/fairseq/libri960_big.json @@ -0,0 +1,54 @@ +{ + "_name": "wav2vec2", + "activation_dropout": 0.0, + "activation_fn": "gelu", + "attention_dropout": 0.1, + "codebook_negatives": 0, + "conv_bias": false, + "conv_feature_layers": "[(512, 10, 5)] + [(512, 3, 2)] * 4 + [(512,2,2)] * 2", + "conv_pos": 128, + "conv_pos_groups": 16, + "cross_sample_negatives": 0, + "dropout": 0.0, + "dropout_features": 0.1, + "dropout_input": 0.1, + "encoder_attention_heads": 16, + "encoder_embed_dim": 1024, + "encoder_ffn_embed_dim": 4096, + "encoder_layerdrop": 0.2, + "encoder_layers": 24, + "extractor_mode": "default", + "feature_grad_mult": 0.1, + "final_dim": 768, + "latent_dim": 0, + "latent_groups": 2, + "latent_temp": [ + 2.0, + 0.5, + 0.999995 + ], + "latent_vars": 320, + "layer_norm_first": false, + "logit_temp": 0.1, + "mask_channel_before": false, + "mask_channel_length": 10, + "mask_channel_min_space": 1, + "mask_channel_other": 0.0, + "mask_channel_prob": 0.0, + "mask_channel_selection": "static", + "mask_length": 10, + "mask_min_space": 1, + "mask_other": 0.0, + "mask_prob": 0.65, + "mask_selection": "static", + "negatives_from_everywhere": false, + "no_mask_channel_overlap": false, + "no_mask_overlap": false, + "num_negatives": 100, + "quantize_input": false, + "quantize_targets": true, + "quantizer_depth": 1, + "quantizer_factor": 3, + "same_quantizer": false, + "target_glu": false +} diff --git a/test/torchaudio_unittest/assets/wav2vec2/fairseq/wav2vec_large_960h.json b/test/torchaudio_unittest/assets/wav2vec2/fairseq/wav2vec_large_960h.json new file mode 100644 index 00000000..d46b38ee --- /dev/null +++ b/test/torchaudio_unittest/assets/wav2vec2/fairseq/wav2vec_large_960h.json @@ -0,0 +1,146 @@ +{ + "_name": "wav2vec_ctc", + "activation_dropout": 0.1, + "apply_mask": true, + "attention_dropout": 0.0, + "blank_mode": "add", + "blank_weight": 0.0, + "conv_feature_layers": "[(512, 10, 5)] + [(512, 3, 2)] * 4 + [(512,2,2)] + [(512,2,2)]", + "dropout": 0.0, + "dropout_input": 0.0, + "encoder_embed_dim": 512, + "feature_grad_mult": 0.0, + "final_dropout": 0.0, + "freeze_finetune_updates": 10000, + "layerdrop": 0.2, + "mask_channel_before": false, + "mask_channel_length": 64, + "mask_channel_min_space": 1, + "mask_channel_other": 0.0, + "mask_channel_prob": 0.1, + "mask_channel_selection": "static", + "mask_length": 10, + "mask_min_space": 1, + "mask_other": 0.0, + "mask_prob": 0.5, + "mask_selection": "static", + "no_mask_channel_overlap": false, + "no_mask_overlap": false, + "no_pretrained_weights": false, + "normalize": false, + "w2v_args": { + "model": { + "_name": "wav2vec2", + "activation_dropout": 0.0, + "activation_fn": "gelu", + "attention_dropout": 0.1, + "codebook_negatives": 0, + "conv_bias": false, + "conv_feature_layers": "[(512, 10, 5)] + [(512, 3, 2)] * 4 + [(512,2,2)] * 2", + "conv_pos": 128, + "conv_pos_groups": 16, + "cross_sample_negatives": 0, + "dropout": 0.0, + "dropout_features": 0.1, + "dropout_input": 0.1, + "encoder_attention_heads": 16, + "encoder_embed_dim": 1024, + "encoder_ffn_embed_dim": 4096, + "encoder_layerdrop": 0.2, + "encoder_layers": 24, + "extractor_mode": "default", + "feature_grad_mult": 0.1, + "final_dim": 768, + "latent_dim": 0, + "latent_groups": 2, + "latent_temp": [ + 2.0, + 0.5, + 0.999995 + ], + "latent_vars": 320, + "layer_norm_first": false, + "logit_temp": 0.1, + "mask_channel_before": false, + "mask_channel_length": 10, + "mask_channel_min_space": 1, + "mask_channel_other": 0.0, + "mask_channel_prob": 0.0, + "mask_channel_selection": "static", + "mask_length": 10, + "mask_min_space": 1, + "mask_other": 0.0, + "mask_prob": 0.65, + "mask_selection": "static", + "negatives_from_everywhere": false, + "no_mask_channel_overlap": false, + "no_mask_overlap": false, + "num_negatives": 100, + "quantize_input": false, + "quantize_targets": true, + "quantizer_depth": 1, + "quantizer_factor": 3, + "same_quantizer": false, + "target_glu": false + }, + "task": { + "_name": "audio_pretraining", + "autoregressive": false, + "binarized_dataset": false, + "enable_padding": false, + "eval_wer": false, + "eval_wer_config": { + "beam": 5, + "constraints": null, + "decoding_format": null, + "diverse_beam_groups": -1, + "diverse_beam_strength": 0.5, + "diversity_rate": -1.0, + "iter_decode_eos_penalty": 0.0, + "iter_decode_force_max_iter": false, + "iter_decode_max_iter": 10, + "iter_decode_with_beam": 1, + "iter_decode_with_external_reranker": false, + "lenpen": 1.0, + "lm_path": null, + "lm_weight": 0.0, + "match_source_len": false, + "max_len_a": 0.0, + "max_len_b": 200, + "min_len": 1, + "nbest": 1, + "no_beamable_mm": false, + "no_early_stop": false, + "no_repeat_ngram_size": 0, + "no_seed_provided": false, + "prefix_size": 0, + "print_alignment": null, + "print_step": false, + "replace_unk": null, + "retain_dropout": false, + "retain_dropout_modules": null, + "retain_iter_history": false, + "sacrebleu": false, + "sampling": false, + "sampling_topk": -1, + "sampling_topp": -1.0, + "score_reference": false, + "temperature": 1.0, + "unkpen": 0.0, + "unnormalized": false + }, + "eval_wer_post_process": "letter", + "eval_wer_tokenizer": null, + "inferred_w2v_config": null, + "labels": null, + "max_sample_size": 320000, + "min_sample_size": 32000, + "normalize": false, + "num_batch_buckets": 0, + "precompute_mask_indices": false, + "sample_rate": 16000, + "tpu": true + } + }, + "w2v_path": "???" +} diff --git a/test/torchaudio_unittest/assets/wav2vec2/fairseq/wav2vec_large_lv60k_960h.json b/test/torchaudio_unittest/assets/wav2vec2/fairseq/wav2vec_large_lv60k_960h.json new file mode 100644 index 00000000..8ba161b4 --- /dev/null +++ b/test/torchaudio_unittest/assets/wav2vec2/fairseq/wav2vec_large_lv60k_960h.json @@ -0,0 +1,146 @@ +{ + "_name": "wav2vec_ctc", + "activation_dropout": 0.1, + "apply_mask": true, + "attention_dropout": 0.0, + "blank_mode": "add", + "blank_weight": 0.0, + "conv_feature_layers": "[(512, 10, 5)] + [(512, 3, 2)] * 4 + [(512,2,2)] + [(512,2,2)]", + "dropout": 0.0, + "dropout_input": 0.0, + "encoder_embed_dim": 512, + "feature_grad_mult": 0.0, + "final_dropout": 0.0, + "freeze_finetune_updates": 10000, + "layerdrop": 0.1, + "mask_channel_before": false, + "mask_channel_length": 64, + "mask_channel_min_space": 1, + "mask_channel_other": 0.0, + "mask_channel_prob": 0.25, + "mask_channel_selection": "static", + "mask_length": 10, + "mask_min_space": 1, + "mask_other": 0.0, + "mask_prob": 0.5, + "mask_selection": "static", + "no_mask_channel_overlap": false, + "no_mask_overlap": false, + "no_pretrained_weights": false, + "normalize": true, + "w2v_args": { + "model": { + "_name": "wav2vec2", + "activation_dropout": 0.0, + "activation_fn": "gelu", + "attention_dropout": 0.1, + "codebook_negatives": 0, + "conv_bias": true, + "conv_feature_layers": "[(512, 10, 5)] + [(512, 3, 2)] * 4 + [(512,2,2)] * 2", + "conv_pos": 128, + "conv_pos_groups": 16, + "cross_sample_negatives": 0, + "dropout": 0.0, + "dropout_features": 0.1, + "dropout_input": 0.1, + "encoder_attention_heads": 16, + "encoder_embed_dim": 1024, + "encoder_ffn_embed_dim": 4096, + "encoder_layerdrop": 0.0, + "encoder_layers": 24, + "extractor_mode": "layer_norm", + "feature_grad_mult": 1.0, + "final_dim": 768, + "latent_dim": 0, + "latent_groups": 2, + "latent_temp": [ + 2.0, + 0.1, + 0.999995 + ], + "latent_vars": 320, + "layer_norm_first": true, + "logit_temp": 0.1, + "mask_channel_before": false, + "mask_channel_length": 10, + "mask_channel_min_space": 1, + "mask_channel_other": 0.0, + "mask_channel_prob": 0.0, + "mask_channel_selection": "static", + "mask_length": 10, + "mask_min_space": 1, + "mask_other": 0.0, + "mask_prob": 0.65, + "mask_selection": "static", + "negatives_from_everywhere": false, + "no_mask_channel_overlap": false, + "no_mask_overlap": false, + "num_negatives": 100, + "quantize_input": false, + "quantize_targets": true, + "quantizer_depth": 1, + "quantizer_factor": 3, + "same_quantizer": false, + "target_glu": false + }, + "task": { + "_name": "audio_pretraining", + "autoregressive": false, + "binarized_dataset": false, + "enable_padding": false, + "eval_wer": false, + "eval_wer_config": { + "beam": 5, + "constraints": null, + "decoding_format": null, + "diverse_beam_groups": -1, + "diverse_beam_strength": 0.5, + "diversity_rate": -1.0, + "iter_decode_eos_penalty": 0.0, + "iter_decode_force_max_iter": false, + "iter_decode_max_iter": 10, + "iter_decode_with_beam": 1, + "iter_decode_with_external_reranker": false, + "lenpen": 1.0, + "lm_path": null, + "lm_weight": 0.0, + "match_source_len": false, + "max_len_a": 0.0, + "max_len_b": 200, + "min_len": 1, + "nbest": 1, + "no_beamable_mm": false, + "no_early_stop": false, + "no_repeat_ngram_size": 0, + "no_seed_provided": false, + "prefix_size": 0, + "print_alignment": null, + "print_step": false, + "replace_unk": null, + "retain_dropout": false, + "retain_dropout_modules": null, + "retain_iter_history": false, + "sacrebleu": false, + "sampling": false, + "sampling_topk": -1, + "sampling_topp": -1.0, + "score_reference": false, + "temperature": 1.0, + "unkpen": 0.0, + "unnormalized": false + }, + "eval_wer_post_process": "letter", + "eval_wer_tokenizer": null, + "inferred_w2v_config": null, + "labels": null, + "max_sample_size": 320000, + "min_sample_size": 32000, + "normalize": true, + "num_batch_buckets": 0, + "precompute_mask_indices": false, + "sample_rate": 16000, + "tpu": true + } + }, + "w2v_path": "???" +} diff --git a/test/torchaudio_unittest/assets/wav2vec2/fairseq/wav2vec_large_lv60k_self_960h.json b/test/torchaudio_unittest/assets/wav2vec2/fairseq/wav2vec_large_lv60k_self_960h.json new file mode 100644 index 00000000..a65ccfe8 --- /dev/null +++ b/test/torchaudio_unittest/assets/wav2vec2/fairseq/wav2vec_large_lv60k_self_960h.json @@ -0,0 +1,146 @@ +{ + "_name": "wav2vec_ctc", + "activation_dropout": 0.1, + "apply_mask": true, + "attention_dropout": 0.0, + "blank_mode": "add", + "blank_weight": 0.0, + "conv_feature_layers": "[(512, 10, 5)] + [(512, 3, 2)] * 4 + [(512,2,2)] + [(512,2,2)]", + "dropout": 0.0, + "dropout_input": 0.0, + "encoder_embed_dim": 768, + "feature_grad_mult": 0.0, + "final_dropout": 0.0, + "freeze_finetune_updates": 10000, + "layerdrop": 0.1, + "mask_channel_before": false, + "mask_channel_length": 64, + "mask_channel_min_space": 1, + "mask_channel_other": 0.0, + "mask_channel_prob": 0.1, + "mask_channel_selection": "static", + "mask_length": 10, + "mask_min_space": 1, + "mask_other": 0.0, + "mask_prob": 0.1, + "mask_selection": "static", + "no_mask_channel_overlap": false, + "no_mask_overlap": false, + "no_pretrained_weights": false, + "normalize": true, + "w2v_args": { + "model": { + "_name": "wav2vec2", + "activation_dropout": 0.0, + "activation_fn": "gelu", + "attention_dropout": 0.1, + "codebook_negatives": 0, + "conv_bias": true, + "conv_feature_layers": "[(512, 10, 5)] + [(512, 3, 2)] * 4 + [(512,2,2)] * 2", + "conv_pos": 128, + "conv_pos_groups": 16, + "cross_sample_negatives": 0, + "dropout": 0.0, + "dropout_features": 0.1, + "dropout_input": 0.1, + "encoder_attention_heads": 16, + "encoder_embed_dim": 1024, + "encoder_ffn_embed_dim": 4096, + "encoder_layerdrop": 0.0, + "encoder_layers": 24, + "extractor_mode": "layer_norm", + "feature_grad_mult": 1.0, + "final_dim": 768, + "latent_dim": 0, + "latent_groups": 2, + "latent_temp": [ + 2.0, + 0.1, + 0.999995 + ], + "latent_vars": 320, + "layer_norm_first": true, + "logit_temp": 0.1, + "mask_channel_before": false, + "mask_channel_length": 10, + "mask_channel_min_space": 1, + "mask_channel_other": 0.0, + "mask_channel_prob": 0.0, + "mask_channel_selection": "static", + "mask_length": 10, + "mask_min_space": 1, + "mask_other": 0.0, + "mask_prob": 0.65, + "mask_selection": "static", + "negatives_from_everywhere": false, + "no_mask_channel_overlap": false, + "no_mask_overlap": false, + "num_negatives": 100, + "quantize_input": false, + "quantize_targets": true, + "quantizer_depth": 1, + "quantizer_factor": 3, + "same_quantizer": false, + "target_glu": false + }, + "task": { + "_name": "audio_pretraining", + "autoregressive": false, + "binarized_dataset": false, + "enable_padding": false, + "eval_wer": false, + "eval_wer_config": { + "beam": 5, + "constraints": null, + "decoding_format": null, + "diverse_beam_groups": -1, + "diverse_beam_strength": 0.5, + "diversity_rate": -1.0, + "iter_decode_eos_penalty": 0.0, + "iter_decode_force_max_iter": false, + "iter_decode_max_iter": 10, + "iter_decode_with_beam": 1, + "iter_decode_with_external_reranker": false, + "lenpen": 1.0, + "lm_path": null, + "lm_weight": 0.0, + "match_source_len": false, + "max_len_a": 0.0, + "max_len_b": 200, + "min_len": 1, + "nbest": 1, + "no_beamable_mm": false, + "no_early_stop": false, + "no_repeat_ngram_size": 0, + "no_seed_provided": false, + "prefix_size": 0, + "print_alignment": null, + "print_step": false, + "replace_unk": null, + "retain_dropout": false, + "retain_dropout_modules": null, + "retain_iter_history": false, + "sacrebleu": false, + "sampling": false, + "sampling_topk": -1, + "sampling_topp": -1.0, + "score_reference": false, + "temperature": 1.0, + "unkpen": 0.0, + "unnormalized": false + }, + "eval_wer_post_process": "letter", + "eval_wer_tokenizer": null, + "inferred_w2v_config": null, + "labels": null, + "max_sample_size": 320000, + "min_sample_size": 32000, + "normalize": true, + "num_batch_buckets": 0, + "precompute_mask_indices": false, + "sample_rate": 16000, + "tpu": true + } + }, + "w2v_path": "/private/home/abaevski/models/wav2vec2/wav2vec_vox_new.pt" +} diff --git a/test/torchaudio_unittest/assets/wav2vec2/fairseq/wav2vec_small.json b/test/torchaudio_unittest/assets/wav2vec2/fairseq/wav2vec_small.json new file mode 100644 index 00000000..8fb7ff28 --- /dev/null +++ b/test/torchaudio_unittest/assets/wav2vec2/fairseq/wav2vec_small.json @@ -0,0 +1,54 @@ +{ + "_name": "wav2vec2", + "activation_dropout": 0.0, + "activation_fn": "gelu", + "attention_dropout": 0.1, + "codebook_negatives": 0, + "conv_bias": false, + "conv_feature_layers": "[(512, 10, 5)] + [(512, 3, 2)] * 4 + [(512,2,2)] * 2", + "conv_pos": 128, + "conv_pos_groups": 16, + "cross_sample_negatives": 0, + "dropout": 0.1, + "dropout_features": 0.1, + "dropout_input": 0.1, + "encoder_attention_heads": 12, + "encoder_embed_dim": 768, + "encoder_ffn_embed_dim": 3072, + "encoder_layerdrop": 0.05, + "encoder_layers": 12, + "extractor_mode": "default", + "feature_grad_mult": 0.1, + "final_dim": 256, + "latent_dim": 0, + "latent_groups": 2, + "latent_temp": [ + 2.0, + 0.5, + 0.999995 + ], + "latent_vars": 320, + "layer_norm_first": false, + "logit_temp": 0.1, + "mask_channel_before": false, + "mask_channel_length": 10, + "mask_channel_min_space": 1, + "mask_channel_other": 0.0, + "mask_channel_prob": 0.0, + "mask_channel_selection": "static", + "mask_length": 10, + "mask_min_space": 1, + "mask_other": 0.0, + "mask_prob": 0.65, + "mask_selection": "static", + "negatives_from_everywhere": false, + "no_mask_channel_overlap": false, + "no_mask_overlap": false, + "num_negatives": 100, + "quantize_input": false, + "quantize_targets": true, + "quantizer_depth": 1, + "quantizer_factor": 3, + "same_quantizer": false, + "target_glu": false +} diff --git a/test/torchaudio_unittest/assets/wav2vec2/fairseq/wav2vec_small_960h.json b/test/torchaudio_unittest/assets/wav2vec2/fairseq/wav2vec_small_960h.json new file mode 100644 index 00000000..f0ee5af8 --- /dev/null +++ b/test/torchaudio_unittest/assets/wav2vec2/fairseq/wav2vec_small_960h.json @@ -0,0 +1,146 @@ +{ + "_name": "wav2vec_ctc", + "activation_dropout": 0.1, + "apply_mask": true, + "attention_dropout": 0.0, + "blank_mode": "add", + "blank_weight": 0.0, + "conv_feature_layers": "[(512, 10, 5)] + [(512, 3, 2)] * 4 + [(512,2,2)] + [(512,2,2)]", + "dropout": 0.0, + "dropout_input": 0.0, + "encoder_embed_dim": 512, + "feature_grad_mult": 0.0, + "final_dropout": 0.0, + "freeze_finetune_updates": 0, + "layerdrop": 0.1, + "mask_channel_before": false, + "mask_channel_length": 64, + "mask_channel_min_space": 1, + "mask_channel_other": 0.0, + "mask_channel_prob": 0.1, + "mask_channel_selection": "static", + "mask_length": 10, + "mask_min_space": 1, + "mask_other": 0.0, + "mask_prob": 0.5, + "mask_selection": "static", + "no_mask_channel_overlap": false, + "no_mask_overlap": false, + "no_pretrained_weights": false, + "normalize": false, + "w2v_args": { + "model": { + "_name": "wav2vec2", + "activation_dropout": 0.0, + "activation_fn": "gelu", + "attention_dropout": 0.1, + "codebook_negatives": 0, + "conv_bias": false, + "conv_feature_layers": "[(512, 10, 5)] + [(512, 3, 2)] * 4 + [(512,2,2)] * 2", + "conv_pos": 128, + "conv_pos_groups": 16, + "cross_sample_negatives": 0, + "dropout": 0.1, + "dropout_features": 0.1, + "dropout_input": 0.1, + "encoder_attention_heads": 12, + "encoder_embed_dim": 768, + "encoder_ffn_embed_dim": 3072, + "encoder_layerdrop": 0.05, + "encoder_layers": 12, + "extractor_mode": "default", + "feature_grad_mult": 0.1, + "final_dim": 256, + "latent_dim": 0, + "latent_groups": 2, + "latent_temp": [ + 2, + 0.5, + 0.999995 + ], + "latent_vars": 320, + "layer_norm_first": false, + "logit_temp": 0.1, + "mask_channel_before": false, + "mask_channel_length": 10, + "mask_channel_min_space": 1, + "mask_channel_other": 0.0, + "mask_channel_prob": 0.0, + "mask_channel_selection": "static", + "mask_length": 10, + "mask_min_space": 1, + "mask_other": 0.0, + "mask_prob": 0.65, + "mask_selection": "static", + "negatives_from_everywhere": false, + "no_mask_channel_overlap": false, + "no_mask_overlap": false, + "num_negatives": 100, + "quantize_input": false, + "quantize_targets": true, + "quantizer_depth": 1, + "quantizer_factor": 3, + "same_quantizer": false, + "target_glu": false + }, + "task": { + "_name": "audio_pretraining", + "autoregressive": false, + "binarized_dataset": false, + "enable_padding": false, + "eval_wer": false, + "eval_wer_config": { + "beam": 5, + "constraints": null, + "decoding_format": null, + "diverse_beam_groups": -1, + "diverse_beam_strength": 0.5, + "diversity_rate": -1.0, + "iter_decode_eos_penalty": 0.0, + "iter_decode_force_max_iter": false, + "iter_decode_max_iter": 10, + "iter_decode_with_beam": 1, + "iter_decode_with_external_reranker": false, + "lenpen": 1.0, + "lm_path": null, + "lm_weight": 0.0, + "match_source_len": false, + "max_len_a": 0.0, + "max_len_b": 200, + "min_len": 1, + "nbest": 1, + "no_beamable_mm": false, + "no_early_stop": false, + "no_repeat_ngram_size": 0, + "no_seed_provided": false, + "prefix_size": 0, + "print_alignment": null, + "print_step": false, + "replace_unk": null, + "retain_dropout": false, + "retain_dropout_modules": null, + "retain_iter_history": false, + "sacrebleu": false, + "sampling": false, + "sampling_topk": -1, + "sampling_topp": -1.0, + "score_reference": false, + "temperature": 1.0, + "unkpen": 0.0, + "unnormalized": false + }, + "eval_wer_post_process": "letter", + "eval_wer_tokenizer": null, + "inferred_w2v_config": null, + "labels": null, + "max_sample_size": 250000, + "min_sample_size": 32000, + "normalize": false, + "num_batch_buckets": 0, + "precompute_mask_indices": false, + "sample_rate": 16000, + "tpu": true + } + }, + "w2v_path": "???" +} diff --git a/test/torchaudio_unittest/assets/wav2vec2/fairseq/wav2vec_vox_new.json b/test/torchaudio_unittest/assets/wav2vec2/fairseq/wav2vec_vox_new.json new file mode 100644 index 00000000..d58e303a --- /dev/null +++ b/test/torchaudio_unittest/assets/wav2vec2/fairseq/wav2vec_vox_new.json @@ -0,0 +1,54 @@ +{ + "_name": "wav2vec2", + "activation_dropout": 0.0, + "activation_fn": "gelu", + "attention_dropout": 0.1, + "codebook_negatives": 0, + "conv_bias": true, + "conv_feature_layers": "[(512, 10, 5)] + [(512, 3, 2)] * 4 + [(512,2,2)] * 2", + "conv_pos": 128, + "conv_pos_groups": 16, + "cross_sample_negatives": 0, + "dropout": 0.0, + "dropout_features": 0.1, + "dropout_input": 0.1, + "encoder_attention_heads": 16, + "encoder_embed_dim": 1024, + "encoder_ffn_embed_dim": 4096, + "encoder_layerdrop": 0.0, + "encoder_layers": 24, + "extractor_mode": "layer_norm", + "feature_grad_mult": 1.0, + "final_dim": 768, + "latent_dim": 0, + "latent_groups": 2, + "latent_temp": [ + 2.0, + 0.1, + 0.999995 + ], + "latent_vars": 320, + "layer_norm_first": true, + "logit_temp": 0.1, + "mask_channel_before": false, + "mask_channel_length": 10, + "mask_channel_min_space": 1, + "mask_channel_other": 0.0, + "mask_channel_prob": 0.0, + "mask_channel_selection": "static", + "mask_length": 10, + "mask_min_space": 1, + "mask_other": 0.0, + "mask_prob": 0.65, + "mask_selection": "static", + "negatives_from_everywhere": false, + "no_mask_channel_overlap": false, + "no_mask_overlap": false, + "num_negatives": 100, + "quantize_input": false, + "quantize_targets": true, + "quantizer_depth": 1, + "quantizer_factor": 3, + "same_quantizer": false, + "target_glu": false +} diff --git a/test/torchaudio_unittest/assets/wav2vec2/fairseq/xlsr_53_56k.json b/test/torchaudio_unittest/assets/wav2vec2/fairseq/xlsr_53_56k.json new file mode 100644 index 00000000..098f60d5 --- /dev/null +++ b/test/torchaudio_unittest/assets/wav2vec2/fairseq/xlsr_53_56k.json @@ -0,0 +1,51 @@ +{ + "_name": "wav2vec2", + "activation_dropout": 0.0, + "activation_fn": "gelu", + "attention_dropout": 0.0, + "codebook_negatives": 0, + "conv_bias": true, + "conv_feature_layers": "[(512, 10, 5)] + [(512, 3, 2)] * 4 + [(512,2,2)] * 2", + "conv_pos": 128, + "conv_pos_groups": 16, + "cross_sample_negatives": 0, + "dropout": 0.0, + "dropout_features": 0.0, + "dropout_input": 0.0, + "encoder_attention_heads": 16, + "encoder_embed_dim": 1024, + "encoder_ffn_embed_dim": 4096, + "encoder_layerdrop": 0.0, + "encoder_layers": 24, + "extractor_mode": "layer_norm", + "feature_grad_mult": 1.0, + "final_dim": 768, + "latent_dim": 0, + "latent_groups": 2, + "latent_temp": [ + 2.0, + 0.1, + 0.999995 + ], + "latent_vars": 320, + "layer_norm_first": true, + "logit_temp": 0.1, + "mask_channel_length": 10, + "mask_channel_min_space": 1, + "mask_channel_other": 0.0, + "mask_channel_prob": 0.0, + "mask_channel_selection": "static", + "mask_length": 10, + "mask_min_space": 1, + "mask_other": 0.0, + "mask_prob": 0.65, + "mask_selection": "static", + "negatives_from_everywhere": false, + "no_mask_channel_overlap": false, + "no_mask_overlap": false, + "num_negatives": 100, + "quantize_input": false, + "quantize_targets": true, + "same_quantizer": false, + "target_glu": false +} diff --git a/test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-base-10k-voxpopuli.json b/test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-base-10k-voxpopuli.json new file mode 100644 index 00000000..927428cb --- /dev/null +++ b/test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-base-10k-voxpopuli.json @@ -0,0 +1,68 @@ +{ + "activation_dropout": 0.1, + "apply_spec_augment": true, + "architectures": [ + "Wav2Vec2Model" + ], + "attention_dropout": 0.1, + "bos_token_id": 1, + "conv_bias": false, + "conv_dim": [ + 512, + 512, + 512, + 512, + 512, + 512, + 512 + ], + "conv_kernel": [ + 10, + 3, + 3, + 3, + 3, + 2, + 2 + ], + "conv_stride": [ + 5, + 2, + 2, + 2, + 2, + 2, + 2 + ], + "ctc_loss_reduction": "sum", + "ctc_zero_infinity": false, + "do_stable_layer_norm": false, + "eos_token_id": 2, + "feat_extract_activation": "gelu", + "feat_extract_dropout": 0.0, + "feat_extract_norm": "group", + "feat_proj_dropout": 0.1, + "final_dropout": 0.1, + "gradient_checkpointing": false, + "hidden_act": "gelu", + "hidden_dropout": 0.1, + "hidden_dropout_prob": 0.1, + "hidden_size": 768, + "initializer_range": 0.02, + "intermediate_size": 3072, + "layer_norm_eps": 1e-05, + "layerdrop": 0.1, + "mask_feature_length": 10, + "mask_feature_prob": 0.0, + "mask_time_length": 10, + "mask_time_prob": 0.05, + "model_type": "wav2vec2", + "num_attention_heads": 12, + "num_conv_pos_embedding_groups": 16, + "num_conv_pos_embeddings": 128, + "num_feat_extract_layers": 7, + "num_hidden_layers": 12, + "pad_token_id": 0, + "transformers_version": "4.5.1", + "vocab_size": 32 +} diff --git a/test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-base-960h.json b/test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-base-960h.json new file mode 100644 index 00000000..3a1c7f1a --- /dev/null +++ b/test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-base-960h.json @@ -0,0 +1,68 @@ +{ + "activation_dropout": 0.1, + "apply_spec_augment": true, + "architectures": [ + "Wav2Vec2ForCTC" + ], + "attention_dropout": 0.1, + "bos_token_id": 1, + "conv_bias": false, + "conv_dim": [ + 512, + 512, + 512, + 512, + 512, + 512, + 512 + ], + "conv_kernel": [ + 10, + 3, + 3, + 3, + 3, + 2, + 2 + ], + "conv_stride": [ + 5, + 2, + 2, + 2, + 2, + 2, + 2 + ], + "ctc_loss_reduction": "sum", + "ctc_zero_infinity": false, + "do_stable_layer_norm": false, + "eos_token_id": 2, + "feat_extract_activation": "gelu", + "feat_extract_dropout": 0.0, + "feat_extract_norm": "group", + "feat_proj_dropout": 0.1, + "final_dropout": 0.1, + "gradient_checkpointing": false, + "hidden_act": "gelu", + "hidden_dropout": 0.1, + "hidden_dropout_prob": 0.1, + "hidden_size": 768, + "initializer_range": 0.02, + "intermediate_size": 3072, + "layer_norm_eps": 1e-05, + "layerdrop": 0.1, + "mask_feature_length": 10, + "mask_feature_prob": 0.0, + "mask_time_length": 10, + "mask_time_prob": 0.05, + "model_type": "wav2vec2", + "num_attention_heads": 12, + "num_conv_pos_embedding_groups": 16, + "num_conv_pos_embeddings": 128, + "num_feat_extract_layers": 7, + "num_hidden_layers": 12, + "pad_token_id": 0, + "transformers_version": "4.5.1", + "vocab_size": 32 +} diff --git a/test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-base.json b/test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-base.json new file mode 100644 index 00000000..7927c2e4 --- /dev/null +++ b/test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-base.json @@ -0,0 +1,77 @@ +{ + "activation_dropout": 0.0, + "apply_spec_augment": true, + "architectures": [ + "Wav2Vec2Model" + ], + "attention_dropout": 0.1, + "bos_token_id": 1, + "conv_bias": false, + "conv_dim": [ + 512, + 512, + 512, + 512, + 512, + 512, + 512 + ], + "conv_kernel": [ + 10, + 3, + 3, + 3, + 3, + 2, + 2 + ], + "conv_stride": [ + 5, + 2, + 2, + 2, + 2, + 2, + 2 + ], + "ctc_loss_reduction": "sum", + "ctc_zero_infinity": false, + "do_stable_layer_norm": false, + "eos_token_id": 2, + "feat_extract_activation": "gelu", + "feat_extract_norm": "group", + "feat_proj_dropout": 0.1, + "final_dropout": 0.0, + "freeze_feat_extract_train": true, + "gradient_checkpointing": true, + "hidden_act": "gelu", + "hidden_dropout": 0.1, + "hidden_size": 768, + "initializer_range": 0.02, + "intermediate_size": 3072, + "layer_norm_eps": 1e-05, + "layerdrop": 0.05, + "mask_channel_length": 10, + "mask_channel_min_space": 1, + "mask_channel_other": 0.0, + "mask_channel_prob": 0.0, + "mask_channel_selection": "static", + "mask_feature_length": 10, + "mask_feature_prob": 0.0, + "mask_time_length": 10, + "mask_time_min_space": 1, + "mask_time_other": 0.0, + "mask_time_prob": 0.05, + "mask_time_selection": "static", + "model_type": "wav2vec2", + "no_mask_channel_overlap": false, + "no_mask_time_overlap": false, + "num_attention_heads": 12, + "num_conv_pos_embedding_groups": 16, + "num_conv_pos_embeddings": 128, + "num_feat_extract_layers": 7, + "num_hidden_layers": 12, + "pad_token_id": 0, + "transformers_version": "4.5.1", + "vocab_size": 32 +} diff --git a/test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-large-960h-lv60-self.json b/test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-large-960h-lv60-self.json new file mode 100644 index 00000000..e9d79893 --- /dev/null +++ b/test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-large-960h-lv60-self.json @@ -0,0 +1,68 @@ +{ + "activation_dropout": 0.1, + "apply_spec_augment": true, + "architectures": [ + "Wav2Vec2ForCTC" + ], + "attention_dropout": 0.1, + "bos_token_id": 1, + "conv_bias": true, + "conv_dim": [ + 512, + 512, + 512, + 512, + 512, + 512, + 512 + ], + "conv_kernel": [ + 10, + 3, + 3, + 3, + 3, + 2, + 2 + ], + "conv_stride": [ + 5, + 2, + 2, + 2, + 2, + 2, + 2 + ], + "ctc_loss_reduction": "sum", + "ctc_zero_infinity": false, + "do_stable_layer_norm": true, + "eos_token_id": 2, + "feat_extract_activation": "gelu", + "feat_extract_dropout": 0.0, + "feat_extract_norm": "layer", + "feat_proj_dropout": 0.1, + "final_dropout": 0.1, + "gradient_checkpointing": false, + "hidden_act": "gelu", + "hidden_dropout": 0.1, + "hidden_dropout_prob": 0.1, + "hidden_size": 1024, + "initializer_range": 0.02, + "intermediate_size": 4096, + "layer_norm_eps": 1e-05, + "layerdrop": 0.1, + "mask_feature_length": 10, + "mask_feature_prob": 0.0, + "mask_time_length": 10, + "mask_time_prob": 0.05, + "model_type": "wav2vec2", + "num_attention_heads": 16, + "num_conv_pos_embedding_groups": 16, + "num_conv_pos_embeddings": 128, + "num_feat_extract_layers": 7, + "num_hidden_layers": 24, + "pad_token_id": 0, + "transformers_version": "4.5.1", + "vocab_size": 32 +} diff --git a/test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-large-960h-lv60.json b/test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-large-960h-lv60.json new file mode 100644 index 00000000..e9d79893 --- /dev/null +++ b/test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-large-960h-lv60.json @@ -0,0 +1,68 @@ +{ + "activation_dropout": 0.1, + "apply_spec_augment": true, + "architectures": [ + "Wav2Vec2ForCTC" + ], + "attention_dropout": 0.1, + "bos_token_id": 1, + "conv_bias": true, + "conv_dim": [ + 512, + 512, + 512, + 512, + 512, + 512, + 512 + ], + "conv_kernel": [ + 10, + 3, + 3, + 3, + 3, + 2, + 2 + ], + "conv_stride": [ + 5, + 2, + 2, + 2, + 2, + 2, + 2 + ], + "ctc_loss_reduction": "sum", + "ctc_zero_infinity": false, + "do_stable_layer_norm": true, + "eos_token_id": 2, + "feat_extract_activation": "gelu", + "feat_extract_dropout": 0.0, + "feat_extract_norm": "layer", + "feat_proj_dropout": 0.1, + "final_dropout": 0.1, + "gradient_checkpointing": false, + "hidden_act": "gelu", + "hidden_dropout": 0.1, + "hidden_dropout_prob": 0.1, + "hidden_size": 1024, + "initializer_range": 0.02, + "intermediate_size": 4096, + "layer_norm_eps": 1e-05, + "layerdrop": 0.1, + "mask_feature_length": 10, + "mask_feature_prob": 0.0, + "mask_time_length": 10, + "mask_time_prob": 0.05, + "model_type": "wav2vec2", + "num_attention_heads": 16, + "num_conv_pos_embedding_groups": 16, + "num_conv_pos_embeddings": 128, + "num_feat_extract_layers": 7, + "num_hidden_layers": 24, + "pad_token_id": 0, + "transformers_version": "4.5.1", + "vocab_size": 32 +} diff --git a/test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-large-960h.json b/test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-large-960h.json new file mode 100644 index 00000000..e50233c0 --- /dev/null +++ b/test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-large-960h.json @@ -0,0 +1,68 @@ +{ + "activation_dropout": 0.1, + "apply_spec_augment": true, + "architectures": [ + "Wav2Vec2ForCTC" + ], + "attention_dropout": 0.1, + "bos_token_id": 1, + "conv_bias": false, + "conv_dim": [ + 512, + 512, + 512, + 512, + 512, + 512, + 512 + ], + "conv_kernel": [ + 10, + 3, + 3, + 3, + 3, + 2, + 2 + ], + "conv_stride": [ + 5, + 2, + 2, + 2, + 2, + 2, + 2 + ], + "ctc_loss_reduction": "sum", + "ctc_zero_infinity": false, + "do_stable_layer_norm": false, + "eos_token_id": 2, + "feat_extract_activation": "gelu", + "feat_extract_dropout": 0.0, + "feat_extract_norm": "group", + "feat_proj_dropout": 0.1, + "final_dropout": 0.1, + "gradient_checkpointing": false, + "hidden_act": "gelu", + "hidden_dropout": 0.1, + "hidden_dropout_prob": 0.1, + "hidden_size": 1024, + "initializer_range": 0.02, + "intermediate_size": 4096, + "layer_norm_eps": 1e-05, + "layerdrop": 0.1, + "mask_feature_length": 10, + "mask_feature_prob": 0.0, + "mask_time_length": 10, + "mask_time_prob": 0.05, + "model_type": "wav2vec2", + "num_attention_heads": 16, + "num_conv_pos_embedding_groups": 16, + "num_conv_pos_embeddings": 128, + "num_feat_extract_layers": 7, + "num_hidden_layers": 24, + "pad_token_id": 0, + "transformers_version": "4.5.1", + "vocab_size": 32 +} diff --git a/test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-large-lv60.json b/test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-large-lv60.json new file mode 100644 index 00000000..ba3d2c2e --- /dev/null +++ b/test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-large-lv60.json @@ -0,0 +1,68 @@ +{ + "activation_dropout": 0.1, + "apply_spec_augment": true, + "architectures": [ + "Wav2Vec2Model" + ], + "attention_dropout": 0.1, + "bos_token_id": 1, + "conv_bias": true, + "conv_dim": [ + 512, + 512, + 512, + 512, + 512, + 512, + 512 + ], + "conv_kernel": [ + 10, + 3, + 3, + 3, + 3, + 2, + 2 + ], + "conv_stride": [ + 5, + 2, + 2, + 2, + 2, + 2, + 2 + ], + "ctc_loss_reduction": "sum", + "ctc_zero_infinity": false, + "do_stable_layer_norm": true, + "eos_token_id": 2, + "feat_extract_activation": "gelu", + "feat_extract_dropout": 0.0, + "feat_extract_norm": "layer", + "feat_proj_dropout": 0.1, + "final_dropout": 0.1, + "gradient_checkpointing": false, + "hidden_act": "gelu", + "hidden_dropout": 0.1, + "hidden_dropout_prob": 0.1, + "hidden_size": 1024, + "initializer_range": 0.02, + "intermediate_size": 4096, + "layer_norm_eps": 1e-05, + "layerdrop": 0.1, + "mask_feature_length": 10, + "mask_feature_prob": 0.0, + "mask_time_length": 10, + "mask_time_prob": 0.05, + "model_type": "wav2vec2", + "num_attention_heads": 16, + "num_conv_pos_embedding_groups": 16, + "num_conv_pos_embeddings": 128, + "num_feat_extract_layers": 7, + "num_hidden_layers": 24, + "pad_token_id": 0, + "transformers_version": "4.5.1", + "vocab_size": 32 +} diff --git a/test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-large-xlsr-53-german.json b/test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-large-xlsr-53-german.json new file mode 100644 index 00000000..120f142b --- /dev/null +++ b/test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-large-xlsr-53-german.json @@ -0,0 +1,68 @@ +{ + "activation_dropout": 0.1, + "apply_spec_augment": true, + "architectures": [ + "Wav2Vec2ForCTC" + ], + "attention_dropout": 0.1, + "bos_token_id": 1, + "conv_bias": true, + "conv_dim": [ + 512, + 512, + 512, + 512, + 512, + 512, + 512 + ], + "conv_kernel": [ + 10, + 3, + 3, + 3, + 3, + 2, + 2 + ], + "conv_stride": [ + 5, + 2, + 2, + 2, + 2, + 2, + 2 + ], + "ctc_loss_reduction": "sum", + "ctc_zero_infinity": false, + "do_stable_layer_norm": true, + "eos_token_id": 2, + "feat_extract_activation": "gelu", + "feat_extract_dropout": 0.0, + "feat_extract_norm": "layer", + "feat_proj_dropout": 0.1, + "final_dropout": 0.1, + "gradient_checkpointing": false, + "hidden_act": "gelu", + "hidden_dropout": 0.1, + "hidden_dropout_prob": 0.1, + "hidden_size": 1024, + "initializer_range": 0.02, + "intermediate_size": 4096, + "layer_norm_eps": 1e-05, + "layerdrop": 0.1, + "mask_feature_length": 10, + "mask_feature_prob": 0.0, + "mask_time_length": 10, + "mask_time_prob": 0.05, + "model_type": "wav2vec2", + "num_attention_heads": 16, + "num_conv_pos_embedding_groups": 16, + "num_conv_pos_embeddings": 128, + "num_feat_extract_layers": 7, + "num_hidden_layers": 24, + "pad_token_id": 0, + "transformers_version": "4.5.1", + "vocab_size": 36 +} diff --git a/test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-large-xlsr-53.json b/test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-large-xlsr-53.json new file mode 100644 index 00000000..80f0f61e --- /dev/null +++ b/test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-large-xlsr-53.json @@ -0,0 +1,75 @@ +{ + "activation_dropout": 0.0, + "apply_spec_augment": true, + "architectures": [ + "Wav2Vec2Model" + ], + "attention_dropout": 0.1, + "bos_token_id": 1, + "conv_bias": true, + "conv_dim": [ + 512, + 512, + 512, + 512, + 512, + 512, + 512 + ], + "conv_kernel": [ + 10, + 3, + 3, + 3, + 3, + 2, + 2 + ], + "conv_stride": [ + 5, + 2, + 2, + 2, + 2, + 2, + 2 + ], + "ctc_loss_reduction": "sum", + "ctc_zero_infinity": false, + "do_stable_layer_norm": true, + "eos_token_id": 2, + "feat_extract_activation": "gelu", + "feat_extract_dropout": 0.0, + "feat_extract_norm": "layer", + "feat_proj_dropout": 0.1, + "final_dropout": 0.0, + "gradient_checkpointing": false, + "hidden_act": "gelu", + "hidden_dropout": 0.1, + "hidden_size": 1024, + "initializer_range": 0.02, + "intermediate_size": 4096, + "layer_norm_eps": 1e-05, + "layerdrop": 0.1, + "mask_channel_length": 10, + "mask_channel_min_space": 1, + "mask_channel_other": 0.0, + "mask_channel_prob": 0.0, + "mask_channel_selection": "static", + "mask_feature_length": 10, + "mask_feature_prob": 0.0, + "mask_time_length": 10, + "mask_time_min_space": 1, + "mask_time_other": 0.0, + "mask_time_prob": 0.075, + "mask_time_selection": "static", + "model_type": "wav2vec2", + "num_attention_heads": 16, + "num_conv_pos_embedding_groups": 16, + "num_conv_pos_embeddings": 128, + "num_feat_extract_layers": 7, + "num_hidden_layers": 24, + "pad_token_id": 0, + "transformers_version": "4.5.1", + "vocab_size": 32 +} diff --git a/test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-large.json b/test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-large.json new file mode 100644 index 00000000..db1635eb --- /dev/null +++ b/test/torchaudio_unittest/assets/wav2vec2/huggingface/facebook/wav2vec2-large.json @@ -0,0 +1,68 @@ +{ + "activation_dropout": 0.1, + "apply_spec_augment": true, + "architectures": [ + "Wav2Vec2Model" + ], + "attention_dropout": 0.1, + "bos_token_id": 1, + "conv_bias": false, + "conv_dim": [ + 512, + 512, + 512, + 512, + 512, + 512, + 512 + ], + "conv_kernel": [ + 10, + 3, + 3, + 3, + 3, + 2, + 2 + ], + "conv_stride": [ + 5, + 2, + 2, + 2, + 2, + 2, + 2 + ], + "ctc_loss_reduction": "sum", + "ctc_zero_infinity": false, + "do_stable_layer_norm": false, + "eos_token_id": 2, + "feat_extract_activation": "gelu", + "feat_extract_dropout": 0.0, + "feat_extract_norm": "group", + "feat_proj_dropout": 0.1, + "final_dropout": 0.1, + "gradient_checkpointing": false, + "hidden_act": "gelu", + "hidden_dropout": 0.1, + "hidden_dropout_prob": 0.1, + "hidden_size": 1024, + "initializer_range": 0.02, + "intermediate_size": 4096, + "layer_norm_eps": 1e-05, + "layerdrop": 0.1, + "mask_feature_length": 10, + "mask_feature_prob": 0.0, + "mask_time_length": 10, + "mask_time_prob": 0.05, + "model_type": "wav2vec2", + "num_attention_heads": 16, + "num_conv_pos_embedding_groups": 16, + "num_conv_pos_embeddings": 128, + "num_feat_extract_layers": 7, + "num_hidden_layers": 24, + "pad_token_id": 0, + "transformers_version": "4.5.1", + "vocab_size": 32 +} diff --git a/test/torchaudio_unittest/assets/wav2vec2/huggingface/generate_huggingface_model_config.py b/test/torchaudio_unittest/assets/wav2vec2/huggingface/generate_huggingface_model_config.py new file mode 100644 index 00000000..d2217930 --- /dev/null +++ b/test/torchaudio_unittest/assets/wav2vec2/huggingface/generate_huggingface_model_config.py @@ -0,0 +1,37 @@ +import os +import json + +from transformers import Wav2Vec2Model + +_THIS_DIR = os.path.dirname(os.path.abspath(__file__)) + + +def _main(): + keys = [ + # pretrained + "facebook/wav2vec2-base", + "facebook/wav2vec2-large", + "facebook/wav2vec2-large-lv60", + "facebook/wav2vec2-base-10k-voxpopuli", + "facebook/wav2vec2-large-xlsr-53", + # finetuned + "facebook/wav2vec2-base-960h", + "facebook/wav2vec2-large-960h", + "facebook/wav2vec2-large-960h-lv60", + "facebook/wav2vec2-large-960h-lv60-self", + "facebook/wav2vec2-large-xlsr-53-german", + ] + for key in keys: + path = os.path.join(_THIS_DIR, f'{key}.json') + print('Generating ', path) + cfg = Wav2Vec2Model.from_pretrained(key).config + cfg = json.loads(cfg.to_json_string()) + del cfg['_name_or_path'] + + with open(path, 'w') as file_: + file_.write(json.dumps(cfg, indent=4, sort_keys=True)) + file_.write('\n') + + +if __name__ == '__main__': + _main() diff --git a/test/torchaudio_unittest/backend/__init__.py b/test/torchaudio_unittest/backend/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/torchaudio_unittest/backend/common.py b/test/torchaudio_unittest/backend/common.py new file mode 100644 index 00000000..dc8e90bf --- /dev/null +++ b/test/torchaudio_unittest/backend/common.py @@ -0,0 +1,25 @@ +from torchaudio_unittest.common_utils import sox_utils + + +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_bits_per_sample(ext, dtype): + bits_per_samples = { + 'flac': 24, + 'mp3': 0, + 'vorbis': 0, + } + return bits_per_samples.get(ext, sox_utils.get_bit_depth(dtype)) diff --git a/test/torchaudio_unittest/backend/soundfile/__init__.py b/test/torchaudio_unittest/backend/soundfile/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/torchaudio_unittest/backend/soundfile/common.py b/test/torchaudio_unittest/backend/soundfile/common.py new file mode 100644 index 00000000..c6b014dd --- /dev/null +++ b/test/torchaudio_unittest/backend/soundfile/common.py @@ -0,0 +1,57 @@ +import itertools +from unittest import skipIf + +from parameterized import parameterized +from torchaudio._internal.module_utils import is_module_available + + +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}).") diff --git a/test/torchaudio_unittest/backend/soundfile/info_test.py b/test/torchaudio_unittest/backend/soundfile/info_test.py new file mode 100644 index 00000000..b6b87224 --- /dev/null +++ b/test/torchaudio_unittest/backend/soundfile/info_test.py @@ -0,0 +1,190 @@ +from unittest.mock import patch +import warnings +import tarfile + +import torch +from torchaudio.backend import soundfile_backend +from torchaudio._internal import module_utils as _mod_utils + +from torchaudio_unittest.common_utils import ( + TempDirMixin, + PytorchTestCase, + skipIfNoModule, + get_wav_data, + save_wav, + nested_params, +) +from torchaudio_unittest.backend.common import ( + get_bits_per_sample, + get_encoding, +) +from .common import skipIfFormatNotSupported, parameterize + +if _mod_utils.is_module_available("soundfile"): + import soundfile + + +@skipIfNoModule("soundfile") +class TestInfo(TempDirMixin, PytorchTestCase): + @parameterize( + ["float32", "int32", "int16", "uint8"], [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() + 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() + path = self.get_temp_path("data.ogg") + soundfile.write(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 == 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() + 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 TorchAudio" in str(w[-1].message) + assert info.bits_per_sample == 0 + + +@skipIfNoModule("soundfile") +class TestFileObject(TempDirMixin, PytorchTestCase): + 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() + 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() + 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) diff --git a/test/torchaudio_unittest/backend/soundfile/load_test.py b/test/torchaudio_unittest/backend/soundfile/load_test.py new file mode 100644 index 00000000..0e3a240d --- /dev/null +++ b/test/torchaudio_unittest/backend/soundfile/load_test.py @@ -0,0 +1,357 @@ +import os +import tarfile +from unittest.mock import patch + +import torch +from torchaudio._internal import module_utils as _mod_utils +from torchaudio.backend import soundfile_backend +from parameterized import parameterized + +from torchaudio_unittest.common_utils import ( + TempDirMixin, + PytorchTestCase, + skipIfNoModule, + get_wav_data, + normalize_wav, + load_wav, + save_wav, +) +from .common import ( + parameterize, + dtype2subtype, + skipIfFormatNotSupported, +) + +if _mod_utils.is_module_available("soundfile"): + import soundfile + + +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(PytorchTestCase): + 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 = ( + torch.float32 + if normalize or ext not in ["wav", "nist"] + else getattr(torch, 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( + ["uint8", "int16", "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( + ["int8", "int16", "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, PytorchTestCase): + 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 + self.assertEqual(data, expected) + + 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) + + 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) + + +@skipIfNoModule("soundfile") +class TestLoad(LoadTestBase): + """Test the correctness of `soundfile_backend.load` for various formats""" + + @parameterize( + ["float32", "int32", "int16"], + [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( + ["int16"], [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", "int16"], [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", "int16"], [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", "int16"], [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) + + +@skipIfNoModule("soundfile") +class TestLoadFormat(TempDirMixin, PytorchTestCase): + """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) + + @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_) + + +@skipIfNoModule("soundfile") +class TestFileObject(TempDirMixin, PytorchTestCase): + 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) + + def test_fileobj_wav(self): + """Loading audio via file-like object works""" + self._test_fileobj('wav') + + @skipIfFormatNotSupported("FLAC") + 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) + + def test_tarfile_wav(self): + """Loading audio via file-like object works""" + self._test_tarfile('wav') + + @skipIfFormatNotSupported("FLAC") + def test_tarfile_flac(self): + """Loading audio via file-like object works""" + self._test_tarfile('flac') diff --git a/test/torchaudio_unittest/backend/soundfile/save_test.py b/test/torchaudio_unittest/backend/soundfile/save_test.py new file mode 100644 index 00000000..e4e8c936 --- /dev/null +++ b/test/torchaudio_unittest/backend/soundfile/save_test.py @@ -0,0 +1,295 @@ +import io +from unittest.mock import patch + +from torchaudio._internal import module_utils as _mod_utils +from torchaudio.backend import soundfile_backend + +from torchaudio_unittest.common_utils import ( + TempDirMixin, + PytorchTestCase, + skipIfNoModule, + get_wav_data, + load_wav, + nested_params, +) +from .common import ( + fetch_wav_subtype, + parameterize, + skipIfFormatNotSupported, +) + +if _mod_utils.is_module_available("soundfile"): + import soundfile + + +class MockedSaveTest(PytorchTestCase): + @nested_params( + ["float32", "int32", "int16", "uint8"], + [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, + ).t() + + 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 + self.assertEqual( + args["data"], input_tensor.t() if channels_first else input_tensor + ) + + @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, + ).t() + expected_data = input_tensor.t() 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 + self.assertEqual(args["data"], expected_data) + + @nested_params( + ["sph", "nist", "nis"], + ["int32", "int16"], + [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", "int16"], [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", "int16"], [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) + + +@skipIfNoModule("soundfile") +class SaveTestBase(TempDirMixin, PytorchTestCase): + 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) + + 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 + 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) + + +@skipIfNoModule("soundfile") +class TestSave(SaveTestBase): + @parameterize( + ["float32", "int32", "int16"], [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", "int16"], [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", "int16"], [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) + + +@skipIfNoModule("soundfile") +class TestSaveParams(TempDirMixin, PytorchTestCase): + """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) + + +@skipIfNoModule("soundfile") +class TestFileObject(TempDirMixin, PytorchTestCase): + 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) + + 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') diff --git a/test/torchaudio_unittest/backend/sox_io/__init__.py b/test/torchaudio_unittest/backend/sox_io/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/torchaudio_unittest/backend/sox_io/common.py b/test/torchaudio_unittest/backend/sox_io/common.py new file mode 100644 index 00000000..c2538b2b --- /dev/null +++ b/test/torchaudio_unittest/backend/sox_io/common.py @@ -0,0 +1,14 @@ +def name_func(func, _, params): + return f'{func.__name__}_{"_".join(str(arg) for arg in params.args)}' + + +def get_enc_params(dtype): + if dtype == 'float32': + return 'PCM_F', 32 + if dtype == 'int32': + return 'PCM_S', 32 + if dtype == 'int16': + return 'PCM_S', 16 + if dtype == 'uint8': + return 'PCM_U', 8 + raise ValueError(f'Unexpected dtype: {dtype}') diff --git a/test/torchaudio_unittest/backend/sox_io/info_test.py b/test/torchaudio_unittest/backend/sox_io/info_test.py new file mode 100644 index 00000000..0405d1ba --- /dev/null +++ b/test/torchaudio_unittest/backend/sox_io/info_test.py @@ -0,0 +1,537 @@ +from contextlib import contextmanager +import io +import os +import itertools +import tarfile + +from parameterized import parameterized +from torchaudio.backend import sox_io_backend +from torchaudio.utils.sox_utils import get_buffer_size, set_buffer_size +from torchaudio._internal import module_utils as _mod_utils + +from torchaudio_unittest.backend.common import ( + get_bits_per_sample, + get_encoding, +) +from torchaudio_unittest.common_utils import ( + TempDirMixin, + HttpServerMixin, + PytorchTestCase, + skipIfNoExec, + skipIfNoModule, + skipIfNoSox, + get_asset_path, + get_wav_data, + save_wav, + sox_utils, +) +from .common import ( + name_func, +) + + +if _mod_utils.is_module_available("requests"): + import requests + + +@skipIfNoExec('sox') +@skipIfNoSox +class TestInfo(TempDirMixin, PytorchTestCase): + @parameterized.expand(list(itertools.product( + ['float32', 'int32', 'int16', 'uint8'], + [8000, 16000], + [1, 2], + )), name_func=name_func) + 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', 'int16', 'uint8'], + [8000, 16000], + [4, 8, 16, 32], + )), name_func=name_func) + 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) + assert info.encoding == get_encoding('wav', dtype) + + @parameterized.expand(list(itertools.product( + [8000, 16000], + [1, 2], + [96, 128, 160, 192, 224, 256, 320], + )), name_func=name_func) + def test_mp3(self, sample_rate, num_channels, bit_rate): + """`sox_io_backend.info` can check mp3 file correctly""" + duration = 1 + path = self.get_temp_path('data.mp3') + sox_utils.gen_audio_file( + path, sample_rate, num_channels, + compression=bit_rate, duration=duration, + ) + info = sox_io_backend.info(path) + assert info.sample_rate == sample_rate + # mp3 does not preserve the number of samples + # assert info.num_frames == sample_rate * duration + assert info.num_channels == num_channels + assert info.bits_per_sample == 0 # bit_per_sample is irrelevant for compressed formats + assert info.encoding == "MP3" + + @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): + """`sox_io_backend.info` can check flac file correctly""" + duration = 1 + path = self.get_temp_path('data.flac') + sox_utils.gen_audio_file( + path, sample_rate, num_channels, + compression=compression_level, 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 == 24 # FLAC standard + assert info.encoding == "FLAC" + + @parameterized.expand(list(itertools.product( + [8000, 16000], + [1, 2], + [-1, 0, 1, 2, 3, 3.6, 5, 10], + )), name_func=name_func) + def test_vorbis(self, sample_rate, num_channels, quality_level): + """`sox_io_backend.info` can check vorbis file correctly""" + duration = 1 + path = self.get_temp_path('data.vorbis') + sox_utils.gen_audio_file( + path, sample_rate, num_channels, + compression=quality_level, 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 == 0 # bit_per_sample is irrelevant for compressed formats + assert info.encoding == "VORBIS" + + @parameterized.expand(list(itertools.product( + [8000, 16000], + [1, 2], + [16, 32], + )), name_func=name_func) + def test_sphere(self, sample_rate, num_channels, bits_per_sample): + """`sox_io_backend.info` can check sph file correctly""" + duration = 1 + path = self.get_temp_path('data.sph') + sox_utils.gen_audio_file( + path, sample_rate, num_channels, duration=duration, + bit_depth=bits_per_sample) + 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 == bits_per_sample + assert info.encoding == "PCM_S" + + @parameterized.expand(list(itertools.product( + ['int32', 'int16', 'uint8'], + [8000, 16000], + [1, 2], + )), name_func=name_func) + def test_amb(self, dtype, sample_rate, num_channels): + """`sox_io_backend.info` can check amb file correctly""" + duration = 1 + path = self.get_temp_path('data.amb') + bits_per_sample = sox_utils.get_bit_depth(dtype) + sox_utils.gen_audio_file( + path, sample_rate, num_channels, + bit_depth=bits_per_sample, 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 == bits_per_sample + assert info.encoding == get_encoding("amb", dtype) + + def test_amr_nb(self): + """`sox_io_backend.info` can check amr-nb file correctly""" + duration = 1 + num_channels = 1 + sample_rate = 8000 + path = self.get_temp_path('data.amr-nb') + sox_utils.gen_audio_file( + path, sample_rate=sample_rate, num_channels=num_channels, bit_depth=16, + 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 == 0 + assert info.encoding == "AMR_NB" + + 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" + + def test_gsm(self): + """`sox_io_backend.info` can check gsm file correctly""" + duration = 1 + num_channels = 1 + sample_rate = 8000 + path = self.get_temp_path('data.gsm') + sox_utils.gen_audio_file( + path, sample_rate=sample_rate, num_channels=num_channels, + duration=duration) + info = sox_io_backend.info(path) + assert info.sample_rate == sample_rate + assert info.num_channels == num_channels + assert info.bits_per_sample == 0 + assert info.encoding == "GSM" + + def test_htk(self): + """`sox_io_backend.info` can check HTK file correctly""" + duration = 1 + num_channels = 1 + sample_rate = 8000 + path = self.get_temp_path('data.htk') + sox_utils.gen_audio_file( + path, sample_rate=sample_rate, num_channels=num_channels, + bit_depth=16, 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 == 16 + assert info.encoding == "PCM_S" + + +@skipIfNoSox +class TestInfoOpus(PytorchTestCase): + @parameterized.expand(list(itertools.product( + ['96k'], + [1, 2], + [0, 5, 10], + )), name_func=name_func) + def test_opus(self, bitrate, num_channels, compression_level): + """`sox_io_backend.info` can check opus file correcty""" + path = 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" + + +@skipIfNoSox +class TestLoadWithoutExtension(PytorchTestCase): + def test_mp3(self): + """Providing `format` allows to read mp3 without extension + + libsox does not check header for mp3 + + https://github.com/pytorch/audio/issues/1040 + + The file was generated with the following command + ffmpeg -f lavfi -i "sine=frequency=1000:duration=5" -ar 16000 -f mp3 test_noext + """ + path = get_asset_path("mp3_without_ext") + sinfo = sox_io_backend.info(path, format="mp3") + assert sinfo.sample_rate == 16000 + assert sinfo.num_frames == 81216 + assert sinfo.num_channels == 1 + assert sinfo.bits_per_sample == 0 # bit_per_sample is irrelevant for compressed formats + assert sinfo.encoding == "MP3" + + +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 + + +@skipIfNoSox +@skipIfNoExec('sox') +class TestFileObject(FileObjTestBase, PytorchTestCase): + 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"), + ('mp3', "float32"), + ('flac', "float32"), + ('vorbis', "float32"), + ('amb', "int16"), + ]) + 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([ + ('vorbis', "float32"), + ]) + def test_fileobj_large_header(self, ext, dtype): + """ + For audio file with header size exceeding default buffer size: + - Querying audio via file object without enlarging buffer size fails. + - Querying audio via file object after enlarging buffer size succeeds. + """ + sample_rate = 16000 + num_frames = 3 * sample_rate + num_channels = 2 + comments = "metadata=" + " ".join(["value" for _ in range(1000)]) + + with self.assertRaisesRegex(RuntimeError, "^Error loading audio file:"): + sinfo = self._query_fileobj(ext, dtype, sample_rate, num_channels, num_frames, comments=comments) + + with self._set_buffer_size(16384): + sinfo = self._query_fileobj(ext, dtype, sample_rate, num_channels, num_frames, comments=comments) + 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"), + ('mp3', "float32"), + ('flac', "float32"), + ('vorbis', "float32"), + ('amb', "int16"), + ]) + 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"), + ('mp3', "float32"), + ('flac', "float32"), + ('vorbis', "float32"), + ('amb', "int16"), + ]) + 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"), + ('mp3', "float32"), + ('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 ['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) + + +@skipIfNoSox +@skipIfNoExec('sox') +@skipIfNoModule("requests") +class TestFileObjectHttp(HttpServerMixin, FileObjTestBase, PytorchTestCase): + def _query_http(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) + + url = self.get_url(audio_file) + format_ = ext if ext in ['mp3'] else None + with requests.get(url, stream=True) as resp: + return sox_io_backend.info(resp.raw, format=format_) + + @parameterized.expand([ + ('wav', "float32"), + ('wav', "int32"), + ('wav', "int16"), + ('wav', "uint8"), + ('mp3', "float32"), + ('flac', "float32"), + ('vorbis', "float32"), + ('amb', "int16"), + ]) + def test_requests(self, ext, dtype): + """Querying compressed audio via requests works""" + sample_rate = 16000 + num_frames = 3.0 * sample_rate + num_channels = 2 + sinfo = self._query_http(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) + + +@skipIfNoSox +class TestInfoNoSuchFile(PytorchTestCase): + def test_info_fail(self): + """ + When attempted to get info on a non-existing file, error message must contain the file path. + """ + path = "non_existing_audio.wav" + with self.assertRaisesRegex(RuntimeError, "^Error loading audio file: failed to open file {0}$".format(path)): + sox_io_backend.info(path) diff --git a/test/torchaudio_unittest/backend/sox_io/load_test.py b/test/torchaudio_unittest/backend/sox_io/load_test.py new file mode 100644 index 00000000..824d012c --- /dev/null +++ b/test/torchaudio_unittest/backend/sox_io/load_test.py @@ -0,0 +1,535 @@ +import io +import itertools +import tarfile + +from parameterized import parameterized +from torchaudio.backend import sox_io_backend +from torchaudio._internal import module_utils as _mod_utils + +from torchaudio_unittest.common_utils import ( + TempDirMixin, + HttpServerMixin, + PytorchTestCase, + skipIfNoExec, + skipIfNoModule, + skipIfNoSox, + get_asset_path, + get_wav_data, + load_wav, + save_wav, + sox_utils, +) +from .common import ( + name_func, +) + + +if _mod_utils.is_module_available("requests"): + import requests + + +class LoadTestBase(TempDirMixin, PytorchTestCase): + def assert_format( + self, + format: str, + sample_rate: float, + num_channels: int, + compression: float = None, + bit_depth: int = None, + duration: float = 1, + normalize: bool = True, + encoding: str = None, + atol: float = 4e-05, + rtol: float = 1.3e-06, + ): + """`sox_io_backend.load` can load given format correctly. + + file encodings introduce delay and boundary effects so + we create a reference wav file from the original file format + + x + | + | 1. Generate given format with Sox + | + v 2. Convert to wav with Sox + given format ----------------------> wav + | | + | 3. Load with torchaudio | 4. Load with scipy + | | + v v + tensor ----------> x <----------- tensor + 5. Compare + + Underlying assumptions are; + i. Conversion of given format to wav with Sox preserves data. + ii. Loading wav file with scipy is correct. + + By combining i & ii, step 2. and 4. allows to load reference given format + data without using torchaudio + """ + + path = self.get_temp_path(f'1.original.{format}') + ref_path = self.get_temp_path('2.reference.wav') + + # 1. Generate the given format with sox + sox_utils.gen_audio_file( + path, sample_rate, num_channels, encoding=encoding, + compression=compression, bit_depth=bit_depth, duration=duration, + ) + # 2. Convert to wav with sox + wav_bit_depth = 32 if bit_depth == 24 else None # for 24-bit wav + sox_utils.convert_audio_file(path, ref_path, bit_depth=wav_bit_depth) + # 3. Load the given format with torchaudio + data, sr = sox_io_backend.load(path, normalize=normalize) + # 4. Load wav with scipy + data_ref = load_wav(ref_path, normalize=normalize)[0] + # 5. Compare + assert sr == sample_rate + self.assertEqual(data, data_ref, atol=atol, rtol=rtol) + + 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 = self.get_temp_path('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 + self.assertEqual(data, expected) + + +@skipIfNoExec('sox') +@skipIfNoSox +class TestLoad(LoadTestBase): + """Test the correctness of `sox_io_backend.load` for various formats""" + @parameterized.expand(list(itertools.product( + ['float32', 'int32', 'int16', 'uint8'], + [8000, 16000], + [1, 2], + [False, True], + )), name_func=name_func) + 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) + + @parameterized.expand(list(itertools.product( + [8000, 16000], + [1, 2], + [False, True], + )), name_func=name_func) + def test_24bit_wav(self, sample_rate, num_channels, normalize): + """`sox_io_backend.load` can load 24bit wav format correctly. Corectly casts it to ``int32`` tensor dtype.""" + self.assert_format("wav", sample_rate, num_channels, bit_depth=24, normalize=normalize, duration=1) + + @parameterized.expand(list(itertools.product( + ['int16'], + [16000], + [2], + [False], + )), name_func=name_func) + def test_wav_large(self, dtype, sample_rate, num_channels, normalize): + """`sox_io_backend.load` can load large wav file correctly.""" + two_hours = 2 * 60 * 60 + self.assert_wav(dtype, sample_rate, num_channels, normalize, two_hours) + + @parameterized.expand(list(itertools.product( + ['float32', 'int32', 'int16', 'uint8'], + [4, 8, 16, 32], + )), name_func=name_func) + def test_multiple_channels(self, dtype, num_channels): + """`sox_io_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, duration=1) + + @parameterized.expand(list(itertools.product( + [8000, 16000, 44100], + [1, 2], + [96, 128, 160, 192, 224, 256, 320], + )), name_func=name_func) + def test_mp3(self, sample_rate, num_channels, bit_rate): + """`sox_io_backend.load` can load mp3 format correctly.""" + self.assert_format("mp3", sample_rate, num_channels, compression=bit_rate, duration=1, atol=5e-05) + + @parameterized.expand(list(itertools.product( + [16000], + [2], + [128], + )), name_func=name_func) + def test_mp3_large(self, sample_rate, num_channels, bit_rate): + """`sox_io_backend.load` can load large mp3 file correctly.""" + two_hours = 2 * 60 * 60 + self.assert_format("mp3", sample_rate, num_channels, compression=bit_rate, duration=two_hours, atol=5e-05) + + @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): + """`sox_io_backend.load` can load flac format correctly.""" + self.assert_format("flac", sample_rate, num_channels, compression=compression_level, bit_depth=16, duration=1) + + @parameterized.expand(list(itertools.product( + [16000], + [2], + [0], + )), name_func=name_func) + def test_flac_large(self, sample_rate, num_channels, compression_level): + """`sox_io_backend.load` can load large flac file correctly.""" + two_hours = 2 * 60 * 60 + self.assert_format( + "flac", sample_rate, num_channels, compression=compression_level, bit_depth=16, duration=two_hours) + + @parameterized.expand(list(itertools.product( + [8000, 16000], + [1, 2], + [-1, 0, 1, 2, 3, 3.6, 5, 10], + )), name_func=name_func) + def test_vorbis(self, sample_rate, num_channels, quality_level): + """`sox_io_backend.load` can load vorbis format correctly.""" + self.assert_format("vorbis", sample_rate, num_channels, compression=quality_level, bit_depth=16, duration=1) + + @parameterized.expand(list(itertools.product( + [16000], + [2], + [10], + )), name_func=name_func) + def test_vorbis_large(self, sample_rate, num_channels, quality_level): + """`sox_io_backend.load` can load large vorbis file correctly.""" + two_hours = 2 * 60 * 60 + self.assert_format( + "vorbis", sample_rate, num_channels, compression=quality_level, bit_depth=16, duration=two_hours) + + @parameterized.expand(list(itertools.product( + ['96k'], + [1, 2], + [0, 5, 10], + )), name_func=name_func) + def test_opus(self, bitrate, num_channels, compression_level): + """`sox_io_backend.load` can load opus file correctly.""" + ops_path = get_asset_path('io', f'{bitrate}_{compression_level}_{num_channels}ch.opus') + wav_path = self.get_temp_path(f'{bitrate}_{compression_level}_{num_channels}ch.opus.wav') + sox_utils.convert_audio_file(ops_path, wav_path) + + expected, sample_rate = load_wav(wav_path) + found, sr = sox_io_backend.load(ops_path) + + assert sample_rate == sr + self.assertEqual(expected, found) + + @parameterized.expand(list(itertools.product( + [8000, 16000], + [1, 2], + )), name_func=name_func) + def test_sphere(self, sample_rate, num_channels): + """`sox_io_backend.load` can load sph format correctly.""" + self.assert_format("sph", sample_rate, num_channels, bit_depth=32, duration=1) + + @parameterized.expand(list(itertools.product( + ['float32', 'int32', 'int16'], + [8000, 16000], + [1, 2], + [False, True], + )), name_func=name_func) + def test_amb(self, dtype, sample_rate, num_channels, normalize): + """`sox_io_backend.load` can load amb format correctly.""" + bit_depth = sox_utils.get_bit_depth(dtype) + encoding = sox_utils.get_encoding(dtype) + self.assert_format( + "amb", sample_rate, num_channels, bit_depth=bit_depth, duration=1, encoding=encoding, normalize=normalize) + + def test_amr_nb(self): + """`sox_io_backend.load` can load amr_nb format correctly.""" + self.assert_format("amr-nb", sample_rate=8000, num_channels=1, bit_depth=32, duration=1) + + +@skipIfNoExec('sox') +@skipIfNoSox +class TestLoadParams(TempDirMixin, PytorchTestCase): + """Test the correctness of frame parameters of `sox_io_backend.load`""" + original = None + path = None + + def setUp(self): + super().setUp() + sample_rate = 8000 + self.original = get_wav_data('float32', num_channels=2) + self.path = self.get_temp_path('test.wav') + save_wav(self.path, self.original, sample_rate) + + @parameterized.expand(list(itertools.product( + [0, 1, 10, 100, 1000], + [-1, 1, 10, 100, 1000], + )), name_func=name_func) + def test_frame(self, frame_offset, num_frames): + """num_frames and frame_offset correctly specify the region of data""" + found, _ = sox_io_backend.load(self.path, frame_offset, num_frames) + frame_end = None if num_frames == -1 else frame_offset + num_frames + self.assertEqual(found, self.original[:, frame_offset:frame_end]) + + @parameterized.expand([(True, ), (False, )], name_func=name_func) + def test_channels_first(self, channels_first): + """channels_first swaps axes""" + found, _ = sox_io_backend.load(self.path, channels_first=channels_first) + expected = self.original if channels_first else self.original.transpose(1, 0) + self.assertEqual(found, expected) + + +@skipIfNoSox +class TestLoadWithoutExtension(PytorchTestCase): + def test_mp3(self): + """Providing format allows to read mp3 without extension + + libsox does not check header for mp3 + + https://github.com/pytorch/audio/issues/1040 + + The file was generated with the following command + ffmpeg -f lavfi -i "sine=frequency=1000:duration=5" -ar 16000 -f mp3 test_noext + """ + path = get_asset_path("mp3_without_ext") + _, sr = sox_io_backend.load(path, format="mp3") + assert sr == 16000 + + +class CloggedFileObj: + def __init__(self, fileobj): + self.fileobj = fileobj + self.buffer = b'' + + def read(self, n): + if not self.buffer: + self.buffer += self.fileobj.read(n) + ret = self.buffer[:2] + self.buffer = self.buffer[2:] + return ret + + +@skipIfNoSox +@skipIfNoExec('sox') +class TestFileObject(TempDirMixin, PytorchTestCase): + """ + In this test suite, the result of file-like object input is compared against file path input, + because `load` function is rigrously tested for file path inputs to match libsox's result, + """ + @parameterized.expand([ + ('wav', None), + ('mp3', 128), + ('mp3', 320), + ('flac', 0), + ('flac', 5), + ('flac', 8), + ('vorbis', -1), + ('vorbis', 10), + ('amb', None), + ]) + def test_fileobj(self, ext, compression): + """Loading audio via file object returns the same result as via file path.""" + sample_rate = 16000 + format_ = ext if ext in ['mp3'] else None + path = self.get_temp_path(f'test.{ext}') + + sox_utils.gen_audio_file( + path, sample_rate, num_channels=2, + compression=compression) + expected, _ = sox_io_backend.load(path) + + with open(path, 'rb') as fileobj: + found, sr = sox_io_backend.load(fileobj, format=format_) + + assert sr == sample_rate + self.assertEqual(expected, found) + + @parameterized.expand([ + ('wav', None), + ('mp3', 128), + ('mp3', 320), + ('flac', 0), + ('flac', 5), + ('flac', 8), + ('vorbis', -1), + ('vorbis', 10), + ('amb', None), + ]) + def test_bytesio(self, ext, compression): + """Loading audio via BytesIO object returns the same result as via file path.""" + sample_rate = 16000 + format_ = ext if ext in ['mp3'] else None + path = self.get_temp_path(f'test.{ext}') + + sox_utils.gen_audio_file( + path, sample_rate, num_channels=2, + compression=compression) + expected, _ = sox_io_backend.load(path) + + with open(path, 'rb') as file_: + fileobj = io.BytesIO(file_.read()) + found, sr = sox_io_backend.load(fileobj, format=format_) + + assert sr == sample_rate + self.assertEqual(expected, found) + + @parameterized.expand([ + ('wav', None), + ('mp3', 128), + ('mp3', 320), + ('flac', 0), + ('flac', 5), + ('flac', 8), + ('vorbis', -1), + ('vorbis', 10), + ('amb', None), + ]) + def test_bytesio_clogged(self, ext, compression): + """Loading audio via clogged file object returns the same result as via file path. + + This test case validates the case where fileobject returns shorter bytes than requeted. + """ + sample_rate = 16000 + format_ = ext if ext in ['mp3'] else None + path = self.get_temp_path(f'test.{ext}') + + sox_utils.gen_audio_file( + path, sample_rate, num_channels=2, + compression=compression) + expected, _ = sox_io_backend.load(path) + + with open(path, 'rb') as file_: + fileobj = CloggedFileObj(io.BytesIO(file_.read())) + found, sr = sox_io_backend.load(fileobj, format=format_) + + assert sr == sample_rate + self.assertEqual(expected, found) + + @parameterized.expand([ + ('wav', None), + ('mp3', 128), + ('mp3', 320), + ('flac', 0), + ('flac', 5), + ('flac', 8), + ('vorbis', -1), + ('vorbis', 10), + ('amb', None), + ]) + def test_bytesio_tiny(self, ext, compression): + """Loading very small audio via file object returns the same result as via file path. + """ + sample_rate = 16000 + format_ = ext if ext in ['mp3'] else None + path = self.get_temp_path(f'test.{ext}') + + sox_utils.gen_audio_file( + path, sample_rate, num_channels=2, + compression=compression, duration=1 / 1600) + expected, _ = sox_io_backend.load(path) + + with open(path, 'rb') as file_: + fileobj = io.BytesIO(file_.read()) + found, sr = sox_io_backend.load(fileobj, format=format_) + + assert sr == sample_rate + self.assertEqual(expected, found) + + @parameterized.expand([ + ('wav', None), + ('mp3', 128), + ('mp3', 320), + ('flac', 0), + ('flac', 5), + ('flac', 8), + ('vorbis', -1), + ('vorbis', 10), + ('amb', None), + ]) + def test_tarfile(self, ext, compression): + """Loading compressed audio via file-like object returns the same result as via file path.""" + sample_rate = 16000 + format_ = ext if ext in ['mp3'] else None + audio_file = f'test.{ext}' + audio_path = self.get_temp_path(audio_file) + archive_path = self.get_temp_path('archive.tar.gz') + + sox_utils.gen_audio_file( + audio_path, sample_rate, num_channels=2, + compression=compression) + expected, _ = sox_io_backend.load(audio_path) + + 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 = sox_io_backend.load(fileobj, format=format_) + + assert sr == sample_rate + self.assertEqual(expected, found) + + +@skipIfNoSox +@skipIfNoExec('sox') +@skipIfNoModule("requests") +class TestFileObjectHttp(HttpServerMixin, PytorchTestCase): + @parameterized.expand([ + ('wav', None), + ('mp3', 128), + ('mp3', 320), + ('flac', 0), + ('flac', 5), + ('flac', 8), + ('vorbis', -1), + ('vorbis', 10), + ('amb', None), + ]) + def test_requests(self, ext, compression): + sample_rate = 16000 + format_ = ext if ext in ['mp3'] else None + audio_file = f'test.{ext}' + audio_path = self.get_temp_path(audio_file) + + sox_utils.gen_audio_file( + audio_path, sample_rate, num_channels=2, compression=compression) + expected, _ = sox_io_backend.load(audio_path) + + url = self.get_url(audio_file) + with requests.get(url, stream=True) as resp: + found, sr = sox_io_backend.load(resp.raw, format=format_) + + assert sr == sample_rate + self.assertEqual(expected, found) + + @parameterized.expand(list(itertools.product( + [0, 1, 10, 100, 1000], + [-1, 1, 10, 100, 1000], + )), name_func=name_func) + def test_frame(self, frame_offset, num_frames): + """num_frames and frame_offset correctly specify the region of data""" + sample_rate = 8000 + audio_file = 'test.wav' + audio_path = self.get_temp_path(audio_file) + + original = get_wav_data('float32', num_channels=2) + save_wav(audio_path, original, sample_rate) + frame_end = None if num_frames == -1 else frame_offset + num_frames + expected = original[:, frame_offset:frame_end] + + url = self.get_url(audio_file) + with requests.get(url, stream=True) as resp: + found, sr = sox_io_backend.load(resp.raw, frame_offset, num_frames) + + assert sr == sample_rate + self.assertEqual(expected, found) + + +@skipIfNoSox +class TestLoadNoSuchFile(PytorchTestCase): + def test_load_fail(self): + """ + When attempted to load a non-existing file, error message must contain the file path. + """ + path = "non_existing_audio.wav" + with self.assertRaisesRegex(RuntimeError, "^Error loading audio file: failed to open file {0}$".format(path)): + sox_io_backend.load(path) diff --git a/test/torchaudio_unittest/backend/sox_io/roundtrip_test.py b/test/torchaudio_unittest/backend/sox_io/roundtrip_test.py new file mode 100644 index 00000000..32c920ee --- /dev/null +++ b/test/torchaudio_unittest/backend/sox_io/roundtrip_test.py @@ -0,0 +1,54 @@ +import itertools + +from torchaudio.backend import sox_io_backend +from parameterized import parameterized + +from torchaudio_unittest.common_utils import ( + TempDirMixin, + PytorchTestCase, + skipIfNoExec, + skipIfNoSox, + get_wav_data, +) +from .common import ( + name_func, + get_enc_params, +) + + +@skipIfNoExec('sox') +@skipIfNoSox +class TestRoundTripIO(TempDirMixin, PytorchTestCase): + """save/load round trip should not degrade data for lossless formats""" + @parameterized.expand(list(itertools.product( + ['float32', 'int32', 'int16', 'uint8'], + [8000, 16000], + [1, 2], + )), name_func=name_func) + def test_wav(self, dtype, sample_rate, num_channels): + """save/load round trip should not degrade data for wav formats""" + original = get_wav_data(dtype, num_channels, normalize=False) + enc, bps = get_enc_params(dtype) + data = original + for i in range(10): + path = self.get_temp_path(f'{i}.wav') + sox_io_backend.save(path, data, sample_rate, encoding=enc, bits_per_sample=bps) + data, sr = sox_io_backend.load(path, normalize=False) + assert sr == sample_rate + self.assertEqual(original, data) + + @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): + """save/load round trip should not degrade data for flac formats""" + original = get_wav_data('float32', num_channels) + data = original + for i in range(10): + path = self.get_temp_path(f'{i}.flac') + sox_io_backend.save(path, data, sample_rate, compression=compression_level) + data, sr = sox_io_backend.load(path) + assert sr == sample_rate + self.assertEqual(original, data) diff --git a/test/torchaudio_unittest/backend/sox_io/save_test.py b/test/torchaudio_unittest/backend/sox_io/save_test.py new file mode 100644 index 00000000..9592e78d --- /dev/null +++ b/test/torchaudio_unittest/backend/sox_io/save_test.py @@ -0,0 +1,402 @@ +import io +import os +import unittest + +import torch +from torchaudio.backend import sox_io_backend +from parameterized import parameterized + +from torchaudio_unittest.common_utils import ( + TempDirMixin, + TorchaudioTestCase, + PytorchTestCase, + skipIfNoExec, + skipIfNoSox, + get_wav_data, + load_wav, + save_wav, + sox_utils, + nested_params, +) +from .common import ( + name_func, + get_enc_params, +) + + +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 SaveTestBase(TempDirMixin, TorchaudioTestCase): + 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 torchaudio | + 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.torchaudio.{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 torchaudio + 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] + + self.assertEqual(found, expected) + + +@skipIfNoExec('sox') +@skipIfNoSox +class SaveTest(SaveTestBase): + @nested_params( + ["path", "fileobj", "bytesio"], + [ + ('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", "fileobj", "bytesio"], + [ + ('float32', ), + ('int32', ), + ('int16', ), + ('uint8', ), + ], + ) + def test_save_wav_dtype(self, test_mode, params): + dtype, = params + self.assert_save_consistency( + "wav", src_dtype=dtype, test_mode=test_mode) + + @nested_params( + ["path", "fileobj", "bytesio"], + [ + None, + -4.2, + -0.2, + 0, + 0.2, + 96, + 128, + 160, + 192, + 224, + 256, + 320, + ], + ) + def test_save_mp3(self, test_mode, bit_rate): + if test_mode in ["fileobj", "bytesio"]: + if bit_rate is not None and bit_rate < 1: + raise unittest.SkipTest( + "mp3 format with variable bit rate is known to " + "not yield the exact same result as sox command.") + self.assert_save_consistency( + "mp3", compression=bit_rate, test_mode=test_mode) + + @nested_params( + ["path", "fileobj", "bytesio"], + [8, 16, 24], + [ + None, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + ], + ) + def test_save_flac(self, test_mode, bits_per_sample, compression_level): + self.assert_save_consistency( + "flac", compression=compression_level, + bits_per_sample=bits_per_sample, test_mode=test_mode) + + @nested_params( + ["path", "fileobj", "bytesio"], + ) + def test_save_htk(self, test_mode): + self.assert_save_consistency("htk", test_mode=test_mode, num_channels=1) + + @nested_params( + ["path", "fileobj", "bytesio"], + [ + None, + -1, + 0, + 1, + 2, + 3, + 3.6, + 5, + 10, + ], + ) + def test_save_vorbis(self, test_mode, quality_level): + self.assert_save_consistency( + "vorbis", compression=quality_level, test_mode=test_mode) + + @nested_params( + ["path", "fileobj", "bytesio"], + [ + ('PCM_S', 8, ), + ('PCM_S', 16, ), + ('PCM_S', 24, ), + ('PCM_S', 32, ), + ('ULAW', 8), + ('ALAW', 8), + ('ALAW', 16), + ('ALAW', 24), + ('ALAW', 32), + ], + ) + def test_save_sphere(self, test_mode, enc_params): + encoding, bits_per_sample = enc_params + self.assert_save_consistency( + "sph", encoding=encoding, bits_per_sample=bits_per_sample, test_mode=test_mode) + + @nested_params( + ["path", "fileobj", "bytesio"], + [ + ('PCM_U', 8, ), + ('PCM_S', 16, ), + ('PCM_S', 24, ), + ('PCM_S', 32, ), + ('PCM_F', 32, ), + ('PCM_F', 64, ), + ('ULAW', 8, ), + ('ALAW', 8, ), + ], + ) + def test_save_amb(self, test_mode, enc_params): + encoding, bits_per_sample = enc_params + self.assert_save_consistency( + "amb", encoding=encoding, bits_per_sample=bits_per_sample, test_mode=test_mode) + + @nested_params( + ["path", "fileobj", "bytesio"], + [ + None, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + ], + ) + def test_save_amr_nb(self, test_mode, bit_rate): + self.assert_save_consistency( + "amr-nb", compression=bit_rate, num_channels=1, test_mode=test_mode) + + @nested_params( + ["path", "fileobj", "bytesio"], + ) + def test_save_gsm(self, test_mode): + self.assert_save_consistency( + "gsm", num_channels=1, test_mode=test_mode) + with self.assertRaises( + RuntimeError, msg="gsm format only supports single channel audio."): + self.assert_save_consistency( + "gsm", num_channels=2, test_mode=test_mode) + with self.assertRaises( + RuntimeError, msg="gsm format only supports a sampling rate of 8kHz."): + self.assert_save_consistency( + "gsm", sample_rate=16000, test_mode=test_mode) + + @parameterized.expand([ + ("wav", "PCM_S", 16), + ("mp3", ), + ("flac", ), + ("vorbis", ), + ("sph", "PCM_S", 16), + ("amr-nb", ), + ("amb", "PCM_S", 16), + ], name_func=name_func) + def test_save_large(self, format, encoding=None, bits_per_sample=None): + """`sox_io_backend.save` can save large files.""" + sample_rate = 8000 + one_hour = 60 * 60 * sample_rate + self.assert_save_consistency( + format, num_channels=1, sample_rate=8000, num_frames=one_hour, + encoding=encoding, bits_per_sample=bits_per_sample) + + @parameterized.expand([ + (32, ), + (64, ), + (128, ), + (256, ), + ], name_func=name_func) + def test_save_multi_channels(self, num_channels): + """`sox_io_backend.save` can save audio with many channels""" + self.assert_save_consistency( + "wav", encoding="PCM_S", bits_per_sample=16, + num_channels=num_channels) + + +@skipIfNoExec('sox') +@skipIfNoSox +class TestSaveParams(TempDirMixin, PytorchTestCase): + """Test the correctness of optional parameters of `sox_io_backend.save`""" + @parameterized.expand([(True, ), (False, )], name_func=name_func) + def test_save_channels_first(self, channels_first): + """channels_first swaps axes""" + path = self.get_temp_path('data.wav') + data = get_wav_data( + 'int16', 2, channels_first=channels_first, normalize=False) + sox_io_backend.save( + path, data, 8000, channels_first=channels_first) + found = load_wav(path, normalize=False)[0] + expected = data if channels_first else data.transpose(1, 0) + self.assertEqual(found, expected) + + @parameterized.expand([ + 'float32', 'int32', 'int16', 'uint8' + ], name_func=name_func) + def test_save_noncontiguous(self, dtype): + """Noncontiguous tensors are saved correctly""" + path = self.get_temp_path('data.wav') + enc, bps = get_enc_params(dtype) + expected = get_wav_data(dtype, 4, normalize=False)[::2, ::2] + assert not expected.is_contiguous() + sox_io_backend.save( + path, expected, 8000, encoding=enc, bits_per_sample=bps) + found = load_wav(path, normalize=False)[0] + self.assertEqual(found, expected) + + @parameterized.expand([ + 'float32', 'int32', 'int16', 'uint8', + ]) + def test_save_tensor_preserve(self, dtype): + """save function should not alter Tensor""" + path = self.get_temp_path('data.wav') + expected = get_wav_data(dtype, 4, normalize=False)[::2, ::2] + + data = expected.clone() + sox_io_backend.save(path, data, 8000) + + self.assertEqual(data, expected) + + +@skipIfNoSox +class TestSaveNonExistingDirectory(PytorchTestCase): + def test_save_fail(self): + """ + When attempted to save into a non-existing dir, error message must contain the file path. + """ + path = os.path.join("non_existing_directory", "foo.wav") + with self.assertRaisesRegex(RuntimeError, "^Error saving audio file: failed to open file {0}$".format(path)): + sox_io_backend.save(path, torch.zeros(1, 1), 8000) diff --git a/test/torchaudio_unittest/backend/sox_io/smoke_test.py b/test/torchaudio_unittest/backend/sox_io/smoke_test.py new file mode 100644 index 00000000..656cfd9f --- /dev/null +++ b/test/torchaudio_unittest/backend/sox_io/smoke_test.py @@ -0,0 +1,155 @@ +import io +import itertools +import unittest + +from torchaudio.utils import sox_utils +from torchaudio.backend import sox_io_backend +from torchaudio._internal.module_utils import is_sox_available +from parameterized import parameterized + +from torchaudio_unittest.common_utils import ( + TempDirMixin, + TorchaudioTestCase, + skipIfNoSox, + get_wav_data, +) +from .common import name_func + + +skipIfNoMP3 = unittest.skipIf( + not is_sox_available() or + 'mp3' not in sox_utils.list_read_formats() or + 'mp3' not in sox_utils.list_write_formats(), + '"sox_io" backend does not support MP3') + + +@skipIfNoSox +class SmokeTest(TempDirMixin, TorchaudioTestCase): + """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}') + 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', '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], + ))) + @skipIfNoMP3 + 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) + + +@skipIfNoSox +class SmokeTestFileObj(TorchaudioTestCase): + """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', '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], + ))) + @skipIfNoMP3 + 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) diff --git a/test/torchaudio_unittest/backend/sox_io/torchscript_test.py b/test/torchaudio_unittest/backend/sox_io/torchscript_test.py new file mode 100644 index 00000000..122f4bc0 --- /dev/null +++ b/test/torchaudio_unittest/backend/sox_io/torchscript_test.py @@ -0,0 +1,148 @@ +import itertools +from typing import Optional + +import torch +import torchaudio +from parameterized import parameterized + +from torchaudio_unittest.common_utils import ( + TempDirMixin, + TorchaudioTestCase, + skipIfNoExec, + skipIfNoSox, + get_wav_data, + save_wav, + load_wav, + sox_utils, + torch_script, +) +from .common import ( + name_func, + get_enc_params, +) + + +def py_info_func(filepath: str) -> torchaudio.backend.sox_io_backend.AudioMetaData: + return torchaudio.info(filepath) + + +def py_load_func(filepath: str, normalize: bool, channels_first: bool): + return torchaudio.load( + filepath, normalize=normalize, channels_first=channels_first) + + +def py_save_func( + filepath: str, + tensor: torch.Tensor, + sample_rate: int, + channels_first: bool = True, + compression: Optional[float] = None, + encoding: Optional[str] = None, + bits_per_sample: Optional[int] = None, +): + torchaudio.save( + filepath, tensor, sample_rate, channels_first, + compression, None, encoding, bits_per_sample) + + +@skipIfNoExec('sox') +@skipIfNoSox +class SoxIO(TempDirMixin, TorchaudioTestCase): + """TorchScript-ability Test suite for `sox_io_backend`""" + backend = 'sox_io' + + @parameterized.expand(list(itertools.product( + ['float32', 'int32', 'int16', 'uint8'], + [8000, 16000], + [1, 2], + )), name_func=name_func) + def test_info_wav(self, dtype, sample_rate, num_channels): + """`sox_io_backend.info` is torchscript-able and returns the same result""" + audio_path = self.get_temp_path(f'{dtype}_{sample_rate}_{num_channels}.wav') + data = get_wav_data(dtype, num_channels, normalize=False, num_frames=1 * sample_rate) + save_wav(audio_path, data, sample_rate) + + ts_info_func = torch_script(py_info_func) + + py_info = py_info_func(audio_path) + ts_info = ts_info_func(audio_path) + + assert py_info.sample_rate == ts_info.sample_rate + assert py_info.num_frames == ts_info.num_frames + assert py_info.num_channels == ts_info.num_channels + + @parameterized.expand(list(itertools.product( + ['float32', 'int32', 'int16', 'uint8'], + [8000, 16000], + [1, 2], + [False, True], + [False, True], + )), name_func=name_func) + def test_load_wav(self, dtype, sample_rate, num_channels, normalize, channels_first): + """`sox_io_backend.load` is torchscript-able and returns the same result""" + audio_path = self.get_temp_path(f'test_load_{dtype}_{sample_rate}_{num_channels}_{normalize}.wav') + data = get_wav_data(dtype, num_channels, normalize=False, num_frames=1 * sample_rate) + save_wav(audio_path, data, sample_rate) + + ts_load_func = torch_script(py_load_func) + + py_data, py_sr = py_load_func( + audio_path, normalize=normalize, channels_first=channels_first) + ts_data, ts_sr = ts_load_func( + audio_path, normalize=normalize, channels_first=channels_first) + + self.assertEqual(py_sr, ts_sr) + self.assertEqual(py_data, ts_data) + + @parameterized.expand(list(itertools.product( + ['float32', 'int32', 'int16', 'uint8'], + [8000, 16000], + [1, 2], + )), name_func=name_func) + def test_save_wav(self, dtype, sample_rate, num_channels): + ts_save_func = torch_script(py_save_func) + + expected = get_wav_data(dtype, num_channels, normalize=False) + py_path = self.get_temp_path(f'test_save_py_{dtype}_{sample_rate}_{num_channels}.wav') + ts_path = self.get_temp_path(f'test_save_ts_{dtype}_{sample_rate}_{num_channels}.wav') + enc, bps = get_enc_params(dtype) + + py_save_func(py_path, expected, sample_rate, True, None, enc, bps) + ts_save_func(ts_path, expected, sample_rate, True, None, enc, bps) + + py_data, py_sr = load_wav(py_path, normalize=False) + ts_data, ts_sr = load_wav(ts_path, normalize=False) + + self.assertEqual(sample_rate, py_sr) + self.assertEqual(sample_rate, ts_sr) + self.assertEqual(expected, py_data) + self.assertEqual(expected, ts_data) + + @parameterized.expand(list(itertools.product( + [8000, 16000], + [1, 2], + list(range(9)), + )), name_func=name_func) + def test_save_flac(self, sample_rate, num_channels, compression_level): + ts_save_func = torch_script(py_save_func) + + expected = get_wav_data('float32', num_channels) + py_path = self.get_temp_path(f'test_save_py_{sample_rate}_{num_channels}_{compression_level}.flac') + ts_path = self.get_temp_path(f'test_save_ts_{sample_rate}_{num_channels}_{compression_level}.flac') + + py_save_func(py_path, expected, sample_rate, True, compression_level, None, None) + ts_save_func(ts_path, expected, sample_rate, True, compression_level, None, None) + + # converting to 32 bit because flac file has 24 bit depth which scipy cannot handle. + py_path_wav = f'{py_path}.wav' + ts_path_wav = f'{ts_path}.wav' + sox_utils.convert_audio_file(py_path, py_path_wav, bit_depth=32) + sox_utils.convert_audio_file(ts_path, ts_path_wav, bit_depth=32) + + py_data, py_sr = load_wav(py_path_wav, normalize=True) + ts_data, ts_sr = load_wav(ts_path_wav, normalize=True) + + self.assertEqual(sample_rate, py_sr) + self.assertEqual(sample_rate, ts_sr) + self.assertEqual(expected, py_data) + self.assertEqual(expected, ts_data) diff --git a/test/torchaudio_unittest/backend/utils_test.py b/test/torchaudio_unittest/backend/utils_test.py new file mode 100644 index 00000000..c89f1e97 --- /dev/null +++ b/test/torchaudio_unittest/backend/utils_test.py @@ -0,0 +1,36 @@ +import torchaudio + +from torchaudio_unittest import common_utils + + +class BackendSwitchMixin: + """Test set/get_audio_backend works""" + backend = None + backend_module = None + + def test_switch(self): + torchaudio.set_audio_backend(self.backend) + if self.backend is None: + assert torchaudio.get_audio_backend() is None + else: + assert torchaudio.get_audio_backend() == self.backend + assert torchaudio.load == self.backend_module.load + assert torchaudio.save == self.backend_module.save + assert torchaudio.info == self.backend_module.info + + +class TestBackendSwitch_NoBackend(BackendSwitchMixin, common_utils.TorchaudioTestCase): + backend = None + backend_module = torchaudio.backend.no_backend + + +@common_utils.skipIfNoSox +class TestBackendSwitch_SoXIO(BackendSwitchMixin, common_utils.TorchaudioTestCase): + backend = 'sox_io' + backend_module = torchaudio.backend.sox_io_backend + + +@common_utils.skipIfNoModule('soundfile') +class TestBackendSwitch_soundfile(BackendSwitchMixin, common_utils.TorchaudioTestCase): + backend = 'soundfile' + backend_module = torchaudio.backend.soundfile_backend diff --git a/test/torchaudio_unittest/common_utils/__init__.py b/test/torchaudio_unittest/common_utils/__init__.py new file mode 100644 index 00000000..48d53ba8 --- /dev/null +++ b/test/torchaudio_unittest/common_utils/__init__.py @@ -0,0 +1,63 @@ +from .data_utils import ( + get_asset_path, + get_whitenoise, + get_sinusoid, + get_spectrogram, +) +from .backend_utils import ( + set_audio_backend, +) +from .case_utils import ( + TempDirMixin, + HttpServerMixin, + TestBaseMixin, + PytorchTestCase, + TorchaudioTestCase, + skipIfNoCuda, + skipIfNoExec, + skipIfNoModule, + skipIfNoKaldi, + skipIfNoSox, + skipIfRocm, + skipIfNoQengine, +) +from .wav_utils import ( + get_wav_data, + normalize_wav, + load_wav, + save_wav, +) +from .parameterized_utils import ( + load_params, + nested_params +) +from .func_utils import torch_script + + +__all__ = [ + 'get_asset_path', + 'get_whitenoise', + 'get_sinusoid', + 'get_spectrogram', + 'set_audio_backend', + 'TempDirMixin', + 'HttpServerMixin', + 'TestBaseMixin', + 'PytorchTestCase', + 'TorchaudioTestCase', + 'skipIfNoCuda', + 'skipIfNoExec', + 'skipIfNoModule', + 'skipIfNoKaldi', + 'skipIfNoSox', + 'skipIfNoSoxBackend', + 'skipIfRocm', + 'skipIfNoQengine', + 'get_wav_data', + 'normalize_wav', + 'load_wav', + 'save_wav', + 'load_params', + 'nested_params', + 'torch_script', +] diff --git a/test/torchaudio_unittest/common_utils/backend_utils.py b/test/torchaudio_unittest/common_utils/backend_utils.py new file mode 100644 index 00000000..84dd73ed --- /dev/null +++ b/test/torchaudio_unittest/common_utils/backend_utils.py @@ -0,0 +1,21 @@ +import unittest + +import torchaudio + + +def set_audio_backend(backend): + """Allow additional backend value, 'default'""" + backends = torchaudio.list_audio_backends() + if backend == 'soundfile': + be = 'soundfile' + elif backend == 'default': + if 'sox_io' in backends: + be = 'sox_io' + elif 'soundfile' in backends: + be = 'soundfile' + else: + raise unittest.SkipTest('No default backend available') + else: + be = backend + + torchaudio.set_audio_backend(be) diff --git a/test/torchaudio_unittest/common_utils/case_utils.py b/test/torchaudio_unittest/common_utils/case_utils.py new file mode 100644 index 00000000..140291a8 --- /dev/null +++ b/test/torchaudio_unittest/common_utils/case_utils.py @@ -0,0 +1,123 @@ +import shutil +import os.path +import subprocess +import tempfile +import time +import unittest + +import torch +from torch.testing._internal.common_utils import TestCase as PytorchTestCase +from torchaudio._internal.module_utils import ( + is_module_available, + is_sox_available, + is_kaldi_available +) + +from .backend_utils import set_audio_backend + + +class TempDirMixin: + """Mixin to provide easy access to temp dir""" + temp_dir_ = None + + @classmethod + def get_base_temp_dir(cls): + # If TORCHAUDIO_TEST_TEMP_DIR is set, use it instead of temporary directory. + # this is handy for debugging. + key = 'TORCHAUDIO_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): + super().tearDownClass() + if cls.temp_dir_ is not None: + cls.temp_dir_.cleanup() + cls.temp_dir_ = None + + 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 + + +class HttpServerMixin(TempDirMixin): + """Mixin that serves temporary directory as web server + + This class creates temporary directory and serve the directory as HTTP service. + The server is up through the execution of all the test suite defined under the subclass. + """ + _proc = None + _port = 8000 + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls._proc = subprocess.Popen( + ['python', '-m', 'http.server', f'{cls._port}'], + cwd=cls.get_base_temp_dir(), + stderr=subprocess.DEVNULL) # Disable server-side error log because it is confusing + time.sleep(2.0) + + @classmethod + def tearDownClass(cls): + super().tearDownClass() + cls._proc.kill() + + def get_url(self, *route): + return f'http://localhost:{self._port}/{self.id()}/{"/".join(route)}' + + +class TestBaseMixin: + """Mixin to provide consistent way to define device/dtype/backend aware TestCase""" + dtype = None + device = None + backend = None + + def setUp(self): + super().setUp() + set_audio_backend(self.backend) + + @property + def complex_dtype(self): + if self.dtype in ['float32', 'float', torch.float, torch.float32]: + return torch.cfloat + if self.dtype in ['float64', 'double', torch.double, torch.float64]: + return torch.cdouble + raise ValueError(f'No corresponding complex dtype for {self.dtype}') + + +class TorchaudioTestCase(TestBaseMixin, PytorchTestCase): + pass + + +def skipIfNoExec(cmd): + return unittest.skipIf(shutil.which(cmd) is None, f'`{cmd}` is not available') + + +def skipIfNoModule(module, display_name=None): + display_name = display_name or module + return unittest.skipIf(not is_module_available(module), f'"{display_name}" is not available') + + +def skipIfNoCuda(test_item): + if torch.cuda.is_available(): + return test_item + force_cuda_test = os.environ.get('TORCHAUDIO_TEST_FORCE_CUDA', '0') + if force_cuda_test not in ['0', '1']: + raise ValueError('"TORCHAUDIO_TEST_FORCE_CUDA" must be either "0" or "1".') + if force_cuda_test == '1': + raise RuntimeError('"TORCHAUDIO_TEST_FORCE_CUDA" is set but CUDA is not available.') + return unittest.skip('CUDA is not available.')(test_item) +skipIfNoSox = unittest.skipIf(not is_sox_available(), reason='Sox not available') +skipIfNoKaldi = unittest.skipIf(not is_kaldi_available(), reason='Kaldi not available') +skipIfRocm = unittest.skipIf(os.getenv('TORCHAUDIO_TEST_WITH_ROCM', '0') == '1', + reason="test doesn't currently work on the ROCm stack") +skipIfNoQengine = unittest.skipIf( + 'fbgemm' not in torch.backends.quantized.supported_engines, + reason="`fbgemm` is not available." +) diff --git a/test/torchaudio_unittest/common_utils/data_utils.py b/test/torchaudio_unittest/common_utils/data_utils.py new file mode 100644 index 00000000..0c97cc1c --- /dev/null +++ b/test/torchaudio_unittest/common_utils/data_utils.py @@ -0,0 +1,155 @@ +import os.path +from typing import Union, Optional + +import torch + + +_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 convert_tensor_encoding( + tensor: torch.tensor, + dtype: torch.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 == torch.int32: + tensor *= (tensor > 0) * 2147483647 + (tensor < 0) * 2147483648 + if dtype == torch.int16: + tensor *= (tensor > 0) * 32767 + (tensor < 0) * 32768 + if dtype == torch.uint8: + tensor *= (tensor > 0) * 127 + (tensor < 0) * 128 + tensor += 128 + tensor = tensor.to(dtype) + return tensor + + +def get_whitenoise( + *, + sample_rate: int = 16000, + duration: float = 1, # seconds + n_channels: int = 1, + seed: int = 0, + dtype: Union[str, torch.dtype] = "float32", + device: Union[str, torch.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(torch, dtype) + if dtype not in [torch.float64, torch.float32, torch.int32, torch.int16, torch.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 torch.random.fork_rng([]): + torch.random.manual_seed(seed) + tensor = torch.randn([n_channels, int(sample_rate * duration)], + dtype=torch.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: Union[str, torch.dtype] = "float32", + device: Union[str, torch.device] = "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(torch, dtype) + pie2 = 2 * 3.141592653589793 + end = pie2 * frequency * duration + theta = torch.linspace(0, end, int(sample_rate * duration), dtype=torch.float32, device=device) + tensor = torch.sin(theta, out=None).repeat([n_channels, 1]) + if not channels_first: + tensor = tensor.t() + return convert_tensor_encoding(tensor, dtype) + + +def get_spectrogram( + waveform, + *, + n_fft: int = 2048, + hop_length: Optional[int] = None, + win_length: Optional[int] = None, + window: Optional[torch.Tensor] = None, + center: bool = True, + pad_mode: str = 'reflect', + power: Optional[float] = None, +): + """Generate a spectrogram of the given Tensor + + Args: + n_fft: The number of FFT bins. + hop_length: Stride for sliding window. default: ``n_fft // 4``. + win_length: The size of window frame and STFT filter. default: ``n_fft``. + winwdow: Window function. default: Hann window + center: Pad the input sequence if True. See ``torch.stft`` for the detail. + pad_mode: Padding method used when center is True. Default: "reflect". + power: If ``None``, raw spectrogram with complex values are returned, + otherwise the norm of the spectrogram is returned. + """ + hop_length = hop_length or n_fft // 4 + win_length = win_length or n_fft + window = torch.hann_window(win_length, device=waveform.device) if window is None else window + spec = torch.stft( + waveform, + n_fft=n_fft, + hop_length=hop_length, + win_length=win_length, + center=center, + window=window, + pad_mode=pad_mode, + return_complex=True) + if power is not None: + spec = spec.abs() ** power + return spec diff --git a/test/torchaudio_unittest/common_utils/func_utils.py b/test/torchaudio_unittest/common_utils/func_utils.py new file mode 100644 index 00000000..f8dd5dd4 --- /dev/null +++ b/test/torchaudio_unittest/common_utils/func_utils.py @@ -0,0 +1,10 @@ +import io +import torch + + +def torch_script(obj): + """TorchScript the given function or Module""" + buffer = io.BytesIO() + torch.jit.save(torch.jit.script(obj), buffer) + buffer.seek(0) + return torch.jit.load(buffer) diff --git a/test/torchaudio_unittest/common_utils/kaldi_utils.py b/test/torchaudio_unittest/common_utils/kaldi_utils.py new file mode 100644 index 00000000..71053bd0 --- /dev/null +++ b/test/torchaudio_unittest/common_utils/kaldi_utils.py @@ -0,0 +1,38 @@ +import subprocess + +import torch + + +def convert_args(**kwargs): + args = [] + for key, value in kwargs.items(): + if key == 'sample_rate': + key = 'sample_frequency' + key = '--' + key.replace('_', '-') + value = str(value).lower() if value in [True, False] else str(value) + args.append('%s=%s' % (key, value)) + return args + + +def run_kaldi(command, input_type, input_value): + """Run provided Kaldi command, pass a tensor and get the resulting tensor + + Args: + command (list of str): The command with arguments + input_type (str): 'ark' or 'scp' + input_value (Tensor for 'ark', string for 'scp'): The input to pass. + Must be a path to an audio file for 'scp'. + """ + import kaldi_io + + key = 'foo' + process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE) + if input_type == 'ark': + kaldi_io.write_mat(process.stdin, input_value.cpu().numpy(), key=key) + elif input_type == 'scp': + process.stdin.write(f'{key} {input_value}'.encode('utf8')) + else: + raise NotImplementedError('Unexpected type') + process.stdin.close() + result = dict(kaldi_io.read_mat_ark(process.stdout))['foo'] + return torch.from_numpy(result.copy()) # copy supresses some torch warning diff --git a/test/torchaudio_unittest/common_utils/parameterized_utils.py b/test/torchaudio_unittest/common_utils/parameterized_utils.py new file mode 100644 index 00000000..000dccaf --- /dev/null +++ b/test/torchaudio_unittest/common_utils/parameterized_utils.py @@ -0,0 +1,53 @@ +import json +from itertools import product + +from parameterized import param, parameterized + +from .data_utils import get_asset_path + + +def load_params(*paths): + with open(get_asset_path(*paths), 'r') as file: + return [param(json.loads(line)) for line in file] + + +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).replace(".", "_") + return f'{func.__name__}_{name}' + + +def nested_params(*params_set): + """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/test/torchaudio_unittest/common_utils/psd_utils.py b/test/torchaudio_unittest/common_utils/psd_utils.py new file mode 100644 index 00000000..3ab33546 --- /dev/null +++ b/test/torchaudio_unittest/common_utils/psd_utils.py @@ -0,0 +1,27 @@ +from typing import Optional + +import numpy as np +import torch + + +def psd_numpy( + X: np.array, + mask: Optional[np.array], + multi_mask: bool = False, + normalize: bool = True, + eps: float = 1e-15 +) -> np.array: + X_conj = np.conj(X) + psd_X = np.einsum("...cft,...eft->...ftce", X, X_conj) + if mask is not None: + if multi_mask: + mask = mask.mean(axis=-3) + if normalize: + mask = mask / (mask.sum(axis=-1, keepdims=True) + eps) + psd = psd_X * mask[..., None, None] + else: + psd = psd_X + + psd = psd.sum(axis=-3) + + return torch.tensor(psd, dtype=torch.cdouble) diff --git a/test/torchaudio_unittest/common_utils/rnnt_utils.py b/test/torchaudio_unittest/common_utils/rnnt_utils.py new file mode 100644 index 00000000..94ea300e --- /dev/null +++ b/test/torchaudio_unittest/common_utils/rnnt_utils.py @@ -0,0 +1,603 @@ +import unittest +import random +import torch +import numpy as np +from torchaudio.functional import rnnt_loss + + +CPU_DEVICE = torch.device("cpu") + + +class _NumpyTransducer(torch.autograd.Function): + @staticmethod + def forward( + ctx, + log_probs, + logit_lengths, + target_lengths, + targets, + blank=-1, + ): + device = log_probs.device + log_probs = log_probs.cpu().data.numpy() + logit_lengths = logit_lengths.cpu().data.numpy() + target_lengths = target_lengths.cpu().data.numpy() + targets = targets.cpu().data.numpy() + + gradients, costs, _, _ = __class__.compute( + log_probs=log_probs, + logit_lengths=logit_lengths, + target_lengths=target_lengths, + targets=targets, + blank=blank, + ) + + costs = torch.FloatTensor(costs).to(device=device) + gradients = torch.FloatTensor(gradients).to(device=device) + ctx.grads = torch.autograd.Variable(gradients) + + return costs + + @staticmethod + def backward(ctx, grad_output): + grad_output = grad_output.view(-1, 1, 1, 1).to(ctx.grads) + return ctx.grads.mul(grad_output), None, None, None, None, None, None, None, None + + @staticmethod + def compute_alpha_one_sequence(log_probs, targets, blank=-1): + max_T, max_U, D = log_probs.shape + alpha = np.zeros((max_T, max_U), dtype=np.float32) + for t in range(1, max_T): + alpha[t, 0] = alpha[t - 1, 0] + log_probs[t - 1, 0, blank] + + for u in range(1, max_U): + alpha[0, u] = alpha[0, u - 1] + log_probs[0, u - 1, targets[u - 1]] + + for t in range(1, max_T): + for u in range(1, max_U): + skip = alpha[t - 1, u] + log_probs[t - 1, u, blank] + emit = alpha[t, u - 1] + log_probs[t, u - 1, targets[u - 1]] + alpha[t, u] = np.logaddexp(skip, emit) + + cost = -(alpha[-1, -1] + log_probs[-1, -1, blank]) + return alpha, cost + + @staticmethod + def compute_beta_one_sequence(log_probs, targets, blank=-1): + max_T, max_U, D = log_probs.shape + beta = np.zeros((max_T, max_U), dtype=np.float32) + beta[-1, -1] = log_probs[-1, -1, blank] + + for t in reversed(range(max_T - 1)): + beta[t, -1] = beta[t + 1, -1] + log_probs[t, -1, blank] + + for u in reversed(range(max_U - 1)): + beta[-1, u] = beta[-1, u + 1] + log_probs[-1, u, targets[u]] + + for t in reversed(range(max_T - 1)): + for u in reversed(range(max_U - 1)): + skip = beta[t + 1, u] + log_probs[t, u, blank] + emit = beta[t, u + 1] + log_probs[t, u, targets[u]] + beta[t, u] = np.logaddexp(skip, emit) + + cost = -beta[0, 0] + return beta, cost + + @staticmethod + def compute_gradients_one_sequence( + log_probs, alpha, beta, targets, blank=-1 + ): + max_T, max_U, D = log_probs.shape + gradients = np.full(log_probs.shape, float("-inf")) + cost = -beta[0, 0] + + gradients[-1, -1, blank] = alpha[-1, -1] + + gradients[:-1, :, blank] = alpha[:-1, :] + beta[1:, :] + + for u, l in enumerate(targets): + gradients[:, u, l] = alpha[:, u] + beta[:, u + 1] + + gradients = -(np.exp(gradients + log_probs + cost)) + return gradients + + @staticmethod + def compute( + log_probs, + logit_lengths, + target_lengths, + targets, + blank=-1, + ): + gradients = np.zeros_like(log_probs) + B_tgt, max_T, max_U, D = log_probs.shape + B_src = logit_lengths.shape[0] + + H = int(B_tgt / B_src) + + alphas = np.zeros((B_tgt, max_T, max_U)) + betas = np.zeros((B_tgt, max_T, max_U)) + betas.fill(float("-inf")) + alphas.fill(float("-inf")) + costs = np.zeros(B_tgt) + for b_tgt in range(B_tgt): + b_src = int(b_tgt / H) + T = int(logit_lengths[b_src]) + # NOTE: see https://arxiv.org/pdf/1211.3711.pdf Section 2.1 + U = int(target_lengths[b_tgt]) + 1 + + seq_log_probs = log_probs[b_tgt, :T, :U, :] + seq_targets = targets[b_tgt, : int(target_lengths[b_tgt])] + alpha, alpha_cost = __class__.compute_alpha_one_sequence( + log_probs=seq_log_probs, targets=seq_targets, blank=blank + ) + + beta, beta_cost = __class__.compute_beta_one_sequence( + log_probs=seq_log_probs, targets=seq_targets, blank=blank + ) + + seq_gradients = __class__.compute_gradients_one_sequence( + log_probs=seq_log_probs, + alpha=alpha, + beta=beta, + targets=seq_targets, + blank=blank, + ) + np.testing.assert_almost_equal(alpha_cost, beta_cost, decimal=2) + gradients[b_tgt, :T, :U, :] = seq_gradients + costs[b_tgt] = beta_cost + alphas[b_tgt, :T, :U] = alpha + betas[b_tgt, :T, :U] = beta + + return gradients, costs, alphas, betas + + +class NumpyTransducerLoss(torch.nn.Module): + def __init__(self, blank=-1): + super().__init__() + self.blank = blank + + def forward( + self, + logits, + logit_lengths, + target_lengths, + targets, + ): + log_probs = torch.nn.functional.log_softmax(logits, dim=-1) + return _NumpyTransducer.apply( + log_probs, + logit_lengths, + target_lengths, + targets, + self.blank, + ) + + +def compute_with_numpy_transducer(data): + costs = NumpyTransducerLoss( + blank=data["blank"], + )( + logits=data["logits"], + logit_lengths=data["logit_lengths"], + target_lengths=data["target_lengths"], + targets=data["targets"], + ) + + loss = torch.sum(costs) + loss.backward() + costs = costs.cpu() + gradients = data["logits"].saved_grad.cpu() + return costs, gradients + + +def compute_with_pytorch_transducer(data): + costs = rnnt_loss( + logits=data["logits"], + logit_lengths=data["logit_lengths"], + target_lengths=data["target_lengths"], + targets=data["targets"], + blank=data["blank"], + reduction="none", + ) + + loss = torch.sum(costs) + loss.backward() + costs = costs.cpu() + gradients = data["logits"].saved_grad.cpu() + return costs, gradients + + +def get_basic_data(device): + # Example provided + # in 6f73a2513dc784c59eec153a45f40bc528355b18 + # of https://github.com/HawkAaron/warp-transducer + + logits = torch.tensor( + [ + [ + [ + [0.1, 0.6, 0.1, 0.1, 0.1], + [0.1, 0.1, 0.6, 0.1, 0.1], + [0.1, 0.1, 0.2, 0.8, 0.1], + ], + [ + [0.1, 0.6, 0.1, 0.1, 0.1], + [0.1, 0.1, 0.2, 0.1, 0.1], + [0.7, 0.1, 0.2, 0.1, 0.1], + ], + ] + ], + dtype=torch.float32, + device=device, + ) + targets = torch.tensor([[1, 2]], dtype=torch.int, device=device) + logit_lengths = torch.tensor([2], dtype=torch.int, device=device) + target_lengths = torch.tensor([2], dtype=torch.int, device=device) + + logits.requires_grad_(True) + + return logits, targets, logit_lengths, target_lengths + + +def get_B1_T10_U3_D4_data( + random=False, + dtype=torch.float32, + device=CPU_DEVICE, +): + B, T, U, D = 2, 10, 3, 4 + + logits = torch.rand(B, T, U, D, dtype=dtype, device=device) + if not random: + logits.fill_(0.1) + logits.requires_grad_(True) + + def grad_hook(grad): + logits.saved_grad = grad.clone() + logits.register_hook(grad_hook) + + data = {} + data["logits"] = logits + data["logit_lengths"] = torch.tensor([10, 10], dtype=torch.int32, device=device) + data["target_lengths"] = torch.tensor([2, 2], dtype=torch.int32, device=device) + data["targets"] = torch.tensor([[1, 2], [1, 2]], dtype=torch.int32, device=device) + data["blank"] = 0 + + return data + + +def get_B1_T2_U3_D5_data(dtype=torch.float32, device=CPU_DEVICE): + logits = torch.tensor( + [ + 0.1, + 0.6, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.6, + 0.1, + 0.1, + 0.1, + 0.1, + 0.2, + 0.8, + 0.1, + 0.1, + 0.6, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.2, + 0.1, + 0.1, + 0.7, + 0.1, + 0.2, + 0.1, + 0.1, + ], + dtype=dtype, + device=device, + ).reshape(1, 2, 3, 5) + logits.requires_grad_(True) + + def grad_hook(grad): + logits.saved_grad = grad.clone() + logits.register_hook(grad_hook) + + targets = torch.tensor([[1, 2]], dtype=torch.int32, device=device) + logit_lengths = torch.tensor([2], dtype=torch.int32, device=device) + target_lengths = torch.tensor([2], dtype=torch.int32, device=device) + + blank = -1 + + ref_costs = torch.tensor([5.09566688538], dtype=dtype) + ref_gradients = torch.tensor( + [ + 0.17703132, + -0.39992708, + 0.17703132, + 0.17703132, + -0.13116692, + 0.12247062, + 0.12247062, + -0.181684, + 0.12247062, + -0.1857276, + 0.06269141, + 0.06269141, + 0.06928471, + 0.12624498, + -0.32091248, + 0.05456069, + -0.2182428, + 0.05456069, + 0.05456069, + 0.05456069, + 0.12073967, + 0.12073967, + -0.48295838, + 0.12073967, + 0.12073967, + 0.30741188, + 0.16871123, + 0.18645471, + 0.16871123, + -0.83128875, + ], + dtype=dtype, + ).reshape(1, 2, 3, 5) + + data = { + "logits": logits, + "targets": targets, + "logit_lengths": logit_lengths, + "target_lengths": target_lengths, + "blank": blank, + } + + return data, ref_costs, ref_gradients + + +def get_B2_T4_U3_D3_data(dtype=torch.float32, device=CPU_DEVICE): + # Test from D21322854 + logits = torch.tensor( + [ + 0.065357, + 0.787530, + 0.081592, + 0.529716, + 0.750675, + 0.754135, + 0.609764, + 0.868140, + 0.622532, + 0.668522, + 0.858039, + 0.164539, + 0.989780, + 0.944298, + 0.603168, + 0.946783, + 0.666203, + 0.286882, + 0.094184, + 0.366674, + 0.736168, + 0.166680, + 0.714154, + 0.399400, + 0.535982, + 0.291821, + 0.612642, + 0.324241, + 0.800764, + 0.524106, + 0.779195, + 0.183314, + 0.113745, + 0.240222, + 0.339470, + 0.134160, + 0.505562, + 0.051597, + 0.640290, + 0.430733, + 0.829473, + 0.177467, + 0.320700, + 0.042883, + 0.302803, + 0.675178, + 0.569537, + 0.558474, + 0.083132, + 0.060165, + 0.107958, + 0.748615, + 0.943918, + 0.486356, + 0.418199, + 0.652408, + 0.024243, + 0.134582, + 0.366342, + 0.295830, + 0.923670, + 0.689929, + 0.741898, + 0.250005, + 0.603430, + 0.987289, + 0.592606, + 0.884672, + 0.543450, + 0.660770, + 0.377128, + 0.358021, + ], + dtype=dtype, + device=device, + ).reshape(2, 4, 3, 3) + logits.requires_grad_(True) + + def grad_hook(grad): + logits.saved_grad = grad.clone() + logits.register_hook(grad_hook) + + targets = torch.tensor([[1, 2], [1, 1]], dtype=torch.int32, device=device) + logit_lengths = torch.tensor([4, 4], dtype=torch.int32, device=device) + target_lengths = torch.tensor([2, 2], dtype=torch.int32, device=device) + + blank = 0 + + ref_costs = torch.tensor([4.2806528590890736, 3.9384369822503591], dtype=dtype) + + ref_gradients = torch.tensor( + [ + -0.186844, + -0.062555, + 0.249399, + -0.203377, + 0.202399, + 0.000977, + -0.141016, + 0.079123, + 0.061893, + -0.011552, + -0.081280, + 0.092832, + -0.154257, + 0.229433, + -0.075176, + -0.246593, + 0.146405, + 0.100188, + -0.012918, + -0.061593, + 0.074512, + -0.055986, + 0.219831, + -0.163845, + -0.497627, + 0.209240, + 0.288387, + 0.013605, + -0.030220, + 0.016615, + 0.113925, + 0.062781, + -0.176706, + -0.667078, + 0.367659, + 0.299419, + -0.356344, + -0.055347, + 0.411691, + -0.096922, + 0.029459, + 0.067463, + -0.063518, + 0.027654, + 0.035863, + -0.154499, + -0.073942, + 0.228441, + -0.166790, + -0.000088, + 0.166878, + -0.172370, + 0.105565, + 0.066804, + 0.023875, + -0.118256, + 0.094381, + -0.104707, + -0.108934, + 0.213642, + -0.369844, + 0.180118, + 0.189726, + 0.025714, + -0.079462, + 0.053748, + 0.122328, + -0.238789, + 0.116460, + -0.598687, + 0.302203, + 0.296484, + ], + dtype=dtype, + ).reshape(2, 4, 3, 3) + + data = { + "logits": logits, + "targets": targets, + "logit_lengths": logit_lengths, + "target_lengths": target_lengths, + "blank": blank, + } + + return data, ref_costs, ref_gradients + + +def get_random_data( + max_B=8, + max_T=128, + max_U=32, + max_D=40, + blank=-1, + dtype=torch.float32, + device=CPU_DEVICE, + seed=None, +): + if seed is not None: + torch.manual_seed(seed=seed) + + if blank != -1: + raise ValueError("blank != -1 is not supported yet.") + + random.seed(0) + B = random.randint(1, max_B - 1) + T = random.randint(5, max_T - 1) + U = random.randint(5, max_U - 1) + D = random.randint(2, max_D - 1) + + logit_lengths = torch.randint(low=5, high=T + 1, size=(B,), dtype=torch.int32, device=device) + target_lengths = torch.randint(low=5, high=U + 1, size=(B,), dtype=torch.int32, device=device) + max_src_length = torch.max(logit_lengths) + max_tgt_length = torch.max(target_lengths) + + targets = torch.randint( + low=0, high=D - 1, size=(B, max_tgt_length), dtype=torch.int32, device=device + ) + logits = torch.rand( + size=(B, max_src_length, max_tgt_length + 1, D), + dtype=dtype, + device=device, + ).requires_grad_(True) + + def grad_hook(grad): + logits.saved_grad = grad.clone() + logits.register_hook(grad_hook) + + return { + "logits": logits, + "targets": targets, + "logit_lengths": logit_lengths, + "target_lengths": target_lengths, + "blank": blank, + } + + +def skipIfNoRNNT(test_item): + try: + torch.ops.torchaudio.rnnt_loss + return test_item + except RuntimeError: + return unittest.skip("torchaudio C++ extension is not compiled with RNN transducer loss") diff --git a/test/torchaudio_unittest/common_utils/sox_utils.py b/test/torchaudio_unittest/common_utils/sox_utils.py new file mode 100644 index 00000000..e72b10e0 --- /dev/null +++ b/test/torchaudio_unittest/common_utils/sox_utils.py @@ -0,0 +1,106 @@ +import sys +import subprocess +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/test/torchaudio_unittest/common_utils/wav_utils.py b/test/torchaudio_unittest/common_utils/wav_utils.py new file mode 100644 index 00000000..b4b79448 --- /dev/null +++ b/test/torchaudio_unittest/common_utils/wav_utils.py @@ -0,0 +1,92 @@ +from typing import Optional + +import torch +import scipy.io.wavfile + + +def normalize_wav(tensor: torch.Tensor) -> torch.Tensor: + if tensor.dtype == torch.float32: + pass + elif tensor.dtype == torch.int32: + tensor = tensor.to(torch.float32) + tensor[tensor > 0] /= 2147483647. + tensor[tensor < 0] /= 2147483648. + elif tensor.dtype == torch.int16: + tensor = tensor.to(torch.float32) + tensor[tensor > 0] /= 32767. + tensor[tensor < 0] /= 32768. + elif tensor.dtype == torch.uint8: + tensor = tensor.to(torch.float32) - 128 + tensor[tensor > 0] /= 127. + tensor[tensor < 0] /= 128. + 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(torch, dtype) + + if num_frames is None: + if dtype == 'uint8': + num_frames = 256 + else: + num_frames = 1 << 16 + + if dtype == 'uint8': + base = torch.linspace(0, 255, num_frames, dtype=dtype_) + elif dtype == 'int8': + base = torch.linspace(-128, 127, num_frames, dtype=dtype_) + elif dtype == 'float32': + base = torch.linspace(-1., 1., num_frames, dtype=dtype_) + elif dtype == 'float64': + base = torch.linspace(-1., 1., num_frames, dtype=dtype_) + elif dtype == 'int32': + base = torch.linspace(-2147483648, 2147483647, num_frames, dtype=dtype_) + elif dtype == 'int16': + base = torch.linspace(-32768, 32767, num_frames, dtype=dtype_) + else: + raise NotImplementedError(f'Unsupported dtype {dtype}') + data = base.repeat([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) -> torch.Tensor: + """Load wav file without torchaudio""" + sample_rate, data = scipy.io.wavfile.read(path) + data = torch.from_numpy(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 torchaudio""" + if channels_first: + data = data.transpose(1, 0) + scipy.io.wavfile.write(path, sample_rate, data.numpy()) diff --git a/test/torchaudio_unittest/compliance_kaldi_test.py b/test/torchaudio_unittest/compliance_kaldi_test.py new file mode 100644 index 00000000..f4e79d42 --- /dev/null +++ b/test/torchaudio_unittest/compliance_kaldi_test.py @@ -0,0 +1,76 @@ +import torch +import torchaudio.compliance.kaldi as kaldi + +from torchaudio_unittest import common_utils + + +def extract_window(window, wave, f, frame_length, frame_shift, snip_edges): + # just a copy of ExtractWindow from feature-window.cc in python + def first_sample_of_frame(frame, window_size, window_shift, snip_edges): + if snip_edges: + return frame * window_shift + else: + midpoint_of_frame = frame * window_shift + window_shift // 2 + beginning_of_frame = midpoint_of_frame - window_size // 2 + return beginning_of_frame + + sample_offset = 0 + num_samples = sample_offset + wave.size(0) + start_sample = first_sample_of_frame(f, frame_length, frame_shift, snip_edges) + end_sample = start_sample + frame_length + + if snip_edges: + assert(start_sample >= sample_offset and end_sample <= num_samples) + else: + assert(sample_offset == 0 or start_sample >= sample_offset) + + wave_start = start_sample - sample_offset + wave_end = wave_start + frame_length + if wave_start >= 0 and wave_end <= wave.size(0): + window[f, :] = wave[wave_start:(wave_start + frame_length)] + else: + wave_dim = wave.size(0) + for s in range(frame_length): + s_in_wave = s + wave_start + while s_in_wave < 0 or s_in_wave >= wave_dim: + if s_in_wave < 0: + s_in_wave = - s_in_wave - 1 + else: + s_in_wave = 2 * wave_dim - 1 - s_in_wave + window[f, s] = wave[s_in_wave] + + +class Test_Kaldi(common_utils.TempDirMixin, common_utils.TorchaudioTestCase): + + def _test_get_strided_helper(self, num_samples, window_size, window_shift, snip_edges): + waveform = torch.arange(num_samples).float() + output = kaldi._get_strided(waveform, window_size, window_shift, snip_edges) + + # from NumFrames in feature-window.cc + n = window_size + if snip_edges: + m = 0 if num_samples < window_size else 1 + (num_samples - window_size) // window_shift + else: + m = (num_samples + (window_shift // 2)) // window_shift + + self.assertTrue(output.dim() == 2) + self.assertTrue(output.shape[0] == m and output.shape[1] == n) + + window = torch.empty((m, window_size)) + + for r in range(m): + extract_window(window, waveform, r, window_size, window_shift, snip_edges) + self.assertEqual(window, output) + + def test_get_strided(self): + # generate any combination where 0 < window_size <= num_samples and + # 0 < window_shift. + for num_samples in range(1, 20): + for window_size in range(1, num_samples + 1): + for window_shift in range(1, 2 * num_samples + 1): + for snip_edges in range(0, 2): + self._test_get_strided_helper(num_samples, window_size, window_shift, snip_edges) + + def test_mfcc_empty(self): + # Passing in an empty tensor should result in an error + self.assertRaises(AssertionError, kaldi.mfcc, torch.empty(0)) diff --git a/test/torchaudio_unittest/datasets/__init__.py b/test/torchaudio_unittest/datasets/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/torchaudio_unittest/datasets/cmuarctic_test.py b/test/torchaudio_unittest/datasets/cmuarctic_test.py new file mode 100644 index 00000000..10ff7668 --- /dev/null +++ b/test/torchaudio_unittest/datasets/cmuarctic_test.py @@ -0,0 +1,84 @@ +import os +from pathlib import Path + +from torchaudio.datasets import cmuarctic + +from torchaudio_unittest.common_utils import ( + TempDirMixin, + TorchaudioTestCase, + get_whitenoise, + save_wav, + normalize_wav, +) + + +def get_mock_dataset(root_dir): + """ + root_dir: directory to the mocked dataset + """ + mocked_data = [] + sample_rate = 16000 + transcript = "This is a test transcript." + + base_dir = os.path.join(root_dir, "ARCTIC", "cmu_us_aew_arctic") + txt_dir = os.path.join(base_dir, "etc") + os.makedirs(txt_dir, exist_ok=True) + txt_file = os.path.join(txt_dir, "txt.done.data") + audio_dir = os.path.join(base_dir, "wav") + os.makedirs(audio_dir, exist_ok=True) + + seed = 42 + with open(txt_file, "w") as txt: + for c in ["a", "b"]: + for i in range(5): + utterance_id = f"arctic_{c}{i:04d}" + path = os.path.join(audio_dir, f"{utterance_id}.wav") + data = get_whitenoise( + sample_rate=sample_rate, + duration=3, + n_channels=1, + dtype="int16", + seed=seed, + ) + save_wav(path, data, sample_rate) + sample = ( + normalize_wav(data), + sample_rate, + transcript, + utterance_id.split("_")[1], + ) + mocked_data.append(sample) + txt.write(f'( {utterance_id} "{transcript}" )\n') + seed += 1 + return mocked_data + + +class TestCMUARCTIC(TempDirMixin, TorchaudioTestCase): + backend = "default" + + root_dir = None + samples = [] + + @classmethod + def setUpClass(cls): + cls.root_dir = cls.get_base_temp_dir() + cls.samples = get_mock_dataset(cls.root_dir) + + def _test_cmuarctic(self, dataset): + n_ite = 0 + for i, (waveform, sample_rate, transcript, utterance_id) in enumerate(dataset): + expected_sample = self.samples[i] + assert sample_rate == expected_sample[1] + assert transcript == expected_sample[2] + assert utterance_id == expected_sample[3] + self.assertEqual(expected_sample[0], waveform, atol=5e-5, rtol=1e-8) + n_ite += 1 + assert n_ite == len(self.samples) + + def test_cmuarctic_str(self): + dataset = cmuarctic.CMUARCTIC(self.root_dir) + self._test_cmuarctic(dataset) + + def test_cmuarctic_path(self): + dataset = cmuarctic.CMUARCTIC(Path(self.root_dir)) + self._test_cmuarctic(dataset) diff --git a/test/torchaudio_unittest/datasets/cmudict_test.py b/test/torchaudio_unittest/datasets/cmudict_test.py new file mode 100644 index 00000000..da645346 --- /dev/null +++ b/test/torchaudio_unittest/datasets/cmudict_test.py @@ -0,0 +1,218 @@ +import os +from pathlib import Path + +from torchaudio.datasets import CMUDict + +from torchaudio_unittest.common_utils import ( + TempDirMixin, + TorchaudioTestCase, +) + + +def get_mock_dataset(root_dir, return_punc=False): + """ + root_dir: directory to the mocked dataset + """ + header = [ + ";;; # CMUdict -- Major Version: 0.07", + ";;; ", + ";;; # $HeadURL$", + ] + + puncs = [ + "!EXCLAMATION-POINT EH2 K S K L AH0 M EY1 SH AH0 N P OY2 N T", + "\"CLOSE-QUOTE K L OW1 Z K W OW1 T", + "#HASH-MARK HH AE1 M AA2 R K", + "%PERCENT P ER0 S EH1 N T", + "&ERSAND AE1 M P ER0 S AE2 N D", + "'END-INNER-QUOTE EH1 N D IH1 N ER0 K W OW1 T", + "(BEGIN-PARENS B IH0 G IH1 N P ER0 EH1 N Z", + ")CLOSE-PAREN K L OW1 Z P ER0 EH1 N", + "+PLUS P L UH1 S", + ",COMMA K AA1 M AH0", + "--DASH D AE1 SH", + "!EXCLAMATION-POINT EH2 K S K L AH0 M EY1 SH AH0 N P OY2 N T", + "/SLASH S L AE1 SH", + ":COLON K OW1 L AH0 N", + ";SEMI-COLON S EH1 M IY0 K OW1 L AH0 N", + "?QUESTION-MARK K W EH1 S CH AH0 N M AA1 R K", + "{BRACE B R EY1 S", + "}CLOSE-BRACE K L OW1 Z B R EY1 S", + "...ELLIPSIS IH2 L IH1 P S IH0 S", + ] + + punc_outputs = [ + "!", + "\"", + "#", + "%", + "&", + "'", + "(", + ")", + "+", + ",", + "--", + "!", + "/", + ":", + ";", + "?", + "{", + "}", + "...", + ] + + words = [ + "3-D TH R IY1 D IY2", + "'BOUT B AW1 T", + "'CAUSE K AH0 Z", + "'TWAS T W AH1 Z", + "A AH0", + "B B IY1", + "C S IY1", + "D D IY1", + "E IY1", + "F EH1 F", + "G JH IY1", + "H EY1 CH", + "I AY1", + "J JH EY1", + "K K EY1", + "L EH1 L", + "M EH1 M", + "N EH1 N", + "O OW1", + "P P IY1", + "Q K Y UW1", + "R AA1 R", + "S EH1 S", + "T T IY1", + "U Y UW1", + "V V IY1", + "X EH1 K S", + "Y W AY1", + "Z Z IY1", + ] + + mocked_symbols = [ + "AA1", + "AA2", + "AE1", + "AE2", + "AH0", + "AH1", + "AY1", + "B", + "CH", + "D", + "EH1", + "EH2", + "ER0", + "EY1", + "F", + "G", + "HH", + "IH0", + "IH1", + "IY0", + "IY1", + "IY2", + "JH", + "K", + "L", + "M", + "N", + "OW1", + "OY2", + "P", + "R", + "S", + "SH", + "T", + "TH", + "UH1", + "UW0", + "UW1", + "V", + "W", + "Y", + "Z", + ] + + dict_file = os.path.join(root_dir, "cmudict-0.7b") + symbol_file = os.path.join(root_dir, "cmudict-0.7b.symbols") + + with open(dict_file, "w") as fileobj: + for section in [header, puncs, words]: + for line in section: + fileobj.write(line) + fileobj.write("\n") + + with open(symbol_file, "w") as txt: + txt.write("\n".join(mocked_symbols)) + + mocked_data = [] + + if return_punc: + for i, ent in enumerate(puncs): + _, phones = ent.split(" ") + mocked_data.append((punc_outputs[i], phones.split(" "))) + + for ent in words: + word, phones = ent.split(" ") + mocked_data.append((word, phones.split(" "))) + + return mocked_data + + +class TestCMUDict(TempDirMixin, TorchaudioTestCase): + root_dir = None + root_punc_dir = None + samples = [] + punc_samples = [] + + @classmethod + def setUpClass(cls): + cls.root_dir = os.path.join(cls.get_base_temp_dir(), "normal") + os.mkdir(cls.root_dir) + cls.samples = get_mock_dataset(cls.root_dir) + cls.root_punc_dir = os.path.join(cls.get_base_temp_dir(), "punc") + os.mkdir(cls.root_punc_dir) + cls.punc_samples = get_mock_dataset(cls.root_punc_dir, return_punc=True) + + def _test_cmudict(self, dataset): + """Test if the dataset is reading the mocked data correctly.""" + n_item = 0 + for i, (word, phones) in enumerate(dataset): + expected_word, expected_phones = self.samples[i] + assert word == expected_word + assert phones == expected_phones + n_item += 1 + assert n_item == len(self.samples) + + def _test_punc_cmudict(self, dataset): + """Test if the dataset is reading the mocked data with punctuations correctly.""" + n_item = 0 + for i, (word, phones) in enumerate(dataset): + expected_word, expected_phones = self.punc_samples[i] + assert word == expected_word + assert phones == expected_phones + n_item += 1 + assert n_item == len(self.punc_samples) + + def test_cmuarctic_path_with_punctuation(self): + dataset = CMUDict(Path(self.root_punc_dir), exclude_punctuations=False) + self._test_punc_cmudict(dataset) + + def test_cmuarctic_str_with_punctuation(self): + dataset = CMUDict(self.root_punc_dir, exclude_punctuations=False) + self._test_punc_cmudict(dataset) + + def test_cmuarctic_path(self): + dataset = CMUDict(Path(self.root_punc_dir), exclude_punctuations=True) + self._test_cmudict(dataset) + + def test_cmuarctic_str(self): + dataset = CMUDict(self.root_punc_dir, exclude_punctuations=True) + self._test_cmudict(dataset) diff --git a/test/torchaudio_unittest/datasets/commonvoice_test.py b/test/torchaudio_unittest/datasets/commonvoice_test.py new file mode 100644 index 00000000..4d7c269f --- /dev/null +++ b/test/torchaudio_unittest/datasets/commonvoice_test.py @@ -0,0 +1,148 @@ +import csv +import os +from pathlib import Path +from typing import Tuple, Dict + +from torch import Tensor +from torchaudio_unittest.common_utils import ( + TempDirMixin, + TorchaudioTestCase, + get_whitenoise, + save_wav, + normalize_wav, +) + +from torchaudio.datasets import COMMONVOICE + +_ORIGINAL_EXT_AUDIO = COMMONVOICE._ext_audio +_SAMPLE_RATE = 48000 +_HEADERS = [u"client_ids", u"path", u"sentence", u"up_votes", u"down_votes", u"age", u"gender", u"accent"] +_EN_TRAIN_CSV_CONTENTS = [ + ["9d16c5d980247861130e0480e2719f448be73d86a496c36d01a477cbdecd8cfd1399403d7a77bf458d211a70711b2da0845c", + "common_voice_en_18885784.wav", + "He was accorded a State funeral, and was buried in Drayton and Toowoomba Cemetery.", "2", "0", "", "", + ""], + ["c82eb9291328620f06025a1f8112b909099e447e485e99236cb87df008650250e79fea5ca772061fb6a370830847b9c44d20", + "common_voice_en_556542.wav", "Once more into the breach", "2", "0", "thirties", "male", "us"], + ["f74d880c5ad4c5917f314a604d3fc4805159d255796fb9f8defca35333ecc002bdf53dc463503c12674ea840b21b4a507b7c", + "common_voice_en_18607573.wav", + "Caddy, show Miss Clare and Miss Summerson their rooms.", "2", "0", "twenties", "male", "canada"], +] + +_FR_TRAIN_CSV_CONTENTS = [ + [ + "a2e8e1e1cc74d08c92a53d7b9ff84e077eb90410edd85b8882f16fd037cecfcb6a19413c6c63ce6458cfea9579878fa91cef" + "18343441c601cae0597a4b0d3144", + "89e67e7682b36786a0b4b4022c4d42090c86edd96c78c12d30088e62522b8fe466ea4912e6a1055dfb91b296a0743e0a2bbe" + "16cebac98ee5349e3e8262cb9329", + "Or sur ce point nous n’avons aucune réponse de votre part.", "2", "0", "twenties", "male", "france"], + [ + "a2e8e1e1cc74d08c92a53d7b9ff84e077eb90410edd85b8882f16fd037cecfcb6a19413c6c63ce6458cfea9579878fa91cef18" + "343441c601cae0597a4b0d3144", + "87d71819a26179e93acfee149d0b21b7bf5e926e367d80b2b3792d45f46e04853a514945783ff764c1fc237b4eb0ee2b0a7a7" + "cbd395acbdfcfa9d76a6e199bbd", + "Monsieur de La Verpillière, laissez parler le ministre", "2", "0", "twenties", "male", "france"], + +] + + +def get_mock_dataset(root_dir, train_csv_contents, ext_audio) -> Tuple[Tensor, int, Dict[str, str]]: + """ + prepares mocked dataset + """ + mocked_data = [] + # Note: extension is changed to wav for the sake of test + # Note: the first content is missing values for `age`, `gender` and `accent` as in the original data. + # Tsv file name difference does not mean different subset, testing as a whole dataset here + tsv_filename = os.path.join(root_dir, "train.tsv") + audio_base_path = os.path.join(root_dir, "clips") + os.makedirs(audio_base_path, exist_ok=True) + with open(tsv_filename, "w", newline='') as tsv: + writer = csv.writer(tsv, delimiter='\t') + writer.writerow(_HEADERS) + for i, content in enumerate(train_csv_contents): + content[2] = str(content[2].encode("utf-8")) + writer.writerow(content) + if not content[1].endswith(ext_audio): + audio_path = os.path.join(audio_base_path, content[1] + ext_audio) + else: + audio_path = os.path.join(audio_base_path, content[1]) + + data = get_whitenoise(sample_rate=_SAMPLE_RATE, duration=1, n_channels=1, seed=i, dtype='float32') + save_wav(audio_path, data, _SAMPLE_RATE) + # Append data entry + mocked_data.append((normalize_wav(data), _SAMPLE_RATE, dict(zip(_HEADERS, content)))) + return mocked_data + + +def get_mock_dataset_en(root_dir, ext_audio) -> Tuple[Tensor, int, Dict[str, str]]: + """ + prepares english mocked dataset + """ + return get_mock_dataset(root_dir, _EN_TRAIN_CSV_CONTENTS, ext_audio) + + +def get_mock_dataset_fr(root_dir, ext_audio) -> Tuple[Tensor, int, Dict[str, str]]: + """ + prepares french mocked dataset + """ + return get_mock_dataset(root_dir, _FR_TRAIN_CSV_CONTENTS, ext_audio) + + +class BaseTestCommonVoice(TempDirMixin): + root_dir = None + data = None + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.root_dir = cls.get_base_temp_dir() + COMMONVOICE._ext_audio = ".wav" + + @classmethod + def tearDownClass(cls): + super().tearDownClass() + COMMONVOICE._ext_audio = _ORIGINAL_EXT_AUDIO + + def _test_commonvoice(self, dataset): + n_ite = 0 + for i, (waveform, sample_rate, dictionary) in enumerate(dataset): + expected_dictionary = self.data[i][2] + expected_data = self.data[i][0] + self.assertEqual(expected_data, waveform, atol=5e-5, rtol=1e-8) + assert sample_rate == _SAMPLE_RATE + assert dictionary == expected_dictionary + n_ite += 1 + assert n_ite == len(self.data) + + +class TestCommonVoiceEN(BaseTestCommonVoice, TorchaudioTestCase): + backend = 'default' + root_dir = None + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.data = get_mock_dataset_en(cls.root_dir, COMMONVOICE._ext_audio) + + def test_commonvoice_str(self): + dataset = COMMONVOICE(self.root_dir) + self._test_commonvoice(dataset) + + def test_commonvoice_path(self): + dataset = COMMONVOICE(Path(self.root_dir)) + self._test_commonvoice(dataset) + + +class TestCommonVoiceFR(BaseTestCommonVoice, TorchaudioTestCase): + backend = 'default' + root_dir = None + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.data = get_mock_dataset_fr(cls.root_dir, COMMONVOICE._ext_audio) + + def test_commonvoice_str(self): + dataset = COMMONVOICE(self.root_dir) + self._test_commonvoice(dataset) diff --git a/test/torchaudio_unittest/datasets/datasets_test.py b/test/torchaudio_unittest/datasets/datasets_test.py new file mode 100644 index 00000000..fda0804e --- /dev/null +++ b/test/torchaudio_unittest/datasets/datasets_test.py @@ -0,0 +1,15 @@ +from torchaudio.datasets.vctk import VCTK + +from torchaudio_unittest.common_utils import ( + TorchaudioTestCase, + get_asset_path, +) + + +class TestDatasets(TorchaudioTestCase): + backend = 'default' + path = get_asset_path() + + def test_vctk(self): + data = VCTK(self.path) + data[0] diff --git a/test/torchaudio_unittest/datasets/gtzan_test.py b/test/torchaudio_unittest/datasets/gtzan_test.py new file mode 100644 index 00000000..838292f5 --- /dev/null +++ b/test/torchaudio_unittest/datasets/gtzan_test.py @@ -0,0 +1,127 @@ +import os +from pathlib import Path + +from torchaudio.datasets import gtzan + +from torchaudio_unittest.common_utils import ( + TempDirMixin, + TorchaudioTestCase, + get_whitenoise, + save_wav, + normalize_wav, +) + + +def get_mock_dataset(root_dir): + """ + root_dir: directory to the mocked dataset + """ + mocked_samples = [] + mocked_training = [] + mocked_validation = [] + mocked_testing = [] + sample_rate = 22050 + + seed = 0 + for genre in gtzan.gtzan_genres: + base_dir = os.path.join(root_dir, 'genres', genre) + os.makedirs(base_dir, exist_ok=True) + for i in range(100): + filename = f'{genre}.{i:05d}' + path = os.path.join(base_dir, f'{filename}.wav') + data = get_whitenoise(sample_rate=sample_rate, duration=0.01, n_channels=1, dtype='int16', seed=seed) + save_wav(path, data, sample_rate) + sample = (normalize_wav(data), sample_rate, genre) + mocked_samples.append(sample) + if filename in gtzan.filtered_test: + mocked_testing.append(sample) + if filename in gtzan.filtered_train: + mocked_training.append(sample) + if filename in gtzan.filtered_valid: + mocked_validation.append(sample) + seed += 1 + return (mocked_samples, mocked_training, mocked_validation, mocked_testing) + + +class TestGTZAN(TempDirMixin, TorchaudioTestCase): + backend = 'default' + + root_dir = None + samples = [] + training = [] + validation = [] + testing = [] + + @classmethod + def setUpClass(cls): + cls.root_dir = cls.get_base_temp_dir() + mocked_data = get_mock_dataset(cls.root_dir) + cls.samples = mocked_data[0] + cls.training = mocked_data[1] + cls.validation = mocked_data[2] + cls.testing = mocked_data[3] + + def test_no_subset(self): + dataset = gtzan.GTZAN(self.root_dir) + + n_ite = 0 + for i, (waveform, sample_rate, label) in enumerate(dataset): + self.assertEqual(waveform, self.samples[i][0], atol=5e-5, rtol=1e-8) + assert sample_rate == self.samples[i][1] + assert label == self.samples[i][2] + n_ite += 1 + assert n_ite == len(self.samples) + + def _test_training(self, dataset): + n_ite = 0 + for i, (waveform, sample_rate, label) in enumerate(dataset): + self.assertEqual(waveform, self.training[i][0], atol=5e-5, rtol=1e-8) + assert sample_rate == self.training[i][1] + assert label == self.training[i][2] + n_ite += 1 + assert n_ite == len(self.training) + + def _test_validation(self, dataset): + n_ite = 0 + for i, (waveform, sample_rate, label) in enumerate(dataset): + self.assertEqual(waveform, self.validation[i][0], atol=5e-5, rtol=1e-8) + assert sample_rate == self.validation[i][1] + assert label == self.validation[i][2] + n_ite += 1 + assert n_ite == len(self.validation) + + def _test_testing(self, dataset): + n_ite = 0 + for i, (waveform, sample_rate, label) in enumerate(dataset): + self.assertEqual(waveform, self.testing[i][0], atol=5e-5, rtol=1e-8) + assert sample_rate == self.testing[i][1] + assert label == self.testing[i][2] + n_ite += 1 + assert n_ite == len(self.testing) + + def test_training_str(self): + train_dataset = gtzan.GTZAN(self.root_dir, subset='training') + self._test_training(train_dataset) + + def test_validation_str(self): + val_dataset = gtzan.GTZAN(self.root_dir, subset='validation') + self._test_validation(val_dataset) + + def test_testing_str(self): + test_dataset = gtzan.GTZAN(self.root_dir, subset='testing') + self._test_testing(test_dataset) + + def test_training_path(self): + root_dir = Path(self.root_dir) + train_dataset = gtzan.GTZAN(root_dir, subset='training') + self._test_training(train_dataset) + + def test_validation_path(self): + root_dir = Path(self.root_dir) + val_dataset = gtzan.GTZAN(root_dir, subset='validation') + self._test_validation(val_dataset) + + def test_testing_path(self): + root_dir = Path(self.root_dir) + test_dataset = gtzan.GTZAN(root_dir, subset='testing') + self._test_testing(test_dataset) diff --git a/test/torchaudio_unittest/datasets/librispeech_test.py b/test/torchaudio_unittest/datasets/librispeech_test.py new file mode 100644 index 00000000..44e98c1f --- /dev/null +++ b/test/torchaudio_unittest/datasets/librispeech_test.py @@ -0,0 +1,128 @@ +import os +from pathlib import Path + +from torchaudio_unittest.common_utils import ( + TempDirMixin, + TorchaudioTestCase, + get_whitenoise, + save_wav, + normalize_wav, +) + +from torchaudio.datasets import librispeech + +# Used to generate a unique transcript for each dummy audio file +_NUMBERS = [ + 'ZERO', + 'ONE', + 'TWO', + 'THREE', + 'FOUR', + 'FIVE', + 'SIX', + 'SEVEN', + 'EIGHT', + 'NINE' +] + + +def get_mock_dataset(root_dir): + """ + root_dir: directory to the mocked dataset + """ + mocked_data = [] + dataset_dir = os.path.join( + root_dir, librispeech.FOLDER_IN_ARCHIVE, librispeech.URL + ) + os.makedirs(dataset_dir, exist_ok=True) + sample_rate = 16000 # 16kHz + seed = 0 + + for speaker_id in range(5): + speaker_path = os.path.join(dataset_dir, str(speaker_id)) + os.makedirs(speaker_path, exist_ok=True) + + for chapter_id in range(3): + chapter_path = os.path.join(speaker_path, str(chapter_id)) + os.makedirs(chapter_path, exist_ok=True) + trans_content = [] + + for utterance_id in range(10): + filename = f'{speaker_id}-{chapter_id}-{utterance_id:04d}.wav' + path = os.path.join(chapter_path, filename) + + transcript = ' '.join( + [_NUMBERS[x] for x in [speaker_id, chapter_id, utterance_id]] + ) + trans_content.append( + f'{speaker_id}-{chapter_id}-{utterance_id:04d} {transcript}' + ) + + data = get_whitenoise( + sample_rate=sample_rate, + duration=0.01, + n_channels=1, + dtype='float32', + seed=seed + ) + save_wav(path, data, sample_rate) + sample = ( + normalize_wav(data), + sample_rate, + transcript, + speaker_id, + chapter_id, + utterance_id + ) + mocked_data.append(sample) + + seed += 1 + + trans_filename = f'{speaker_id}-{chapter_id}.trans.txt' + trans_path = os.path.join(chapter_path, trans_filename) + with open(trans_path, 'w') as f: + f.write('\n'.join(trans_content)) + return mocked_data + + +class TestLibriSpeech(TempDirMixin, TorchaudioTestCase): + backend = 'default' + + root_dir = None + samples = [] + + @classmethod + def setUpClass(cls): + cls.root_dir = cls.get_base_temp_dir() + cls.samples = get_mock_dataset(cls.root_dir) + + @classmethod + def tearDownClass(cls): + # In case of test failure + librispeech.LIBRISPEECH._ext_audio = '.flac' + + def _test_librispeech(self, dataset): + num_samples = 0 + for i, ( + data, sample_rate, transcript, speaker_id, chapter_id, utterance_id + ) in enumerate(dataset): + self.assertEqual(data, self.samples[i][0], atol=5e-5, rtol=1e-8) + assert sample_rate == self.samples[i][1] + assert transcript == self.samples[i][2] + assert speaker_id == self.samples[i][3] + assert chapter_id == self.samples[i][4] + assert utterance_id == self.samples[i][5] + num_samples += 1 + + assert num_samples == len(self.samples) + librispeech.LIBRISPEECH._ext_audio = '.flac' + + def test_librispeech_str(self): + librispeech.LIBRISPEECH._ext_audio = '.wav' + dataset = librispeech.LIBRISPEECH(self.root_dir) + self._test_librispeech(dataset) + + def test_librispeech_path(self): + librispeech.LIBRISPEECH._ext_audio = '.wav' + dataset = librispeech.LIBRISPEECH(Path(self.root_dir)) + self._test_librispeech(dataset) diff --git a/test/torchaudio_unittest/datasets/libritts_test.py b/test/torchaudio_unittest/datasets/libritts_test.py new file mode 100644 index 00000000..32e6dbcf --- /dev/null +++ b/test/torchaudio_unittest/datasets/libritts_test.py @@ -0,0 +1,89 @@ +import os +from pathlib import Path + +from torchaudio_unittest.common_utils import ( + TempDirMixin, + TorchaudioTestCase, + get_whitenoise, + save_wav, + normalize_wav, +) + +from torchaudio.datasets.libritts import LIBRITTS + +_UTTERANCE_IDS = [ + [19, 198, '000000', '000000'], + [26, 495, '000004', '000000'], +] +_ORIGINAL_TEXT = 'this is the original text.' +_NORMALIZED_TEXT = 'this is the normalized text.' + + +def get_mock_dataset(root_dir): + """ + root_dir: directory to the mocked dataset + """ + mocked_data = [] + base_dir = os.path.join(root_dir, 'LibriTTS', 'train-clean-100') + for i, utterance_id in enumerate(_UTTERANCE_IDS): + filename = f'{"_".join(str(u) for u in utterance_id)}.wav' + file_dir = os.path.join(base_dir, str(utterance_id[0]), str(utterance_id[1])) + os.makedirs(file_dir, exist_ok=True) + path = os.path.join(file_dir, filename) + + data = get_whitenoise(sample_rate=24000, duration=2, n_channels=1, dtype='int16', seed=i) + save_wav(path, data, 24000) + mocked_data.append(normalize_wav(data)) + + original_text_filename = f'{"_".join(str(u) for u in utterance_id)}.original.txt' + path_original = os.path.join(file_dir, original_text_filename) + with open(path_original, 'w') as file_: + file_.write(_ORIGINAL_TEXT) + + normalized_text_filename = f'{"_".join(str(u) for u in utterance_id)}.normalized.txt' + path_normalized = os.path.join(file_dir, normalized_text_filename) + with open(path_normalized, 'w') as file_: + file_.write(_NORMALIZED_TEXT) + return mocked_data, _UTTERANCE_IDS, _ORIGINAL_TEXT, _NORMALIZED_TEXT + + +class TestLibriTTS(TempDirMixin, TorchaudioTestCase): + backend = 'default' + + root_dir = None + data = [] + _utterance_ids, _original_text, _normalized_text = [], [], [] + + @classmethod + def setUpClass(cls): + cls.root_dir = cls.get_base_temp_dir() + cls.data, cls._utterance_ids, cls._original_text, cls._normalized_text = get_mock_dataset(cls.root_dir) + + def _test_libritts(self, dataset): + n_ites = 0 + for i, (waveform, + sample_rate, + original_text, + normalized_text, + speaker_id, + chapter_id, + utterance_id) in enumerate(dataset): + expected_ids = self._utterance_ids[i] + expected_data = self.data[i] + self.assertEqual(expected_data, waveform, atol=5e-5, rtol=1e-8) + assert sample_rate == 24000 + assert speaker_id == expected_ids[0] + assert chapter_id == expected_ids[1] + assert original_text == self._original_text + assert normalized_text == self._normalized_text + assert utterance_id == f'{"_".join(str(u) for u in expected_ids[-4:])}' + n_ites += 1 + assert n_ites == len(self._utterance_ids) + + def test_libritts_str(self): + dataset = LIBRITTS(self.root_dir) + self._test_libritts(dataset) + + def test_libritts_path(self): + dataset = LIBRITTS(Path(self.root_dir)) + self._test_libritts(dataset) diff --git a/test/torchaudio_unittest/datasets/ljspeech_test.py b/test/torchaudio_unittest/datasets/ljspeech_test.py new file mode 100644 index 00000000..4cf834b2 --- /dev/null +++ b/test/torchaudio_unittest/datasets/ljspeech_test.py @@ -0,0 +1,92 @@ +import csv +import os +from pathlib import Path + +from torchaudio_unittest.common_utils import ( + TempDirMixin, + TorchaudioTestCase, + get_whitenoise, + normalize_wav, + save_wav, +) + +from torchaudio.datasets import ljspeech + +_TRANSCRIPTS = [ + "Test transcript 1", + "Test transcript 2", + "Test transcript 3", + "In 1465 Sweynheim and Pannartz began printing in the monastery of Subiaco near Rome," +] + +_NORMALIZED_TRANSCRIPT = [ + "Test transcript one", + "Test transcript two", + "Test transcript three", + "In fourteen sixty-five Sweynheim and Pannartz began printing in the monastery of Subiaco near Rome," +] + + +def get_mock_dataset(root_dir): + """ + root_dir: path to the mocked dataset + """ + mocked_data = [] + base_dir = os.path.join(root_dir, "LJSpeech-1.1") + archive_dir = os.path.join(base_dir, "wavs") + os.makedirs(archive_dir, exist_ok=True) + metadata_path = os.path.join(base_dir, "metadata.csv") + sample_rate = 22050 + + with open(metadata_path, mode="w", newline='') as metadata_file: + metadata_writer = csv.writer( + metadata_file, delimiter="|", quoting=csv.QUOTE_NONE + ) + for i, (transcript, normalized_transcript) in enumerate( + zip(_TRANSCRIPTS, _NORMALIZED_TRANSCRIPT) + ): + fileid = f'LJ001-{i:04d}' + metadata_writer.writerow([fileid, transcript, normalized_transcript]) + filename = fileid + ".wav" + path = os.path.join(archive_dir, filename) + data = get_whitenoise( + sample_rate=sample_rate, duration=1, n_channels=1, dtype="int16", seed=i + ) + save_wav(path, data, sample_rate) + mocked_data.append(normalize_wav(data)) + return mocked_data, _TRANSCRIPTS, _NORMALIZED_TRANSCRIPT + + +class TestLJSpeech(TempDirMixin, TorchaudioTestCase): + backend = "default" + + root_dir = None + data, _transcripts, _normalized_transcript = [], [], [] + + @classmethod + def setUpClass(cls): + cls.root_dir = cls.get_base_temp_dir() + cls.data, cls._transcripts, cls._normalized_transcript = get_mock_dataset(cls.root_dir) + + def _test_ljspeech(self, dataset): + n_ite = 0 + for i, (waveform, sample_rate, transcript, normalized_transcript) in enumerate( + dataset + ): + expected_transcript = self._transcripts[i] + expected_normalized_transcript = self._normalized_transcript[i] + expected_data = self.data[i] + self.assertEqual(expected_data, waveform, atol=5e-5, rtol=1e-8) + assert sample_rate == sample_rate + assert transcript == expected_transcript + assert normalized_transcript == expected_normalized_transcript + n_ite += 1 + assert n_ite == len(self.data) + + def test_ljspeech_str(self): + dataset = ljspeech.LJSPEECH(self.root_dir) + self._test_ljspeech(dataset) + + def test_ljspeech_path(self): + dataset = ljspeech.LJSPEECH(Path(self.root_dir)) + self._test_ljspeech(dataset) diff --git a/test/torchaudio_unittest/datasets/speechcommands_test.py b/test/torchaudio_unittest/datasets/speechcommands_test.py new file mode 100644 index 00000000..19a352ee --- /dev/null +++ b/test/torchaudio_unittest/datasets/speechcommands_test.py @@ -0,0 +1,161 @@ +import os +from pathlib import Path + +from torchaudio_unittest.common_utils import ( + TempDirMixin, + TorchaudioTestCase, + get_whitenoise, + normalize_wav, + save_wav, +) + +from torchaudio.datasets import speechcommands + +_LABELS = [ + "bed", + "bird", + "cat", + "dog", + "down", + "eight", + "five", + "follow", + "forward", + "four", + "go", + "happy", + "house", + "learn", + "left", + "marvin", + "nine", + "no", + "off", + "on", + "one", + "right", + "seven", + "sheila", + "six", + "stop", + "three", + "tree", + "two", + "up", + "visual", + "wow", + "yes", + "zero", +] + + +def get_mock_dataset(dataset_dir): + """ + dataset_dir: directory to the mocked dataset + """ + mocked_samples = [] + mocked_train_samples = [] + mocked_valid_samples = [] + mocked_test_samples = [] + os.makedirs(dataset_dir, exist_ok=True) + sample_rate = 16000 # 16kHz sample rate + seed = 0 + valid_file = os.path.join(dataset_dir, "validation_list.txt") + test_file = os.path.join(dataset_dir, "testing_list.txt") + with open(valid_file, "w") as valid, open(test_file, "w") as test: + for label in _LABELS: + path = os.path.join(dataset_dir, label) + os.makedirs(path, exist_ok=True) + for j in range(6): + # generate hash ID for speaker + speaker = "{:08x}".format(j) + + for utterance in range(3): + filename = f"{speaker}{speechcommands.HASH_DIVIDER}{utterance}.wav" + file_path = os.path.join(path, filename) + seed += 1 + data = get_whitenoise( + sample_rate=sample_rate, + duration=0.01, + n_channels=1, + dtype="int16", + seed=seed, + ) + save_wav(file_path, data, sample_rate) + sample = ( + normalize_wav(data), + sample_rate, + label, + speaker, + utterance, + ) + mocked_samples.append(sample) + if j < 2: + mocked_train_samples.append(sample) + elif j < 4: + valid.write(f'{label}/{filename}\n') + mocked_valid_samples.append(sample) + elif j < 6: + test.write(f'{label}/{filename}\n') + mocked_test_samples.append(sample) + return mocked_samples, mocked_train_samples, mocked_valid_samples, mocked_test_samples + + +class TestSpeechCommands(TempDirMixin, TorchaudioTestCase): + backend = "default" + + root_dir = None + samples = [] + train_samples = [] + valid_samples = [] + test_samples = [] + + @classmethod + def setUpClass(cls): + cls.root_dir = cls.get_base_temp_dir() + dataset_dir = os.path.join( + cls.root_dir, speechcommands.FOLDER_IN_ARCHIVE, speechcommands.URL + ) + cls.samples, cls.train_samples, cls.valid_samples, cls.test_samples = get_mock_dataset(dataset_dir) + + def _testSpeechCommands(self, dataset, data_samples): + num_samples = 0 + for i, (data, sample_rate, label, speaker_id, utterance_number) in enumerate( + dataset + ): + self.assertEqual(data, data_samples[i][0], atol=5e-5, rtol=1e-8) + assert sample_rate == data_samples[i][1] + assert label == data_samples[i][2] + assert speaker_id == data_samples[i][3] + assert utterance_number == data_samples[i][4] + num_samples += 1 + + assert num_samples == len(data_samples) + + def testSpeechCommands_str(self): + dataset = speechcommands.SPEECHCOMMANDS(self.root_dir) + self._testSpeechCommands(dataset, self.samples) + + def testSpeechCommands_path(self): + dataset = speechcommands.SPEECHCOMMANDS(Path(self.root_dir)) + self._testSpeechCommands(dataset, self.samples) + + def testSpeechCommandsSubsetTrain(self): + dataset = speechcommands.SPEECHCOMMANDS(self.root_dir, subset="training") + self._testSpeechCommands(dataset, self.train_samples) + + def testSpeechCommandsSubsetValid(self): + dataset = speechcommands.SPEECHCOMMANDS(self.root_dir, subset="validation") + self._testSpeechCommands(dataset, self.valid_samples) + + def testSpeechCommandsSubsetTest(self): + dataset = speechcommands.SPEECHCOMMANDS(self.root_dir, subset="testing") + self._testSpeechCommands(dataset, self.test_samples) + + def testSpeechCommandsSum(self): + dataset_all = speechcommands.SPEECHCOMMANDS(self.root_dir) + dataset_train = speechcommands.SPEECHCOMMANDS(self.root_dir, subset="training") + dataset_valid = speechcommands.SPEECHCOMMANDS(self.root_dir, subset="validation") + dataset_test = speechcommands.SPEECHCOMMANDS(self.root_dir, subset="testing") + + assert len(dataset_train) + len(dataset_valid) + len(dataset_test) == len(dataset_all) diff --git a/test/torchaudio_unittest/datasets/tedlium_test.py b/test/torchaudio_unittest/datasets/tedlium_test.py new file mode 100644 index 00000000..00c3e174 --- /dev/null +++ b/test/torchaudio_unittest/datasets/tedlium_test.py @@ -0,0 +1,150 @@ +import os +import platform +from pathlib import Path + +from torchaudio_unittest.common_utils import ( + TempDirMixin, + TorchaudioTestCase, + get_whitenoise, + save_wav, + skipIfNoSox +) + +from torchaudio.datasets import tedlium + +# Used to generate a unique utterance for each dummy audio file +_UTTERANCES = [ + "AaronHuey_2010X 1 AaronHuey_2010X 0.0 2.0 script1\n", + "AaronHuey_2010X 1 AaronHuey_2010X 2.0 4.0 script2\n", + "AaronHuey_2010X 1 AaronHuey_2010X 4.0 6.0 script3\n", + "AaronHuey_2010X 1 AaronHuey_2010X 6.0 8.0 script4\n", + "AaronHuey_2010X 1 AaronHuey_2010X 8.0 10.0 script5\n", +] + +_PHONEME = [ + "a AH", + "a(2) EY", + "aachen AA K AH N", + "aad AE D", + "aaden EY D AH N", + "aadmi AE D M IY", + "aae EY EY", +] + + +def get_mock_dataset(dataset_dir): + """ + dataset_dir: directory of the mocked dataset + """ + mocked_samples = {} + os.makedirs(dataset_dir, exist_ok=True) + sample_rate = 16000 # 16kHz + seed = 0 + + for release in ["release1", "release2", "release3"]: + data = get_whitenoise(sample_rate=sample_rate, duration=10.00, n_channels=1, dtype="float32", seed=seed) + if release in ["release1", "release2"]: + release_dir = os.path.join( + dataset_dir, + tedlium._RELEASE_CONFIGS[release]["folder_in_archive"], + tedlium._RELEASE_CONFIGS[release]["subset"], + ) + else: + release_dir = os.path.join( + dataset_dir, + tedlium._RELEASE_CONFIGS[release]["folder_in_archive"], + tedlium._RELEASE_CONFIGS[release]["data_path"], + ) + os.makedirs(release_dir, exist_ok=True) + os.makedirs(os.path.join(release_dir, "stm"), exist_ok=True) # Subfolder for transcripts + os.makedirs(os.path.join(release_dir, "sph"), exist_ok=True) # Subfolder for audio files + filename = f"{release}.sph" + path = os.path.join(os.path.join(release_dir, "sph"), filename) + save_wav(path, data, sample_rate) + + trans_filename = f"{release}.stm" + trans_path = os.path.join(os.path.join(release_dir, "stm"), trans_filename) + with open(trans_path, "w") as f: + f.write("".join(_UTTERANCES)) + + dict_filename = f"{release}.dic" + dict_path = os.path.join(release_dir, dict_filename) + with open(dict_path, "w") as f: + f.write("\n".join(_PHONEME)) + + # Create a samples list to compare with + mocked_samples[release] = [] + for utterance in _UTTERANCES: + talk_id, _, speaker_id, start_time, end_time, identifier, transcript = utterance.split(" ", 6) + start_time = int(float(start_time)) * sample_rate + end_time = int(float(end_time)) * sample_rate + sample = ( + data[:, start_time:end_time], + sample_rate, + transcript, + talk_id, + speaker_id, + identifier, + ) + mocked_samples[release].append(sample) + seed += 1 + return mocked_samples + + +class Tedlium(TempDirMixin): + root_dir = None + samples = {} + + @classmethod + def setUpClass(cls): + cls.root_dir = cls.get_base_temp_dir() + cls.root_dir = dataset_dir = os.path.join(cls.root_dir, "tedlium") + cls.samples = get_mock_dataset(dataset_dir) + + def _test_tedlium(self, dataset, release): + num_samples = 0 + for i, (data, sample_rate, transcript, talk_id, speaker_id, identifier) in enumerate(dataset): + self.assertEqual(data, self.samples[release][i][0], atol=5e-5, rtol=1e-8) + assert sample_rate == self.samples[release][i][1] + assert transcript == self.samples[release][i][2] + assert talk_id == self.samples[release][i][3] + assert speaker_id == self.samples[release][i][4] + assert identifier == self.samples[release][i][5] + num_samples += 1 + + assert num_samples == len(self.samples[release]) + + dataset._dict_path = os.path.join(dataset._path, f"{release}.dic") + phoneme_dict = dataset.phoneme_dict + phoenemes = [f"{key} {' '.join(value)}" for key, value in phoneme_dict.items()] + assert phoenemes == _PHONEME + + def test_tedlium_release1_str(self): + release = "release1" + dataset = tedlium.TEDLIUM(self.root_dir, release=release) + self._test_tedlium(dataset, release) + + def test_tedlium_release1_path(self): + release = "release1" + dataset = tedlium.TEDLIUM(Path(self.root_dir), release=release) + self._test_tedlium(dataset, release) + + def test_tedlium_release2(self): + release = "release2" + dataset = tedlium.TEDLIUM(self.root_dir, release=release) + self._test_tedlium(dataset, release) + + def test_tedlium_release3(self): + release = "release3" + dataset = tedlium.TEDLIUM(self.root_dir, release=release) + self._test_tedlium(dataset, release) + + +class TestTedliumSoundfile(Tedlium, TorchaudioTestCase): + backend = "soundfile" + + +if platform.system() != "Windows": + @skipIfNoSox + class TestTedliumSoxIO(Tedlium, TorchaudioTestCase): + backend = "sox_io" diff --git a/test/torchaudio_unittest/datasets/utils_test.py b/test/torchaudio_unittest/datasets/utils_test.py new file mode 100644 index 00000000..89069914 --- /dev/null +++ b/test/torchaudio_unittest/datasets/utils_test.py @@ -0,0 +1,37 @@ +import torch +from torchaudio_unittest.common_utils import ( + TorchaudioTestCase, + TempDirMixin +) + +from torchaudio.datasets import utils as dataset_utils + + +class Dataset(torch.utils.data.Dataset): + def __getitem__(self, n): + sample_rate = 8000 + waveform = n * torch.ones(2, 256) + return waveform, sample_rate + + def __len__(self) -> int: + return 2 + + def __iter__(self): + for i in range(len(self)): + yield self[i] + + +class TestIterator(TorchaudioTestCase, TempDirMixin): + backend = 'default' + + def test_disckcache_iterator(self): + data = dataset_utils.diskcache_iterator(Dataset(), self.get_base_temp_dir()) + # Save + data[0] + # Load + data[0] + + def test_bg_iterator(self): + data = dataset_utils.bg_iterator(Dataset(), 5) + for _ in data: + pass diff --git a/test/torchaudio_unittest/datasets/vctk_test.py b/test/torchaudio_unittest/datasets/vctk_test.py new file mode 100644 index 00000000..4171c3c3 --- /dev/null +++ b/test/torchaudio_unittest/datasets/vctk_test.py @@ -0,0 +1,107 @@ +import os +from pathlib import Path + +from torchaudio.datasets import vctk + +from torchaudio_unittest.common_utils import ( + TempDirMixin, + TorchaudioTestCase, + get_whitenoise, + save_wav, + normalize_wav, +) + +# Used to generate a unique transcript for each dummy audio file +_TRANSCRIPT = [ + 'Please call Stella', + 'Ask her to bring these things', + 'with her from the store', + 'Six spoons of fresh snow peas, five thick slabs of blue cheese, and maybe a snack for her brother Bob', + 'We also need a small plastic snake and a big toy frog for the kids', + 'She can scoop these things into three red bags, and we will go meet her Wednesday at the train station', + 'When the sunlight strikes raindrops in the air, they act as a prism and form a rainbow', + 'The rainbow is a division of white light into many beautiful colors', + 'These take the shape of a long round arch, with its path high above, and its two ends \ + apparently beyond the horizon', + 'There is, according to legend, a boiling pot of gold at one end' +] + + +def get_mock_dataset(root_dir): + """ + root_dir: root directory of the mocked data + """ + mocked_samples = [] + dataset_dir = os.path.join(root_dir, 'VCTK-Corpus-0.92') + os.makedirs(dataset_dir, exist_ok=True) + sample_rate = 48000 + seed = 0 + + for speaker in range(225, 230): + speaker_id = 'p' + str(speaker) + audio_dir = os.path.join(dataset_dir, 'wav48_silence_trimmed', speaker_id) + os.makedirs(audio_dir, exist_ok=True) + + file_dir = os.path.join(dataset_dir, 'txt', speaker_id) + os.makedirs(file_dir, exist_ok=True) + + for utterance_id in range(1, 11): + filename = f'{speaker_id}_{utterance_id:03d}_mic2' + audio_file_path = os.path.join(audio_dir, filename + '.wav') + + data = get_whitenoise( + sample_rate=sample_rate, + duration=0.01, + n_channels=1, + dtype='float32', + seed=seed + ) + save_wav(audio_file_path, data, sample_rate) + + txt_file_path = os.path.join(file_dir, filename[:-5] + '.txt') + transcript = _TRANSCRIPT[utterance_id - 1] + with open(txt_file_path, 'w') as f: + f.write(transcript) + + sample = ( + normalize_wav(data), + sample_rate, + transcript, + speaker_id, + utterance_id + ) + mocked_samples.append(sample) + seed += 1 + return mocked_samples + + +class TestVCTK(TempDirMixin, TorchaudioTestCase): + backend = 'default' + + root_dir = None + samples = [] + + @classmethod + def setUpClass(cls): + cls.root_dir = cls.get_base_temp_dir() + cls.samples = get_mock_dataset(cls.root_dir) + + def _test_vctk(self, dataset): + num_samples = 0 + for i, (data, sample_rate, transcript, speaker_id, utterance_id) in enumerate(dataset): + self.assertEqual(data, self.samples[i][0], atol=5e-5, rtol=1e-8) + assert sample_rate == self.samples[i][1] + assert transcript == self.samples[i][2] + assert speaker_id == self.samples[i][3] + assert int(utterance_id) == self.samples[i][4] + num_samples += 1 + + assert num_samples == len(self.samples) + + def test_vctk_str(self): + dataset = vctk.VCTK_092(self.root_dir, audio_ext=".wav") + self._test_vctk(dataset) + + def test_vctk_path(self): + dataset = vctk.VCTK_092(Path(self.root_dir), audio_ext=".wav") + self._test_vctk(dataset) diff --git a/test/torchaudio_unittest/datasets/yesno_test.py b/test/torchaudio_unittest/datasets/yesno_test.py new file mode 100644 index 00000000..2f414498 --- /dev/null +++ b/test/torchaudio_unittest/datasets/yesno_test.py @@ -0,0 +1,67 @@ +import os +from pathlib import Path + +from torchaudio.datasets import yesno + +from torchaudio_unittest.common_utils import ( + TempDirMixin, + TorchaudioTestCase, + get_whitenoise, + save_wav, + normalize_wav, +) + + +def get_mock_data(root_dir, labels): + """ + root_dir: path + labels: list of labels + """ + mocked_data = [] + base_dir = os.path.join(root_dir, 'waves_yesno') + os.makedirs(base_dir, exist_ok=True) + for i, label in enumerate(labels): + filename = f'{"_".join(str(l) for l in label)}.wav' + path = os.path.join(base_dir, filename) + data = get_whitenoise(sample_rate=8000, duration=6, n_channels=1, dtype='int16', seed=i) + save_wav(path, data, 8000) + mocked_data.append(normalize_wav(data)) + return mocked_data + + +class TestYesNo(TempDirMixin, TorchaudioTestCase): + backend = 'default' + + root_dir = None + data = [] + labels = [ + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 1, 1, 1], + [0, 1, 0, 1, 0, 1, 1, 0], + [1, 1, 1, 1, 0, 0, 0, 0], + [1, 1, 1, 1, 1, 1, 1, 1], + ] + + @classmethod + def setUpClass(cls): + cls.root_dir = cls.get_base_temp_dir() + cls.data = get_mock_data(cls.root_dir, cls.labels) + + def _test_yesno(self, dataset): + n_ite = 0 + for i, (waveform, sample_rate, label) in enumerate(dataset): + expected_label = self.labels[i] + expected_data = self.data[i] + self.assertEqual(expected_data, waveform, atol=5e-5, rtol=1e-8) + assert sample_rate == 8000 + assert label == expected_label + n_ite += 1 + assert n_ite == len(self.data) + + def test_yesno_str(self): + dataset = yesno.YESNO(self.root_dir) + self._test_yesno(dataset) + + def test_yesno_path(self): + dataset = yesno.YESNO(Path(self.root_dir)) + self._test_yesno(dataset) diff --git a/test/torchaudio_unittest/example/__init__.py b/test/torchaudio_unittest/example/__init__.py new file mode 100644 index 00000000..9e4fa819 --- /dev/null +++ b/test/torchaudio_unittest/example/__init__.py @@ -0,0 +1,8 @@ +import os +import sys + + +sys.path.append( + os.path.join( + os.path.dirname(__file__), + '..', '..', '..', 'examples')) diff --git a/test/torchaudio_unittest/example/souce_sepration/__init__.py b/test/torchaudio_unittest/example/souce_sepration/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/torchaudio_unittest/example/souce_sepration/metrics_test.py b/test/torchaudio_unittest/example/souce_sepration/metrics_test.py new file mode 100644 index 00000000..b7793b07 --- /dev/null +++ b/test/torchaudio_unittest/example/souce_sepration/metrics_test.py @@ -0,0 +1,39 @@ +from itertools import product + +import torch +from torch.testing._internal.common_utils import TestCase +from parameterized import parameterized + +from . import sdr_reference +from source_separation.utils import metrics + + +class TestSDR(TestCase): + @parameterized.expand([(1, ), (2, ), (32, )]) + def test_sdr(self, batch_size): + """sdr produces the same result as the reference implementation""" + num_frames = 256 + + estimation = torch.rand(batch_size, num_frames) + origin = torch.rand(batch_size, num_frames) + + sdr_ref = sdr_reference.calc_sdr_torch(estimation, origin) + sdr = metrics.sdr(estimation.unsqueeze(1), origin.unsqueeze(1)).squeeze(1) + + self.assertEqual(sdr, sdr_ref) + + @parameterized.expand(list(product([1, 2, 32], [2, 3, 4, 5]))) + def test_sdr_pit(self, batch_size, num_sources): + """sdr_pit produces the same result as the reference implementation""" + num_frames = 256 + + estimation = torch.randn(batch_size, num_sources, num_frames) + origin = torch.randn(batch_size, num_sources, num_frames) + + estimation -= estimation.mean(axis=2, keepdim=True) + origin -= origin.mean(axis=2, keepdim=True) + + batch_sdr_ref = sdr_reference.batch_SDR_torch(estimation, origin) + batch_sdr = metrics.sdr_pit(estimation, origin) + + self.assertEqual(batch_sdr, batch_sdr_ref) diff --git a/test/torchaudio_unittest/example/souce_sepration/sdr_reference.py b/test/torchaudio_unittest/example/souce_sepration/sdr_reference.py new file mode 100644 index 00000000..7652fab0 --- /dev/null +++ b/test/torchaudio_unittest/example/souce_sepration/sdr_reference.py @@ -0,0 +1,98 @@ +"""Reference Implementation of SDR and PIT SDR. + +This module was taken from the following implementation + +https://github.com/naplab/Conv-TasNet/blob/e66d82a8f956a69749ec8a4ae382217faa097c5c/utility/sdr.py + +which was made available by Yi Luo under the following liscence, + +Creative Commons Attribution-NonCommercial-ShareAlike 3.0 United States License. + +The module was modified in the following manner; + - Remove the functions other than `calc_sdr_torch` and `batch_SDR_torch`, + - Remove the import statements required only for the removed functions. + - Add `# flake8: noqa` so as not to report any format issue on this module. + +The implementation of the retained functions and their formats are kept as-is. +""" + +# flake8: noqa + +import numpy as np +from itertools import permutations + +import torch + + +def calc_sdr_torch(estimation, origin, mask=None): + """ + batch-wise SDR caculation for one audio file on pytorch Variables. + estimation: (batch, nsample) + origin: (batch, nsample) + mask: optional, (batch, nsample), binary + """ + + if mask is not None: + origin = origin * mask + estimation = estimation * mask + + origin_power = torch.pow(origin, 2).sum(1, keepdim=True) + 1e-8 # (batch, 1) + + scale = torch.sum(origin*estimation, 1, keepdim=True) / origin_power # (batch, 1) + + est_true = scale * origin # (batch, nsample) + est_res = estimation - est_true # (batch, nsample) + + true_power = torch.pow(est_true, 2).sum(1) + res_power = torch.pow(est_res, 2).sum(1) + + return 10*torch.log10(true_power) - 10*torch.log10(res_power) # (batch, 1) + + +def batch_SDR_torch(estimation, origin, mask=None): + """ + batch-wise SDR caculation for multiple audio files. + estimation: (batch, nsource, nsample) + origin: (batch, nsource, nsample) + mask: optional, (batch, nsample), binary + """ + + batch_size_est, nsource_est, nsample_est = estimation.size() + batch_size_ori, nsource_ori, nsample_ori = origin.size() + + assert batch_size_est == batch_size_ori, "Estimation and original sources should have same shape." + assert nsource_est == nsource_ori, "Estimation and original sources should have same shape." + assert nsample_est == nsample_ori, "Estimation and original sources should have same shape." + + assert nsource_est < nsample_est, "Axis 1 should be the number of sources, and axis 2 should be the signal." + + batch_size = batch_size_est + nsource = nsource_est + nsample = nsample_est + + # zero mean signals + estimation = estimation - torch.mean(estimation, 2, keepdim=True).expand_as(estimation) + origin = origin - torch.mean(origin, 2, keepdim=True).expand_as(estimation) + + # possible permutations + perm = list(set(permutations(np.arange(nsource)))) + + # pair-wise SDR + SDR = torch.zeros((batch_size, nsource, nsource)).type(estimation.type()) + for i in range(nsource): + for j in range(nsource): + SDR[:,i,j] = calc_sdr_torch(estimation[:,i], origin[:,j], mask) + + # choose the best permutation + SDR_max = [] + SDR_perm = [] + for permute in perm: + sdr = [] + for idx in range(len(permute)): + sdr.append(SDR[:,idx,permute[idx]].view(batch_size,-1)) + sdr = torch.sum(torch.cat(sdr, 1), 1) + SDR_perm.append(sdr.view(batch_size, 1)) + SDR_perm = torch.cat(SDR_perm, 1) + SDR_max, _ = torch.max(SDR_perm, dim=1) + + return SDR_max / nsource diff --git a/test/torchaudio_unittest/example/souce_sepration/wsj0mix_test.py b/test/torchaudio_unittest/example/souce_sepration/wsj0mix_test.py new file mode 100644 index 00000000..46927b18 --- /dev/null +++ b/test/torchaudio_unittest/example/souce_sepration/wsj0mix_test.py @@ -0,0 +1,111 @@ +import os + +from torchaudio_unittest.common_utils import ( + TempDirMixin, + TorchaudioTestCase, + get_whitenoise, + save_wav, + normalize_wav, +) + +from source_separation.utils.dataset import wsj0mix + + +_FILENAMES = [ + "012c0207_1.9952_01cc0202_-1.9952.wav", + "01co0302_1.63_014c020q_-1.63.wav", + "01do0316_0.24011_205a0104_-0.24011.wav", + "01lc020x_1.1301_027o030r_-1.1301.wav", + "01mc0202_0.34056_205o0106_-0.34056.wav", + "01nc020t_0.53821_018o030w_-0.53821.wav", + "01po030f_2.2136_40ko031a_-2.2136.wav", + "01ra010o_2.4098_403a010f_-2.4098.wav", + "01xo030b_0.22377_016o031a_-0.22377.wav", + "02ac020x_0.68566_01ec020b_-0.68566.wav", + "20co010m_0.82801_019c0212_-0.82801.wav", + "20da010u_1.2483_017c0211_-1.2483.wav", + "20oo010d_1.0631_01ic020s_-1.0631.wav", + "20sc0107_2.0222_20fo010h_-2.0222.wav", + "20tc010f_0.051456_404a0110_-0.051456.wav", + "407c0214_1.1712_02ca0113_-1.1712.wav", + "40ao030w_2.4697_20vc010a_-2.4697.wav", + "40pa0101_1.1087_40ea0107_-1.1087.wav", +] + + +def _mock_dataset(root_dir, num_speaker): + dirnames = ["mix"] + [f"s{i+1}" for i in range(num_speaker)] + for dirname in dirnames: + os.makedirs(os.path.join(root_dir, dirname), exist_ok=True) + + seed = 0 + sample_rate = 8000 + expected = [] + for filename in _FILENAMES: + mix = None + src = [] + for dirname in dirnames: + waveform = get_whitenoise( + sample_rate=8000, duration=1, n_channels=1, dtype="int16", seed=seed + ) + seed += 1 + + path = os.path.join(root_dir, dirname, filename) + save_wav(path, waveform, sample_rate) + waveform = normalize_wav(waveform) + + if dirname == "mix": + mix = waveform + else: + src.append(waveform) + expected.append((sample_rate, mix, src)) + return expected + + +class TestWSJ0Mix2(TempDirMixin, TorchaudioTestCase): + backend = "default" + root_dir = None + expected = None + + @classmethod + def setUpClass(cls): + cls.root_dir = cls.get_base_temp_dir() + cls.expected = _mock_dataset(cls.root_dir, 2) + + def test_wsj0mix(self): + dataset = wsj0mix.WSJ0Mix(self.root_dir, num_speakers=2, sample_rate=8000) + + n_ite = 0 + for i, sample in enumerate(dataset): + (_, sample_mix, sample_src) = sample + (_, expected_mix, expected_src) = self.expected[i] + self.assertEqual(sample_mix, expected_mix, atol=5e-5, rtol=1e-8) + self.assertEqual(sample_src[0], expected_src[0], atol=5e-5, rtol=1e-8) + self.assertEqual(sample_src[1], expected_src[1], atol=5e-5, rtol=1e-8) + n_ite += 1 + assert n_ite == len(self.expected) + + +class TestWSJ0Mix3(TempDirMixin, TorchaudioTestCase): + backend = "default" + root_dir = None + expected = None + + @classmethod + def setUpClass(cls): + cls.root_dir = cls.get_base_temp_dir() + cls.expected = _mock_dataset(cls.root_dir, 3) + + def test_wsj0mix(self): + dataset = wsj0mix.WSJ0Mix(self.root_dir, num_speakers=3, sample_rate=8000) + + n_ite = 0 + for i, sample in enumerate(dataset): + (_, sample_mix, sample_src) = sample + (_, expected_mix, expected_src) = self.expected[i] + self.assertEqual(sample_mix, expected_mix, atol=5e-5, rtol=1e-8) + self.assertEqual(sample_src[0], expected_src[0], atol=5e-5, rtol=1e-8) + self.assertEqual(sample_src[1], expected_src[1], atol=5e-5, rtol=1e-8) + self.assertEqual(sample_src[2], expected_src[2], atol=5e-5, rtol=1e-8) + n_ite += 1 + assert n_ite == len(self.expected) diff --git a/test/torchaudio_unittest/example/tacotron2/__init__.py b/test/torchaudio_unittest/example/tacotron2/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/torchaudio_unittest/example/tacotron2/tacotron2_loss_cpu_test.py b/test/torchaudio_unittest/example/tacotron2/tacotron2_loss_cpu_test.py new file mode 100644 index 00000000..f4d2e389 --- /dev/null +++ b/test/torchaudio_unittest/example/tacotron2/tacotron2_loss_cpu_test.py @@ -0,0 +1,23 @@ +import torch + +from .tacotron2_loss_impl import ( + Tacotron2LossShapeTests, + Tacotron2LossTorchscriptTests, + Tacotron2LossGradcheckTests, +) +from torchaudio_unittest.common_utils import PytorchTestCase + + +class TestTacotron2LossShapeFloat32CPU(Tacotron2LossShapeTests, PytorchTestCase): + dtype = torch.float32 + device = torch.device("cpu") + + +class TestTacotron2TorchsciptFloat32CPU(Tacotron2LossTorchscriptTests, PytorchTestCase): + dtype = torch.float32 + device = torch.device("cpu") + + +class TestTacotron2GradcheckFloat64CPU(Tacotron2LossGradcheckTests, PytorchTestCase): + dtype = torch.float64 # gradcheck needs a higher numerical accuracy + device = torch.device("cpu") diff --git a/test/torchaudio_unittest/example/tacotron2/tacotron2_loss_gpu_test.py b/test/torchaudio_unittest/example/tacotron2/tacotron2_loss_gpu_test.py new file mode 100644 index 00000000..9c1ae252 --- /dev/null +++ b/test/torchaudio_unittest/example/tacotron2/tacotron2_loss_gpu_test.py @@ -0,0 +1,26 @@ +import torch + +from .tacotron2_loss_impl import ( + Tacotron2LossShapeTests, + Tacotron2LossTorchscriptTests, + Tacotron2LossGradcheckTests, +) +from torchaudio_unittest.common_utils import skipIfNoCuda, PytorchTestCase + + +@skipIfNoCuda +class TestTacotron2LossShapeFloat32CUDA(PytorchTestCase, Tacotron2LossShapeTests): + dtype = torch.float32 + device = torch.device("cuda") + + +@skipIfNoCuda +class TestTacotron2TorchsciptFloat32CUDA(PytorchTestCase, Tacotron2LossTorchscriptTests): + dtype = torch.float32 + device = torch.device("cuda") + + +@skipIfNoCuda +class TestTacotron2GradcheckFloat64CUDA(PytorchTestCase, Tacotron2LossGradcheckTests): + dtype = torch.float64 # gradcheck needs a higher numerical accuracy + device = torch.device("cuda") diff --git a/test/torchaudio_unittest/example/tacotron2/tacotron2_loss_impl.py b/test/torchaudio_unittest/example/tacotron2/tacotron2_loss_impl.py new file mode 100644 index 00000000..848126e6 --- /dev/null +++ b/test/torchaudio_unittest/example/tacotron2/tacotron2_loss_impl.py @@ -0,0 +1,111 @@ +import torch +from torch.autograd import gradcheck, gradgradcheck + +from pipeline_tacotron2.loss import Tacotron2Loss +from torchaudio_unittest.common_utils import ( + TestBaseMixin, + torch_script, +) + + +class Tacotron2LossInputMixin(TestBaseMixin): + + def _get_inputs(self, n_mel=80, n_batch=16, max_mel_specgram_length=300): + mel_specgram = torch.rand( + n_batch, n_mel, max_mel_specgram_length, dtype=self.dtype, device=self.device + ) + mel_specgram_postnet = torch.rand( + n_batch, n_mel, max_mel_specgram_length, dtype=self.dtype, device=self.device + ) + gate_out = torch.rand(n_batch, dtype=self.dtype, device=self.device) + truth_mel_specgram = torch.rand( + n_batch, n_mel, max_mel_specgram_length, dtype=self.dtype, device=self.device + ) + truth_gate_out = torch.rand(n_batch, dtype=self.dtype, device=self.device) + + truth_mel_specgram.requires_grad = False + truth_gate_out.requires_grad = False + + return ( + mel_specgram, + mel_specgram_postnet, + gate_out, + truth_mel_specgram, + truth_gate_out, + ) + + +class Tacotron2LossShapeTests(Tacotron2LossInputMixin): + + def test_tacotron2_loss_shape(self): + """Validate the output shape of Tacotron2Loss.""" + n_batch = 16 + + ( + mel_specgram, + mel_specgram_postnet, + gate_out, + truth_mel_specgram, + truth_gate_out, + ) = self._get_inputs(n_batch=n_batch) + + mel_loss, mel_postnet_loss, gate_loss = Tacotron2Loss()( + (mel_specgram, mel_specgram_postnet, gate_out), + (truth_mel_specgram, truth_gate_out) + ) + + self.assertEqual(mel_loss.size(), torch.Size([])) + self.assertEqual(mel_postnet_loss.size(), torch.Size([])) + self.assertEqual(gate_loss.size(), torch.Size([])) + + +class Tacotron2LossTorchscriptTests(Tacotron2LossInputMixin): + + def _assert_torchscript_consistency(self, fn, tensors): + ts_func = torch_script(fn) + + output = fn(tensors[:3], tensors[3:]) + ts_output = ts_func(tensors[:3], tensors[3:]) + + self.assertEqual(ts_output, output) + + def test_tacotron2_loss_torchscript_consistency(self): + """Validate the torchscript consistency of Tacotron2Loss.""" + + loss_fn = Tacotron2Loss() + self._assert_torchscript_consistency(loss_fn, self._get_inputs()) + + +class Tacotron2LossGradcheckTests(Tacotron2LossInputMixin): + + def test_tacotron2_loss_gradcheck(self): + """Performing gradient check on Tacotron2Loss.""" + ( + mel_specgram, + mel_specgram_postnet, + gate_out, + truth_mel_specgram, + truth_gate_out, + ) = self._get_inputs() + + mel_specgram.requires_grad_(True) + mel_specgram_postnet.requires_grad_(True) + gate_out.requires_grad_(True) + + def _fn(mel_specgram, mel_specgram_postnet, gate_out, truth_mel_specgram, truth_gate_out): + loss_fn = Tacotron2Loss() + return loss_fn( + (mel_specgram, mel_specgram_postnet, gate_out), + (truth_mel_specgram, truth_gate_out), + ) + + gradcheck( + _fn, + (mel_specgram, mel_specgram_postnet, gate_out, truth_mel_specgram, truth_gate_out), + fast_mode=True, + ) + gradgradcheck( + _fn, + (mel_specgram, mel_specgram_postnet, gate_out, truth_mel_specgram, truth_gate_out), + fast_mode=True, + ) diff --git a/test/torchaudio_unittest/example/tacotron2/test_text_preprocessing.py b/test/torchaudio_unittest/example/tacotron2/test_text_preprocessing.py new file mode 100644 index 00000000..8da02de8 --- /dev/null +++ b/test/torchaudio_unittest/example/tacotron2/test_text_preprocessing.py @@ -0,0 +1,97 @@ +from parameterized import parameterized + +from torchaudio._internal.module_utils import is_module_available +from torchaudio_unittest.common_utils import TorchaudioTestCase, skipIfNoModule + +if is_module_available("unidecode") and is_module_available("inflect"): + from pipeline_tacotron2.text.text_preprocessing import text_to_sequence + from pipeline_tacotron2.text.numbers import ( + _remove_commas, + _expand_pounds, + _expand_dollars, + _expand_decimal_point, + _expand_ordinal, + _expand_number, + ) + + +@skipIfNoModule("unidecode") +@skipIfNoModule("inflect") +class TestTextPreprocessor(TorchaudioTestCase): + + @parameterized.expand( + [ + ["dr. Strange?", [15, 26, 14, 31, 26, 29, 11, 30, 31, 29, 12, 25, 18, 16, 10]], + ["ML, is fun.", [24, 23, 6, 11, 20, 30, 11, 17, 32, 25, 7]], + ["I love torchaudio!", [20, 11, 23, 26, 33, 16, 11, 31, 26, 29, 14, 19, 12, 32, 15, 20, 26, 2]], + # 'one thousand dollars, twenty cents' + ["$1,000.20", [26, 25, 16, 11, 31, 19, 26, 32, 30, 12, 25, 15, 11, 15, 26, 23, 23, + 12, 29, 30, 6, 11, 31, 34, 16, 25, 31, 36, 11, 14, 16, 25, 31, 30]], + ] + ) + def test_text_to_sequence(self, sent, seq): + + assert (text_to_sequence(sent) == seq) + + @parameterized.expand( + [ + ["He, she, and I have $1,000", "He, she, and I have $1000"], + ] + ) + def test_remove_commas(self, sent, truth): + + assert (_remove_commas(sent) == truth) + + @parameterized.expand( + [ + ["He, she, and I have £1000", "He, she, and I have 1000 pounds"], + ] + ) + def test_expand_pounds(self, sent, truth): + + assert (_expand_pounds(sent) == truth) + + @parameterized.expand( + [ + ["He, she, and I have $1000", "He, she, and I have 1000 dollars"], + ["He, she, and I have $3000.01", "He, she, and I have 3000 dollars, 1 cent"], + ["He has $500.20 and she has $1000.50.", + "He has 500 dollars, 20 cents and she has 1000 dollars, 50 cents."], + ] + ) + def test_expand_dollars(self, sent, truth): + + assert (_expand_dollars(sent) == truth) + + @parameterized.expand( + [ + ["1000.20", "1000 point 20"], + ["1000.1", "1000 point 1"], + ] + ) + def test_expand_decimal_point(self, sent, truth): + + assert (_expand_decimal_point(sent) == truth) + + @parameterized.expand( + [ + ["21st centry", "twenty-first centry"], + ["20th centry", "twentieth centry"], + ["2nd place.", "second place."], + ] + ) + def test_expand_ordinal(self, sent, truth): + + assert (_expand_ordinal(sent) == truth) + _expand_ordinal, + + @parameterized.expand( + [ + ["100020 dollars.", "one hundred thousand twenty dollars."], + ["1234567890!", "one billion, two hundred thirty-four million, " + "five hundred sixty-seven thousand, eight hundred ninety!"], + ] + ) + def test_expand_number(self, sent, truth): + + assert (_expand_number(sent) == truth) diff --git a/test/torchaudio_unittest/functional/__init__.py b/test/torchaudio_unittest/functional/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/torchaudio_unittest/functional/autograd_cpu_test.py b/test/torchaudio_unittest/functional/autograd_cpu_test.py new file mode 100644 index 00000000..a34823a7 --- /dev/null +++ b/test/torchaudio_unittest/functional/autograd_cpu_test.py @@ -0,0 +1,13 @@ +import torch +from .autograd_impl import Autograd, AutogradFloat32 +from torchaudio_unittest import common_utils + + +class TestAutogradLfilterCPU(Autograd, common_utils.PytorchTestCase): + dtype = torch.float64 + device = torch.device('cpu') + + +class TestAutogradRNNTCPU(AutogradFloat32, common_utils.PytorchTestCase): + dtype = torch.float32 + device = torch.device('cpu') diff --git a/test/torchaudio_unittest/functional/autograd_cuda_test.py b/test/torchaudio_unittest/functional/autograd_cuda_test.py new file mode 100644 index 00000000..341c575d --- /dev/null +++ b/test/torchaudio_unittest/functional/autograd_cuda_test.py @@ -0,0 +1,15 @@ +import torch +from .autograd_impl import Autograd, AutogradFloat32 +from torchaudio_unittest import common_utils + + +@common_utils.skipIfNoCuda +class TestAutogradLfilterCUDA(Autograd, common_utils.PytorchTestCase): + dtype = torch.float64 + device = torch.device('cuda') + + +@common_utils.skipIfNoCuda +class TestAutogradRNNTCUDA(AutogradFloat32, common_utils.PytorchTestCase): + dtype = torch.float32 + device = torch.device('cuda') diff --git a/test/torchaudio_unittest/functional/autograd_impl.py b/test/torchaudio_unittest/functional/autograd_impl.py new file mode 100644 index 00000000..ecd27722 --- /dev/null +++ b/test/torchaudio_unittest/functional/autograd_impl.py @@ -0,0 +1,269 @@ +from typing import Callable, Tuple +from functools import partial +import torch +from parameterized import parameterized +from torch import Tensor +import torchaudio.functional as F +from torch.autograd import gradcheck, gradgradcheck +from torchaudio_unittest.common_utils import ( + TestBaseMixin, + get_whitenoise, + rnnt_utils, +) + + +class Autograd(TestBaseMixin): + def assert_grad( + self, + transform: Callable[..., Tensor], + inputs: Tuple[torch.Tensor], + *, + enable_all_grad: bool = True, + ): + inputs_ = [] + for i in inputs: + if torch.is_tensor(i): + i = i.to(dtype=self.dtype, device=self.device) + if enable_all_grad: + i.requires_grad = True + inputs_.append(i) + assert gradcheck(transform, inputs_) + assert gradgradcheck(transform, inputs_) + + def test_lfilter_x(self): + torch.random.manual_seed(2434) + x = get_whitenoise(sample_rate=22050, duration=0.01, n_channels=2) + a = torch.tensor([0.7, 0.2, 0.6]) + b = torch.tensor([0.4, 0.2, 0.9]) + x.requires_grad = True + self.assert_grad(F.lfilter, (x, a, b), enable_all_grad=False) + + def test_lfilter_a(self): + torch.random.manual_seed(2434) + x = get_whitenoise(sample_rate=22050, duration=0.01, n_channels=2) + a = torch.tensor([0.7, 0.2, 0.6]) + b = torch.tensor([0.4, 0.2, 0.9]) + a.requires_grad = True + self.assert_grad(F.lfilter, (x, a, b), enable_all_grad=False) + + def test_lfilter_b(self): + torch.random.manual_seed(2434) + x = get_whitenoise(sample_rate=22050, duration=0.01, n_channels=2) + a = torch.tensor([0.7, 0.2, 0.6]) + b = torch.tensor([0.4, 0.2, 0.9]) + b.requires_grad = True + self.assert_grad(F.lfilter, (x, a, b), enable_all_grad=False) + + def test_lfilter_all_inputs(self): + torch.random.manual_seed(2434) + x = get_whitenoise(sample_rate=22050, duration=0.01, n_channels=2) + a = torch.tensor([0.7, 0.2, 0.6]) + b = torch.tensor([0.4, 0.2, 0.9]) + self.assert_grad(F.lfilter, (x, a, b)) + + def test_lfilter_filterbanks(self): + torch.random.manual_seed(2434) + x = get_whitenoise(sample_rate=22050, duration=0.01, n_channels=3) + a = torch.tensor([[0.7, 0.2, 0.6], + [0.8, 0.2, 0.9]]) + b = torch.tensor([[0.4, 0.2, 0.9], + [0.7, 0.2, 0.6]]) + self.assert_grad(partial(F.lfilter, batching=False), (x, a, b)) + + def test_lfilter_batching(self): + torch.random.manual_seed(2434) + x = get_whitenoise(sample_rate=22050, duration=0.01, n_channels=2) + a = torch.tensor([[0.7, 0.2, 0.6], + [0.8, 0.2, 0.9]]) + b = torch.tensor([[0.4, 0.2, 0.9], + [0.7, 0.2, 0.6]]) + self.assert_grad(F.lfilter, (x, a, b)) + + def test_filtfilt_a(self): + torch.random.manual_seed(2434) + x = get_whitenoise(sample_rate=22050, duration=0.01, n_channels=2) + a = torch.tensor([0.7, 0.2, 0.6]) + b = torch.tensor([0.4, 0.2, 0.9]) + a.requires_grad = True + self.assert_grad(F.filtfilt, (x, a, b), enable_all_grad=False) + + def test_filtfilt_b(self): + torch.random.manual_seed(2434) + x = get_whitenoise(sample_rate=22050, duration=0.01, n_channels=2) + a = torch.tensor([0.7, 0.2, 0.6]) + b = torch.tensor([0.4, 0.2, 0.9]) + b.requires_grad = True + self.assert_grad(F.filtfilt, (x, a, b), enable_all_grad=False) + + def test_filtfilt_all_inputs(self): + torch.random.manual_seed(2434) + x = get_whitenoise(sample_rate=22050, duration=0.01, n_channels=2) + a = torch.tensor([0.7, 0.2, 0.6]) + b = torch.tensor([0.4, 0.2, 0.9]) + self.assert_grad(F.filtfilt, (x, a, b)) + + def test_filtfilt_batching(self): + torch.random.manual_seed(2434) + x = get_whitenoise(sample_rate=22050, duration=0.01, n_channels=2) + a = torch.tensor([[0.7, 0.2, 0.6], + [0.8, 0.2, 0.9]]) + b = torch.tensor([[0.4, 0.2, 0.9], + [0.7, 0.2, 0.6]]) + self.assert_grad(F.filtfilt, (x, a, b)) + + def test_biquad(self): + torch.random.manual_seed(2434) + x = get_whitenoise(sample_rate=22050, duration=0.01, n_channels=1) + a = torch.tensor([0.7, 0.2, 0.6]) + b = torch.tensor([0.4, 0.2, 0.9]) + self.assert_grad(F.biquad, (x, b[0], b[1], b[2], a[0], a[1], a[2])) + + @parameterized.expand([ + (800, 0.7, True), + (800, 0.7, False), + ]) + def test_band_biquad(self, central_freq, Q, noise): + torch.random.manual_seed(2434) + sr = 22050 + x = get_whitenoise(sample_rate=sr, duration=0.01, n_channels=1) + central_freq = torch.tensor(central_freq) + Q = torch.tensor(Q) + self.assert_grad(F.band_biquad, (x, sr, central_freq, Q, noise)) + + @parameterized.expand([ + (800, 0.7, 10), + (800, 0.7, -10), + ]) + def test_bass_biquad(self, central_freq, Q, gain): + torch.random.manual_seed(2434) + sr = 22050 + x = get_whitenoise(sample_rate=sr, duration=0.01, n_channels=1) + central_freq = torch.tensor(central_freq) + Q = torch.tensor(Q) + gain = torch.tensor(gain) + self.assert_grad(F.bass_biquad, (x, sr, gain, central_freq, Q)) + + @parameterized.expand([ + (3000, 0.7, 10), + (3000, 0.7, -10), + + ]) + def test_treble_biquad(self, central_freq, Q, gain): + torch.random.manual_seed(2434) + sr = 22050 + x = get_whitenoise(sample_rate=sr, duration=0.01, n_channels=1) + central_freq = torch.tensor(central_freq) + Q = torch.tensor(Q) + gain = torch.tensor(gain) + self.assert_grad(F.treble_biquad, (x, sr, gain, central_freq, Q)) + + @parameterized.expand([ + (800, 0.7, ), + ]) + def test_allpass_biquad(self, central_freq, Q): + torch.random.manual_seed(2434) + sr = 22050 + x = get_whitenoise(sample_rate=sr, duration=0.01, n_channels=1) + central_freq = torch.tensor(central_freq) + Q = torch.tensor(Q) + self.assert_grad(F.allpass_biquad, (x, sr, central_freq, Q)) + + @parameterized.expand([ + (800, 0.7, ), + ]) + def test_lowpass_biquad(self, cutoff_freq, Q): + torch.random.manual_seed(2434) + sr = 22050 + x = get_whitenoise(sample_rate=sr, duration=0.01, n_channels=1) + cutoff_freq = torch.tensor(cutoff_freq) + Q = torch.tensor(Q) + self.assert_grad(F.lowpass_biquad, (x, sr, cutoff_freq, Q)) + + @parameterized.expand([ + (800, 0.7, ), + ]) + def test_highpass_biquad(self, cutoff_freq, Q): + torch.random.manual_seed(2434) + sr = 22050 + x = get_whitenoise(sample_rate=sr, duration=0.01, n_channels=1) + cutoff_freq = torch.tensor(cutoff_freq) + Q = torch.tensor(Q) + self.assert_grad(F.highpass_biquad, (x, sr, cutoff_freq, Q)) + + @parameterized.expand([ + (800, 0.7, True), + (800, 0.7, False), + ]) + def test_bandpass_biquad(self, central_freq, Q, const_skirt_gain): + torch.random.manual_seed(2434) + sr = 22050 + x = get_whitenoise(sample_rate=sr, duration=0.01, n_channels=1) + central_freq = torch.tensor(central_freq) + Q = torch.tensor(Q) + self.assert_grad(F.bandpass_biquad, (x, sr, central_freq, Q, const_skirt_gain)) + + @parameterized.expand([ + (800, 0.7, 10), + (800, 0.7, -10), + ]) + def test_equalizer_biquad(self, central_freq, Q, gain): + torch.random.manual_seed(2434) + sr = 22050 + x = get_whitenoise(sample_rate=sr, duration=0.01, n_channels=1) + central_freq = torch.tensor(central_freq) + Q = torch.tensor(Q) + gain = torch.tensor(gain) + self.assert_grad(F.equalizer_biquad, (x, sr, central_freq, gain, Q)) + + @parameterized.expand([ + (800, 0.7, ), + ]) + def test_bandreject_biquad(self, central_freq, Q): + torch.random.manual_seed(2434) + sr = 22050 + x = get_whitenoise(sample_rate=sr, duration=0.01, n_channels=1) + central_freq = torch.tensor(central_freq) + Q = torch.tensor(Q) + self.assert_grad(F.bandreject_biquad, (x, sr, central_freq, Q)) + + +class AutogradFloat32(TestBaseMixin): + def assert_grad( + self, + transform: Callable[..., Tensor], + inputs: Tuple[torch.Tensor], + enable_all_grad: bool = True, + ): + inputs_ = [] + for i in inputs: + if torch.is_tensor(i): + i = i.to(dtype=self.dtype, device=self.device) + if enable_all_grad: + i.requires_grad = True + inputs_.append(i) + # gradcheck with float32 requires higher atol and epsilon + assert gradcheck(transform, inputs, eps=1e-3, atol=1e-3, nondet_tol=0.) + + @parameterized.expand([ + (rnnt_utils.get_B1_T10_U3_D4_data, ), + (rnnt_utils.get_B2_T4_U3_D3_data, ), + (rnnt_utils.get_B1_T2_U3_D5_data, ), + ]) + def test_rnnt_loss(self, data_func): + def get_data(data_func, device): + data = data_func() + if type(data) == tuple: + data = data[0] + return data + + data = get_data(data_func, self.device) + inputs = ( + data["logits"].to(torch.float32), # logits + data["targets"], # targets + data["logit_lengths"], # logit_lengths + data["target_lengths"], # target_lengths + data["blank"], # blank + -1, # clamp + ) + + self.assert_grad(F.rnnt_loss, inputs, enable_all_grad=False) diff --git a/test/torchaudio_unittest/functional/batch_consistency_test.py b/test/torchaudio_unittest/functional/batch_consistency_test.py new file mode 100644 index 00000000..042bfe52 --- /dev/null +++ b/test/torchaudio_unittest/functional/batch_consistency_test.py @@ -0,0 +1,249 @@ +"""Test numerical consistency among single input and batched input.""" +import itertools +import math + +from parameterized import parameterized, parameterized_class +import torch +import torchaudio.functional as F + +from torchaudio_unittest import common_utils + + +def _name_from_args(func, _, params): + """Return a parameterized test name, based on parameter values.""" + return "{}_{}".format( + func.__name__, + "_".join(str(arg) for arg in params.args)) + + +@parameterized_class([ + # Single-item batch isolates problems that come purely from adding a + # dimension (rather than processing multiple items) + {"batch_size": 1}, + {"batch_size": 3}, +]) +class TestFunctional(common_utils.TorchaudioTestCase): + """Test functions defined in `functional` module""" + backend = 'default' + + def assert_batch_consistency( + self, functional, batch, *args, atol=1e-8, rtol=1e-5, seed=42, + **kwargs): + n = batch.size(0) + + # Compute items separately, then batch the result + torch.random.manual_seed(seed) + items_input = batch.clone() + items_result = torch.stack([ + functional(items_input[i], *args, **kwargs) for i in range(n) + ]) + + # Batch the input and run + torch.random.manual_seed(seed) + batch_input = batch.clone() + batch_result = functional(batch_input, *args, **kwargs) + + self.assertEqual(items_input, batch_input, rtol=rtol, atol=atol) + self.assertEqual(items_result, batch_result, rtol=rtol, atol=atol) + + def test_griffinlim(self): + n_fft = 400 + ws = 400 + hop = 200 + window = torch.hann_window(ws) + power = 2 + momentum = 0.99 + n_iter = 32 + length = 1000 + torch.random.manual_seed(0) + batch = torch.rand(self.batch_size, 1, 201, 6) + self.assert_batch_consistency( + F.griffinlim, batch, window, n_fft, hop, ws, power, + n_iter, momentum, length, 0, atol=5e-5) + + @parameterized.expand(list(itertools.product( + [8000, 16000, 44100], + [1, 2], + )), name_func=_name_from_args) + def test_detect_pitch_frequency(self, sample_rate, n_channels): + # Use different frequencies to ensure each item in the batch returns a + # different answer. + torch.manual_seed(0) + frequencies = torch.randint(100, 1000, [self.batch_size]) + waveforms = torch.stack([ + common_utils.get_sinusoid( + frequency=frequency, sample_rate=sample_rate, + n_channels=n_channels, duration=5) + for frequency in frequencies + ]) + self.assert_batch_consistency( + F.detect_pitch_frequency, waveforms, sample_rate) + + def test_amplitude_to_DB(self): + torch.manual_seed(0) + spec = torch.rand(self.batch_size, 2, 100, 100) * 200 + + amplitude_mult = 20. + amin = 1e-10 + ref = 1.0 + db_mult = math.log10(max(amin, ref)) + + # Test with & without a `top_db` clamp + self.assert_batch_consistency( + F.amplitude_to_DB, spec, amplitude_mult, + amin, db_mult, top_db=None) + self.assert_batch_consistency( + F.amplitude_to_DB, spec, amplitude_mult, + amin, db_mult, top_db=40.) + + def test_amplitude_to_DB_itemwise_clamps(self): + """Ensure that the clamps are separate for each spectrogram in a batch. + + The clamp was determined per-batch in a prior implementation, which + meant it was determined by the loudest item, thus items weren't + independent. See: + + https://github.com/pytorch/audio/issues/994 + + """ + amplitude_mult = 20. + amin = 1e-10 + ref = 1.0 + db_mult = math.log10(max(amin, ref)) + top_db = 20. + + # Make a batch of noise + torch.manual_seed(0) + spec = torch.rand([2, 2, 100, 100]) * 200 + # Make one item blow out the other + spec[0] += 50 + + batchwise_dbs = F.amplitude_to_DB(spec, amplitude_mult, amin, + db_mult, top_db=top_db) + itemwise_dbs = torch.stack([ + F.amplitude_to_DB(item, amplitude_mult, amin, + db_mult, top_db=top_db) + for item in spec + ]) + + self.assertEqual(batchwise_dbs, itemwise_dbs) + + def test_amplitude_to_DB_not_channelwise_clamps(self): + """Check that clamps are applied per-item, not per channel.""" + amplitude_mult = 20. + amin = 1e-10 + ref = 1.0 + db_mult = math.log10(max(amin, ref)) + top_db = 40. + + torch.manual_seed(0) + spec = torch.rand([1, 2, 100, 100]) * 200 + # Make one channel blow out the other + spec[:, 0] += 50 + + specwise_dbs = F.amplitude_to_DB(spec, amplitude_mult, amin, + db_mult, top_db=top_db) + channelwise_dbs = torch.stack([ + F.amplitude_to_DB(spec[:, i], amplitude_mult, amin, + db_mult, top_db=top_db) + for i in range(spec.size(-3)) + ]) + + # Just check channelwise gives a different answer. + difference = (specwise_dbs - channelwise_dbs).abs() + assert (difference >= 1e-5).any() + + def test_contrast(self): + torch.random.manual_seed(0) + waveforms = torch.rand(self.batch_size, 2, 100) - 0.5 + self.assert_batch_consistency( + F.contrast, waveforms, enhancement_amount=80.) + + def test_dcshift(self): + torch.random.manual_seed(0) + waveforms = torch.rand(self.batch_size, 2, 100) - 0.5 + self.assert_batch_consistency( + F.dcshift, waveforms, shift=0.5, limiter_gain=0.05) + + def test_overdrive(self): + torch.random.manual_seed(0) + waveforms = torch.rand(self.batch_size, 2, 100) - 0.5 + self.assert_batch_consistency( + F.overdrive, waveforms, gain=45, colour=30) + + def test_phaser(self): + sample_rate = 44100 + n_channels = 2 + waveform = common_utils.get_whitenoise( + sample_rate=sample_rate, n_channels=self.batch_size * n_channels, + duration=1) + batch = waveform.view(self.batch_size, n_channels, waveform.size(-1)) + self.assert_batch_consistency(F.phaser, batch, sample_rate) + + def test_flanger(self): + torch.random.manual_seed(0) + waveforms = torch.rand(self.batch_size, 2, 100) - 0.5 + sample_rate = 44100 + self.assert_batch_consistency(F.flanger, waveforms, sample_rate) + + @parameterized.expand(list(itertools.product( + [True, False], # center + [True, False], # norm_vars + )), name_func=_name_from_args) + def test_sliding_window_cmn(self, center, norm_vars): + torch.manual_seed(0) + spectrogram = torch.rand(self.batch_size, 2, 1024, 1024) * 200 + self.assert_batch_consistency( + F.sliding_window_cmn, spectrogram, center=center, + norm_vars=norm_vars) + + @parameterized.expand([("sinc_interpolation"), ("kaiser_window")]) + def test_resample_waveform(self, resampling_method): + num_channels = 3 + sr = 16000 + new_sr = sr // 2 + multi_sound = common_utils.get_whitenoise(sample_rate=sr, n_channels=num_channels, duration=0.5,) + + self.assert_batch_consistency( + F.resample, multi_sound, orig_freq=sr, new_freq=new_sr, + resampling_method=resampling_method, rtol=1e-4, atol=1e-7) + + @common_utils.skipIfNoKaldi + def test_compute_kaldi_pitch(self): + sample_rate = 44100 + n_channels = 2 + waveform = common_utils.get_whitenoise( + sample_rate=sample_rate, n_channels=self.batch_size * n_channels) + batch = waveform.view(self.batch_size, n_channels, waveform.size(-1)) + self.assert_batch_consistency( + F.compute_kaldi_pitch, batch, sample_rate=sample_rate) + + def test_lfilter(self): + signal_length = 2048 + torch.manual_seed(2434) + x = torch.randn(self.batch_size, signal_length) + a = torch.rand(self.batch_size, 3) + b = torch.rand(self.batch_size, 3) + + batchwise_output = F.lfilter(x, a, b, batching=True) + itemwise_output = torch.stack([ + F.lfilter(x[i], a[i], b[i]) + for i in range(self.batch_size) + ]) + + self.assertEqual(batchwise_output, itemwise_output) + + def test_filtfilt(self): + signal_length = 2048 + torch.manual_seed(2434) + x = torch.randn(self.batch_size, signal_length) + a = torch.rand(self.batch_size, 3) + b = torch.rand(self.batch_size, 3) + + batchwise_output = F.filtfilt(x, a, b) + itemwise_output = torch.stack([ + F.filtfilt(x[i], a[i], b[i]) + for i in range(self.batch_size) + ]) + + self.assertEqual(batchwise_output, itemwise_output) diff --git a/test/torchaudio_unittest/functional/functional_cpu_test.py b/test/torchaudio_unittest/functional/functional_cpu_test.py new file mode 100644 index 00000000..520ff86b --- /dev/null +++ b/test/torchaudio_unittest/functional/functional_cpu_test.py @@ -0,0 +1,63 @@ +import torch +import torchaudio.functional as F +import unittest +from parameterized import parameterized + +from torchaudio_unittest.common_utils import PytorchTestCase, TorchaudioTestCase, skipIfNoSox +from .functional_impl import Functional, FunctionalCPUOnly + + +class TestFunctionalFloat32(Functional, FunctionalCPUOnly, PytorchTestCase): + dtype = torch.float32 + device = torch.device('cpu') + + @unittest.expectedFailure + def test_lfilter_9th_order_filter_stability(self): + super().test_lfilter_9th_order_filter_stability() + + +class TestFunctionalFloat64(Functional, PytorchTestCase): + dtype = torch.float64 + device = torch.device('cpu') + + +@skipIfNoSox +class TestApplyCodec(TorchaudioTestCase): + backend = "sox_io" + + def _smoke_test(self, format, compression, check_num_frames): + """ + The purpose of this test suite is to verify that apply_codec functionalities do not exhibit + abnormal behaviors. + """ + torch.random.manual_seed(42) + sample_rate = 8000 + num_frames = 3 * sample_rate + num_channels = 2 + waveform = torch.rand(num_channels, num_frames) + + augmented = F.apply_codec(waveform, + sample_rate, + format, + True, + compression + ) + assert augmented.dtype == waveform.dtype + assert augmented.shape[0] == num_channels + if check_num_frames: + assert augmented.shape[1] == num_frames + + def test_wave(self): + self._smoke_test("wav", compression=None, check_num_frames=True) + + @parameterized.expand([(96,), (128,), (160,), (192,), (224,), (256,), (320,)]) + def test_mp3(self, compression): + self._smoke_test("mp3", compression, check_num_frames=False) + + @parameterized.expand([(0,), (1,), (2,), (3,), (4,), (5,), (6,), (7,), (8,)]) + def test_flac(self, compression): + self._smoke_test("flac", compression, check_num_frames=False) + + @parameterized.expand([(-1,), (0,), (1,), (2,), (3,), (3.6,), (5,), (10,)]) + def test_vorbis(self, compression): + self._smoke_test("vorbis", compression, check_num_frames=False) diff --git a/test/torchaudio_unittest/functional/functional_cuda_test.py b/test/torchaudio_unittest/functional/functional_cuda_test.py new file mode 100644 index 00000000..fd355472 --- /dev/null +++ b/test/torchaudio_unittest/functional/functional_cuda_test.py @@ -0,0 +1,21 @@ +import torch +import unittest + +from torchaudio_unittest.common_utils import PytorchTestCase, skipIfNoCuda +from .functional_impl import Functional + + +@skipIfNoCuda +class TestFunctionalFloat32(Functional, PytorchTestCase): + dtype = torch.float32 + device = torch.device('cuda') + + @unittest.expectedFailure + def test_lfilter_9th_order_filter_stability(self): + super().test_lfilter_9th_order_filter_stability() + + +@skipIfNoCuda +class TestLFilterFloat64(Functional, PytorchTestCase): + dtype = torch.float64 + device = torch.device('cuda') diff --git a/test/torchaudio_unittest/functional/functional_impl.py b/test/torchaudio_unittest/functional/functional_impl.py new file mode 100644 index 00000000..c3bebbf0 --- /dev/null +++ b/test/torchaudio_unittest/functional/functional_impl.py @@ -0,0 +1,584 @@ +"""Test definition common to CPU and CUDA""" +import math +import itertools +import warnings + +import numpy as np +import torch +import torchaudio.functional as F +from parameterized import parameterized +from scipy import signal + +from torchaudio_unittest.common_utils import ( + TestBaseMixin, + get_sinusoid, + nested_params, + get_whitenoise, + rnnt_utils, +) + + +class Functional(TestBaseMixin): + def _test_resample_waveform_accuracy(self, up_scale_factor=None, down_scale_factor=None, + resampling_method="sinc_interpolation", atol=1e-1, rtol=1e-4): + # resample the signal and compare it to the ground truth + n_to_trim = 20 + sample_rate = 1000 + new_sample_rate = sample_rate + + if up_scale_factor is not None: + new_sample_rate = int(new_sample_rate * up_scale_factor) + + if down_scale_factor is not None: + new_sample_rate = int(new_sample_rate / down_scale_factor) + + duration = 5 # seconds + original_timestamps = torch.arange(0, duration, 1.0 / sample_rate) + + sound = 123 * torch.cos(2 * math.pi * 3 * original_timestamps).unsqueeze(0) + estimate = F.resample(sound, sample_rate, new_sample_rate, + resampling_method=resampling_method).squeeze() + + new_timestamps = torch.arange(0, duration, 1.0 / new_sample_rate)[:estimate.size(0)] + ground_truth = 123 * torch.cos(2 * math.pi * 3 * new_timestamps) + + # trim the first/last n samples as these points have boundary effects + ground_truth = ground_truth[..., n_to_trim:-n_to_trim] + estimate = estimate[..., n_to_trim:-n_to_trim] + + self.assertEqual(estimate, ground_truth, atol=atol, rtol=rtol) + + def _test_costs_and_gradients( + self, data, ref_costs, ref_gradients, atol=1e-6, rtol=1e-2 + ): + logits_shape = data["logits"].shape + costs, gradients = rnnt_utils.compute_with_pytorch_transducer(data=data) + self.assertEqual(costs, ref_costs, atol=atol, rtol=rtol) + self.assertEqual(logits_shape, gradients.shape) + self.assertEqual(gradients, ref_gradients, atol=atol, rtol=rtol) + + def test_lfilter_simple(self): + """ + Create a very basic signal, + Then make a simple 4th order delay + The output should be same as the input but shifted + """ + + torch.random.manual_seed(42) + waveform = torch.rand(2, 44100 * 1, dtype=self.dtype, device=self.device) + b_coeffs = torch.tensor([0, 0, 0, 1], dtype=self.dtype, device=self.device) + a_coeffs = torch.tensor([1, 0, 0, 0], dtype=self.dtype, device=self.device) + output_waveform = F.lfilter(waveform, a_coeffs, b_coeffs) + + self.assertEqual(output_waveform[:, 3:], waveform[:, 0:-3], atol=1e-5, rtol=1e-5) + + def test_lfilter_clamp(self): + input_signal = torch.ones(1, 44100 * 1, dtype=self.dtype, device=self.device) + b_coeffs = torch.tensor([1, 0], dtype=self.dtype, device=self.device) + a_coeffs = torch.tensor([1, -0.95], dtype=self.dtype, device=self.device) + output_signal = F.lfilter(input_signal, a_coeffs, b_coeffs, clamp=True) + assert output_signal.max() <= 1 + output_signal = F.lfilter(input_signal, a_coeffs, b_coeffs, clamp=False) + assert output_signal.max() > 1 + + @parameterized.expand([ + ((44100,), (4,), (44100,)), + ((3, 44100), (4,), (3, 44100,)), + ((2, 3, 44100), (4,), (2, 3, 44100,)), + ((1, 2, 3, 44100), (4,), (1, 2, 3, 44100,)), + ((44100,), (2, 4), (2, 44100)), + ((3, 44100), (1, 4), (3, 1, 44100)), + ((1, 2, 44100), (3, 4), (1, 2, 3, 44100)) + ]) + def test_lfilter_shape(self, input_shape, coeff_shape, target_shape): + torch.random.manual_seed(42) + waveform = torch.rand(*input_shape, dtype=self.dtype, device=self.device) + b_coeffs = torch.rand(*coeff_shape, dtype=self.dtype, device=self.device) + a_coeffs = torch.rand(*coeff_shape, dtype=self.dtype, device=self.device) + output_waveform = F.lfilter(waveform, a_coeffs, b_coeffs, batching=False) + assert input_shape == waveform.size() + assert target_shape == output_waveform.size() + + def test_lfilter_9th_order_filter_stability(self): + """ + Validate the precision of lfilter against reference scipy implementation when using high order filter. + The reference implementation use cascaded second-order filters so is more numerically accurate. + """ + # create an impulse signal + x = torch.zeros(1024, dtype=self.dtype, device=self.device) + x[0] = 1 + + # get target impulse response + sos = signal.butter(9, 850, 'hp', fs=22050, output='sos') + y = torch.from_numpy(signal.sosfilt(sos, x.cpu().numpy())).to(self.dtype).to(self.device) + + # get lfilter coefficients + b, a = signal.butter(9, 850, 'hp', fs=22050, output='ba') + b, a = torch.from_numpy(b).to(self.dtype).to(self.device), torch.from_numpy( + a).to(self.dtype).to(self.device) + + # predict impulse response + yhat = F.lfilter(x, a, b, False) + self.assertEqual(yhat, y, atol=1e-4, rtol=1e-5) + + def test_filtfilt_simple(self): + """ + Check that, for an arbitrary signal, applying filtfilt with filter coefficients + corresponding to a pure delay filter imparts no time delay. + """ + waveform = get_whitenoise(sample_rate=8000, n_channels=2, dtype=self.dtype).to( + device=self.device + ) + b_coeffs = torch.tensor([0, 0, 0, 1], dtype=self.dtype, device=self.device) + a_coeffs = torch.tensor([1, 0, 0, 0], dtype=self.dtype, device=self.device) + padded_waveform = torch.cat( + (waveform, torch.zeros(2, 3, dtype=self.dtype, device=self.device)), axis=1 + ) + output_waveform = F.filtfilt(padded_waveform, a_coeffs, b_coeffs) + + self.assertEqual(output_waveform, padded_waveform, atol=1e-5, rtol=1e-5) + + def test_filtfilt_filter_sinusoid(self): + """ + Check that, for a signal comprising two sinusoids, applying filtfilt + with appropriate filter coefficients correctly removes the higher-frequency + sinusoid while imparting no time delay. + """ + T = 1.0 + samples = 1000 + + waveform_k0 = get_sinusoid( + frequency=5, sample_rate=samples // T, dtype=self.dtype, device=self.device + ).squeeze(0) + waveform_k1 = get_sinusoid( + frequency=200, + sample_rate=samples // T, + dtype=self.dtype, + device=self.device, + ).squeeze(0) + waveform = waveform_k0 + waveform_k1 + + # Transfer function numerator and denominator polynomial coefficients + # corresponding to 8th-order Butterworth filter with 100-cycle/T cutoff. + # Generated with + # >>> from scipy import signal + # >>> b_coeffs, a_coeffs = signal.butter(8, 0.2) + b_coeffs = torch.tensor( + [ + 2.39596441e-05, + 1.91677153e-04, + 6.70870035e-04, + 1.34174007e-03, + 1.67717509e-03, + 1.34174007e-03, + 6.70870035e-04, + 1.91677153e-04, + 2.39596441e-05, + ], + dtype=self.dtype, + device=self.device, + ) + a_coeffs = torch.tensor( + [ + 1.0, + -4.78451489, + 10.44504107, + -13.45771989, + 11.12933104, + -6.0252604, + 2.0792738, + -0.41721716, + 0.0372001, + ], + dtype=self.dtype, + device=self.device, + ) + + # Extend waveform in each direction, preserving periodicity. + padded_waveform = torch.cat((waveform[:-1], waveform, waveform[1:])) + + output_waveform = F.filtfilt(padded_waveform, a_coeffs, b_coeffs) + + # Remove padding from output waveform; confirm that result + # closely matches waveform_k0. + self.assertEqual( + output_waveform[samples - 1: 2 * samples - 1], + waveform_k0, + atol=1e-3, + rtol=1e-3, + ) + + @parameterized.expand([(0., ), (1., ), (2., ), (3., )]) + def test_spectogram_grad_at_zero(self, power): + """The gradient of power spectrogram should not be nan but zero near x=0 + + https://github.com/pytorch/audio/issues/993 + """ + x = torch.zeros(1, 22050, requires_grad=True) + spec = F.spectrogram( + x, + pad=0, + window=None, + n_fft=2048, + hop_length=None, + win_length=None, + power=power, + normalized=False, + ) + spec.sum().backward() + assert not x.grad.isnan().sum() + + def test_compute_deltas_one_channel(self): + specgram = torch.tensor([[[1.0, 2.0, 3.0, 4.0]]], dtype=self.dtype, device=self.device) + expected = torch.tensor([[[0.5, 1.0, 1.0, 0.5]]], dtype=self.dtype, device=self.device) + computed = F.compute_deltas(specgram, win_length=3) + self.assertEqual(computed, expected) + + def test_compute_deltas_two_channels(self): + specgram = torch.tensor([[[1.0, 2.0, 3.0, 4.0], + [1.0, 2.0, 3.0, 4.0]]], dtype=self.dtype, device=self.device) + expected = torch.tensor([[[0.5, 1.0, 1.0, 0.5], + [0.5, 1.0, 1.0, 0.5]]], dtype=self.dtype, device=self.device) + computed = F.compute_deltas(specgram, win_length=3) + self.assertEqual(computed, expected) + + @parameterized.expand([(100,), (440,)]) + def test_detect_pitch_frequency_pitch(self, frequency): + sample_rate = 44100 + test_sine_waveform = get_sinusoid( + frequency=frequency, sample_rate=sample_rate, duration=5 + ) + + freq = F.detect_pitch_frequency(test_sine_waveform, sample_rate) + + threshold = 1 + s = ((freq - frequency).abs() > threshold).sum() + self.assertFalse(s) + + @parameterized.expand([([100, 100],), ([2, 100, 100],), ([2, 2, 100, 100],)]) + def test_amplitude_to_DB_reversible(self, shape): + """Round trip between amplitude and db should return the original for various shape + + This implicitly also tests `DB_to_amplitude`. + + """ + amplitude_mult = 20. + power_mult = 10. + amin = 1e-10 + ref = 1.0 + db_mult = math.log10(max(amin, ref)) + + torch.manual_seed(0) + spec = torch.rand(*shape, dtype=self.dtype, device=self.device) * 200 + + # Spectrogram amplitude -> DB -> amplitude + db = F.amplitude_to_DB(spec, amplitude_mult, amin, db_mult, top_db=None) + x2 = F.DB_to_amplitude(db, ref, 0.5) + + self.assertEqual(x2, spec, atol=5e-5, rtol=1e-5) + + # Spectrogram power -> DB -> power + db = F.amplitude_to_DB(spec, power_mult, amin, db_mult, top_db=None) + x2 = F.DB_to_amplitude(db, ref, 1.) + + self.assertEqual(x2, spec) + + @parameterized.expand([([100, 100],), ([2, 100, 100],), ([2, 2, 100, 100],)]) + def test_amplitude_to_DB_top_db_clamp(self, shape): + """Ensure values are properly clamped when `top_db` is supplied.""" + amplitude_mult = 20. + amin = 1e-10 + ref = 1.0 + db_mult = math.log10(max(amin, ref)) + top_db = 40. + + torch.manual_seed(0) + # A random tensor is used for increased entropy, but the max and min for + # each spectrogram still need to be predictable. The max determines the + # decibel cutoff, and the distance from the min must be large enough + # that it triggers a clamp. + spec = torch.rand(*shape, dtype=self.dtype, device=self.device) + # Ensure each spectrogram has a min of 0 and a max of 1. + spec -= spec.amin([-2, -1])[..., None, None] + spec /= spec.amax([-2, -1])[..., None, None] + # Expand the range to (0, 200) - wide enough to properly test clamping. + spec *= 200 + + decibels = F.amplitude_to_DB(spec, amplitude_mult, amin, + db_mult, top_db=top_db) + # Ensure the clamp was applied + below_limit = decibels < 6.0205 + assert not below_limit.any(), ( + "{} decibel values were below the expected cutoff:\n{}".format( + below_limit.sum().item(), decibels + ) + ) + # Ensure it didn't over-clamp + close_to_limit = decibels < 6.0207 + assert close_to_limit.any(), ( + f"No values were close to the limit. Did it over-clamp?\n{decibels}" + ) + + @parameterized.expand( + list(itertools.product([(1, 2, 1025, 400, 2), (1025, 400, 2)], [1, 2, 0.7])) + ) + def test_complex_norm(self, shape, power): + torch.random.manual_seed(42) + complex_tensor = torch.randn(*shape, dtype=self.dtype, device=self.device) + expected_norm_tensor = complex_tensor.pow(2).sum(-1).pow(power / 2) + norm_tensor = F.complex_norm(complex_tensor, power) + self.assertEqual(norm_tensor, expected_norm_tensor, atol=1e-5, rtol=1e-5) + + @parameterized.expand( + list(itertools.product([(2, 1025, 400), (1, 201, 100)], [100], [0., 30.], [1, 2])) + ) + def test_mask_along_axis(self, shape, mask_param, mask_value, axis): + torch.random.manual_seed(42) + specgram = torch.randn(*shape, dtype=self.dtype, device=self.device) + mask_specgram = F.mask_along_axis(specgram, mask_param, mask_value, axis) + + other_axis = 1 if axis == 2 else 2 + + masked_columns = (mask_specgram == mask_value).sum(other_axis) + num_masked_columns = (masked_columns == mask_specgram.size(other_axis)).sum() + num_masked_columns = torch.div( + num_masked_columns, mask_specgram.size(0), rounding_mode='floor') + + assert mask_specgram.size() == specgram.size() + assert num_masked_columns < mask_param + + @parameterized.expand(list(itertools.product([100], [0., 30.], [2, 3]))) + def test_mask_along_axis_iid(self, mask_param, mask_value, axis): + torch.random.manual_seed(42) + specgrams = torch.randn(4, 2, 1025, 400, dtype=self.dtype, device=self.device) + + mask_specgrams = F.mask_along_axis_iid(specgrams, mask_param, mask_value, axis) + + other_axis = 2 if axis == 3 else 3 + + masked_columns = (mask_specgrams == mask_value).sum(other_axis) + num_masked_columns = (masked_columns == mask_specgrams.size(other_axis)).sum(-1) + + assert mask_specgrams.size() == specgrams.size() + assert (num_masked_columns < mask_param).sum() == num_masked_columns.numel() + + @parameterized.expand( + list(itertools.product([(2, 1025, 400), (1, 201, 100)], [100], [0., 30.], [1, 2])) + ) + def test_mask_along_axis_preserve(self, shape, mask_param, mask_value, axis): + """mask_along_axis should not alter original input Tensor + + Test is run 5 times to bound the probability of no masking occurring to 1e-10 + See https://github.com/pytorch/audio/issues/1478 + """ + torch.random.manual_seed(42) + for _ in range(5): + specgram = torch.randn(*shape, dtype=self.dtype, device=self.device) + specgram_copy = specgram.clone() + F.mask_along_axis(specgram, mask_param, mask_value, axis) + + self.assertEqual(specgram, specgram_copy) + + @parameterized.expand(list(itertools.product([100], [0., 30.], [2, 3]))) + def test_mask_along_axis_iid_preserve(self, mask_param, mask_value, axis): + """mask_along_axis_iid should not alter original input Tensor + + Test is run 5 times to bound the probability of no masking occurring to 1e-10 + See https://github.com/pytorch/audio/issues/1478 + """ + torch.random.manual_seed(42) + for _ in range(5): + specgrams = torch.randn(4, 2, 1025, 400, dtype=self.dtype, device=self.device) + specgrams_copy = specgrams.clone() + F.mask_along_axis_iid(specgrams, mask_param, mask_value, axis) + + self.assertEqual(specgrams, specgrams_copy) + + @parameterized.expand(list(itertools.product( + ["sinc_interpolation", "kaiser_window"], + [16000, 44100], + ))) + def test_resample_identity(self, resampling_method, sample_rate): + waveform = get_whitenoise(sample_rate=sample_rate, duration=1) + + resampled = F.resample(waveform, sample_rate, sample_rate) + self.assertEqual(waveform, resampled) + + @parameterized.expand([("sinc_interpolation"), ("kaiser_window")]) + def test_resample_waveform_upsample_size(self, resampling_method): + sr = 16000 + waveform = get_whitenoise(sample_rate=sr, duration=0.5,) + upsampled = F.resample(waveform, sr, sr * 2, resampling_method=resampling_method) + assert upsampled.size(-1) == waveform.size(-1) * 2 + + @parameterized.expand([("sinc_interpolation"), ("kaiser_window")]) + def test_resample_waveform_downsample_size(self, resampling_method): + sr = 16000 + waveform = get_whitenoise(sample_rate=sr, duration=0.5,) + downsampled = F.resample(waveform, sr, sr // 2, resampling_method=resampling_method) + assert downsampled.size(-1) == waveform.size(-1) // 2 + + @parameterized.expand([("sinc_interpolation"), ("kaiser_window")]) + def test_resample_waveform_identity_size(self, resampling_method): + sr = 16000 + waveform = get_whitenoise(sample_rate=sr, duration=0.5,) + resampled = F.resample(waveform, sr, sr, resampling_method=resampling_method) + assert resampled.size(-1) == waveform.size(-1) + + @parameterized.expand(list(itertools.product( + ["sinc_interpolation", "kaiser_window"], + list(range(1, 20)), + ))) + def test_resample_waveform_downsample_accuracy(self, resampling_method, i): + self._test_resample_waveform_accuracy(down_scale_factor=i * 2, resampling_method=resampling_method) + + @parameterized.expand(list(itertools.product( + ["sinc_interpolation", "kaiser_window"], + list(range(1, 20)), + ))) + def test_resample_waveform_upsample_accuracy(self, resampling_method, i): + self._test_resample_waveform_accuracy(up_scale_factor=1.0 + i / 20.0, resampling_method=resampling_method) + + @nested_params( + [0.5, 1.01, 1.3], + [True, False], + ) + def test_phase_vocoder_shape(self, rate, test_pseudo_complex): + """Verify the output shape of phase vocoder""" + hop_length = 256 + num_freq = 1025 + num_frames = 400 + batch_size = 2 + + torch.random.manual_seed(42) + spec = torch.randn( + batch_size, num_freq, num_frames, dtype=self.complex_dtype, device=self.device) + if test_pseudo_complex: + spec = torch.view_as_real(spec) + + phase_advance = torch.linspace( + 0, + np.pi * hop_length, + num_freq, + dtype=self.dtype, device=self.device)[..., None] + + spec_stretch = F.phase_vocoder(spec, rate=rate, phase_advance=phase_advance) + + assert spec.dim() == spec_stretch.dim() + expected_shape = torch.Size([batch_size, num_freq, int(np.ceil(num_frames / rate))]) + output_shape = (torch.view_as_complex(spec_stretch) if test_pseudo_complex else spec_stretch).shape + assert output_shape == expected_shape + + @parameterized.expand( + [ + # words + ["", "", 0], # equal + ["abc", "abc", 0], + ["ᑌᑎIᑕO", "ᑌᑎIᑕO", 0], + + ["abc", "", 3], # deletion + ["aa", "aaa", 1], + ["aaa", "aa", 1], + ["ᑌᑎI", "ᑌᑎIᑕO", 2], + + ["aaa", "aba", 1], # substitution + ["aba", "aaa", 1], + ["aba", " ", 3], + + ["abc", "bcd", 2], # mix deletion and substitution + ["0ᑌᑎI", "ᑌᑎIᑕO", 3], + + # sentences + [["hello", "", "Tᕮ᙭T"], ["hello", "", "Tᕮ᙭T"], 0], # equal + [[], [], 0], + + [["hello", "world"], ["hello", "world", "!"], 1], # deletion + [["hello", "world"], ["world"], 1], + [["hello", "world"], [], 2], + + [["Tᕮ᙭T", ], ["world"], 1], # substitution + [["Tᕮ᙭T", "XD"], ["world", "hello"], 2], + [["", "XD"], ["world", ""], 2], + ["aba", " ", 3], + + [["hello", "world"], ["world", "hello", "!"], 2], # mix deletion and substitution + [["Tᕮ᙭T", "world", "LOL", "XD"], ["world", "hello", "ʕ•́ᴥ•̀ʔっ"], 3], + ] + ) + def test_simple_case_edit_distance(self, seq1, seq2, distance): + assert F.edit_distance(seq1, seq2) == distance + assert F.edit_distance(seq2, seq1) == distance + + @nested_params( + [-4, -2, 0, 2, 4], + ) + def test_pitch_shift_shape(self, n_steps): + sample_rate = 16000 + torch.random.manual_seed(42) + waveform = torch.rand(2, 44100 * 1, dtype=self.dtype, device=self.device) + waveform_shift = F.pitch_shift(waveform, sample_rate, n_steps) + assert waveform.size() == waveform_shift.size() + + def test_rnnt_loss_basic_backward(self): + logits, targets, logit_lengths, target_lengths = rnnt_utils.get_basic_data(self.device) + loss = F.rnnt_loss(logits, targets, logit_lengths, target_lengths) + loss.backward() + + def test_rnnt_loss_basic_forward_no_grad(self): + """In early stage, calls to `rnnt_loss` resulted in segmentation fault when + `logits` have `requires_grad = False`. This test makes sure that this no longer + occurs and the functional call runs without error. + + See https://github.com/pytorch/audio/pull/1707 + """ + logits, targets, logit_lengths, target_lengths = rnnt_utils.get_basic_data(self.device) + logits.requires_grad_(False) + F.rnnt_loss(logits, targets, logit_lengths, target_lengths) + + @parameterized.expand([ + (rnnt_utils.get_B1_T2_U3_D5_data, torch.float32, 1e-6, 1e-2), + (rnnt_utils.get_B2_T4_U3_D3_data, torch.float32, 1e-6, 1e-2), + (rnnt_utils.get_B1_T2_U3_D5_data, torch.float16, 1e-3, 1e-2), + (rnnt_utils.get_B2_T4_U3_D3_data, torch.float16, 1e-3, 1e-2), + ]) + def test_rnnt_loss_costs_and_gradients(self, data_func, dtype, atol, rtol): + data, ref_costs, ref_gradients = data_func( + dtype=dtype, + device=self.device, + ) + self._test_costs_and_gradients( + data=data, + ref_costs=ref_costs, + ref_gradients=ref_gradients, + atol=atol, + rtol=rtol, + ) + + def test_rnnt_loss_costs_and_gradients_random_data_with_numpy_fp32(self): + seed = 777 + for i in range(5): + data = rnnt_utils.get_random_data(dtype=torch.float32, device=self.device, seed=(seed + i)) + ref_costs, ref_gradients = rnnt_utils.compute_with_numpy_transducer(data=data) + self._test_costs_and_gradients( + data=data, ref_costs=ref_costs, ref_gradients=ref_gradients + ) + + +class FunctionalCPUOnly(TestBaseMixin): + def test_melscale_fbanks_no_warning_high_n_freq(self): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + F.melscale_fbanks(288, 0, 8000, 128, 16000) + assert len(w) == 0 + + def test_melscale_fbanks_no_warning_low_n_mels(self): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + F.melscale_fbanks(201, 0, 8000, 89, 16000) + assert len(w) == 0 + + def test_melscale_fbanks_warning(self): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + F.melscale_fbanks(201, 0, 8000, 128, 16000) + assert len(w) == 1 diff --git a/test/torchaudio_unittest/functional/kaldi_compatibility_cpu_test.py b/test/torchaudio_unittest/functional/kaldi_compatibility_cpu_test.py new file mode 100644 index 00000000..90a63474 --- /dev/null +++ b/test/torchaudio_unittest/functional/kaldi_compatibility_cpu_test.py @@ -0,0 +1,19 @@ +import torch + +from torchaudio_unittest.common_utils import PytorchTestCase +from .kaldi_compatibility_test_impl import Kaldi, KaldiCPUOnly + + +class TestKaldiCPUOnly(KaldiCPUOnly, PytorchTestCase): + dtype = torch.float32 + device = torch.device('cpu') + + +class TestKaldiFloat32(Kaldi, PytorchTestCase): + dtype = torch.float32 + device = torch.device('cpu') + + +class TestKaldiFloat64(Kaldi, PytorchTestCase): + dtype = torch.float64 + device = torch.device('cpu') diff --git a/test/torchaudio_unittest/functional/kaldi_compatibility_cuda_test.py b/test/torchaudio_unittest/functional/kaldi_compatibility_cuda_test.py new file mode 100644 index 00000000..47a1bea3 --- /dev/null +++ b/test/torchaudio_unittest/functional/kaldi_compatibility_cuda_test.py @@ -0,0 +1,16 @@ +import torch + +from torchaudio_unittest.common_utils import PytorchTestCase, skipIfNoCuda +from .kaldi_compatibility_test_impl import Kaldi + + +@skipIfNoCuda +class TestKaldiFloat32(Kaldi, PytorchTestCase): + dtype = torch.float32 + device = torch.device('cuda') + + +@skipIfNoCuda +class TestKaldiFloat64(Kaldi, PytorchTestCase): + dtype = torch.float64 + device = torch.device('cuda') diff --git a/test/torchaudio_unittest/functional/kaldi_compatibility_test_impl.py b/test/torchaudio_unittest/functional/kaldi_compatibility_test_impl.py new file mode 100644 index 00000000..bad30afd --- /dev/null +++ b/test/torchaudio_unittest/functional/kaldi_compatibility_test_impl.py @@ -0,0 +1,60 @@ +from parameterized import parameterized +import torch +import torchaudio.functional as F + +from torchaudio_unittest.common_utils import ( + get_sinusoid, + load_params, + save_wav, + skipIfNoExec, + TempDirMixin, + TestBaseMixin, +) +from torchaudio_unittest.common_utils.kaldi_utils import ( + convert_args, + run_kaldi, +) + + +class Kaldi(TempDirMixin, TestBaseMixin): + def assert_equal(self, output, *, expected, rtol=None, atol=None): + expected = expected.to(dtype=self.dtype, device=self.device) + self.assertEqual(output, expected, rtol=rtol, atol=atol) + + @skipIfNoExec('apply-cmvn-sliding') + def test_sliding_window_cmn(self): + """sliding_window_cmn should be numerically compatible with apply-cmvn-sliding""" + kwargs = { + 'cmn_window': 600, + 'min_cmn_window': 100, + 'center': False, + 'norm_vars': False, + } + + tensor = torch.randn(40, 10, dtype=self.dtype, device=self.device) + result = F.sliding_window_cmn(tensor, **kwargs) + command = ['apply-cmvn-sliding'] + convert_args(**kwargs) + ['ark:-', 'ark:-'] + kaldi_result = run_kaldi(command, 'ark', tensor) + self.assert_equal(result, expected=kaldi_result) + + +class KaldiCPUOnly(TempDirMixin, TestBaseMixin): + def assert_equal(self, output, *, expected, rtol=None, atol=None): + expected = expected.to(dtype=self.dtype, device=self.device) + self.assertEqual(output, expected, rtol=rtol, atol=atol) + + @parameterized.expand(load_params('kaldi_test_pitch_args.jsonl')) + @skipIfNoExec('compute-kaldi-pitch-feats') + def test_pitch_feats(self, kwargs): + """compute_kaldi_pitch produces numerically compatible result with compute-kaldi-pitch-feats""" + sample_rate = kwargs['sample_rate'] + waveform = get_sinusoid(dtype='float32', sample_rate=sample_rate) + result = F.compute_kaldi_pitch(waveform[0], **kwargs) + + waveform = get_sinusoid(dtype='int16', sample_rate=sample_rate) + wave_file = self.get_temp_path('test.wav') + save_wav(wave_file, waveform, sample_rate) + + command = ['compute-kaldi-pitch-feats'] + convert_args(**kwargs) + ['scp:-', 'ark:-'] + kaldi_result = run_kaldi(command, 'scp', wave_file) + self.assert_equal(result, expected=kaldi_result) diff --git a/test/torchaudio_unittest/functional/librosa_compatibility_cpu_test.py b/test/torchaudio_unittest/functional/librosa_compatibility_cpu_test.py new file mode 100644 index 00000000..dcfe6fcf --- /dev/null +++ b/test/torchaudio_unittest/functional/librosa_compatibility_cpu_test.py @@ -0,0 +1,10 @@ +from torchaudio_unittest.common_utils import PytorchTestCase +from .librosa_compatibility_test_impl import Functional, FunctionalComplex + + +class TestFunctionalCPU(Functional, PytorchTestCase): + device = 'cpu' + + +class TestFunctionalComplexCPU(FunctionalComplex, PytorchTestCase): + device = 'cpu' diff --git a/test/torchaudio_unittest/functional/librosa_compatibility_cuda_test.py b/test/torchaudio_unittest/functional/librosa_compatibility_cuda_test.py new file mode 100644 index 00000000..9f42b4dc --- /dev/null +++ b/test/torchaudio_unittest/functional/librosa_compatibility_cuda_test.py @@ -0,0 +1,12 @@ +from torchaudio_unittest.common_utils import PytorchTestCase, skipIfNoCuda +from .librosa_compatibility_test_impl import Functional, FunctionalComplex + + +@skipIfNoCuda +class TestFunctionalCUDA(Functional, PytorchTestCase): + device = 'cuda' + + +@skipIfNoCuda +class TestFunctionalComplexCUDA(FunctionalComplex, PytorchTestCase): + device = 'cuda' diff --git a/test/torchaudio_unittest/functional/librosa_compatibility_test_impl.py b/test/torchaudio_unittest/functional/librosa_compatibility_test_impl.py new file mode 100644 index 00000000..a63f0da9 --- /dev/null +++ b/test/torchaudio_unittest/functional/librosa_compatibility_test_impl.py @@ -0,0 +1,161 @@ +import unittest +from distutils.version import StrictVersion + +import torch +from parameterized import param + +import torchaudio.functional as F +from torchaudio._internal.module_utils import is_module_available + +LIBROSA_AVAILABLE = is_module_available('librosa') + +if LIBROSA_AVAILABLE: + import numpy as np + import librosa + + +from torchaudio_unittest.common_utils import ( + TestBaseMixin, + nested_params, + get_whitenoise, + get_spectrogram, +) + + +@unittest.skipIf(not LIBROSA_AVAILABLE, "Librosa not available") +class Functional(TestBaseMixin): + """Test suite for functions in `functional` module.""" + dtype = torch.float64 + + @nested_params([0, 0.99]) + def test_griffinlim(self, momentum): + # FFT params + n_fft = 400 + win_length = n_fft + hop_length = n_fft // 4 + window = torch.hann_window(win_length, device=self.device) + power = 1 + # GriffinLim params + n_iter = 8 + + waveform = get_whitenoise(device=self.device, dtype=self.dtype) + specgram = get_spectrogram( + waveform, n_fft=n_fft, hop_length=hop_length, power=power, + win_length=win_length, window=window) + + result = F.griffinlim( + specgram, + window=window, + n_fft=n_fft, + hop_length=hop_length, + win_length=win_length, + power=power, + n_iter=n_iter, + momentum=momentum, + length=waveform.size(1), + rand_init=False) + expected = librosa.griffinlim( + specgram[0].cpu().numpy(), + n_iter=n_iter, + hop_length=hop_length, + momentum=momentum, + init=None, + length=waveform.size(1))[None, ...] + self.assertEqual(result, torch.from_numpy(expected), atol=5e-5, rtol=1e-07) + + @nested_params( + [ + param(), + param(n_mels=128, sample_rate=44100), + param(n_mels=128, fmin=2000.0, fmax=5000.0), + param(n_mels=56, fmin=100.0, fmax=9000.0), + param(n_mels=56, fmin=800.0, fmax=900.0), + param(n_mels=56, fmin=1900.0, fmax=900.0), + param(n_mels=10, fmin=1900.0, fmax=900.0), + ], + [param(norm=n) for n in [None, 'slaney']], + [param(mel_scale=s) for s in ['htk', 'slaney']], + ) + def test_create_mel_fb(self, n_mels=40, sample_rate=22050, n_fft=2048, + fmin=0.0, fmax=8000.0, norm=None, mel_scale="htk"): + if (norm == "slaney" and StrictVersion(librosa.__version__) < StrictVersion("0.7.2")): + self.skipTest('Test is known to fail with older versions of librosa.') + if self.device != 'cpu': + self.skipTest('No need to run this test on CUDA') + + expected = librosa.filters.mel( + sr=sample_rate, + n_fft=n_fft, + n_mels=n_mels, + fmax=fmax, + fmin=fmin, + htk=mel_scale == "htk", + norm=norm).T + result = F.melscale_fbanks( + sample_rate=sample_rate, + n_mels=n_mels, + f_max=fmax, + f_min=fmin, + n_freqs=(n_fft // 2 + 1), + norm=norm, + mel_scale=mel_scale) + self.assertEqual(result, torch.from_numpy(expected), atol=7e-5, rtol=1.3e-6) + + def test_amplitude_to_DB_power(self): + amin = 1e-10 + db_multiplier = 0.0 + top_db = 80.0 + multiplier = 10.0 + + spec = get_spectrogram(get_whitenoise(device=self.device, dtype=self.dtype), power=2) + result = F.amplitude_to_DB(spec, multiplier, amin, db_multiplier, top_db) + expected = librosa.core.power_to_db(spec[0].cpu().numpy())[None, ...] + self.assertEqual(result, torch.from_numpy(expected)) + + def test_amplitude_to_DB(self): + amin = 1e-10 + db_multiplier = 0.0 + top_db = 80.0 + multiplier = 20.0 + + spec = get_spectrogram(get_whitenoise(device=self.device, dtype=self.dtype), power=1) + result = F.amplitude_to_DB(spec, multiplier, amin, db_multiplier, top_db) + expected = librosa.core.amplitude_to_db(spec[0].cpu().numpy())[None, ...] + self.assertEqual(result, torch.from_numpy(expected)) + + +@unittest.skipIf(not LIBROSA_AVAILABLE, "Librosa not available") +class FunctionalComplex(TestBaseMixin): + @nested_params( + [0.5, 1.01, 1.3], + [True, False], + ) + def test_phase_vocoder(self, rate, test_pseudo_complex): + hop_length = 256 + num_freq = 1025 + num_frames = 400 + torch.random.manual_seed(42) + + # Due to cummulative sum, numerical error in using torch.float32 will + # result in bottom right values of the stretched sectrogram to not + # match with librosa. + spec = torch.randn(num_freq, num_frames, device=self.device, dtype=torch.complex128) + phase_advance = torch.linspace( + 0, + np.pi * hop_length, + num_freq, + device=self.device, + dtype=torch.float64)[..., None] + + stretched = F.phase_vocoder( + torch.view_as_real(spec) if test_pseudo_complex else spec, + rate=rate, phase_advance=phase_advance) + + expected_stretched = librosa.phase_vocoder( + spec.cpu().numpy(), + rate=rate, + hop_length=hop_length) + + self.assertEqual( + torch.view_as_complex(stretched) if test_pseudo_complex else stretched, + torch.from_numpy(expected_stretched)) diff --git a/test/torchaudio_unittest/functional/sox_compatibility_test.py b/test/torchaudio_unittest/functional/sox_compatibility_test.py new file mode 100644 index 00000000..fe9744f2 --- /dev/null +++ b/test/torchaudio_unittest/functional/sox_compatibility_test.py @@ -0,0 +1,299 @@ +import torch +import torchaudio.functional as F + +from torchaudio_unittest.common_utils import ( + skipIfNoSox, + skipIfNoExec, + TempDirMixin, + TorchaudioTestCase, + get_asset_path, + sox_utils, + load_wav, + save_wav, + get_whitenoise, +) + + +@skipIfNoSox +@skipIfNoExec('sox') +class TestFunctionalFiltering(TempDirMixin, TorchaudioTestCase): + def run_sox_effect(self, input_file, effect): + output_file = self.get_temp_path('expected.wav') + sox_utils.run_sox_effect(input_file, output_file, [str(e) for e in effect]) + return load_wav(output_file) + + def assert_sox_effect(self, result, input_path, effects, atol=1e-04, rtol=1e-5): + expected, _ = self.run_sox_effect(input_path, effects) + self.assertEqual(result, expected, atol=atol, rtol=rtol) + + def get_whitenoise(self, sample_rate=8000): + noise = get_whitenoise( + sample_rate=sample_rate, duration=3, scale_factor=0.9, + ) + path = self.get_temp_path("whitenoise.wav") + save_wav(path, noise, sample_rate) + return noise, path + + def test_gain(self): + path = get_asset_path('steam-train-whistle-daniel_simon.wav') + data, _ = load_wav(path) + result = F.gain(data, 3) + self.assert_sox_effect(result, path, ['gain', 3]) + + def test_dither(self): + path = get_asset_path('steam-train-whistle-daniel_simon.wav') + data, _ = load_wav(path) + result = F.dither(data) + self.assert_sox_effect(result, path, ['dither']) + + def test_dither_noise(self): + path = get_asset_path('steam-train-whistle-daniel_simon.wav') + data, _ = load_wav(path) + result = F.dither(data, noise_shaping=True) + self.assert_sox_effect(result, path, ['dither', '-s'], atol=1.5e-4) + + def test_lowpass(self): + cutoff_freq = 3000 + sample_rate = 8000 + + data, path = self.get_whitenoise(sample_rate) + result = F.lowpass_biquad(data, sample_rate, cutoff_freq) + self.assert_sox_effect(result, path, ['lowpass', cutoff_freq], atol=1.5e-4) + + def test_highpass(self): + cutoff_freq = 2000 + sample_rate = 8000 + + data, path = self.get_whitenoise(sample_rate) + result = F.highpass_biquad(data, sample_rate, cutoff_freq) + self.assert_sox_effect(result, path, ['highpass', cutoff_freq], atol=1.5e-4) + + def test_allpass(self): + central_freq = 1000 + q = 0.707 + sample_rate = 8000 + + data, path = self.get_whitenoise(sample_rate) + result = F.allpass_biquad(data, sample_rate, central_freq, q) + self.assert_sox_effect(result, path, ['allpass', central_freq, f'{q}q']) + + def test_bandpass_with_csg(self): + central_freq = 1000 + q = 0.707 + const_skirt_gain = True + sample_rate = 8000 + + data, path = self.get_whitenoise(sample_rate) + result = F.bandpass_biquad(data, sample_rate, central_freq, q, const_skirt_gain) + self.assert_sox_effect(result, path, ['bandpass', '-c', central_freq, f'{q}q']) + + def test_bandpass_without_csg(self): + central_freq = 1000 + q = 0.707 + const_skirt_gain = False + sample_rate = 8000 + + data, path = self.get_whitenoise(sample_rate) + result = F.bandpass_biquad(data, sample_rate, central_freq, q, const_skirt_gain) + self.assert_sox_effect(result, path, ['bandpass', central_freq, f'{q}q']) + + def test_bandreject(self): + central_freq = 1000 + q = 0.707 + sample_rate = 8000 + + data, path = self.get_whitenoise(sample_rate) + result = F.bandreject_biquad(data, sample_rate, central_freq, q) + self.assert_sox_effect(result, path, ['bandreject', central_freq, f'{q}q']) + + def test_band_with_noise(self): + central_freq = 1000 + q = 0.707 + noise = True + sample_rate = 8000 + + data, path = self.get_whitenoise(sample_rate) + result = F.band_biquad(data, sample_rate, central_freq, q, noise) + self.assert_sox_effect(result, path, ['band', '-n', central_freq, f'{q}q']) + + def test_band_without_noise(self): + central_freq = 1000 + q = 0.707 + noise = False + sample_rate = 8000 + + data, path = self.get_whitenoise(sample_rate) + result = F.band_biquad(data, sample_rate, central_freq, q, noise) + self.assert_sox_effect(result, path, ['band', central_freq, f'{q}q']) + + def test_treble(self): + central_freq = 1000 + q = 0.707 + gain = 40 + sample_rate = 8000 + + data, path = self.get_whitenoise(sample_rate) + result = F.treble_biquad(data, sample_rate, gain, central_freq, q) + self.assert_sox_effect(result, path, ['treble', gain, central_freq, f'{q}q']) + + def test_bass(self): + central_freq = 1000 + q = 0.707 + gain = 40 + sample_rate = 8000 + + data, path = self.get_whitenoise(sample_rate) + result = F.bass_biquad(data, sample_rate, gain, central_freq, q) + self.assert_sox_effect(result, path, ['bass', gain, central_freq, f'{q}q'], atol=1.5e-4) + + def test_deemph(self): + sample_rate = 44100 + data, path = self.get_whitenoise(sample_rate) + result = F.deemph_biquad(data, sample_rate) + self.assert_sox_effect(result, path, ['deemph']) + + def test_riaa(self): + sample_rate = 44100 + data, path = self.get_whitenoise(sample_rate) + result = F.riaa_biquad(data, sample_rate) + self.assert_sox_effect(result, path, ['riaa']) + + def test_contrast(self): + enhancement_amount = 80. + + data, path = self.get_whitenoise() + result = F.contrast(data, enhancement_amount) + self.assert_sox_effect(result, path, ['contrast', enhancement_amount]) + + def test_dcshift_with_limiter(self): + shift = 0.5 + limiter_gain = 0.05 + + data, path = self.get_whitenoise() + result = F.dcshift(data, shift, limiter_gain) + self.assert_sox_effect(result, path, ['dcshift', shift, limiter_gain]) + + def test_dcshift_without_limiter(self): + shift = 0.6 + + data, path = self.get_whitenoise() + result = F.dcshift(data, shift) + self.assert_sox_effect(result, path, ['dcshift', shift]) + + def test_overdrive(self): + gain = 30 + colour = 40 + + data, path = self.get_whitenoise() + result = F.overdrive(data, gain, colour) + self.assert_sox_effect(result, path, ['overdrive', gain, colour]) + + def test_phaser_sine(self): + gain_in = 0.5 + gain_out = 0.8 + delay_ms = 2.0 + decay = 0.4 + speed = 0.5 + sample_rate = 8000 + + data, path = self.get_whitenoise(sample_rate) + result = F.phaser(data, sample_rate, gain_in, gain_out, delay_ms, decay, speed, sinusoidal=True) + self.assert_sox_effect(result, path, ['phaser', gain_in, gain_out, delay_ms, decay, speed, '-s']) + + def test_phaser_triangle(self): + gain_in = 0.5 + gain_out = 0.8 + delay_ms = 2.0 + decay = 0.4 + speed = 0.5 + sample_rate = 8000 + + data, path = self.get_whitenoise(sample_rate) + result = F.phaser(data, sample_rate, gain_in, gain_out, delay_ms, decay, speed, sinusoidal=False) + self.assert_sox_effect(result, path, ['phaser', gain_in, gain_out, delay_ms, decay, speed, '-t']) + + def test_flanger_triangle_linear(self): + delay = 0.6 + depth = 0.87 + regen = 3.0 + width = 0.9 + speed = 0.5 + phase = 30 + sample_rate = 8000 + + data, path = self.get_whitenoise(sample_rate) + result = F.flanger( + data, sample_rate, delay, depth, regen, width, speed, phase, + modulation='triangular', interpolation='linear') + self.assert_sox_effect( + result, path, ['flanger', delay, depth, regen, width, speed, 'triangle', phase, 'linear']) + + def test_flanger_triangle_quad(self): + delay = 0.8 + depth = 0.88 + regen = 3.0 + width = 0.4 + speed = 0.5 + phase = 40 + sample_rate = 8000 + + data, path = self.get_whitenoise(sample_rate) + result = F.flanger( + data, sample_rate, delay, depth, regen, width, speed, phase, + modulation='triangular', interpolation='quadratic') + self.assert_sox_effect( + result, path, ['flanger', delay, depth, regen, width, speed, 'triangle', phase, 'quadratic']) + + def test_flanger_sine_linear(self): + delay = 0.8 + depth = 0.88 + regen = 3.0 + width = 0.23 + speed = 1.3 + phase = 60 + sample_rate = 8000 + + data, path = self.get_whitenoise(sample_rate) + result = F.flanger( + data, sample_rate, delay, depth, regen, width, speed, phase, + modulation='sinusoidal', interpolation='linear') + self.assert_sox_effect( + result, path, ['flanger', delay, depth, regen, width, speed, 'sine', phase, 'linear']) + + def test_flanger_sine_quad(self): + delay = 0.9 + depth = 0.9 + regen = 4.0 + width = 0.23 + speed = 1.3 + phase = 25 + sample_rate = 8000 + + data, path = self.get_whitenoise(sample_rate) + result = F.flanger( + data, sample_rate, delay, depth, regen, width, speed, phase, + modulation='sinusoidal', interpolation='quadratic') + self.assert_sox_effect( + result, path, ['flanger', delay, depth, regen, width, speed, 'sine', phase, 'quadratic']) + + def test_equalizer(self): + center_freq = 300 + q = 0.707 + gain = 1 + sample_rate = 8000 + + data, path = self.get_whitenoise(sample_rate) + result = F.equalizer_biquad(data, sample_rate, center_freq, gain, q) + self.assert_sox_effect(result, path, ['equalizer', center_freq, q, gain]) + + def test_perf_biquad_filtering(self): + b0 = 0.4 + b1 = 0.2 + b2 = 0.9 + a0 = 0.7 + a1 = 0.2 + a2 = 0.6 + + data, path = self.get_whitenoise() + result = F.lfilter(data, torch.tensor([a0, a1, a2]), torch.tensor([b0, b1, b2])) + self.assert_sox_effect(result, path, ['biquad', b0, b1, b2, a0, a1, a2]) diff --git a/test/torchaudio_unittest/functional/torchscript_consistency_cpu_test.py b/test/torchaudio_unittest/functional/torchscript_consistency_cpu_test.py new file mode 100644 index 00000000..2971067d --- /dev/null +++ b/test/torchaudio_unittest/functional/torchscript_consistency_cpu_test.py @@ -0,0 +1,14 @@ +import torch + +from torchaudio_unittest.common_utils import PytorchTestCase +from .torchscript_consistency_impl import Functional, FunctionalFloat32Only + + +class TestFunctionalFloat32(Functional, FunctionalFloat32Only, PytorchTestCase): + dtype = torch.float32 + device = torch.device('cpu') + + +class TestFunctionalFloat64(Functional, PytorchTestCase): + dtype = torch.float64 + device = torch.device('cpu') diff --git a/test/torchaudio_unittest/functional/torchscript_consistency_cuda_test.py b/test/torchaudio_unittest/functional/torchscript_consistency_cuda_test.py new file mode 100644 index 00000000..30e2b396 --- /dev/null +++ b/test/torchaudio_unittest/functional/torchscript_consistency_cuda_test.py @@ -0,0 +1,16 @@ +import torch + +from torchaudio_unittest.common_utils import skipIfNoCuda, PytorchTestCase +from .torchscript_consistency_impl import Functional, FunctionalFloat32Only + + +@skipIfNoCuda +class TestFunctionalFloat32(Functional, FunctionalFloat32Only, PytorchTestCase): + dtype = torch.float32 + device = torch.device('cuda') + + +@skipIfNoCuda +class TestFunctionalFloat64(Functional, PytorchTestCase): + dtype = torch.float64 + device = torch.device('cuda') diff --git a/test/torchaudio_unittest/functional/torchscript_consistency_impl.py b/test/torchaudio_unittest/functional/torchscript_consistency_impl.py new file mode 100644 index 00000000..4097b20e --- /dev/null +++ b/test/torchaudio_unittest/functional/torchscript_consistency_impl.py @@ -0,0 +1,718 @@ +"""Test suites for jit-ability and its numerical compatibility""" +import unittest + +import torch +import torchaudio.functional as F +from parameterized import parameterized + +from torchaudio_unittest import common_utils +from torchaudio_unittest.common_utils import ( + TempDirMixin, + TestBaseMixin, + skipIfRocm, + torch_script, +) + + +class Functional(TempDirMixin, TestBaseMixin): + """Implements test for `functional` module that are performed for different devices""" + def _assert_consistency(self, func, tensor, shape_only=False): + tensor = tensor.to(device=self.device, dtype=self.dtype) + ts_func = torch_script(func) + + torch.random.manual_seed(40) + output = func(tensor) + + torch.random.manual_seed(40) + ts_output = ts_func(tensor) + + if shape_only: + ts_output = ts_output.shape + output = output.shape + self.assertEqual(ts_output, output) + + def _assert_consistency_complex(self, func, tensor, test_pseudo_complex=False): + assert tensor.is_complex() + tensor = tensor.to(device=self.device, dtype=self.complex_dtype) + ts_func = torch_script(func) + + if test_pseudo_complex: + tensor = torch.view_as_real(tensor) + + torch.random.manual_seed(40) + output = func(tensor) + + torch.random.manual_seed(40) + ts_output = ts_func(tensor) + + self.assertEqual(ts_output, output) + + def test_spectrogram_complex(self): + def func(tensor): + n_fft = 400 + ws = 400 + hop = 200 + pad = 0 + window = torch.hann_window(ws, device=tensor.device, dtype=tensor.dtype) + power = None + normalize = False + return F.spectrogram(tensor, pad, window, n_fft, hop, ws, power, normalize) + + tensor = common_utils.get_whitenoise() + self._assert_consistency(func, tensor) + + def test_spectrogram_real(self): + def func(tensor): + n_fft = 400 + ws = 400 + hop = 200 + pad = 0 + window = torch.hann_window(ws, device=tensor.device, dtype=tensor.dtype) + power = 2. + normalize = False + return F.spectrogram(tensor, pad, window, n_fft, hop, ws, power, normalize, return_complex=False) + + tensor = common_utils.get_whitenoise() + self._assert_consistency(func, tensor) + + def test_inverse_spectrogram_complex(self): + def func(tensor): + length = 400 + n_fft = 400 + hop = 200 + ws = 400 + pad = 0 + window = torch.hann_window(ws, device=tensor.device, dtype=torch.float64) + normalize = False + return F.inverse_spectrogram(tensor, length, pad, window, n_fft, hop, ws, normalize) + + waveform = common_utils.get_whitenoise(sample_rate=8000, duration=0.05) + tensor = common_utils.get_spectrogram(waveform, n_fft=400, hop_length=200) + self._assert_consistency_complex(func, tensor) + + def test_inverse_spectrogram_real(self): + def func(tensor): + length = 400 + n_fft = 400 + hop = 200 + ws = 400 + pad = 0 + window = torch.hann_window(ws, device=tensor.device, dtype=tensor.dtype) + normalize = False + return F.inverse_spectrogram(tensor, length, pad, window, n_fft, hop, ws, normalize) + + waveform = common_utils.get_whitenoise(sample_rate=8000, duration=0.05) + tensor = common_utils.get_spectrogram(waveform, n_fft=400, hop_length=200) + tensor = torch.view_as_real(tensor) + self._assert_consistency(func, tensor) + + @skipIfRocm + def test_griffinlim(self): + def func(tensor): + n_fft = 400 + ws = 400 + hop = 200 + window = torch.hann_window(ws, device=tensor.device, dtype=tensor.dtype) + power = 2. + momentum = 0.99 + n_iter = 32 + length = 1000 + rand_int = False + return F.griffinlim(tensor, window, n_fft, hop, ws, power, n_iter, momentum, length, rand_int) + + tensor = torch.rand((1, 201, 6)) + self._assert_consistency(func, tensor) + + def test_compute_deltas(self): + def func(tensor): + win_length = 2 * 7 + 1 + return F.compute_deltas(tensor, win_length=win_length) + + channel = 13 + n_mfcc = channel * 3 + time = 1021 + tensor = torch.randn(channel, n_mfcc, time) + self._assert_consistency(func, tensor) + + def test_detect_pitch_frequency(self): + waveform = common_utils.get_sinusoid(sample_rate=44100) + + def func(tensor): + sample_rate = 44100 + return F.detect_pitch_frequency(tensor, sample_rate) + + self._assert_consistency(func, waveform) + + def test_melscale_fbanks(self): + if self.device != torch.device('cpu'): + raise unittest.SkipTest('No need to perform test on device other than CPU') + + def func(_): + n_stft = 100 + f_min = 0.0 + f_max = 20.0 + n_mels = 10 + sample_rate = 16000 + norm = "slaney" + return F.melscale_fbanks(n_stft, f_min, f_max, n_mels, sample_rate, norm) + + dummy = torch.zeros(1, 1) + self._assert_consistency(func, dummy) + + def test_linear_fbanks(self): + if self.device != torch.device('cpu'): + raise unittest.SkipTest('No need to perform test on device other than CPU') + + def func(_): + n_stft = 100 + f_min = 0.0 + f_max = 20.0 + n_filter = 10 + sample_rate = 16000 + return F.linear_fbanks(n_stft, f_min, f_max, n_filter, sample_rate) + + dummy = torch.zeros(1, 1) + self._assert_consistency(func, dummy) + + def test_amplitude_to_DB(self): + def func(tensor): + multiplier = 10.0 + amin = 1e-10 + db_multiplier = 0.0 + top_db = 80.0 + return F.amplitude_to_DB(tensor, multiplier, amin, db_multiplier, top_db) + + tensor = torch.rand((6, 201)) + self._assert_consistency(func, tensor) + + def test_DB_to_amplitude(self): + def func(tensor): + ref = 1. + power = 1. + return F.DB_to_amplitude(tensor, ref, power) + + tensor = torch.rand((1, 100)) + self._assert_consistency(func, tensor) + + def test_create_dct(self): + if self.device != torch.device('cpu'): + raise unittest.SkipTest('No need to perform test on device other than CPU') + + def func(_): + n_mfcc = 40 + n_mels = 128 + norm = "ortho" + return F.create_dct(n_mfcc, n_mels, norm) + + dummy = torch.zeros(1, 1) + self._assert_consistency(func, dummy) + + def test_mu_law_encoding(self): + def func(tensor): + qc = 256 + return F.mu_law_encoding(tensor, qc) + + waveform = common_utils.get_whitenoise() + self._assert_consistency(func, waveform) + + def test_mu_law_decoding(self): + def func(tensor): + qc = 256 + return F.mu_law_decoding(tensor, qc) + + tensor = torch.rand((1, 10)) + self._assert_consistency(func, tensor) + + def test_complex_norm(self): + def func(tensor): + power = 2. + return F.complex_norm(tensor, power) + + tensor = torch.randn(1, 2, 1025, 400, 2) + self._assert_consistency(func, tensor) + + def test_mask_along_axis(self): + def func(tensor): + mask_param = 100 + mask_value = 30. + axis = 2 + return F.mask_along_axis(tensor, mask_param, mask_value, axis) + + tensor = torch.randn(2, 1025, 400) + self._assert_consistency(func, tensor) + + def test_mask_along_axis_iid(self): + def func(tensor): + mask_param = 100 + mask_value = 30. + axis = 2 + return F.mask_along_axis_iid(tensor, mask_param, mask_value, axis) + + tensor = torch.randn(4, 2, 1025, 400) + self._assert_consistency(func, tensor) + + def test_gain(self): + def func(tensor): + gainDB = 2.0 + return F.gain(tensor, gainDB) + + tensor = torch.rand((1, 1000)) + self._assert_consistency(func, tensor) + + def test_dither_TPDF(self): + def func(tensor): + return F.dither(tensor, 'TPDF') + + tensor = common_utils.get_whitenoise(n_channels=2) + self._assert_consistency(func, tensor, shape_only=True) + + def test_dither_RPDF(self): + def func(tensor): + return F.dither(tensor, 'RPDF') + + tensor = common_utils.get_whitenoise(n_channels=2) + self._assert_consistency(func, tensor, shape_only=True) + + def test_dither_GPDF(self): + def func(tensor): + return F.dither(tensor, 'GPDF') + + tensor = common_utils.get_whitenoise(n_channels=2) + self._assert_consistency(func, tensor, shape_only=True) + + def test_dither_noise_shaping(self): + def func(tensor): + return F.dither(tensor, noise_shaping=True) + + tensor = common_utils.get_whitenoise(n_channels=2) + self._assert_consistency(func, tensor) + + def test_lfilter(self): + if self.dtype == torch.float64: + raise unittest.SkipTest("This test is known to fail for float64") + + waveform = common_utils.get_whitenoise() + + def func(tensor): + # Design an IIR lowpass filter using scipy.signal filter design + # https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.iirdesign.html#scipy.signal.iirdesign + # + # Example + # >>> from scipy.signal import iirdesign + # >>> b, a = iirdesign(0.2, 0.3, 1, 60) + b_coeffs = torch.tensor( + [ + 0.00299893, + -0.0051152, + 0.00841964, + -0.00747802, + 0.00841964, + -0.0051152, + 0.00299893, + ], + device=tensor.device, + dtype=tensor.dtype, + ) + a_coeffs = torch.tensor( + [ + 1.0, + -4.8155751, + 10.2217618, + -12.14481273, + 8.49018171, + -3.3066882, + 0.56088705, + ], + device=tensor.device, + dtype=tensor.dtype, + ) + return F.lfilter(tensor, a_coeffs, b_coeffs) + + self._assert_consistency(func, waveform) + + def test_filtfilt(self): + def func(tensor): + torch.manual_seed(296) + b_coeffs = torch.rand(4, device=tensor.device, dtype=tensor.dtype) + a_coeffs = torch.rand(4, device=tensor.device, dtype=tensor.dtype) + return F.filtfilt(tensor, a_coeffs, b_coeffs) + + waveform = common_utils.get_whitenoise(sample_rate=8000) + self._assert_consistency(func, waveform) + + def test_lowpass(self): + if self.dtype == torch.float64: + raise unittest.SkipTest("This test is known to fail for float64") + + waveform = common_utils.get_whitenoise(sample_rate=44100) + + def func(tensor): + sample_rate = 44100 + cutoff_freq = 3000. + return F.lowpass_biquad(tensor, sample_rate, cutoff_freq) + + self._assert_consistency(func, waveform) + + def test_highpass(self): + if self.dtype == torch.float64: + raise unittest.SkipTest("This test is known to fail for float64") + + waveform = common_utils.get_whitenoise(sample_rate=44100) + + def func(tensor): + sample_rate = 44100 + cutoff_freq = 2000. + return F.highpass_biquad(tensor, sample_rate, cutoff_freq) + + self._assert_consistency(func, waveform) + + def test_allpass(self): + if self.dtype == torch.float64: + raise unittest.SkipTest("This test is known to fail for float64") + + waveform = common_utils.get_whitenoise(sample_rate=44100) + + def func(tensor): + sample_rate = 44100 + central_freq = 1000. + q = 0.707 + return F.allpass_biquad(tensor, sample_rate, central_freq, q) + + self._assert_consistency(func, waveform) + + def test_bandpass_with_csg(self): + if self.dtype == torch.float64: + raise unittest.SkipTest("This test is known to fail for float64") + + waveform = common_utils.get_whitenoise(sample_rate=44100) + + def func(tensor): + sample_rate = 44100 + central_freq = 1000. + q = 0.707 + const_skirt_gain = True + return F.bandpass_biquad(tensor, sample_rate, central_freq, q, const_skirt_gain) + + self._assert_consistency(func, waveform) + + def test_bandpass_without_csg(self): + if self.dtype == torch.float64: + raise unittest.SkipTest("This test is known to fail for float64") + + waveform = common_utils.get_whitenoise(sample_rate=44100) + + def func(tensor): + sample_rate = 44100 + central_freq = 1000. + q = 0.707 + const_skirt_gain = True + return F.bandpass_biquad(tensor, sample_rate, central_freq, q, const_skirt_gain) + + self._assert_consistency(func, waveform) + + def test_bandreject(self): + if self.dtype == torch.float64: + raise unittest.SkipTest("This test is known to fail for float64") + + waveform = common_utils.get_whitenoise(sample_rate=44100) + + def func(tensor): + sample_rate = 44100 + central_freq = 1000. + q = 0.707 + return F.bandreject_biquad(tensor, sample_rate, central_freq, q) + + self._assert_consistency(func, waveform) + + def test_band_with_noise(self): + if self.dtype == torch.float64: + raise unittest.SkipTest("This test is known to fail for float64") + + waveform = common_utils.get_whitenoise(sample_rate=44100) + + def func(tensor): + sample_rate = 44100 + central_freq = 1000. + q = 0.707 + noise = True + return F.band_biquad(tensor, sample_rate, central_freq, q, noise) + + self._assert_consistency(func, waveform) + + def test_band_without_noise(self): + if self.dtype == torch.float64: + raise unittest.SkipTest("This test is known to fail for float64") + + waveform = common_utils.get_whitenoise(sample_rate=44100) + + def func(tensor): + sample_rate = 44100 + central_freq = 1000. + q = 0.707 + noise = False + return F.band_biquad(tensor, sample_rate, central_freq, q, noise) + + self._assert_consistency(func, waveform) + + def test_treble(self): + if self.dtype == torch.float64: + raise unittest.SkipTest("This test is known to fail for float64") + + waveform = common_utils.get_whitenoise(sample_rate=44100) + + def func(tensor): + sample_rate = 44100 + gain = 40. + central_freq = 1000. + q = 0.707 + return F.treble_biquad(tensor, sample_rate, gain, central_freq, q) + + self._assert_consistency(func, waveform) + + def test_bass(self): + if self.dtype == torch.float64: + raise unittest.SkipTest("This test is known to fail for float64") + + waveform = common_utils.get_whitenoise(sample_rate=44100) + + def func(tensor): + sample_rate = 44100 + gain = 40. + central_freq = 1000. + q = 0.707 + return F.bass_biquad(tensor, sample_rate, gain, central_freq, q) + + self._assert_consistency(func, waveform) + + def test_deemph(self): + if self.dtype == torch.float64: + raise unittest.SkipTest("This test is known to fail for float64") + + waveform = common_utils.get_whitenoise(sample_rate=44100) + + def func(tensor): + sample_rate = 44100 + return F.deemph_biquad(tensor, sample_rate) + + self._assert_consistency(func, waveform) + + def test_riaa(self): + if self.dtype == torch.float64: + raise unittest.SkipTest("This test is known to fail for float64") + + waveform = common_utils.get_whitenoise(sample_rate=44100) + + def func(tensor): + sample_rate = 44100 + return F.riaa_biquad(tensor, sample_rate) + + self._assert_consistency(func, waveform) + + def test_equalizer(self): + if self.dtype == torch.float64: + raise unittest.SkipTest("This test is known to fail for float64") + + waveform = common_utils.get_whitenoise(sample_rate=44100) + + def func(tensor): + sample_rate = 44100 + center_freq = 300. + gain = 1. + q = 0.707 + return F.equalizer_biquad(tensor, sample_rate, center_freq, gain, q) + + self._assert_consistency(func, waveform) + + def test_perf_biquad_filtering(self): + if self.dtype == torch.float64: + raise unittest.SkipTest("This test is known to fail for float64") + + waveform = common_utils.get_whitenoise() + + def func(tensor): + a = torch.tensor([0.7, 0.2, 0.6], device=tensor.device, dtype=tensor.dtype) + b = torch.tensor([0.4, 0.2, 0.9], device=tensor.device, dtype=tensor.dtype) + return F.lfilter(tensor, a, b) + + self._assert_consistency(func, waveform) + + def test_sliding_window_cmn(self): + def func(tensor): + cmn_window = 600 + min_cmn_window = 100 + center = False + norm_vars = False + a = torch.tensor( + [ + [ + -1.915875792503357, + 1.147700309753418 + ], + [ + 1.8242558240890503, + 1.3869990110397339 + ] + ], + device=tensor.device, + dtype=tensor.dtype + ) + return F.sliding_window_cmn(a, cmn_window, min_cmn_window, center, norm_vars) + b = torch.tensor( + [ + [ + -1.8701, + -0.1196 + ], + [ + 1.8701, + 0.1196 + ] + ] + ) + self._assert_consistency(func, b) + + def test_contrast(self): + waveform = common_utils.get_whitenoise() + + def func(tensor): + enhancement_amount = 80. + return F.contrast(tensor, enhancement_amount) + + self._assert_consistency(func, waveform) + + def test_dcshift(self): + waveform = common_utils.get_whitenoise() + + def func(tensor): + shift = 0.5 + limiter_gain = 0.05 + return F.dcshift(tensor, shift, limiter_gain) + + self._assert_consistency(func, waveform) + + def test_overdrive(self): + waveform = common_utils.get_whitenoise() + + def func(tensor): + gain = 30. + colour = 50. + return F.overdrive(tensor, gain, colour) + + self._assert_consistency(func, waveform) + + def test_phaser(self): + waveform = common_utils.get_whitenoise(sample_rate=44100) + + def func(tensor): + gain_in = 0.5 + gain_out = 0.8 + delay_ms = 2.0 + decay = 0.4 + speed = 0.5 + sample_rate = 44100 + return F.phaser(tensor, sample_rate, gain_in, gain_out, delay_ms, decay, speed, sinusoidal=True) + + self._assert_consistency(func, waveform) + + def test_flanger(self): + torch.random.manual_seed(40) + waveform = torch.rand(2, 100) - 0.5 + + def func(tensor): + delay = 0.8 + depth = 0.88 + regen = 3.0 + width = 0.23 + speed = 1.3 + phase = 60. + sample_rate = 44100 + return F.flanger(tensor, sample_rate, delay, depth, regen, width, speed, + phase, modulation='sinusoidal', interpolation='linear') + + self._assert_consistency(func, waveform) + + def test_spectral_centroid(self): + + def func(tensor): + sample_rate = 44100 + n_fft = 400 + ws = 400 + hop = 200 + pad = 0 + window = torch.hann_window(ws, device=tensor.device, dtype=tensor.dtype) + return F.spectral_centroid(tensor, sample_rate, pad, window, n_fft, hop, ws) + + tensor = common_utils.get_whitenoise(sample_rate=44100) + self._assert_consistency(func, tensor) + + @common_utils.skipIfNoKaldi + def test_compute_kaldi_pitch(self): + if self.dtype != torch.float32 or self.device != torch.device('cpu'): + raise unittest.SkipTest("Only float32, cpu is supported.") + + def func(tensor): + sample_rate: float = 44100. + return F.compute_kaldi_pitch(tensor, sample_rate) + + tensor = common_utils.get_whitenoise(sample_rate=44100) + self._assert_consistency(func, tensor) + + def test_resample_sinc(self): + def func(tensor): + sr1, sr2 = 16000, 8000 + return F.resample(tensor, sr1, sr2, resampling_method="sinc_interpolation") + + tensor = common_utils.get_whitenoise(sample_rate=16000) + self._assert_consistency(func, tensor) + + def test_resample_kaiser(self): + def func(tensor): + sr1, sr2 = 16000, 8000 + return F.resample(tensor, sr1, sr2, resampling_method="kaiser_window") + + def func_beta(tensor): + sr1, sr2 = 16000, 8000 + beta = 6. + return F.resample(tensor, sr1, sr2, resampling_method="kaiser_window", beta=beta) + + tensor = common_utils.get_whitenoise(sample_rate=16000) + self._assert_consistency(func, tensor) + self._assert_consistency(func_beta, tensor) + + @parameterized.expand([(True, ), (False, )]) + def test_phase_vocoder(self, test_paseudo_complex): + def func(tensor): + is_complex = tensor.is_complex() + + n_freq = tensor.size(-2 if is_complex else -3) + rate = 0.5 + hop_length = 256 + phase_advance = torch.linspace( + 0, + 3.14 * hop_length, + n_freq, + dtype=(torch.real(tensor) if is_complex else tensor).dtype, + device=tensor.device, + )[..., None] + return F.phase_vocoder(tensor, rate, phase_advance) + + tensor = torch.view_as_complex(torch.randn(2, 1025, 400, 2)) + self._assert_consistency_complex(func, tensor, test_paseudo_complex) + + +class FunctionalFloat32Only(TestBaseMixin): + def test_rnnt_loss(self): + def func(tensor): + targets = torch.tensor([[1, 2]], device=tensor.device, dtype=torch.int32) + logit_lengths = torch.tensor([2], device=tensor.device, dtype=torch.int32) + target_lengths = torch.tensor([2], device=tensor.device, dtype=torch.int32) + return F.rnnt_loss(tensor, targets, logit_lengths, target_lengths) + + logits = torch.tensor([[[[0.1, 0.6, 0.1, 0.1, 0.1], + [0.1, 0.1, 0.6, 0.1, 0.1], + [0.1, 0.1, 0.2, 0.8, 0.1]], + [[0.1, 0.6, 0.1, 0.1, 0.1], + [0.1, 0.1, 0.2, 0.1, 0.1], + [0.7, 0.1, 0.2, 0.1, 0.1]]]]) + tensor = logits.to(device=self.device, dtype=torch.float32) + self._assert_consistency(func, tensor) diff --git a/test/torchaudio_unittest/kaldi_io_test.py b/test/torchaudio_unittest/kaldi_io_test.py new file mode 100644 index 00000000..4ceeeebb --- /dev/null +++ b/test/torchaudio_unittest/kaldi_io_test.py @@ -0,0 +1,33 @@ +import torch +import torchaudio.kaldi_io as kio + +from torchaudio_unittest import common_utils + + +class Test_KaldiIO(common_utils.TorchaudioTestCase): + data1 = [[1, 2, 3], [11, 12, 13], [21, 22, 23]] + data2 = [[31, 32, 33], [41, 42, 43], [51, 52, 53]] + + def _test_helper(self, file_name, expected_data, fn, expected_dtype): + """ Takes a file_name to the input data and a function fn to extract the + data. It compares the extracted data to the expected_data. The expected_dtype + will be used to check that the extracted data is of the right type. + """ + test_filepath = common_utils.get_asset_path(file_name) + expected_output = {'key' + str(idx + 1): torch.tensor(val, dtype=expected_dtype) + for idx, val in enumerate(expected_data)} + + for key, vec in fn(test_filepath): + self.assertTrue(key in expected_output) + self.assertTrue(isinstance(vec, torch.Tensor)) + self.assertEqual(vec.dtype, expected_dtype) + self.assertTrue(torch.all(torch.eq(vec, expected_output[key]))) + + def test_read_vec_int_ark(self): + self._test_helper("vec_int.ark", self.data1, kio.read_vec_int_ark, torch.int32) + + def test_read_vec_flt_ark(self): + self._test_helper("vec_flt.ark", self.data1, kio.read_vec_flt_ark, torch.float32) + + def test_read_mat_ark(self): + self._test_helper("mat.ark", [self.data1, self.data2], kio.read_mat_ark, torch.float32) diff --git a/test/torchaudio_unittest/models/__init__.py b/test/torchaudio_unittest/models/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/torchaudio_unittest/models/models_test.py b/test/torchaudio_unittest/models/models_test.py new file mode 100644 index 00000000..18f3085e --- /dev/null +++ b/test/torchaudio_unittest/models/models_test.py @@ -0,0 +1,248 @@ +import itertools +from collections import namedtuple + +import torch +from parameterized import parameterized +from torchaudio.models import ConvTasNet, DeepSpeech, Wav2Letter, WaveRNN +from torchaudio.models.wavernn import MelResNet, UpsampleNetwork +from torchaudio_unittest import common_utils +from torchaudio_unittest.common_utils import torch_script + + +class TestWav2Letter(common_utils.TorchaudioTestCase): + + def test_waveform(self): + batch_size = 2 + num_features = 1 + num_classes = 40 + input_length = 320 + + model = Wav2Letter(num_classes=num_classes, num_features=num_features) + + x = torch.rand(batch_size, num_features, input_length) + out = model(x) + + assert out.size() == (batch_size, num_classes, 2) + + def test_mfcc(self): + batch_size = 2 + num_features = 13 + num_classes = 40 + input_length = 2 + + model = Wav2Letter(num_classes=num_classes, input_type="mfcc", num_features=num_features) + + x = torch.rand(batch_size, num_features, input_length) + out = model(x) + + assert out.size() == (batch_size, num_classes, 2) + + +class TestMelResNet(common_utils.TorchaudioTestCase): + + def test_waveform(self): + """Validate the output dimensions of a MelResNet block. + """ + + n_batch = 2 + n_time = 200 + n_freq = 100 + n_output = 128 + n_res_block = 10 + n_hidden = 128 + kernel_size = 5 + + model = MelResNet(n_res_block, n_freq, n_hidden, n_output, kernel_size) + + x = torch.rand(n_batch, n_freq, n_time) + out = model(x) + + assert out.size() == (n_batch, n_output, n_time - kernel_size + 1) + + +class TestUpsampleNetwork(common_utils.TorchaudioTestCase): + + def test_waveform(self): + """Validate the output dimensions of a UpsampleNetwork block. + """ + + upsample_scales = [5, 5, 8] + n_batch = 2 + n_time = 200 + n_freq = 100 + n_output = 256 + n_res_block = 10 + n_hidden = 128 + kernel_size = 5 + + total_scale = 1 + for upsample_scale in upsample_scales: + total_scale *= upsample_scale + + model = UpsampleNetwork(upsample_scales, + n_res_block, + n_freq, + n_hidden, + n_output, + kernel_size) + + x = torch.rand(n_batch, n_freq, n_time) + out1, out2 = model(x) + + assert out1.size() == (n_batch, n_freq, total_scale * (n_time - kernel_size + 1)) + assert out2.size() == (n_batch, n_output, total_scale * (n_time - kernel_size + 1)) + + +class TestWaveRNN(common_utils.TorchaudioTestCase): + + def test_waveform(self): + """Validate the output dimensions of a WaveRNN model. + """ + + upsample_scales = [5, 5, 8] + n_rnn = 512 + n_fc = 512 + n_classes = 512 + hop_length = 200 + n_batch = 2 + n_time = 200 + n_freq = 100 + n_output = 256 + n_res_block = 10 + n_hidden = 128 + kernel_size = 5 + + model = WaveRNN(upsample_scales, n_classes, hop_length, n_res_block, + n_rnn, n_fc, kernel_size, n_freq, n_hidden, n_output) + + x = torch.rand(n_batch, 1, hop_length * (n_time - kernel_size + 1)) + mels = torch.rand(n_batch, 1, n_freq, n_time) + out = model(x, mels) + + assert out.size() == (n_batch, 1, hop_length * (n_time - kernel_size + 1), n_classes) + + def test_infer_waveform(self): + """Validate the output dimensions of a WaveRNN model's infer method. + """ + + upsample_scales = [5, 5, 8] + n_rnn = 128 + n_fc = 128 + n_classes = 128 + hop_length = 200 + n_batch = 2 + n_time = 50 + n_freq = 25 + n_output = 64 + n_res_block = 2 + n_hidden = 32 + kernel_size = 5 + + model = WaveRNN(upsample_scales, n_classes, hop_length, n_res_block, + n_rnn, n_fc, kernel_size, n_freq, n_hidden, n_output) + + x = torch.rand(n_batch, n_freq, n_time) + lengths = torch.tensor([n_time, n_time // 2]) + out, waveform_lengths = model.infer(x, lengths) + + assert out.size() == (n_batch, 1, hop_length * n_time) + assert waveform_lengths[0] == hop_length * n_time + assert waveform_lengths[1] == hop_length * n_time // 2 + + def test_torchscript_infer(self): + """Scripted model outputs the same as eager mode""" + + upsample_scales = [5, 5, 8] + n_rnn = 128 + n_fc = 128 + n_classes = 128 + hop_length = 200 + n_batch = 2 + n_time = 50 + n_freq = 25 + n_output = 64 + n_res_block = 2 + n_hidden = 32 + kernel_size = 5 + + model = WaveRNN(upsample_scales, n_classes, hop_length, n_res_block, + n_rnn, n_fc, kernel_size, n_freq, n_hidden, n_output) + model.eval() + x = torch.rand(n_batch, n_freq, n_time) + torch.random.manual_seed(0) + out_eager = model.infer(x) + torch.random.manual_seed(0) + out_script = torch_script(model).infer(x) + self.assertEqual(out_eager, out_script) + + +_ConvTasNetParams = namedtuple( + '_ConvTasNetParams', + [ + 'enc_num_feats', + 'enc_kernel_size', + 'msk_num_feats', + 'msk_num_hidden_feats', + 'msk_kernel_size', + 'msk_num_layers', + 'msk_num_stacks', + ] +) + + +class TestConvTasNet(common_utils.TorchaudioTestCase): + @parameterized.expand(list(itertools.product( + [2, 3], + [ + _ConvTasNetParams(128, 40, 128, 256, 3, 7, 2), + _ConvTasNetParams(256, 40, 128, 256, 3, 7, 2), + _ConvTasNetParams(512, 40, 128, 256, 3, 7, 2), + _ConvTasNetParams(512, 40, 128, 256, 3, 7, 2), + _ConvTasNetParams(512, 40, 128, 512, 3, 7, 2), + _ConvTasNetParams(512, 40, 128, 512, 3, 7, 2), + _ConvTasNetParams(512, 40, 256, 256, 3, 7, 2), + _ConvTasNetParams(512, 40, 256, 512, 3, 7, 2), + _ConvTasNetParams(512, 40, 256, 512, 3, 7, 2), + _ConvTasNetParams(512, 40, 128, 512, 3, 6, 4), + _ConvTasNetParams(512, 40, 128, 512, 3, 4, 6), + _ConvTasNetParams(512, 40, 128, 512, 3, 8, 3), + _ConvTasNetParams(512, 32, 128, 512, 3, 8, 3), + _ConvTasNetParams(512, 16, 128, 512, 3, 8, 3), + ], + ))) + def test_paper_configuration(self, num_sources, model_params): + """ConvTasNet model works on the valid configurations in the paper""" + batch_size = 32 + num_frames = 8000 + + model = ConvTasNet( + num_sources=num_sources, + enc_kernel_size=model_params.enc_kernel_size, + enc_num_feats=model_params.enc_num_feats, + msk_kernel_size=model_params.msk_kernel_size, + msk_num_feats=model_params.msk_num_feats, + msk_num_hidden_feats=model_params.msk_num_hidden_feats, + msk_num_layers=model_params.msk_num_layers, + msk_num_stacks=model_params.msk_num_stacks, + ) + tensor = torch.rand(batch_size, 1, num_frames) + output = model(tensor) + + assert output.shape == (batch_size, num_sources, num_frames) + + +class TestDeepSpeech(common_utils.TorchaudioTestCase): + + def test_deepspeech(self): + n_batch = 2 + n_feature = 1 + n_channel = 1 + n_class = 40 + n_time = 320 + + model = DeepSpeech(n_feature=n_feature, n_class=n_class) + + x = torch.rand(n_batch, n_channel, n_time, n_feature) + out = model(x) + + assert out.size() == (n_batch, n_time, n_class) diff --git a/test/torchaudio_unittest/models/tacotron2/__init__.py b/test/torchaudio_unittest/models/tacotron2/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/torchaudio_unittest/models/tacotron2/model_test_cpu_test.py b/test/torchaudio_unittest/models/tacotron2/model_test_cpu_test.py new file mode 100644 index 00000000..612699b6 --- /dev/null +++ b/test/torchaudio_unittest/models/tacotron2/model_test_cpu_test.py @@ -0,0 +1,23 @@ +import torch + +from torchaudio_unittest.common_utils import PytorchTestCase +from .model_test_impl import ( + Tacotron2EncoderTests, + Tacotron2DecoderTests, + Tacotron2Tests, +) + + +class TestTacotron2EncoderFloat32CPU(Tacotron2EncoderTests, PytorchTestCase): + dtype = torch.float32 + device = torch.device("cpu") + + +class TestTacotron2DecoderFloat32CPU(Tacotron2DecoderTests, PytorchTestCase): + dtype = torch.float32 + device = torch.device("cpu") + + +class TestTacotron2Float32CPU(Tacotron2Tests, PytorchTestCase): + dtype = torch.float32 + device = torch.device("cpu") diff --git a/test/torchaudio_unittest/models/tacotron2/model_test_gpu_test.py b/test/torchaudio_unittest/models/tacotron2/model_test_gpu_test.py new file mode 100644 index 00000000..7a6fdd11 --- /dev/null +++ b/test/torchaudio_unittest/models/tacotron2/model_test_gpu_test.py @@ -0,0 +1,26 @@ +import torch + +from torchaudio_unittest.common_utils import skipIfNoCuda, PytorchTestCase +from .model_test_impl import ( + Tacotron2EncoderTests, + Tacotron2DecoderTests, + Tacotron2Tests, +) + + +@skipIfNoCuda +class TestTacotron2EncoderFloat32CUDA(Tacotron2EncoderTests, PytorchTestCase): + dtype = torch.float32 + device = torch.device("cuda") + + +@skipIfNoCuda +class TestTacotron2DecoderFloat32CUDA(Tacotron2DecoderTests, PytorchTestCase): + dtype = torch.float32 + device = torch.device("cuda") + + +@skipIfNoCuda +class TestTacotron2Float32CUDA(Tacotron2Tests, PytorchTestCase): + dtype = torch.float32 + device = torch.device("cuda") diff --git a/test/torchaudio_unittest/models/tacotron2/model_test_impl.py b/test/torchaudio_unittest/models/tacotron2/model_test_impl.py new file mode 100644 index 00000000..fb43a140 --- /dev/null +++ b/test/torchaudio_unittest/models/tacotron2/model_test_impl.py @@ -0,0 +1,381 @@ +from typing import Tuple +import torch +from torch import Tensor +from torchaudio.models import Tacotron2 +from torchaudio.models.tacotron2 import _Encoder, _Decoder +from torchaudio_unittest.common_utils import TestBaseMixin, torch_script + + +class Tacotron2InferenceWrapper(torch.nn.Module): + + def __init__(self, model): + super().__init__() + self.model = model + + def forward(self, text: Tensor, text_lengths: Tensor) -> Tuple[Tensor, Tensor, Tensor]: + return self.model.infer(text, text_lengths) + + +class Tacotron2DecoderInferenceWrapper(torch.nn.Module): + + def __init__(self, model): + super().__init__() + self.model = model + + def forward(self, memory: Tensor, memory_lengths: Tensor) -> Tuple[Tensor, Tensor, Tensor, Tensor]: + return self.model.infer(memory, memory_lengths) + + +class TorchscriptConsistencyMixin(TestBaseMixin): + r"""Mixin to provide easy access assert torchscript consistency""" + + def _assert_torchscript_consistency(self, model, tensors): + ts_func = torch_script(model) + + torch.random.manual_seed(40) + output = model(*tensors) + + torch.random.manual_seed(40) + ts_output = ts_func(*tensors) + + self.assertEqual(ts_output, output) + + +class Tacotron2EncoderTests(TorchscriptConsistencyMixin): + + def test_tacotron2_torchscript_consistency(self): + r"""Validate the torchscript consistency of a Encoder.""" + n_batch, n_seq, encoder_embedding_dim = 16, 64, 512 + model = _Encoder(encoder_embedding_dim=encoder_embedding_dim, + encoder_n_convolution=3, + encoder_kernel_size=5).to(self.device).eval() + + x = torch.rand( + n_batch, encoder_embedding_dim, n_seq, device=self.device, dtype=self.dtype + ) + input_lengths = ( + torch.ones(n_batch, device=self.device, dtype=torch.int32) * n_seq + ) + + self._assert_torchscript_consistency(model, (x, input_lengths)) + + def test_encoder_output_shape(self): + r"""Feed tensors with specific shape to Tacotron2 Decoder and validate + that it outputs with a tensor with expected shape. + """ + n_batch, n_seq, encoder_embedding_dim = 16, 64, 512 + model = _Encoder(encoder_embedding_dim=encoder_embedding_dim, + encoder_n_convolution=3, + encoder_kernel_size=5).to(self.device).eval() + + x = torch.rand( + n_batch, encoder_embedding_dim, n_seq, device=self.device, dtype=self.dtype + ) + input_lengths = ( + torch.ones(n_batch, device=self.device, dtype=torch.int32) * n_seq + ) + out = model(x, input_lengths) + + assert out.size() == (n_batch, n_seq, encoder_embedding_dim) + + +def _get_decoder_model(n_mels=80, encoder_embedding_dim=512, + decoder_max_step=2000, gate_threshold=0.5): + model = _Decoder( + n_mels=n_mels, + n_frames_per_step=1, + encoder_embedding_dim=encoder_embedding_dim, + decoder_rnn_dim=1024, + decoder_max_step=decoder_max_step, + decoder_dropout=0.1, + decoder_early_stopping=True, + attention_rnn_dim=1024, + attention_hidden_dim=128, + attention_location_n_filter=32, + attention_location_kernel_size=31, + attention_dropout=0.1, + prenet_dim=256, + gate_threshold=gate_threshold, + ) + return model + + +class Tacotron2DecoderTests(TorchscriptConsistencyMixin): + + def test_decoder_torchscript_consistency(self): + r"""Validate the torchscript consistency of a Decoder.""" + n_batch = 16 + n_mels = 80 + n_seq = 200 + encoder_embedding_dim = 256 + n_time_steps = 150 + + model = _get_decoder_model(n_mels=n_mels, encoder_embedding_dim=encoder_embedding_dim) + model = model.to(self.device).eval() + + memory = torch.rand( + n_batch, n_seq, encoder_embedding_dim, dtype=self.dtype, device=self.device + ) + decoder_inputs = torch.rand( + n_batch, n_mels, n_time_steps, dtype=self.dtype, device=self.device + ) + memory_lengths = torch.ones(n_batch, dtype=torch.int32, device=self.device) + + self._assert_torchscript_consistency( + model, (memory, decoder_inputs, memory_lengths) + ) + + def test_decoder_output_shape(self): + r"""Feed tensors with specific shape to Tacotron2 Decoder and validate + that it outputs with a tensor with expected shape. + """ + n_batch = 16 + n_mels = 80 + n_seq = 200 + encoder_embedding_dim = 256 + n_time_steps = 150 + + model = _get_decoder_model(n_mels=n_mels, encoder_embedding_dim=encoder_embedding_dim) + model = model.to(self.device).eval() + + memory = torch.rand( + n_batch, n_seq, encoder_embedding_dim, dtype=self.dtype, device=self.device + ) + decoder_inputs = torch.rand( + n_batch, n_mels, n_time_steps, dtype=self.dtype, device=self.device + ) + memory_lengths = torch.ones(n_batch, dtype=torch.int32, device=self.device) + + mel_specgram, gate_outputs, alignments = model( + memory, decoder_inputs, memory_lengths + ) + + assert mel_specgram.size() == (n_batch, n_mels, n_time_steps) + assert gate_outputs.size() == (n_batch, n_time_steps) + assert alignments.size() == (n_batch, n_time_steps, n_seq) + + def test_decoder_inference_torchscript_consistency(self): + r"""Validate the torchscript consistency of a Decoder.""" + n_batch = 16 + n_mels = 80 + n_seq = 200 + encoder_embedding_dim = 256 + decoder_max_step = 300 # make inference more efficient + gate_threshold = 0.505 # make inference more efficient + + model = _get_decoder_model( + n_mels=n_mels, + encoder_embedding_dim=encoder_embedding_dim, + decoder_max_step=decoder_max_step, + gate_threshold=gate_threshold, + ) + model = model.to(self.device).eval() + + memory = torch.rand( + n_batch, n_seq, encoder_embedding_dim, dtype=self.dtype, device=self.device + ) + memory_lengths = torch.ones(n_batch, dtype=torch.int32, device=self.device) + + model_wrapper = Tacotron2DecoderInferenceWrapper(model) + + self._assert_torchscript_consistency(model_wrapper, (memory, memory_lengths)) + + def test_decoder_inference_output_shape(self): + r"""Validate the torchscript consistency of a Decoder.""" + n_batch = 16 + n_mels = 80 + n_seq = 200 + encoder_embedding_dim = 256 + decoder_max_step = 300 # make inference more efficient + gate_threshold = 0.505 # if set to 0.5, the model will only run one step + + model = _get_decoder_model( + n_mels=n_mels, + encoder_embedding_dim=encoder_embedding_dim, + decoder_max_step=decoder_max_step, + gate_threshold=gate_threshold, + ) + model = model.to(self.device).eval() + + memory = torch.rand( + n_batch, n_seq, encoder_embedding_dim, dtype=self.dtype, device=self.device + ) + memory_lengths = torch.ones(n_batch, dtype=torch.int32, device=self.device) + + mel_specgram, mel_specgram_lengths, gate_outputs, alignments = model.infer( + memory, memory_lengths + ) + + assert len(mel_specgram.size()) == 3 + assert mel_specgram.size()[:-1] == (n_batch, n_mels, ) + assert mel_specgram.size()[2] == mel_specgram_lengths.max().item() + assert len(mel_specgram_lengths.size()) == 1 + assert mel_specgram_lengths.size()[0] == n_batch + assert mel_specgram_lengths.max().item() <= model.decoder_max_step + assert len(gate_outputs.size()) == 2 + assert gate_outputs.size()[0] == n_batch + assert gate_outputs.size()[1] == mel_specgram_lengths.max().item() + assert len(alignments.size()) == 2 + assert alignments.size()[0] == n_seq + assert alignments.size()[1] == mel_specgram_lengths.max().item() * n_batch + + +def _get_tacotron2_model(n_mels, decoder_max_step=2000, gate_threshold=0.5): + return Tacotron2( + mask_padding=False, + n_mels=n_mels, + n_symbol=148, + n_frames_per_step=1, + symbol_embedding_dim=512, + encoder_embedding_dim=512, + encoder_n_convolution=3, + encoder_kernel_size=5, + decoder_rnn_dim=1024, + decoder_max_step=decoder_max_step, + decoder_dropout=0.1, + decoder_early_stopping=True, + attention_rnn_dim=1024, + attention_hidden_dim=128, + attention_location_n_filter=32, + attention_location_kernel_size=31, + attention_dropout=0.1, + prenet_dim=256, + postnet_n_convolution=5, + postnet_kernel_size=5, + postnet_embedding_dim=512, + gate_threshold=gate_threshold, + ) + + +class Tacotron2Tests(TorchscriptConsistencyMixin): + + def _get_inputs( + self, n_mels: int, n_batch: int, max_mel_specgram_length: int, max_text_length: int + ): + text = torch.randint( + 0, 148, (n_batch, max_text_length), dtype=torch.int32, device=self.device + ) + text_lengths = max_text_length * torch.ones( + (n_batch,), dtype=torch.int32, device=self.device + ) + mel_specgram = torch.rand( + n_batch, + n_mels, + max_mel_specgram_length, + dtype=self.dtype, + device=self.device, + ) + mel_specgram_lengths = max_mel_specgram_length * torch.ones( + (n_batch,), dtype=torch.int32, device=self.device + ) + return text, text_lengths, mel_specgram, mel_specgram_lengths + + def test_tacotron2_torchscript_consistency(self): + r"""Validate the torchscript consistency of a Tacotron2.""" + n_batch = 16 + n_mels = 80 + max_mel_specgram_length = 300 + max_text_length = 100 + + model = _get_tacotron2_model(n_mels).to(self.device).eval() + inputs = self._get_inputs( + n_mels, n_batch, max_mel_specgram_length, max_text_length + ) + + self._assert_torchscript_consistency(model, inputs) + + def test_tacotron2_output_shape(self): + r"""Feed tensors with specific shape to Tacotron2 and validate + that it outputs with a tensor with expected shape. + """ + n_batch = 16 + n_mels = 80 + max_mel_specgram_length = 300 + max_text_length = 100 + + model = _get_tacotron2_model(n_mels).to(self.device).eval() + inputs = self._get_inputs( + n_mels, n_batch, max_mel_specgram_length, max_text_length + ) + mel_out, mel_out_postnet, gate_outputs, alignments = model(*inputs) + + assert mel_out.size() == (n_batch, n_mels, max_mel_specgram_length) + assert mel_out_postnet.size() == (n_batch, n_mels, max_mel_specgram_length) + assert gate_outputs.size() == (n_batch, max_mel_specgram_length) + assert alignments.size() == (n_batch, max_mel_specgram_length, max_text_length) + + def test_tacotron2_backward(self): + r"""Make sure calling the backward function on Tacotron2's outputs does + not error out. Following: + https://github.com/pytorch/vision/blob/23b8760374a5aaed53c6e5fc83a7e83dbe3b85df/test/test_models.py#L255 + """ + n_batch = 16 + n_mels = 80 + max_mel_specgram_length = 300 + max_text_length = 100 + + model = _get_tacotron2_model(n_mels).to(self.device) + inputs = self._get_inputs( + n_mels, n_batch, max_mel_specgram_length, max_text_length + ) + mel_out, mel_out_postnet, gate_outputs, _ = model(*inputs) + + mel_out.sum().backward(retain_graph=True) + mel_out_postnet.sum().backward(retain_graph=True) + gate_outputs.sum().backward() + + def _get_inference_inputs(self, n_batch: int, max_text_length: int): + text = torch.randint( + 0, 148, (n_batch, max_text_length), dtype=torch.int32, device=self.device + ) + text_lengths = max_text_length * torch.ones( + (n_batch,), dtype=torch.int32, device=self.device + ) + return text, text_lengths + + def test_tacotron2_inference_torchscript_consistency(self): + r"""Validate the torchscript consistency of Tacotron2 inference function.""" + n_batch = 16 + n_mels = 40 + max_text_length = 100 + decoder_max_step = 200 # make inference more efficient + gate_threshold = 0.51 # if set to 0.5, the model will only run one step + + model = _get_tacotron2_model( + n_mels, decoder_max_step=decoder_max_step, gate_threshold=gate_threshold + ).to(self.device).eval() + inputs = self._get_inference_inputs(n_batch, max_text_length) + + model_wrapper = Tacotron2InferenceWrapper(model) + + self._assert_torchscript_consistency(model_wrapper, inputs) + + def test_tacotron2_inference_output_shape(self): + r"""Feed tensors with specific shape to Tacotron2 inference function and validate + that it outputs with a tensor with expected shape. + """ + n_batch = 16 + n_mels = 40 + max_text_length = 100 + decoder_max_step = 200 # make inference more efficient + gate_threshold = 0.51 # if set to 0.5, the model will only run one step + + model = _get_tacotron2_model( + n_mels, decoder_max_step=decoder_max_step, gate_threshold=gate_threshold + ).to(self.device).eval() + inputs = self._get_inference_inputs(n_batch, max_text_length) + + mel_out, mel_specgram_lengths, alignments = model.infer(*inputs) + + # There is no guarantee on exactly what max_mel_specgram_length should be + # We only know that it should be smaller than model.decoder.decoder_max_step + assert len(mel_out.size()) == 3 + assert mel_out.size()[:2] == (n_batch, n_mels, ) + assert mel_out.size()[2] == mel_specgram_lengths.max().item() + assert len(mel_specgram_lengths.size()) == 1 + assert mel_specgram_lengths.size()[0] == n_batch + assert mel_specgram_lengths.max().item() <= model.decoder.decoder_max_step + assert len(alignments.size()) == 3 + assert alignments.size()[0] == n_batch + assert alignments.size()[1] == mel_specgram_lengths.max().item() + assert alignments.size()[2] == max_text_length diff --git a/test/torchaudio_unittest/models/wav2vec2/__init__.py b/test/torchaudio_unittest/models/wav2vec2/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/torchaudio_unittest/models/wav2vec2/fairseq_integration_test.py b/test/torchaudio_unittest/models/wav2vec2/fairseq_integration_test.py new file mode 100644 index 00000000..3e3b869a --- /dev/null +++ b/test/torchaudio_unittest/models/wav2vec2/fairseq_integration_test.py @@ -0,0 +1,240 @@ +import json + +import torch +from torchaudio.models.wav2vec2 import ( + wav2vec2_base, + wav2vec2_large, + wav2vec2_large_lv60k, + hubert_base, + hubert_large, + hubert_xlarge, +) +from torchaudio.models.wav2vec2.utils import ( + import_fairseq_model, +) +from parameterized import parameterized + +from torchaudio_unittest.common_utils import ( + get_asset_path, + skipIfNoModule, + TorchaudioTestCase, +) + + +def _load_config(*paths): + with open(f'{get_asset_path("wav2vec2", "fairseq", *paths)}.json', 'r') as file_: + return json.load(file_) + + +def _name_func(testcase_func, i, param): + return f'{testcase_func.__name__}_{i}_{param[0][1].__name__}' + + +# Pretraining models +WAV2VEC2_BASE = _load_config('wav2vec_small') +WAV2VEC2_LARGE = _load_config('libri960_big') +WAV2VEC2_LARGE_LV60K = _load_config('wav2vec_vox_new') +WAV2VEC2_XLSR_53_56K = _load_config('xlsr_53_56k') +HUBERT_BASE = _load_config('hubert_base_ls960') +HUBERT_LARGE_LL60K = _load_config('hubert_large_ll60k') +HUBERT_XLARGE_LL60K = _load_config('hubert_xtralarge_ll60k') +# Finetuning models +WAV2VEC2_BASE_960H = _load_config('wav2vec_small_960h') +WAV2VEC2_LARGE_960H = _load_config('wav2vec_large_960h') +WAV2VEC2_LARGE_LV60K_960H = _load_config('wav2vec_large_lv60k_960h') +WAV2VEC2_LARGE_LV60K_SELF_960H = _load_config('wav2vec_large_lv60k_self_960h') +HUBERT_LARGE = _load_config('hubert_large_ll60k_finetune_ls960') +HUBERT_XLARGE = _load_config('hubert_xtralarge_ll60k_finetune_ls960') + + +# Config and corresponding factory functions +WAV2VEC2_PRETRAINING_CONFIGS = parameterized.expand([ + (WAV2VEC2_BASE, wav2vec2_base), + (WAV2VEC2_LARGE, wav2vec2_large), + (WAV2VEC2_LARGE_LV60K, wav2vec2_large_lv60k), + (WAV2VEC2_XLSR_53_56K, wav2vec2_large_lv60k), +], name_func=_name_func) +HUBERT_PRETRAINING_CONFIGS = parameterized.expand([ + (HUBERT_BASE, hubert_base), + (HUBERT_LARGE_LL60K, hubert_large), + (HUBERT_XLARGE_LL60K, hubert_xlarge), +], name_func=_name_func) +ALL_PRETRAINING_CONFIGS = parameterized.expand([ + (WAV2VEC2_BASE, wav2vec2_base), + (WAV2VEC2_LARGE, wav2vec2_large), + (WAV2VEC2_LARGE_LV60K, wav2vec2_large_lv60k), + (WAV2VEC2_XLSR_53_56K, wav2vec2_large_lv60k), + (HUBERT_BASE, hubert_base), + (HUBERT_LARGE_LL60K, hubert_large), + (HUBERT_XLARGE_LL60K, hubert_xlarge), +], name_func=_name_func) +FINETUNING_CONFIGS = parameterized.expand([ + (WAV2VEC2_BASE_960H, wav2vec2_base), + (WAV2VEC2_LARGE_960H, wav2vec2_large), + (WAV2VEC2_LARGE_LV60K_960H, wav2vec2_large_lv60k), + (WAV2VEC2_LARGE_LV60K_SELF_960H, wav2vec2_large_lv60k), + (HUBERT_LARGE, hubert_large), + (HUBERT_XLARGE, hubert_xlarge), +], name_func=_name_func) + + +@skipIfNoModule('fairseq') +class TestFairseqIntegration(TorchaudioTestCase): + """Test the process of importing the models from fairseq. + + Test methods in this test suite check the following things + 1. Models loaded with fairseq cane be imported. + 2. The same model can be recreated without fairseq. + """ + def _get_model(self, config, num_out=None): + import copy + from omegaconf import OmegaConf + from fairseq.models.wav2vec.wav2vec2 import ( + Wav2Vec2Config, + Wav2Vec2Model, + ) + from fairseq.models.wav2vec.wav2vec2_asr import ( + Wav2VecEncoder, + Wav2Vec2CtcConfig, + ) + from fairseq.models.hubert.hubert_asr import ( + HubertCtcConfig, + HubertEncoder, + ) + from fairseq.models.hubert.hubert import ( + HubertModel, + HubertConfig, + ) + from fairseq.tasks.hubert_pretraining import HubertPretrainingConfig + + if config['_name'] == 'wav2vec_ctc': + config = copy.deepcopy(config) + config['w2v_args'] = OmegaConf.create(config['w2v_args']) + return Wav2VecEncoder(Wav2Vec2CtcConfig(**config), num_out) + if config['_name'] == 'wav2vec2': + return Wav2Vec2Model(Wav2Vec2Config(**config)) + if config['_name'] == 'hubert_ctc': + config = copy.deepcopy(config) + config['w2v_args'] = OmegaConf.create(config['w2v_args']) + ctc_cfg = HubertCtcConfig(**config) + return HubertEncoder(ctc_cfg, tgt_dict=range(num_out)) + if config['_name'] == 'hubert': + dicts = [list(range(i)) for i in config['num_classes']] + return HubertModel( + HubertConfig(**config['model']), + HubertPretrainingConfig(**config['task']), + dicts, + ) + raise ValueError(f'Unexpected configuration: {config["_name"]}') + + @WAV2VEC2_PRETRAINING_CONFIGS + def test_import_wave2vec2_pretraining_model(self, config, _): + """Wav2vec2 pretraining models from fairseq can be imported and yields the same results""" + batch_size, num_frames = 3, 1024 + + torch.manual_seed(0) + original = self._get_model(config).eval() + imported = import_fairseq_model(original).eval() + + x = torch.randn(batch_size, num_frames) + hyp, _ = imported.extract_features(x) + refs = original.extract_features(x, padding_mask=torch.zeros_like(x), layer=-1) + for i, (ref, _) in enumerate(refs['layer_results']): + self.assertEqual(hyp[i], ref.transpose(0, 1)) + + @HUBERT_PRETRAINING_CONFIGS + def test_import_hubert_pretraining_model(self, config, factory_func): + """HuBERT pretraining models from fairseq can be imported and yields the same results""" + batch_size, num_frames = 3, 1024 + + torch.manual_seed(0) + original = self._get_model(config).eval() + imported = import_fairseq_model(original).eval() + + x = torch.randn(batch_size, num_frames) + mask = torch.zeros_like(x) + hyp, _ = imported.extract_features(x) + + # check the last layer + ref, _ = original.extract_features(x, padding_mask=mask, output_layer=len(original.encoder.layers)) + atol = 3.0e-05 if factory_func is hubert_xlarge else 1.0e-5 + self.assertEqual(hyp[-1], ref, atol=atol, rtol=1.3e-6) + + # check the first layer + ref, _ = original.extract_features(x, padding_mask=mask, output_layer=1) + self.assertEqual(hyp[0], ref) + + @ALL_PRETRAINING_CONFIGS + def test_recreate_pretraining_model(self, config, factory_func): + """Imported pretraining models can be recreated via a factory function without fairseq.""" + batch_size, num_frames = 3, 1024 + + original = self._get_model(config).eval() + imported = import_fairseq_model(original).eval() + + reloaded = factory_func() + reloaded.load_state_dict(imported.state_dict()) + reloaded.eval() + + x = torch.randn(batch_size, num_frames) + lengths = torch.randint(low=0, high=num_frames, size=[batch_size, ]) + # Without mask + ref, _ = imported(x) + hyp, _ = reloaded(x) + self.assertEqual(ref, hyp) + + # With mask + ref, ref_lengths = imported(x, lengths) + hyp, hyp_lengths = reloaded(x, lengths) + self.assertEqual(ref, hyp) + self.assertEqual(ref_lengths, hyp_lengths) + + @FINETUNING_CONFIGS + def test_import_finetuning_model(self, config, _): + """Fintuned wav2vec2 models from fairseq can be imported and yields the same results""" + num_out = 28 + batch_size, num_frames = 3, 1024 + + original = self._get_model(config, num_out).eval() + imported = import_fairseq_model(original).eval() + + # Without mask + x = torch.randn(batch_size, num_frames) + ref = original(x, torch.zeros_like(x))['encoder_out'].transpose(0, 1) + hyp, _ = imported(x) + self.assertEqual(ref, hyp) + + # With mask + lengths = torch.randint(low=0, high=num_frames, size=[batch_size, ]) + mask = torch.arange(num_frames).expand(batch_size, num_frames) >= lengths[:, None] + ref = original(x, mask)['encoder_out'].transpose(0, 1) + hyp, output_lengths = imported(x, lengths) + for i, l in enumerate(output_lengths): + self.assertEqual(ref[i, :l, ...], hyp[i, :l, ...]) + + @FINETUNING_CONFIGS + def test_recreate_finetuning_model(self, config, factory_func): + """Imported finetuning models can be recreated via a factory function without fairseq.""" + num_out = 28 + batch_size, num_frames = 3, 1024 + + original = self._get_model(config, num_out).eval() + imported = import_fairseq_model(original).eval() + + reloaded = factory_func(aux_num_out=num_out) + reloaded.load_state_dict(imported.state_dict()) + reloaded.eval() + + # Without mask + torch.manual_seed(0) + x = torch.randn(batch_size, num_frames) + ref, _ = imported(x) + hyp, _ = reloaded(x) + self.assertEqual(ref, hyp) + + # With mask + lengths = torch.randint(low=0, high=num_frames, size=[batch_size, ]) + ref, ref_lengths = imported(x, lengths) + hyp, hyp_lengths = reloaded(x, lengths) + self.assertEqual(ref, hyp) + self.assertEqual(ref_lengths, hyp_lengths) diff --git a/test/torchaudio_unittest/models/wav2vec2/huggingface_intergration_test.py b/test/torchaudio_unittest/models/wav2vec2/huggingface_intergration_test.py new file mode 100644 index 00000000..ff177b7c --- /dev/null +++ b/test/torchaudio_unittest/models/wav2vec2/huggingface_intergration_test.py @@ -0,0 +1,224 @@ +import json + +import torch +from torchaudio.models.wav2vec2 import ( + wav2vec2_base, + wav2vec2_large, + wav2vec2_large_lv60k, +) +from torchaudio.models.wav2vec2.utils import import_huggingface_model +from parameterized import parameterized + +from torchaudio_unittest.common_utils import ( + get_asset_path, + skipIfNoModule, + TorchaudioTestCase, +) + + +def _load_config(*paths): + with open(f'{get_asset_path("wav2vec2", "huggingface", *paths)}.json', 'r') as file_: + return json.load(file_) + + +def _name_func(testcase_func, i, param): + return f"{testcase_func.__name__}_{i}_{param[0][1].__name__}" + + +# Pretrained +HF_BASE = _load_config('facebook', 'wav2vec2-base') +HF_LARGE = _load_config('facebook', 'wav2vec2-large') +HF_LARGE_LV60 = _load_config('facebook', 'wav2vec2-large-lv60') +HF_LARGE_XLSR_53 = _load_config('facebook', 'wav2vec2-large-xlsr-53') +HF_BASE_10K_VOXPOPULI = _load_config('facebook', 'wav2vec2-base-10k-voxpopuli') +# Finetuned +HF_BASE_960H = _load_config('facebook', 'wav2vec2-base-960h') +HF_LARGE_960H = _load_config('facebook', 'wav2vec2-large-960h') +HF_LARGE_LV60_960H = _load_config('facebook', 'wav2vec2-large-960h-lv60') +HF_LARGE_LV60_SELF_960H = _load_config('facebook', 'wav2vec2-large-960h-lv60-self') +HF_LARGE_XLSR_DE = _load_config('facebook', 'wav2vec2-large-xlsr-53-german') + +# Config and corresponding factory functions +PRETRAIN_CONFIGS = parameterized.expand([ + (HF_BASE, wav2vec2_base), + (HF_LARGE, wav2vec2_large), + (HF_LARGE_LV60, wav2vec2_large_lv60k), + (HF_LARGE_XLSR_53, wav2vec2_large_lv60k), + (HF_BASE_10K_VOXPOPULI, wav2vec2_base), +], name_func=_name_func) +FINETUNE_CONFIGS = parameterized.expand([ + (HF_BASE_960H, wav2vec2_base), + (HF_LARGE_960H, wav2vec2_large), + (HF_LARGE_LV60_960H, wav2vec2_large_lv60k), + (HF_LARGE_LV60_SELF_960H, wav2vec2_large_lv60k), + (HF_LARGE_XLSR_DE, wav2vec2_large_lv60k), +], name_func=_name_func) + + +@skipIfNoModule('transformers') +class TestHFIntegration(TorchaudioTestCase): + """Test the process of importing the models from Hugging Face Transformers + + Test methods in this test suite check the following things + 1. Models loaded with Hugging Face Transformers cane be imported. + 2. The same model can be recreated without Hugging Face Transformers. + """ + def _get_model(self, config): + # Helper function to avoid importing transformers on module scope. + # Normally, we use `is_module_available` helper function to check if + # the library is available, and import it on module scope if available. + # However, somehow, once "transformers" is imported, `is_module_available` + # starts to fail. Therefore, we defer importing "transformers" until + # the actual tests are started. + from transformers.models.wav2vec2 import ( + Wav2Vec2Config, + Wav2Vec2Model, + Wav2Vec2ForCTC, + ) + if config['architectures'] == ['Wav2Vec2Model']: + return Wav2Vec2Model(Wav2Vec2Config(**config)) + if config['architectures'] == ['Wav2Vec2ForCTC']: + return Wav2Vec2ForCTC(Wav2Vec2Config(**config)) + raise ValueError(f'Unexpected arch: {config["architectures"]}') + + def _test_import_pretrain(self, original, imported, config): + torch.manual_seed(0) + # FeatureExtractor + x = torch.randn(3, 1024) + ref = original.feature_extractor(x).transpose(1, 2) + hyp, _ = imported.feature_extractor(x, None) + self.assertEqual(ref, hyp) + # Feature projection + x = torch.randn(3, 10, config['conv_dim'][-1]) + ref = original.feature_projection(x)[0] + hyp = imported.encoder.feature_projection(x) + self.assertEqual(ref, hyp) + # Convolutional Positional Encoder + x = torch.randn(3, 256, config['hidden_size']) + ref = original.encoder.pos_conv_embed(x) + hyp = imported.encoder.transformer.pos_conv_embed(x) + self.assertEqual(ref, hyp) + # Encoder Transformer Layer + for original_, imported_ in zip(original.encoder.layers, imported.encoder.transformer.layers): + b, l, e = 16, 3, config["hidden_size"] + x = torch.randn(b, l, e) + mask = torch.randn(b, 1, l, l) + + ref, = original_(x, attention_mask=mask, output_attentions=False) + hyp = imported_(x, mask) + self.assertEqual(ref, hyp) + # The whole Encoder Transformer + b, l, e = 16, 3, config["hidden_size"] + x = torch.randn(b, l, e) + ref = original.encoder(x).last_hidden_state + hyp = imported.encoder.transformer(x) + self.assertEqual(ref, hyp) + + def _test_import_finetune(self, original, imported, config): + # Aux + x = torch.randn(3, 10, config["hidden_size"]) + ref = original.lm_head(x) + hyp = imported.aux(x) + self.assertEqual(ref, hyp) + # The whole model without mask + x = torch.randn(3, 1024) + ref = original(x).logits + hyp, _ = imported(x) + self.assertEqual(ref, hyp) + # The whole model without mask + batch_size, num_frames = 3, 1024 + x = torch.randn(batch_size, num_frames) + ref = original(x).logits + hyp, _ = imported(x) + self.assertEqual(ref, hyp) + + # The whole model with mask + batch_size, num_frames = 3, 1024 + x = torch.randn(batch_size, num_frames) + lengths = torch.randint(low=0, high=num_frames, size=[batch_size, ]) + mask = torch.arange(num_frames).expand(batch_size, num_frames) < lengths[:, None] + + ref = original(x, attention_mask=mask).logits + hyp, output_lengths = imported(x, lengths) + + for i, l in enumerate(output_lengths): + self.assertEqual(ref[i, :l, ...], hyp[i, :l, ...]) + + @PRETRAIN_CONFIGS + def test_import_pretrain(self, config, _): + """wav2vec2 models from HF transformers can be imported and yields the same results""" + original = self._get_model(config).eval() + imported = import_huggingface_model(original).eval() + self._test_import_pretrain(original, imported, config) + + @FINETUNE_CONFIGS + def test_import_finetune(self, config, _): + """wav2vec2 models from HF transformers can be imported and yields the same results""" + original = self._get_model(config).eval() + imported = import_huggingface_model(original).eval() + self._test_import_pretrain(original.wav2vec2, imported, config) + self._test_import_finetune(original, imported, config) + + def _test_recreate(self, imported, reloaded, config): + torch.manual_seed(0) + # FeatureExtractor + x = torch.randn(3, 1024) + ref, _ = imported.feature_extractor(x, None) + hyp, _ = reloaded.feature_extractor(x, None) + self.assertEqual(ref, hyp) + # Feature projection + x = torch.randn(3, 10, config['conv_dim'][-1]) + ref = imported.encoder.feature_projection(x) + hyp = reloaded.encoder.feature_projection(x) + self.assertEqual(ref, hyp) + # Convolutional Positional Encoder + x = torch.randn(3, 256, config['hidden_size']) + ref = imported.encoder.transformer.pos_conv_embed(x) + hyp = reloaded.encoder.transformer.pos_conv_embed(x) + self.assertEqual(ref, hyp) + # Encoder Transformer Layer + for imported_, reloaded_ in zip(imported.encoder.transformer.layers, reloaded.encoder.transformer.layers): + b, l, e = 16, 3, config["hidden_size"] + x = torch.randn(b, l, e) + mask = torch.randn(b, 1, l, l) + + ref = imported_(x, mask) + hyp = reloaded_(x, mask) + self.assertEqual(ref, hyp) + # The whole Encoder Transformer + # TODO: Add mask pattern. Expected mask shapes and values are different. + b, l, e = 16, 3, config["hidden_size"] + x = torch.randn(b, l, e) + mask = torch.randn(b, 1, l, l) + ref = imported.encoder.transformer(x) + hyp = reloaded.encoder.transformer(x) + self.assertEqual(ref, hyp) + # Aux + if imported.aux is not None: + x = torch.randn(3, 10, config["hidden_size"]) + ref = imported.aux(x) + hyp = reloaded.aux(x) + self.assertEqual(ref, hyp) + # The whole model + x = torch.randn(3, 1024) + ref, _ = imported(x) + hyp, _ = reloaded(x) + self.assertEqual(ref, hyp) + + @PRETRAIN_CONFIGS + def test_recreate_pretrain(self, config, factory_func): + """Imported models can be recreated via a factory function without Hugging Face transformers.""" + imported = import_huggingface_model(self._get_model(config)).eval() + reloaded = factory_func() + reloaded.load_state_dict(imported.state_dict()) + reloaded.eval() + self._test_recreate(imported, reloaded, config) + + @FINETUNE_CONFIGS + def test_recreate_finetune(self, config, factory_func): + """Imported models can be recreated via a factory function without Hugging Face transformers.""" + imported = import_huggingface_model(self._get_model(config)).eval() + reloaded = factory_func(aux_num_out=imported.aux.out_features) + reloaded.load_state_dict(imported.state_dict()) + reloaded.eval() + self._test_recreate(imported, reloaded, config) diff --git a/test/torchaudio_unittest/models/wav2vec2/model_test.py b/test/torchaudio_unittest/models/wav2vec2/model_test.py new file mode 100644 index 00000000..22a707d8 --- /dev/null +++ b/test/torchaudio_unittest/models/wav2vec2/model_test.py @@ -0,0 +1,243 @@ +import os + +import torch +import torch.nn.functional as F + +from torchaudio.models.wav2vec2 import ( + wav2vec2_base, + wav2vec2_large, + wav2vec2_large_lv60k, + hubert_base, + hubert_large, + hubert_xlarge, +) +from torchaudio_unittest.common_utils import ( + TorchaudioTestCase, + skipIfNoQengine, + skipIfNoCuda, + torch_script, +) +from parameterized import parameterized + + +def _name_func(testcase_func, i, param): + return f"{testcase_func.__name__}_{i}_{param[0][0].__name__}" + + +factory_funcs = parameterized.expand([ + (wav2vec2_base, ), + (wav2vec2_large, ), + (wav2vec2_large_lv60k, ), + (hubert_base, ), + (hubert_large, ), + (hubert_xlarge, ), +], name_func=_name_func) + + +class TestWav2Vec2Model(TorchaudioTestCase): + def _smoke_test(self, model, device, dtype): + model = model.to(device=device, dtype=dtype) + model = model.eval() + + torch.manual_seed(0) + batch_size, num_frames = 3, 1024 + + waveforms = torch.randn( + batch_size, num_frames, device=device, dtype=dtype) + lengths = torch.randint( + low=0, high=num_frames, size=[batch_size, ], device=device) + + model(waveforms, lengths) + + @parameterized.expand([(torch.float32, ), (torch.float64, )]) + def test_cpu_smoke_test(self, dtype): + model = wav2vec2_base() + self._smoke_test(model, torch.device('cpu'), dtype) + model = wav2vec2_base(aux_num_out=32) + self._smoke_test(model, torch.device('cpu'), dtype) + + @parameterized.expand([(torch.float32, ), (torch.float64, )]) + @skipIfNoCuda + def test_cuda_smoke_test(self, dtype): + model = wav2vec2_base() + self._smoke_test(model, torch.device('cuda'), dtype) + model = wav2vec2_base(aux_num_out=32) + self._smoke_test(model, torch.device('cuda'), dtype) + + def _feature_extractor_test(self, model): + batch_size, num_frames = 3, 1024 + + model.eval() + num_layers = len(model.encoder.transformer.layers) + + torch.manual_seed(0) + waveforms = torch.randn(batch_size, num_frames) + lengths = torch.randint(low=0, high=num_frames, size=[batch_size, ]) + + # Not providing num_layers returns all the intermediate features from + # tranformer layers + all_features, lengths_ = model.extract_features(waveforms, lengths, num_layers=None) + assert len(all_features) == num_layers + for features in all_features: + assert features.ndim == 3 + assert features.shape[0] == batch_size + assert lengths_.shape == torch.Size([batch_size]) + + # Limiting the number of layers to `l`. + for l in range(1, num_layers + 1): + features, lengths_ = model.extract_features(waveforms, lengths, num_layers=l) + assert len(features) == l + for i in range(l): + self.assertEqual(all_features[i], features[i]) + assert lengths_.shape == torch.Size([batch_size]) + + @factory_funcs + def test_extract_feature(self, factory_func): + """`extract_features` method does not fail""" + self._feature_extractor_test(factory_func(aux_num_out=32)) + + def _test_batch_consistency(self, model): + model.eval() + batch_size, max_frames = 5, 5 * 1024 + torch.manual_seed(0) + waveforms = torch.randn(batch_size, max_frames) + input_lengths = torch.tensor([i * 3200 for i in range(1, 6)]) + + # Batch process with lengths + batch_logits, output_lengths = model(waveforms, input_lengths) + for i in range(batch_size): + # Par-sample process without feeding length + single_logit, _ = model(waveforms[i:i + 1, :input_lengths[i]], None) + batch_logit = batch_logits[i:i + 1, :output_lengths[i]] + + # Convert to probability so that it's easier to interpretate the diff + single_prob = F.softmax(single_logit, dim=2) + batch_prob = F.softmax(batch_logit, dim=2) + # We allow max atol=0.005 -> 0.5% + self.assertEqual(single_prob, batch_prob, atol=0.005, rtol=0) + + @factory_funcs + def test_pretrain_batch_consistency(self, factory_func): + """Results from single process and batched process should be reasonably close + """ + self._test_batch_consistency(factory_func()) + + @factory_funcs + def test_finetune_batch_consistency(self, factory_func): + """Results from single process and batched process should be reasonably close + """ + self._test_batch_consistency(factory_func(aux_num_out=32)) + + def _test_zero_length(self, model): + model.eval() + torch.manual_seed(0) + batch_size = 3 + waveforms = torch.randn(batch_size, 1024) + input_lengths = torch.zeros(batch_size) + _, output_lengths = model(waveforms, input_lengths) + self.assertEqual(torch.zeros_like(output_lengths), output_lengths) + _, output_lengths = model.extract_features(waveforms, input_lengths) + self.assertEqual(torch.zeros_like(output_lengths), output_lengths) + + @factory_funcs + def test_pretrain_zero_length(self, factory_func): + """Passing zero length should not fail""" + self._test_zero_length(factory_func()) + + @factory_funcs + def test_finetune_zero_length(self, factory_func): + """Passing zero length should not fail""" + self._test_zero_length(factory_func(aux_num_out=32)) + + def _test_torchscript(self, model): + model.eval() + + batch_size, num_frames = 3, 1024 + + torch.manual_seed(0) + waveforms = torch.randn(batch_size, num_frames) + lengths = torch.randint(low=0, high=num_frames, size=[batch_size, ]) + + ref_out, ref_len = model(waveforms, lengths) + + scripted = torch_script(model) + + hyp_out, hyp_len = scripted(waveforms, lengths) + + self.assertEqual(hyp_out, ref_out) + self.assertEqual(hyp_len, ref_len) + + @factory_funcs + def test_pretrain_torchscript(self, factory_func): + """Wav2Vec2Model should be scriptable""" + if factory_func is hubert_xlarge and os.name == 'nt' and os.environ.get('CI') == 'true': + self.skipTest( + 'hubert_xlarge is known to fail on Windows CI. ' + 'See https://github.com/pytorch/pytorch/issues/65776') + self._test_torchscript(factory_func()) + + @factory_funcs + def test_finetune_torchscript(self, factory_func): + """Wav2Vec2Model should be scriptable""" + if factory_func is hubert_xlarge and os.name == 'nt' and os.environ.get('CI') == 'true': + self.skipTest( + 'hubert_xlarge is known to fail on Windows CI. ' + 'See https://github.com/pytorch/pytorch/issues/65776') + self._test_torchscript(factory_func(aux_num_out=32)) + + def _test_quantize_smoke_test(self, model): + model.eval() + batch_size, num_frames = 3, 1024 + + # Remove the weight normalization forward hook + model.encoder.transformer.pos_conv_embed.__prepare_scriptable__() + quantized = torch.quantization.quantize_dynamic( + model, qconfig_spec={torch.nn.Linear}, dtype=torch.qint8) + + # A lazy way to check that Modules are different + assert str(quantized) != str(model), "Dynamic quantization did not modify the module." + + torch.manual_seed(0) + waveforms = torch.randn(batch_size, num_frames) + lengths = torch.randint(low=0, high=num_frames, size=[batch_size, ]) + + _, _ = quantized(waveforms, lengths) + + @factory_funcs + @skipIfNoQengine + def test_quantize(self, factory_func): + """Wav2Vec2Model should support basic quantization""" + self._test_quantize_smoke_test(factory_func(aux_num_out=32)) + + def _test_quantize_torchscript(self, model): + model.eval() + + batch_size, num_frames = 3, 1024 + + # Remove the weight normalization forward hook + model.encoder.transformer.pos_conv_embed.__prepare_scriptable__() + quantized = torch.quantization.quantize_dynamic( + model, qconfig_spec={torch.nn.Linear}, dtype=torch.qint8) + + # A lazy way to check that Modules are different + assert str(quantized) != str(model), "Dynamic quantization did not modify the module." + + torch.manual_seed(0) + waveforms = torch.randn(batch_size, num_frames) + lengths = torch.randint(low=0, high=num_frames, size=[batch_size, ]) + + ref_out, ref_len = quantized(waveforms, lengths) + + # Script + scripted = torch_script(quantized) + + hyp_out, hyp_len = scripted(waveforms, lengths) + + self.assertEqual(hyp_out, ref_out) + self.assertEqual(hyp_len, ref_len) + + @factory_funcs + @skipIfNoQengine + def test_quantize_torchscript(self, factory_func): + """Quantized Wav2Vec2Model should be scriptable""" + self._test_quantize_torchscript(factory_func(aux_num_out=32)) diff --git a/test/torchaudio_unittest/sox_effect/__init__.py b/test/torchaudio_unittest/sox_effect/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/torchaudio_unittest/sox_effect/common.py b/test/torchaudio_unittest/sox_effect/common.py new file mode 100644 index 00000000..00ac4a45 --- /dev/null +++ b/test/torchaudio_unittest/sox_effect/common.py @@ -0,0 +1,26 @@ +import json + +from parameterized import param + +from torchaudio_unittest.common_utils import get_asset_path + + +def name_func(func, _, params): + if isinstance(params.args[0], str): + args = "_".join([str(arg) for arg in params.args]) + else: + args = "_".join([str(arg) for arg in params.args[0]]) + return f'{func.__name__}_{args}' + + +def load_params(*paths): + params = [] + with open(get_asset_path(*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 diff --git a/test/torchaudio_unittest/sox_effect/dataset_test.py b/test/torchaudio_unittest/sox_effect/dataset_test.py new file mode 100644 index 00000000..0b8cc9c4 --- /dev/null +++ b/test/torchaudio_unittest/sox_effect/dataset_test.py @@ -0,0 +1,158 @@ +import sys +import platform +from unittest import skipIf +from typing import List, Tuple +from concurrent.futures import ProcessPoolExecutor + +import numpy as np +import torch +import torchaudio + +from torchaudio_unittest.common_utils import ( + TempDirMixin, + PytorchTestCase, + skipIfNoSox, + get_whitenoise, + save_wav, +) + + +class RandomPerturbationFile(torch.utils.data.Dataset): + """Given flist, apply random speed perturbation""" + def __init__(self, flist: List[str], sample_rate: int): + super().__init__() + self.flist = flist + self.sample_rate = sample_rate + self.rng = None + + def __getitem__(self, index): + speed = self.rng.uniform(0.5, 2.0) + effects = [ + ['gain', '-n', '-10'], + ['speed', f'{speed:.5f}'], # duration of data is 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 + ] + data, _ = torchaudio.sox_effects.apply_effects_file(self.flist[index], effects) + return data + + def __len__(self): + return len(self.flist) + + +class RandomPerturbationTensor(torch.utils.data.Dataset): + """Apply speed purturbation to (synthetic) Tensor data""" + def __init__(self, signals: List[Tuple[torch.Tensor, int]], sample_rate: int): + super().__init__() + self.signals = signals + self.sample_rate = sample_rate + self.rng = None + + def __getitem__(self, index): + speed = self.rng.uniform(0.5, 2.0) + effects = [ + ['gain', '-n', '-10'], + ['speed', f'{speed:.5f}'], # duration of data is 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 + ] + tensor, sample_rate = self.signals[index] + data, _ = torchaudio.sox_effects.apply_effects_tensor(tensor, sample_rate, effects) + return data + + def __len__(self): + return len(self.signals) + + +def init_random_seed(worker_id): + dataset = torch.utils.data.get_worker_info().dataset + dataset.rng = np.random.RandomState(worker_id) + + +@skipIfNoSox +@skipIf( + platform.system() == 'Darwin' and + sys.version_info.major == 3 and + sys.version_info.minor in [6, 7], + 'This test is known to get stuck for macOS with Python < 3.8. ' + 'See https://github.com/pytorch/pytorch/issues/46409' +) +class TestSoxEffectsDataset(TempDirMixin, PytorchTestCase): + """Test `apply_effects_file` in multi-process dataloader setting""" + + def _generate_dataset(self, num_samples=128): + flist = [] + for i in range(num_samples): + sample_rate = np.random.choice([8000, 16000, 44100]) + dtype = np.random.choice(['float32', 'int32', 'int16', 'uint8']) + data = get_whitenoise(n_channels=2, sample_rate=sample_rate, duration=1, dtype=dtype) + path = self.get_temp_path(f'{i:03d}_{dtype}_{sample_rate}.wav') + save_wav(path, data, sample_rate) + flist.append(path) + return flist + + def test_apply_effects_file(self): + sample_rate = 12000 + flist = self._generate_dataset() + dataset = RandomPerturbationFile(flist, sample_rate) + loader = torch.utils.data.DataLoader( + dataset, batch_size=32, num_workers=16, + worker_init_fn=init_random_seed, + ) + for batch in loader: + assert batch.shape == (32, 2, 2 * sample_rate) + + def _generate_signals(self, num_samples=128): + signals = [] + for _ in range(num_samples): + sample_rate = np.random.choice([8000, 16000, 44100]) + data = get_whitenoise( + n_channels=2, sample_rate=sample_rate, duration=1, dtype='float32') + signals.append((data, sample_rate)) + return signals + + def test_apply_effects_tensor(self): + sample_rate = 12000 + signals = self._generate_signals() + dataset = RandomPerturbationTensor(signals, sample_rate) + loader = torch.utils.data.DataLoader( + dataset, batch_size=32, num_workers=16, + worker_init_fn=init_random_seed, + ) + for batch in loader: + assert batch.shape == (32, 2, 2 * sample_rate) + + +def speed(path): + wav, sample_rate = torchaudio.backend.sox_io_backend.load(path) + effects = [ + ['speed', '1.03756523535464655'], + ['rate', f'{sample_rate}'], + ] + return torchaudio.sox_effects.apply_effects_tensor(wav, sample_rate, effects)[0] + + +@skipIfNoSox +class TestProcessPoolExecutor(TempDirMixin, PytorchTestCase): + backend = "sox_io" + + def setUp(self): + sample_rate = 16000 + self.flist = [] + for i in range(10): + path = self.get_temp_path(f'{i}.wav') + data = get_whitenoise(n_channels=1, sample_rate=sample_rate, duration=1, dtype='float') + save_wav(path, data, sample_rate) + self.flist.append(path) + + def test_executor(self): + """Test that apply_effects_tensor with speed + rate does not crush + + https://github.com/pytorch/audio/issues/1021 + """ + executor = ProcessPoolExecutor(1) + futures = [executor.submit(speed, path) for path in self.flist] + for future in futures: + future.result() diff --git a/test/torchaudio_unittest/sox_effect/smoke_test.py b/test/torchaudio_unittest/sox_effect/smoke_test.py new file mode 100644 index 00000000..70a6a346 --- /dev/null +++ b/test/torchaudio_unittest/sox_effect/smoke_test.py @@ -0,0 +1,78 @@ +from torchaudio import sox_effects +from parameterized import parameterized + +from torchaudio_unittest.common_utils import ( + TempDirMixin, + TorchaudioTestCase, + skipIfNoSox, + get_wav_data, + get_sinusoid, + save_wav, +) +from .common import ( + load_params, +) + + +@skipIfNoSox +class SmokeTest(TempDirMixin, TorchaudioTestCase): + """Run smoke test on various effects + + The purpose of this test suite is to verify that sox_effect 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. + """ + @parameterized.expand( + load_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_tensor(self, args): + """`apply_effects_tensor` should not crash""" + effects = args['effects'] + num_channels = args.get("num_channels", 2) + input_sr = args.get("input_sample_rate", 8000) + original = get_sinusoid( + frequency=800, sample_rate=input_sr, + n_channels=num_channels, dtype='float32') + _found, _sr = sox_effects.apply_effects_tensor(original, input_sr, effects) + + @parameterized.expand( + load_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_file(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) + + input_path = self.get_temp_path('input.wav') + data = get_wav_data(dtype, num_channels, channels_first=channels_first) + save_wav(input_path, data, input_sr, channels_first=channels_first) + + _found, _sr = sox_effects.apply_effects_file( + input_path, effects, normalize=False, channels_first=channels_first) + + @parameterized.expand( + load_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_fileobj(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) + + input_path = self.get_temp_path('input.wav') + data = get_wav_data(dtype, num_channels, channels_first=channels_first) + save_wav(input_path, data, input_sr, channels_first=channels_first) + + with open(input_path, 'rb') as fileobj: + _found, _sr = sox_effects.apply_effects_file( + fileobj, effects, normalize=False, channels_first=channels_first) diff --git a/test/torchaudio_unittest/sox_effect/sox_effect_test.py b/test/torchaudio_unittest/sox_effect/sox_effect_test.py new file mode 100644 index 00000000..ca93e7d4 --- /dev/null +++ b/test/torchaudio_unittest/sox_effect/sox_effect_test.py @@ -0,0 +1,423 @@ +import io +import itertools +from pathlib import Path +import tarfile + +from parameterized import parameterized +from torchaudio import sox_effects +from torchaudio._internal import module_utils as _mod_utils + +from torchaudio_unittest.common_utils import ( + TempDirMixin, + HttpServerMixin, + PytorchTestCase, + skipIfNoSox, + skipIfNoModule, + skipIfNoExec, + get_asset_path, + get_sinusoid, + get_wav_data, + save_wav, + load_wav, + sox_utils, +) +from .common import ( + load_params, + name_func, +) + + +if _mod_utils.is_module_available("requests"): + import requests + + +@skipIfNoSox +class TestSoxEffects(PytorchTestCase): + def test_init(self): + """Calling init_sox_effects multiple times does not crush""" + for _ in range(3): + sox_effects.init_sox_effects() + + +@skipIfNoSox +class TestSoxEffectsTensor(TempDirMixin, PytorchTestCase): + """Test suite for `apply_effects_tensor` function""" + @parameterized.expand(list(itertools.product( + ['float32', 'int32', 'int16', 'uint8'], + [8000, 16000], + [1, 2, 4, 8], + [True, False] + )), name_func=name_func) + 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) + # 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) + + @parameterized.expand( + load_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) + + +@skipIfNoSox +class TestSoxEffectsFile(TempDirMixin, PytorchTestCase): + """Test suite for `apply_effects_file` function""" + @parameterized.expand(list(itertools.product( + ['float32', 'int32', 'int16', 'uint8'], + [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) + + @parameterized.expand( + load_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) + + 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) + + +@skipIfNoSox +class TestFileFormats(TempDirMixin, PytorchTestCase): + """`apply_effects_file` gives the same result as sox on various file formats""" + @parameterized.expand(list(itertools.product( + ['float32', 'int32', 'int16', 'uint8'], + [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) + + @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_mp3(self, sample_rate, num_channels): + """`apply_effects_file` works on various mp3 format""" + channels_first = True + effects = [['band', '300', '10']] + + input_path = self.get_temp_path('input.mp3') + 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) + + 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, atol=1e-4, rtol=1e-8) + + @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) + + @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) + + +@skipIfNoSox +class TestApplyEffectFileWithoutExtension(PytorchTestCase): + def test_mp3(self): + """Providing format allows to read mp3 without extension + + libsox does not check header for mp3 + + https://github.com/pytorch/audio/issues/1040 + + The file was generated with the following command + ffmpeg -f lavfi -i "sine=frequency=1000:duration=5" -ar 16000 -f mp3 test_noext + """ + effects = [['band', '300', '10']] + path = get_asset_path("mp3_without_ext") + _, sr = sox_effects.apply_effects_file(path, effects, format="mp3") + assert sr == 16000 + + +@skipIfNoExec('sox') +@skipIfNoSox +class TestFileObject(TempDirMixin, PytorchTestCase): + @parameterized.expand([ + ('wav', None), + ('mp3', 128), + ('mp3', 320), + ('flac', 0), + ('flac', 5), + ('flac', 8), + ('vorbis', -1), + ('vorbis', 10), + ('amb', None), + ]) + def test_fileobj(self, ext, compression): + """Applying effects via file object works""" + sample_rate = 16000 + channels_first = True + effects = [['band', '300', '10']] + format_ = ext if ext in ['mp3'] else None + 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) + 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, format=format_) + save_wav(self.get_temp_path('result.wav'), found, sr, channels_first=channels_first) + assert sr == expected_sr + self.assertEqual(found, expected) + + @parameterized.expand([ + ('wav', None), + ('mp3', 128), + ('mp3', 320), + ('flac', 0), + ('flac', 5), + ('flac', 8), + ('vorbis', -1), + ('vorbis', 10), + ('amb', None), + ]) + def test_bytesio(self, ext, compression): + """Applying effects via BytesIO object works""" + sample_rate = 16000 + channels_first = True + effects = [['band', '300', '10']] + format_ = ext if ext in ['mp3'] else None + 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) + 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, format=format_) + save_wav(self.get_temp_path('result.wav'), found, sr, channels_first=channels_first) + assert sr == expected_sr + self.assertEqual(found, expected) + + @parameterized.expand([ + ('wav', None), + ('mp3', 128), + ('mp3', 320), + ('flac', 0), + ('flac', 5), + ('flac', 8), + ('vorbis', -1), + ('vorbis', 10), + ('amb', 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']] + format_ = ext if ext in ['mp3'] else None + 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') + + 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, format=format_) + save_wav(self.get_temp_path('result.wav'), found, sr, channels_first=channels_first) + assert sr == expected_sr + self.assertEqual(found, expected) + + +@skipIfNoSox +@skipIfNoExec('sox') +@skipIfNoModule("requests") +class TestFileObjectHttp(HttpServerMixin, PytorchTestCase): + @parameterized.expand([ + ('wav', None), + ('mp3', 128), + ('mp3', 320), + ('flac', 0), + ('flac', 5), + ('flac', 8), + ('vorbis', -1), + ('vorbis', 10), + ('amb', None), + ]) + def test_requests(self, ext, compression): + sample_rate = 16000 + channels_first = True + effects = [['band', '300', '10']] + format_ = ext if ext in ['mp3'] else None + audio_file = f'input.{ext}' + input_path = self.get_temp_path(audio_file) + reference_path = self.get_temp_path('reference.wav') + + 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) + + url = self.get_url(audio_file) + with requests.get(url, stream=True) as resp: + found, sr = sox_effects.apply_effects_file( + resp.raw, effects, channels_first=channels_first, format=format_) + save_wav(self.get_temp_path('result.wav'), found, sr, channels_first=channels_first) + assert sr == expected_sr + self.assertEqual(found, expected) diff --git a/test/torchaudio_unittest/sox_effect/torchscript_test.py b/test/torchaudio_unittest/sox_effect/torchscript_test.py new file mode 100644 index 00000000..4bc81c2b --- /dev/null +++ b/test/torchaudio_unittest/sox_effect/torchscript_test.py @@ -0,0 +1,96 @@ +from typing import List + +import torch +from torchaudio import sox_effects +from parameterized import parameterized + +from torchaudio_unittest.common_utils import ( + TempDirMixin, + TorchaudioTestCase, + skipIfNoSox, + get_sinusoid, + save_wav, + torch_script, +) +from .common import ( + load_params, +) + + +class SoxEffectTensorTransform(torch.nn.Module): + effects: List[List[str]] + + def __init__(self, effects: List[List[str]], sample_rate: int, channels_first: bool): + super().__init__() + self.effects = effects + self.sample_rate = sample_rate + self.channels_first = channels_first + + def forward(self, tensor: torch.Tensor): + return sox_effects.apply_effects_tensor( + tensor, self.sample_rate, self.effects, self.channels_first) + + +class SoxEffectFileTransform(torch.nn.Module): + effects: List[List[str]] + channels_first: bool + + def __init__(self, effects: List[List[str]], channels_first: bool): + super().__init__() + self.effects = effects + self.channels_first = channels_first + + def forward(self, path: str): + return sox_effects.apply_effects_file(path, self.effects, self.channels_first) + + +@skipIfNoSox +class TestTorchScript(TempDirMixin, TorchaudioTestCase): + @parameterized.expand( + load_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_tensor(self, args): + effects = args['effects'] + channels_first = True + num_channels = args.get("num_channels", 2) + input_sr = args.get("input_sample_rate", 8000) + + trans = SoxEffectTensorTransform(effects, input_sr, channels_first) + + trans = torch_script(trans) + + wav = get_sinusoid( + frequency=800, sample_rate=input_sr, + n_channels=num_channels, dtype='float32', channels_first=channels_first) + found, sr_found = trans(wav) + expected, sr_expected = sox_effects.apply_effects_tensor( + wav, input_sr, effects, channels_first) + + assert sr_found == sr_expected + self.assertEqual(expected, found) + + @parameterized.expand( + load_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_file(self, args): + effects = args['effects'] + channels_first = True + num_channels = args.get("num_channels", 2) + input_sr = args.get("input_sample_rate", 8000) + + trans = SoxEffectFileTransform(effects, channels_first) + trans = torch_script(trans) + + path = self.get_temp_path('input.wav') + wav = get_sinusoid( + frequency=800, sample_rate=input_sr, + n_channels=num_channels, dtype='float32', channels_first=channels_first) + save_wav(path, wav, sample_rate=input_sr, channels_first=channels_first) + + found, sr_found = trans(path) + expected, sr_expected = sox_effects.apply_effects_file(path, effects, channels_first) + + assert sr_found == sr_expected + self.assertEqual(expected, found) diff --git a/test/torchaudio_unittest/transforms/__init__.py b/test/torchaudio_unittest/transforms/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/torchaudio_unittest/transforms/autograd_cpu_test.py b/test/torchaudio_unittest/transforms/autograd_cpu_test.py new file mode 100644 index 00000000..f9d4aa19 --- /dev/null +++ b/test/torchaudio_unittest/transforms/autograd_cpu_test.py @@ -0,0 +1,10 @@ +from torchaudio_unittest.common_utils import PytorchTestCase +from .autograd_test_impl import AutogradTestMixin, AutogradTestFloat32 + + +class AutogradCPUTest(AutogradTestMixin, PytorchTestCase): + device = 'cpu' + + +class AutogradRNNTCPUTest(AutogradTestFloat32, PytorchTestCase): + device = 'cpu' diff --git a/test/torchaudio_unittest/transforms/autograd_cuda_test.py b/test/torchaudio_unittest/transforms/autograd_cuda_test.py new file mode 100644 index 00000000..7565f885 --- /dev/null +++ b/test/torchaudio_unittest/transforms/autograd_cuda_test.py @@ -0,0 +1,15 @@ +from torchaudio_unittest.common_utils import ( + PytorchTestCase, + skipIfNoCuda, +) +from .autograd_test_impl import AutogradTestMixin, AutogradTestFloat32 + + +@skipIfNoCuda +class AutogradCUDATest(AutogradTestMixin, PytorchTestCase): + device = 'cuda' + + +@skipIfNoCuda +class AutogradRNNTCUDATest(AutogradTestFloat32, PytorchTestCase): + device = 'cuda' diff --git a/test/torchaudio_unittest/transforms/autograd_test_impl.py b/test/torchaudio_unittest/transforms/autograd_test_impl.py new file mode 100644 index 00000000..8bf37b0a --- /dev/null +++ b/test/torchaudio_unittest/transforms/autograd_test_impl.py @@ -0,0 +1,337 @@ +from typing import List +import unittest + +from parameterized import parameterized +import torch +from torch.autograd import gradcheck, gradgradcheck +import torchaudio.transforms as T + +from torchaudio_unittest.common_utils import ( + TestBaseMixin, + get_whitenoise, + get_spectrogram, + nested_params, + rnnt_utils, +) + + +class _DeterministicWrapper(torch.nn.Module): + """Helper transform wrapper to make the given transform deterministic""" + def __init__(self, transform, seed=0): + super().__init__() + self.seed = seed + self.transform = transform + + def forward(self, input: torch.Tensor): + torch.random.manual_seed(self.seed) + return self.transform(input) + + +class AutogradTestMixin(TestBaseMixin): + def assert_grad( + self, + transform: torch.nn.Module, + inputs: List[torch.Tensor], + *, + nondet_tol: float = 0.0, + ): + transform = transform.to(dtype=torch.float64, device=self.device) + + # gradcheck and gradgradcheck only pass if the input tensors are of dtype `torch.double` or + # `torch.cdouble`, when the default eps and tolerance values are used. + inputs_ = [] + for i in inputs: + if torch.is_tensor(i): + i = i.to( + dtype=torch.cdouble if i.is_complex() else torch.double, + device=self.device) + i.requires_grad = True + inputs_.append(i) + assert gradcheck(transform, inputs_) + assert gradgradcheck(transform, inputs_, nondet_tol=nondet_tol) + + @parameterized.expand([ + ({'pad': 0, 'normalized': False, 'power': None, 'return_complex': True}, ), + ({'pad': 3, 'normalized': False, 'power': None, 'return_complex': True}, ), + ({'pad': 0, 'normalized': True, 'power': None, 'return_complex': True}, ), + ({'pad': 3, 'normalized': True, 'power': None, 'return_complex': True}, ), + ({'pad': 0, 'normalized': False, 'power': None}, ), + ({'pad': 3, 'normalized': False, 'power': None}, ), + ({'pad': 0, 'normalized': True, 'power': None}, ), + ({'pad': 3, 'normalized': True, 'power': None}, ), + ({'pad': 0, 'normalized': False, 'power': 1.0}, ), + ({'pad': 3, 'normalized': False, 'power': 1.0}, ), + ({'pad': 0, 'normalized': True, 'power': 1.0}, ), + ({'pad': 3, 'normalized': True, 'power': 1.0}, ), + ({'pad': 0, 'normalized': False, 'power': 2.0}, ), + ({'pad': 3, 'normalized': False, 'power': 2.0}, ), + ({'pad': 0, 'normalized': True, 'power': 2.0}, ), + ({'pad': 3, 'normalized': True, 'power': 2.0}, ), + ]) + def test_spectrogram(self, kwargs): + # replication_pad1d_backward_cuda is not deteministic and + # gives very small (~2.7756e-17) difference. + # + # See https://github.com/pytorch/pytorch/issues/54093 + transform = T.Spectrogram(**kwargs) + waveform = get_whitenoise(sample_rate=8000, duration=0.05, n_channels=2) + self.assert_grad(transform, [waveform], nondet_tol=1e-10) + + @parameterized.expand([(False, ), (True, )]) + def test_inverse_spectrogram(self, return_complex): + # create a realistic input: + waveform = get_whitenoise(sample_rate=8000, duration=0.05, n_channels=2) + length = waveform.shape[-1] + spectrogram = get_spectrogram(waveform, n_fft=400) + if not return_complex: + spectrogram = torch.view_as_real(spectrogram) + + # test + inv_transform = T.InverseSpectrogram(n_fft=400) + self.assert_grad(inv_transform, [spectrogram, length]) + + def test_melspectrogram(self): + # replication_pad1d_backward_cuda is not deteministic and + # gives very small (~2.7756e-17) difference. + # + # See https://github.com/pytorch/pytorch/issues/54093 + sample_rate = 8000 + transform = T.MelSpectrogram(sample_rate=sample_rate) + waveform = get_whitenoise(sample_rate=sample_rate, duration=0.05, n_channels=2) + self.assert_grad(transform, [waveform], nondet_tol=1e-10) + + @nested_params( + [0, 0.99], + [False, True], + ) + def test_griffinlim(self, momentum, rand_init): + n_fft = 400 + power = 1 + n_iter = 3 + spec = get_spectrogram( + get_whitenoise(sample_rate=8000, duration=0.05, n_channels=2), + n_fft=n_fft, power=power) + transform = _DeterministicWrapper( + T.GriffinLim(n_fft=n_fft, n_iter=n_iter, momentum=momentum, rand_init=rand_init, power=power)) + self.assert_grad(transform, [spec]) + + @parameterized.expand([(False, ), (True, )]) + def test_mfcc(self, log_mels): + sample_rate = 8000 + transform = T.MFCC(sample_rate=sample_rate, log_mels=log_mels) + waveform = get_whitenoise(sample_rate=sample_rate, duration=0.05, n_channels=2) + self.assert_grad(transform, [waveform]) + + @parameterized.expand([(False, ), (True, )]) + def test_lfcc(self, log_lf): + sample_rate = 8000 + transform = T.LFCC(sample_rate=sample_rate, log_lf=log_lf) + waveform = get_whitenoise(sample_rate=sample_rate, duration=0.05, n_channels=2) + self.assert_grad(transform, [waveform]) + + def test_compute_deltas(self): + transform = T.ComputeDeltas() + spec = torch.rand(10, 20) + self.assert_grad(transform, [spec]) + + @parameterized.expand([(8000, 8000), (8000, 4000), (4000, 8000)]) + def test_resample(self, orig_freq, new_freq): + transform = T.Resample(orig_freq=orig_freq, new_freq=new_freq) + waveform = get_whitenoise(sample_rate=8000, duration=0.05, n_channels=2) + self.assert_grad(transform, [waveform]) + + @parameterized.expand([("linear", ), ("exponential", ), ("logarithmic", ), ("quarter_sine", ), ("half_sine", )]) + def test_fade(self, fade_shape): + transform = T.Fade(fade_shape=fade_shape) + waveform = get_whitenoise(sample_rate=8000, duration=0.05, n_channels=2) + self.assert_grad(transform, [waveform], nondet_tol=1e-10) + + @parameterized.expand([(T.TimeMasking,), (T.FrequencyMasking,)]) + def test_masking(self, masking_transform): + sample_rate = 8000 + n_fft = 400 + spectrogram = get_spectrogram( + get_whitenoise(sample_rate=sample_rate, duration=0.05, n_channels=2), + n_fft=n_fft, power=1) + deterministic_transform = _DeterministicWrapper(masking_transform(400)) + self.assert_grad(deterministic_transform, [spectrogram]) + + @parameterized.expand([(T.TimeMasking,), (T.FrequencyMasking,)]) + def test_masking_iid(self, masking_transform): + sample_rate = 8000 + n_fft = 400 + specs = [get_spectrogram( + get_whitenoise(sample_rate=sample_rate, duration=0.05, n_channels=2, seed=i), + n_fft=n_fft, power=1) + for i in range(3) + ] + + batch = torch.stack(specs) + assert batch.ndim == 4 + deterministic_transform = _DeterministicWrapper(masking_transform(400, True)) + self.assert_grad(deterministic_transform, [batch]) + + def test_spectral_centroid(self): + sample_rate = 8000 + transform = T.SpectralCentroid(sample_rate=sample_rate) + waveform = get_whitenoise(sample_rate=sample_rate, duration=0.05, n_channels=2) + self.assert_grad(transform, [waveform], nondet_tol=1e-10) + + def test_amplitude_to_db(self): + sample_rate = 8000 + transform = T.AmplitudeToDB() + waveform = get_whitenoise(sample_rate=sample_rate, duration=0.05, n_channels=2) + self.assert_grad(transform, [waveform]) + + def test_melscale(self): + sample_rate = 8000 + n_fft = 400 + n_mels = n_fft // 2 + 1 + transform = T.MelScale(sample_rate=sample_rate, n_mels=n_mels) + spec = get_spectrogram( + get_whitenoise(sample_rate=sample_rate, duration=0.05, n_channels=2), + n_fft=n_fft, power=1) + self.assert_grad(transform, [spec]) + + @parameterized.expand([(1.5, "amplitude"), (2, "power"), (10, "db")]) + def test_vol(self, gain, gain_type): + sample_rate = 8000 + transform = T.Vol(gain=gain, gain_type=gain_type) + waveform = get_whitenoise(sample_rate=sample_rate, duration=0.05, n_channels=2) + self.assert_grad(transform, [waveform]) + + @parameterized.expand([ + ({'cmn_window': 100, 'min_cmn_window': 50, 'center': False, 'norm_vars': False}, ), + ({'cmn_window': 100, 'min_cmn_window': 50, 'center': True, 'norm_vars': False}, ), + ({'cmn_window': 100, 'min_cmn_window': 50, 'center': False, 'norm_vars': True}, ), + ({'cmn_window': 100, 'min_cmn_window': 50, 'center': True, 'norm_vars': True}, ), + ]) + def test_sliding_window_cmn(self, kwargs): + n_fft = 10 + power = 1 + spec = get_spectrogram( + get_whitenoise(sample_rate=200, duration=0.05, n_channels=2), + n_fft=n_fft, power=power) + spec_reshaped = spec.transpose(-1, -2) + + transform = T.SlidingWindowCmn(**kwargs) + self.assert_grad(transform, [spec_reshaped]) + + @unittest.expectedFailure + def test_timestretch_zeros_fail(self): + """Test that ``T.TimeStretch`` fails gradcheck at 0 + + This is because ``F.phase_vocoder`` converts data from cartesian to polar coordinate, + which performs ``atan2(img, real)``, and gradient is not defined at 0. + """ + n_fft = 16 + transform = T.TimeStretch(n_freq=n_fft // 2 + 1, fixed_rate=0.99) + waveform = torch.zeros(2, 40) + spectrogram = get_spectrogram(waveform, n_fft=n_fft, power=None) + self.assert_grad(transform, [spectrogram]) + + @nested_params( + [0.7, 0.8, 0.9, 1.0, 1.3], + [False, True], + ) + def test_timestretch_non_zero(self, rate, test_pseudo_complex): + """Verify that ``T.TimeStretch`` does not fail if it's not close to 0 + + ``T.TimeStrech`` is not differentiable around 0, so this test checks the differentiability + for cases where input is not zero. + + As tested above, when spectrogram contains values close to zero, the gradients are unstable + and gradcheck fails. + + In this test, we generate spectrogram from random signal, then we push the points around + zero away from the origin. + + This process does not reflect the real use-case, and it is not practical for users, but + this helps us understand to what degree the function is differentiable and when not. + """ + n_fft = 16 + transform = T.TimeStretch(n_freq=n_fft // 2 + 1, fixed_rate=rate) + waveform = get_whitenoise(sample_rate=40, duration=1, n_channels=2) + spectrogram = get_spectrogram(waveform, n_fft=n_fft, power=None) + + # 1e-3 is too small (on CPU) + epsilon = 1e-2 + too_close = spectrogram.abs() < epsilon + spectrogram[too_close] = epsilon * spectrogram[too_close] / spectrogram[too_close].abs() + if test_pseudo_complex: + spectrogram = torch.view_as_real(spectrogram) + self.assert_grad(transform, [spectrogram]) + + def test_psd(self): + transform = T.PSD() + waveform = get_whitenoise(sample_rate=8000, duration=0.05, n_channels=2) + spectrogram = get_spectrogram(waveform, n_fft=400) + self.assert_grad(transform, [spectrogram]) + + @parameterized.expand([ + [True], + [False], + ]) + def test_psd_with_mask(self, multi_mask): + transform = T.PSD(multi_mask=multi_mask) + waveform = get_whitenoise(sample_rate=8000, duration=0.05, n_channels=2) + spectrogram = get_spectrogram(waveform, n_fft=400) + if multi_mask: + mask = torch.rand(spectrogram.shape[-3:]) + else: + mask = torch.rand(spectrogram.shape[-2:]) + + self.assert_grad(transform, [spectrogram, mask]) + + @parameterized.expand([ + "ref_channel", + # stv_power test time too long, comment for now + # "stv_power", + # stv_evd will fail since the eigenvalues are not distinct + # "stv_evd", + ]) + def test_mvdr(self, solution): + transform = T.MVDR(solution=solution) + waveform = get_whitenoise(sample_rate=8000, duration=0.05, n_channels=2) + spectrogram = get_spectrogram(waveform, n_fft=400) + mask_s = torch.rand(spectrogram.shape[-2:]) + mask_n = torch.rand(spectrogram.shape[-2:]) + self.assert_grad(transform, [spectrogram, mask_s, mask_n]) + + +class AutogradTestFloat32(TestBaseMixin): + def assert_grad( + self, + transform: torch.nn.Module, + inputs: List[torch.Tensor], + ): + inputs_ = [] + for i in inputs: + if torch.is_tensor(i): + i = i.to(dtype=torch.float32, device=self.device) + inputs_.append(i) + # gradcheck with float32 requires higher atol and epsilon + assert gradcheck(transform, inputs, eps=1e-3, atol=1e-3, nondet_tol=0.) + + @parameterized.expand([ + (rnnt_utils.get_B1_T10_U3_D4_data, ), + (rnnt_utils.get_B2_T4_U3_D3_data, ), + (rnnt_utils.get_B1_T2_U3_D5_data, ), + ]) + def test_rnnt_loss(self, data_func): + def get_data(data_func, device): + data = data_func() + if type(data) == tuple: + data = data[0] + return data + + data = get_data(data_func, self.device) + inputs = ( + data["logits"].to(torch.float32), + data["targets"], + data["logit_lengths"], + data["target_lengths"], + ) + loss = T.RNNTLoss(blank=data["blank"]) + + self.assert_grad(loss, inputs) diff --git a/test/torchaudio_unittest/transforms/batch_consistency_test.py b/test/torchaudio_unittest/transforms/batch_consistency_test.py new file mode 100644 index 00000000..aa1f3817 --- /dev/null +++ b/test/torchaudio_unittest/transforms/batch_consistency_test.py @@ -0,0 +1,228 @@ +"""Test numerical consistency among single input and batched input.""" +import torch +from parameterized import parameterized +from torchaudio import transforms as T + +from torchaudio_unittest import common_utils + + +class TestTransforms(common_utils.TorchaudioTestCase): + """Test suite for classes defined in `transforms` module""" + backend = 'default' + + def assert_batch_consistency( + self, transform, batch, *args, atol=1e-8, rtol=1e-5, seed=42, + **kwargs): + n = batch.size(0) + + # Compute items separately, then batch the result + torch.random.manual_seed(seed) + items_input = batch.clone() + items_result = torch.stack([ + transform(items_input[i], *args, **kwargs) for i in range(n) + ]) + + # Batch the input and run + torch.random.manual_seed(seed) + batch_input = batch.clone() + batch_result = transform(batch_input, *args, **kwargs) + + self.assertEqual(items_input, batch_input, rtol=rtol, atol=atol) + self.assertEqual(items_result, batch_result, rtol=rtol, atol=atol) + + def test_batch_AmplitudeToDB(self): + spec = torch.rand((3, 2, 6, 201)) + transform = T.AmplitudeToDB() + + self.assert_batch_consistency(transform, spec) + + def test_batch_Resample(self): + waveform = torch.randn(3, 2, 2786) + transform = T.Resample() + + self.assert_batch_consistency(transform, waveform) + + def test_batch_MelScale(self): + specgram = torch.randn(3, 2, 201, 256) + transform = T.MelScale() + + self.assert_batch_consistency(transform, specgram) + + def test_batch_InverseMelScale(self): + n_mels = 32 + n_stft = 5 + mel_spec = torch.randn(3, 2, n_mels, 32) ** 2 + transform = T.InverseMelScale(n_stft, n_mels) + + # Because InverseMelScale runs SGD on randomly initialized values so they do not yield + # exactly same result. For this reason, tolerance is very relaxed here. + self.assert_batch_consistency(transform, mel_spec, atol=1.0, rtol=1e-5) + + def test_batch_compute_deltas(self): + specgram = torch.randn(3, 2, 31, 2786) + transform = T.ComputeDeltas() + + self.assert_batch_consistency(transform, specgram) + + def test_batch_mulaw(self): + waveform = common_utils.get_whitenoise(sample_rate=8000, duration=1, n_channels=6) + waveform = waveform.reshape(3, 2, -1) + + # Single then transform then batch + expected = [T.MuLawEncoding()(waveform[i]) for i in range(3)] + expected = torch.stack(expected) + + # Batch then transform + computed = T.MuLawEncoding()(waveform) + + # shape = (3, 2, 201, 1394) + self.assertEqual(computed, expected) + + # Single then transform then batch + expected_decoded = [T.MuLawDecoding()(expected[i]) for i in range(3)] + expected_decoded = torch.stack(expected_decoded) + + # Batch then transform + computed_decoded = T.MuLawDecoding()(computed) + + # shape = (3, 2, 201, 1394) + self.assertEqual(computed_decoded, expected_decoded) + + def test_batch_spectrogram(self): + waveform = common_utils.get_whitenoise(sample_rate=8000, duration=1, n_channels=6) + waveform = waveform.reshape(3, 2, -1) + transform = T.Spectrogram() + + self.assert_batch_consistency(transform, waveform) + + def test_batch_inverse_spectrogram(self): + waveform = common_utils.get_whitenoise(sample_rate=8000, duration=1, n_channels=6) + specgram = common_utils.get_spectrogram(waveform, n_fft=400) + specgram = specgram.reshape(3, 2, specgram.shape[-2], specgram.shape[-1]) + transform = T.InverseSpectrogram(n_fft=400) + + self.assert_batch_consistency(transform, specgram) + + def test_batch_melspectrogram(self): + waveform = common_utils.get_whitenoise(sample_rate=8000, duration=1, n_channels=6) + waveform = waveform.reshape(3, 2, -1) + transform = T.MelSpectrogram() + + self.assert_batch_consistency(transform, waveform) + + def test_batch_mfcc(self): + waveform = common_utils.get_whitenoise(sample_rate=8000, duration=1, n_channels=6) + waveform = waveform.reshape(3, 2, -1) + transform = T.MFCC() + + self.assert_batch_consistency(transform, waveform, atol=1e-4, rtol=1e-5) + + def test_batch_lfcc(self): + waveform = common_utils.get_whitenoise(sample_rate=8000, duration=1, n_channels=6) + waveform = waveform.reshape(3, 2, -1) + transform = T.LFCC() + + self.assert_batch_consistency(transform, waveform, atol=1e-4, rtol=1e-5) + + @parameterized.expand([(True, ), (False, )]) + def test_batch_TimeStretch(self, test_pseudo_complex): + rate = 2 + num_freq = 1025 + num_frames = 400 + batch = 3 + + spec = torch.randn(batch, num_freq, num_frames, dtype=torch.complex64) + if test_pseudo_complex: + spec = torch.view_as_real(spec) + + transform = T.TimeStretch( + fixed_rate=rate, + n_freq=num_freq, + hop_length=512 + ) + + self.assert_batch_consistency(transform, spec, atol=1e-5, rtol=1e-5) + + def test_batch_Fade(self): + waveform = common_utils.get_whitenoise(sample_rate=8000, duration=1, n_channels=6) + waveform = waveform.reshape(3, 2, -1) + fade_in_len = 3000 + fade_out_len = 3000 + transform = T.Fade(fade_in_len, fade_out_len) + + self.assert_batch_consistency(transform, waveform) + + def test_batch_Vol(self): + waveform = common_utils.get_whitenoise(sample_rate=8000, duration=1, n_channels=6) + waveform = waveform.reshape(3, 2, -1) + transform = T.Vol(gain=1.1) + + self.assert_batch_consistency(transform, waveform) + + def test_batch_spectral_centroid(self): + sample_rate = 44100 + waveform = common_utils.get_whitenoise(sample_rate=sample_rate, n_channels=6) + waveform = waveform.reshape(3, 2, -1) + transform = T.SpectralCentroid(sample_rate) + + self.assert_batch_consistency(transform, waveform) + + def test_batch_pitch_shift(self): + sample_rate = 8000 + n_steps = -2 + waveform = common_utils.get_whitenoise(sample_rate=sample_rate, duration=0.05, n_channels=6) + waveform = waveform.reshape(3, 2, -1) + transform = T.PitchShift(sample_rate, n_steps, n_fft=400) + + self.assert_batch_consistency(transform, waveform) + + def test_batch_PSD(self): + waveform = common_utils.get_whitenoise(sample_rate=8000, duration=1, n_channels=6) + specgram = common_utils.get_spectrogram(waveform, n_fft=400) + specgram = specgram.reshape(3, 2, specgram.shape[-2], specgram.shape[-1]) + transform = T.PSD() + + self.assert_batch_consistency(transform, specgram) + + def test_batch_PSD_with_mask(self): + waveform = common_utils.get_whitenoise(sample_rate=8000, duration=1, n_channels=6) + waveform = waveform.to(torch.double) + specgram = common_utils.get_spectrogram(waveform, n_fft=400) + specgram = specgram.reshape(3, 2, specgram.shape[-2], specgram.shape[-1]) + mask = torch.rand((3, specgram.shape[-2], specgram.shape[-1])) + transform = T.PSD() + + # Single then transform then batch + expected = [transform(specgram[i], mask[i]) for i in range(3)] + expected = torch.stack(expected) + + # Batch then transform + computed = transform(specgram, mask) + + self.assertEqual(computed, expected) + + @parameterized.expand([ + [True], + [False], + ]) + def test_MVDR(self, multi_mask): + waveform = common_utils.get_whitenoise(sample_rate=8000, duration=1, n_channels=6) + waveform = waveform.to(torch.double) + specgram = common_utils.get_spectrogram(waveform, n_fft=400) + specgram = specgram.reshape(3, 2, specgram.shape[-2], specgram.shape[-1]) + if multi_mask: + mask_s = torch.rand((3, 2, specgram.shape[-2], specgram.shape[-1])) + mask_n = torch.rand((3, 2, specgram.shape[-2], specgram.shape[-1])) + else: + mask_s = torch.rand((3, specgram.shape[-2], specgram.shape[-1])) + mask_n = torch.rand((3, specgram.shape[-2], specgram.shape[-1])) + transform = T.MVDR(multi_mask=multi_mask) + + # Single then transform then batch + expected = [transform(specgram[i], mask_s[i], mask_n[i]) for i in range(3)] + expected = torch.stack(expected) + + # Batch then transform + computed = transform(specgram, mask_s, mask_n) + + self.assertEqual(computed, expected) diff --git a/test/torchaudio_unittest/transforms/kaldi_compatibility_cpu_test.py b/test/torchaudio_unittest/transforms/kaldi_compatibility_cpu_test.py new file mode 100644 index 00000000..43be412b --- /dev/null +++ b/test/torchaudio_unittest/transforms/kaldi_compatibility_cpu_test.py @@ -0,0 +1,14 @@ +import torch + +from torchaudio_unittest import common_utils +from .kaldi_compatibility_impl import Kaldi + + +class TestKaldiFloat32(Kaldi, common_utils.PytorchTestCase): + dtype = torch.float32 + device = torch.device('cpu') + + +class TestKaldiFloat64(Kaldi, common_utils.PytorchTestCase): + dtype = torch.float64 + device = torch.device('cpu') diff --git a/test/torchaudio_unittest/transforms/kaldi_compatibility_cuda_test.py b/test/torchaudio_unittest/transforms/kaldi_compatibility_cuda_test.py new file mode 100644 index 00000000..28adb7fc --- /dev/null +++ b/test/torchaudio_unittest/transforms/kaldi_compatibility_cuda_test.py @@ -0,0 +1,16 @@ +import torch + +from torchaudio_unittest import common_utils +from .kaldi_compatibility_impl import Kaldi + + +@common_utils.skipIfNoCuda +class TestKaldiFloat32(Kaldi, common_utils.PytorchTestCase): + dtype = torch.float32 + device = torch.device('cuda') + + +@common_utils.skipIfNoCuda +class TestKaldiFloat64(Kaldi, common_utils.PytorchTestCase): + dtype = torch.float64 + device = torch.device('cuda') diff --git a/test/torchaudio_unittest/transforms/kaldi_compatibility_impl.py b/test/torchaudio_unittest/transforms/kaldi_compatibility_impl.py new file mode 100644 index 00000000..9a0e6ab8 --- /dev/null +++ b/test/torchaudio_unittest/transforms/kaldi_compatibility_impl.py @@ -0,0 +1,55 @@ +"""Test suites for checking numerical compatibility against Kaldi""" +import torchaudio.compliance.kaldi +from parameterized import parameterized + +from torchaudio_unittest.common_utils import ( + TestBaseMixin, + TempDirMixin, + load_params, + skipIfNoExec, + get_asset_path, + load_wav, +) +from torchaudio_unittest.common_utils.kaldi_utils import ( + convert_args, + run_kaldi, +) + + +class Kaldi(TempDirMixin, TestBaseMixin): + def assert_equal(self, output, *, expected, rtol=None, atol=None): + expected = expected.to(dtype=self.dtype, device=self.device) + self.assertEqual(output, expected, rtol=rtol, atol=atol) + + @parameterized.expand(load_params('kaldi_test_fbank_args.jsonl')) + @skipIfNoExec('compute-fbank-feats') + def test_fbank(self, kwargs): + """fbank should be numerically compatible with compute-fbank-feats""" + wave_file = get_asset_path('kaldi_file.wav') + waveform = load_wav(wave_file, normalize=False)[0].to(dtype=self.dtype, device=self.device) + result = torchaudio.compliance.kaldi.fbank(waveform, **kwargs) + command = ['compute-fbank-feats'] + convert_args(**kwargs) + ['scp:-', 'ark:-'] + kaldi_result = run_kaldi(command, 'scp', wave_file) + self.assert_equal(result, expected=kaldi_result, rtol=1e-4, atol=1e-8) + + @parameterized.expand(load_params('kaldi_test_spectrogram_args.jsonl')) + @skipIfNoExec('compute-spectrogram-feats') + def test_spectrogram(self, kwargs): + """spectrogram should be numerically compatible with compute-spectrogram-feats""" + wave_file = get_asset_path('kaldi_file.wav') + waveform = load_wav(wave_file, normalize=False)[0].to(dtype=self.dtype, device=self.device) + result = torchaudio.compliance.kaldi.spectrogram(waveform, **kwargs) + command = ['compute-spectrogram-feats'] + convert_args(**kwargs) + ['scp:-', 'ark:-'] + kaldi_result = run_kaldi(command, 'scp', wave_file) + self.assert_equal(result, expected=kaldi_result, rtol=1e-4, atol=1e-8) + + @parameterized.expand(load_params('kaldi_test_mfcc_args.jsonl')) + @skipIfNoExec('compute-mfcc-feats') + def test_mfcc(self, kwargs): + """mfcc should be numerically compatible with compute-mfcc-feats""" + wave_file = get_asset_path('kaldi_file.wav') + waveform = load_wav(wave_file, normalize=False)[0].to(dtype=self.dtype, device=self.device) + result = torchaudio.compliance.kaldi.mfcc(waveform, **kwargs) + command = ['compute-mfcc-feats'] + convert_args(**kwargs) + ['scp:-', 'ark:-'] + kaldi_result = run_kaldi(command, 'scp', wave_file) + self.assert_equal(result, expected=kaldi_result, rtol=1e-4, atol=1e-8) diff --git a/test/torchaudio_unittest/transforms/librosa_compatibility_cpu_test.py b/test/torchaudio_unittest/transforms/librosa_compatibility_cpu_test.py new file mode 100644 index 00000000..300f4a3f --- /dev/null +++ b/test/torchaudio_unittest/transforms/librosa_compatibility_cpu_test.py @@ -0,0 +1,9 @@ +import torch + +from torchaudio_unittest.common_utils import PytorchTestCase +from .librosa_compatibility_test_impl import TransformsTestBase + + +class TestTransforms(TransformsTestBase, PytorchTestCase): + dtype = torch.float64 + device = torch.device('cpu') diff --git a/test/torchaudio_unittest/transforms/librosa_compatibility_cuda_test.py b/test/torchaudio_unittest/transforms/librosa_compatibility_cuda_test.py new file mode 100644 index 00000000..d553458a --- /dev/null +++ b/test/torchaudio_unittest/transforms/librosa_compatibility_cuda_test.py @@ -0,0 +1,10 @@ +import torch + +from torchaudio_unittest.common_utils import PytorchTestCase, skipIfNoCuda +from .librosa_compatibility_test_impl import TransformsTestBase + + +@skipIfNoCuda +class TestTransforms(TransformsTestBase, PytorchTestCase): + dtype = torch.float64 + device = torch.device('cuda') diff --git a/test/torchaudio_unittest/transforms/librosa_compatibility_test_impl.py b/test/torchaudio_unittest/transforms/librosa_compatibility_test_impl.py new file mode 100644 index 00000000..9bc8edbb --- /dev/null +++ b/test/torchaudio_unittest/transforms/librosa_compatibility_test_impl.py @@ -0,0 +1,141 @@ +import unittest + +import torch +import torchaudio.transforms as T +from torchaudio._internal.module_utils import is_module_available +from parameterized import param, parameterized + +from torchaudio_unittest.common_utils import ( + TestBaseMixin, + get_whitenoise, + get_sinusoid, + get_spectrogram, + nested_params, +) + +LIBROSA_AVAILABLE = is_module_available('librosa') + +if LIBROSA_AVAILABLE: + import librosa + + +@unittest.skipIf(not LIBROSA_AVAILABLE, "Librosa not available") +class TransformsTestBase(TestBaseMixin): + @parameterized.expand([ + param(n_fft=400, hop_length=200, power=2.0), + param(n_fft=600, hop_length=100, power=2.0), + param(n_fft=400, hop_length=200, power=3.0), + param(n_fft=200, hop_length=50, power=2.0), + ]) + def test_Spectrogram(self, n_fft, hop_length, power): + sample_rate = 16000 + waveform = get_whitenoise( + sample_rate=sample_rate, n_channels=1, + ).to(self.device, self.dtype) + + expected = librosa.core.spectrum._spectrogram( + y=waveform[0].cpu().numpy(), + n_fft=n_fft, hop_length=hop_length, power=power)[0] + + result = T.Spectrogram( + n_fft=n_fft, hop_length=hop_length, power=power, + ).to(self.device, self.dtype)(waveform)[0] + self.assertEqual(result, torch.from_numpy(expected), atol=1e-5, rtol=1e-5) + + def test_Spectrogram_complex(self): + n_fft = 400 + hop_length = 200 + sample_rate = 16000 + waveform = get_whitenoise( + sample_rate=sample_rate, n_channels=1, + ).to(self.device, self.dtype) + + expected = librosa.core.spectrum._spectrogram( + y=waveform[0].cpu().numpy(), + n_fft=n_fft, hop_length=hop_length, power=1)[0] + + result = T.Spectrogram( + n_fft=n_fft, hop_length=hop_length, power=None, return_complex=True, + ).to(self.device, self.dtype)(waveform)[0] + self.assertEqual(result.abs(), torch.from_numpy(expected), atol=1e-5, rtol=1e-5) + + @nested_params( + [ + param(n_fft=400, hop_length=200, n_mels=64), + param(n_fft=600, hop_length=100, n_mels=128), + param(n_fft=200, hop_length=50, n_mels=32), + ], + [param(norm=norm) for norm in [None, 'slaney']], + [param(mel_scale=mel_scale) for mel_scale in ['htk', 'slaney']], + ) + def test_MelSpectrogram(self, n_fft, hop_length, n_mels, norm, mel_scale): + sample_rate = 16000 + waveform = get_sinusoid( + sample_rate=sample_rate, n_channels=1, + ).to(self.device, self.dtype) + + expected = librosa.feature.melspectrogram( + y=waveform[0].cpu().numpy(), + sr=sample_rate, n_fft=n_fft, + hop_length=hop_length, n_mels=n_mels, norm=norm, + htk=mel_scale == "htk") + result = T.MelSpectrogram( + sample_rate=sample_rate, window_fn=torch.hann_window, + hop_length=hop_length, n_mels=n_mels, + n_fft=n_fft, norm=norm, mel_scale=mel_scale, + ).to(self.device, self.dtype)(waveform)[0] + self.assertEqual(result, torch.from_numpy(expected), atol=5e-4, rtol=1e-5) + + def test_magnitude_to_db(self): + spectrogram = get_spectrogram( + get_whitenoise(), n_fft=400, power=2).to(self.device, self.dtype) + result = T.AmplitudeToDB('magnitude', 80.).to(self.device, self.dtype)(spectrogram)[0] + expected = librosa.core.spectrum.amplitude_to_db(spectrogram[0].cpu().numpy()) + self.assertEqual(result, torch.from_numpy(expected)) + + def test_power_to_db(self): + spectrogram = get_spectrogram( + get_whitenoise(), n_fft=400, power=2).to(self.device, self.dtype) + result = T.AmplitudeToDB('power', 80.).to(self.device, self.dtype)(spectrogram)[0] + expected = librosa.core.spectrum.power_to_db(spectrogram[0].cpu().numpy()) + self.assertEqual(result, torch.from_numpy(expected)) + + @nested_params([ + param(n_fft=400, hop_length=200, n_mels=64, n_mfcc=40), + param(n_fft=600, hop_length=100, n_mels=128, n_mfcc=20), + param(n_fft=200, hop_length=50, n_mels=32, n_mfcc=25), + ]) + def test_mfcc(self, n_fft, hop_length, n_mels, n_mfcc): + sample_rate = 16000 + waveform = get_whitenoise( + sample_rate=sample_rate, n_channels=1).to(self.device, self.dtype) + result = T.MFCC( + sample_rate=sample_rate, n_mfcc=n_mfcc, norm='ortho', + melkwargs={'hop_length': hop_length, 'n_fft': n_fft, 'n_mels': n_mels}, + ).to(self.device, self.dtype)(waveform)[0] + + melspec = librosa.feature.melspectrogram( + y=waveform[0].cpu().numpy(), sr=sample_rate, n_fft=n_fft, + win_length=n_fft, hop_length=hop_length, + n_mels=n_mels, htk=True, norm=None) + expected = librosa.feature.mfcc( + S=librosa.core.spectrum.power_to_db(melspec), + n_mfcc=n_mfcc, dct_type=2, norm='ortho') + self.assertEqual(result, torch.from_numpy(expected), atol=5e-4, rtol=1e-5) + + @parameterized.expand([ + param(n_fft=400, hop_length=200), + param(n_fft=600, hop_length=100), + param(n_fft=200, hop_length=50), + ]) + def test_spectral_centroid(self, n_fft, hop_length): + sample_rate = 16000 + waveform = get_whitenoise( + sample_rate=sample_rate, n_channels=1).to(self.device, self.dtype) + + result = T.SpectralCentroid( + sample_rate=sample_rate, n_fft=n_fft, hop_length=hop_length, + ).to(self.device, self.dtype)(waveform) + expected = librosa.feature.spectral_centroid( + y=waveform[0].cpu().numpy(), sr=sample_rate, n_fft=n_fft, hop_length=hop_length) + self.assertEqual(result, torch.from_numpy(expected), atol=5e-4, rtol=1e-5) diff --git a/test/torchaudio_unittest/transforms/sox_compatibility_test.py b/test/torchaudio_unittest/transforms/sox_compatibility_test.py new file mode 100644 index 00000000..be6c9020 --- /dev/null +++ b/test/torchaudio_unittest/transforms/sox_compatibility_test.py @@ -0,0 +1,88 @@ +import warnings + +import torch +import torchaudio.transforms as T +from parameterized import parameterized + +from torchaudio_unittest.common_utils import ( + skipIfNoSox, + skipIfNoExec, + TempDirMixin, + TorchaudioTestCase, + get_asset_path, + sox_utils, + load_wav, + save_wav, + get_whitenoise, +) + + +@skipIfNoSox +@skipIfNoExec('sox') +class TestFunctionalFiltering(TempDirMixin, TorchaudioTestCase): + def run_sox_effect(self, input_file, effect): + output_file = self.get_temp_path('expected.wav') + sox_utils.run_sox_effect(input_file, output_file, [str(e) for e in effect]) + return load_wav(output_file) + + def assert_sox_effect(self, result, input_path, effects, atol=1e-04, rtol=1e-5): + expected, _ = self.run_sox_effect(input_path, effects) + self.assertEqual(result, expected, atol=atol, rtol=rtol) + + def get_whitenoise(self, sample_rate=8000): + noise = get_whitenoise( + sample_rate=sample_rate, duration=3, scale_factor=0.9, + ) + path = self.get_temp_path("whitenoise.wav") + save_wav(path, noise, sample_rate) + return noise, path + + @parameterized.expand([ + ('q', 'quarter_sine'), + ('h', 'half_sine'), + ('t', 'linear'), + ]) + def test_fade(self, fade_shape_sox, fade_shape): + fade_in_len, fade_out_len = 44100, 44100 + data, path = self.get_whitenoise(sample_rate=44100) + result = T.Fade(fade_in_len, fade_out_len, fade_shape)(data) + self.assert_sox_effect(result, path, ['fade', fade_shape_sox, '1', '0', '1']) + + @parameterized.expand([ + ('amplitude', 1.1), + ('db', 2), + ('power', 2), + ]) + def test_vol(self, gain_type, gain): + data, path = self.get_whitenoise() + result = T.Vol(gain, gain_type)(data) + self.assert_sox_effect(result, path, ['vol', f'{gain}', gain_type]) + + @parameterized.expand(['vad-go-stereo-44100.wav', 'vad-go-mono-32000.wav']) + def test_vad(self, filename): + path = get_asset_path(filename) + data, sample_rate = load_wav(path) + result = T.Vad(sample_rate)(data) + self.assert_sox_effect(result, path, ['vad']) + + def test_vad_warning(self): + """vad should throw a warning if input dimension is greater than 2""" + sample_rate = 41100 + + data = torch.rand(5, 5, sample_rate) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + T.Vad(sample_rate)(data) + assert len(w) == 1 + + data = torch.rand(5, sample_rate) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + T.Vad(sample_rate)(data) + assert len(w) == 0 + + data = torch.rand(sample_rate) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + T.Vad(sample_rate)(data) + assert len(w) == 0 diff --git a/test/torchaudio_unittest/transforms/torchscript_consistency_cpu_test.py b/test/torchaudio_unittest/transforms/torchscript_consistency_cpu_test.py new file mode 100644 index 00000000..eb5b2d8b --- /dev/null +++ b/test/torchaudio_unittest/transforms/torchscript_consistency_cpu_test.py @@ -0,0 +1,14 @@ +import torch + +from torchaudio_unittest.common_utils import PytorchTestCase +from .torchscript_consistency_impl import Transforms, TransformsFloat32Only, TransformsFloat64Only + + +class TestTransformsFloat32(Transforms, TransformsFloat32Only, PytorchTestCase): + dtype = torch.float32 + device = torch.device('cpu') + + +class TestTransformsFloat64(Transforms, TransformsFloat64Only, PytorchTestCase): + dtype = torch.float64 + device = torch.device('cpu') diff --git a/test/torchaudio_unittest/transforms/torchscript_consistency_cuda_test.py b/test/torchaudio_unittest/transforms/torchscript_consistency_cuda_test.py new file mode 100644 index 00000000..81a81b82 --- /dev/null +++ b/test/torchaudio_unittest/transforms/torchscript_consistency_cuda_test.py @@ -0,0 +1,16 @@ +import torch + +from torchaudio_unittest.common_utils import skipIfNoCuda, PytorchTestCase +from .torchscript_consistency_impl import Transforms, TransformsFloat32Only, TransformsFloat64Only + + +@skipIfNoCuda +class TestTransformsFloat32(Transforms, TransformsFloat32Only, PytorchTestCase): + dtype = torch.float32 + device = torch.device('cuda') + + +@skipIfNoCuda +class TestTransformsFloat64(Transforms, TransformsFloat64Only, PytorchTestCase): + dtype = torch.float64 + device = torch.device('cuda') diff --git a/test/torchaudio_unittest/transforms/torchscript_consistency_impl.py b/test/torchaudio_unittest/transforms/torchscript_consistency_impl.py new file mode 100644 index 00000000..27f57adb --- /dev/null +++ b/test/torchaudio_unittest/transforms/torchscript_consistency_impl.py @@ -0,0 +1,202 @@ +"""Test suites for jit-ability and its numerical compatibility""" + +import torch +import torchaudio.transforms as T +from parameterized import parameterized + +from torchaudio_unittest import common_utils +from torchaudio_unittest.common_utils import ( + skipIfRocm, + TestBaseMixin, + torch_script, +) + + +class Transforms(TestBaseMixin): + """Implements test for Transforms that are performed for different devices""" + def _assert_consistency(self, transform, tensor, *args): + tensor = tensor.to(device=self.device, dtype=self.dtype) + transform = transform.to(device=self.device, dtype=self.dtype) + + ts_transform = torch_script(transform) + + output = transform(tensor, *args) + ts_output = ts_transform(tensor, *args) + self.assertEqual(ts_output, output) + + def _assert_consistency_complex(self, transform, tensor, test_pseudo_complex=False, *args): + assert tensor.is_complex() + tensor = tensor.to(device=self.device, dtype=self.complex_dtype) + transform = transform.to(device=self.device, dtype=self.dtype) + + ts_transform = torch_script(transform) + + if test_pseudo_complex: + tensor = torch.view_as_real(tensor) + output = transform(tensor, *args) + ts_output = ts_transform(tensor, *args) + self.assertEqual(ts_output, output) + + def test_Spectrogram(self): + tensor = torch.rand((1, 1000)) + self._assert_consistency(T.Spectrogram(), tensor) + + def test_Spectrogram_return_complex(self): + tensor = torch.rand((1, 1000)) + self._assert_consistency(T.Spectrogram(power=None, return_complex=True), tensor) + + def test_InverseSpectrogram(self): + tensor = common_utils.get_whitenoise(sample_rate=8000) + spectrogram = common_utils.get_spectrogram(tensor, n_fft=400, hop_length=100) + self._assert_consistency_complex(T.InverseSpectrogram(n_fft=400, hop_length=100), spectrogram) + + def test_InverseSpectrogram_pseudocomplex(self): + tensor = common_utils.get_whitenoise(sample_rate=8000) + spectrogram = common_utils.get_spectrogram(tensor, n_fft=400, hop_length=100) + spectrogram = torch.view_as_real(spectrogram) + self._assert_consistency(T.InverseSpectrogram(n_fft=400, hop_length=100), spectrogram) + + @skipIfRocm + def test_GriffinLim(self): + tensor = torch.rand((1, 201, 6)) + self._assert_consistency(T.GriffinLim(length=1000, rand_init=False), tensor) + + def test_AmplitudeToDB(self): + spec = torch.rand((6, 201)) + self._assert_consistency(T.AmplitudeToDB(), spec) + + def test_MelScale(self): + spec_f = torch.rand((1, 201, 6)) + self._assert_consistency(T.MelScale(n_stft=201), spec_f) + + def test_MelSpectrogram(self): + tensor = torch.rand((1, 1000)) + self._assert_consistency(T.MelSpectrogram(), tensor) + + def test_MFCC(self): + tensor = torch.rand((1, 1000)) + self._assert_consistency(T.MFCC(), tensor) + + def test_LFCC(self): + tensor = torch.rand((1, 1000)) + self._assert_consistency(T.LFCC(), tensor) + + def test_Resample(self): + sr1, sr2 = 16000, 8000 + tensor = common_utils.get_whitenoise(sample_rate=sr1) + self._assert_consistency(T.Resample(sr1, sr2), tensor) + + def test_ComplexNorm(self): + tensor = torch.rand((1, 2, 201, 2)) + self._assert_consistency(T.ComplexNorm(), tensor) + + def test_MuLawEncoding(self): + tensor = common_utils.get_whitenoise() + self._assert_consistency(T.MuLawEncoding(), tensor) + + def test_MuLawDecoding(self): + tensor = torch.rand((1, 10)) + self._assert_consistency(T.MuLawDecoding(), tensor) + + def test_Fade(self): + waveform = common_utils.get_whitenoise() + fade_in_len = 3000 + fade_out_len = 3000 + self._assert_consistency(T.Fade(fade_in_len, fade_out_len), waveform) + + def test_FrequencyMasking(self): + tensor = torch.rand((10, 2, 50, 10, 2)) + self._assert_consistency(T.FrequencyMasking(freq_mask_param=60, iid_masks=False), tensor) + + def test_TimeMasking(self): + tensor = torch.rand((10, 2, 50, 10, 2)) + self._assert_consistency(T.TimeMasking(time_mask_param=30, iid_masks=False), tensor) + + def test_Vol(self): + waveform = common_utils.get_whitenoise() + self._assert_consistency(T.Vol(1.1), waveform) + + def test_SlidingWindowCmn(self): + tensor = torch.rand((1000, 10)) + self._assert_consistency(T.SlidingWindowCmn(), tensor) + + def test_Vad(self): + filepath = common_utils.get_asset_path("vad-go-mono-32000.wav") + waveform, sample_rate = common_utils.load_wav(filepath) + self._assert_consistency(T.Vad(sample_rate=sample_rate), waveform) + + def test_SpectralCentroid(self): + sample_rate = 44100 + waveform = common_utils.get_whitenoise(sample_rate=sample_rate) + self._assert_consistency(T.SpectralCentroid(sample_rate=sample_rate), waveform) + + @parameterized.expand([(True, ), (False, )]) + def test_TimeStretch(self, test_pseudo_complex): + n_freq = 400 + hop_length = 512 + fixed_rate = 1.3 + tensor = torch.view_as_complex(torch.rand((10, 2, n_freq, 10, 2))) + self._assert_consistency_complex( + T.TimeStretch(n_freq=n_freq, hop_length=hop_length, fixed_rate=fixed_rate), + tensor, + test_pseudo_complex + ) + + def test_PitchShift(self): + sample_rate = 8000 + n_steps = 4 + waveform = common_utils.get_whitenoise(sample_rate=sample_rate) + self._assert_consistency( + T.PitchShift(sample_rate=sample_rate, n_steps=n_steps), + waveform + ) + + def test_PSD(self): + tensor = common_utils.get_whitenoise(sample_rate=8000, n_channels=4) + spectrogram = common_utils.get_spectrogram(tensor, n_fft=400, hop_length=100) + spectrogram = spectrogram.to(self.device) + self._assert_consistency_complex(T.PSD(), spectrogram) + + def test_PSD_with_mask(self): + tensor = common_utils.get_whitenoise(sample_rate=8000, n_channels=4) + spectrogram = common_utils.get_spectrogram(tensor, n_fft=400, hop_length=100) + spectrogram = spectrogram.to(self.device) + mask = torch.rand(spectrogram.shape[-2:], device=self.device) + self._assert_consistency_complex(T.PSD(), spectrogram, False, mask) + + +class TransformsFloat32Only(TestBaseMixin): + def test_rnnt_loss(self): + logits = torch.tensor([[[[0.1, 0.6, 0.1, 0.1, 0.1], + [0.1, 0.1, 0.6, 0.1, 0.1], + [0.1, 0.1, 0.2, 0.8, 0.1]], + [[0.1, 0.6, 0.1, 0.1, 0.1], + [0.1, 0.1, 0.2, 0.1, 0.1], + [0.7, 0.1, 0.2, 0.1, 0.1]]]]) + tensor = logits.to(device=self.device, dtype=torch.float32) + targets = torch.tensor([[1, 2]], device=tensor.device, dtype=torch.int32) + logit_lengths = torch.tensor([2], device=tensor.device, dtype=torch.int32) + target_lengths = torch.tensor([2], device=tensor.device, dtype=torch.int32) + + self._assert_consistency(T.RNNTLoss(), logits, targets, logit_lengths, target_lengths) + + +class TransformsFloat64Only(TestBaseMixin): + @parameterized.expand([ + ["ref_channel", True], + ["stv_evd", True], + ["stv_power", True], + ["ref_channel", False], + ["stv_evd", False], + ["stv_power", False], + ]) + def test_MVDR(self, solution, online): + tensor = common_utils.get_whitenoise(sample_rate=8000, n_channels=4) + spectrogram = common_utils.get_spectrogram(tensor, n_fft=400, hop_length=100) + spectrogram = spectrogram.to(device=self.device, dtype=torch.cdouble) + mask_s = torch.rand(spectrogram.shape[-2:], device=self.device) + mask_n = torch.rand(spectrogram.shape[-2:], device=self.device) + self._assert_consistency_complex( + T.MVDR(solution=solution, online=online), + spectrogram, False, mask_s, mask_n + ) diff --git a/test/torchaudio_unittest/transforms/transforms_cpu_test.py b/test/torchaudio_unittest/transforms/transforms_cpu_test.py new file mode 100644 index 00000000..5177b9a4 --- /dev/null +++ b/test/torchaudio_unittest/transforms/transforms_cpu_test.py @@ -0,0 +1,14 @@ +import torch + +from torchaudio_unittest.common_utils import PytorchTestCase +from . transforms_test_impl import TransformsTestBase + + +class TransformsCPUFloat32Test(TransformsTestBase, PytorchTestCase): + device = 'cpu' + dtype = torch.float32 + + +class TransformsCPUFloat64Test(TransformsTestBase, PytorchTestCase): + device = 'cpu' + dtype = torch.float64 diff --git a/test/torchaudio_unittest/transforms/transforms_cuda_test.py b/test/torchaudio_unittest/transforms/transforms_cuda_test.py new file mode 100644 index 00000000..94896603 --- /dev/null +++ b/test/torchaudio_unittest/transforms/transforms_cuda_test.py @@ -0,0 +1,19 @@ +import torch + +from torchaudio_unittest.common_utils import ( + PytorchTestCase, + skipIfNoCuda, +) +from . transforms_test_impl import TransformsTestBase + + +@skipIfNoCuda +class TransformsCUDAFloat32Test(TransformsTestBase, PytorchTestCase): + device = 'cuda' + dtype = torch.float32 + + +@skipIfNoCuda +class TransformsCUDAFloat64Test(TransformsTestBase, PytorchTestCase): + device = 'cuda' + dtype = torch.float64 diff --git a/test/torchaudio_unittest/transforms/transforms_test.py b/test/torchaudio_unittest/transforms/transforms_test.py new file mode 100644 index 00000000..7808789c --- /dev/null +++ b/test/torchaudio_unittest/transforms/transforms_test.py @@ -0,0 +1,314 @@ +import math + +import torch +import torchaudio +import torchaudio.transforms as transforms +import torchaudio.functional as F + +from torchaudio_unittest import common_utils + + +class Tester(common_utils.TorchaudioTestCase): + backend = 'default' + + # create a sinewave signal for testing + sample_rate = 16000 + freq = 440 + volume = .3 + waveform = (torch.cos(2 * math.pi * torch.arange(0, 4 * sample_rate).float() * freq / sample_rate)) + waveform.unsqueeze_(0) # (1, 64000) + waveform = (waveform * volume * 2**31).long() + + def scale(self, waveform, factor=2.0**31): + # scales a waveform by a factor + if not waveform.is_floating_point(): + waveform = waveform.to(torch.get_default_dtype()) + return waveform / factor + + def test_mu_law_companding(self): + + quantization_channels = 256 + + waveform = self.waveform.clone() + if not waveform.is_floating_point(): + waveform = waveform.to(torch.get_default_dtype()) + waveform /= torch.abs(waveform).max() + + self.assertTrue(waveform.min() >= -1. and waveform.max() <= 1.) + + waveform_mu = transforms.MuLawEncoding(quantization_channels)(waveform) + self.assertTrue(waveform_mu.min() >= 0. and waveform_mu.max() <= quantization_channels) + + waveform_exp = transforms.MuLawDecoding(quantization_channels)(waveform_mu) + self.assertTrue(waveform_exp.min() >= -1. and waveform_exp.max() <= 1.) + + def test_AmplitudeToDB(self): + filepath = common_utils.get_asset_path('steam-train-whistle-daniel_simon.wav') + waveform = common_utils.load_wav(filepath)[0] + + mag_to_db_transform = transforms.AmplitudeToDB('magnitude', 80.) + power_to_db_transform = transforms.AmplitudeToDB('power', 80.) + + mag_to_db_torch = mag_to_db_transform(torch.abs(waveform)) + power_to_db_torch = power_to_db_transform(torch.pow(waveform, 2)) + + self.assertEqual(mag_to_db_torch, power_to_db_torch) + + def test_melscale_load_save(self): + specgram = torch.ones(1, 201, 100) + melscale_transform = transforms.MelScale() + melscale_transform(specgram) + + melscale_transform_copy = transforms.MelScale() + melscale_transform_copy.load_state_dict(melscale_transform.state_dict()) + + fb = melscale_transform.fb + fb_copy = melscale_transform_copy.fb + + self.assertEqual(fb_copy.size(), (201, 128)) + self.assertEqual(fb, fb_copy) + + def test_melspectrogram_load_save(self): + waveform = self.waveform.float() + mel_spectrogram_transform = transforms.MelSpectrogram() + mel_spectrogram_transform(waveform) + + mel_spectrogram_transform_copy = transforms.MelSpectrogram() + mel_spectrogram_transform_copy.load_state_dict(mel_spectrogram_transform.state_dict()) + + window = mel_spectrogram_transform.spectrogram.window + window_copy = mel_spectrogram_transform_copy.spectrogram.window + + fb = mel_spectrogram_transform.mel_scale.fb + fb_copy = mel_spectrogram_transform_copy.mel_scale.fb + + self.assertEqual(window, window_copy) + # the default for n_fft = 400 and n_mels = 128 + self.assertEqual(fb_copy.size(), (201, 128)) + self.assertEqual(fb, fb_copy) + + def test_mel2(self): + top_db = 80. + s2db = transforms.AmplitudeToDB('power', top_db) + + waveform = self.waveform.clone() # (1, 16000) + waveform_scaled = self.scale(waveform) # (1, 16000) + mel_transform = transforms.MelSpectrogram() + # check defaults + spectrogram_torch = s2db(mel_transform(waveform_scaled)) # (1, 128, 321) + self.assertTrue(spectrogram_torch.dim() == 3) + self.assertTrue(spectrogram_torch.ge(spectrogram_torch.max() - top_db).all()) + self.assertEqual(spectrogram_torch.size(1), mel_transform.n_mels) + # check correctness of filterbank conversion matrix + self.assertTrue(mel_transform.mel_scale.fb.sum(1).le(1.).all()) + self.assertTrue(mel_transform.mel_scale.fb.sum(1).ge(0.).all()) + # check options + kwargs = {'window_fn': torch.hamming_window, 'pad': 10, 'win_length': 500, + 'hop_length': 125, 'n_fft': 800, 'n_mels': 50} + mel_transform2 = transforms.MelSpectrogram(**kwargs) + spectrogram2_torch = s2db(mel_transform2(waveform_scaled)) # (1, 50, 513) + self.assertTrue(spectrogram2_torch.dim() == 3) + self.assertTrue(spectrogram_torch.ge(spectrogram_torch.max() - top_db).all()) + self.assertEqual(spectrogram2_torch.size(1), mel_transform2.n_mels) + self.assertTrue(mel_transform2.mel_scale.fb.sum(1).le(1.).all()) + self.assertTrue(mel_transform2.mel_scale.fb.sum(1).ge(0.).all()) + # check on multi-channel audio + filepath = common_utils.get_asset_path('steam-train-whistle-daniel_simon.wav') + x_stereo = common_utils.load_wav(filepath)[0] # (2, 278756), 44100 + spectrogram_stereo = s2db(mel_transform(x_stereo)) # (2, 128, 1394) + self.assertTrue(spectrogram_stereo.dim() == 3) + self.assertTrue(spectrogram_stereo.size(0) == 2) + self.assertTrue(spectrogram_torch.ge(spectrogram_torch.max() - top_db).all()) + self.assertEqual(spectrogram_stereo.size(1), mel_transform.n_mels) + # check filterbank matrix creation + fb_matrix_transform = transforms.MelScale( + n_mels=100, sample_rate=16000, f_min=0., f_max=None, n_stft=400) + self.assertTrue(fb_matrix_transform.fb.sum(1).le(1.).all()) + self.assertTrue(fb_matrix_transform.fb.sum(1).ge(0.).all()) + self.assertEqual(fb_matrix_transform.fb.size(), (400, 100)) + + def test_mfcc_defaults(self): + """Check the default configuration of the MFCC transform. + """ + sample_rate = 16000 + audio = common_utils.get_whitenoise(sample_rate=sample_rate) + + n_mfcc = 40 + mfcc_transform = torchaudio.transforms.MFCC(sample_rate=sample_rate, + n_mfcc=n_mfcc, + norm='ortho') + torch_mfcc = mfcc_transform(audio) # (1, 40, 81) + self.assertEqual(torch_mfcc.dim(), 3) + self.assertEqual(torch_mfcc.shape[1], n_mfcc) + self.assertEqual(torch_mfcc.shape[2], 81) + + def test_mfcc_kwargs_passthrough(self): + """Check kwargs get correctly passed to the MelSpectrogram transform. + """ + sample_rate = 16000 + audio = common_utils.get_whitenoise(sample_rate=sample_rate) + + n_mfcc = 40 + melkwargs = {'win_length': 200} + mfcc_transform = torchaudio.transforms.MFCC(sample_rate=sample_rate, + n_mfcc=n_mfcc, + norm='ortho', + melkwargs=melkwargs) + torch_mfcc = mfcc_transform(audio) # (1, 40, 161) + self.assertEqual(torch_mfcc.shape[2], 161) + + def test_mfcc_norms(self): + """Check if MFCC-DCT norms work correctly. + """ + sample_rate = 16000 + audio = common_utils.get_whitenoise(sample_rate=sample_rate) + + n_mfcc = 40 + n_mels = 128 + mfcc_transform = torchaudio.transforms.MFCC(sample_rate=sample_rate, + n_mfcc=n_mfcc, + norm='ortho') + # check norms work correctly + mfcc_transform_norm_none = torchaudio.transforms.MFCC(sample_rate=sample_rate, + n_mfcc=n_mfcc, + norm=None) + torch_mfcc_norm_none = mfcc_transform_norm_none(audio) # (1, 40, 81) + + norm_check = mfcc_transform(audio) + norm_check[:, 0, :] *= math.sqrt(n_mels) * 2 + norm_check[:, 1:, :] *= math.sqrt(n_mels / 2) * 2 + + self.assertEqual(torch_mfcc_norm_none, norm_check) + + def test_lfcc_defaults(self): + """Check default settings for LFCC transform. + """ + sample_rate = 16000 + audio = common_utils.get_whitenoise(sample_rate=sample_rate) + + n_lfcc = 40 + n_filter = 128 + lfcc_transform = torchaudio.transforms.LFCC(sample_rate=sample_rate, + n_filter=n_filter, + n_lfcc=n_lfcc, + norm='ortho') + torch_lfcc = lfcc_transform(audio) # (1, 40, 81) + self.assertEqual(torch_lfcc.dim(), 3) + self.assertEqual(torch_lfcc.shape[1], n_lfcc) + self.assertEqual(torch_lfcc.shape[2], 81) + + def test_lfcc_arg_passthrough(self): + """Check if kwargs get correctly passed to the underlying Spectrogram transform. + """ + sample_rate = 16000 + audio = common_utils.get_whitenoise(sample_rate=sample_rate) + + n_lfcc = 40 + n_filter = 128 + speckwargs = {'win_length': 200} + lfcc_transform = torchaudio.transforms.LFCC(sample_rate=sample_rate, + n_filter=n_filter, + n_lfcc=n_lfcc, + norm='ortho', + speckwargs=speckwargs) + torch_lfcc = lfcc_transform(audio) # (1, 40, 161) + self.assertEqual(torch_lfcc.shape[2], 161) + + def test_lfcc_norms(self): + """Check if LFCC-DCT norm works correctly. + """ + sample_rate = 16000 + audio = common_utils.get_whitenoise(sample_rate=sample_rate) + + n_lfcc = 40 + n_filter = 128 + lfcc_transform = torchaudio.transforms.LFCC(sample_rate=sample_rate, + n_filter=n_filter, + n_lfcc=n_lfcc, + norm='ortho') + + lfcc_transform_norm_none = torchaudio.transforms.LFCC(sample_rate=sample_rate, + n_filter=n_filter, + n_lfcc=n_lfcc, + norm=None) + torch_lfcc_norm_none = lfcc_transform_norm_none(audio) # (1, 40, 161) + + norm_check = lfcc_transform(audio) # (1, 40, 161) + norm_check[:, 0, :] *= math.sqrt(n_filter) * 2 + norm_check[:, 1:, :] *= math.sqrt(n_filter / 2) * 2 + + self.assertEqual(torch_lfcc_norm_none, norm_check) + + def test_resample_size(self): + input_path = common_utils.get_asset_path('sinewave.wav') + waveform, sample_rate = common_utils.load_wav(input_path) + + upsample_rate = sample_rate * 2 + downsample_rate = sample_rate // 2 + invalid_resampling_method = 'foo' + + with self.assertRaises(ValueError): + torchaudio.transforms.Resample(sample_rate, upsample_rate, + resampling_method=invalid_resampling_method) + + upsample_resample = torchaudio.transforms.Resample( + sample_rate, upsample_rate, resampling_method='sinc_interpolation') + up_sampled = upsample_resample(waveform) + + # we expect the upsampled signal to have twice as many samples + self.assertTrue(up_sampled.size(-1) == waveform.size(-1) * 2) + + downsample_resample = torchaudio.transforms.Resample( + sample_rate, downsample_rate, resampling_method='sinc_interpolation') + down_sampled = downsample_resample(waveform) + + # we expect the downsampled signal to have half as many samples + self.assertTrue(down_sampled.size(-1) == waveform.size(-1) // 2) + + def test_compute_deltas(self): + channel = 13 + n_mfcc = channel * 3 + time = 1021 + win_length = 2 * 7 + 1 + specgram = torch.randn(channel, n_mfcc, time) + transform = transforms.ComputeDeltas(win_length=win_length) + computed = transform(specgram) + self.assertTrue(computed.shape == specgram.shape, (computed.shape, specgram.shape)) + + def test_compute_deltas_transform_same_as_functional(self, atol=1e-6, rtol=1e-8): + channel = 13 + n_mfcc = channel * 3 + time = 1021 + win_length = 2 * 7 + 1 + specgram = torch.randn(channel, n_mfcc, time) + + transform = transforms.ComputeDeltas(win_length=win_length) + computed_transform = transform(specgram) + + computed_functional = F.compute_deltas(specgram, win_length=win_length) + self.assertEqual(computed_functional, computed_transform, atol=atol, rtol=rtol) + + def test_compute_deltas_twochannel(self): + specgram = torch.tensor([1., 2., 3., 4.]).repeat(1, 2, 1) + expected = torch.tensor([[[0.5, 1.0, 1.0, 0.5], + [0.5, 1.0, 1.0, 0.5]]]) + transform = transforms.ComputeDeltas(win_length=3) + computed = transform(specgram) + assert computed.shape == expected.shape, (computed.shape, expected.shape) + self.assertEqual(computed, expected, atol=1e-6, rtol=1e-8) + + +class SmokeTest(common_utils.TorchaudioTestCase): + + def test_spectrogram(self): + specgram = transforms.Spectrogram(center=False, pad_mode="reflect", onesided=False) + self.assertEqual(specgram.center, False) + self.assertEqual(specgram.pad_mode, "reflect") + self.assertEqual(specgram.onesided, False) + + def test_melspectrogram(self): + melspecgram = transforms.MelSpectrogram(center=True, pad_mode="reflect", onesided=False) + specgram = melspecgram.spectrogram + self.assertEqual(specgram.center, True) + self.assertEqual(specgram.pad_mode, "reflect") + self.assertEqual(specgram.onesided, False) diff --git a/test/torchaudio_unittest/transforms/transforms_test_impl.py b/test/torchaudio_unittest/transforms/transforms_test_impl.py new file mode 100644 index 00000000..9aa5496a --- /dev/null +++ b/test/torchaudio_unittest/transforms/transforms_test_impl.py @@ -0,0 +1,134 @@ +import torch +import torchaudio.transforms as T +from parameterized import parameterized, param +from torchaudio_unittest.common_utils import ( + TestBaseMixin, + get_whitenoise, + get_spectrogram, + nested_params, +) +from torchaudio_unittest.common_utils.psd_utils import psd_numpy + + +def _get_ratio(mat): + return (mat.sum() / mat.numel()).item() + + +class TransformsTestBase(TestBaseMixin): + def test_InverseMelScale(self): + """Gauge the quality of InverseMelScale transform. + + As InverseMelScale is currently implemented with + random initialization + iterative optimization, + it is not practically possible to assert the difference between + the estimated spectrogram and the original spectrogram as a whole. + Estimated spectrogram has very huge descrepency locally. + Thus in this test we gauge what percentage of elements are bellow + certain tolerance. + At the moment, the quality of estimated spectrogram is not good. + When implementation is changed in a way it makes the quality even worse, + this test will fail. + """ + n_fft = 400 + power = 1 + n_mels = 64 + sample_rate = 8000 + + n_stft = n_fft // 2 + 1 + + # Generate reference spectrogram and input mel-scaled spectrogram + expected = get_spectrogram( + get_whitenoise(sample_rate=sample_rate, duration=1, n_channels=2), + n_fft=n_fft, power=power).to(self.device, self.dtype) + input = T.MelScale( + n_mels=n_mels, sample_rate=sample_rate, n_stft=n_stft + ).to(self.device, self.dtype)(expected) + + # Run transform + transform = T.InverseMelScale( + n_stft, n_mels=n_mels, sample_rate=sample_rate).to(self.device, self.dtype) + torch.random.manual_seed(0) + result = transform(input) + + # Compare + epsilon = 1e-60 + relative_diff = torch.abs((result - expected) / (expected + epsilon)) + + for tol in [1e-1, 1e-3, 1e-5, 1e-10]: + print( + f"Ratio of relative diff smaller than {tol:e} is " + f"{_get_ratio(relative_diff < tol)}") + assert _get_ratio(relative_diff < 1e-1) > 0.2 + assert _get_ratio(relative_diff < 1e-3) > 5e-3 + assert _get_ratio(relative_diff < 1e-5) > 1e-5 + + @nested_params( + ["sinc_interpolation", "kaiser_window"], + [16000, 44100], + ) + def test_resample_identity(self, resampling_method, sample_rate): + """When sampling rate is not changed, the transform returns an identical Tensor""" + waveform = get_whitenoise(sample_rate=sample_rate, duration=1) + + resampler = T.Resample(sample_rate, sample_rate, resampling_method) + resampled = resampler(waveform) + self.assertEqual(waveform, resampled) + + @nested_params( + ["sinc_interpolation", "kaiser_window"], + [None, torch.float64], + ) + def test_resample_cache_dtype(self, resampling_method, dtype): + """Providing dtype changes the kernel cache dtype""" + transform = T.Resample(16000, 44100, resampling_method, dtype=dtype) + + assert transform.kernel.dtype == dtype if dtype is not None else torch.float32 + + @parameterized.expand([ + param(n_fft=300, center=True, onesided=True), + param(n_fft=400, center=True, onesided=False), + param(n_fft=400, center=True, onesided=False), + param(n_fft=300, center=True, onesided=False), + param(n_fft=400, hop_length=10), + param(n_fft=800, win_length=400, hop_length=20), + param(n_fft=800, win_length=400, hop_length=20, normalized=True), + param(), + param(n_fft=400, pad=32), + # These tests do not work - cause runtime error + # See https://github.com/pytorch/pytorch/issues/62323 + # param(n_fft=400, center=False, onesided=True), + # param(n_fft=400, center=False, onesided=False), + ]) + def test_roundtrip_spectrogram(self, **args): + """Test the spectrogram + inverse spectrogram results in approximate identity.""" + + waveform = get_whitenoise(sample_rate=8000, duration=0.5, dtype=self.dtype) + + s = T.Spectrogram(**args, power=None) + inv_s = T.InverseSpectrogram(**args) + transformed = s.forward(waveform) + restored = inv_s.forward(transformed, length=waveform.shape[-1]) + self.assertEqual(waveform, restored, atol=1e-6, rtol=1e-6) + + @parameterized.expand([ + param(0.5, 1, True, False), + param(0.5, 1, None, False), + param(1, 4, True, True), + param(1, 6, None, True), + ]) + def test_psd(self, duration, channel, mask, multi_mask): + """Providing dtype changes the kernel cache dtype""" + transform = T.PSD(multi_mask) + waveform = get_whitenoise(sample_rate=8000, duration=duration, n_channels=channel) + spectrogram = get_spectrogram(waveform, n_fft=400) # (channel, freq, time) + spectrogram = spectrogram.to(torch.cdouble) + if mask is not None: + if multi_mask: + mask = torch.rand(spectrogram.shape[-3:]) + else: + mask = torch.rand(spectrogram.shape[-2:]) + psd_np = psd_numpy(spectrogram.detach().numpy(), mask.detach().numpy(), multi_mask) + else: + psd_np = psd_numpy(spectrogram.detach().numpy(), mask, multi_mask) + psd = transform(spectrogram, mask) + self.assertEqual(psd, psd_np, atol=1e-5, rtol=1e-5) diff --git a/test/torchaudio_unittest/utils/__init__.py b/test/torchaudio_unittest/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/torchaudio_unittest/utils/sox_utils_test.py b/test/torchaudio_unittest/utils/sox_utils_test.py new file mode 100644 index 00000000..7bd90184 --- /dev/null +++ b/test/torchaudio_unittest/utils/sox_utils_test.py @@ -0,0 +1,49 @@ +from torchaudio.utils import sox_utils + +from torchaudio_unittest.common_utils import ( + PytorchTestCase, + skipIfNoSox, +) + + +@skipIfNoSox +class TestSoxUtils(PytorchTestCase): + """Smoke tests for sox_util module""" + def test_set_seed(self): + """`set_seed` does not crush""" + sox_utils.set_seed(0) + + def test_set_verbosity(self): + """`set_verbosity` does not crush""" + for val in range(6, 0, -1): + sox_utils.set_verbosity(val) + + def test_set_buffer_size(self): + """`set_buffer_size` does not crush""" + sox_utils.set_buffer_size(131072) + # back to default + sox_utils.set_buffer_size(8192) + + def test_set_use_threads(self): + """`set_use_threads` does not crush""" + sox_utils.set_use_threads(True) + # back to default + sox_utils.set_use_threads(False) + + def test_list_effects(self): + """`list_effects` returns the list of available effects""" + effects = sox_utils.list_effects() + # We cannot infer what effects are available, so only check some of them. + assert 'highpass' in effects + assert 'phaser' in effects + assert 'gain' in effects + + def test_list_read_formats(self): + """`list_read_formats` returns the list of supported formats""" + formats = sox_utils.list_read_formats() + assert 'wav' in formats + + def test_list_write_formats(self): + """`list_write_formats` returns the list of supported formats""" + formats = sox_utils.list_write_formats() + assert 'opus' not in formats diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt new file mode 100644 index 00000000..ef984d57 --- /dev/null +++ b/third_party/CMakeLists.txt @@ -0,0 +1,24 @@ +set(TORCHAUDIO_THIRD_PARTIES "") + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden") + +################################################################################ +# sox +################################################################################ +add_library(libsox INTERFACE) +if (BUILD_SOX) + add_subdirectory(sox) + target_include_directories(libsox INTERFACE ${SOX_INCLUDE_DIR}) + target_link_libraries(libsox INTERFACE ${SOX_LIBRARIES}) + list(APPEND TORCHAUDIO_THIRD_PARTIES libsox) +endif() + +################################################################################ +# kaldi +################################################################################ +if (BUILD_KALDI) + add_subdirectory(kaldi) + list(APPEND TORCHAUDIO_THIRD_PARTIES kaldi) +endif() + +set_property(GLOBAL PROPERTY TORCHAUDIO_THIRD_PARTIES "${TORCHAUDIO_THIRD_PARTIES}") diff --git a/third_party/kaldi/CMakeLists.txt b/third_party/kaldi/CMakeLists.txt new file mode 100644 index 00000000..a121c7b8 --- /dev/null +++ b/third_party/kaldi/CMakeLists.txt @@ -0,0 +1,32 @@ +set(KALDI_REPO ${CMAKE_CURRENT_SOURCE_DIR}/submodule) + +if (NOT EXISTS ${KALDI_REPO}/src/base/version.h) +# Apply custom patch +execute_process( + WORKING_DIRECTORY ${KALDI_REPO} + COMMAND "git" "checkout" "." + ) +execute_process( + WORKING_DIRECTORY ${KALDI_REPO} + COMMAND git apply ../kaldi.patch + ) +# Update the version string +execute_process( + WORKING_DIRECTORY ${KALDI_REPO}/src/base + COMMAND sh get_version.sh + ) +endif() + +set(KALDI_SOURCES + src/matrix/kaldi-vector.cc + src/matrix/kaldi-matrix.cc + submodule/src/base/kaldi-error.cc + submodule/src/base/kaldi-math.cc + submodule/src/feat/feature-functions.cc + submodule/src/feat/pitch-functions.cc + submodule/src/feat/resample.cc + ) + +add_library(kaldi STATIC ${KALDI_SOURCES}) +target_include_directories(kaldi PUBLIC src submodule/src) +target_include_directories(kaldi PRIVATE ${TORCH_INCLUDE_DIRS}) diff --git a/third_party/kaldi/README.md b/third_party/kaldi/README.md new file mode 100644 index 00000000..58c48747 --- /dev/null +++ b/third_party/kaldi/README.md @@ -0,0 +1,6 @@ +# Custom Kaldi build + +This directory contains original Kaldi repository (as submodule), [the custom implementation of Kaldi's vector/matrix](./src) and the build script. + +We use the custom build process so that the resulting library only contains what torchaudio needs. +We use the custom vector/matrix implementation so that we can use the same BLAS library that PyTorch is compiled with, and so that we can (hopefully, in future) take advantage of other PyTorch features (such as differentiability and GPU support). The down side of this approach is that it adds a lot of overhead compared to the original Kaldi (operator dispatch and element-wise processing, which PyTorch is not efficient at). We can improve this gradually, and if you are interested in helping, please let us know by opening an issue. \ No newline at end of file diff --git a/third_party/kaldi/kaldi.patch b/third_party/kaldi/kaldi.patch new file mode 100644 index 00000000..40667bce --- /dev/null +++ b/third_party/kaldi/kaldi.patch @@ -0,0 +1,76 @@ +diff --git a/src/base/kaldi-types.h b/src/base/kaldi-types.h +index 7ebf4f853..c15b288b2 100644 +--- a/src/base/kaldi-types.h ++++ b/src/base/kaldi-types.h +@@ -41,6 +41,7 @@ typedef float BaseFloat; + + // for discussion on what to do if you need compile kaldi + // without OpenFST, see the bottom of this this file ++/* + #include + + namespace kaldi { +@@ -53,10 +54,10 @@ namespace kaldi { + typedef float float32; + typedef double double64; + } // end namespace kaldi ++*/ + + // 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; +@@ -70,6 +71,5 @@ namespace kaldi { + typedef float float32; + typedef double double64; + } // end namespace kaldi +-*/ + + #endif // KALDI_BASE_KALDI_TYPES_H_ +diff --git a/src/matrix/matrix-lib.h b/src/matrix/matrix-lib.h +index b6059b06c..4fb9e1b16 100644 +--- a/src/matrix/matrix-lib.h ++++ b/src/matrix/matrix-lib.h +@@ -25,14 +25,14 @@ + #include "base/kaldi-common.h" + #include "matrix/kaldi-vector.h" + #include "matrix/kaldi-matrix.h" +-#include "matrix/sp-matrix.h" +-#include "matrix/tp-matrix.h" ++// #include "matrix/sp-matrix.h" ++// #include "matrix/tp-matrix.h" + #include "matrix/matrix-functions.h" + #include "matrix/srfft.h" + #include "matrix/compressed-matrix.h" +-#include "matrix/sparse-matrix.h" ++// #include "matrix/sparse-matrix.h" + #include "matrix/optimization.h" +-#include "matrix/numpy-array.h" ++// #include "matrix/numpy-array.h" + + #endif + +diff --git a/src/util/common-utils.h b/src/util/common-utils.h +index cfb0c255c..48d199e97 100644 +--- a/src/util/common-utils.h ++++ b/src/util/common-utils.h +@@ -21,11 +21,11 @@ + + #include "base/kaldi-common.h" + #include "util/parse-options.h" +-#include "util/kaldi-io.h" +-#include "util/simple-io-funcs.h" +-#include "util/kaldi-holder.h" +-#include "util/kaldi-table.h" +-#include "util/table-types.h" +-#include "util/text-utils.h" ++// #include "util/kaldi-io.h" ++// #include "util/simple-io-funcs.h" ++// #include "util/kaldi-holder.h" ++// #include "util/kaldi-table.h" ++// #include "util/table-types.h" ++// #include "util/text-utils.h" + + #endif // KALDI_UTIL_COMMON_UTILS_H_ diff --git a/third_party/kaldi/src/matrix/kaldi-matrix.cc b/third_party/kaldi/src/matrix/kaldi-matrix.cc new file mode 100644 index 00000000..a89c3809 --- /dev/null +++ b/third_party/kaldi/src/matrix/kaldi-matrix.cc @@ -0,0 +1,39 @@ +#include "matrix/kaldi-matrix.h" +#include + +namespace { + +template +void assert_matrix_shape(const torch::Tensor& tensor_); + +template <> +void assert_matrix_shape(const torch::Tensor& tensor_) { + TORCH_INTERNAL_ASSERT(tensor_.ndimension() == 2); + TORCH_INTERNAL_ASSERT(tensor_.dtype() == torch::kFloat32); + TORCH_CHECK(tensor_.device().is_cpu(), "Input tensor has to be on CPU."); +} + +template <> +void assert_matrix_shape(const torch::Tensor& tensor_) { + TORCH_INTERNAL_ASSERT(tensor_.ndimension() == 2); + TORCH_INTERNAL_ASSERT(tensor_.dtype() == torch::kFloat64); + TORCH_CHECK(tensor_.device().is_cpu(), "Input tensor has to be on CPU."); +} + +} // namespace + +namespace kaldi { + +template +MatrixBase::MatrixBase(torch::Tensor tensor) : tensor_(tensor) { + assert_matrix_shape(tensor_); +}; + +template class Matrix; +template class Matrix; +template class MatrixBase; +template class MatrixBase; +template class SubMatrix; +template class SubMatrix; + +} // namespace kaldi diff --git a/third_party/kaldi/src/matrix/kaldi-matrix.h b/third_party/kaldi/src/matrix/kaldi-matrix.h new file mode 100644 index 00000000..f64828b8 --- /dev/null +++ b/third_party/kaldi/src/matrix/kaldi-matrix.h @@ -0,0 +1,178 @@ +// https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-matrix.h + +#ifndef KALDI_MATRIX_KALDI_MATRIX_H_ +#define KALDI_MATRIX_KALDI_MATRIX_H_ + +#include +#include "matrix/kaldi-vector.h" +#include "matrix/matrix-common.h" + +using namespace torch::indexing; + +namespace kaldi { + +// https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-matrix.h#L44-L48 +template +class MatrixBase { + public: + //////////////////////////////////////////////////////////////////////////////// + // PyTorch-specific items + //////////////////////////////////////////////////////////////////////////////// + torch::Tensor tensor_; + /// Construct VectorBase which is an interface to an existing torch::Tensor + /// object. + MatrixBase(torch::Tensor tensor); + + //////////////////////////////////////////////////////////////////////////////// + // Kaldi-compatible items + //////////////////////////////////////////////////////////////////////////////// + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-matrix.h#L62-L63 + inline MatrixIndexT NumRows() const { + return tensor_.size(0); + }; + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-matrix.h#L65-L66 + inline MatrixIndexT NumCols() const { + return tensor_.size(1); + }; + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-matrix.h#L177-L178 + void CopyColFromVec(const VectorBase& v, const MatrixIndexT col) { + tensor_.index_put_({Slice(), col}, v.tensor_); + } + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-matrix.h#L99-L107 + inline Real& operator()(MatrixIndexT r, MatrixIndexT c) { + // CPU only + return tensor_.accessor()[r][c]; + } + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-matrix.h#L112-L120 + inline const Real operator()(MatrixIndexT r, MatrixIndexT c) const { + return tensor_.index({Slice(r), Slice(c)}).item().template to(); + } + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-matrix.h#L138-L141 + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-matrix.cc#L859-L898 + template + void CopyFromMat( + const MatrixBase& M, + MatrixTransposeType trans = kNoTrans) { + auto src = M.tensor_; + if (trans == kTrans) + src = src.transpose(1, 0); + tensor_.index_put_({Slice(), Slice()}, src); + } + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-matrix.h#L186-L191 + inline const SubVector Row(MatrixIndexT i) const { + return SubVector(*this, i); + } + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-matrix.h#L208-L211 + inline SubMatrix RowRange( + const MatrixIndexT row_offset, + const MatrixIndexT num_rows) const { + return SubMatrix(*this, row_offset, num_rows, 0, NumCols()); + } + + protected: + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-matrix.h#L749-L753 + explicit MatrixBase() : tensor_(torch::empty({0, 0})) { + KALDI_ASSERT_IS_FLOATING_TYPE(Real); + } +}; + +// https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-matrix.h#L781-L784 +template +class Matrix : public MatrixBase { + public: + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-matrix.h#L786-L787 + Matrix() : MatrixBase() {} + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-matrix.h#L789-L793 + Matrix( + const MatrixIndexT r, + const MatrixIndexT c, + MatrixResizeType resize_type = kSetZero, + MatrixStrideType stride_type = kDefaultStride) + : MatrixBase() { + Resize(r, c, resize_type, stride_type); + } + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-matrix.h#L808-L811 + explicit Matrix( + const MatrixBase& M, + MatrixTransposeType trans = kNoTrans) + : MatrixBase( + trans == kNoTrans ? M.tensor_ : M.tensor_.transpose(1, 0)) {} + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-matrix.h#L816-L819 + template + explicit Matrix( + const MatrixBase& M, + MatrixTransposeType trans = kNoTrans) + : MatrixBase( + trans == kNoTrans ? M.tensor_ : M.tensor_.transpose(1, 0)) {} + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-matrix.h#L859-L874 + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-matrix.cc#L817-L857 + void Resize( + const MatrixIndexT r, + const MatrixIndexT c, + MatrixResizeType resize_type = kSetZero, + MatrixStrideType stride_type = kDefaultStride) { + auto& tensor_ = MatrixBase::tensor_; + switch (resize_type) { + case kSetZero: + tensor_.resize_({r, c}).zero_(); + break; + case kUndefined: + tensor_.resize_({r, c}); + break; + case kCopyData: + auto tmp = tensor_; + auto tmp_rows = tmp.size(0); + auto tmp_cols = tmp.size(1); + tensor_.resize_({r, c}).zero_(); + auto rows = Slice(None, r < tmp_rows ? r : tmp_rows); + auto cols = Slice(None, c < tmp_cols ? c : tmp_cols); + tensor_.index_put_({rows, cols}, tmp.index({rows, cols})); + break; + } + } + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-matrix.h#L876-L883 + Matrix& operator=(const MatrixBase& other) { + if (MatrixBase::NumRows() != other.NumRows() || + MatrixBase::NumCols() != other.NumCols()) + Resize(other.NumRows(), other.NumCols(), kUndefined); + MatrixBase::CopyFromMat(other); + return *this; + } +}; + +// https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-matrix.h#L940-L948 +template +class SubMatrix : public MatrixBase { + public: + SubMatrix( + const MatrixBase& T, + const MatrixIndexT ro, // row offset, 0 < ro < NumRows() + const MatrixIndexT r, // number of rows, r > 0 + const MatrixIndexT co, // column offset, 0 < co < NumCols() + const MatrixIndexT c) // number of columns, c > 0 + : MatrixBase( + T.tensor_.index({Slice(ro, ro + r), Slice(co, co + c)})) {} +}; + +// https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-matrix.h#L1059-L1060 +template +std::ostream& operator<<(std::ostream& Out, const MatrixBase& M) { + Out << M.tensor_; + return Out; +} + +} // namespace kaldi + +#endif diff --git a/third_party/kaldi/src/matrix/kaldi-vector.cc b/third_party/kaldi/src/matrix/kaldi-vector.cc new file mode 100644 index 00000000..df59f13a --- /dev/null +++ b/third_party/kaldi/src/matrix/kaldi-vector.cc @@ -0,0 +1,42 @@ +#include "matrix/kaldi-vector.h" +#include +#include "matrix/kaldi-matrix.h" + +namespace { + +template +void assert_vector_shape(const torch::Tensor& tensor_); + +template <> +void assert_vector_shape(const torch::Tensor& tensor_) { + TORCH_INTERNAL_ASSERT(tensor_.ndimension() == 1); + TORCH_INTERNAL_ASSERT(tensor_.dtype() == torch::kFloat32); + TORCH_CHECK(tensor_.device().is_cpu(), "Input tensor has to be on CPU."); +} + +template <> +void assert_vector_shape(const torch::Tensor& tensor_) { + TORCH_INTERNAL_ASSERT(tensor_.ndimension() == 1); + TORCH_INTERNAL_ASSERT(tensor_.dtype() == torch::kFloat64); + TORCH_CHECK(tensor_.device().is_cpu(), "Input tensor has to be on CPU."); +} + +} // namespace + +namespace kaldi { + +template +VectorBase::VectorBase(torch::Tensor tensor) + : tensor_(tensor), data_(tensor.data_ptr()) { + assert_vector_shape(tensor_); +}; + +template +VectorBase::VectorBase() : VectorBase(torch::empty({0})) {} + +template class Vector; +template class Vector; +template class VectorBase; +template class VectorBase; + +} // namespace kaldi diff --git a/third_party/kaldi/src/matrix/kaldi-vector.h b/third_party/kaldi/src/matrix/kaldi-vector.h new file mode 100644 index 00000000..ea11ca4d --- /dev/null +++ b/third_party/kaldi/src/matrix/kaldi-vector.h @@ -0,0 +1,319 @@ +// https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h + +#ifndef KALDI_MATRIX_KALDI_VECTOR_H_ +#define KALDI_MATRIX_KALDI_VECTOR_H_ + +#include +#include "matrix/matrix-common.h" + +using namespace torch::indexing; + +namespace kaldi { + +// https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L36-L40 +template +class VectorBase { + public: + //////////////////////////////////////////////////////////////////////////////// + // PyTorch-specific things + //////////////////////////////////////////////////////////////////////////////// + torch::Tensor tensor_; + + /// Construct VectorBase which is an interface to an existing torch::Tensor + /// object. + VectorBase(torch::Tensor tensor); + + //////////////////////////////////////////////////////////////////////////////// + // Kaldi-compatible methods + //////////////////////////////////////////////////////////////////////////////// + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L42-L43 + void SetZero() { + Set(0); + } + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L48-L49 + void Set(Real f) { + tensor_.index_put_({"..."}, f); + } + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L62-L63 + inline MatrixIndexT Dim() const { + return tensor_.numel(); + }; + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L68-L69 + inline Real* Data() { + return data_; + } + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L71-L72 + inline const Real* Data() const { + return data_; + } + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L74-L79 + inline Real operator()(MatrixIndexT i) const { + return data_[i]; + }; + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L81-L86 + inline Real& operator()(MatrixIndexT i) { + return tensor_.accessor()[i]; + }; + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L88-L95 + SubVector Range(const MatrixIndexT o, const MatrixIndexT l) { + return SubVector(*this, o, l); + } + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L97-L105 + const SubVector Range(const MatrixIndexT o, const MatrixIndexT l) + const { + return SubVector(*this, o, l); + } + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L107-L108 + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.cc#L226-L233 + void CopyFromVec(const VectorBase& v) { + TORCH_INTERNAL_ASSERT(tensor_.sizes() == v.tensor_.sizes()); + tensor_.copy_(v.tensor_); + } + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L137-L139 + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.cc#L816-L832 + void ApplyFloor(Real floor_val, MatrixIndexT* floored_count = nullptr) { + auto index = tensor_ < floor_val; + auto tmp = tensor_.index_put_({index}, floor_val); + if (floored_count) { + *floored_count = index.sum().item().template to(); + } + } + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L164-L165 + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.cc#L449-L479 + void ApplyPow(Real power) { + tensor_.pow_(power); + TORCH_INTERNAL_ASSERT(!tensor_.isnan().sum().item().template to()); + } + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L181-L184 + template + void AddVec(const Real alpha, const VectorBase& v) { + tensor_ += alpha * v.tensor_; + } + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L186-L187 + void AddVec2(const Real alpha, const VectorBase& v) { + tensor_ += alpha * (v.tensor_.square()); + } + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L196-L198 + void AddMatVec( + const Real alpha, + const MatrixBase& M, + const MatrixTransposeType trans, + const VectorBase& v, + const Real beta) { // **beta previously defaulted to 0.0** + auto mat = M.tensor_; + if (trans == kTrans) { + mat = mat.transpose(1, 0); + } + tensor_.addmv_(mat, v.tensor_, beta, alpha); + } + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L221-L222 + void MulElements(const VectorBase& v) { + tensor_ *= v.tensor_; + } + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L233-L234 + void Add(Real c) { + tensor_ += c; + } + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L236-L239 + void AddVecVec( + Real alpha, + const VectorBase& v, + const VectorBase& r, + Real beta) { + tensor_ = beta * tensor_ + alpha * v.tensor_ * r.tensor_; + } + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L246-L247 + void Scale(Real alpha) { + tensor_ *= alpha; + } + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L305-L306 + Real Min() const { + if (tensor_.numel()) { + return tensor_.min().item().template to(); + } + return std::numeric_limits::infinity(); + } + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L308-L310 + Real Min(MatrixIndexT* index) const { + TORCH_INTERNAL_ASSERT(tensor_.numel()); + torch::Tensor value, ind; + std::tie(value, ind) = tensor_.min(0); + *index = ind.item().to(); + return value.item().to(); + } + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L312-L313 + Real Sum() const { + return tensor_.sum().item().template to(); + }; + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L320-L321 + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.cc#L718-L736 + void AddRowSumMat(Real alpha, const MatrixBase& M, Real beta = 1.0) { + Vector ones(M.NumRows()); + ones.Set(1.0); + this->AddMatVec(alpha, M, kTrans, ones, beta); + } + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L323-L324 + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.cc#L738-L757 + void AddColSumMat(Real alpha, const MatrixBase& M, Real beta = 1.0) { + Vector ones(M.NumCols()); + ones.Set(1.0); + this->AddMatVec(alpha, M, kNoTrans, ones, beta); + } + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L326-L330 + void AddDiagMat2( + Real alpha, + const MatrixBase& M, + MatrixTransposeType trans = kNoTrans, + Real beta = 1.0) { + auto mat = M.tensor_; + if (trans == kNoTrans) { + tensor_ = + beta * tensor_ + torch::diag(torch::mm(mat, mat.transpose(1, 0))); + } else { + tensor_ = + beta * tensor_ + torch::diag(torch::mm(mat.transpose(1, 0), mat)); + } + } + + protected: + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L362-L365 + explicit VectorBase(); + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L378-L379 + Real* data_; + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L382 + KALDI_DISALLOW_COPY_AND_ASSIGN(VectorBase); +}; + +// https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L385-L390 +template +class Vector : public VectorBase { + public: + //////////////////////////////////////////////////////////////////////////////// + // PyTorch-compatibility things + //////////////////////////////////////////////////////////////////////////////// + /// Construct VectorBase which is an interface to an existing torch::Tensor + /// object. + Vector(torch::Tensor tensor) : VectorBase(tensor){}; + + //////////////////////////////////////////////////////////////////////////////// + // Kaldi-compatible methods + //////////////////////////////////////////////////////////////////////////////// + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L392-L393 + Vector() : VectorBase(){}; + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L395-L399 + explicit Vector(const MatrixIndexT s, MatrixResizeType resize_type = kSetZero) + : VectorBase() { + Resize(s, resize_type); + } + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L406-L410 + // Note: unlike the original implementation, this is "explicit". + explicit Vector(const Vector& v) + : VectorBase(v.tensor_.clone()) {} + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L412-L416 + explicit Vector(const VectorBase& v) + : VectorBase(v.tensor_.clone()) {} + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L434-L435 + void Swap(Vector* other) { + auto tmp = VectorBase::tensor_; + this->tensor_ = other->tensor_; + other->tensor_ = tmp; + } + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L444-L451 + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.cc#L189-L223 + void Resize(MatrixIndexT length, MatrixResizeType resize_type = kSetZero) { + auto& tensor_ = this->tensor_; + switch (resize_type) { + case kSetZero: + tensor_.resize_({length}).zero_(); + break; + case kUndefined: + tensor_.resize_({length}); + break; + case kCopyData: + auto tmp = tensor_; + auto tmp_numel = tensor_.numel(); + tensor_.resize_({length}).zero_(); + auto numel = Slice(length < tmp_numel ? length : tmp_numel); + tensor_.index_put_({numel}, tmp.index({numel})); + break; + } + // data_ptr() causes compiler error + this->data_ = static_cast(tensor_.data_ptr()); + } + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L463-L468 + Vector& operator=(const VectorBase& other) { + Resize(other.Dim(), kUndefined); + this->CopyFromVec(other); + return *this; + } +}; + +// https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L482-L485 +template +class SubVector : public VectorBase { + public: + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L487-L499 + SubVector( + const VectorBase& t, + const MatrixIndexT origin, + const MatrixIndexT length) + : VectorBase(t.tensor_.index({Slice(origin, origin + length)})) {} + + SubVector(kaldi::SubVector&& v): VectorBase(v.tensor_) { + this->data_ = v.data_; + // v.tensor_ = + v.data_ = NULL; + } + + // https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L524-L528 + SubVector(const MatrixBase& matrix, MatrixIndexT row) + : VectorBase(matrix.tensor_.index({row})) {} +}; + +// https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L540-L543 +template +std::ostream& operator<<(std::ostream& out, const VectorBase& v) { + out << v.tensor_; + return out; +} + +// https://github.com/kaldi-asr/kaldi/blob/7fb716aa0f56480af31514c7e362db5c9f787fd4/src/matrix/kaldi-vector.h#L573-L575 +template +Real VecVec(const VectorBase& v1, const VectorBase& v2) { + return torch::dot(v1.tensor_, v2.tensor_).item().template to(); +} + +} // namespace kaldi + +#endif diff --git a/third_party/sox/CMakeLists.txt b/third_party/sox/CMakeLists.txt new file mode 100644 index 00000000..124d605e --- /dev/null +++ b/third_party/sox/CMakeLists.txt @@ -0,0 +1,216 @@ +include(ExternalProject) + +set(INSTALL_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../install) +set(ARCHIVE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/archives) +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}" +) + +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 < ${CMAKE_CURRENT_SOURCE_DIR}/patch/libmad.patch && cp ${CMAKE_CURRENT_SOURCE_DIR}/patch/config.guess ${CMAKE_CURRENT_BINARY_DIR}/src/mad/config.guess && cp ${CMAKE_CURRENT_SOURCE_DIR}/patch/config.sub ${CMAKE_CURRENT_BINARY_DIR}/src/mad/config.sub + 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 +) + +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 + 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 + CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env ${envs} ${CMAKE_CURRENT_BINARY_DIR}/src/lame/configure ${COMMON_ARGS} --enable-nasm + PATCH_COMMAND cp ${CMAKE_CURRENT_SOURCE_DIR}/patch/config.guess ${CMAKE_CURRENT_BINARY_DIR}/src/lame/config.guess && cp ${CMAKE_CURRENT_SOURCE_DIR}/patch/config.sub ${CMAKE_CURRENT_BINARY_DIR}/src/lame/config.sub + 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 + 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 + 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 + 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 + 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 + 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-mad + --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/libmad.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 + ) + +ExternalProject_Add(sox + PREFIX ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ogg flac vorbis opusfile lame mad amr + 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 + CONFIGURE_COMMAND ${CMAKE_COMMAND} -E env ${envs} ${CMAKE_CURRENT_BINARY_DIR}/src/sox/configure ${COMMON_ARGS} ${SOX_OPTIONS} + PATCH_COMMAND patch -p1 < ${CMAKE_CURRENT_SOURCE_DIR}/patch/sox.patch + 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_dependencies(libsox sox) +set(SOX_INCLUDE_DIR ${INSTALL_DIR}/include PARENT_SCOPE) +set(SOX_LIBRARIES ${SOX_LIBRARIES} PARENT_SCOPE) diff --git a/third_party/sox/patch/config.guess b/third_party/sox/patch/config.guess new file mode 100644 index 00000000..dc0a6b29 --- /dev/null +++ b/third_party/sox/patch/config.guess @@ -0,0 +1,1702 @@ +#! /bin/sh +# Attempt to guess a canonical system name. +# Copyright 1992-2021 Free Software Foundation, Inc. + +timestamp='2021-05-24' + +# 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 . + + +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-2021 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 + +# 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 + { 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 + eval "$($CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^LIBC' | sed 's, ,,g')" + + # 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. + echo "$machine-${os}${release}${abi-}" + exit ;; + *:Bitrig:*:*) + UNAME_MACHINE_ARCH=$(arch | sed 's/Bitrig.//') + echo "$UNAME_MACHINE_ARCH"-unknown-bitrig"$UNAME_RELEASE" + exit ;; + *:OpenBSD:*:*) + UNAME_MACHINE_ARCH=$(arch | sed 's/OpenBSD.//') + echo "$UNAME_MACHINE_ARCH"-unknown-openbsd"$UNAME_RELEASE" + exit ;; + *:SecBSD:*:*) + UNAME_MACHINE_ARCH=$(arch | sed 's/SecBSD.//') + echo "$UNAME_MACHINE_ARCH"-unknown-secbsd"$UNAME_RELEASE" + exit ;; + *:LibertyBSD:*:*) + UNAME_MACHINE_ARCH=$(arch | sed 's/^.*BSD\.//') + echo "$UNAME_MACHINE_ARCH"-unknown-libertybsd"$UNAME_RELEASE" + exit ;; + *:MidnightBSD:*:*) + echo "$UNAME_MACHINE"-unknown-midnightbsd"$UNAME_RELEASE" + exit ;; + *:ekkoBSD:*:*) + echo "$UNAME_MACHINE"-unknown-ekkobsd"$UNAME_RELEASE" + exit ;; + *:SolidBSD:*:*) + echo "$UNAME_MACHINE"-unknown-solidbsd"$UNAME_RELEASE" + exit ;; + *:OS108:*:*) + echo "$UNAME_MACHINE"-unknown-os108_"$UNAME_RELEASE" + exit ;; + macppc:MirBSD:*:*) + echo powerpc-unknown-mirbsd"$UNAME_RELEASE" + exit ;; + *:MirBSD:*:*) + echo "$UNAME_MACHINE"-unknown-mirbsd"$UNAME_RELEASE" + exit ;; + *:Sortix:*:*) + echo "$UNAME_MACHINE"-unknown-sortix + exit ;; + *:Twizzler:*:*) + echo "$UNAME_MACHINE"-unknown-twizzler + exit ;; + *:Redox:*:*) + echo "$UNAME_MACHINE"-unknown-redox + exit ;; + mips:OSF1:*.*) + echo mips-dec-osf1 + exit ;; + 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. + echo "$UNAME_MACHINE"-dec-osf"$(echo "$UNAME_RELEASE" | sed -e 's/^[PVTX]//' | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz)" + exit ;; + Amiga*:UNIX_System_V:4.0:*) + echo m68k-unknown-sysv4 + exit ;; + *:[Aa]miga[Oo][Ss]:*:*) + echo "$UNAME_MACHINE"-unknown-amigaos + exit ;; + *:[Mm]orph[Oo][Ss]:*:*) + echo "$UNAME_MACHINE"-unknown-morphos + exit ;; + *:OS/390:*:*) + echo i370-ibm-openedition + exit ;; + *:z/VM:*:*) + echo s390-ibm-zvmoe + exit ;; + *:OS400:*:*) + echo powerpc-ibm-os400 + exit ;; + arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) + echo arm-acorn-riscix"$UNAME_RELEASE" + exit ;; + arm*:riscos:*:*|arm*:RISCOS:*:*) + echo arm-unknown-riscos + exit ;; + SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) + echo hppa1.1-hitachi-hiuxmpp + exit ;; + Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) + # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. + if test "$( (/bin/universe) 2>/dev/null)" = att ; then + echo pyramid-pyramid-sysv3 + else + echo pyramid-pyramid-bsd + fi + exit ;; + NILE*:*:*:dcosx) + echo pyramid-pyramid-svr4 + exit ;; + DRS?6000:unix:4.0:6*) + echo sparc-icl-nx6 + exit ;; + DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*) + case $(/usr/bin/uname -p) in + sparc) echo sparc-icl-nx7; exit ;; + esac ;; + s390x:SunOS:*:*) + echo "$UNAME_MACHINE"-ibm-solaris2"$(echo "$UNAME_RELEASE" | sed -e 's/[^.]*//')" + exit ;; + sun4H:SunOS:5.*:*) + echo sparc-hal-solaris2"$(echo "$UNAME_RELEASE"|sed -e 's/[^.]*//')" + exit ;; + sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) + echo sparc-sun-solaris2"$(echo "$UNAME_RELEASE" | sed -e 's/[^.]*//')" + exit ;; + i86pc:AuroraUX:5.*:* | i86xen:AuroraUX:5.*:*) + echo i386-pc-auroraux"$UNAME_RELEASE" + exit ;; + 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 -E - 2>/dev/null) | \ + grep IS_64BIT_ARCH >/dev/null + then + SUN_ARCH=x86_64 + fi + fi + echo "$SUN_ARCH"-pc-solaris2"$(echo "$UNAME_RELEASE"|sed -e 's/[^.]*//')" + exit ;; + 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. + echo sparc-sun-solaris3"$(echo "$UNAME_RELEASE"|sed -e 's/[^.]*//')" + exit ;; + 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'. + echo sparc-sun-sunos"$(echo "$UNAME_RELEASE"|sed -e 's/-/_/')" + exit ;; + sun3*:SunOS:*:*) + echo m68k-sun-sunos"$UNAME_RELEASE" + exit ;; + 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) + echo m68k-sun-sunos"$UNAME_RELEASE" + ;; + sun4) + echo sparc-sun-sunos"$UNAME_RELEASE" + ;; + esac + exit ;; + aushp:SunOS:*:*) + echo sparc-auspex-sunos"$UNAME_RELEASE" + exit ;; + # 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:*:*) + echo m68k-atari-mint"$UNAME_RELEASE" + exit ;; + atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint"$UNAME_RELEASE" + exit ;; + *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) + echo m68k-atari-mint"$UNAME_RELEASE" + exit ;; + milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) + echo m68k-milan-mint"$UNAME_RELEASE" + exit ;; + hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) + echo m68k-hades-mint"$UNAME_RELEASE" + exit ;; + *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) + echo m68k-unknown-mint"$UNAME_RELEASE" + exit ;; + m68k:machten:*:*) + echo m68k-apple-machten"$UNAME_RELEASE" + exit ;; + powerpc:machten:*:*) + echo powerpc-apple-machten"$UNAME_RELEASE" + exit ;; + RISC*:Mach:*:*) + echo mips-dec-mach_bsd4.3 + exit ;; + RISC*:ULTRIX:*:*) + echo mips-dec-ultrix"$UNAME_RELEASE" + exit ;; + VAX*:ULTRIX*:*:*) + echo vax-dec-ultrix"$UNAME_RELEASE" + exit ;; + 2020:CLIX:*:* | 2430:CLIX:*:*) + echo clipper-intergraph-clix"$UNAME_RELEASE" + exit ;; + 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; } + echo mips-mips-riscos"$UNAME_RELEASE" + exit ;; + Motorola:PowerMAX_OS:*:*) + echo powerpc-motorola-powermax + exit ;; + Motorola:*:4.3:PL8-*) + echo powerpc-harris-powermax + exit ;; + Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) + echo powerpc-harris-powermax + exit ;; + Night_Hawk:Power_UNIX:*:*) + echo powerpc-harris-powerunix + exit ;; + m88k:CX/UX:7*:*) + echo m88k-harris-cxux7 + exit ;; + m88k:*:4*:R4*) + echo m88k-motorola-sysv4 + exit ;; + m88k:*:3*:R3*) + echo m88k-motorola-sysv3 + exit ;; + 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 + echo m88k-dg-dgux"$UNAME_RELEASE" + else + echo m88k-dg-dguxbcs"$UNAME_RELEASE" + fi + else + echo i586-dg-dgux"$UNAME_RELEASE" + fi + exit ;; + M88*:DolphinOS:*:*) # DolphinOS (SVR3) + echo m88k-dolphin-sysv3 + exit ;; + M88*:*:R3*:*) + # Delta 88k system running SVR3 + echo m88k-motorola-sysv3 + exit ;; + XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) + echo m88k-tektronix-sysv3 + exit ;; + Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) + echo m68k-tektronix-bsd + exit ;; + *:IRIX*:*:*) + echo mips-sgi-irix"$(echo "$UNAME_RELEASE"|sed -e 's/-/_/g')" + exit ;; + ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. + echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id + exit ;; # Note that: echo "'$(uname -s)'" gives 'AIX ' + i*86:AIX:*:*) + echo i386-ibm-aix + exit ;; + ia64:AIX:*:*) + if test -x /usr/bin/oslevel ; then + IBM_REV=$(/usr/bin/oslevel) + else + IBM_REV="$UNAME_VERSION.$UNAME_RELEASE" + fi + echo "$UNAME_MACHINE"-ibm-aix"$IBM_REV" + exit ;; + *: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 + echo "$SYSTEM_NAME" + else + echo rs6000-ibm-aix3.2.5 + fi + elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then + echo rs6000-ibm-aix3.2.4 + else + echo rs6000-ibm-aix3.2 + fi + exit ;; + *: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 + echo "$IBM_ARCH"-ibm-aix"$IBM_REV" + exit ;; + *:AIX:*:*) + echo rs6000-ibm-aix + exit ;; + ibmrt:4.4BSD:*|romp-ibm:4.4BSD:*) + echo romp-ibm-bsd4.4 + exit ;; + ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and + echo romp-ibm-bsd"$UNAME_RELEASE" # 4.3 with uname added to + exit ;; # report: romp-ibm BSD 4.3 + *:BOSX:*:*) + echo rs6000-bull-bosx + exit ;; + DPX/2?00:B.O.S.:*:*) + echo m68k-bull-sysv3 + exit ;; + 9000/[34]??:4.3bsd:1.*:*) + echo m68k-hp-bsd + exit ;; + hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) + echo m68k-hp-bsd4.4 + exit ;; + 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 + echo "$HP_ARCH"-hp-hpux"$HPUX_REV" + exit ;; + ia64:HP-UX:*:*) + HPUX_REV=$(echo "$UNAME_RELEASE"|sed -e 's/[^.]*.[0B]*//') + echo ia64-hp-hpux"$HPUX_REV" + exit ;; + 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; } + echo unknown-hitachi-hiuxwe2 + exit ;; + 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:*) + echo hppa1.1-hp-bsd + exit ;; + 9000/8??:4.3bsd:*:*) + echo hppa1.0-hp-bsd + exit ;; + *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) + echo hppa1.0-hp-mpeix + exit ;; + hp7??:OSF1:*:* | hp8?[79]:OSF1:*:*) + echo hppa1.1-hp-osf + exit ;; + hp8??:OSF1:*:*) + echo hppa1.0-hp-osf + exit ;; + i*86:OSF1:*:*) + if test -x /usr/sbin/sysversion ; then + echo "$UNAME_MACHINE"-unknown-osf1mk + else + echo "$UNAME_MACHINE"-unknown-osf1 + fi + exit ;; + parisc*:Lites*:*:*) + echo hppa1.1-hp-lites + exit ;; + C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) + echo c1-convex-bsd + exit ;; + 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*:*) + echo c34-convex-bsd + exit ;; + C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) + echo c38-convex-bsd + exit ;; + C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) + echo c4-convex-bsd + exit ;; + CRAY*Y-MP:*:*:*) + echo ymp-cray-unicos"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' + exit ;; + 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:*:*:*) + echo t90-cray-unicos"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*T3E:*:*:*) + echo alphaev5-cray-unicosmk"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*SV1:*:*:*) + echo sv1-cray-unicos"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' + exit ;; + *:UNICOS/mp:*:*) + echo craynv-cray-unicosmp"$UNAME_RELEASE" | sed -e 's/\.[^.]*$/.X/' + exit ;; + 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/ /_/') + echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit ;; + 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/ /_/') + echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit ;; + i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) + echo "$UNAME_MACHINE"-pc-bsdi"$UNAME_RELEASE" + exit ;; + sparc*:BSD/OS:*:*) + echo sparc-unknown-bsdi"$UNAME_RELEASE" + exit ;; + *:BSD/OS:*:*) + echo "$UNAME_MACHINE"-unknown-bsdi"$UNAME_RELEASE" + exit ;; + 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 + echo "${UNAME_PROCESSOR}"-unknown-freebsd"$(echo ${UNAME_RELEASE}|sed -e 's/[-(].*//')"-gnueabi + else + echo "${UNAME_PROCESSOR}"-unknown-freebsd"$(echo ${UNAME_RELEASE}|sed -e 's/[-(].*//')"-gnueabihf + fi + exit ;; + *:FreeBSD:*:*) + UNAME_PROCESSOR=$(/usr/bin/uname -p) + case $UNAME_PROCESSOR in + amd64) + UNAME_PROCESSOR=x86_64 ;; + i386) + UNAME_PROCESSOR=i586 ;; + esac + echo "$UNAME_PROCESSOR"-unknown-freebsd"$(echo "$UNAME_RELEASE"|sed -e 's/[-(].*//')" + exit ;; + i*:CYGWIN*:*) + echo "$UNAME_MACHINE"-pc-cygwin + exit ;; + *:MINGW64*:*) + echo "$UNAME_MACHINE"-pc-mingw64 + exit ;; + *:MINGW*:*) + echo "$UNAME_MACHINE"-pc-mingw32 + exit ;; + *:MSYS*:*) + echo "$UNAME_MACHINE"-pc-msys + exit ;; + i*:PW*:*) + echo "$UNAME_MACHINE"-pc-pw32 + exit ;; + *:Interix*:*) + case $UNAME_MACHINE in + x86) + echo i586-pc-interix"$UNAME_RELEASE" + exit ;; + authenticamd | genuineintel | EM64T) + echo x86_64-unknown-interix"$UNAME_RELEASE" + exit ;; + IA64) + echo ia64-unknown-interix"$UNAME_RELEASE" + exit ;; + esac ;; + i*:UWIN*:*) + echo "$UNAME_MACHINE"-pc-uwin + exit ;; + amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*) + echo x86_64-pc-cygwin + exit ;; + prep*:SunOS:5.*:*) + echo powerpcle-unknown-solaris2"$(echo "$UNAME_RELEASE"|sed -e 's/[^.]*//')" + exit ;; + *:GNU:*:*) + # the GNU system + echo "$(echo "$UNAME_MACHINE"|sed -e 's,[-/].*$,,')-unknown-$LIBC$(echo "$UNAME_RELEASE"|sed -e 's,/.*$,,')" + exit ;; + *:GNU/*:*:*) + # other systems with GNU libc and userland + echo "$UNAME_MACHINE-unknown-$(echo "$UNAME_SYSTEM" | sed 's,^[^/]*/,,' | tr "[:upper:]" "[:lower:]")$(echo "$UNAME_RELEASE"|sed -e 's/[-(].*//')-$LIBC" + exit ;; + *:Minix:*:*) + echo "$UNAME_MACHINE"-unknown-minix + exit ;; + aarch64:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + aarch64_be:Linux:*:*) + UNAME_MACHINE=aarch64_be + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + 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 + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + arc:Linux:*:* | arceb:Linux:*:* | arc64:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + arm*:Linux:*:*) + set_cc_for_build + if echo __ARM_EABI__ | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ARM_EABI__ + then + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + else + if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ARM_PCS_VFP + then + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"eabi + else + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC"eabihf + fi + fi + exit ;; + avr32*:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + cris:Linux:*:*) + echo "$UNAME_MACHINE"-axis-linux-"$LIBC" + exit ;; + crisv32:Linux:*:*) + echo "$UNAME_MACHINE"-axis-linux-"$LIBC" + exit ;; + e2k:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + frv:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + hexagon:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + i*86:Linux:*:*) + echo "$UNAME_MACHINE"-pc-linux-"$LIBC" + exit ;; + ia64:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + k1om:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + loongarch32:Linux:*:* | loongarch64:Linux:*:* | loongarchx32:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + m32r*:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + m68*:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + 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 + eval "$($CC_FOR_BUILD -E "$dummy.c" 2>/dev/null | grep '^CPU\|^MIPS_ENDIAN\|^LIBCABI')" + test "x$CPU" != x && { echo "$CPU${MIPS_ENDIAN}-unknown-linux-$LIBCABI"; exit; } + ;; + mips64el:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + openrisc*:Linux:*:*) + echo or1k-unknown-linux-"$LIBC" + exit ;; + or32:Linux:*:* | or1k*:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + padre:Linux:*:*) + echo sparc-unknown-linux-"$LIBC" + exit ;; + parisc64:Linux:*:* | hppa64:Linux:*:*) + echo hppa64-unknown-linux-"$LIBC" + exit ;; + parisc:Linux:*:* | hppa:Linux:*:*) + # Look for CPU level + case $(grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2) in + PA7*) echo hppa1.1-unknown-linux-"$LIBC" ;; + PA8*) echo hppa2.0-unknown-linux-"$LIBC" ;; + *) echo hppa-unknown-linux-"$LIBC" ;; + esac + exit ;; + ppc64:Linux:*:*) + echo powerpc64-unknown-linux-"$LIBC" + exit ;; + ppc:Linux:*:*) + echo powerpc-unknown-linux-"$LIBC" + exit ;; + ppc64le:Linux:*:*) + echo powerpc64le-unknown-linux-"$LIBC" + exit ;; + ppcle:Linux:*:*) + echo powerpcle-unknown-linux-"$LIBC" + exit ;; + riscv32:Linux:*:* | riscv32be:Linux:*:* | riscv64:Linux:*:* | riscv64be:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + s390:Linux:*:* | s390x:Linux:*:*) + echo "$UNAME_MACHINE"-ibm-linux-"$LIBC" + exit ;; + sh64*:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + sh*:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + sparc:Linux:*:* | sparc64:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + tile*:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + vax:Linux:*:*) + echo "$UNAME_MACHINE"-dec-linux-"$LIBC" + exit ;; + 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 + echo "$UNAME_MACHINE"-pc-linux-"$LIBCABI" + exit ;; + xtensa*:Linux:*:*) + echo "$UNAME_MACHINE"-unknown-linux-"$LIBC" + exit ;; + 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. + echo i386-sequent-sysv4 + exit ;; + 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. + echo "$UNAME_MACHINE"-pc-sysv4.2uw"$UNAME_VERSION" + exit ;; + i*86:OS/2:*:*) + # If we were able to find `uname', then EMX Unix compatibility + # is probably installed. + echo "$UNAME_MACHINE"-pc-os2-emx + exit ;; + i*86:XTS-300:*:STOP) + echo "$UNAME_MACHINE"-unknown-stop + exit ;; + i*86:atheos:*:*) + echo "$UNAME_MACHINE"-unknown-atheos + exit ;; + i*86:syllable:*:*) + echo "$UNAME_MACHINE"-pc-syllable + exit ;; + i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.[02]*:*) + echo i386-unknown-lynxos"$UNAME_RELEASE" + exit ;; + i*86:*DOS:*:*) + echo "$UNAME_MACHINE"-pc-msdosdjgpp + exit ;; + i*86:*:4.*:*) + UNAME_REL=$(echo "$UNAME_RELEASE" | sed 's/\/MP$//') + if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then + echo "$UNAME_MACHINE"-univel-sysv"$UNAME_REL" + else + echo "$UNAME_MACHINE"-pc-sysv"$UNAME_REL" + fi + exit ;; + 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 + echo "$UNAME_MACHINE-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION}" + exit ;; + 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 + echo "$UNAME_MACHINE"-pc-sco"$UNAME_REL" + else + echo "$UNAME_MACHINE"-pc-sysv32 + fi + exit ;; + 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. + echo i586-pc-msdosdjgpp + exit ;; + Intel:Mach:3*:*) + echo i386-pc-mach3 + exit ;; + paragon:*:*:*) + echo i860-intel-osf1 + exit ;; + i860:*:4.*:*) # i860-SVR4 + if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then + echo i860-stardent-sysv"$UNAME_RELEASE" # Stardent Vistra i860-SVR4 + else # Add other i860-SVR4 vendors below as they are discovered. + echo i860-unknown-sysv"$UNAME_RELEASE" # Unknown i860-SVR4 + fi + exit ;; + mini*:CTIX:SYS*5:*) + # "miniframe" + echo m68010-convergent-sysv + exit ;; + mc68k:UNIX:SYSTEM5:3.51m) + echo m68k-convergent-sysv + exit ;; + M680?0:D-NIX:5.3:*) + echo m68k-diab-dnix + exit ;; + 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*:*) + echo m68k-unknown-lynxos"$UNAME_RELEASE" + exit ;; + mc68030:UNIX_System_V:4.*:*) + echo m68k-atari-sysv4 + exit ;; + TSUNAMI:LynxOS:2.*:*) + echo sparc-unknown-lynxos"$UNAME_RELEASE" + exit ;; + rs6000:LynxOS:2.*:*) + echo rs6000-unknown-lynxos"$UNAME_RELEASE" + exit ;; + PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.[02]*:*) + echo powerpc-unknown-lynxos"$UNAME_RELEASE" + exit ;; + SM[BE]S:UNIX_SV:*:*) + echo mips-dde-sysv"$UNAME_RELEASE" + exit ;; + RM*:ReliantUNIX-*:*:*) + echo mips-sni-sysv4 + exit ;; + RM*:SINIX-*:*:*) + echo mips-sni-sysv4 + exit ;; + *:SINIX-*:*:*) + if uname -p 2>/dev/null >/dev/null ; then + UNAME_MACHINE=$( (uname -p) 2>/dev/null) + echo "$UNAME_MACHINE"-sni-sysv4 + else + echo ns32k-sni-sysv + fi + exit ;; + PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort + # says + echo i586-unisys-sysv4 + exit ;; + *:UNIX_System_V:4*:FTX*) + # From Gerald Hewes . + # How about differentiating between stratus architectures? -djm + echo hppa1.1-stratus-sysv4 + exit ;; + *:*:*:FTX*) + # From seanf@swdc.stratus.com. + echo i860-stratus-sysv4 + exit ;; + i*86:VOS:*:*) + # From Paul.Green@stratus.com. + echo "$UNAME_MACHINE"-stratus-vos + exit ;; + *:VOS:*:*) + # From Paul.Green@stratus.com. + echo hppa1.1-stratus-vos + exit ;; + mc68*:A/UX:*:*) + echo m68k-apple-aux"$UNAME_RELEASE" + exit ;; + news*:NEWS-OS:6*:*) + echo mips-sony-newsos6 + exit ;; + R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) + if test -d /usr/nec; then + echo mips-nec-sysv"$UNAME_RELEASE" + else + echo mips-unknown-sysv"$UNAME_RELEASE" + fi + exit ;; + BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. + echo powerpc-be-beos + exit ;; + BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. + echo powerpc-apple-beos + exit ;; + BePC:BeOS:*:*) # BeOS running on Intel PC compatible. + echo i586-pc-beos + exit ;; + BePC:Haiku:*:*) # Haiku running on Intel PC compatible. + echo i586-pc-haiku + exit ;; + x86_64:Haiku:*:*) + echo x86_64-unknown-haiku + exit ;; + SX-4:SUPER-UX:*:*) + echo sx4-nec-superux"$UNAME_RELEASE" + exit ;; + SX-5:SUPER-UX:*:*) + echo sx5-nec-superux"$UNAME_RELEASE" + exit ;; + SX-6:SUPER-UX:*:*) + echo sx6-nec-superux"$UNAME_RELEASE" + exit ;; + SX-7:SUPER-UX:*:*) + echo sx7-nec-superux"$UNAME_RELEASE" + exit ;; + SX-8:SUPER-UX:*:*) + echo sx8-nec-superux"$UNAME_RELEASE" + exit ;; + SX-8R:SUPER-UX:*:*) + echo sx8r-nec-superux"$UNAME_RELEASE" + exit ;; + SX-ACE:SUPER-UX:*:*) + echo sxace-nec-superux"$UNAME_RELEASE" + exit ;; + Power*:Rhapsody:*:*) + echo powerpc-apple-rhapsody"$UNAME_RELEASE" + exit ;; + *:Rhapsody:*:*) + echo "$UNAME_MACHINE"-apple-rhapsody"$UNAME_RELEASE" + exit ;; + arm64:Darwin:*:*) + echo aarch64-apple-darwin"$UNAME_RELEASE" + exit ;; + *: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 + echo "$UNAME_PROCESSOR"-apple-darwin"$UNAME_RELEASE" + exit ;; + *:procnto*:*:* | *:QNX:[0123456789]*:*) + UNAME_PROCESSOR=$(uname -p) + if test "$UNAME_PROCESSOR" = x86; then + UNAME_PROCESSOR=i386 + UNAME_MACHINE=pc + fi + echo "$UNAME_PROCESSOR"-"$UNAME_MACHINE"-nto-qnx"$UNAME_RELEASE" + exit ;; + *:QNX:*:4*) + echo i386-pc-qnx + exit ;; + NEO-*:NONSTOP_KERNEL:*:*) + echo neo-tandem-nsk"$UNAME_RELEASE" + exit ;; + NSE-*:NONSTOP_KERNEL:*:*) + echo nse-tandem-nsk"$UNAME_RELEASE" + exit ;; + NSR-*:NONSTOP_KERNEL:*:*) + echo nsr-tandem-nsk"$UNAME_RELEASE" + exit ;; + NSV-*:NONSTOP_KERNEL:*:*) + echo nsv-tandem-nsk"$UNAME_RELEASE" + exit ;; + NSX-*:NONSTOP_KERNEL:*:*) + echo nsx-tandem-nsk"$UNAME_RELEASE" + exit ;; + *:NonStop-UX:*:*) + echo mips-compaq-nonstopux + exit ;; + BS2000:POSIX*:*:*) + echo bs2000-siemens-sysv + exit ;; + DS/*:UNIX_System_V:*:*) + echo "$UNAME_MACHINE"-"$UNAME_SYSTEM"-"$UNAME_RELEASE" + exit ;; + *: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 + echo "$UNAME_MACHINE"-unknown-plan9 + exit ;; + *:TOPS-10:*:*) + echo pdp10-unknown-tops10 + exit ;; + *:TENEX:*:*) + echo pdp10-unknown-tenex + exit ;; + KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) + echo pdp10-dec-tops20 + exit ;; + XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) + echo pdp10-xkl-tops20 + exit ;; + *:TOPS-20:*:*) + echo pdp10-unknown-tops20 + exit ;; + *:ITS:*:*) + echo pdp10-unknown-its + exit ;; + SEI:*:*:SEIUX) + echo mips-sei-seiux"$UNAME_RELEASE" + exit ;; + *:DragonFly:*:*) + echo "$UNAME_MACHINE"-unknown-dragonfly"$(echo "$UNAME_RELEASE"|sed -e 's/[-(].*//')" + exit ;; + *:*VMS:*:*) + UNAME_MACHINE=$( (uname -p) 2>/dev/null) + case $UNAME_MACHINE in + A*) echo alpha-dec-vms ; exit ;; + I*) echo ia64-dec-vms ; exit ;; + V*) echo vax-dec-vms ; exit ;; + esac ;; + *:XENIX:*:SysV) + echo i386-pc-xenix + exit ;; + i*86:skyos:*:*) + echo "$UNAME_MACHINE"-pc-skyos"$(echo "$UNAME_RELEASE" | sed -e 's/ .*$//')" + exit ;; + i*86:rdos:*:*) + echo "$UNAME_MACHINE"-pc-rdos + exit ;; + *:AROS:*:*) + echo "$UNAME_MACHINE"-unknown-aros + exit ;; + x86_64:VMkernel:*:*) + echo "$UNAME_MACHINE"-unknown-esx + exit ;; + amd64:Isilon\ OneFS:*:*) + echo x86_64-unknown-onefs + exit ;; + *:Unleashed:*:*) + echo "$UNAME_MACHINE"-unknown-unleashed"$UNAME_RELEASE" + exit ;; +esac + +# 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/third_party/sox/patch/config.sub b/third_party/sox/patch/config.sub new file mode 100644 index 00000000..7384e919 --- /dev/null +++ b/third_party/sox/patch/config.sub @@ -0,0 +1,1864 @@ +#! /bin/sh +# Configuration validation subroutine script. +# Copyright 1992-2021 Free Software Foundation, Inc. + +timestamp='2021-04-30' + +# 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. + +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-2021 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 +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 + ;; + # 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 + 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 caes, 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 + 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-uclibc* ) + ;; + uclinux-uclibc* ) + ;; + -dietlibc* | -newlib* | -musl* | -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/third_party/sox/patch/libmad.patch b/third_party/sox/patch/libmad.patch new file mode 100644 index 00000000..a8057878 --- /dev/null +++ b/third_party/sox/patch/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/third_party/sox/patch/sox.patch b/third_party/sox/patch/sox.patch new file mode 100644 index 00000000..fe8df945 --- /dev/null +++ b/third_party/sox/patch/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/torchaudio/__init__.py b/torchaudio/__init__.py new file mode 100644 index 00000000..d492dc7f --- /dev/null +++ b/torchaudio/__init__.py @@ -0,0 +1,38 @@ +from torchaudio import _extension # noqa: F401 +from torchaudio import ( + compliance, + datasets, + functional, + models, + pipelines, + kaldi_io, + utils, + sox_effects, + transforms, +) + +from torchaudio.backend import ( + list_audio_backends, + get_audio_backend, + set_audio_backend, +) + +try: + from .version import __version__, git_version # noqa: F401 +except ImportError: + pass + +__all__ = [ + 'compliance', + 'datasets', + 'functional', + 'models', + 'pipelines', + 'kaldi_io', + 'utils', + 'sox_effects', + 'transforms', + 'list_audio_backends', + 'get_audio_backend', + 'set_audio_backend', +] diff --git a/torchaudio/_extension.py b/torchaudio/_extension.py new file mode 100644 index 00000000..6bb217c6 --- /dev/null +++ b/torchaudio/_extension.py @@ -0,0 +1,27 @@ +import os +import warnings +from pathlib import Path + +import torch +from torchaudio._internal import module_utils as _mod_utils # noqa: F401 + + +def _init_extension(): + if not _mod_utils.is_module_available('torchaudio._torchaudio'): + warnings.warn('torchaudio C++ extension is not available.') + return + + suffix = 'pyd' if os.name == 'nt' else 'so' + path = Path(__file__).parent / 'lib' / f'libtorchaudio.{suffix}' + # In case `torchaudio` is deployed with `pex` format, this file does not exist. + # In this case, we expect that `libtorchaudio` is available somewhere + # in the search path of dynamic loading mechanism, and importing `_torchaudio`, + # which depends on `libtorchaudio` and dynamic loader will handle it for us. + if path.exists(): + torch.ops.load_library(path) + torch.classes.load_library(path) + # This import is for initializing the methods registered via PyBind11 + from torchaudio import _torchaudio # noqa + + +_init_extension() diff --git a/torchaudio/_internal/__init__.py b/torchaudio/_internal/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/torchaudio/_internal/module_utils.py b/torchaudio/_internal/module_utils.py new file mode 100644 index 00000000..eecc7cf6 --- /dev/null +++ b/torchaudio/_internal/module_utils.py @@ -0,0 +1,125 @@ +import warnings +import importlib.util +from typing import Optional +from functools import wraps + +import torch + + +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('torchaudio._torchaudio') and torch.ops.torchaudio.is_kaldi_available() + + +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 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(): + return is_module_available('torchaudio._torchaudio') and torch.ops.torchaudio.is_sox_available() + + +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 sox') + return wrapped + return decorator diff --git a/torchaudio/backend/__init__.py b/torchaudio/backend/__init__.py new file mode 100644 index 00000000..c3fdf0b4 --- /dev/null +++ b/torchaudio/backend/__init__.py @@ -0,0 +1,10 @@ +# flake8: noqa +from . import utils +from .utils import ( + list_audio_backends, + get_audio_backend, + set_audio_backend, +) + + +utils._init_audio_backend() diff --git a/torchaudio/backend/common.py b/torchaudio/backend/common.py new file mode 100644 index 00000000..f944b23d --- /dev/null +++ b/torchaudio/backend/common.py @@ -0,0 +1,51 @@ +class AudioMetaData: + """Return type of ``torchaudio.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 + * ``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/torchaudio/backend/no_backend.py b/torchaudio/backend/no_backend.py new file mode 100644 index 00000000..7160c89f --- /dev/null +++ b/torchaudio/backend/no_backend.py @@ -0,0 +1,22 @@ +from pathlib import Path +from typing import Callable, Optional, Tuple, Union + +from torch import Tensor + + +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/torchaudio/backend/soundfile_backend.py b/torchaudio/backend/soundfile_backend.py new file mode 100644 index 00000000..8b0b27c5 --- /dev/null +++ b/torchaudio/backend/soundfile_backend.py @@ -0,0 +1,433 @@ +"""The new soundfile backend which will become default in 0.8.0 onward""" +from typing import Tuple, Optional +import warnings + +import torch +from torchaudio._internal import module_utils as _mod_utils +from .common import AudioMetaData + + +if _mod_utils.is_soundfile_available(): + import soundfile + +# 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 TorchAudio. 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') + + +@_mod_utils.requires_soundfile() +def info(filepath: str, format: Optional[str] = None) -> AudioMetaData: + """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, + which has a restriction on type annotation due to TorchScript compiler compatiblity. + + 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: + AudioMetaData: meta data of the given audio. + + """ + sinfo = soundfile.info(filepath) + return AudioMetaData( + sinfo.samplerate, + sinfo.frames, + sinfo.channels, + bits_per_sample=_get_bit_depth(sinfo.subtype), + encoding=_get_encoding(sinfo.format, sinfo.subtype), + ) + + +_SUBTYPE2DTYPE = { + "PCM_S8": "int8", + "PCM_U8": "uint8", + "PCM_16": "int16", + "PCM_32": "int32", + "FLOAT": "float32", + "DOUBLE": "float64", +} + + +@_mod_utils.requires_soundfile() +def load( + filepath: str, + frame_offset: int = 0, + num_frames: int = -1, + normalize: bool = True, + channels_first: bool = True, + format: Optional[str] = None, +) -> Tuple[torch.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, + which has a restriction on type annotation due to TorchScript compiler compatiblity. + + 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: + (torch.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 = torch.from_numpy(waveform) + if channels_first: + waveform = waveform.t() + return waveform, sample_rate + + +def _get_subtype_for_wav( + dtype: torch.dtype, + encoding: str, + bits_per_sample: int): + if not encoding: + if not bits_per_sample: + subtype = { + torch.uint8: "PCM_U8", + torch.int16: "PCM_16", + torch.int32: "PCM_32", + torch.float32: "FLOAT", + torch.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: torch.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}") + + +@_mod_utils.requires_soundfile() +def save( + filepath: str, + src: torch.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, + which has a restriction on type annotation due to TorchScript compiler compatiblity. + + Args: + filepath (str or pathlib.Path): Path to audio file. + src (torch.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 + ) diff --git a/torchaudio/backend/sox_io_backend.py b/torchaudio/backend/sox_io_backend.py new file mode 100644 index 00000000..b725bcc9 --- /dev/null +++ b/torchaudio/backend/sox_io_backend.py @@ -0,0 +1,317 @@ +import os +from typing import Tuple, Optional + +import torch +from torchaudio._internal import ( + module_utils as _mod_utils, +) + +import torchaudio +from .common import AudioMetaData + + +@_mod_utils.requires_sox() +def info( + filepath: str, + format: Optional[str] = None, +) -> AudioMetaData: + """Get signal information of an audio file. + + Args: + filepath (path-like object or file-like object): + Source of audio data. When the function is not compiled by TorchScript, + (e.g. ``torch.jit.script``), the following types are accepted; + + * ``path-like``: file path + * ``file-like``: Object with ``read(size: int) -> bytes`` method, + which returns byte string of at most ``size`` length. + + When the function is compiled by TorchScript, only ``str`` type is allowed. + + Note: + + * When the input type is file-like object, this function cannot + get the correct length (``num_samples``) for certain formats, + such as ``mp3`` and ``vorbis``. + In this case, the value of ``num_samples`` is ``0``. + * This argument is intentionally annotated as ``str`` only due to + TorchScript compiler compatibility. + + 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: + AudioMetaData: Metadata of the given audio. + """ + if not torch.jit.is_scripting(): + if hasattr(filepath, 'read'): + sinfo = torchaudio._torchaudio.get_info_fileobj(filepath, format) + return AudioMetaData(*sinfo) + filepath = os.fspath(filepath) + sinfo = torch.ops.torchaudio.sox_io_get_info(filepath, format) + return AudioMetaData(*sinfo) + + +@_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[torch.Tensor, int]: + """Load audio data from file. + + Note: + This function can handle all the codecs that underlying libsox can handle, + however it is tested on the following formats; + + * WAV, AMB + + * 32-bit floating-point + * 32-bit signed integer + * 24-bit signed integer + * 16-bit signed integer + * 8-bit unsigned integer (WAV only) + + * MP3 + * FLAC + * OGG/VORBIS + * OPUS + * SPHERE + * AMR-NB + + To load ``MP3``, ``FLAC``, ``OGG/VORBIS``, ``OPUS`` and other codecs ``libsox`` does not + handle natively, your installation of ``torchaudio`` has to be linked to ``libsox`` + and corresponding codec libraries such as ``libmad`` or ``libmp3lame`` etc. + + 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, 24-bit signed integer, and 8-bit unsigned integer, 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. Since torch does not + support ``int24`` dtype, 24-bit signed PCM are converted to ``int32`` tensors. + + ``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]``. + + Args: + filepath (path-like object or file-like object): + Source of audio data. When the function is not compiled by TorchScript, + (e.g. ``torch.jit.script``), the following types are accepted; + + * ``path-like``: file path + * ``file-like``: Object with ``read(size: int) -> bytes`` method, + which returns byte string of at most ``size`` length. + + When the function is compiled by TorchScript, only ``str`` type is allowed. + + Note: This argument is intentionally annotated as ``str`` only due to + TorchScript compiler compatibility. + frame_offset (int): + 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): + 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: + (torch.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]`. + """ + if not torch.jit.is_scripting(): + if hasattr(filepath, 'read'): + return torchaudio._torchaudio.load_audio_fileobj( + filepath, frame_offset, num_frames, normalize, channels_first, format) + filepath = os.fspath(filepath) + return torch.ops.torchaudio.sox_io_load_audio_file( + filepath, frame_offset, num_frames, normalize, channels_first, format) + + +@_mod_utils.requires_sox() +def save( + filepath: str, + src: torch.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. + + Args: + filepath (str or pathlib.Path): Path to save file. + This function also handles ``pathlib.Path`` objects, but is annotated + as ``str`` for TorchScript compiler compatibility. + src (torch.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 or None, optional): Used for formats other than WAV. + This corresponds to ``-C`` option of ``sox`` command. + + ``"mp3"`` + Either bitrate (in ``kbps``) with quality factor, such as ``128.2``, or + VBR encoding with quality factor such as ``-4.2``. Default: ``-4.5``. + + ``"flac"`` + Whole number from ``0`` to ``8``. ``8`` is default and highest compression. + + ``"ogg"``, ``"vorbis"`` + Number from ``-1`` to ``10``; ``-1`` is the highest compression + and lowest quality. Default: ``3``. + + See the detail at http://sox.sourceforge.net/soxformat.html. + format (str or None, optional): Override the audio format. + When ``filepath`` argument is path-like object, audio format is infered from + file extension. If 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"``, ``"mp3"``, ``"ogg"``, ``"vorbis"``, ``"amr-nb"``, + ``"amb"``, ``"flac"``, ``"sph"``, ``"gsm"``, and ``"htk"``. + + encoding (str or None, optional): Changes the encoding for the supported formats. + This argument is effective only for supported formats, such as ``"wav"``, ``""amb"`` + 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) + + Default values + If not provided, the default value is picked based on ``format`` and ``bits_per_sample``. + + ``"wav"``, ``"amb"`` + - | If both ``encoding`` and ``bits_per_sample`` are not provided, the ``dtype`` of the + | Tensor is used to determine the default value. + - ``"PCM_U"`` if dtype is ``uint8`` + - ``"PCM_S"`` if dtype is ``int16`` or ``int32` + - ``"PCM_F"`` if dtype is ``float32`` + + - ``"PCM_U"`` if ``bits_per_sample=8`` + - ``"PCM_S"`` otherwise + + ``"sph"`` format; + - the default value is ``"PCM_S"`` + + bits_per_sample (int or None, optional): Changes the bit depth for the supported formats. + When ``format`` is one of ``"wav"``, ``"flac"``, ``"sph"``, or ``"amb"``, you can change the + bit depth. Valid values are ``8``, ``16``, ``32`` and ``64``. + + Default Value; + If not provided, the default values are picked based on ``format`` and ``"encoding"``; + + ``"wav"``, ``"amb"``; + - | If both ``encoding`` and ``bits_per_sample`` are not provided, the ``dtype`` of the + | Tensor is used. + - ``8`` if dtype is ``uint8`` + - ``16`` if dtype is ``int16`` + - ``32`` if dtype is ``int32`` or ``float32`` + + - ``8`` if ``encoding`` is ``"PCM_U"``, ``"ULAW"`` or ``"ALAW"`` + - ``16`` if ``encoding`` is ``"PCM_S"`` + - ``32`` if ``encoding`` is ``"PCM_F"`` + + ``"flac"`` format; + - the default value is ``24`` + + ``"sph"`` format; + - ``16`` if ``encoding`` is ``"PCM_U"``, ``"PCM_S"``, ``"PCM_F"`` or not provided. + - ``8`` if ``encoding`` is ``"ULAW"`` or ``"ALAW"`` + + ``"amb"`` format; + - ``8`` if ``encoding`` is ``"PCM_U"``, ``"ULAW"`` or ``"ALAW"`` + - ``16`` if ``encoding`` is ``"PCM_S"`` or not provided. + - ``32`` if ``encoding`` is ``"PCM_F"`` + + Supported formats/encodings/bit depth/compression are; + + ``"wav"``, ``"amb"`` + - 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. + + ``"mp3"`` + Fixed bit rate (such as 128kHz) and variable bit rate compression. + Default: VBR with high quality. + + ``"flac"`` + - 8-bit + - 16-bit + - 24-bit (default) + + ``"ogg"``, ``"vorbis"`` + - Different quality level. Default: approx. 112kbps + + ``"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 + + ``"amr-nb"`` + Bitrate ranging from 4.75 kbit/s to 12.2 kbit/s. Default: 4.75 kbit/s + + ``"gsm"`` + Lossy Speech Compression, CPU intensive. + + ``"htk"`` + Uses a default single-channel 16-bit PCM format. + + Note: + To save into formats that ``libsox`` does not handle natively, (such as ``"mp3"``, + ``"flac"``, ``"ogg"`` and ``"vorbis"``), your installation of ``torchaudio`` has + to be linked to ``libsox`` and corresponding codec libraries such as ``libmad`` + or ``libmp3lame`` etc. + """ + if not torch.jit.is_scripting(): + if hasattr(filepath, 'write'): + torchaudio._torchaudio.save_audio_fileobj( + filepath, src, sample_rate, channels_first, compression, + format, encoding, bits_per_sample) + return + filepath = os.fspath(filepath) + torch.ops.torchaudio.sox_io_save_audio_file( + filepath, src, sample_rate, channels_first, compression, format, encoding, bits_per_sample) diff --git a/torchaudio/backend/utils.py b/torchaudio/backend/utils.py new file mode 100644 index 00000000..ab670e0c --- /dev/null +++ b/torchaudio/backend/utils.py @@ -0,0 +1,83 @@ +"""Defines utilities for switching audio backends""" +import warnings +from typing import Optional, List + +import torchaudio +from torchaudio._internal import module_utils as _mod_utils +from . import ( + no_backend, + sox_io_backend, + soundfile_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(torchaudio, func, getattr(module, func)) + + +def _init_audio_backend(): + backends = list_audio_backends() + if 'sox_io' in backends: + set_audio_backend('sox_io') + elif 'soundfile' in backends: + set_audio_backend('soundfile') + 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 torchaudio.load == no_backend.load: + return None + if torchaudio.load == sox_io_backend.load: + return 'sox_io' + if torchaudio.load == soundfile_backend.load: + return 'soundfile' + raise ValueError('Unknown backend.') diff --git a/torchaudio/compliance/__init__.py b/torchaudio/compliance/__init__.py new file mode 100644 index 00000000..795065dc --- /dev/null +++ b/torchaudio/compliance/__init__.py @@ -0,0 +1,5 @@ +from . import kaldi + +__all__ = [ + 'kaldi', +] diff --git a/torchaudio/compliance/kaldi.py b/torchaudio/compliance/kaldi.py new file mode 100644 index 00000000..60442f0d --- /dev/null +++ b/torchaudio/compliance/kaldi.py @@ -0,0 +1,750 @@ +from typing import Tuple + +import math +import torch +from torch import Tensor + +import torchaudio + +__all__ = [ + 'get_mel_banks', + 'inverse_mel_scale', + 'inverse_mel_scale_scalar', + 'mel_scale', + 'mel_scale_scalar', + 'spectrogram', + 'fbank', + 'mfcc', + 'vtln_warp_freq', + 'vtln_warp_mel_freq', +] + +# numeric_limits::epsilon() 1.1920928955078125e-07 +EPSILON = torch.tensor(torch.finfo(torch.float).eps) +# 1 milliseconds = 0.001 seconds +MILLISECONDS_TO_SECONDS = 0.001 + +# window types +HAMMING = 'hamming' +HANNING = 'hanning' +POVEY = 'povey' +RECTANGULAR = 'rectangular' +BLACKMAN = 'blackman' +WINDOWS = [HAMMING, HANNING, POVEY, RECTANGULAR, BLACKMAN] + + +def _get_epsilon(device, dtype): + return EPSILON.to(device=device, dtype=dtype) + + +def _next_power_of_2(x: int) -> int: + r"""Returns the smallest power of 2 that is greater than x + """ + return 1 if x == 0 else 2 ** (x - 1).bit_length() + + +def _get_strided(waveform: Tensor, window_size: int, window_shift: int, snip_edges: bool) -> Tensor: + r"""Given a waveform (1D tensor of size ``num_samples``), it returns a 2D tensor (m, ``window_size``) + representing how the window is shifted along the waveform. Each row is a frame. + + Args: + waveform (Tensor): Tensor of size ``num_samples`` + window_size (int): Frame length + window_shift (int): Frame shift + snip_edges (bool): If True, end effects will be handled by outputting only frames that completely fit + in the file, and the number of frames depends on the frame_length. If False, the number of frames + depends only on the frame_shift, and we reflect the data at the ends. + + Returns: + Tensor: 2D tensor of size (m, ``window_size``) where each row is a frame + """ + assert waveform.dim() == 1 + num_samples = waveform.size(0) + strides = (window_shift * waveform.stride(0), waveform.stride(0)) + + if snip_edges: + if num_samples < window_size: + return torch.empty((0, 0), dtype=waveform.dtype, device=waveform.device) + else: + m = 1 + (num_samples - window_size) // window_shift + else: + reversed_waveform = torch.flip(waveform, [0]) + m = (num_samples + (window_shift // 2)) // window_shift + pad = window_size // 2 - window_shift // 2 + pad_right = reversed_waveform + if pad > 0: + # torch.nn.functional.pad returns [2,1,0,1,2] for 'reflect' + # but we want [2, 1, 0, 0, 1, 2] + pad_left = reversed_waveform[-pad:] + waveform = torch.cat((pad_left, waveform, pad_right), dim=0) + else: + # pad is negative so we want to trim the waveform at the front + waveform = torch.cat((waveform[-pad:], pad_right), dim=0) + + sizes = (m, window_size) + return waveform.as_strided(sizes, strides) + + +def _feature_window_function(window_type: str, + window_size: int, + blackman_coeff: float, + device: torch.device, + dtype: int, + ) -> Tensor: + r"""Returns a window function with the given type and size + """ + if window_type == HANNING: + return torch.hann_window(window_size, periodic=False, device=device, dtype=dtype) + elif window_type == HAMMING: + return torch.hamming_window(window_size, periodic=False, alpha=0.54, beta=0.46, device=device, dtype=dtype) + elif window_type == POVEY: + # like hanning but goes to zero at edges + return torch.hann_window(window_size, periodic=False, device=device, dtype=dtype).pow(0.85) + elif window_type == RECTANGULAR: + return torch.ones(window_size, device=device, dtype=dtype) + elif window_type == BLACKMAN: + a = 2 * math.pi / (window_size - 1) + window_function = torch.arange(window_size, device=device, dtype=dtype) + # can't use torch.blackman_window as they use different coefficients + return (blackman_coeff - 0.5 * torch.cos(a * window_function) + + (0.5 - blackman_coeff) * torch.cos(2 * a * window_function)).to(device=device, dtype=dtype) + else: + raise Exception('Invalid window type ' + window_type) + + +def _get_log_energy(strided_input: Tensor, + epsilon: Tensor, + energy_floor: float) -> Tensor: + r"""Returns the log energy of size (m) for a strided_input (m,*) + """ + device, dtype = strided_input.device, strided_input.dtype + log_energy = torch.max(strided_input.pow(2).sum(1), epsilon).log() # size (m) + if energy_floor == 0.0: + return log_energy + return torch.max( + log_energy, torch.tensor(math.log(energy_floor), device=device, dtype=dtype)) + + +def _get_waveform_and_window_properties(waveform: Tensor, + channel: int, + sample_frequency: float, + frame_shift: float, + frame_length: float, + round_to_power_of_two: bool, + preemphasis_coefficient: float) -> Tuple[Tensor, int, int, int]: + r"""Gets the waveform and window properties + """ + channel = max(channel, 0) + assert channel < waveform.size(0), ('Invalid channel {} for size {}'.format(channel, waveform.size(0))) + waveform = waveform[channel, :] # size (n) + window_shift = int(sample_frequency * frame_shift * MILLISECONDS_TO_SECONDS) + window_size = int(sample_frequency * frame_length * MILLISECONDS_TO_SECONDS) + padded_window_size = _next_power_of_2(window_size) if round_to_power_of_two else window_size + + assert 2 <= window_size <= len( + waveform), ('choose a window size {} that is [2, {}]' + .format(window_size, len(waveform))) + assert 0 < window_shift, '`window_shift` must be greater than 0' + assert padded_window_size % 2 == 0, 'the padded `window_size` must be divisible by two.' \ + ' use `round_to_power_of_two` or change `frame_length`' + assert 0. <= preemphasis_coefficient <= 1.0, '`preemphasis_coefficient` must be between [0,1]' + assert sample_frequency > 0, '`sample_frequency` must be greater than zero' + return waveform, window_shift, window_size, padded_window_size + + +def _get_window(waveform: Tensor, + padded_window_size: int, + window_size: int, + window_shift: int, + window_type: str, + blackman_coeff: float, + snip_edges: bool, + raw_energy: bool, + energy_floor: float, + dither: float, + remove_dc_offset: bool, + preemphasis_coefficient: float) -> Tuple[Tensor, Tensor]: + r"""Gets a window and its log energy + + Returns: + (Tensor, Tensor): strided_input of size (m, ``padded_window_size``) and signal_log_energy of size (m) + """ + device, dtype = waveform.device, waveform.dtype + epsilon = _get_epsilon(device, dtype) + + # size (m, window_size) + strided_input = _get_strided(waveform, window_size, window_shift, snip_edges) + + if dither != 0.0: + # Returns a random number strictly between 0 and 1 + x = torch.max(epsilon, torch.rand(strided_input.shape, device=device, dtype=dtype)) + rand_gauss = torch.sqrt(-2 * x.log()) * torch.cos(2 * math.pi * x) + strided_input = strided_input + rand_gauss * dither + + if remove_dc_offset: + # Subtract each row/frame by its mean + row_means = torch.mean(strided_input, dim=1).unsqueeze(1) # size (m, 1) + strided_input = strided_input - row_means + + if raw_energy: + # Compute the log energy of each row/frame before applying preemphasis and + # window function + signal_log_energy = _get_log_energy(strided_input, epsilon, energy_floor) # size (m) + + if preemphasis_coefficient != 0.0: + # strided_input[i,j] -= preemphasis_coefficient * strided_input[i, max(0, j-1)] for all i,j + offset_strided_input = torch.nn.functional.pad( + strided_input.unsqueeze(0), (1, 0), mode='replicate').squeeze(0) # size (m, window_size + 1) + strided_input = strided_input - preemphasis_coefficient * offset_strided_input[:, :-1] + + # Apply window_function to each row/frame + window_function = _feature_window_function( + window_type, window_size, blackman_coeff, device, dtype).unsqueeze(0) # size (1, window_size) + strided_input = strided_input * window_function # size (m, window_size) + + # Pad columns with zero until we reach size (m, padded_window_size) + if padded_window_size != window_size: + padding_right = padded_window_size - window_size + strided_input = torch.nn.functional.pad( + strided_input.unsqueeze(0), (0, padding_right), mode='constant', value=0).squeeze(0) + + # Compute energy after window function (not the raw one) + if not raw_energy: + signal_log_energy = _get_log_energy(strided_input, epsilon, energy_floor) # size (m) + + return strided_input, signal_log_energy + + +def _subtract_column_mean(tensor: Tensor, subtract_mean: bool) -> Tensor: + # subtracts the column mean of the tensor size (m, n) if subtract_mean=True + # it returns size (m, n) + if subtract_mean: + col_means = torch.mean(tensor, dim=0).unsqueeze(0) + tensor = tensor - col_means + return tensor + + +def spectrogram(waveform: Tensor, + blackman_coeff: float = 0.42, + channel: int = -1, + dither: float = 0.0, + energy_floor: float = 1.0, + frame_length: float = 25.0, + frame_shift: float = 10.0, + min_duration: float = 0.0, + preemphasis_coefficient: float = 0.97, + raw_energy: bool = True, + remove_dc_offset: bool = True, + round_to_power_of_two: bool = True, + sample_frequency: float = 16000.0, + snip_edges: bool = True, + subtract_mean: bool = False, + window_type: str = POVEY) -> Tensor: + r"""Create a spectrogram from a raw audio signal. This matches the input/output of Kaldi's + compute-spectrogram-feats. + + Args: + waveform (Tensor): Tensor of audio of size (c, n) where c is in the range [0,2) + blackman_coeff (float, optional): Constant coefficient for generalized Blackman window. (Default: ``0.42``) + channel (int, optional): Channel to extract (-1 -> expect mono, 0 -> left, 1 -> right) (Default: ``-1``) + dither (float, optional): Dithering constant (0.0 means no dither). If you turn this off, you should set + the energy_floor option, e.g. to 1.0 or 0.1 (Default: ``0.0``) + energy_floor (float, optional): Floor on energy (absolute, not relative) in Spectrogram computation. Caution: + this floor is applied to the zeroth component, representing the total signal energy. The floor on the + individual spectrogram elements is fixed at std::numeric_limits::epsilon(). (Default: ``1.0``) + frame_length (float, optional): Frame length in milliseconds (Default: ``25.0``) + frame_shift (float, optional): Frame shift in milliseconds (Default: ``10.0``) + min_duration (float, optional): Minimum duration of segments to process (in seconds). (Default: ``0.0``) + preemphasis_coefficient (float, optional): Coefficient for use in signal preemphasis (Default: ``0.97``) + raw_energy (bool, optional): If True, compute energy before preemphasis and windowing (Default: ``True``) + remove_dc_offset (bool, optional): Subtract mean from waveform on each frame (Default: ``True``) + round_to_power_of_two (bool, optional): If True, round window size to power of two by zero-padding input + to FFT. (Default: ``True``) + sample_frequency (float, optional): Waveform data sample frequency (must match the waveform file, if + specified there) (Default: ``16000.0``) + snip_edges (bool, optional): If True, end effects will be handled by outputting only frames that completely fit + in the file, and the number of frames depends on the frame_length. If False, the number of frames + depends only on the frame_shift, and we reflect the data at the ends. (Default: ``True``) + subtract_mean (bool, optional): Subtract mean of each feature file [CMS]; not recommended to do + it this way. (Default: ``False``) + window_type (str, optional): Type of window ('hamming'|'hanning'|'povey'|'rectangular'|'blackman') + (Default: ``'povey'``) + + Returns: + Tensor: A spectrogram identical to what Kaldi would output. The shape is + (m, ``padded_window_size // 2 + 1``) where m is calculated in _get_strided + """ + device, dtype = waveform.device, waveform.dtype + epsilon = _get_epsilon(device, dtype) + + waveform, window_shift, window_size, padded_window_size = _get_waveform_and_window_properties( + waveform, channel, sample_frequency, frame_shift, frame_length, round_to_power_of_two, preemphasis_coefficient) + + if len(waveform) < min_duration * sample_frequency: + # signal is too short + return torch.empty(0) + + strided_input, signal_log_energy = _get_window( + waveform, padded_window_size, window_size, window_shift, window_type, blackman_coeff, + snip_edges, raw_energy, energy_floor, dither, remove_dc_offset, preemphasis_coefficient) + + # size (m, padded_window_size // 2 + 1, 2) + fft = torch.fft.rfft(strided_input) + + # Convert the FFT into a power spectrum + power_spectrum = torch.max(fft.abs().pow(2.), epsilon).log() # size (m, padded_window_size // 2 + 1) + power_spectrum[:, 0] = signal_log_energy + + power_spectrum = _subtract_column_mean(power_spectrum, subtract_mean) + return power_spectrum + + +def inverse_mel_scale_scalar(mel_freq: float) -> float: + return 700.0 * (math.exp(mel_freq / 1127.0) - 1.0) + + +def inverse_mel_scale(mel_freq: Tensor) -> Tensor: + return 700.0 * ((mel_freq / 1127.0).exp() - 1.0) + + +def mel_scale_scalar(freq: float) -> float: + return 1127.0 * math.log(1.0 + freq / 700.0) + + +def mel_scale(freq: Tensor) -> Tensor: + return 1127.0 * (1.0 + freq / 700.0).log() + + +def vtln_warp_freq(vtln_low_cutoff: float, + vtln_high_cutoff: float, + low_freq: float, + high_freq: float, + vtln_warp_factor: float, + freq: Tensor) -> Tensor: + r"""This computes a VTLN warping function that is not the same as HTK's one, + but has similar inputs (this function has the advantage of never producing + empty bins). + + This function computes a warp function F(freq), defined between low_freq + and high_freq inclusive, with the following properties: + F(low_freq) == low_freq + F(high_freq) == high_freq + The function is continuous and piecewise linear with two inflection + points. + The lower inflection point (measured in terms of the unwarped + frequency) is at frequency l, determined as described below. + The higher inflection point is at a frequency h, determined as + described below. + If l <= f <= h, then F(f) = f/vtln_warp_factor. + If the higher inflection point (measured in terms of the unwarped + frequency) is at h, then max(h, F(h)) == vtln_high_cutoff. + Since (by the last point) F(h) == h/vtln_warp_factor, then + max(h, h/vtln_warp_factor) == vtln_high_cutoff, so + h = vtln_high_cutoff / max(1, 1/vtln_warp_factor). + = vtln_high_cutoff * min(1, vtln_warp_factor). + If the lower inflection point (measured in terms of the unwarped + frequency) is at l, then min(l, F(l)) == vtln_low_cutoff + This implies that l = vtln_low_cutoff / min(1, 1/vtln_warp_factor) + = vtln_low_cutoff * max(1, vtln_warp_factor) + Args: + vtln_low_cutoff (float): Lower frequency cutoffs for VTLN + vtln_high_cutoff (float): Upper frequency cutoffs for VTLN + low_freq (float): Lower frequency cutoffs in mel computation + high_freq (float): Upper frequency cutoffs in mel computation + vtln_warp_factor (float): Vtln warp factor + freq (Tensor): given frequency in Hz + + Returns: + Tensor: Freq after vtln warp + """ + assert vtln_low_cutoff > low_freq, 'be sure to set the vtln_low option higher than low_freq' + assert vtln_high_cutoff < high_freq, 'be sure to set the vtln_high option lower than high_freq [or negative]' + l = vtln_low_cutoff * max(1.0, vtln_warp_factor) + h = vtln_high_cutoff * min(1.0, vtln_warp_factor) + scale = 1.0 / vtln_warp_factor + Fl = scale * l # F(l) + Fh = scale * h # F(h) + assert l > low_freq and h < high_freq + # slope of left part of the 3-piece linear function + scale_left = (Fl - low_freq) / (l - low_freq) + # [slope of center part is just "scale"] + + # slope of right part of the 3-piece linear function + scale_right = (high_freq - Fh) / (high_freq - h) + + res = torch.empty_like(freq) + + outside_low_high_freq = torch.lt(freq, low_freq) | torch.gt(freq, high_freq) # freq < low_freq || freq > high_freq + before_l = torch.lt(freq, l) # freq < l + before_h = torch.lt(freq, h) # freq < h + after_h = torch.ge(freq, h) # freq >= h + + # order of operations matter here (since there is overlapping frequency regions) + res[after_h] = high_freq + scale_right * (freq[after_h] - high_freq) + res[before_h] = scale * freq[before_h] + res[before_l] = low_freq + scale_left * (freq[before_l] - low_freq) + res[outside_low_high_freq] = freq[outside_low_high_freq] + + return res + + +def vtln_warp_mel_freq(vtln_low_cutoff: float, + vtln_high_cutoff: float, + low_freq, high_freq: float, + vtln_warp_factor: float, + mel_freq: Tensor) -> Tensor: + r""" + Args: + vtln_low_cutoff (float): Lower frequency cutoffs for VTLN + vtln_high_cutoff (float): Upper frequency cutoffs for VTLN + low_freq (float): Lower frequency cutoffs in mel computation + high_freq (float): Upper frequency cutoffs in mel computation + vtln_warp_factor (float): Vtln warp factor + mel_freq (Tensor): Given frequency in Mel + + Returns: + Tensor: ``mel_freq`` after vtln warp + """ + return mel_scale(vtln_warp_freq(vtln_low_cutoff, vtln_high_cutoff, low_freq, high_freq, + vtln_warp_factor, inverse_mel_scale(mel_freq))) + + +def get_mel_banks(num_bins: int, + window_length_padded: int, + sample_freq: float, + low_freq: float, + high_freq: float, + vtln_low: float, + vtln_high: float, + vtln_warp_factor: float) -> Tuple[Tensor, Tensor]: + """ + Returns: + (Tensor, Tensor): The tuple consists of ``bins`` (which is + melbank of size (``num_bins``, ``num_fft_bins``)) and ``center_freqs`` (which is + center frequencies of bins of size (``num_bins``)). + """ + assert num_bins > 3, 'Must have at least 3 mel bins' + assert window_length_padded % 2 == 0 + num_fft_bins = window_length_padded / 2 + nyquist = 0.5 * sample_freq + + if high_freq <= 0.0: + high_freq += nyquist + + assert (0.0 <= low_freq < nyquist) and (0.0 < high_freq <= nyquist) and (low_freq < high_freq), \ + ('Bad values in options: low-freq {} and high-freq {} vs. nyquist {}'.format(low_freq, high_freq, nyquist)) + + # fft-bin width [think of it as Nyquist-freq / half-window-length] + fft_bin_width = sample_freq / window_length_padded + mel_low_freq = mel_scale_scalar(low_freq) + mel_high_freq = mel_scale_scalar(high_freq) + + # divide by num_bins+1 in next line because of end-effects where the bins + # spread out to the sides. + mel_freq_delta = (mel_high_freq - mel_low_freq) / (num_bins + 1) + + if vtln_high < 0.0: + vtln_high += nyquist + + assert vtln_warp_factor == 1.0 or ((low_freq < vtln_low < high_freq) and + (0.0 < vtln_high < high_freq) and (vtln_low < vtln_high)), \ + ('Bad values in options: vtln-low {} and vtln-high {}, versus ' + 'low-freq {} and high-freq {}'.format(vtln_low, vtln_high, low_freq, high_freq)) + + bin = torch.arange(num_bins).unsqueeze(1) + left_mel = mel_low_freq + bin * mel_freq_delta # size(num_bins, 1) + center_mel = mel_low_freq + (bin + 1.0) * mel_freq_delta # size(num_bins, 1) + right_mel = mel_low_freq + (bin + 2.0) * mel_freq_delta # size(num_bins, 1) + + if vtln_warp_factor != 1.0: + left_mel = vtln_warp_mel_freq(vtln_low, vtln_high, low_freq, high_freq, vtln_warp_factor, left_mel) + center_mel = vtln_warp_mel_freq(vtln_low, vtln_high, low_freq, high_freq, vtln_warp_factor, center_mel) + right_mel = vtln_warp_mel_freq(vtln_low, vtln_high, low_freq, high_freq, vtln_warp_factor, right_mel) + + center_freqs = inverse_mel_scale(center_mel) # size (num_bins) + # size(1, num_fft_bins) + mel = mel_scale(fft_bin_width * torch.arange(num_fft_bins)).unsqueeze(0) + + # size (num_bins, num_fft_bins) + up_slope = (mel - left_mel) / (center_mel - left_mel) + down_slope = (right_mel - mel) / (right_mel - center_mel) + + if vtln_warp_factor == 1.0: + # left_mel < center_mel < right_mel so we can min the two slopes and clamp negative values + bins = torch.max(torch.zeros(1), torch.min(up_slope, down_slope)) + else: + # warping can move the order of left_mel, center_mel, right_mel anywhere + bins = torch.zeros_like(up_slope) + up_idx = torch.gt(mel, left_mel) & torch.le(mel, center_mel) # left_mel < mel <= center_mel + down_idx = torch.gt(mel, center_mel) & torch.lt(mel, right_mel) # center_mel < mel < right_mel + bins[up_idx] = up_slope[up_idx] + bins[down_idx] = down_slope[down_idx] + + return bins, center_freqs + + +def fbank(waveform: Tensor, + blackman_coeff: float = 0.42, + channel: int = -1, + dither: float = 0.0, + energy_floor: float = 1.0, + frame_length: float = 25.0, + frame_shift: float = 10.0, + high_freq: float = 0.0, + htk_compat: bool = False, + low_freq: float = 20.0, + min_duration: float = 0.0, + num_mel_bins: int = 23, + preemphasis_coefficient: float = 0.97, + raw_energy: bool = True, + remove_dc_offset: bool = True, + round_to_power_of_two: bool = True, + sample_frequency: float = 16000.0, + snip_edges: bool = True, + subtract_mean: bool = False, + use_energy: bool = False, + use_log_fbank: bool = True, + use_power: bool = True, + vtln_high: float = -500.0, + vtln_low: float = 100.0, + vtln_warp: float = 1.0, + window_type: str = POVEY) -> Tensor: + r"""Create a fbank from a raw audio signal. This matches the input/output of Kaldi's + compute-fbank-feats. + + Args: + waveform (Tensor): Tensor of audio of size (c, n) where c is in the range [0,2) + blackman_coeff (float, optional): Constant coefficient for generalized Blackman window. (Default: ``0.42``) + channel (int, optional): Channel to extract (-1 -> expect mono, 0 -> left, 1 -> right) (Default: ``-1``) + dither (float, optional): Dithering constant (0.0 means no dither). If you turn this off, you should set + the energy_floor option, e.g. to 1.0 or 0.1 (Default: ``0.0``) + energy_floor (float, optional): Floor on energy (absolute, not relative) in Spectrogram computation. Caution: + this floor is applied to the zeroth component, representing the total signal energy. The floor on the + individual spectrogram elements is fixed at std::numeric_limits::epsilon(). (Default: ``1.0``) + frame_length (float, optional): Frame length in milliseconds (Default: ``25.0``) + frame_shift (float, optional): Frame shift in milliseconds (Default: ``10.0``) + high_freq (float, optional): High cutoff frequency for mel bins (if <= 0, offset from Nyquist) + (Default: ``0.0``) + htk_compat (bool, optional): If true, put energy last. Warning: not sufficient to get HTK compatible features + (need to change other parameters). (Default: ``False``) + low_freq (float, optional): Low cutoff frequency for mel bins (Default: ``20.0``) + min_duration (float, optional): Minimum duration of segments to process (in seconds). (Default: ``0.0``) + num_mel_bins (int, optional): Number of triangular mel-frequency bins (Default: ``23``) + preemphasis_coefficient (float, optional): Coefficient for use in signal preemphasis (Default: ``0.97``) + raw_energy (bool, optional): If True, compute energy before preemphasis and windowing (Default: ``True``) + remove_dc_offset (bool, optional): Subtract mean from waveform on each frame (Default: ``True``) + round_to_power_of_two (bool, optional): If True, round window size to power of two by zero-padding input + to FFT. (Default: ``True``) + sample_frequency (float, optional): Waveform data sample frequency (must match the waveform file, if + specified there) (Default: ``16000.0``) + snip_edges (bool, optional): If True, end effects will be handled by outputting only frames that completely fit + in the file, and the number of frames depends on the frame_length. If False, the number of frames + depends only on the frame_shift, and we reflect the data at the ends. (Default: ``True``) + subtract_mean (bool, optional): Subtract mean of each feature file [CMS]; not recommended to do + it this way. (Default: ``False``) + use_energy (bool, optional): Add an extra dimension with energy to the FBANK output. (Default: ``False``) + use_log_fbank (bool, optional):If true, produce log-filterbank, else produce linear. (Default: ``True``) + use_power (bool, optional): If true, use power, else use magnitude. (Default: ``True``) + vtln_high (float, optional): High inflection point in piecewise linear VTLN warping function (if + negative, offset from high-mel-freq (Default: ``-500.0``) + vtln_low (float, optional): Low inflection point in piecewise linear VTLN warping function (Default: ``100.0``) + vtln_warp (float, optional): Vtln warp factor (only applicable if vtln_map not specified) (Default: ``1.0``) + window_type (str, optional): Type of window ('hamming'|'hanning'|'povey'|'rectangular'|'blackman') + (Default: ``'povey'``) + + Returns: + Tensor: A fbank identical to what Kaldi would output. The shape is (m, ``num_mel_bins + use_energy``) + where m is calculated in _get_strided + """ + device, dtype = waveform.device, waveform.dtype + + waveform, window_shift, window_size, padded_window_size = _get_waveform_and_window_properties( + waveform, channel, sample_frequency, frame_shift, frame_length, round_to_power_of_two, preemphasis_coefficient) + + if len(waveform) < min_duration * sample_frequency: + # signal is too short + return torch.empty(0, device=device, dtype=dtype) + + # strided_input, size (m, padded_window_size) and signal_log_energy, size (m) + strided_input, signal_log_energy = _get_window( + waveform, padded_window_size, window_size, window_shift, window_type, blackman_coeff, + snip_edges, raw_energy, energy_floor, dither, remove_dc_offset, preemphasis_coefficient) + + # size (m, padded_window_size // 2 + 1) + spectrum = torch.fft.rfft(strided_input).abs() + if use_power: + spectrum = spectrum.pow(2.) + + # size (num_mel_bins, padded_window_size // 2) + mel_energies, _ = get_mel_banks(num_mel_bins, padded_window_size, sample_frequency, + low_freq, high_freq, vtln_low, vtln_high, vtln_warp) + mel_energies = mel_energies.to(device=device, dtype=dtype) + + # pad right column with zeros and add dimension, size (num_mel_bins, padded_window_size // 2 + 1) + mel_energies = torch.nn.functional.pad(mel_energies, (0, 1), mode='constant', value=0) + + # sum with mel fiterbanks over the power spectrum, size (m, num_mel_bins) + mel_energies = torch.mm(spectrum, mel_energies.T) + if use_log_fbank: + # avoid log of zero (which should be prevented anyway by dithering) + mel_energies = torch.max(mel_energies, _get_epsilon(device, dtype)).log() + + # if use_energy then add it as the last column for htk_compat == true else first column + if use_energy: + signal_log_energy = signal_log_energy.unsqueeze(1) # size (m, 1) + # returns size (m, num_mel_bins + 1) + if htk_compat: + mel_energies = torch.cat((mel_energies, signal_log_energy), dim=1) + else: + mel_energies = torch.cat((signal_log_energy, mel_energies), dim=1) + + mel_energies = _subtract_column_mean(mel_energies, subtract_mean) + return mel_energies + + +def _get_dct_matrix(num_ceps: int, num_mel_bins: int) -> Tensor: + # returns a dct matrix of size (num_mel_bins, num_ceps) + # size (num_mel_bins, num_mel_bins) + dct_matrix = torchaudio.functional.create_dct(num_mel_bins, num_mel_bins, 'ortho') + # kaldi expects the first cepstral to be weighted sum of factor sqrt(1/num_mel_bins) + # this would be the first column in the dct_matrix for torchaudio as it expects a + # right multiply (which would be the first column of the kaldi's dct_matrix as kaldi + # expects a left multiply e.g. dct_matrix * vector). + dct_matrix[:, 0] = math.sqrt(1 / float(num_mel_bins)) + dct_matrix = dct_matrix[:, :num_ceps] + return dct_matrix + + +def _get_lifter_coeffs(num_ceps: int, cepstral_lifter: float) -> Tensor: + # returns size (num_ceps) + # Compute liftering coefficients (scaling on cepstral coeffs) + # coeffs are numbered slightly differently from HTK: the zeroth index is C0, which is not affected. + i = torch.arange(num_ceps) + return 1.0 + 0.5 * cepstral_lifter * torch.sin(math.pi * i / cepstral_lifter) + + +def mfcc( + waveform: Tensor, + blackman_coeff: float = 0.42, + cepstral_lifter: float = 22.0, + channel: int = -1, + dither: float = 0.0, + energy_floor: float = 1.0, + frame_length: float = 25.0, + frame_shift: float = 10.0, + high_freq: float = 0.0, + htk_compat: bool = False, + low_freq: float = 20.0, + num_ceps: int = 13, + min_duration: float = 0.0, + num_mel_bins: int = 23, + preemphasis_coefficient: float = 0.97, + raw_energy: bool = True, + remove_dc_offset: bool = True, + round_to_power_of_two: bool = True, + sample_frequency: float = 16000.0, + snip_edges: bool = True, + subtract_mean: bool = False, + use_energy: bool = False, + vtln_high: float = -500.0, + vtln_low: float = 100.0, + vtln_warp: float = 1.0, + window_type: str = POVEY) -> Tensor: + r"""Create a mfcc from a raw audio signal. This matches the input/output of Kaldi's + compute-mfcc-feats. + + Args: + waveform (Tensor): Tensor of audio of size (c, n) where c is in the range [0,2) + blackman_coeff (float, optional): Constant coefficient for generalized Blackman window. (Default: ``0.42``) + cepstral_lifter (float, optional): Constant that controls scaling of MFCCs (Default: ``22.0``) + channel (int, optional): Channel to extract (-1 -> expect mono, 0 -> left, 1 -> right) (Default: ``-1``) + dither (float, optional): Dithering constant (0.0 means no dither). If you turn this off, you should set + the energy_floor option, e.g. to 1.0 or 0.1 (Default: ``0.0``) + energy_floor (float, optional): Floor on energy (absolute, not relative) in Spectrogram computation. Caution: + this floor is applied to the zeroth component, representing the total signal energy. The floor on the + individual spectrogram elements is fixed at std::numeric_limits::epsilon(). (Default: ``1.0``) + frame_length (float, optional): Frame length in milliseconds (Default: ``25.0``) + frame_shift (float, optional): Frame shift in milliseconds (Default: ``10.0``) + high_freq (float, optional): High cutoff frequency for mel bins (if <= 0, offset from Nyquist) + (Default: ``0.0``) + htk_compat (bool, optional): If true, put energy last. Warning: not sufficient to get HTK compatible + features (need to change other parameters). (Default: ``False``) + low_freq (float, optional): Low cutoff frequency for mel bins (Default: ``20.0``) + num_ceps (int, optional): Number of cepstra in MFCC computation (including C0) (Default: ``13``) + min_duration (float, optional): Minimum duration of segments to process (in seconds). (Default: ``0.0``) + num_mel_bins (int, optional): Number of triangular mel-frequency bins (Default: ``23``) + preemphasis_coefficient (float, optional): Coefficient for use in signal preemphasis (Default: ``0.97``) + raw_energy (bool, optional): If True, compute energy before preemphasis and windowing (Default: ``True``) + remove_dc_offset (bool, optional): Subtract mean from waveform on each frame (Default: ``True``) + round_to_power_of_two (bool, optional): If True, round window size to power of two by zero-padding input + to FFT. (Default: ``True``) + sample_frequency (float, optional): Waveform data sample frequency (must match the waveform file, if + specified there) (Default: ``16000.0``) + snip_edges (bool, optional): If True, end effects will be handled by outputting only frames that completely fit + in the file, and the number of frames depends on the frame_length. If False, the number of frames + depends only on the frame_shift, and we reflect the data at the ends. (Default: ``True``) + subtract_mean (bool, optional): Subtract mean of each feature file [CMS]; not recommended to do + it this way. (Default: ``False``) + use_energy (bool, optional): Add an extra dimension with energy to the FBANK output. (Default: ``False``) + vtln_high (float, optional): High inflection point in piecewise linear VTLN warping function (if + negative, offset from high-mel-freq (Default: ``-500.0``) + vtln_low (float, optional): Low inflection point in piecewise linear VTLN warping function (Default: ``100.0``) + vtln_warp (float, optional): Vtln warp factor (only applicable if vtln_map not specified) (Default: ``1.0``) + window_type (str, optional): Type of window ('hamming'|'hanning'|'povey'|'rectangular'|'blackman') + (Default: ``"povey"``) + + Returns: + Tensor: A mfcc identical to what Kaldi would output. The shape is (m, ``num_ceps``) + where m is calculated in _get_strided + """ + assert num_ceps <= num_mel_bins, 'num_ceps cannot be larger than num_mel_bins: %d vs %d' % (num_ceps, num_mel_bins) + + device, dtype = waveform.device, waveform.dtype + + # The mel_energies should not be squared (use_power=True), not have mean subtracted + # (subtract_mean=False), and use log (use_log_fbank=True). + # size (m, num_mel_bins + use_energy) + feature = fbank(waveform=waveform, blackman_coeff=blackman_coeff, channel=channel, + dither=dither, energy_floor=energy_floor, frame_length=frame_length, + frame_shift=frame_shift, high_freq=high_freq, htk_compat=htk_compat, + low_freq=low_freq, min_duration=min_duration, num_mel_bins=num_mel_bins, + preemphasis_coefficient=preemphasis_coefficient, raw_energy=raw_energy, + remove_dc_offset=remove_dc_offset, round_to_power_of_two=round_to_power_of_two, + sample_frequency=sample_frequency, snip_edges=snip_edges, subtract_mean=False, + use_energy=use_energy, use_log_fbank=True, use_power=True, + vtln_high=vtln_high, vtln_low=vtln_low, vtln_warp=vtln_warp, window_type=window_type) + + if use_energy: + # size (m) + signal_log_energy = feature[:, num_mel_bins if htk_compat else 0] + # offset is 0 if htk_compat==True else 1 + mel_offset = int(not htk_compat) + feature = feature[:, mel_offset:(num_mel_bins + mel_offset)] + + # size (num_mel_bins, num_ceps) + dct_matrix = _get_dct_matrix(num_ceps, num_mel_bins).to(dtype=dtype, device=device) + + # size (m, num_ceps) + feature = feature.matmul(dct_matrix) + + if cepstral_lifter != 0.0: + # size (1, num_ceps) + lifter_coeffs = _get_lifter_coeffs(num_ceps, cepstral_lifter).unsqueeze(0) + feature *= lifter_coeffs.to(device=device, dtype=dtype) + + # if use_energy then replace the last column for htk_compat == true else first column + if use_energy: + feature[:, 0] = signal_log_energy + + if htk_compat: + energy = feature[:, 0].unsqueeze(1) # size (m, 1) + feature = feature[:, 1:] # size (m, num_ceps - 1) + if not use_energy: + # scale on C0 (actually removing a scale we previously added that's + # part of one common definition of the cosine transform.) + energy *= math.sqrt(2) + + feature = torch.cat((feature, energy), dim=1) + + feature = _subtract_column_mean(feature, subtract_mean) + return feature diff --git a/torchaudio/csrc/CMakeLists.txt b/torchaudio/csrc/CMakeLists.txt new file mode 100644 index 00000000..2187abaf --- /dev/null +++ b/torchaudio/csrc/CMakeLists.txt @@ -0,0 +1,171 @@ +get_property(TORCHAUDIO_THIRD_PARTIES GLOBAL PROPERTY TORCHAUDIO_THIRD_PARTIES) + +################################################################################ +# libtorchaudio +################################################################################ +set( + LIBTORCHAUDIO_SOURCES + lfilter.cpp + overdrive.cpp + utils.cpp + ) + +if(BUILD_RNNT) + list( + APPEND + LIBTORCHAUDIO_SOURCES + rnnt/cpu/compute_alphas.cpp + rnnt/cpu/compute_betas.cpp + rnnt/cpu/compute.cpp + rnnt/compute_alphas.cpp + rnnt/compute_betas.cpp + rnnt/compute.cpp + rnnt/autograd.cpp + ) + if (USE_CUDA) + list( + APPEND + LIBTORCHAUDIO_SOURCES + rnnt/gpu/compute_alphas.cu + rnnt/gpu/compute_betas.cu + rnnt/gpu/compute.cu + ) + endif() +endif() + +if(BUILD_KALDI) + list(APPEND LIBTORCHAUDIO_SOURCES kaldi.cpp) +endif() + +if(BUILD_SOX) + list( + APPEND + LIBTORCHAUDIO_SOURCES + sox/io.cpp + sox/utils.cpp + sox/effects.cpp + sox/effects_chain.cpp + sox/types.cpp + ) +endif() + +add_library( + libtorchaudio + SHARED + ${LIBTORCHAUDIO_SOURCES} + ) +set_target_properties(libtorchaudio PROPERTIES PREFIX "") + +target_include_directories( + libtorchaudio + PRIVATE + ${PROJECT_SOURCE_DIR} + ) + +target_link_libraries( + libtorchaudio + torch + ${TORCHAUDIO_THIRD_PARTIES} + ) + +if (BUILD_SOX) + target_compile_definitions(libtorchaudio PUBLIC INCLUDE_SOX) +endif() + +if (BUILD_KALDI) + target_compile_definitions(libtorchaudio PUBLIC INCLUDE_KALDI) +endif() + +if(USE_CUDA) + target_compile_definitions(libtorchaudio PRIVATE USE_CUDA) + target_include_directories( + libtorchaudio + PRIVATE + ${CUDA_TOOLKIT_INCLUDE} + ) + target_link_libraries( + libtorchaudio + ${C10_CUDA_LIBRARY} + ${CUDA_CUDART_LIBRARY} + ) +endif() + +if (MSVC) + set_target_properties(libtorchaudio PROPERTIES SUFFIX ".pyd") +endif(MSVC) + +install( + TARGETS libtorchaudio + LIBRARY DESTINATION lib + RUNTIME DESTINATION lib # For Windows + ) + +if (APPLE) + set(TORCHAUDIO_LIBRARY libtorchaudio CACHE INTERNAL "") +else() + set(TORCHAUDIO_LIBRARY -Wl,--no-as-needed libtorchaudio -Wl,--as-needed CACHE INTERNAL "") +endif() + +################################################################################ +# _torchaudio.so +################################################################################ +if (BUILD_TORCHAUDIO_PYTHON_EXTENSION) + set( + EXTENSION_SOURCES + pybind/pybind.cpp + ) + if(BUILD_SOX) + list( + APPEND + EXTENSION_SOURCES + pybind/sox/effects.cpp + pybind/sox/effects_chain.cpp + pybind/sox/io.cpp + pybind/sox/utils.cpp + ) + endif() + add_library( + _torchaudio + SHARED + ${EXTENSION_SOURCES} + ) + + set_target_properties(_torchaudio PROPERTIES PREFIX "") + if (MSVC) + set_target_properties(_torchaudio 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(_torchaudio PROPERTIES LINK_FLAGS "-undefined dynamic_lookup") + endif() + + target_include_directories( + _torchaudio + PRIVATE + ${PROJECT_SOURCE_DIR} + ${Python_INCLUDE_DIR} + ) + + # See https://github.com/pytorch/pytorch/issues/38122 + find_library(TORCH_PYTHON_LIBRARY torch_python PATHS "${TORCH_INSTALL_PREFIX}/lib") + + if (WIN32) + find_package(Python3 ${PYTHON_VERSION} EXACT COMPONENTS Development) + set(ADDITIONAL_ITEMS Python3::Python) + endif() + + target_link_libraries( + _torchaudio + libtorchaudio + ${TORCH_PYTHON_LIBRARY} + ${ADDITIONAL_ITEMS} + ) + + install( + TARGETS _torchaudio + LIBRARY DESTINATION . + RUNTIME DESTINATION . # For Windows + ) +endif() diff --git a/torchaudio/csrc/kaldi.cpp b/torchaudio/csrc/kaldi.cpp new file mode 100644 index 00000000..6f2b36c2 --- /dev/null +++ b/torchaudio/csrc/kaldi.cpp @@ -0,0 +1,93 @@ +#include +#include "feat/pitch-functions.h" + +namespace torchaudio { +namespace kaldi { + +namespace { + +torch::Tensor denormalize(const torch::Tensor& t) { + auto ret = t; + auto pos = t > 0, neg = t < 0; + ret.index_put({pos}, t.index({pos}) * 32767); + ret.index_put({neg}, t.index({neg}) * 32768); + return ret; +} + +torch::Tensor compute_kaldi_pitch( + const torch::Tensor& wave, + const ::kaldi::PitchExtractionOptions& opts) { + ::kaldi::VectorBase<::kaldi::BaseFloat> input(wave); + ::kaldi::Matrix<::kaldi::BaseFloat> output; + ::kaldi::ComputeKaldiPitch(opts, input, &output); + return output.tensor_; +} + +} // namespace + +torch::Tensor ComputeKaldiPitch( + const torch::Tensor& wave, + double sample_frequency, + double frame_length, + double frame_shift, + double min_f0, + double max_f0, + double soft_min_f0, + double penalty_factor, + double lowpass_cutoff, + double resample_frequency, + double delta_pitch, + double nccf_ballast, + int64_t lowpass_filter_width, + int64_t upsample_filter_width, + int64_t max_frames_latency, + int64_t frames_per_chunk, + bool simulate_first_pass_online, + int64_t recompute_frame, + bool snip_edges) { + TORCH_CHECK(wave.ndimension() == 2, "Input tensor must be 2 dimentional."); + TORCH_CHECK(wave.device().is_cpu(), "Input tensor must be on CPU."); + TORCH_CHECK( + wave.dtype() == torch::kFloat32, "Input tensor must be float32 type."); + + ::kaldi::PitchExtractionOptions opts; + opts.samp_freq = static_cast<::kaldi::BaseFloat>(sample_frequency); + opts.frame_shift_ms = static_cast<::kaldi::BaseFloat>(frame_shift); + opts.frame_length_ms = static_cast<::kaldi::BaseFloat>(frame_length); + opts.min_f0 = static_cast<::kaldi::BaseFloat>(min_f0); + opts.max_f0 = static_cast<::kaldi::BaseFloat>(max_f0); + opts.soft_min_f0 = static_cast<::kaldi::BaseFloat>(soft_min_f0); + opts.penalty_factor = static_cast<::kaldi::BaseFloat>(penalty_factor); + opts.lowpass_cutoff = static_cast<::kaldi::BaseFloat>(lowpass_cutoff); + opts.resample_freq = static_cast<::kaldi::BaseFloat>(resample_frequency); + opts.delta_pitch = static_cast<::kaldi::BaseFloat>(delta_pitch); + opts.lowpass_filter_width = static_cast<::kaldi::int32>(lowpass_filter_width); + opts.upsample_filter_width = + static_cast<::kaldi::int32>(upsample_filter_width); + opts.max_frames_latency = static_cast<::kaldi::int32>(max_frames_latency); + opts.frames_per_chunk = static_cast<::kaldi::int32>(frames_per_chunk); + opts.simulate_first_pass_online = simulate_first_pass_online; + opts.recompute_frame = static_cast<::kaldi::int32>(recompute_frame); + opts.snip_edges = snip_edges; + + // Kaldi's float type expects value range of int16 expressed as float + torch::Tensor wave_ = denormalize(wave); + + auto batch_size = wave_.size(0); + std::vector results(batch_size); + at::parallel_for(0, batch_size, 1, [&](int64_t begin, int64_t end) { + for (auto i = begin; i < end; ++i) { + results[i] = compute_kaldi_pitch(wave_.index({i}), opts); + } + }); + return torch::stack(results, 0); +} + +TORCH_LIBRARY_FRAGMENT(torchaudio, m) { + m.def( + "torchaudio::kaldi_ComputeKaldiPitch", + &torchaudio::kaldi::ComputeKaldiPitch); +} + +} // namespace kaldi +} // namespace torchaudio diff --git a/torchaudio/csrc/lfilter.cpp b/torchaudio/csrc/lfilter.cpp new file mode 100644 index 00000000..eec34d1b --- /dev/null +++ b/torchaudio/csrc/lfilter.cpp @@ -0,0 +1,283 @@ +#include +#include + +namespace { + +template +void host_lfilter_core_loop( + const torch::Tensor& input_signal_windows, + const torch::Tensor& a_coeff_flipped, + torch::Tensor& padded_output_waveform) { + int64_t n_batch = input_signal_windows.size(0); + int64_t n_channel = input_signal_windows.size(1); + int64_t n_samples_input = input_signal_windows.size(2); + int64_t n_samples_output = padded_output_waveform.size(2); + int64_t n_order = a_coeff_flipped.size(1); + scalar_t* output_data = padded_output_waveform.data_ptr(); + const scalar_t* input_data = input_signal_windows.data_ptr(); + const scalar_t* a_coeff_flipped_data = a_coeff_flipped.data_ptr(); + + at::parallel_for(0, n_channel * n_batch, 1, [&](int64_t begin, int64_t end) { + for (auto i = begin; i < end; i++) { + int64_t offset_input = i * n_samples_input; + int64_t offset_output = i * n_samples_output; + int64_t i_channel = i % n_channel; + for (int64_t i_sample = 0; i_sample < n_samples_input; i_sample++) { + scalar_t a0 = input_data[offset_input + i_sample]; + for (int64_t i_coeff = 0; i_coeff < n_order; i_coeff++) { + a0 -= output_data[offset_output + i_sample + i_coeff] * + a_coeff_flipped_data[i_coeff + i_channel * n_order]; + } + output_data[offset_output + i_sample + n_order - 1] = a0; + } + } + }); +} + +void cpu_lfilter_core_loop( + const torch::Tensor& input_signal_windows, + const torch::Tensor& a_coeff_flipped, + torch::Tensor& padded_output_waveform) { + TORCH_CHECK( + input_signal_windows.device().is_cpu() && + a_coeff_flipped.device().is_cpu() && + padded_output_waveform.device().is_cpu()); + + TORCH_CHECK( + input_signal_windows.is_contiguous() && a_coeff_flipped.is_contiguous() && + padded_output_waveform.is_contiguous()); + + TORCH_CHECK( + (input_signal_windows.dtype() == torch::kFloat32 || + input_signal_windows.dtype() == torch::kFloat64) && + (a_coeff_flipped.dtype() == torch::kFloat32 || + a_coeff_flipped.dtype() == torch::kFloat64) && + (padded_output_waveform.dtype() == torch::kFloat32 || + padded_output_waveform.dtype() == torch::kFloat64)); + + TORCH_CHECK(input_signal_windows.size(0) == padded_output_waveform.size(0)); + TORCH_CHECK(input_signal_windows.size(1) == padded_output_waveform.size(1)); + + TORCH_CHECK( + input_signal_windows.size(2) + a_coeff_flipped.size(1) - 1 == + padded_output_waveform.size(2)); + + AT_DISPATCH_FLOATING_TYPES( + input_signal_windows.scalar_type(), "lfilter_core_loop", [&] { + host_lfilter_core_loop( + input_signal_windows, a_coeff_flipped, padded_output_waveform); + }); +} + +void lfilter_core_generic_loop( + const torch::Tensor& input_signal_windows, + const torch::Tensor& a_coeff_flipped, + torch::Tensor& padded_output_waveform) { + int64_t n_samples_input = input_signal_windows.size(2); + int64_t n_order = a_coeff_flipped.size(1); + auto coeff = a_coeff_flipped.unsqueeze(2); + for (int64_t i_sample = 0; i_sample < n_samples_input; i_sample++) { + auto windowed_output_signal = + padded_output_waveform + .index( + {torch::indexing::Slice(), + torch::indexing::Slice(), + torch::indexing::Slice(i_sample, i_sample + n_order)}) + .transpose(0, 1); + auto o0 = + input_signal_windows.index( + {torch::indexing::Slice(), torch::indexing::Slice(), i_sample}) - + at::matmul(windowed_output_signal, coeff).squeeze(2).transpose(0, 1); + padded_output_waveform.index_put_( + {torch::indexing::Slice(), + torch::indexing::Slice(), + i_sample + n_order - 1}, + o0); + } +} + +class DifferentiableIIR : public torch::autograd::Function { + public: + static torch::Tensor forward( + torch::autograd::AutogradContext* ctx, + const torch::Tensor& waveform, + const torch::Tensor& a_coeffs_normalized) { + auto device = waveform.device(); + auto dtype = waveform.dtype(); + int64_t n_batch = waveform.size(0); + int64_t n_channel = waveform.size(1); + int64_t n_sample = waveform.size(2); + int64_t n_order = a_coeffs_normalized.size(1); + int64_t n_sample_padded = n_sample + n_order - 1; + + auto a_coeff_flipped = a_coeffs_normalized.flip(1).contiguous(); + + auto options = torch::TensorOptions().dtype(dtype).device(device); + auto padded_output_waveform = + torch::zeros({n_batch, n_channel, n_sample_padded}, options); + + if (device.is_cpu()) { + cpu_lfilter_core_loop(waveform, a_coeff_flipped, padded_output_waveform); + } else { + lfilter_core_generic_loop( + waveform, a_coeff_flipped, padded_output_waveform); + } + + auto output = padded_output_waveform.index( + {torch::indexing::Slice(), + torch::indexing::Slice(), + torch::indexing::Slice(n_order - 1, torch::indexing::None)}); + + ctx->save_for_backward({waveform, a_coeffs_normalized, output}); + return output; + } + + static torch::autograd::tensor_list backward( + torch::autograd::AutogradContext* ctx, + torch::autograd::tensor_list grad_outputs) { + auto saved = ctx->get_saved_variables(); + auto x = saved[0]; + auto a_coeffs_normalized = saved[1]; + auto y = saved[2]; + + int64_t n_batch = x.size(0); + int64_t n_channel = x.size(1); + int64_t n_order = a_coeffs_normalized.size(1); + + auto dx = torch::Tensor(); + auto da = torch::Tensor(); + auto dy = grad_outputs[0]; + + namespace F = torch::nn::functional; + + if (a_coeffs_normalized.requires_grad()) { + auto dyda = F::pad( + DifferentiableIIR::apply(-y, a_coeffs_normalized), + F::PadFuncOptions({n_order - 1, 0})); + + da = F::conv1d( + dyda.view({1, n_batch * n_channel, -1}), + dy.view({n_batch * n_channel, 1, -1}), + F::Conv1dFuncOptions().groups(n_batch * n_channel)) + .view({n_batch, n_channel, -1}) + .sum(0) + .flip(1); + } + + if (x.requires_grad()) { + dx = DifferentiableIIR::apply(dy.flip(2), a_coeffs_normalized).flip(2); + } + + return {dx, da}; + } +}; + +class DifferentiableFIR : public torch::autograd::Function { + public: + static torch::Tensor forward( + torch::autograd::AutogradContext* ctx, + const torch::Tensor& waveform, + const torch::Tensor& b_coeffs) { + int64_t n_order = b_coeffs.size(1); + int64_t n_channel = b_coeffs.size(0); + + namespace F = torch::nn::functional; + auto b_coeff_flipped = b_coeffs.flip(1).contiguous(); + auto padded_waveform = + F::pad(waveform, F::PadFuncOptions({n_order - 1, 0})); + + auto output = F::conv1d( + padded_waveform, + b_coeff_flipped.unsqueeze(1), + F::Conv1dFuncOptions().groups(n_channel)); + + ctx->save_for_backward({waveform, b_coeffs, output}); + return output; + } + + static torch::autograd::tensor_list backward( + torch::autograd::AutogradContext* ctx, + torch::autograd::tensor_list grad_outputs) { + auto saved = ctx->get_saved_variables(); + auto x = saved[0]; + auto b_coeffs = saved[1]; + auto y = saved[2]; + + int64_t n_batch = x.size(0); + int64_t n_channel = x.size(1); + int64_t n_order = b_coeffs.size(1); + + auto dx = torch::Tensor(); + auto db = torch::Tensor(); + auto dy = grad_outputs[0]; + + namespace F = torch::nn::functional; + + if (b_coeffs.requires_grad()) { + db = F::conv1d( + F::pad(x, F::PadFuncOptions({n_order - 1, 0})) + .view({1, n_batch * n_channel, -1}), + dy.view({n_batch * n_channel, 1, -1}), + F::Conv1dFuncOptions().groups(n_batch * n_channel)) + .view({n_batch, n_channel, -1}) + .sum(0) + .flip(1); + } + + if (x.requires_grad()) { + dx = F::conv1d( + F::pad(dy, F::PadFuncOptions({0, n_order - 1})), + b_coeffs.unsqueeze(1), + F::Conv1dFuncOptions().groups(n_channel)); + } + + return {dx, db}; + } +}; + +torch::Tensor lfilter_core( + const torch::Tensor& waveform, + const torch::Tensor& a_coeffs, + const torch::Tensor& b_coeffs) { + TORCH_CHECK(waveform.device() == a_coeffs.device()); + TORCH_CHECK(b_coeffs.device() == a_coeffs.device()); + TORCH_CHECK(a_coeffs.sizes() == b_coeffs.sizes()); + + TORCH_INTERNAL_ASSERT(waveform.sizes().size() == 3); + TORCH_INTERNAL_ASSERT(a_coeffs.sizes().size() == 2); + TORCH_INTERNAL_ASSERT(a_coeffs.size(0) == waveform.size(1)); + + int64_t n_order = b_coeffs.size(1); + + TORCH_INTERNAL_ASSERT(n_order > 0); + + auto filtered_waveform = DifferentiableFIR::apply( + waveform, + b_coeffs / + a_coeffs.index( + {torch::indexing::Slice(), torch::indexing::Slice(0, 1)})); + + auto output = DifferentiableIIR::apply( + filtered_waveform, + a_coeffs / + a_coeffs.index( + {torch::indexing::Slice(), torch::indexing::Slice(0, 1)})); + return output; +} + +} // namespace + +// Note: We want to avoid using "catch-all" kernel. +// The following registration should be replaced with CPU specific registration. +TORCH_LIBRARY_FRAGMENT(torchaudio, m) { + m.def("torchaudio::_lfilter_core_loop", &cpu_lfilter_core_loop); +} + +TORCH_LIBRARY(torchaudio, m) { + m.def( + "torchaudio::_lfilter(Tensor waveform, Tensor a_coeffs, Tensor b_coeffs) -> Tensor"); +} + +TORCH_LIBRARY_IMPL(torchaudio, CompositeImplicitAutograd, m) { + m.impl("torchaudio::_lfilter", lfilter_core); +} diff --git a/torchaudio/csrc/overdrive.cpp b/torchaudio/csrc/overdrive.cpp new file mode 100644 index 00000000..4954271e --- /dev/null +++ b/torchaudio/csrc/overdrive.cpp @@ -0,0 +1,52 @@ +#include +#include + +namespace { + +template +void overdrive_cpu_kernel( + at::TensorAccessor waveform_accessor, + at::TensorAccessor temp_accessor, + at::TensorAccessor last_in_accessor, + at::TensorAccessor last_out_accessor, + at::TensorAccessor output_waveform_accessor) { + int64_t n_frames = waveform_accessor.size(1); + int64_t n_channels = waveform_accessor.size(0); + + at::parallel_for(0, n_channels, 1, [&](int64_t begin, int64_t end) { + for (int64_t i_channel = begin; i_channel < end; ++i_channel) { + for (int64_t i_frame = 0; i_frame < n_frames; ++i_frame) { + last_out_accessor[i_channel] = temp_accessor[i_channel][i_frame] - + last_in_accessor[i_channel] + 0.995 * last_out_accessor[i_channel]; + last_in_accessor[i_channel] = temp_accessor[i_channel][i_frame]; + output_waveform_accessor[i_channel][i_frame] = + waveform_accessor[i_channel][i_frame] * 0.5 + + last_out_accessor[i_channel] * 0.75; + } + } + }); +} + +void overdrive_core_loop_cpu( + at::Tensor& waveform, + at::Tensor& temp, + at::Tensor& last_in, + at::Tensor& last_out, + at::Tensor& output_waveform) { + AT_DISPATCH_FLOATING_TYPES(waveform.scalar_type(), "overdrive_cpu", ([&] { + overdrive_cpu_kernel( + waveform.accessor(), + temp.accessor(), + last_in.accessor(), + last_out.accessor(), + output_waveform.accessor()); + })); +} + +} // namespace + +// Note: We want to avoid using "catch-all" kernel. +// The following registration should be replaced with CPU specific registration. +TORCH_LIBRARY_FRAGMENT(torchaudio, m) { + m.def("torchaudio::_overdrive_core_loop", &overdrive_core_loop_cpu); +} diff --git a/torchaudio/csrc/pybind/pybind.cpp b/torchaudio/csrc/pybind/pybind.cpp new file mode 100644 index 00000000..583ee415 --- /dev/null +++ b/torchaudio/csrc/pybind/pybind.cpp @@ -0,0 +1,27 @@ +#include + +#ifdef INCLUDE_SOX +#include +#include +#endif + +PYBIND11_MODULE(_torchaudio, m) { +#ifdef INCLUDE_SOX + m.def( + "get_info_fileobj", + &torchaudio::sox_io::get_info_fileobj, + "Get metadata of audio in file object."); + m.def( + "load_audio_fileobj", + &torchaudio::sox_io::load_audio_fileobj, + "Load audio from file object."); + m.def( + "save_audio_fileobj", + &torchaudio::sox_io::save_audio_fileobj, + "Save audio to file obj."); + m.def( + "apply_effects_fileobj", + &torchaudio::sox_effects::apply_effects_fileobj, + "Decode audio data from file-like obj and apply effects."); +#endif +} diff --git a/torchaudio/csrc/pybind/sox/effects.cpp b/torchaudio/csrc/pybind/sox/effects.cpp new file mode 100644 index 00000000..43c3b08d --- /dev/null +++ b/torchaudio/csrc/pybind/sox/effects.cpp @@ -0,0 +1,117 @@ +#include +#include +#include + +using namespace torchaudio::sox_utils; + +namespace torchaudio::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, + c10::optional normalize, + c10::optional channels_first, + c10::optional format) -> std::tuple { + // 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 `torchaudio.utils.sox_utils.set_buffer_size`. + const auto capacity = [&]() { + // NOTE: + // Use the abstraction provided by `libtorchaudio` to access the global + // config defined by libsox. Directly using `sox_get_globals` function will + // end up retrieving the static variable defined in `_torchaudio`, 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 + validate_input_memfile(sf); + + // 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); + torchaudio::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::make_tuple( + tensor, static_cast(chain.getOutputSampleRate())); +} + +} // namespace torchaudio::sox_effects diff --git a/torchaudio/csrc/pybind/sox/effects.h b/torchaudio/csrc/pybind/sox/effects.h new file mode 100644 index 00000000..5406ba24 --- /dev/null +++ b/torchaudio/csrc/pybind/sox/effects.h @@ -0,0 +1,17 @@ +#ifndef TORCHAUDIO_PYBIND_SOX_EFFECTS_H +#define TORCHAUDIO_PYBIND_SOX_EFFECTS_H + +#include + +namespace torchaudio::sox_effects { + +auto apply_effects_fileobj( + py::object fileobj, + const std::vector>& effects, + c10::optional normalize, + c10::optional channels_first, + c10::optional format) -> std::tuple; + +} // namespace torchaudio::sox_effects + +#endif diff --git a/torchaudio/csrc/pybind/sox/effects_chain.cpp b/torchaudio/csrc/pybind/sox/effects_chain.cpp new file mode 100644 index 00000000..12f8d31d --- /dev/null +++ b/torchaudio/csrc/pybind/sox/effects_chain.cpp @@ -0,0 +1,227 @@ +#include +#include +#include + +using namespace torchaudio::sox_utils; + +namespace torchaudio::sox_effects_chain { + +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 + + // 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 torchaudio::sox_effects_chain diff --git a/torchaudio/csrc/pybind/sox/effects_chain.h b/torchaudio/csrc/pybind/sox/effects_chain.h new file mode 100644 index 00000000..7e3c0267 --- /dev/null +++ b/torchaudio/csrc/pybind/sox/effects_chain.h @@ -0,0 +1,28 @@ +#ifndef TORCHAUDIO_PYBIND_SOX_EFFECTS_CHAIN_H +#define TORCHAUDIO_PYBIND_SOX_EFFECTS_CHAIN_H + +#include +#include + +namespace torchaudio::sox_effects_chain { + +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 torchaudio::sox_effects_chain + +#endif diff --git a/torchaudio/csrc/pybind/sox/io.cpp b/torchaudio/csrc/pybind/sox/io.cpp new file mode 100644 index 00000000..2935575b --- /dev/null +++ b/torchaudio/csrc/pybind/sox/io.cpp @@ -0,0 +1,190 @@ +#include +#include +#include +#include +#include +#include + +#include + +using namespace torchaudio::sox_utils; + +namespace torchaudio::sox_io { + +auto get_info_fileobj(py::object fileobj, c10::optional format) + -> std::tuple { + // Prepare in-memory file object + // When libsox opens a file, it also reads the header. + // When opening a file there are two functions that might touch FILE* (and the + // underlying buffer). + // * `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 + // + // `auto_detect_format` function only requires 256 bytes, but format-dependent + // `startread` handler might require more data. In case of vorbis, the size of + // header is unbounded, but typically 4kB maximum. + // + // "The header size is unbounded, although for streaming a rule-of-thumb of + // 4kB or less is recommended (and Xiph.Org's Vorbis encoder follows this + // suggestion)." + // + // See: + // https://xiph.org/vorbis/doc/Vorbis_I_spec.html + const auto capacity = [&]() { + // NOTE: + // Use the abstraction provided by `libtorchaudio` to access the global + // config defined by libsox. Directly using `sox_get_globals` function will + // end up retrieving the static variable defined in `_torchaudio`, which is + // not correct. + 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)); +} + +auto load_audio_fileobj( + py::object fileobj, + c10::optional frame_offset, + c10::optional num_frames, + c10::optional normalize, + c10::optional channels_first, + c10::optional format) -> std::tuple { + auto effects = get_effects(frame_offset, num_frames); + return torchaudio::sox_effects::apply_effects_fileobj( + std::move(fileobj), + effects, + normalize, + channels_first, + std::move(format)); +} + +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, + torch::Tensor tensor, + int64_t sample_rate, + bool channels_first, + c10::optional compression, + c10::optional format, + c10::optional encoding, + c10::optional bits_per_sample) { + validate_input_tensor(tensor); + + 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.size(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.size(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.size(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."); + } + + torchaudio::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 torchaudio::sox_io diff --git a/torchaudio/csrc/pybind/sox/io.h b/torchaudio/csrc/pybind/sox/io.h new file mode 100644 index 00000000..4059bdc3 --- /dev/null +++ b/torchaudio/csrc/pybind/sox/io.h @@ -0,0 +1,31 @@ +#ifndef TORCHAUDIO_PYBIND_SOX_IO_H +#define TORCHAUDIO_PYBIND_SOX_IO_H + +#include + +namespace torchaudio::sox_io { + +auto get_info_fileobj(py::object fileobj, c10::optional format) + -> std::tuple; + +auto load_audio_fileobj( + py::object fileobj, + c10::optional frame_offset, + c10::optional num_frames, + c10::optional normalize, + c10::optional channels_first, + c10::optional format) -> std::tuple; + +void save_audio_fileobj( + py::object fileobj, + torch::Tensor tensor, + int64_t sample_rate, + bool channels_first, + c10::optional compression, + c10::optional format, + c10::optional encoding, + c10::optional bits_per_sample); + +} // namespace torchaudio::sox_io + +#endif diff --git a/torchaudio/csrc/pybind/sox/utils.cpp b/torchaudio/csrc/pybind/sox/utils.cpp new file mode 100644 index 00000000..39669b02 --- /dev/null +++ b/torchaudio/csrc/pybind/sox/utils.cpp @@ -0,0 +1,31 @@ +#include + +namespace torchaudio::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; +} + +} // namespace torchaudio::sox_utils diff --git a/torchaudio/csrc/pybind/sox/utils.h b/torchaudio/csrc/pybind/sox/utils.h new file mode 100644 index 00000000..f13944f5 --- /dev/null +++ b/torchaudio/csrc/pybind/sox/utils.h @@ -0,0 +1,12 @@ +#ifndef TORCHAUDIO_PYBIND_SOX_UTILS_H +#define TORCHAUDIO_PYBIND_SOX_UTILS_H + +#include + +namespace torchaudio::sox_utils { + +auto read_fileobj(py::object* fileobj, uint64_t size, char* buffer) -> uint64_t; + +} // namespace torchaudio::sox_utils + +#endif diff --git a/torchaudio/csrc/rnnt/autograd.cpp b/torchaudio/csrc/rnnt/autograd.cpp new file mode 100644 index 00000000..4fa6ba79 --- /dev/null +++ b/torchaudio/csrc/rnnt/autograd.cpp @@ -0,0 +1,56 @@ +#include +#include + +namespace torchaudio { +namespace rnnt { + +class RNNTLossFunction : public torch::autograd::Function { + public: + static torch::autograd::tensor_list forward( + torch::autograd::AutogradContext* ctx, + torch::Tensor& logits, + const torch::Tensor& targets, + const torch::Tensor& logit_lengths, + const torch::Tensor& target_lengths, + int64_t blank, + double clamp) { + torch::Tensor undef; + auto result = + rnnt_loss(logits, targets, logit_lengths, target_lengths, blank, clamp); + auto costs = std::get<0>(result); + auto grads = std::get<1>(result).value_or(undef); + ctx->save_for_backward({grads}); + return {costs, grads}; + } + + static torch::autograd::tensor_list backward( + torch::autograd::AutogradContext* ctx, + torch::autograd::tensor_list grad_outputs) { + auto saved = ctx->get_saved_variables(); + auto grad = saved[0]; + auto grad_out = grad_outputs[0].view({-1, 1, 1, 1}); + auto result = grad * grad_out; + torch::Tensor undef; + return {result, undef, undef, undef, undef, undef, undef, undef}; + } +}; + +std::tuple> rnnt_loss_autograd( + torch::Tensor& logits, + const torch::Tensor& targets, + const torch::Tensor& logit_lengths, + const torch::Tensor& target_lengths, + int64_t blank, + double clamp) { + at::AutoDispatchBelowADInplaceOrView guard; + auto results = RNNTLossFunction::apply( + logits, targets, logit_lengths, target_lengths, blank, clamp); + return std::make_tuple(results[0], results[1]); +} + +TORCH_LIBRARY_IMPL(torchaudio, Autograd, m) { + m.impl("rnnt_loss", rnnt_loss_autograd); +} + +} // namespace rnnt +} // namespace torchaudio diff --git a/torchaudio/csrc/rnnt/compute.cpp b/torchaudio/csrc/rnnt/compute.cpp new file mode 100644 index 00000000..9c3bf84a --- /dev/null +++ b/torchaudio/csrc/rnnt/compute.cpp @@ -0,0 +1,25 @@ +#include +#include + +std::tuple> rnnt_loss( + torch::Tensor& logits, + const torch::Tensor& targets, + const torch::Tensor& logit_lengths, + const torch::Tensor& target_lengths, + int64_t blank, + double clamp) { + static auto op = torch::Dispatcher::singleton() + .findSchemaOrThrow("torchaudio::rnnt_loss", "") + .typed(); + return op.call(logits, targets, logit_lengths, target_lengths, blank, clamp); +} + +TORCH_LIBRARY_FRAGMENT(torchaudio, m) { + m.def( + "rnnt_loss(Tensor logits," + "Tensor targets," + "Tensor logit_lengths," + "Tensor target_lengths," + "int blank," + "float clamp) -> (Tensor, Tensor?)"); +} diff --git a/torchaudio/csrc/rnnt/compute.h b/torchaudio/csrc/rnnt/compute.h new file mode 100644 index 00000000..eea16a5f --- /dev/null +++ b/torchaudio/csrc/rnnt/compute.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +std::tuple> rnnt_loss( + torch::Tensor& logits, + const torch::Tensor& targets, + const torch::Tensor& logit_lengths, + const torch::Tensor& target_lengths, + int64_t blank, + double clamp); diff --git a/torchaudio/csrc/rnnt/compute_alphas.cpp b/torchaudio/csrc/rnnt/compute_alphas.cpp new file mode 100644 index 00000000..adbcc1c8 --- /dev/null +++ b/torchaudio/csrc/rnnt/compute_alphas.cpp @@ -0,0 +1,11 @@ +#include + +TORCH_LIBRARY_FRAGMENT(torchaudio, m) { + m.def( + "rnnt_loss_alphas(Tensor logits," + "Tensor targets," + "Tensor logit_lengths," + "Tensor target_lengths," + "int blank," + "float clamp) -> Tensor"); +} diff --git a/torchaudio/csrc/rnnt/compute_betas.cpp b/torchaudio/csrc/rnnt/compute_betas.cpp new file mode 100644 index 00000000..77288381 --- /dev/null +++ b/torchaudio/csrc/rnnt/compute_betas.cpp @@ -0,0 +1,11 @@ +#include + +TORCH_LIBRARY_FRAGMENT(torchaudio, m) { + m.def( + "rnnt_loss_betas(Tensor logits," + "Tensor targets," + "Tensor logit_lengths," + "Tensor target_lengths," + "int blank," + "float clamp) -> Tensor"); +} diff --git a/torchaudio/csrc/rnnt/cpu/compute.cpp b/torchaudio/csrc/rnnt/cpu/compute.cpp new file mode 100644 index 00000000..088f68be --- /dev/null +++ b/torchaudio/csrc/rnnt/cpu/compute.cpp @@ -0,0 +1,148 @@ +#include +#include + +namespace torchaudio { +namespace rnnt { +namespace cpu { + +// Entry point into RNNT Loss +std::tuple> compute( + torch::Tensor& logits, + const torch::Tensor& targets, + const torch::Tensor& logit_lengths, + const torch::Tensor& target_lengths, + int64_t blank, + double clamp) { + TORCH_CHECK( + logits.device().type() == targets.device().type(), + "logits and targets must be on the same device"); + TORCH_CHECK( + logits.device().type() == logit_lengths.device().type(), + "logits and logit_lengths must be on the same device"); + TORCH_CHECK( + logits.device().type() == target_lengths.device().type(), + "logits and target_lengths must be on the same device"); + + TORCH_CHECK( + logits.dtype() == torch::kFloat32 || logits.dtype() == torch::kFloat16, + "logits must be float32 or float16 (half) type"); + TORCH_CHECK(targets.dtype() == torch::kInt32, "targets must be int32 type"); + TORCH_CHECK( + logit_lengths.dtype() == torch::kInt32, + "logit_lengths must be int32 type"); + TORCH_CHECK( + target_lengths.dtype() == torch::kInt32, + "target_lengths must be int32 type"); + + TORCH_CHECK(logits.is_contiguous(), "logits must be contiguous"); + TORCH_CHECK(targets.is_contiguous(), "targets must be contiguous"); + TORCH_CHECK( + logit_lengths.is_contiguous(), "logit_lengths must be contiguous"); + TORCH_CHECK( + target_lengths.is_contiguous(), "target_lengths must be contiguous"); + + TORCH_CHECK( + logits.dim() == 4, "logits must be 4-D (batch, time, target, class)"); + TORCH_CHECK( + targets.dim() == 2, "targets must be 2-D (batch, max target length)"); + TORCH_CHECK(logit_lengths.dim() == 1, "logit_lengths must be 1-D"); + TORCH_CHECK(target_lengths.dim() == 1, "target_lengths must be 1-D"); + + TORCH_CHECK( + logit_lengths.size(0) == logits.size(0), + "batch dimension mismatch between logits and logit_lengths"); + TORCH_CHECK( + target_lengths.size(0) == logits.size(0), + "batch dimension mismatch between logits and target_lengths"); + TORCH_CHECK( + targets.size(0) == logits.size(0), + "batch dimension mismatch between logits and targets"); + + TORCH_CHECK( + blank >= 0 && blank < logits.size(-1), + "blank must be within [0, logits.shape[-1])"); + + TORCH_CHECK( + logits.size(1) == at::max(logit_lengths).item().toInt(), + "input length mismatch"); + TORCH_CHECK( + logits.size(2) == at::max(target_lengths).item().toInt() + 1, + "output length mismatch"); + TORCH_CHECK( + targets.size(1) == at::max(target_lengths).item().toInt(), + "target length mismatch"); + + Options options; + options.batchSize_ = logit_lengths.size(0); + options.nHypos_ = target_lengths.size(0) / logit_lengths.size(0); + options.maxSrcLen_ = logits.size(1); + options.maxTgtLen_ = logits.size(2); + options.numTargets_ = logits.size(3); + options.blank_ = blank; + options.clamp_ = clamp; + + CHECK_EQ(logits.device().type(), torch::DeviceType::CPU); + options.device_ = CPU; + + torch::Tensor costs = torch::empty( + options.batchSize_ * options.nHypos_, + torch::TensorOptions().device(logits.device()).dtype(logits.dtype())); + c10::optional gradients = torch::zeros_like(logits); + + torch::Tensor int_workspace = torch::empty( + IntWorkspace::ComputeSizeFromOptions(options), + torch::TensorOptions() + .device(logits.device()) + .dtype(torch::ScalarType::Int)); + + torch::Tensor float_workspace = torch::empty( + DtypeWorkspace::ComputeSizeFromOptions(options), + torch::TensorOptions() + .device(logits.device()) + .dtype(torch::ScalarType::Float)); + + Workspace workspace( + /*options=*/options, + /*dtype_data=*/float_workspace.data_ptr(), + /*dtype_size=*/float_workspace.numel(), + /*int_data=*/int_workspace.data_ptr(), + /*int_size=*/int_workspace.numel()); + + switch (logits.scalar_type()) { + case torch::ScalarType::Float: { + Compute( + /*workspace=*/workspace, + /*logits=*/logits.data_ptr(), + /*targets=*/targets.data_ptr(), + /*logit_lengths=*/logit_lengths.data_ptr(), + /*target_lengths=*/target_lengths.data_ptr(), + /*costs=*/costs.data_ptr(), + /*gradients=*/gradients->data_ptr()); + break; + } + case torch::ScalarType::Half: { + Compute( + /*workspace=*/workspace, + /*logits=*/logits.data_ptr(), + /*targets=*/targets.data_ptr(), + /*logit_lengths=*/logit_lengths.data_ptr(), + /*target_lengths=*/target_lengths.data_ptr(), + /*costs=*/costs.data_ptr(), + /*gradients=*/gradients->data_ptr()); + break; + } + default: { + break; + } + }; + + return std::make_tuple(costs, gradients); +} + +TORCH_LIBRARY_IMPL(torchaudio, CPU, m) { + m.impl("rnnt_loss", &compute); +} + +} // namespace cpu +} // namespace rnnt +} // namespace torchaudio diff --git a/torchaudio/csrc/rnnt/cpu/compute_alphas.cpp b/torchaudio/csrc/rnnt/cpu/compute_alphas.cpp new file mode 100644 index 00000000..6a1fb0f8 --- /dev/null +++ b/torchaudio/csrc/rnnt/cpu/compute_alphas.cpp @@ -0,0 +1,70 @@ +#include +#include + +namespace torchaudio { +namespace rnnt { +namespace cpu { + +torch::Tensor compute_alphas( + const torch::Tensor& logits, + const torch::Tensor& targets, + const torch::Tensor& logit_lengths, + const torch::Tensor& target_lengths, + int64_t blank, + double clamp) { + Options options; + options.batchSize_ = logit_lengths.size(0); + options.nHypos_ = target_lengths.size(0) / logit_lengths.size(0); + options.maxSrcLen_ = logits.size(1); + options.maxTgtLen_ = logits.size(2); + options.numTargets_ = logits.size(3); + options.blank_ = blank; + options.clamp_ = clamp; + + CHECK_EQ(logits.device().type(), torch::DeviceType::CPU); + options.device_ = CPU; + + torch::Tensor alphas = torch::zeros( + {options.batchSize_ * options.nHypos_, + options.maxSrcLen_, + options.maxTgtLen_}, + torch::TensorOptions().device(logits.device()).dtype(logits.dtype())); + + torch::Tensor int_workspace = torch::empty( + IntWorkspace::ComputeSizeFromOptions(options), + torch::TensorOptions() + .device(logits.device()) + .dtype(torch::ScalarType::Int)); + + torch::Tensor float_workspace = torch::empty( + DtypeWorkspace::ComputeSizeFromOptions(options), + torch::TensorOptions() + .device(logits.device()) + .dtype(torch::ScalarType::Float)); + + Workspace workspace( + /*options=*/options, + /*dtype_data=*/float_workspace.data_ptr(), + /*dtype_size=*/float_workspace.numel(), + /*int_data=*/int_workspace.data_ptr(), + /*int_size=*/int_workspace.numel()); + + // Only support float, this is mainly to enable easy + // unit-testing + ComputeAlphas( + /*workspace=*/workspace, + /*logits=*/logits.data_ptr(), + /*targets=*/targets.data_ptr(), + /*logit_lengths=*/logit_lengths.data_ptr(), + /*target_lengths=*/target_lengths.data_ptr(), + /*alphas=*/alphas.data_ptr()); + return alphas; +} + +TORCH_LIBRARY_IMPL(torchaudio, CPU, m) { + m.impl("rnnt_loss_alphas", &compute_alphas); +} + +} // namespace cpu +} // namespace rnnt +} // namespace torchaudio diff --git a/torchaudio/csrc/rnnt/cpu/compute_betas.cpp b/torchaudio/csrc/rnnt/cpu/compute_betas.cpp new file mode 100644 index 00000000..51e738d8 --- /dev/null +++ b/torchaudio/csrc/rnnt/cpu/compute_betas.cpp @@ -0,0 +1,75 @@ +#include +#include + +namespace torchaudio { +namespace rnnt { +namespace cpu { + +torch::Tensor compute_betas( + const torch::Tensor& logits, + const torch::Tensor& targets, + const torch::Tensor& logit_lengths, + const torch::Tensor& target_lengths, + int64_t blank, + double clamp) { + Options options; + options.batchSize_ = logit_lengths.size(0); + options.nHypos_ = target_lengths.size(0) / logit_lengths.size(0); + options.maxSrcLen_ = logits.size(1); + options.maxTgtLen_ = logits.size(2); + options.numTargets_ = logits.size(3); + options.blank_ = blank; + options.clamp_ = clamp; + + CHECK_EQ(logits.device().type(), torch::DeviceType::CPU); + options.device_ = CPU; + + torch::Tensor costs = torch::empty( + target_lengths.size(0), + torch::TensorOptions().device(logits.device()).dtype(logits.dtype())); + + torch::Tensor betas = torch::zeros( + {options.batchSize_ * options.nHypos_, + options.maxSrcLen_, + options.maxTgtLen_}, + torch::TensorOptions().device(logits.device()).dtype(logits.dtype())); + + torch::Tensor int_workspace = torch::empty( + IntWorkspace::ComputeSizeFromOptions(options), + torch::TensorOptions() + .device(logits.device()) + .dtype(torch::ScalarType::Int)); + + torch::Tensor float_workspace = torch::empty( + DtypeWorkspace::ComputeSizeFromOptions(options), + torch::TensorOptions() + .device(logits.device()) + .dtype(torch::ScalarType::Float)); + + Workspace workspace( + /*options=*/options, + /*dtype_data=*/float_workspace.data_ptr(), + /*dtype_size=*/float_workspace.numel(), + /*int_data=*/int_workspace.data_ptr(), + /*int_size=*/int_workspace.numel()); + + // Only support float, this is mainly to enable easy + // unit-testing + ComputeBetas( + /*workspace=*/workspace, + /*logits=*/logits.data_ptr(), + /*targets=*/targets.data_ptr(), + /*logit_lengths=*/logit_lengths.data_ptr(), + /*target_lengths=*/target_lengths.data_ptr(), + /*costs=*/costs.data_ptr(), + /*betas=*/betas.data_ptr()); + return betas; +} + +TORCH_LIBRARY_IMPL(torchaudio, CPU, m) { + m.impl("rnnt_loss_betas", &compute_betas); +} + +} // namespace cpu +} // namespace rnnt +} // namespace torchaudio diff --git a/torchaudio/csrc/rnnt/cpu/cpu_kernels.h b/torchaudio/csrc/rnnt/cpu/cpu_kernels.h new file mode 100644 index 00000000..468cb418 --- /dev/null +++ b/torchaudio/csrc/rnnt/cpu/cpu_kernels.h @@ -0,0 +1,498 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include + +namespace torchaudio { +namespace rnnt { +namespace cpu { + +template +struct LogProbs { + DTYPE skip_; // blank. + DTYPE emit_; // target. + + LogProbs(DTYPE skip, DTYPE emit) : skip_(skip), emit_(emit) {} + + DTYPE& skip() { + return skip_; + } + DTYPE& emit() { + return emit_; + } + + const DTYPE& skip() const { + return skip_; + } + const DTYPE& emit() const { + return emit_; + } +}; + +// TensorView: view a block of allocated memory as a tensor. +template +class TensorView { + public: + TensorView(const std::vector& dims, DTYPE* data) + : dims_(dims), data_(data) { + strides_.resize(dims.size()); + strides_.back() = 1; + for (int i = dims.size() - 2; i >= 0; --i) { + strides_[i] = strides_[i + 1] * dims[i + 1]; + } + } + + DTYPE& operator()(const std::vector& indices) { + CHECK_EQ(indices.size(), dims_.size()); + int index = indices.back(); + for (int i = indices.size() - 2; i >= 0; --i) { + index += indices[i] * strides_[i]; + } + return data_[index]; + } + + void SetZero() { + int size = dims_[0] * strides_[0]; + std::memset(data_, 0, sizeof(DTYPE) * size); + } + + private: + std::vector dims_; + std::vector strides_; + DTYPE* data_; +}; + +template +status_t LogSumExp2D(int N, int D, const DTYPE* logits, CAST_DTYPE* outputs) { + for (int i = 0; i < N * D; i += D) { + CAST_DTYPE max = logits[i]; + for (int j = 1; j < D; ++j) { + max = std::max(max, CAST_DTYPE(logits[i + j])); + } + CAST_DTYPE sum = 0; + for (int j = 0; j < D; ++j) { + sum = sum + std::exp(CAST_DTYPE(logits[i + j]) - max); + } + outputs[i / D] = max + std::log(sum); + } + + return SUCCESS; +} + +template +void ComputeLogProbsOneSequence( + const Options& options, + TensorView& logits, + const int* targets, + int srcLen, + int tgtLen, + TensorView& denom, + TensorView>& logProbs) { + const int& T = srcLen; + const int& U = tgtLen; + const int& blank = options.blank_; + + for (int t = 0; t < T; ++t) { + for (int u = 0; u < U; ++u) { + if (u < U - 1) { + logProbs({t, u}).emit() = + CAST_DTYPE(logits({t, u, targets[u]})) - denom({t, u}); + } + logProbs({t, u}).skip() = + CAST_DTYPE(logits({t, u, blank})) - denom({t, u}); + } + } +} + +template +status_t ComputeLogProbs( + const Options& options, + const DTYPE* logits, + const int* targets, + const int* srcLengths, + const int* tgtLengths, + const CAST_DTYPE* denominators, + CAST_DTYPE* logProbs) { + std::vector> seqLogits; + std::vector seqTargets; + std::vector> seqDenoms; + std::vector>> seqlogProbs; + + const int& B = options.batchSize_; + const int& maxT = options.maxSrcLen_; + const int& maxU = options.maxTgtLen_; + const int& D = options.numTargets_; + for (int b = 0; b < B; ++b) { + seqLogits.push_back( + TensorView({maxT, maxU, D}, logits + b * maxT * maxU * D)); + seqTargets.push_back(targets + b * (maxU - 1)); + seqDenoms.push_back(TensorView( + {maxT, maxU}, denominators + b * maxT * maxU)); + seqlogProbs.push_back(TensorView>( + {maxT, maxU}, + reinterpret_cast*>(logProbs) + b * maxT * maxU)); + } + + //#pragma omp parallel for + for (int b = 0; b < B; ++b) { // use max 2 * B threads. + ComputeLogProbsOneSequence( + /*options=*/options, + /*logits=*/seqLogits[b], + /*targets=*/seqTargets[b], + /*srcLen=*/srcLengths[b], + /*tgtLen=*/tgtLengths[b] + 1, // with prepended blank. + /*denom=*/seqDenoms[b], + /*logProbs=*/seqlogProbs[b]); + } + + return SUCCESS; +} + +template +DTYPE ComputeAlphaOneSequence( + const Options& options, + TensorView>& logProbs, + int srcLen, + int tgtLen, + TensorView& alpha) { + const int& T = srcLen; + const int& U = tgtLen; + + alpha({0, 0}) = DTYPE(0); + + for (int t = 1; t < T; ++t) { // u == 0. + alpha({t, 0}) = alpha({t - 1, 0}) + logProbs({t - 1, 0}).skip(); + } + + for (int u = 1; u < U; ++u) { // t == 0. + alpha({0, u}) = alpha({0, u - 1}) + logProbs({0, u - 1}).emit(); + } + + for (int t = 1; t < T; ++t) { + for (int u = 1; u < U; ++u) { + alpha({t, u}) = math::lse( + alpha({t - 1, u}) + logProbs({t - 1, u}).skip(), + alpha({t, u - 1}) + logProbs({t, u - 1}).emit()); + } + } + + DTYPE forward_score = alpha({T - 1, U - 1}) + logProbs({T - 1, U - 1}).skip(); + + return forward_score; +} + +template +DTYPE ComputeBetaOneSequence( + const Options& options, + TensorView>& logProbs, + int srcLen, + int tgtLen, + TensorView& beta) { + const int& T = srcLen; + const int& U = tgtLen; + + beta({T - 1, U - 1}) = logProbs({T - 1, U - 1}).skip(); + + for (int t = T - 2; t >= 0; --t) { // u == U - 1. + beta({t, U - 1}) = beta({t + 1, U - 1}) + logProbs({t, U - 1}).skip(); + } + + for (int u = U - 2; u >= 0; --u) { // t == T - 1. + beta({T - 1, u}) = beta({T - 1, u + 1}) + logProbs({T - 1, u}).emit(); + } + + for (int t = T - 2; t >= 0; --t) { + for (int u = U - 2; u >= 0; --u) { + beta({t, u}) = math::lse( + beta({t + 1, u}) + logProbs({t, u}).skip(), + beta({t, u + 1}) + logProbs({t, u}).emit()); + } + } + + DTYPE backward_score = beta({0, 0}); + + return backward_score; +} + +template +DTYPE ComputeAlphaOrBetaOneSequence( + int thread, + const Options& options, + TensorView>& logProbs, + int srcLen, + int tgtLen, + TensorView& alpha, + TensorView& beta) { + if (thread & 1) { + return ComputeAlphaOneSequence( + /*options=*/options, + /*logProbs=*/logProbs, + /*srcLen=*/srcLen, + /*tgtLen=*/tgtLen, + /*alpha=*/alpha); + } else { + return ComputeBetaOneSequence( + /*options=*/options, + /*logProbs=*/logProbs, + /*srcLen=*/srcLen, + /*tgtLen=*/tgtLen, + /*beta=*/beta); + } +} + +template +void ComputeAlphasBetas( + const Options& options, + const CAST_DTYPE* logProbs, + const int* srcLengths, + const int* tgtLengths, + CAST_DTYPE* alphas, + CAST_DTYPE* betas, + DTYPE* costs) { + std::vector>> seqlogProbs; + std::vector> seq_alphas; + std::vector> seq_betas; + + const int& B = options.batchSize_; + const int& maxT = options.maxSrcLen_; + const int& maxU = options.maxTgtLen_; + + for (int b = 0; b < B; ++b) { + seqlogProbs.push_back(TensorView>( + {maxT, maxU}, + reinterpret_cast*>( + const_cast(logProbs)) + + b * maxT * maxU)); + seq_alphas.push_back( + TensorView({maxT, maxU}, alphas + b * maxT * maxU)); + seq_betas.push_back( + TensorView({maxT, maxU}, betas + b * maxT * maxU)); + } + + std::vector scores(B << 1); + //#pragma omp parallel for + for (int t = 0; t < (B << 1); ++t) { // use max 2 * B threads. + int i = (t >> 1); + scores[t] = ComputeAlphaOrBetaOneSequence( + /*thread=*/t, + /*options=*/options, + /*logProbs=*/seqlogProbs[i], + /*srcLen=*/srcLengths[i], + /*tgtLen=*/tgtLengths[i] + 1, // with prepended blank. + /*alpha=*/seq_alphas[i], + /*beta=*/seq_betas[i]); + } + for (int b = 0; b < B; ++b) { + costs[b] = -scores[b << 1]; + } +} + +template +void ComputeGradientsOneSequence( + const Options& options, + TensorView& logits, + const int* targets, + int srcLen, + int tgtLen, + TensorView& denom, + TensorView& alpha, + TensorView& beta, + TensorView& gradients) { + // don't set gradients to zero to here as gradients might reuse memory from + // logits + + const int& T = srcLen; + const int& U = tgtLen; + const int& D = options.numTargets_; + const int& blank = options.blank_; + const CAST_DTYPE clamp = options.clamp_; + + CAST_DTYPE cost = -beta({0, 0}); + + // Note - below gradient is different from numpy_transducer, since we + // compute log_softmax more efficiently within the loss, to save memory The + // details of the below implementation / equations can be found in Sec 3.2 + // (function merging) in below paper: + // https://www.microsoft.com/en-us/research/uploads/prod/2019/10/RNNT.pdf + + for (int t = 0; t < T; ++t) { + for (int u = 0; u < U; ++u) { + CAST_DTYPE c = alpha({t, u}) + cost - denom({t, u}); + for (int d = 0; d < D; ++d) { + CAST_DTYPE g = CAST_DTYPE(logits({t, u, d})) + c; + if (d == blank && t == T - 1 && u == U - 1) { // last blank transition. + gradients({t, u, d}) = std::exp(g + beta({t, u})) - std::exp(g); + } else if (d == blank && t < T - 1) { + gradients({t, u, d}) = + std::exp(g + beta({t, u})) - std::exp(g + beta({t + 1, u})); + } else if (u < U - 1 && d == targets[u]) { + gradients({t, u, d}) = + std::exp(g + beta({t, u})) - std::exp(g + beta({t, u + 1})); + } else { + gradients({t, u, d}) = std::exp(g + beta({t, u})); + } + + if (clamp > 0) { + gradients({t, u, d}) = + math::min(CAST_DTYPE(gradients({t, u, d})), clamp); + gradients({t, u, d}) = + math::max(CAST_DTYPE(gradients({t, u, d})), -clamp); + } + } + } + } + + // zero out the rest of the gradients, necessary when reusing logits memory + // check the memory location to see if it's necessary + if (&gradients({0, 0, 0}) == &logits({0, 0, 0})) { + const int& maxT = options.maxSrcLen_; + const int& maxU = options.maxTgtLen_; + for (int t = T; t < maxT; ++t) { + for (int u = 0; u < maxU; ++u) { + for (int d = 0; d < D; ++d) { + gradients({t, u, d}) = 0.; + } + } + } + for (int t = 0; t < T; ++t) { + for (int u = U; u < maxU; ++u) { + for (int d = 0; d < D; ++d) { + gradients({t, u, d}) = 0.; + } + } + } + } +} + +template +void ComputeGradients( + const Options& options, + const DTYPE* logits, + const int* targets, + const int* srcLengths, + const int* tgtLengths, + const CAST_DTYPE* denominators, + const CAST_DTYPE* alphas, + const CAST_DTYPE* betas, + DTYPE* gradients) { + std::vector> seqLogits; + std::vector seqTargets; + std::vector> seqDenoms; + std::vector> seq_alphas; + std::vector> seq_betas; + std::vector> seq_gradients; + + const int& B = options.batchSize_; + const int& maxT = options.maxSrcLen_; + const int& maxU = options.maxTgtLen_; + const int& D = options.numTargets_; + for (int b = 0; b < B; ++b) { + seqLogits.push_back( + TensorView({maxT, maxU, D}, logits + b * maxT * maxU * D)); + seqTargets.push_back(targets + b * (maxU - 1)); + seqDenoms.push_back(TensorView( + {maxT, maxU}, denominators + b * maxT * maxU)); + seq_alphas.push_back( + TensorView({maxT, maxU}, alphas + b * maxT * maxU)); + seq_betas.push_back( + TensorView({maxT, maxU}, betas + b * maxT * maxU)); + seq_gradients.push_back( + TensorView({maxT, maxU, D}, gradients + b * maxT * maxU * D)); + } + + //#pragma omp parallel for + for (int b = 0; b < B; ++b) { // use max 2 * B threads. + ComputeGradientsOneSequence( + /*options=*/options, + /*logits=*/seqLogits[b], + /*targets=*/seqTargets[b], + /*srcLen=*/srcLengths[b], + /*tgtLen=*/tgtLengths[b] + 1, // with prepended blank. + /*denom=*/seqDenoms[b], + /*alpha=*/seq_alphas[b], + /*beta=*/seq_betas[b], + /*gradients=*/seq_gradients[b]); + } +} + +template +void ComputeAlphas( + const Options& options, + const CAST_DTYPE* logProbs, + const int* srcLengths, + const int* tgtLengths, + CAST_DTYPE* alphas) { + std::vector>> seqlogProbs; + std::vector> seq_alphas; + + const int& B = options.batchSize_; + const int& maxT = options.maxSrcLen_; + const int& maxU = options.maxTgtLen_; + + for (int b = 0; b < B; ++b) { + seqlogProbs.push_back(TensorView>( + {maxT, maxU}, + reinterpret_cast*>( + const_cast(logProbs)) + + b * maxT * maxU)); + seq_alphas.push_back( + TensorView({maxT, maxU}, alphas + b * maxT * maxU)); + } + + std::vector scores(B << 1); + //#pragma omp parallel for + for (int i = 0; i < B; ++i) { // use max 2 * B threads. + ComputeAlphaOneSequence( + options, + /*logProbs=*/seqlogProbs[i], + /*srcLen=*/srcLengths[i], + /*tgtLen=*/tgtLengths[i] + 1, // with prepended blank. + /*alpha=*/seq_alphas[i]); + } +} + +template +void ComputeBetas( + const Options& options, + const CAST_DTYPE* logProbs, + const int* srcLengths, + const int* tgtLengths, + CAST_DTYPE* costs, + CAST_DTYPE* betas) { + std::vector>> seqlogProbs; + std::vector> seq_betas; + + const int& B = options.batchSize_; + const int& maxT = options.maxSrcLen_; + const int& maxU = options.maxTgtLen_; + + for (int b = 0; b < B; ++b) { + seqlogProbs.push_back(TensorView>( + {maxT, maxU}, + reinterpret_cast*>( + const_cast(logProbs)) + + b * maxT * maxU)); + seq_betas.push_back( + TensorView({maxT, maxU}, betas + b * maxT * maxU)); + } + + std::vector scores(B << 1); + //#pragma omp parallel for + for (int i = 0; i < B; ++i) { // use max 2 * B threads. + ComputeBetaOneSequence( + options, + /*logProbs=*/seqlogProbs[i], + /*srcLen=*/srcLengths[i], + /*tgtLen=*/tgtLengths[i] + 1, // with prepended blank. + /*betas=*/seq_betas[i]); + } +} + +} // namespace cpu +} // namespace rnnt +} // namespace torchaudio diff --git a/torchaudio/csrc/rnnt/cpu/cpu_transducer.h b/torchaudio/csrc/rnnt/cpu/cpu_transducer.h new file mode 100644 index 00000000..9d1fc867 --- /dev/null +++ b/torchaudio/csrc/rnnt/cpu/cpu_transducer.h @@ -0,0 +1,184 @@ +#pragma once + +#include +#include + +namespace torchaudio { +namespace rnnt { +namespace cpu { + +// Inputs: +// workspace: workspace. +// logits: pointer to (B, maxT, maxU, D) logits. +// targets: pointer to (B, maxU - 1) targets in the batch. +// srcLengths: pointer to (B, ) source lengths in the batch. +// tgtLengths: pointer to (B, ) target lengths in the batch. +// +// Outputs: +// costs: pointer to (B, ) costs in the batch. +// gradients: pointer to (B, maxT, maxU, D) gradients in the batch. +template +status_t Compute( + const Workspace& workspace, + const DTYPE* logits, + const int* targets, + const int* srcLengths, + const int* tgtLengths, + DTYPE* costs, + DTYPE* gradients = nullptr) { + const Options& options = workspace.GetOptions(); + + CHECK_EQ(options.device_, CPU); + + const int& B = options.batchSize_; + const int& maxT = options.maxSrcLen_; + const int& maxU = options.maxTgtLen_; + const int& D = options.numTargets_; + + { // compute denominators. + LogSumExp2D( + /*N=*/B * maxT * maxU, + /*D=*/D, + /*logits=*/logits, + /*denominators=*/workspace.GetPointerToDenominators()); + } + + { // compute log prob pairs. + ComputeLogProbs( + /*options=*/options, + /*logits=*/logits, + /*targets=*/targets, + /*srcLengths=*/srcLengths, + /*tgtLengths=*/tgtLengths, + /*denominators=*/workspace.GetPointerToDenominators(), + /*log_probs=*/workspace.GetPointerToLogProbs()); + } + + { // compute alphas and betas. + ComputeAlphasBetas( + /*options=*/options, + /*log_probs=*/workspace.GetPointerToLogProbs(), + /*srcLengths=*/srcLengths, + /*tgtLengths=*/tgtLengths, + /*alphas=*/workspace.GetPointerToAlphas(), + /*betas=*/workspace.GetPointerToBetas(), + /*costs=*/costs); + } + + if (gradients != nullptr) { + ComputeGradients( + /*options=*/options, + /*logits=*/logits, + /*targets=*/targets, + /*srcLengths=*/srcLengths, + /*tgtLengths=*/tgtLengths, + /*denominators=*/workspace.GetPointerToDenominators(), + /*alphas=*/workspace.GetPointerToAlphas(), + /*betas=*/workspace.GetPointerToBetas(), + /*gradients=*/gradients); + } + + return SUCCESS; +} + +template +status_t ComputeAlphas( + const Workspace& workspace, + const DTYPE* logits, + const int* targets, + const int* srcLengths, + const int* tgtLengths, + DTYPE* alphas) { + const Options& options = workspace.GetOptions(); + + CHECK_EQ(options.device_, CPU); + + const int& B = options.batchSize_; + const int& maxT = options.maxSrcLen_; + const int& maxU = options.maxTgtLen_; + const int& D = options.numTargets_; + + { // compute denominators. + LogSumExp2D( + /*N=*/B * maxT * maxU, + /*D=*/D, + /*logits=*/logits, + /*denominators=*/workspace.GetPointerToDenominators()); + } + + { // compute log prob pairs. + ComputeLogProbs( + /*options=*/options, + /*logits=*/logits, + /*targets=*/targets, + /*srcLengths=*/srcLengths, + /*tgtLengths=*/tgtLengths, + /*denominators=*/workspace.GetPointerToDenominators(), + /*log_probs=*/workspace.GetPointerToLogProbs()); + } + + { // compute alphas. + ComputeAlphas( + /*options=*/options, + /*log_probs=*/workspace.GetPointerToLogProbs(), + /*srcLengths=*/srcLengths, + /*tgtLengths=*/tgtLengths, + /*alphas=*/alphas); + } + + return SUCCESS; +} + +template +status_t ComputeBetas( + const Workspace& workspace, + const DTYPE* logits, + const int* targets, + const int* srcLengths, + const int* tgtLengths, + DTYPE* costs, + DTYPE* betas) { + const Options& options = workspace.GetOptions(); + + CHECK_EQ(options.device_, CPU); + + const int& B = options.batchSize_; + const int& maxT = options.maxSrcLen_; + const int& maxU = options.maxTgtLen_; + const int& D = options.numTargets_; + + { // compute denominators. + LogSumExp2D( + /*N=*/B * maxT * maxU, + /*D=*/D, + /*logits=*/logits, + /*denominators=*/workspace.GetPointerToDenominators()); + } + + { // compute log prob pairs. + ComputeLogProbs( + /*options=*/options, + /*logits=*/logits, + /*targets=*/targets, + /*srcLengths=*/srcLengths, + /*tgtLengths=*/tgtLengths, + /*denominators=*/workspace.GetPointerToDenominators(), + /*log_probs=*/workspace.GetPointerToLogProbs()); + } + + { // compute betas. + ComputeBetas( + /*options=*/options, + /*log_probs=*/workspace.GetPointerToLogProbs(), + /*srcLengths=*/srcLengths, + /*tgtLengths=*/tgtLengths, + /*costs=*/costs, + /*betas=*/betas); + } + + return SUCCESS; +} + +} // namespace cpu +} // namespace rnnt +} // namespace torchaudio diff --git a/torchaudio/csrc/rnnt/cpu/kernel_utils.h b/torchaudio/csrc/rnnt/cpu/kernel_utils.h new file mode 100644 index 00000000..5a4b0fb8 --- /dev/null +++ b/torchaudio/csrc/rnnt/cpu/kernel_utils.h @@ -0,0 +1,66 @@ +#pragma once + +#include + +#include + +namespace torchaudio { +namespace rnnt { + +inline HOST_AND_DEVICE bool in_range( + int start, + int end, // inclusive + int val) { + return start <= val && val <= end; +} + +#define LOG_PROBS_SKIP_IDX 0 +#define LOG_PROBS_EMIT_IDX 1 + +struct Indexer2D { + const int& size2_; + + FORCE_INLINE HOST_AND_DEVICE Indexer2D(const int& size2) : size2_(size2) {} + + FORCE_INLINE HOST_AND_DEVICE int operator()(int index1, int index2) { + return index1 * size2_ + index2; + } +}; + +struct Indexer3D { + const int& size2_; + const int& size3_; + + FORCE_INLINE HOST_AND_DEVICE Indexer3D(const int& size2, const int& size3) + : size2_(size2), size3_(size3) {} + + FORCE_INLINE HOST_AND_DEVICE int operator()( + int index1, + int index2, + int index3) { + return (index1 * size2_ + index2) * size3_ + index3; + } +}; + +struct Indexer4D { + const int& size2_; + const int& size3_; + const int& size4_; + + HOST_AND_DEVICE Indexer4D( + const int& size2, + const int& size3, + const int& size4) + : size2_(size2), size3_(size3), size4_(size4) {} + + HOST_AND_DEVICE int operator()( + int index1, + int index2, + int index3, + int index4) { + return ((index1 * size2_ + index2) * size3_ + index3) * size4_ + index4; + } +}; + +} // namespace rnnt +} // namespace torchaudio diff --git a/torchaudio/csrc/rnnt/cpu/math.h b/torchaudio/csrc/rnnt/cpu/math.h new file mode 100644 index 00000000..e630a65c --- /dev/null +++ b/torchaudio/csrc/rnnt/cpu/math.h @@ -0,0 +1,42 @@ +#pragma once + +#include + +namespace torchaudio { +namespace rnnt { + +namespace math { + +template +FORCE_INLINE HOST_AND_DEVICE DTYPE max(DTYPE x, DTYPE y) { + if (x > y) + return x; + else + return y; +} + +template +FORCE_INLINE HOST_AND_DEVICE DTYPE min(DTYPE x, DTYPE y) { + if (x > y) + return y; + else + return x; +} + +// log_sum_exp +template +FORCE_INLINE HOST_AND_DEVICE DTYPE lse(DTYPE x, DTYPE y); + +template <> +FORCE_INLINE HOST_AND_DEVICE float lse(float x, float y) { + if (y > x) { + return y + log1pf(expf(x - y)); + } else { + return x + log1pf(expf(y - x)); + } +} + +} // namespace math + +} // namespace rnnt +} // namespace torchaudio diff --git a/torchaudio/csrc/rnnt/gpu/compute.cu b/torchaudio/csrc/rnnt/gpu/compute.cu new file mode 100644 index 00000000..0ccee481 --- /dev/null +++ b/torchaudio/csrc/rnnt/gpu/compute.cu @@ -0,0 +1,151 @@ +#include +#include +#include + +namespace torchaudio { +namespace rnnt { +namespace gpu { + +// Entry point into RNNT Loss +std::tuple> compute( + torch::Tensor& logits, + const torch::Tensor& targets, + const torch::Tensor& logit_lengths, + const torch::Tensor& target_lengths, + int64_t blank, + double clamp) { + TORCH_CHECK( + logits.device().type() == targets.device().type(), + "logits and targets must be on the same device"); + TORCH_CHECK( + logits.device().type() == logit_lengths.device().type(), + "logits and logit_lengths must be on the same device"); + TORCH_CHECK( + logits.device().type() == target_lengths.device().type(), + "logits and target_lengths must be on the same device"); + + TORCH_CHECK( + logits.dtype() == torch::kFloat32 || logits.dtype() == torch::kFloat16, + "logits must be float32 or float16 (half) type"); + TORCH_CHECK(targets.dtype() == torch::kInt32, "targets must be int32 type"); + TORCH_CHECK( + logit_lengths.dtype() == torch::kInt32, + "logit_lengths must be int32 type"); + TORCH_CHECK( + target_lengths.dtype() == torch::kInt32, + "target_lengths must be int32 type"); + + TORCH_CHECK(logits.is_contiguous(), "logits must be contiguous"); + TORCH_CHECK(targets.is_contiguous(), "targets must be contiguous"); + TORCH_CHECK( + logit_lengths.is_contiguous(), "logit_lengths must be contiguous"); + TORCH_CHECK( + target_lengths.is_contiguous(), "target_lengths must be contiguous"); + + TORCH_CHECK( + logits.dim() == 4, "logits must be 4-D (batch, time, target, class)"); + TORCH_CHECK( + targets.dim() == 2, "targets must be 2-D (batch, max target length)"); + TORCH_CHECK(logit_lengths.dim() == 1, "logit_lengths must be 1-D"); + TORCH_CHECK(target_lengths.dim() == 1, "target_lengths must be 1-D"); + + TORCH_CHECK( + logit_lengths.size(0) == logits.size(0), + "batch dimension mismatch between logits and logit_lengths"); + TORCH_CHECK( + target_lengths.size(0) == logits.size(0), + "batch dimension mismatch between logits and target_lengths"); + TORCH_CHECK( + targets.size(0) == logits.size(0), + "batch dimension mismatch between logits and targets"); + + TORCH_CHECK( + blank >= 0 && blank < logits.size(-1), + "blank must be within [0, logits.shape[-1])"); + + TORCH_CHECK( + logits.size(1) == at::max(logit_lengths).item().toInt(), + "input length mismatch"); + TORCH_CHECK( + logits.size(2) == at::max(target_lengths).item().toInt() + 1, + "output length mismatch"); + TORCH_CHECK( + targets.size(1) == at::max(target_lengths).item().toInt(), + "target length mismatch"); + + Options options; + options.batchSize_ = logit_lengths.size(0); + options.nHypos_ = target_lengths.size(0) / logit_lengths.size(0); + options.maxSrcLen_ = logits.size(1); + options.maxTgtLen_ = logits.size(2); + options.numTargets_ = logits.size(3); + options.blank_ = blank; + options.clamp_ = clamp; + + CHECK_EQ(logits.device().type(), torch::DeviceType::CUDA); + options.stream_ = at::cuda::getCurrentCUDAStream(); + cudaSetDevice(logits.get_device()); + options.device_ = GPU; + + torch::Tensor costs = torch::empty( + options.batchSize_ * options.nHypos_, + torch::TensorOptions().device(logits.device()).dtype(logits.dtype())); + c10::optional gradients = torch::zeros_like(logits); + + torch::Tensor int_workspace = torch::empty( + IntWorkspace::ComputeSizeFromOptions(options), + torch::TensorOptions() + .device(logits.device()) + .dtype(torch::ScalarType::Int)); + + torch::Tensor float_workspace = torch::empty( + DtypeWorkspace::ComputeSizeFromOptions(options), + torch::TensorOptions() + .device(logits.device()) + .dtype(torch::ScalarType::Float)); + + Workspace workspace( + /*options=*/options, + /*dtype_data=*/float_workspace.data_ptr(), + /*dtype_size=*/float_workspace.numel(), + /*int_data=*/int_workspace.data_ptr(), + /*int_size=*/int_workspace.numel()); + + switch (logits.scalar_type()) { + case torch::ScalarType::Float: { + Compute( + /*workspace=*/workspace, + /*logits=*/logits.data_ptr(), + /*targets=*/targets.data_ptr(), + /*logit_lengths=*/logit_lengths.data_ptr(), + /*target_lengths=*/target_lengths.data_ptr(), + /*costs=*/costs.data_ptr(), + /*gradients=*/gradients->data_ptr()); + break; + } + case torch::ScalarType::Half: { + Compute( + /*workspace=*/workspace, + /*logits=*/logits.data_ptr(), + /*targets=*/targets.data_ptr(), + /*logit_lengths=*/logit_lengths.data_ptr(), + /*target_lengths=*/target_lengths.data_ptr(), + /*costs=*/costs.data_ptr(), + /*gradients=*/gradients->data_ptr()); + break; + } + default: { + break; + } + }; + + return std::make_tuple(costs, gradients); +} + +TORCH_LIBRARY_IMPL(torchaudio, CUDA, m) { + m.impl("rnnt_loss", &compute); +} + +} // namespace gpu +} // namespace rnnt +} // namespace torchaudio diff --git a/torchaudio/csrc/rnnt/gpu/compute_alphas.cu b/torchaudio/csrc/rnnt/gpu/compute_alphas.cu new file mode 100644 index 00000000..9a59b534 --- /dev/null +++ b/torchaudio/csrc/rnnt/gpu/compute_alphas.cu @@ -0,0 +1,73 @@ +#include +#include +#include + +namespace torchaudio { +namespace rnnt { +namespace gpu { + +torch::Tensor compute_alphas( + const torch::Tensor& logits, + const torch::Tensor& targets, + const torch::Tensor& logit_lengths, + const torch::Tensor& target_lengths, + int64_t blank, + double clamp) { + Options options; + options.batchSize_ = logit_lengths.size(0); + options.nHypos_ = target_lengths.size(0) / logit_lengths.size(0); + options.maxSrcLen_ = logits.size(1); + options.maxTgtLen_ = logits.size(2); + options.numTargets_ = logits.size(3); + options.blank_ = blank; + options.clamp_ = clamp; + + CHECK_EQ(logits.device().type(), torch::DeviceType::CUDA); + options.stream_ = at::cuda::getCurrentCUDAStream(); + cudaSetDevice(logits.get_device()); + options.device_ = GPU; + + torch::Tensor alphas = torch::zeros( + {options.batchSize_ * options.nHypos_, + options.maxSrcLen_, + options.maxTgtLen_}, + torch::TensorOptions().device(logits.device()).dtype(logits.dtype())); + + torch::Tensor int_workspace = torch::empty( + IntWorkspace::ComputeSizeFromOptions(options), + torch::TensorOptions() + .device(logits.device()) + .dtype(torch::ScalarType::Int)); + + torch::Tensor float_workspace = torch::empty( + DtypeWorkspace::ComputeSizeFromOptions(options), + torch::TensorOptions() + .device(logits.device()) + .dtype(torch::ScalarType::Float)); + + Workspace workspace( + /*options=*/options, + /*dtype_data=*/float_workspace.data_ptr(), + /*dtype_size=*/float_workspace.numel(), + /*int_data=*/int_workspace.data_ptr(), + /*int_size=*/int_workspace.numel()); + + // Only support float, this is mainly to enable easy + // unit-testing + ComputeAlphas( + /*workspace=*/workspace, + /*logits=*/logits.data_ptr(), + /*targets=*/targets.data_ptr(), + /*logit_lengths=*/logit_lengths.data_ptr(), + /*target_lengths=*/target_lengths.data_ptr(), + /*alphas=*/alphas.data_ptr()); + return alphas; +} + +TORCH_LIBRARY_IMPL(torchaudio, CUDA, m) { + m.impl("rnnt_loss_alphas", &compute_alphas); +} + +} // namespace gpu +} // namespace rnnt +} // namespace torchaudio diff --git a/torchaudio/csrc/rnnt/gpu/compute_betas.cu b/torchaudio/csrc/rnnt/gpu/compute_betas.cu new file mode 100644 index 00000000..75b8e2a5 --- /dev/null +++ b/torchaudio/csrc/rnnt/gpu/compute_betas.cu @@ -0,0 +1,78 @@ +#include +#include +#include + +namespace torchaudio { +namespace rnnt { +namespace gpu { + +torch::Tensor compute_betas( + const torch::Tensor& logits, + const torch::Tensor& targets, + const torch::Tensor& logit_lengths, + const torch::Tensor& target_lengths, + int64_t blank, + double clamp) { + Options options; + options.batchSize_ = logit_lengths.size(0); + options.nHypos_ = target_lengths.size(0) / logit_lengths.size(0); + options.maxSrcLen_ = logits.size(1); + options.maxTgtLen_ = logits.size(2); + options.numTargets_ = logits.size(3); + options.blank_ = blank; + options.clamp_ = clamp; + + CHECK_EQ(logits.device().type(), torch::DeviceType::CUDA); + options.stream_ = at::cuda::getCurrentCUDAStream(); + cudaSetDevice(logits.get_device()); + options.device_ = GPU; + + torch::Tensor costs = torch::empty( + target_lengths.size(0), + torch::TensorOptions().device(logits.device()).dtype(logits.dtype())); + + torch::Tensor betas = torch::zeros( + {options.batchSize_ * options.nHypos_, + options.maxSrcLen_, + options.maxTgtLen_}, + torch::TensorOptions().device(logits.device()).dtype(logits.dtype())); + + torch::Tensor int_workspace = torch::empty( + IntWorkspace::ComputeSizeFromOptions(options), + torch::TensorOptions() + .device(logits.device()) + .dtype(torch::ScalarType::Int)); + + torch::Tensor float_workspace = torch::empty( + DtypeWorkspace::ComputeSizeFromOptions(options), + torch::TensorOptions() + .device(logits.device()) + .dtype(torch::ScalarType::Float)); + + Workspace workspace( + /*options=*/options, + /*dtype_data=*/float_workspace.data_ptr(), + /*dtype_size=*/float_workspace.numel(), + /*int_data=*/int_workspace.data_ptr(), + /*int_size=*/int_workspace.numel()); + + // Only support float, this is mainly to enable easy + // unit-testing + ComputeBetas( + /*workspace=*/workspace, + /*logits=*/logits.data_ptr(), + /*targets=*/targets.data_ptr(), + /*logit_lengths=*/logit_lengths.data_ptr(), + /*target_lengths=*/target_lengths.data_ptr(), + /*costs=*/costs.data_ptr(), + /*betas=*/betas.data_ptr()); + return betas; +} + +TORCH_LIBRARY_IMPL(torchaudio, CUDA, m) { + m.impl("rnnt_loss_betas", &compute_betas); +} + +} // namespace gpu +} // namespace rnnt +} // namespace torchaudio diff --git a/torchaudio/csrc/rnnt/gpu/gpu_kernel_utils.cuh b/torchaudio/csrc/rnnt/gpu/gpu_kernel_utils.cuh new file mode 100644 index 00000000..e5f1cfc2 --- /dev/null +++ b/torchaudio/csrc/rnnt/gpu/gpu_kernel_utils.cuh @@ -0,0 +1,98 @@ +#pragma once + +#ifdef USE_CUDA + +#include + +namespace torchaudio { +namespace rnnt { + +template +__global__ void ReduceMax2D( + int dim, + const DTYPE* inputs, // [N, dim] + CAST_DTYPE* outputs) { + __shared__ CAST_DTYPE shared[NUM_THREADS]; + + // each thread reduces one matrix row + int offset = blockIdx.x * dim; // [n, 0] + CAST_DTYPE val = inputs[offset]; // default = inputs(n, 0) + for (int d = threadIdx.x; d < dim; d += NUM_THREADS) { + CAST_DTYPE next = inputs[offset + d]; + if (next > val) { + val = next; + } + } + + shared[threadIdx.x] = val; + __syncthreads(); + + for (int stride = (NUM_THREADS >> 1); stride >= WARP_SIZE; stride >>= 1) { + if (threadIdx.x < stride && threadIdx.x + stride < dim) { + if (shared[threadIdx.x + stride] > shared[threadIdx.x]) { + shared[threadIdx.x] = shared[threadIdx.x + stride]; + val = shared[threadIdx.x]; + } + } + __syncthreads(); + } + + CAST_DTYPE shf; + for (int stride = (WARP_SIZE >> 1); stride > 0; stride >>= 1) { + shf = __shfl_down_sync(0xFFFFFFFF, val, stride); + if (threadIdx.x < stride && threadIdx.x + stride < dim) { + if (shf > val) { + val = shf; + } + } + } + + if (threadIdx.x == 0) { + outputs[blockIdx.x] = val; + } +} + +template +__global__ void ReduceLogSumExpGivenMax2D( + int dim, + const DTYPE* inputs, // [N, dim] + CAST_DTYPE* outputs) { // in: max -> out: logsum + + __shared__ CAST_DTYPE shared[NUM_THREADS]; + + CAST_DTYPE max = outputs[blockIdx.x]; + CAST_DTYPE val = 0; + + int offset = blockIdx.x * dim; + for (int d = threadIdx.x; d < dim; d += NUM_THREADS) { + val = val + std::exp(CAST_DTYPE(inputs[offset + d]) - max); + } + + shared[threadIdx.x] = val; + __syncthreads(); + + for (int stride = (NUM_THREADS >> 1); stride >= WARP_SIZE; stride >>= 1) { + if (threadIdx.x < stride && threadIdx.x + stride < dim) { + val = shared[threadIdx.x] + shared[threadIdx.x + stride]; + shared[threadIdx.x] = val; + } + __syncthreads(); + } + + CAST_DTYPE shf; + for (int stride = (WARP_SIZE >> 1); stride > 0; stride >>= 1) { + shf = __shfl_down_sync(0xFFFFFFFF, val, stride); + if (threadIdx.x < stride && threadIdx.x + stride < dim) { + val = val + shf; + } + } + + if (threadIdx.x == 0) { + outputs[blockIdx.x] = max + std::log(val); + } +} + +} // namespace rnnt +} // namespace torchaudio + +#endif // USE_CUDA diff --git a/torchaudio/csrc/rnnt/gpu/gpu_kernels.cuh b/torchaudio/csrc/rnnt/gpu/gpu_kernels.cuh new file mode 100644 index 00000000..90b5ebfd --- /dev/null +++ b/torchaudio/csrc/rnnt/gpu/gpu_kernels.cuh @@ -0,0 +1,409 @@ +#pragma once + +#ifdef USE_CUDA + +#include + +#include +#include +#include + +namespace torchaudio { +namespace rnnt { + +template +__global__ void ComputeLogProbs( + int maxSrcLen, + int maxTgtLen, + int numTargets, + int blank, + const DTYPE* logits, + const int* targets, + const int* srcLengths, + const int* tgtLengths, + const CAST_DTYPE* denominators, + CAST_DTYPE* logProbs, + int H = 1) { + const int& maxT = maxSrcLen; + const int& maxU = maxTgtLen; + const int& D = numTargets; + + const int bTgt = blockIdx.z; // 0 <= b < B + const int bSrc = bTgt / H; + const int T = srcLengths[bSrc]; + const int U = tgtLengths[bTgt] + 1; + + const int t = blockIdx.x * blockDim.x + threadIdx.x; + const int u = blockIdx.y; + + if (t >= T || u >= U) { // out of boundary. + return; + } + + Indexer3D indexer(maxT, maxU); + + int idx = indexer(bTgt, t, u); + + // skip: log_prob(b, t, u).skip() = logits(b, t, u, blank) - denom(b, t, u). + logProbs[(idx << 1) + LOG_PROBS_SKIP_IDX] = + CAST_DTYPE(logits[idx * D + blank]) - denominators[idx]; + + if (u < U - 1) { + // emit: log_prob(b, t, u).emit() = logits(b, t, u, tgt[u]) - denom(b, t, + // u). + int target = targets[Indexer2D(maxU - 1)(bTgt, u)]; + logProbs[(idx << 1) + LOG_PROBS_EMIT_IDX] = + CAST_DTYPE(logits[idx * D + target]) - denominators[idx]; + } +} + +template +__device__ void ComputeAlphas( + int maxSrcLen, + int maxTgtLen, + int numTargets, + int blank, + const CAST_DTYPE* logProbs, + const int* srcLengths, + const int* tgtLengths, + int* alpha_counters, + volatile CAST_DTYPE* alphas, + int H = 1) { + const int& maxT = maxSrcLen; + const int& maxU = maxTgtLen; + + const int bTgt = blockIdx.z; // 0 <= b < B + const int bSrc = bTgt / H; + const int T = srcLengths[bSrc]; + const int U = tgtLengths[bTgt] + 1; + + const int t = blockIdx.x * blockDim.x + threadIdx.x + 1; + const int u = blockIdx.y + 1; + + if (t >= T || u >= U) { // out of boundary. + return; + } + + int* counter = alpha_counters + Indexer2D(maxU)(bTgt, blockIdx.y); + + Indexer3D idxr(maxT, maxU); + + if (t == 1 && u == 1) { + alphas[idxr(bTgt, 0, 0)] = 0; + } + + if (blockIdx.x > 0) { // wait for previous warp (in t-axis) is ready. + while (atomicAdd(counter, 0) < blockIdx.x) { + } + } + + if (blockIdx.y > 0) { // wait for previous warp (in u-axis) is ready. + while (atomicAdd(counter - 1, 0) <= blockIdx.x) { + } + } + + if (t == 1 && u < U) { + // alpha(0, u) = alpha(0, u - 1) + logProbs(0, u - 1).emit(). + alphas[idxr(bTgt, 0, u)] = alphas[idxr(bTgt, 0, u - 1)] + + logProbs[(idxr(bTgt, 0, u - 1) << 1) + LOG_PROBS_EMIT_IDX]; + } + + if (blockIdx.y == 0 && t < T) { + CAST_DTYPE skip_prob = + logProbs[(idxr(bTgt, t - 1, 0) << 1) + LOG_PROBS_SKIP_IDX]; + CAST_DTYPE val; + +#pragma unroll + for (int i = 1; i < warpSize; i <<= 1) { + val = __shfl_up_sync(0xffffffff, skip_prob, i); + if (i <= threadIdx.x) { + skip_prob = skip_prob + val; + } + } + + val = alphas[idxr(bTgt, blockIdx.x * blockDim.x, 0)]; + alphas[idxr(bTgt, t, 0)] = skip_prob + val; + } + + if (t < T && u < U) { + CAST_DTYPE skip_prob = + logProbs[(idxr(bTgt, t - 1, u) << 1) + LOG_PROBS_SKIP_IDX]; + CAST_DTYPE emit_prob = + logProbs[(idxr(bTgt, t, u - 1) << 1) + LOG_PROBS_EMIT_IDX]; + + CAST_DTYPE skip = + alphas[idxr(bTgt, blockIdx.x * blockDim.x, u)] + skip_prob; + CAST_DTYPE emit = alphas[idxr(bTgt, t, u - 1)] + emit_prob; + + CAST_DTYPE val = math::lse(skip, emit); + CAST_DTYPE out = val; + + for (int i = 1; i < warpSize; ++i) { + val = __shfl_up_sync(0xffffffff, val, 1); + if (i == threadIdx.x) { + val = math::lse(val + skip_prob, emit); + out = val; + } + } + + alphas[idxr(bTgt, t, u)] = out; + } + + if (threadIdx.x == 0) { + __threadfence(); + atomicAdd(counter, 1); + } +} + +template +__device__ void ComputeBetasCosts( + int maxSrcLen, + int maxTgtLen, + int numTargets, + int blank, + const CAST_DTYPE* logProbs, + const int* srcLengths, + const int* tgtLengths, + int* betaCounters, + volatile CAST_DTYPE* betas, + DTYPE* costs, + int H = 1) { + const int& maxT = maxSrcLen; + const int& maxU = maxTgtLen; + + const int bTgt = blockIdx.z; // 0 <= b < B + const int bSrc = bTgt / H; + const int T = srcLengths[bSrc]; + const int U = tgtLengths[bTgt] + 1; + + const int t = T - 2 - blockIdx.x * blockDim.x - threadIdx.x; + const int u = U - 2 - blockIdx.y; + + if (t < 0 || u < 0) { // out of boundary. + return; + } + + int* counter = betaCounters + Indexer2D(maxU)(bTgt, blockIdx.y); + + Indexer3D idxr(maxT, maxU); + + if (t == T - 2 && u == U - 2) { + betas[idxr(bTgt, T - 1, U - 1)] = + logProbs[(idxr(bTgt, T - 1, U - 1) << 1) + LOG_PROBS_SKIP_IDX]; + } + + if (blockIdx.x > 0) { // wait for previous warp (in t-axis) is ready. + while (atomicAdd(counter, 0) < blockIdx.x) { + } + } + + if (blockIdx.y > 0) { // wait for previous warp (in u-axis) is ready. + while (atomicAdd(counter - 1, 0) <= blockIdx.x) { + } + } + + if (t == T - 2 && u >= 0) { + betas[idxr(bTgt, T - 1, u)] = betas[idxr(bTgt, T - 1, u + 1)] + + logProbs[(idxr(bTgt, T - 1, u) << 1) + LOG_PROBS_EMIT_IDX]; + } + + if (blockIdx.y == 0 && t >= 0) { + CAST_DTYPE skip_prob = + logProbs[(idxr(bTgt, t, U - 1) << 1) + LOG_PROBS_SKIP_IDX]; + CAST_DTYPE val; + +#pragma unroll + for (int i = 1; i < warpSize; i <<= 1) { + val = __shfl_up_sync(0xffffffff, skip_prob, i); + if (i <= threadIdx.x) { + skip_prob = skip_prob + val; + } + } + + betas[idxr(bTgt, t, U - 1)] = + betas[idxr(bTgt, T - 1 - blockIdx.x * blockDim.x, U - 1)] + skip_prob; + } + + if (t >= 0 && u >= 0) { + CAST_DTYPE skip_prob = + logProbs[(idxr(bTgt, t, u) << 1) + LOG_PROBS_SKIP_IDX]; + CAST_DTYPE emit_prob = + logProbs[(idxr(bTgt, t, u) << 1) + LOG_PROBS_EMIT_IDX]; + + CAST_DTYPE skip = betas[idxr(bTgt, t + threadIdx.x + 1, u)] + skip_prob; + CAST_DTYPE emit = betas[idxr(bTgt, t, u + 1)] + emit_prob; + + CAST_DTYPE val = math::lse(skip, emit); + CAST_DTYPE out = val; + + for (int i = 1; i < warpSize; ++i) { + val = __shfl_up_sync(0xffffffff, val, 1); + if (i == threadIdx.x) { + val = math::lse(val + skip_prob, emit); + out = val; + } + } + + betas[idxr(bTgt, t, u)] = out; + + if (t == 0 && u == 0) { // use -beta(0, 0) as cost. + costs[bTgt] = DTYPE(-out); + } + } + + if (threadIdx.x == 0) { + __threadfence(); + atomicAdd(counter, 1); + } +} + +template +__global__ void ComputeAlphasBetasCosts( + int maxSrcLen, + int maxTgtLen, + int numTargets, + int blank, + const CAST_DTYPE* logProbs, + const int* srcLengths, + const int* tgtLengths, + int* alpha_counters, + volatile CAST_DTYPE* alphas, + int* betaCounters, + volatile CAST_DTYPE* betas, + DTYPE* costs, + int warpSize = 0, + int numWarps = 0, + int H = 1) { + assert(threadIdx.y == 0 || threadIdx.y == 1); + + if (threadIdx.y == 0) { + ComputeAlphas( + /*maxSrcLen=*/maxSrcLen, + /*maxTgtLen=*/maxTgtLen, + /*numTargets=*/numTargets, + /*blank=*/blank, + /*logProbs=*/logProbs, + /*srcLengths=*/srcLengths, + /*tgtLengths=*/tgtLengths, + /*alpha_counters=*/alpha_counters, + /*alphas=*/alphas, + H); + } else { // threadIdx.y == 1 + ComputeBetasCosts( + /*maxSrcLen=*/maxSrcLen, + /*maxTgtLen=*/maxTgtLen, + /*numTargets=*/numTargets, + /*blank=*/blank, + /*logProbs=*/logProbs, + /*srcLengths=*/srcLengths, + /*tgtLengths=*/tgtLengths, + /*betaCounters=*/betaCounters, + /*beta=*/betas, + /*costs=*/costs, + H); + } +} + +template +__global__ void ComputeGradients( + int maxSrcLen, + int maxTgtLen, + int numTargets, + int blank, + CAST_DTYPE clamp, + const DTYPE* logits, + const int* targets, + const int* srcLengths, + const int* tgtLengths, + const CAST_DTYPE* denominators, + const CAST_DTYPE* alphas, + const CAST_DTYPE* betas, + DTYPE* gradients, + int H = 1) { + const int bTgt = blockIdx.z; // 0 <= b < B + const int t = blockIdx.x * blockDim.x + threadIdx.x; + const int u = blockIdx.y; + + ComputeGradientsElement( + bTgt, + t, + u, + maxSrcLen, + maxTgtLen, + numTargets, + blank, + clamp, + logits, + targets, + srcLengths, + tgtLengths, + denominators, + alphas, + betas, + gradients, + H); +} + +// This is a __global__ wrapper around ComputeAlphas +// device kernel to enable unit testing +template +__global__ void ComputeAlphasWrapper( + int maxSrcLen, + int maxTgtLen, + int numTargets, + int blank, + const CAST_DTYPE* logProbs, + const int* srcLengths, + const int* tgtLengths, + int* alpha_counters, + volatile CAST_DTYPE* alphas, + int H = 1) { + ComputeAlphas( + maxSrcLen, + maxTgtLen, + numTargets, + blank, + logProbs, + srcLengths, + tgtLengths, + alpha_counters, + alphas, + H); +} + +// This is a __global__ wrapper around ComputeBetas +// device kernel to enable unit testing +template +__global__ void ComputeBetasWrapper( + int maxSrcLen, + int maxTgtLen, + int numTargets, + int blank, + const CAST_DTYPE* logProbs, + const int* srcLengths, + const int* tgtLengths, + int* betaCounters, + volatile CAST_DTYPE* betas, + DTYPE* costs, + int H = 1) { + ComputeBetasCosts( + maxSrcLen, + maxTgtLen, + numTargets, + blank, + logProbs, + srcLengths, + tgtLengths, + betaCounters, + betas, + costs, + H); +} + +// #undef LOG_PROBS_SKIP_IDX +// #undef LOG_PROBS_EMIT_IDX + +} // namespace rnnt +} // namespace torchaudio + +#endif // USE_CUDA diff --git a/torchaudio/csrc/rnnt/gpu/gpu_transducer.h b/torchaudio/csrc/rnnt/gpu/gpu_transducer.h new file mode 100644 index 00000000..54d16b9f --- /dev/null +++ b/torchaudio/csrc/rnnt/gpu/gpu_transducer.h @@ -0,0 +1,391 @@ +#pragma once + +#ifdef USE_CUDA + +#include +#include +#include + +namespace torchaudio { +namespace rnnt { +namespace gpu { + +#define gpuErrchk(ans) \ + { gpuAssert((ans), __FILE__, __LINE__); } + +inline void gpuAssert( + cudaError_t code, + const char* file, + int line, + bool abort = true) { + if (code != cudaSuccess) { + fprintf( + stderr, + "\nGPUassert: %s %s %d\n", + cudaGetErrorString(code), + file, + line); + if (abort) + exit(code); + } +} + +template +status_t LogSumExp2D( + cudaStream_t stream, + int N, + int D, + const DTYPE* logits, // [N, D] + CAST_DTYPE* outputs) { + { // compute max among D. + dim3 block_dims(N); + dim3 thread_dims(REDUCE_THREADS); + + ReduceMax2D + <<>>( + /*dim=*/D, + /*inputs=*/logits, + /*outputs=*/outputs); + + // BUGBUG: These error codes are only accurate when launching with + // blocking. Otherwise they usually reflect earlier errors. + if (cudaGetLastError() != cudaSuccess) { + return COMPUTE_DENOMINATOR_REDUCE_MAX_FAILED; + } + } + + { // compute log(sum(exp(d_i - max))) + dim3 block_dims(N); + dim3 thread_dims(REDUCE_THREADS); + + ReduceLogSumExpGivenMax2D + <<>>( + /*dim=*/D, + /*inputs=*/logits, + /*outputs=*/outputs); + + if (cudaGetLastError() != cudaSuccess) { + return COMPUTE_DENOMINATOR_REDUCE_SUM_FAILED; + } + } + + return SUCCESS; +} + +// Inputs: +// workspace: workspace. +// logits: pointer to (B, max_T, max_U, D) logits. +// targets: pointer to (B, max_U - 1) targets in the batch. +// srcLengths: pointer to (B, ) source lengths in the batch. +// tgtLengths: pointer to (B, ) target lengths in the batch. +// +// Outputs: +// costs: pointer to (B, ) costs in the batch. +// gradients: pointer to (B, max_T, max_U, D) gradients in the batch. +template +status_t Compute( + const Workspace& workspace, + const DTYPE* logits, + const int* targets, + const int* srcLengths, + const int* tgtLengths, + DTYPE* costs, + DTYPE* gradients = nullptr) { + const Options& options = workspace.GetOptions(); + + const cudaStream_t& stream = options.stream_; + const int& B = options.batchSize_; + const int& H = options.nHypos_; + const int& max_T = options.maxSrcLen_; + const int& max_U = options.maxTgtLen_; + const int& D = options.numTargets_; + const int& blank = options.blank_; + const CAST_DTYPE clamp = options.clamp_; + + { // compute denominators. + status_t status = LogSumExp2D( + /*stream=*/stream, + /*N=*/B * H * max_T * max_U, + /*D=*/D, + /*logits=*/logits, + /*denominators=*/workspace.GetPointerToDenominators()); + + if (status != SUCCESS) { + return status; + } + } + + { // compute log probability pairs (blank and target). + int num_segments = + (max_T + MAX_THREADS_PER_BLOCK - 1) / MAX_THREADS_PER_BLOCK; + dim3 block_dims(num_segments, max_U, B * H); + dim3 thread_dims(MAX_THREADS_PER_BLOCK); + + ComputeLogProbs<<>>( + /*max_src_len=*/max_T, + /*max_tgt_len=*/max_U, + /*num_targets=*/D, + /*blank=*/blank, + /*logits=*/logits, + /*targets=*/targets, + /*srcLengths=*/srcLengths, + /*tgtLengths=*/tgtLengths, + /*denominators=*/workspace.GetPointerToDenominators(), + /*log_probs=*/workspace.GetPointerToLogProbs(), + H); + + if (cudaGetLastError() != cudaSuccess) { + return COMPUTE_LOG_PROBS_FAILED; + } + } + + { // compute alphas, betas and costs. + // warp is usually a group of threads (32) + int num_warps = (max_T + WARP_SIZE - 1) / WARP_SIZE; + + // each block is identified by 3 d tuple. + // we are using num_warp * max_U * B * H blocks + // where num_warp is division among Time axis + dim3 block_dims(num_warps, max_U, B * H); + + // each thread is identified by a 2 d tuple + // 2nd dim is 2. 1 for alpha, 1 for beta + dim3 thread_dims(WARP_SIZE, 2); + + ComputeAlphasBetasCosts + <<>>( + /*max_src_len=*/max_T, + /*max_tgt_len=*/max_U, + /*num_targets=*/D, + /*blank=*/blank, + /*log_probs=*/workspace.GetPointerToLogProbs(), + /*srcLengths=*/srcLengths, + /*tgtLengths=*/tgtLengths, + /*alpha_counters=*/workspace.GetPointerToAlphaCounters(), + /*alphas=*/workspace.GetPointerToAlphas(), + /*beta_counters=*/workspace.GetPointerToBetaCounters(), + /*betas=*/workspace.GetPointerToBetas(), + /*costs=*/costs, + /*warp_size=*/WARP_SIZE, + /*num_warps=*/num_warps, + H); + if (cudaGetLastError() != cudaSuccess) { + return COMPUTE_ALPHAS_BETAS_COSTS_FAILED; + } + } + + if (gradients != nullptr) { // compute gradients. + // don't set gradients to zero to here as gradients might reuse memory from + // logits + + int num_blocks = + (max_T + MAX_THREADS_PER_BLOCK - 1) / MAX_THREADS_PER_BLOCK; + dim3 block_dims(num_blocks, max_U, B * H); + dim3 thread_dims(MAX_THREADS_PER_BLOCK); + + ComputeGradients<<>>( + /*max_src_len=*/max_T, + /*max_tgt_len=*/max_U, + /*num_targets=*/D, + /*blank=*/blank, + /*clamp=*/clamp, + /*logits=*/logits, + /*targets=*/targets, + /*srcLengths=*/srcLengths, + /*tgtLengths=*/tgtLengths, + /*denominators=*/workspace.GetPointerToDenominators(), + /*alphas=*/workspace.GetPointerToAlphas(), + /*betas=*/workspace.GetPointerToBetas(), + /*gradients=*/gradients, + H); + if (cudaGetLastError() != cudaSuccess) { + return COMPUTE_GRADIENTS_FAILED; + } + } + + return SUCCESS; +} + +template +status_t ComputeAlphas( + const Workspace& workspace, + const DTYPE* logits, + const int* targets, + const int* srcLengths, + const int* tgtLengths, + DTYPE* alphas) { + const Options& options = workspace.GetOptions(); + + const cudaStream_t& stream = options.stream_; + const int& B = options.batchSize_; + const int& H = options.nHypos_; + const int& max_T = options.maxSrcLen_; + const int& max_U = options.maxTgtLen_; + const int& D = options.numTargets_; + const int& blank = options.blank_; + + { // compute denominators. + status_t status = LogSumExp2D( + /*stream=*/stream, + /*N=*/B * H * max_T * max_U, + /*D=*/D, + /*logits=*/logits, + /*denominators=*/workspace.GetPointerToDenominators()); + + if (status != SUCCESS) { + return status; + } + } + + { // compute log probability pairs (blank and target). + int num_segments = + (max_T + MAX_THREADS_PER_BLOCK - 1) / MAX_THREADS_PER_BLOCK; + dim3 block_dims(num_segments, max_U, B * H); + dim3 thread_dims(MAX_THREADS_PER_BLOCK); + + ComputeLogProbs<<>>( + /*max_src_len=*/max_T, + /*max_tgt_len=*/max_U, + /*num_targets=*/D, + /*blank=*/blank, + /*logits=*/logits, + /*targets=*/targets, + /*srcLengths=*/srcLengths, + /*tgtLengths=*/tgtLengths, + /*denominators=*/workspace.GetPointerToDenominators(), + /*log_probs=*/workspace.GetPointerToLogProbs(), + H); + + if (cudaGetLastError() != cudaSuccess) { + return COMPUTE_LOG_PROBS_FAILED; + } + } + { // compute alphas + // warp is usually a group of threads (32) + int num_warps = (max_T + WARP_SIZE - 1) / WARP_SIZE; + + // each block is identified by 3 d tuple. + // we are using num_warp * max_U * B blocks + // where num_warp is division among Time axis + dim3 block_dims(num_warps, max_U, B * H); + + // each thread is identified by a 2 d tuple + // 2nd dim is 1 for alpha only + dim3 thread_dims(WARP_SIZE, 1); + + ComputeAlphasWrapper + <<>>( + /*max_src_len=*/max_T, + /*max_tgt_len=*/max_U, + /*num_targets=*/D, + /*blank=*/blank, + /*log_probs=*/workspace.GetPointerToLogProbs(), + /*srcLengths=*/srcLengths, + /*tgtLengths=*/tgtLengths, + /*alpha_counters=*/workspace.GetPointerToAlphaCounters(), + /*alphas=*/(volatile DTYPE*)alphas, + H); + + if (cudaGetLastError() != cudaSuccess) { + return COMPUTE_ALPHAS_BETAS_COSTS_FAILED; + } + } + + return SUCCESS; +} + +template +status_t ComputeBetas( + const Workspace& workspace, + const DTYPE* logits, + const int* targets, + const int* srcLengths, + const int* tgtLengths, + DTYPE* costs, + DTYPE* betas) { + const Options& options = workspace.GetOptions(); + + const cudaStream_t& stream = options.stream_; + const int& B = options.batchSize_; + const int& H = options.nHypos_; + const int& max_T = options.maxSrcLen_; + const int& max_U = options.maxTgtLen_; + const int& D = options.numTargets_; + const int& blank = options.blank_; + + { // compute denominators. + status_t status = LogSumExp2D( + /*stream=*/stream, + /*N=*/B * H * max_T * max_U, + /*D=*/D, + /*logits=*/logits, + /*denominators=*/workspace.GetPointerToDenominators()); + + if (status != SUCCESS) { + return status; + } + } + + { // compute log probability pairs (blank and target). + int num_segments = + (max_T + MAX_THREADS_PER_BLOCK - 1) / MAX_THREADS_PER_BLOCK; + dim3 block_dims(num_segments, max_U, B * H); + dim3 thread_dims(MAX_THREADS_PER_BLOCK); + + ComputeLogProbs<<>>( + /*max_src_len=*/max_T, + /*max_tgt_len=*/max_U, + /*num_targets=*/D, + /*blank=*/blank, + /*logits=*/logits, + /*targets=*/targets, + /*srcLengths=*/srcLengths, + /*tgtLengths=*/tgtLengths, + /*denominators=*/workspace.GetPointerToDenominators(), + /*log_probs=*/workspace.GetPointerToLogProbs(), + H); + + if (cudaGetLastError() != cudaSuccess) { + return COMPUTE_LOG_PROBS_FAILED; + } + } + { // compute betas + // warp is usually a group of threads (32) + int num_warps = (max_T + WARP_SIZE - 1) / WARP_SIZE; + + // each block is identified by 3 d tuple. + // we are using num_warp * max_U * B blocks + // where num_warp is division among Time axis + dim3 block_dims(num_warps, max_U, B * H); + + // each thread is identified by a 2 d tuple + // 2nd dim is 1 for betas only + dim3 thread_dims(WARP_SIZE, 1); + + ComputeBetasWrapper + <<>>( + /*max_src_len=*/max_T, + /*max_tgt_len=*/max_U, + /*num_targets=*/D, + /*blank=*/blank, + /*log_probs=*/workspace.GetPointerToLogProbs(), + /*srcLengths=*/srcLengths, + /*tgtLengths=*/tgtLengths, + /*alpha_counters=*/workspace.GetPointerToBetaCounters(), + /*alphas=*/(volatile DTYPE*)betas, + costs, + H); + + if (cudaGetLastError() != cudaSuccess) { + return COMPUTE_ALPHAS_BETAS_COSTS_FAILED; + } + } + + return SUCCESS; +} + +} // namespace gpu +} // namespace rnnt +} // namespace torchaudio + +#endif // USE_CUDA diff --git a/torchaudio/csrc/rnnt/gpu/half.cuh b/torchaudio/csrc/rnnt/gpu/half.cuh new file mode 100644 index 00000000..72a2f37e --- /dev/null +++ b/torchaudio/csrc/rnnt/gpu/half.cuh @@ -0,0 +1,38 @@ +#pragma once + +#ifdef USE_C10_HALF +#include "c10/util/Half.h" +#endif // USE_C10_HALF + +#include + +namespace torchaudio { +namespace rnnt { + +struct alignas(sizeof(__half)) Half { + __half x; + + HOST_AND_DEVICE Half() = default; + + FORCE_INLINE HOST_AND_DEVICE Half(float f) { + x = __float2half_rn(f); + if (isinf(__half2float(x))) { + x = __float2half_rz(f); // round toward 0. + } + } + + FORCE_INLINE HOST_AND_DEVICE operator float() const { + return __half2float(x); + } + + FORCE_INLINE HOST_AND_DEVICE Half(__half f) { + x = f; + } + + FORCE_INLINE HOST_AND_DEVICE operator __half() const { + return x; + } +}; + +} // namespace rnnt +} // namespace torchaudio diff --git a/torchaudio/csrc/rnnt/gpu/kernel_utils.h b/torchaudio/csrc/rnnt/gpu/kernel_utils.h new file mode 100644 index 00000000..3b2989b0 --- /dev/null +++ b/torchaudio/csrc/rnnt/gpu/kernel_utils.h @@ -0,0 +1,66 @@ +#pragma once + +#include + +#include + +namespace torchaudio { +namespace rnnt { + +inline HOST_AND_DEVICE bool in_range( + int start, + int end, // inclusive + int val) { + return start <= val && val <= end; +} + +#define LOG_PROBS_SKIP_IDX 0 +#define LOG_PROBS_EMIT_IDX 1 + +struct Indexer2D { + const int& size2_; + + FORCE_INLINE HOST_AND_DEVICE Indexer2D(const int& size2) : size2_(size2) {} + + FORCE_INLINE HOST_AND_DEVICE int operator()(int index1, int index2) { + return index1 * size2_ + index2; + } +}; + +struct Indexer3D { + const int& size2_; + const int& size3_; + + FORCE_INLINE HOST_AND_DEVICE Indexer3D(const int& size2, const int& size3) + : size2_(size2), size3_(size3) {} + + FORCE_INLINE HOST_AND_DEVICE int operator()( + int index1, + int index2, + int index3) { + return (index1 * size2_ + index2) * size3_ + index3; + } +}; + +struct Indexer4D { + const int& size2_; + const int& size3_; + const int& size4_; + + HOST_AND_DEVICE Indexer4D( + const int& size2, + const int& size3, + const int& size4) + : size2_(size2), size3_(size3), size4_(size4) {} + + HOST_AND_DEVICE int operator()( + int index1, + int index2, + int index3, + int index4) { + return ((index1 * size2_ + index2) * size3_ + index3) * size4_ + index4; + } +}; + +} // namespace rnnt +} // namespace torchaudio diff --git a/torchaudio/csrc/rnnt/gpu/kernels.h b/torchaudio/csrc/rnnt/gpu/kernels.h new file mode 100644 index 00000000..b0627c21 --- /dev/null +++ b/torchaudio/csrc/rnnt/gpu/kernels.h @@ -0,0 +1,108 @@ +#pragma once + +#include + +#include +#include + +namespace torchaudio { +namespace rnnt { + +template +HOST_AND_DEVICE void ComputeGradientsElement( + int bTgt, + int t, + int u, + int maxSrcLen, + int maxTgtLen, + int numTargets, + int blank, + CAST_DTYPE clamp, + const DTYPE* logits, + const int* targets, + const int* srcLengths, + const int* tgtLengths, + const CAST_DTYPE* denominators, + const CAST_DTYPE* alphas, + const CAST_DTYPE* betas, + DTYPE* gradients, + int H = 1) { + const int& maxT = maxSrcLen; + const int& maxU = maxTgtLen; + const int& D = numTargets; + + const int bSrc = bTgt / H; + const int T = srcLengths[bSrc]; + const int U = tgtLengths[bTgt] + 1; + + if (t >= T || u >= U) { // out of boundary. + if (gradients == logits && t < maxT && u < maxU) { + // gradients and logits are pointing to the same memory location + Indexer3D idxr3(maxT, maxU); + int idx_b_t_u_zero = idxr3(bTgt, t, u); + if (idx_b_t_u_zero != -1) { + int start = idx_b_t_u_zero * D; + for (int b_t_u_d = start; b_t_u_d < start + D; ++b_t_u_d) { + gradients[b_t_u_d] = 0; + } + } + } + return; + } + + int costIdx = bTgt * maxT * maxU; + CAST_DTYPE cost = -(betas[costIdx]); + + Indexer2D idxr2(maxU - 1); + + int idx_b_t_u, idx_b_t_up1, idx_b_tp1_u; + Indexer3D idxr3(maxT, maxU); + idx_b_t_u = idxr3(bTgt, t, u); + idx_b_t_up1 = idxr3(bTgt, t, u + 1); + idx_b_tp1_u = idxr3(bTgt, t + 1, u); + + if (idx_b_t_u == -1) { + return; + } + + if (isinf(cost) || isnan(cost)) { + for (int d = 0; d < D; ++d) { + int b_t_u_d = idx_b_t_u * D + d; + gradients[b_t_u_d] = 0; + } + return; + } + + CAST_DTYPE c = alphas[idx_b_t_u] + cost - denominators[idx_b_t_u]; + for (int d = 0; d < D; ++d) { + int b_t_u_d = idx_b_t_u * D + d; + CAST_DTYPE g = CAST_DTYPE(logits[b_t_u_d]) + c; + + if (d == blank && t == T - 1 && u == U - 1) { // last blank transition. + gradients[b_t_u_d] = std::exp(g + betas[idx_b_t_u]) - std::exp(g); + } else if (t < T - 1 && d == blank) { + gradients[b_t_u_d] = std::exp(g + betas[idx_b_t_u]); + if (idx_b_tp1_u != -1) { + gradients[b_t_u_d] = + gradients[b_t_u_d] - std::exp(g + betas[idx_b_tp1_u]); + } + } else if (u < U - 1 && d == targets[idxr2(bTgt, u)]) { + gradients[b_t_u_d] = std::exp(g + betas[idx_b_t_u]); + if (idx_b_t_up1 != -1) { + gradients[b_t_u_d] = + gradients[b_t_u_d] - std::exp(g + betas[idx_b_t_up1]); + } + } else { + gradients[b_t_u_d] = std::exp(g + betas[idx_b_t_u]); + } + + if (clamp > 0) { + auto g = CAST_DTYPE(gradients[b_t_u_d]); + gradients[b_t_u_d] = math::min(g, clamp); + gradients[b_t_u_d] = math::max(g, -clamp); + } + } +} + +} // namespace rnnt +} // namespace torchaudio diff --git a/torchaudio/csrc/rnnt/gpu/math.cuh b/torchaudio/csrc/rnnt/gpu/math.cuh new file mode 100644 index 00000000..643fa983 --- /dev/null +++ b/torchaudio/csrc/rnnt/gpu/math.cuh @@ -0,0 +1,48 @@ +#pragma once + +#ifdef USE_CUDA + +#include + +#endif // USE_CUDA + +#include + +namespace torchaudio { +namespace rnnt { + +namespace math { + +template +FORCE_INLINE HOST_AND_DEVICE DTYPE max(DTYPE x, DTYPE y) { + if (x > y) + return x; + else + return y; +} + +template +FORCE_INLINE HOST_AND_DEVICE DTYPE min(DTYPE x, DTYPE y) { + if (x > y) + return y; + else + return x; +} + +// log_sum_exp +template +FORCE_INLINE HOST_AND_DEVICE DTYPE lse(DTYPE x, DTYPE y); + +template <> +FORCE_INLINE HOST_AND_DEVICE float lse(float x, float y) { + if (y > x) { + return y + log1pf(expf(x - y)); + } else { + return x + log1pf(expf(y - x)); + } +} + +} // namespace math + +} // namespace rnnt +} // namespace torchaudio diff --git a/torchaudio/csrc/rnnt/macros.cpp b/torchaudio/csrc/rnnt/macros.cpp new file mode 100644 index 00000000..d2ea30a6 --- /dev/null +++ b/torchaudio/csrc/rnnt/macros.cpp @@ -0,0 +1,16 @@ +#include + +const char* ToString(level_t level) { + switch (level) { + case INFO: + return "INFO"; + case WARNING: + return "WARNING"; + case ERROR: + return "ERROR"; + case FATAL: + return "FATAL"; + default: + return "UNKNOWN"; + } +} diff --git a/torchaudio/csrc/rnnt/macros.h b/torchaudio/csrc/rnnt/macros.h new file mode 100644 index 00000000..abcbc399 --- /dev/null +++ b/torchaudio/csrc/rnnt/macros.h @@ -0,0 +1,21 @@ +#pragma once + +#ifdef USE_CUDA +#define WARP_SIZE 32 +#define MAX_THREADS_PER_BLOCK 1024 +#define REDUCE_THREADS 256 +#define HOST_AND_DEVICE __host__ __device__ +#define FORCE_INLINE __forceinline__ +#include +#include +#else +#define HOST_AND_DEVICE +#define FORCE_INLINE inline +#endif // USE_CUDA + +#include +#include + +typedef enum { INFO = 0, WARNING = 1, ERROR = 2, FATAL = 3 } level_t; + +const char* ToString(level_t level); diff --git a/torchaudio/csrc/rnnt/options.h b/torchaudio/csrc/rnnt/options.h new file mode 100644 index 00000000..79109950 --- /dev/null +++ b/torchaudio/csrc/rnnt/options.h @@ -0,0 +1,77 @@ +#pragma once + +//#include + +#ifdef USE_CUDA +#include +#endif // USE_CUDA + +#include +#include + +namespace torchaudio { +namespace rnnt { + +typedef struct Options { + // the device to compute transducer loss. + device_t device_; +#ifdef USE_CUDA + // the stream to launch kernels in when using GPU. + cudaStream_t stream_; +#endif + // The maximum number of threads that can be used. + int numThreads_; + + // the index for "blank". + int blank_; + // whether to backtrack the best path. + bool backtrack_; + // gradient clamp value. + float clamp_; + + // batch size = B. + int batchSize_; + + // Number of hypos per sample = H + int nHypos_; + + // the maximum length of src encodings = max_T. + int maxSrcLen_; + // the maximum length of tgt encodings = max_U. + int maxTgtLen_; + // num_targets = D. + int numTargets_; + + Options() + : device_(UNDEFINED), + numThreads_(0), + blank_(-1), + backtrack_(false), + clamp_(-1), // negative for disabling clamping by default. + batchSize_(0), + nHypos_(1), + maxSrcLen_(0), + maxTgtLen_(0), + numTargets_(0) {} + + int BU() const { + return batchSize_ * maxTgtLen_ * nHypos_; + } + + int BTU() const { + return batchSize_ * maxSrcLen_ * maxTgtLen_ * nHypos_; + } + + friend std::ostream& operator<<(std::ostream& os, const Options& options) { + os << "Options(" + << "batchSize_=" << options.batchSize_ << ", " + << "maxSrcLen_=" << options.maxSrcLen_ << ", " + << "maxTgtLen_=" << options.maxTgtLen_ << ", " + << "numTargets_=" << options.numTargets_ << ")"; + + return os; + } +} Options; + +} // namespace rnnt +} // namespace torchaudio diff --git a/torchaudio/csrc/rnnt/types.cpp b/torchaudio/csrc/rnnt/types.cpp new file mode 100644 index 00000000..3c08a7ee --- /dev/null +++ b/torchaudio/csrc/rnnt/types.cpp @@ -0,0 +1,41 @@ +#include + +namespace torchaudio { +namespace rnnt { + +const char* toString(status_t status) { + switch (status) { + case SUCCESS: + return "success"; + case FAILURE: + return "failure"; + case COMPUTE_DENOMINATOR_REDUCE_MAX_FAILED: + return "compute_denominator_reduce_max_failed"; + case COMPUTE_DENOMINATOR_REDUCE_SUM_FAILED: + return "compute_denominator_reduce_sum_failed"; + case COMPUTE_LOG_PROBS_FAILED: + return "compute_log_probs_failed"; + case COMPUTE_ALPHAS_BETAS_COSTS_FAILED: + return "compute_alphas_betas_costs_failed"; + case COMPUTE_GRADIENTS_FAILED: + return "compute_gradients_failed"; + default: + return "unknown"; + } +} + +const char* toString(device_t device) { + switch (device) { + case UNDEFINED: + return "undefined"; + case CPU: + return "cpu"; + case GPU: + return "gpu"; + default: + return "unknown"; + } +} + +} // namespace rnnt +} // namespace torchaudio diff --git a/torchaudio/csrc/rnnt/types.h b/torchaudio/csrc/rnnt/types.h new file mode 100644 index 00000000..34d2998c --- /dev/null +++ b/torchaudio/csrc/rnnt/types.h @@ -0,0 +1,23 @@ +#pragma once + +namespace torchaudio { +namespace rnnt { + +typedef enum { + SUCCESS = 0, + FAILURE = 1, + COMPUTE_DENOMINATOR_REDUCE_MAX_FAILED = 2, + COMPUTE_DENOMINATOR_REDUCE_SUM_FAILED = 3, + COMPUTE_LOG_PROBS_FAILED = 4, + COMPUTE_ALPHAS_BETAS_COSTS_FAILED = 5, + COMPUTE_GRADIENTS_FAILED = 6 +} status_t; + +typedef enum { UNDEFINED = 0, CPU = 1, GPU = 2 } device_t; + +const char* toString(status_t status); + +const char* toString(device_t device); + +} // namespace rnnt +} // namespace torchaudio diff --git a/torchaudio/csrc/rnnt/workspace.h b/torchaudio/csrc/rnnt/workspace.h new file mode 100644 index 00000000..31b57647 --- /dev/null +++ b/torchaudio/csrc/rnnt/workspace.h @@ -0,0 +1,223 @@ +#pragma once + +#include +#include + +#include + +namespace torchaudio { +namespace rnnt { + +// Since CUDA has strict memory alignment, it's better to keep allocated memory +// blocks separate for different data types. + +// DtypeWorkspace holds a "view" of workspace for: +// 1. softmax denominators (in log form), size = B * max_T * max_U +// 2. log probibility pairs for blank and target, size = B * max_T * max_U +// 3. alphas, size = B * max_T * max_U +// 4. betas, size = B * max_T * max_U +template +class DtypeWorkspace { + public: + DtypeWorkspace() : options_(), size_(0), data_(nullptr) {} + DtypeWorkspace(const Options& options, DTYPE* data, int size) + : DtypeWorkspace() { + Reset(options, data, size); + } + ~DtypeWorkspace() {} + + static int ComputeSizeFromOptions(const Options& options) { + CHECK_NE(options.device_, UNDEFINED); + return ComputeSizeForDenominators(options) + + ComputeSizeForLogProbs(options) + ComputeSizeForAlphas(options) + + ComputeSizeForBetas(options); + } + + void Free(); + void Reset(const Options& options, DTYPE* data, int size) { + int needed_size = ComputeSizeFromOptions(options); + CHECK_LE(needed_size, size); + options_ = options; + data_ = data; + size_ = size; + } + int Size() const { + return size_; + } + + DTYPE* GetPointerToDenominators() const { + return data_; + } + DTYPE* GetPointerToLogProbs() const { + return GetPointerToDenominators() + ComputeSizeForDenominators(options_); + } + DTYPE* GetPointerToAlphas() const { + return GetPointerToLogProbs() + ComputeSizeForLogProbs(options_); + } + DTYPE* GetPointerToBetas() const { + return GetPointerToAlphas() + ComputeSizeForAlphas(options_); + } + + private: + static int ComputeSizeForDenominators(const Options& options) { // B * T * U + return options.BTU(); + } + + static int ComputeSizeForLogProbs(const Options& options) { // B * T * U * 2 + return options.BTU() * 2; + } + + static int ComputeSizeForAlphas(const Options& options) { // B * T * U + return options.BTU(); + } + + static int ComputeSizeForBetas(const Options& options) { // B * T * U + return options.BTU(); + } + + Options options_; + int size_; // number of elements in allocated memory. + DTYPE* data_; // pointer to the allocated memory. +}; + +// IntWorkspace holds a "view" of workspace for: +// 1. alpha counters, size = B * max_U +// 2. beta counters, size = B * max_U +class IntWorkspace { + public: + IntWorkspace() : options_(), size_(0), data_(nullptr) {} + IntWorkspace(const Options& options, int* data, int size) : IntWorkspace() { + Reset(options, data, size); + } + ~IntWorkspace() {} + + static int ComputeSizeFromOptions(const Options& options) { + return ComputeSizeForAlphaCounters(options) + + ComputeSizeForBetaCounters(options); + } + + void Reset(const Options& options, int* data, int size) { + int needed_size = ComputeSizeFromOptions(options); + CHECK_LE(needed_size, size); + options_ = options; + data_ = data; + size_ = size; + ResetAlphaBetaCounters(); + } + int Size() const { + return size_; + } + + int* GetPointerToAlphaCounters() const { + CHECK_EQ(options_.device_, GPU); + return data_; + } + int* GetPointerToBetaCounters() const { + CHECK_EQ(options_.device_, GPU); + return GetPointerToAlphaCounters() + ComputeSizeForAlphaCounters(options_); + } + + private: + inline void ResetAlphaBetaCounters() { +#ifdef USE_CUDA + if (data_ != nullptr && options_.device_ == GPU) { + cudaMemset( + GetPointerToAlphaCounters(), + 0, + ComputeSizeForAlphaCounters(options_) * sizeof(int)); + cudaMemset( + GetPointerToBetaCounters(), + 0, + ComputeSizeForBetaCounters(options_) * sizeof(int)); + } +#endif // USE_CUDA + } + + static int ComputeSizeForAlphaCounters(const Options& options) { // B * U +#ifdef USE_CUDA + if (options.device_ == GPU) { + return options.BU(); + } else { + return 0; + } +#else + return 0; +#endif // USE_CUDA + } + static int ComputeSizeForBetaCounters(const Options& options) { // B * U +#ifdef USE_CUDA + if (options.device_ == GPU) { + return options.BU(); + } else { + return 0; + } +#else + return 0; +#endif // USE_CUDA + } + + Options options_; + int size_; // number of elements in allocated memory. + int* data_; // pointer to the allocated memory. +}; + +// Workspace holds: +// 1. DtypeWorkspace +// 2. IntWorkspace +template +class Workspace { + public: + Workspace() : options_(), dtype_workspace_(), int_workspace_() {} + Workspace( + const Options& options, + DTYPE* dtype_data, + int dtype_size, + int* int_data, + int int_size) + : Workspace() { + Reset(options, dtype_data, dtype_size, int_data, int_size); + } + ~Workspace() {} + + void Reset( + const Options& options, + DTYPE* dtype_data, + int dtype_size, + int* int_data, + int int_size) { + options_ = options; + dtype_workspace_.Reset(options_, dtype_data, dtype_size); + int_workspace_.Reset(options_, int_data, int_size); + } + + const Options& GetOptions() const { + return options_; + } + + DTYPE* GetPointerToDenominators() const { + return dtype_workspace_.GetPointerToDenominators(); + } + DTYPE* GetPointerToLogProbs() const { + return dtype_workspace_.GetPointerToLogProbs(); + } + DTYPE* GetPointerToAlphas() const { + return dtype_workspace_.GetPointerToAlphas(); + } + DTYPE* GetPointerToBetas() const { + return dtype_workspace_.GetPointerToBetas(); + } + int* GetPointerToAlphaCounters() const { + return int_workspace_.GetPointerToAlphaCounters(); + } + int* GetPointerToBetaCounters() const { + return int_workspace_.GetPointerToBetaCounters(); + } + + private: + Options options_; + DtypeWorkspace dtype_workspace_; + IntWorkspace int_workspace_; +}; + +} // namespace rnnt +} // namespace torchaudio diff --git a/torchaudio/csrc/sox/effects.cpp b/torchaudio/csrc/sox/effects.cpp new file mode 100644 index 00000000..aaa61f92 --- /dev/null +++ b/torchaudio/csrc/sox/effects.cpp @@ -0,0 +1,155 @@ +#include +#include +#include +#include + +using namespace torchaudio::sox_utils; + +namespace torchaudio::sox_effects { + +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( + torch::Tensor 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(); + torchaudio::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.numel()); + + // 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, + c10::optional normalize, + c10::optional channels_first, + const c10::optional& format) + -> std::tuple { + // Open input file + SoxFormat sf(sox_open_read( + path.c_str(), + /*signal=*/nullptr, + /*encoding=*/nullptr, + /*filetype=*/format.has_value() ? format.value().c_str() : nullptr)); + + validate_input_file(sf, path); + + 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 + torchaudio::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()); +} + +TORCH_LIBRARY_FRAGMENT(torchaudio, m) { + m.def( + "torchaudio::sox_effects_initialize_sox_effects", + &torchaudio::sox_effects::initialize_sox_effects); + m.def( + "torchaudio::sox_effects_shutdown_sox_effects", + &torchaudio::sox_effects::shutdown_sox_effects); + m.def( + "torchaudio::sox_effects_apply_effects_tensor", + &torchaudio::sox_effects::apply_effects_tensor); + m.def( + "torchaudio::sox_effects_apply_effects_file", + &torchaudio::sox_effects::apply_effects_file); +} + +} // namespace torchaudio::sox_effects diff --git a/torchaudio/csrc/sox/effects.h b/torchaudio/csrc/sox/effects.h new file mode 100644 index 00000000..71c0c778 --- /dev/null +++ b/torchaudio/csrc/sox/effects.h @@ -0,0 +1,29 @@ +#ifndef TORCHAUDIO_SOX_EFFECTS_H +#define TORCHAUDIO_SOX_EFFECTS_H + +#include +#include + +namespace torchaudio::sox_effects { + +void initialize_sox_effects(); + +void shutdown_sox_effects(); + +auto apply_effects_tensor( + torch::Tensor 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, + c10::optional normalize, + c10::optional channels_first, + const c10::optional& format) + -> std::tuple; + +} // namespace torchaudio::sox_effects + +#endif diff --git a/torchaudio/csrc/sox/effects_chain.cpp b/torchaudio/csrc/sox/effects_chain.cpp new file mode 100644 index 00000000..b141e8c8 --- /dev/null +++ b/torchaudio/csrc/sox/effects_chain.cpp @@ -0,0 +1,323 @@ +#include +#include + +using namespace torch::indexing; +using namespace torchaudio::sox_utils; + +namespace torchaudio { +namespace 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; + torch::Tensor* 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.numel(); + 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 + auto chunk = [&]() { + auto i_frame = index / num_channels; + auto num_frames = *osamp / num_channels; + auto t = (priv->channels_first) + ? tensor.index({Slice(), Slice(i_frame, i_frame + num_frames)}).t() + : tensor.index({Slice(i_frame, i_frame + num_frames), Slice()}); + return t.reshape({-1}); + }(); + + // Convert to sox_sample_t (int32_t) + switch (chunk.dtype().toScalarType()) { + case c10::ScalarType::Float: { + // Need to convert to 64-bit precision so that + // values around INT32_MIN/MAX are handled correctly. + chunk = chunk.to(c10::ScalarType::Double); + chunk *= 2147483648.; + chunk.clamp_(INT32_MIN, INT32_MAX); + chunk = chunk.to(c10::ScalarType::Int); + break; + } + case c10::ScalarType::Int: { + break; + } + case c10::ScalarType::Short: { + chunk = chunk.to(c10::ScalarType::Int); + chunk *= 65536; + break; + } + case c10::ScalarType::Byte: { + chunk = chunk.to(c10::ScalarType::Int); + chunk -= 128; + chunk *= 16777216; + break; + } + default: + throw std::runtime_error("Unexpected dtype."); + } + // Write to buffer + chunk = chunk.contiguous(); + memcpy(obuf, chunk.data_ptr(), *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( + torch::Tensor* 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 sox_effects_chain +} // namespace torchaudio diff --git a/torchaudio/csrc/sox/effects_chain.h b/torchaudio/csrc/sox/effects_chain.h new file mode 100644 index 00000000..c456276e --- /dev/null +++ b/torchaudio/csrc/sox/effects_chain.h @@ -0,0 +1,63 @@ +#ifndef TORCHAUDIO_SOX_EFFECTS_CHAIN_H +#define TORCHAUDIO_SOX_EFFECTS_CHAIN_H + +#include +#include + +namespace torchaudio { +namespace 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( + torch::Tensor* 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(); +}; + +} // namespace sox_effects_chain +} // namespace torchaudio + +#endif diff --git a/torchaudio/csrc/sox/io.cpp b/torchaudio/csrc/sox/io.cpp new file mode 100644 index 00000000..f86f121a --- /dev/null +++ b/torchaudio/csrc/sox/io.cpp @@ -0,0 +1,143 @@ +#include +#include +#include +#include +#include + +using namespace torch::indexing; +using namespace torchaudio::sox_utils; + +namespace torchaudio { +namespace sox_io { + +std::tuple get_info_file( + const std::string& path, + const c10::optional& format) { + SoxFormat sf(sox_open_read( + path.c_str(), + /*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 c10::optional& frame_offset, + const c10::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; +} + +std::tuple load_audio_file( + const std::string& path, + const c10::optional& frame_offset, + const c10::optional& num_frames, + c10::optional normalize, + c10::optional channels_first, + const c10::optional& format) { + auto effects = get_effects(frame_offset, num_frames); + return torchaudio::sox_effects::apply_effects_file( + path, effects, normalize, channels_first, format); +} + +void save_audio_file( + const std::string& path, + torch::Tensor tensor, + int64_t sample_rate, + bool channels_first, + c10::optional compression, + c10::optional format, + c10::optional encoding, + c10::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.size(channels_first ? 0 : 1); + TORCH_CHECK( + num_channels == 1, "amr-nb format only supports single channel audio."); + } else if (filetype == "htk") { + const auto num_channels = tensor.size(channels_first ? 0 : 1); + TORCH_CHECK( + num_channels == 1, "htk format only supports single channel audio."); + } else if (filetype == "gsm") { + const auto num_channels = tensor.size(channels_first ? 0 : 1); + 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); + } + + torchaudio::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(); +} + +TORCH_LIBRARY_FRAGMENT(torchaudio, m) { + m.def("torchaudio::sox_io_get_info", &torchaudio::sox_io::get_info_file); + m.def( + "torchaudio::sox_io_load_audio_file", + &torchaudio::sox_io::load_audio_file); + m.def( + "torchaudio::sox_io_save_audio_file", + &torchaudio::sox_io::save_audio_file); +} + +} // namespace sox_io +} // namespace torchaudio diff --git a/torchaudio/csrc/sox/io.h b/torchaudio/csrc/sox/io.h new file mode 100644 index 00000000..e6c8cffb --- /dev/null +++ b/torchaudio/csrc/sox/io.h @@ -0,0 +1,40 @@ +#ifndef TORCHAUDIO_SOX_IO_H +#define TORCHAUDIO_SOX_IO_H + +#include +#include + +namespace torchaudio { +namespace sox_io { + +auto get_effects( + const c10::optional& frame_offset, + const c10::optional& num_frames) + -> std::vector>; + +std::tuple get_info_file( + const std::string& path, + const c10::optional& format); + +std::tuple load_audio_file( + const std::string& path, + const c10::optional& frame_offset, + const c10::optional& num_frames, + c10::optional normalize, + c10::optional channels_first, + const c10::optional& format); + +void save_audio_file( + const std::string& path, + torch::Tensor tensor, + int64_t sample_rate, + bool channels_first, + c10::optional compression, + c10::optional format, + c10::optional encoding, + c10::optional bits_per_sample); + +} // namespace sox_io +} // namespace torchaudio + +#endif diff --git a/torchaudio/csrc/sox/types.cpp b/torchaudio/csrc/sox/types.cpp new file mode 100644 index 00000000..b4bbf710 --- /dev/null +++ b/torchaudio/csrc/sox/types.cpp @@ -0,0 +1,139 @@ +#include + +namespace torchaudio { +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 c10::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 c10::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 torchaudio diff --git a/torchaudio/csrc/sox/types.h b/torchaudio/csrc/sox/types.h new file mode 100644 index 00000000..afd84791 --- /dev/null +++ b/torchaudio/csrc/sox/types.h @@ -0,0 +1,60 @@ +#ifndef TORCHAUDIO_SOX_TYPES_H +#define TORCHAUDIO_SOX_TYPES_H + +#include +#include + +namespace torchaudio { +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 c10::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 c10::optional bit_depth); + +std::string get_encoding(sox_encoding_t encoding); + +} // namespace sox_utils +} // namespace torchaudio + +#endif diff --git a/torchaudio/csrc/sox/utils.cpp b/torchaudio/csrc/sox/utils.cpp new file mode 100644 index 00000000..2208ed20 --- /dev/null +++ b/torchaudio/csrc/sox/utils.cpp @@ -0,0 +1,522 @@ +#include +#include +#include +#include + +namespace torchaudio { +namespace sox_utils { + +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 torch::Tensor tensor) { + if (!tensor.device().is_cpu()) { + throw std::runtime_error("Input tensor has to be on CPU."); + } + + if (tensor.ndimension() != 2) { + throw std::runtime_error("Input tensor has to be 2D."); + } + + switch (tensor.dtype().toScalarType()) { + case c10::ScalarType::Byte: + case c10::ScalarType::Short: + case c10::ScalarType::Int: + case c10::ScalarType::Float: + break; + default: + throw std::runtime_error( + "Input tensor has to be one of float32, int32, int16 or uint8 type."); + } +} + +caffe2::TypeMeta get_dtype( + const sox_encoding_t encoding, + const unsigned precision) { + const auto dtype = [&]() { + switch (encoding) { + case SOX_ENCODING_UNSIGNED: // 8-bit PCM WAV + return torch::kUInt8; + case SOX_ENCODING_SIGN2: // 16-bit, 24-bit, or 32-bit PCM WAV + switch (precision) { + case 16: + return torch::kInt16; + case 24: // Cast 24-bit to 32-bit. + case 32: + return torch::kInt32; + 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 torch::kFloat32; + } + }(); + return c10::scalarTypeToTypeMeta(dtype); +} + +torch::Tensor convert_to_tensor( + sox_sample_t* buffer, + const int32_t num_samples, + const int32_t num_channels, + const caffe2::TypeMeta dtype, + const bool normalize, + const bool channels_first) { + torch::Tensor t; + uint64_t dummy = 0; + SOX_SAMPLE_LOCALS; + if (normalize || dtype == torch::kFloat32) { + t = torch::empty( + {num_samples / num_channels, num_channels}, torch::kFloat32); + auto ptr = t.data_ptr(); + for (int32_t i = 0; i < num_samples; ++i) { + ptr[i] = SOX_SAMPLE_TO_FLOAT_32BIT(buffer[i], dummy); + } + } else if (dtype == torch::kInt32) { + t = torch::from_blob( + buffer, {num_samples / num_channels, num_channels}, torch::kInt32) + .clone(); + } else if (dtype == torch::kInt16) { + t = torch::empty({num_samples / num_channels, num_channels}, torch::kInt16); + auto ptr = t.data_ptr(); + for (int32_t i = 0; i < num_samples; ++i) { + ptr[i] = SOX_SAMPLE_TO_SIGNED_16BIT(buffer[i], dummy); + } + } else if (dtype == torch::kUInt8) { + t = torch::empty({num_samples / num_channels, num_channels}, torch::kUInt8); + auto ptr = t.data_ptr(); + for (int32_t i = 0; i < num_samples; ++i) { + ptr[i] = SOX_SAMPLE_TO_UNSIGNED_8BIT(buffer[i], dummy); + } + } else { + throw std::runtime_error("Unsupported dtype."); + } + if (channels_first) { + t = t.transpose(1, 0); + } + return t.contiguous(); +} + +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, + caffe2::TypeMeta 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.toScalarType()) { + case c10::ScalarType::Float: + return std::make_tuple<>(SOX_ENCODING_FLOAT, 32); + case c10::ScalarType::Int: + return std::make_tuple<>(SOX_ENCODING_SIGN2, 32); + case c10::ScalarType::Short: + return std::make_tuple<>(SOX_ENCODING_SIGN2, 16); + case c10::ScalarType::Byte: + 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 caffe2::TypeMeta dtype, + const c10::optional encoding, + const c10::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, caffe2::TypeMeta 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.toScalarType()) { + case c10::ScalarType::Byte: + return 8; + case c10::ScalarType::Short: + return 16; + case c10::ScalarType::Int: + return 32; + case c10::ScalarType::Float: + 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 torch::Tensor* 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->size(channels_first ? 0 : 1)), + /*precision=*/get_precision(filetype, waveform->dtype()), + /*length=*/static_cast(waveform->numel())}; +} + +sox_encodinginfo_t get_tensor_encodinginfo(caffe2::TypeMeta dtype) { + sox_encoding_t encoding = [&]() { + switch (dtype.toScalarType()) { + case c10::ScalarType::Byte: + return SOX_ENCODING_UNSIGNED; + case c10::ScalarType::Short: + return SOX_ENCODING_SIGN2; + case c10::ScalarType::Int: + return SOX_ENCODING_SIGN2; + case c10::ScalarType::Float: + return SOX_ENCODING_FLOAT; + default: + throw std::runtime_error("Unsupported dtype."); + } + }(); + unsigned bits_per_sample = [&]() { + switch (dtype.toScalarType()) { + case c10::ScalarType::Byte: + return 8; + case c10::ScalarType::Short: + return 16; + case c10::ScalarType::Int: + return 32; + case c10::ScalarType::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 caffe2::TypeMeta dtype, + const c10::optional compression, + const c10::optional encoding, + const c10::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}; +} + +TORCH_LIBRARY_FRAGMENT(torchaudio, m) { + m.def("torchaudio::sox_utils_set_seed", &torchaudio::sox_utils::set_seed); + m.def( + "torchaudio::sox_utils_set_verbosity", + &torchaudio::sox_utils::set_verbosity); + m.def( + "torchaudio::sox_utils_set_use_threads", + &torchaudio::sox_utils::set_use_threads); + m.def( + "torchaudio::sox_utils_set_buffer_size", + &torchaudio::sox_utils::set_buffer_size); + m.def( + "torchaudio::sox_utils_list_effects", + &torchaudio::sox_utils::list_effects); + m.def( + "torchaudio::sox_utils_list_read_formats", + &torchaudio::sox_utils::list_read_formats); + m.def( + "torchaudio::sox_utils_list_write_formats", + &torchaudio::sox_utils::list_write_formats); + m.def( + "torchaudio::sox_utils_get_buffer_size", + &torchaudio::sox_utils::get_buffer_size); +} + +} // namespace sox_utils +} // namespace torchaudio diff --git a/torchaudio/csrc/sox/utils.h b/torchaudio/csrc/sox/utils.h new file mode 100644 index 00000000..73b76e71 --- /dev/null +++ b/torchaudio/csrc/sox/utils.h @@ -0,0 +1,118 @@ +#ifndef TORCHAUDIO_SOX_UTILS_H +#define TORCHAUDIO_SOX_UTILS_H + +#include +#include + +namespace torchaudio { +namespace sox_utils { + +//////////////////////////////////////////////////////////////////////////////// +// APIs for Python interaction +//////////////////////////////////////////////////////////////////////////////// + +/// Set sox global options +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 file is found, has known encoding, and not empty +void validate_input_file(const SoxFormat& sf, const std::string& path); + +/// Verify that input memory buffer has known encoding, and not empty +void validate_input_memfile(const SoxFormat& sf); + +/// +/// Verify that input Tensor is 2D, CPU and either uin8, int16, int32 or float32 +void validate_input_tensor(const torch::Tensor); + +/// +/// Get target dtype for the given encoding and precision. +caffe2::TypeMeta 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]. +torch::Tensor convert_to_tensor( + sox_sample_t* buffer, + const int32_t num_samples, + const int32_t num_channels, + const caffe2::TypeMeta 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 torch::Tensor object. +sox_signalinfo_t get_signalinfo( + const torch::Tensor* 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 caffe2::TypeMeta dtype); + +/// Get sox_encodinginfo_t for saving to file/file object +sox_encodinginfo_t get_encodinginfo_for_save( + const std::string& format, + const caffe2::TypeMeta dtype, + const c10::optional compression, + const c10::optional encoding, + const c10::optional bits_per_sample); + +} // namespace sox_utils +} // namespace torchaudio +#endif diff --git a/torchaudio/csrc/utils.cpp b/torchaudio/csrc/utils.cpp new file mode 100644 index 00000000..e8050bb6 --- /dev/null +++ b/torchaudio/csrc/utils.cpp @@ -0,0 +1,30 @@ +#include + +namespace torchaudio { + +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 +} + +} // namespace + +TORCH_LIBRARY_FRAGMENT(torchaudio, m) { + m.def("torchaudio::is_sox_available", &is_sox_available); + m.def("torchaudio::is_kaldi_available", &is_kaldi_available); +} + +} // namespace torchaudio diff --git a/torchaudio/datasets/__init__.py b/torchaudio/datasets/__init__.py new file mode 100644 index 00000000..2065444c --- /dev/null +++ b/torchaudio/datasets/__init__.py @@ -0,0 +1,32 @@ +from .commonvoice import COMMONVOICE +from .librispeech import LIBRISPEECH +from .speechcommands import SPEECHCOMMANDS +from .utils import bg_iterator, diskcache_iterator +from .vctk import VCTK, VCTK_092 +from .gtzan import GTZAN +from .yesno import YESNO +from .ljspeech import LJSPEECH +from .cmuarctic import CMUARCTIC +from .cmudict import CMUDict +from .librimix import LibriMix +from .libritts import LIBRITTS +from .tedlium import TEDLIUM + + +__all__ = [ + "COMMONVOICE", + "LIBRISPEECH", + "SPEECHCOMMANDS", + "VCTK", + "VCTK_092", + "YESNO", + "LJSPEECH", + "GTZAN", + "CMUARCTIC", + "CMUDict", + "LibriMix", + "LIBRITTS", + "diskcache_iterator", + "bg_iterator", + "TEDLIUM", +] diff --git a/torchaudio/datasets/cmuarctic.py b/torchaudio/datasets/cmuarctic.py new file mode 100644 index 00000000..a0139919 --- /dev/null +++ b/torchaudio/datasets/cmuarctic.py @@ -0,0 +1,173 @@ +import os +import csv +from pathlib import Path +from typing import Tuple, Union + +import torchaudio +from torch import Tensor +from torch.utils.data import Dataset +from torchaudio.datasets.utils import ( + download_url, + extract_archive, +) + +URL = "aew" +FOLDER_IN_ARCHIVE = "ARCTIC" +_CHECKSUMS = { + "http://festvox.org/cmu_arctic/packed/cmu_us_aew_arctic.tar.bz2": + "4382b116efcc8339c37e01253cb56295", + "http://festvox.org/cmu_arctic/packed/cmu_us_ahw_arctic.tar.bz2": + "b072d6e961e3f36a2473042d097d6da9", + "http://festvox.org/cmu_arctic/packed/cmu_us_aup_arctic.tar.bz2": + "5301c7aee8919d2abd632e2667adfa7f", + "http://festvox.org/cmu_arctic/packed/cmu_us_awb_arctic.tar.bz2": + "280fdff1e9857119d9a2c57b50e12db7", + "http://festvox.org/cmu_arctic/packed/cmu_us_axb_arctic.tar.bz2": + "5e21cb26c6529c533df1d02ccde5a186", + "http://festvox.org/cmu_arctic/packed/cmu_us_bdl_arctic.tar.bz2": + "b2c3e558f656af2e0a65da0ac0c3377a", + "http://festvox.org/cmu_arctic/packed/cmu_us_clb_arctic.tar.bz2": + "3957c503748e3ce17a3b73c1b9861fb0", + "http://festvox.org/cmu_arctic/packed/cmu_us_eey_arctic.tar.bz2": + "59708e932d27664f9eda3e8e6859969b", + "http://festvox.org/cmu_arctic/packed/cmu_us_fem_arctic.tar.bz2": + "dba4f992ff023347c07c304bf72f4c73", + "http://festvox.org/cmu_arctic/packed/cmu_us_gka_arctic.tar.bz2": + "24a876ea7335c1b0ff21460e1241340f", + "http://festvox.org/cmu_arctic/packed/cmu_us_jmk_arctic.tar.bz2": + "afb69d95f02350537e8a28df5ab6004b", + "http://festvox.org/cmu_arctic/packed/cmu_us_ksp_arctic.tar.bz2": + "4ce5b3b91a0a54b6b685b1b05aa0b3be", + "http://festvox.org/cmu_arctic/packed/cmu_us_ljm_arctic.tar.bz2": + "6f45a3b2c86a4ed0465b353be291f77d", + "http://festvox.org/cmu_arctic/packed/cmu_us_lnh_arctic.tar.bz2": + "c6a15abad5c14d27f4ee856502f0232f", + "http://festvox.org/cmu_arctic/packed/cmu_us_rms_arctic.tar.bz2": + "71072c983df1e590d9e9519e2a621f6e", + "http://festvox.org/cmu_arctic/packed/cmu_us_rxr_arctic.tar.bz2": + "3771ff03a2f5b5c3b53aa0a68b9ad0d5", + "http://festvox.org/cmu_arctic/packed/cmu_us_slp_arctic.tar.bz2": + "9cbf984a832ea01b5058ba9a96862850", + "http://festvox.org/cmu_arctic/packed/cmu_us_slt_arctic.tar.bz2": + "959eecb2cbbc4ac304c6b92269380c81", +} + + +def load_cmuarctic_item(line: str, + path: str, + folder_audio: str, + ext_audio: str) -> Tuple[Tensor, int, str, str]: + + utterance_id, transcript = line[0].strip().split(" ", 2)[1:] + + # Remove space, double quote, and single parenthesis from transcript + transcript = transcript[1:-3] + + file_audio = os.path.join(path, folder_audio, utterance_id + ext_audio) + + # Load audio + waveform, sample_rate = torchaudio.load(file_audio) + + return ( + waveform, + sample_rate, + transcript, + utterance_id.split("_")[1] + ) + + +class CMUARCTIC(Dataset): + """Create a Dataset for CMU_ARCTIC. + + Args: + root (str or Path): Path to the directory where the dataset is found or downloaded. + url (str, optional): + The URL to download the dataset from or the type of the dataset to dowload. + (default: ``"aew"``) + Allowed type values are ``"aew"``, ``"ahw"``, ``"aup"``, ``"awb"``, ``"axb"``, ``"bdl"``, + ``"clb"``, ``"eey"``, ``"fem"``, ``"gka"``, ``"jmk"``, ``"ksp"``, ``"ljm"``, ``"lnh"``, + ``"rms"``, ``"rxr"``, ``"slp"`` or ``"slt"``. + folder_in_archive (str, optional): + The top-level directory of the dataset. (default: ``"ARCTIC"``) + download (bool, optional): + Whether to download the dataset if it is not found at root path. (default: ``False``). + """ + + _file_text = "txt.done.data" + _folder_text = "etc" + _ext_audio = ".wav" + _folder_audio = "wav" + + def __init__(self, + root: Union[str, Path], + url: str = URL, + folder_in_archive: str = FOLDER_IN_ARCHIVE, + download: bool = False) -> None: + + if url in [ + "aew", + "ahw", + "aup", + "awb", + "axb", + "bdl", + "clb", + "eey", + "fem", + "gka", + "jmk", + "ksp", + "ljm", + "lnh", + "rms", + "rxr", + "slp", + "slt" + ]: + + url = "cmu_us_" + url + "_arctic" + ext_archive = ".tar.bz2" + base_url = "http://www.festvox.org/cmu_arctic/packed/" + + url = os.path.join(base_url, url + ext_archive) + + # Get string representation of 'root' in case Path object is passed + root = os.fspath(root) + + basename = os.path.basename(url) + root = os.path.join(root, folder_in_archive) + if not os.path.isdir(root): + os.mkdir(root) + archive = os.path.join(root, basename) + + basename = basename.split(".")[0] + + self._path = os.path.join(root, basename) + + if download: + if not os.path.isdir(self._path): + if not os.path.isfile(archive): + checksum = _CHECKSUMS.get(url, None) + download_url(url, root, hash_value=checksum, hash_type="md5") + extract_archive(archive) + + self._text = os.path.join(self._path, self._folder_text, self._file_text) + + with open(self._text, "r") as text: + walker = csv.reader(text, delimiter="\n") + self._walker = list(walker) + + def __getitem__(self, n: int) -> Tuple[Tensor, int, str, str]: + """Load the n-th sample from the dataset. + + Args: + n (int): The index of the sample to be loaded + + Returns: + (Tensor, int, str, str): ``(waveform, sample_rate, transcript, utterance_id)`` + """ + line = self._walker[n] + return load_cmuarctic_item(line, self._path, self._folder_audio, self._ext_audio) + + def __len__(self) -> int: + return len(self._walker) diff --git a/torchaudio/datasets/cmudict.py b/torchaudio/datasets/cmudict.py new file mode 100644 index 00000000..2c6acccf --- /dev/null +++ b/torchaudio/datasets/cmudict.py @@ -0,0 +1,182 @@ +import os +import re +from pathlib import Path +from typing import Iterable, Tuple, Union, List + +from torch.utils.data import Dataset +from torchaudio.datasets.utils import download_url + +_CHECKSUMS = { + "http://svn.code.sf.net/p/cmusphinx/code/trunk/cmudict/cmudict-0.7b": + "825f4ebd9183f2417df9f067a9cabe86", + "http://svn.code.sf.net/p/cmusphinx/code/trunk/cmudict/cmudict-0.7b.symbols": + "385e490aabc71b48e772118e3d02923e", +} +_PUNCTUATIONS = set([ + "!EXCLAMATION-POINT", + "\"CLOSE-QUOTE", + "\"DOUBLE-QUOTE", + "\"END-OF-QUOTE", + "\"END-QUOTE", + "\"IN-QUOTES", + "\"QUOTE", + "\"UNQUOTE", + "#HASH-MARK", + "#POUND-SIGN", + "#SHARP-SIGN", + "%PERCENT", + "&ERSAND", + "'END-INNER-QUOTE", + "'END-QUOTE", + "'INNER-QUOTE", + "'QUOTE", + "'SINGLE-QUOTE", + "(BEGIN-PARENS", + "(IN-PARENTHESES", + "(LEFT-PAREN", + "(OPEN-PARENTHESES", + "(PAREN", + "(PARENS", + "(PARENTHESES", + ")CLOSE-PAREN", + ")CLOSE-PARENTHESES", + ")END-PAREN", + ")END-PARENS", + ")END-PARENTHESES", + ")END-THE-PAREN", + ")PAREN", + ")PARENS", + ")RIGHT-PAREN", + ")UN-PARENTHESES", + "+PLUS", + ",COMMA", + "--DASH", + "-DASH", + "-HYPHEN", + "...ELLIPSIS", + ".DECIMAL", + ".DOT", + ".FULL-STOP", + ".PERIOD", + ".POINT", + "/SLASH", + ":COLON", + ";SEMI-COLON", + ";SEMI-COLON(1)", + "?QUESTION-MARK", + "{BRACE", + "{LEFT-BRACE", + "{OPEN-BRACE", + "}CLOSE-BRACE", + "}RIGHT-BRACE", +]) + + +def _parse_dictionary(lines: Iterable[str], exclude_punctuations: bool) -> List[str]: + _alt_re = re.compile(r'\([0-9]+\)') + cmudict: List[Tuple[str, List[str]]] = list() + for line in lines: + if not line or line.startswith(';;;'): # ignore comments + continue + + word, phones = line.strip().split(' ') + if word in _PUNCTUATIONS: + if exclude_punctuations: + continue + # !EXCLAMATION-POINT -> ! + # --DASH -> -- + # ...ELLIPSIS -> ... + if word.startswith("..."): + word = "..." + elif word.startswith("--"): + word = "--" + else: + word = word[0] + + # if a word have multiple pronunciations, there will be (number) appended to it + # for example, DATAPOINTS and DATAPOINTS(1), + # the regular expression `_alt_re` removes the '(1)' and change the word DATAPOINTS(1) to DATAPOINTS + word = re.sub(_alt_re, '', word) + phones = phones.split(" ") + cmudict.append((word, phones)) + + return cmudict + + +class CMUDict(Dataset): + """Create a Dataset for CMU Pronouncing Dictionary (CMUDict). + + Args: + root (str or Path): Path to the directory where the dataset is found or downloaded. + exclude_punctuations (bool, optional): + When enabled, exclude the pronounciation of punctuations, such as + `!EXCLAMATION-POINT` and `#HASH-MARK`. + download (bool, optional): + Whether to download the dataset if it is not found at root path. (default: ``False``). + url (str, optional): + The URL to download the dictionary from. + (default: ``"http://svn.code.sf.net/p/cmusphinx/code/trunk/cmudict/cmudict-0.7b"``) + url_symbols (str, optional): + The URL to download the list of symbols from. + (default: ``"http://svn.code.sf.net/p/cmusphinx/code/trunk/cmudict/cmudict-0.7b.symbols"``) + """ + + def __init__(self, + root: Union[str, Path], + exclude_punctuations: bool = True, + *, + download: bool = False, + url: str = "http://svn.code.sf.net/p/cmusphinx/code/trunk/cmudict/cmudict-0.7b", + url_symbols: str = "http://svn.code.sf.net/p/cmusphinx/code/trunk/cmudict/cmudict-0.7b.symbols", + ) -> None: + + self.exclude_punctuations = exclude_punctuations + + self._root_path = Path(root) + if not os.path.isdir(self._root_path): + raise RuntimeError(f'The root directory does not exist; {root}') + + dict_file = self._root_path / os.path.basename(url) + symbol_file = self._root_path / os.path.basename(url_symbols) + if not os.path.exists(dict_file): + if not download: + raise RuntimeError( + 'The dictionary file is not found in the following location. ' + f'Set `download=True` to download it. {dict_file}') + checksum = _CHECKSUMS.get(url, None) + download_url(url, root, hash_value=checksum, hash_type="md5") + if not os.path.exists(symbol_file): + if not download: + raise RuntimeError( + 'The symbol file is not found in the following location. ' + f'Set `download=True` to download it. {symbol_file}') + checksum = _CHECKSUMS.get(url_symbols, None) + download_url(url_symbols, root, hash_value=checksum, hash_type="md5") + + with open(symbol_file, "r") as text: + self._symbols = [line.strip() for line in text.readlines()] + + with open(dict_file, "r", encoding='latin-1') as text: + self._dictionary = _parse_dictionary( + text.readlines(), exclude_punctuations=self.exclude_punctuations) + + def __getitem__(self, n: int) -> Tuple[str, List[str]]: + """Load the n-th sample from the dataset. + + Args: + n (int): The index of the sample to be loaded. + + Returns: + (str, List[str]): The corresponding word and phonemes ``(word, [phonemes])``. + + """ + return self._dictionary[n] + + def __len__(self) -> int: + return len(self._dictionary) + + @property + def symbols(self) -> List[str]: + """list[str]: A list of phonemes symbols, such as `AA`, `AE`, `AH`. + """ + return self._symbols.copy() diff --git a/torchaudio/datasets/commonvoice.py b/torchaudio/datasets/commonvoice.py new file mode 100644 index 00000000..20f9234f --- /dev/null +++ b/torchaudio/datasets/commonvoice.py @@ -0,0 +1,76 @@ +import csv +import os +from pathlib import Path +from typing import List, Dict, Tuple, Union + +from torch import Tensor +from torch.utils.data import Dataset + +import torchaudio + + +def load_commonvoice_item(line: List[str], + header: List[str], + path: str, + folder_audio: str, + ext_audio: str) -> Tuple[Tensor, int, Dict[str, str]]: + # Each line as the following data: + # client_id, path, sentence, up_votes, down_votes, age, gender, accent + + assert header[1] == "path" + fileid = line[1] + filename = os.path.join(path, folder_audio, fileid) + if not filename.endswith(ext_audio): + filename += ext_audio + waveform, sample_rate = torchaudio.load(filename) + + dic = dict(zip(header, line)) + + return waveform, sample_rate, dic + + +class COMMONVOICE(Dataset): + """Create a Dataset for CommonVoice. + + Args: + root (str or Path): Path to the directory where the dataset is located. + (Where the ``tsv`` file is present.) + tsv (str, optional): + The name of the tsv file used to construct the metadata, such as + ``"train.tsv"``, ``"test.tsv"``, ``"dev.tsv"``, ``"invalidated.tsv"``, + ``"validated.tsv"`` and ``"other.tsv"``. (default: ``"train.tsv"``) + """ + + _ext_txt = ".txt" + _ext_audio = ".mp3" + _folder_audio = "clips" + + def __init__(self, + root: Union[str, Path], + tsv: str = "train.tsv") -> None: + + # Get string representation of 'root' in case Path object is passed + self._path = os.fspath(root) + self._tsv = os.path.join(self._path, tsv) + + with open(self._tsv, "r") as tsv_: + walker = csv.reader(tsv_, delimiter="\t") + self._header = next(walker) + self._walker = list(walker) + + def __getitem__(self, n: int) -> Tuple[Tensor, int, Dict[str, str]]: + """Load the n-th sample from the dataset. + + Args: + n (int): The index of the sample to be loaded + + Returns: + (Tensor, int, Dict[str, str]): ``(waveform, sample_rate, dictionary)``, where dictionary + is built from the TSV file with the following keys: ``client_id``, ``path``, ``sentence``, + ``up_votes``, ``down_votes``, ``age``, ``gender`` and ``accent``. + """ + line = self._walker[n] + return load_commonvoice_item(line, self._header, self._path, self._folder_audio, self._ext_audio) + + def __len__(self) -> int: + return len(self._walker) diff --git a/torchaudio/datasets/gtzan.py b/torchaudio/datasets/gtzan.py new file mode 100644 index 00000000..b78104ca --- /dev/null +++ b/torchaudio/datasets/gtzan.py @@ -0,0 +1,1113 @@ +import os +from pathlib import Path +from typing import Tuple, Optional, Union + +import torchaudio +from torch import Tensor +from torch.utils.data import Dataset +from torchaudio.datasets.utils import ( + download_url, + extract_archive, +) + +# The following lists prefixed with `filtered_` provide a filtered split +# that: +# +# a. Mitigate a known issue with GTZAN (duplication) +# +# b. Provide a standard split for testing it against other +# methods (e.g. the one in jordipons/sklearn-audio-transfer-learning). +# +# Those are used when GTZAN is initialised with the `filtered` keyword. +# The split was taken from (github) jordipons/sklearn-audio-transfer-learning. + +gtzan_genres = [ + "blues", + "classical", + "country", + "disco", + "hiphop", + "jazz", + "metal", + "pop", + "reggae", + "rock", +] + +filtered_test = [ + "blues.00012", + "blues.00013", + "blues.00014", + "blues.00015", + "blues.00016", + "blues.00017", + "blues.00018", + "blues.00019", + "blues.00020", + "blues.00021", + "blues.00022", + "blues.00023", + "blues.00024", + "blues.00025", + "blues.00026", + "blues.00027", + "blues.00028", + "blues.00061", + "blues.00062", + "blues.00063", + "blues.00064", + "blues.00065", + "blues.00066", + "blues.00067", + "blues.00068", + "blues.00069", + "blues.00070", + "blues.00071", + "blues.00072", + "blues.00098", + "blues.00099", + "classical.00011", + "classical.00012", + "classical.00013", + "classical.00014", + "classical.00015", + "classical.00016", + "classical.00017", + "classical.00018", + "classical.00019", + "classical.00020", + "classical.00021", + "classical.00022", + "classical.00023", + "classical.00024", + "classical.00025", + "classical.00026", + "classical.00027", + "classical.00028", + "classical.00029", + "classical.00034", + "classical.00035", + "classical.00036", + "classical.00037", + "classical.00038", + "classical.00039", + "classical.00040", + "classical.00041", + "classical.00049", + "classical.00077", + "classical.00078", + "classical.00079", + "country.00030", + "country.00031", + "country.00032", + "country.00033", + "country.00034", + "country.00035", + "country.00036", + "country.00037", + "country.00038", + "country.00039", + "country.00040", + "country.00043", + "country.00044", + "country.00046", + "country.00047", + "country.00048", + "country.00050", + "country.00051", + "country.00053", + "country.00054", + "country.00055", + "country.00056", + "country.00057", + "country.00058", + "country.00059", + "country.00060", + "country.00061", + "country.00062", + "country.00063", + "country.00064", + "disco.00001", + "disco.00021", + "disco.00058", + "disco.00062", + "disco.00063", + "disco.00064", + "disco.00065", + "disco.00066", + "disco.00069", + "disco.00076", + "disco.00077", + "disco.00078", + "disco.00079", + "disco.00080", + "disco.00081", + "disco.00082", + "disco.00083", + "disco.00084", + "disco.00085", + "disco.00086", + "disco.00087", + "disco.00088", + "disco.00091", + "disco.00092", + "disco.00093", + "disco.00094", + "disco.00096", + "disco.00097", + "disco.00099", + "hiphop.00000", + "hiphop.00026", + "hiphop.00027", + "hiphop.00030", + "hiphop.00040", + "hiphop.00043", + "hiphop.00044", + "hiphop.00045", + "hiphop.00051", + "hiphop.00052", + "hiphop.00053", + "hiphop.00054", + "hiphop.00062", + "hiphop.00063", + "hiphop.00064", + "hiphop.00065", + "hiphop.00066", + "hiphop.00067", + "hiphop.00068", + "hiphop.00069", + "hiphop.00070", + "hiphop.00071", + "hiphop.00072", + "hiphop.00073", + "hiphop.00074", + "hiphop.00075", + "hiphop.00099", + "jazz.00073", + "jazz.00074", + "jazz.00075", + "jazz.00076", + "jazz.00077", + "jazz.00078", + "jazz.00079", + "jazz.00080", + "jazz.00081", + "jazz.00082", + "jazz.00083", + "jazz.00084", + "jazz.00085", + "jazz.00086", + "jazz.00087", + "jazz.00088", + "jazz.00089", + "jazz.00090", + "jazz.00091", + "jazz.00092", + "jazz.00093", + "jazz.00094", + "jazz.00095", + "jazz.00096", + "jazz.00097", + "jazz.00098", + "jazz.00099", + "metal.00012", + "metal.00013", + "metal.00014", + "metal.00015", + "metal.00022", + "metal.00023", + "metal.00025", + "metal.00026", + "metal.00027", + "metal.00028", + "metal.00029", + "metal.00030", + "metal.00031", + "metal.00032", + "metal.00033", + "metal.00038", + "metal.00039", + "metal.00067", + "metal.00070", + "metal.00073", + "metal.00074", + "metal.00075", + "metal.00078", + "metal.00083", + "metal.00085", + "metal.00087", + "metal.00088", + "pop.00000", + "pop.00001", + "pop.00013", + "pop.00014", + "pop.00043", + "pop.00063", + "pop.00064", + "pop.00065", + "pop.00066", + "pop.00069", + "pop.00070", + "pop.00071", + "pop.00072", + "pop.00073", + "pop.00074", + "pop.00075", + "pop.00076", + "pop.00077", + "pop.00078", + "pop.00079", + "pop.00082", + "pop.00088", + "pop.00089", + "pop.00090", + "pop.00091", + "pop.00092", + "pop.00093", + "pop.00094", + "pop.00095", + "pop.00096", + "reggae.00034", + "reggae.00035", + "reggae.00036", + "reggae.00037", + "reggae.00038", + "reggae.00039", + "reggae.00040", + "reggae.00046", + "reggae.00047", + "reggae.00048", + "reggae.00052", + "reggae.00053", + "reggae.00064", + "reggae.00065", + "reggae.00066", + "reggae.00067", + "reggae.00068", + "reggae.00071", + "reggae.00079", + "reggae.00082", + "reggae.00083", + "reggae.00084", + "reggae.00087", + "reggae.00088", + "reggae.00089", + "reggae.00090", + "rock.00010", + "rock.00011", + "rock.00012", + "rock.00013", + "rock.00014", + "rock.00015", + "rock.00027", + "rock.00028", + "rock.00029", + "rock.00030", + "rock.00031", + "rock.00032", + "rock.00033", + "rock.00034", + "rock.00035", + "rock.00036", + "rock.00037", + "rock.00039", + "rock.00040", + "rock.00041", + "rock.00042", + "rock.00043", + "rock.00044", + "rock.00045", + "rock.00046", + "rock.00047", + "rock.00048", + "rock.00086", + "rock.00087", + "rock.00088", + "rock.00089", + "rock.00090", +] + +filtered_train = [ + "blues.00029", + "blues.00030", + "blues.00031", + "blues.00032", + "blues.00033", + "blues.00034", + "blues.00035", + "blues.00036", + "blues.00037", + "blues.00038", + "blues.00039", + "blues.00040", + "blues.00041", + "blues.00042", + "blues.00043", + "blues.00044", + "blues.00045", + "blues.00046", + "blues.00047", + "blues.00048", + "blues.00049", + "blues.00073", + "blues.00074", + "blues.00075", + "blues.00076", + "blues.00077", + "blues.00078", + "blues.00079", + "blues.00080", + "blues.00081", + "blues.00082", + "blues.00083", + "blues.00084", + "blues.00085", + "blues.00086", + "blues.00087", + "blues.00088", + "blues.00089", + "blues.00090", + "blues.00091", + "blues.00092", + "blues.00093", + "blues.00094", + "blues.00095", + "blues.00096", + "blues.00097", + "classical.00030", + "classical.00031", + "classical.00032", + "classical.00033", + "classical.00043", + "classical.00044", + "classical.00045", + "classical.00046", + "classical.00047", + "classical.00048", + "classical.00050", + "classical.00051", + "classical.00052", + "classical.00053", + "classical.00054", + "classical.00055", + "classical.00056", + "classical.00057", + "classical.00058", + "classical.00059", + "classical.00060", + "classical.00061", + "classical.00062", + "classical.00063", + "classical.00064", + "classical.00065", + "classical.00066", + "classical.00067", + "classical.00080", + "classical.00081", + "classical.00082", + "classical.00083", + "classical.00084", + "classical.00085", + "classical.00086", + "classical.00087", + "classical.00088", + "classical.00089", + "classical.00090", + "classical.00091", + "classical.00092", + "classical.00093", + "classical.00094", + "classical.00095", + "classical.00096", + "classical.00097", + "classical.00098", + "classical.00099", + "country.00019", + "country.00020", + "country.00021", + "country.00022", + "country.00023", + "country.00024", + "country.00025", + "country.00026", + "country.00028", + "country.00029", + "country.00065", + "country.00066", + "country.00067", + "country.00068", + "country.00069", + "country.00070", + "country.00071", + "country.00072", + "country.00073", + "country.00074", + "country.00075", + "country.00076", + "country.00077", + "country.00078", + "country.00079", + "country.00080", + "country.00081", + "country.00082", + "country.00083", + "country.00084", + "country.00085", + "country.00086", + "country.00087", + "country.00088", + "country.00089", + "country.00090", + "country.00091", + "country.00092", + "country.00093", + "country.00094", + "country.00095", + "country.00096", + "country.00097", + "country.00098", + "country.00099", + "disco.00005", + "disco.00015", + "disco.00016", + "disco.00017", + "disco.00018", + "disco.00019", + "disco.00020", + "disco.00022", + "disco.00023", + "disco.00024", + "disco.00025", + "disco.00026", + "disco.00027", + "disco.00028", + "disco.00029", + "disco.00030", + "disco.00031", + "disco.00032", + "disco.00033", + "disco.00034", + "disco.00035", + "disco.00036", + "disco.00037", + "disco.00039", + "disco.00040", + "disco.00041", + "disco.00042", + "disco.00043", + "disco.00044", + "disco.00045", + "disco.00047", + "disco.00049", + "disco.00053", + "disco.00054", + "disco.00056", + "disco.00057", + "disco.00059", + "disco.00061", + "disco.00070", + "disco.00073", + "disco.00074", + "disco.00089", + "hiphop.00002", + "hiphop.00003", + "hiphop.00004", + "hiphop.00005", + "hiphop.00006", + "hiphop.00007", + "hiphop.00008", + "hiphop.00009", + "hiphop.00010", + "hiphop.00011", + "hiphop.00012", + "hiphop.00013", + "hiphop.00014", + "hiphop.00015", + "hiphop.00016", + "hiphop.00017", + "hiphop.00018", + "hiphop.00019", + "hiphop.00020", + "hiphop.00021", + "hiphop.00022", + "hiphop.00023", + "hiphop.00024", + "hiphop.00025", + "hiphop.00028", + "hiphop.00029", + "hiphop.00031", + "hiphop.00032", + "hiphop.00033", + "hiphop.00034", + "hiphop.00035", + "hiphop.00036", + "hiphop.00037", + "hiphop.00038", + "hiphop.00041", + "hiphop.00042", + "hiphop.00055", + "hiphop.00056", + "hiphop.00057", + "hiphop.00058", + "hiphop.00059", + "hiphop.00060", + "hiphop.00061", + "hiphop.00077", + "hiphop.00078", + "hiphop.00079", + "hiphop.00080", + "jazz.00000", + "jazz.00001", + "jazz.00011", + "jazz.00012", + "jazz.00013", + "jazz.00014", + "jazz.00015", + "jazz.00016", + "jazz.00017", + "jazz.00018", + "jazz.00019", + "jazz.00020", + "jazz.00021", + "jazz.00022", + "jazz.00023", + "jazz.00024", + "jazz.00041", + "jazz.00047", + "jazz.00048", + "jazz.00049", + "jazz.00050", + "jazz.00051", + "jazz.00052", + "jazz.00053", + "jazz.00054", + "jazz.00055", + "jazz.00056", + "jazz.00057", + "jazz.00058", + "jazz.00059", + "jazz.00060", + "jazz.00061", + "jazz.00062", + "jazz.00063", + "jazz.00064", + "jazz.00065", + "jazz.00066", + "jazz.00067", + "jazz.00068", + "jazz.00069", + "jazz.00070", + "jazz.00071", + "jazz.00072", + "metal.00002", + "metal.00003", + "metal.00005", + "metal.00021", + "metal.00024", + "metal.00035", + "metal.00046", + "metal.00047", + "metal.00048", + "metal.00049", + "metal.00050", + "metal.00051", + "metal.00052", + "metal.00053", + "metal.00054", + "metal.00055", + "metal.00056", + "metal.00057", + "metal.00059", + "metal.00060", + "metal.00061", + "metal.00062", + "metal.00063", + "metal.00064", + "metal.00065", + "metal.00066", + "metal.00069", + "metal.00071", + "metal.00072", + "metal.00079", + "metal.00080", + "metal.00084", + "metal.00086", + "metal.00089", + "metal.00090", + "metal.00091", + "metal.00092", + "metal.00093", + "metal.00094", + "metal.00095", + "metal.00096", + "metal.00097", + "metal.00098", + "metal.00099", + "pop.00002", + "pop.00003", + "pop.00004", + "pop.00005", + "pop.00006", + "pop.00007", + "pop.00008", + "pop.00009", + "pop.00011", + "pop.00012", + "pop.00016", + "pop.00017", + "pop.00018", + "pop.00019", + "pop.00020", + "pop.00023", + "pop.00024", + "pop.00025", + "pop.00026", + "pop.00027", + "pop.00028", + "pop.00029", + "pop.00031", + "pop.00032", + "pop.00033", + "pop.00034", + "pop.00035", + "pop.00036", + "pop.00038", + "pop.00039", + "pop.00040", + "pop.00041", + "pop.00042", + "pop.00044", + "pop.00046", + "pop.00049", + "pop.00050", + "pop.00080", + "pop.00097", + "pop.00098", + "pop.00099", + "reggae.00000", + "reggae.00001", + "reggae.00002", + "reggae.00004", + "reggae.00006", + "reggae.00009", + "reggae.00011", + "reggae.00012", + "reggae.00014", + "reggae.00015", + "reggae.00016", + "reggae.00017", + "reggae.00018", + "reggae.00019", + "reggae.00020", + "reggae.00021", + "reggae.00022", + "reggae.00023", + "reggae.00024", + "reggae.00025", + "reggae.00026", + "reggae.00027", + "reggae.00028", + "reggae.00029", + "reggae.00030", + "reggae.00031", + "reggae.00032", + "reggae.00042", + "reggae.00043", + "reggae.00044", + "reggae.00045", + "reggae.00049", + "reggae.00050", + "reggae.00051", + "reggae.00054", + "reggae.00055", + "reggae.00056", + "reggae.00057", + "reggae.00058", + "reggae.00059", + "reggae.00060", + "reggae.00063", + "reggae.00069", + "rock.00000", + "rock.00001", + "rock.00002", + "rock.00003", + "rock.00004", + "rock.00005", + "rock.00006", + "rock.00007", + "rock.00008", + "rock.00009", + "rock.00016", + "rock.00017", + "rock.00018", + "rock.00019", + "rock.00020", + "rock.00021", + "rock.00022", + "rock.00023", + "rock.00024", + "rock.00025", + "rock.00026", + "rock.00057", + "rock.00058", + "rock.00059", + "rock.00060", + "rock.00061", + "rock.00062", + "rock.00063", + "rock.00064", + "rock.00065", + "rock.00066", + "rock.00067", + "rock.00068", + "rock.00069", + "rock.00070", + "rock.00091", + "rock.00092", + "rock.00093", + "rock.00094", + "rock.00095", + "rock.00096", + "rock.00097", + "rock.00098", + "rock.00099", +] + +filtered_valid = [ + "blues.00000", + "blues.00001", + "blues.00002", + "blues.00003", + "blues.00004", + "blues.00005", + "blues.00006", + "blues.00007", + "blues.00008", + "blues.00009", + "blues.00010", + "blues.00011", + "blues.00050", + "blues.00051", + "blues.00052", + "blues.00053", + "blues.00054", + "blues.00055", + "blues.00056", + "blues.00057", + "blues.00058", + "blues.00059", + "blues.00060", + "classical.00000", + "classical.00001", + "classical.00002", + "classical.00003", + "classical.00004", + "classical.00005", + "classical.00006", + "classical.00007", + "classical.00008", + "classical.00009", + "classical.00010", + "classical.00068", + "classical.00069", + "classical.00070", + "classical.00071", + "classical.00072", + "classical.00073", + "classical.00074", + "classical.00075", + "classical.00076", + "country.00000", + "country.00001", + "country.00002", + "country.00003", + "country.00004", + "country.00005", + "country.00006", + "country.00007", + "country.00009", + "country.00010", + "country.00011", + "country.00012", + "country.00013", + "country.00014", + "country.00015", + "country.00016", + "country.00017", + "country.00018", + "country.00027", + "country.00041", + "country.00042", + "country.00045", + "country.00049", + "disco.00000", + "disco.00002", + "disco.00003", + "disco.00004", + "disco.00006", + "disco.00007", + "disco.00008", + "disco.00009", + "disco.00010", + "disco.00011", + "disco.00012", + "disco.00013", + "disco.00014", + "disco.00046", + "disco.00048", + "disco.00052", + "disco.00067", + "disco.00068", + "disco.00072", + "disco.00075", + "disco.00090", + "disco.00095", + "hiphop.00081", + "hiphop.00082", + "hiphop.00083", + "hiphop.00084", + "hiphop.00085", + "hiphop.00086", + "hiphop.00087", + "hiphop.00088", + "hiphop.00089", + "hiphop.00090", + "hiphop.00091", + "hiphop.00092", + "hiphop.00093", + "hiphop.00094", + "hiphop.00095", + "hiphop.00096", + "hiphop.00097", + "hiphop.00098", + "jazz.00002", + "jazz.00003", + "jazz.00004", + "jazz.00005", + "jazz.00006", + "jazz.00007", + "jazz.00008", + "jazz.00009", + "jazz.00010", + "jazz.00025", + "jazz.00026", + "jazz.00027", + "jazz.00028", + "jazz.00029", + "jazz.00030", + "jazz.00031", + "jazz.00032", + "metal.00000", + "metal.00001", + "metal.00006", + "metal.00007", + "metal.00008", + "metal.00009", + "metal.00010", + "metal.00011", + "metal.00016", + "metal.00017", + "metal.00018", + "metal.00019", + "metal.00020", + "metal.00036", + "metal.00037", + "metal.00068", + "metal.00076", + "metal.00077", + "metal.00081", + "metal.00082", + "pop.00010", + "pop.00053", + "pop.00055", + "pop.00058", + "pop.00059", + "pop.00060", + "pop.00061", + "pop.00062", + "pop.00081", + "pop.00083", + "pop.00084", + "pop.00085", + "pop.00086", + "reggae.00061", + "reggae.00062", + "reggae.00070", + "reggae.00072", + "reggae.00074", + "reggae.00076", + "reggae.00077", + "reggae.00078", + "reggae.00085", + "reggae.00092", + "reggae.00093", + "reggae.00094", + "reggae.00095", + "reggae.00096", + "reggae.00097", + "reggae.00098", + "reggae.00099", + "rock.00038", + "rock.00049", + "rock.00050", + "rock.00051", + "rock.00052", + "rock.00053", + "rock.00054", + "rock.00055", + "rock.00056", + "rock.00071", + "rock.00072", + "rock.00073", + "rock.00074", + "rock.00075", + "rock.00076", + "rock.00077", + "rock.00078", + "rock.00079", + "rock.00080", + "rock.00081", + "rock.00082", + "rock.00083", + "rock.00084", + "rock.00085", +] + + +URL = "http://opihi.cs.uvic.ca/sound/genres.tar.gz" +FOLDER_IN_ARCHIVE = "genres" +_CHECKSUMS = { + "http://opihi.cs.uvic.ca/sound/genres.tar.gz": "5b3d6dddb579ab49814ab86dba69e7c7" +} + + +def load_gtzan_item(fileid: str, path: str, ext_audio: str) -> Tuple[Tensor, str]: + """ + Loads a file from the dataset and returns the raw waveform + as a Torch Tensor, its sample rate as an integer, and its + genre as a string. + """ + # Filenames are of the form label.id, e.g. blues.00078 + label, _ = fileid.split(".") + + # Read wav + file_audio = os.path.join(path, label, fileid + ext_audio) + waveform, sample_rate = torchaudio.load(file_audio) + + return waveform, sample_rate, label + + +class GTZAN(Dataset): + """Create a Dataset for GTZAN. + + Note: + Please see http://marsyas.info/downloads/datasets.html if you are planning to use + this dataset to publish results. + + Args: + root (str or Path): Path to the directory where the dataset is found or downloaded. + url (str, optional): The URL to download the dataset from. + (default: ``"http://opihi.cs.uvic.ca/sound/genres.tar.gz"``) + folder_in_archive (str, optional): The top-level directory of the dataset. + download (bool, optional): + Whether to download the dataset if it is not found at root path. (default: ``False``). + subset (str or None, optional): Which subset of the dataset to use. + One of ``"training"``, ``"validation"``, ``"testing"`` or ``None``. + If ``None``, the entire dataset is used. (default: ``None``). + """ + + _ext_audio = ".wav" + + def __init__( + self, + root: Union[str, Path], + url: str = URL, + folder_in_archive: str = FOLDER_IN_ARCHIVE, + download: bool = False, + subset: Optional[str] = None, + ) -> None: + + # super(GTZAN, self).__init__() + + # Get string representation of 'root' in case Path object is passed + root = os.fspath(root) + + self.root = root + self.url = url + self.folder_in_archive = folder_in_archive + self.download = download + self.subset = subset + + assert subset is None or subset in ["training", "validation", "testing"], ( + "When `subset` not None, it must take a value from " + + "{'training', 'validation', 'testing'}." + ) + + archive = os.path.basename(url) + archive = os.path.join(root, archive) + self._path = os.path.join(root, folder_in_archive) + + if download: + if not os.path.isdir(self._path): + if not os.path.isfile(archive): + checksum = _CHECKSUMS.get(url, None) + download_url(url, root, hash_value=checksum, hash_type="md5") + extract_archive(archive) + + if not os.path.isdir(self._path): + raise RuntimeError( + "Dataset not found. Please use `download=True` to download it." + ) + + if self.subset is None: + # Check every subdirectory under dataset root + # which has the same name as the genres in + # GTZAN (e.g. `root_dir'/blues/, `root_dir'/rock, etc.) + # This lets users remove or move around song files, + # useful when e.g. they want to use only some of the files + # in a genre or want to label other files with a different + # genre. + self._walker = [] + + root = os.path.expanduser(self._path) + + for directory in gtzan_genres: + fulldir = os.path.join(root, directory) + + if not os.path.exists(fulldir): + continue + + songs_in_genre = os.listdir(fulldir) + songs_in_genre.sort() + for fname in songs_in_genre: + name, ext = os.path.splitext(fname) + if ext.lower() == ".wav" and "." in name: + # Check whether the file is of the form + # `gtzan_genre`.`5 digit number`.wav + genre, num = name.split(".") + if genre in gtzan_genres and len(num) == 5 and num.isdigit(): + self._walker.append(name) + else: + if self.subset == "training": + self._walker = filtered_train + elif self.subset == "validation": + self._walker = filtered_valid + elif self.subset == "testing": + self._walker = filtered_test + + def __getitem__(self, n: int) -> Tuple[Tensor, int, str]: + """Load the n-th sample from the dataset. + + Args: + n (int): The index of the sample to be loaded + + Returns: + (Tensor, int, str): ``(waveform, sample_rate, label)`` + """ + fileid = self._walker[n] + item = load_gtzan_item(fileid, self._path, self._ext_audio) + waveform, sample_rate, label = item + return waveform, sample_rate, label + + def __len__(self) -> int: + return len(self._walker) diff --git a/torchaudio/datasets/librimix.py b/torchaudio/datasets/librimix.py new file mode 100644 index 00000000..ab3b3877 --- /dev/null +++ b/torchaudio/datasets/librimix.py @@ -0,0 +1,89 @@ +from pathlib import Path +from typing import Union, Tuple, List + +import torch +from torch.utils.data import Dataset + +import torchaudio + +SampleType = Tuple[int, torch.Tensor, List[torch.Tensor]] + + +class LibriMix(Dataset): + r"""Create the LibriMix dataset. + + Args: + root (str or Path): The path to the directory where the directory ``Libri2Mix`` or + ``Libri3Mix`` is stored. + subset (str, optional): The subset to use. Options: [``train-360`, ``train-100``, + ``dev``, and ``test``] (Default: ``train-360``). + num_speakers (int, optional): The number of speakers, which determines the directories + to traverse. The Dataset will traverse ``s1`` to ``sN`` directories to collect + N source audios. (Default: 2) + sample_rate (int, optional): sample rate of audio files. The ``sample_rate`` determines + which subdirectory the audio are fetched. If any of the audio has a different sample + rate, raises ``ValueError``. Options: [8000, 16000] (Default: 8000) + task (str, optional): the task of LibriMix. + Options: [``enh_single``, ``enh_both``, ``sep_clean``, ``sep_noisy``] + (Default: ``sep_clean``) + + Note: + The LibriMix dataset needs to be manually generated. Please check https://github.com/JorisCos/LibriMix + """ + def __init__( + self, + root: Union[str, Path], + subset: str = "train-360", + num_speakers: int = 2, + sample_rate: int = 8000, + task: str = "sep_clean", + ): + self.root = Path(root) / f"Libri{num_speakers}Mix" + if sample_rate == 8000: + self.root = self.root / "wav8k/min" / subset + elif sample_rate == 16000: + self.root = self.root / "wav16k/min" / subset + else: + raise ValueError( + f"Unsupported sample rate. Found {sample_rate}." + ) + self.sample_rate = sample_rate + self.task = task + self.mix_dir = (self.root / f"mix_{task.split('_')[1]}").resolve() + self.src_dirs = [(self.root / f"s{i+1}").resolve() for i in range(num_speakers)] + + self.files = [p.name for p in self.mix_dir.glob("*wav")] + self.files.sort() + + def _load_audio(self, path) -> torch.Tensor: + waveform, sample_rate = torchaudio.load(path) + if sample_rate != self.sample_rate: + raise ValueError( + f"The dataset contains audio file of sample rate {sample_rate}, " + f"but the requested sample rate is {self.sample_rate}." + ) + return waveform + + def _load_sample(self, filename) -> SampleType: + mixed = self._load_audio(str(self.mix_dir / filename)) + srcs = [] + for i, dir_ in enumerate(self.src_dirs): + src = self._load_audio(str(dir_ / filename)) + if mixed.shape != src.shape: + raise ValueError( + f"Different waveform shapes. mixed: {mixed.shape}, src[{i}]: {src.shape}" + ) + srcs.append(src) + return self.sample_rate, mixed, srcs + + def __len__(self) -> int: + return len(self.files) + + def __getitem__(self, key: int) -> SampleType: + """Load the n-th sample from the dataset. + Args: + key (int): The index of the sample to be loaded + Returns: + (int, Tensor, List[Tensor]): ``(sample_rate, mix_waveform, list_of_source_waveforms)`` + """ + return self._load_sample(self.files[key]) diff --git a/torchaudio/datasets/librispeech.py b/torchaudio/datasets/librispeech.py new file mode 100644 index 00000000..ad8a2649 --- /dev/null +++ b/torchaudio/datasets/librispeech.py @@ -0,0 +1,143 @@ +import os +from typing import Tuple, Union +from pathlib import Path + +import torchaudio +from torch import Tensor +from torch.utils.data import Dataset +from torchaudio.datasets.utils import ( + download_url, + extract_archive, +) + +URL = "train-clean-100" +FOLDER_IN_ARCHIVE = "LibriSpeech" +_CHECKSUMS = { + "http://www.openslr.org/resources/12/dev-clean.tar.gz": + "76f87d090650617fca0cac8f88b9416e0ebf80350acb97b343a85fa903728ab3", + "http://www.openslr.org/resources/12/dev-other.tar.gz": + "12661c48e8c3fe1de2c1caa4c3e135193bfb1811584f11f569dd12645aa84365", + "http://www.openslr.org/resources/12/test-clean.tar.gz": + "39fde525e59672dc6d1551919b1478f724438a95aa55f874b576be21967e6c23", + "http://www.openslr.org/resources/12/test-other.tar.gz": + "d09c181bba5cf717b3dee7d4d592af11a3ee3a09e08ae025c5506f6ebe961c29", + "http://www.openslr.org/resources/12/train-clean-100.tar.gz": + "d4ddd1d5a6ab303066f14971d768ee43278a5f2a0aa43dc716b0e64ecbbbf6e2", + "http://www.openslr.org/resources/12/train-clean-360.tar.gz": + "146a56496217e96c14334a160df97fffedd6e0a04e66b9c5af0d40be3c792ecf", + "http://www.openslr.org/resources/12/train-other-500.tar.gz": + "ddb22f27f96ec163645d53215559df6aa36515f26e01dd70798188350adcb6d2" +} + + +def load_librispeech_item(fileid: str, + path: str, + ext_audio: str, + ext_txt: str) -> Tuple[Tensor, int, str, int, int, int]: + speaker_id, chapter_id, utterance_id = fileid.split("-") + + file_text = speaker_id + "-" + chapter_id + ext_txt + file_text = os.path.join(path, speaker_id, chapter_id, file_text) + + fileid_audio = speaker_id + "-" + chapter_id + "-" + utterance_id + file_audio = fileid_audio + ext_audio + file_audio = os.path.join(path, speaker_id, chapter_id, file_audio) + + # Load audio + waveform, sample_rate = torchaudio.load(file_audio) + + # Load text + with open(file_text) as ft: + for line in ft: + fileid_text, transcript = line.strip().split(" ", 1) + if fileid_audio == fileid_text: + break + else: + # Translation not found + raise FileNotFoundError("Translation not found for " + fileid_audio) + + return ( + waveform, + sample_rate, + transcript, + int(speaker_id), + int(chapter_id), + int(utterance_id), + ) + + +class LIBRISPEECH(Dataset): + """Create a Dataset for LibriSpeech. + + Args: + root (str or Path): Path to the directory where the dataset is found or downloaded. + url (str, optional): The URL to download the dataset from, + or the type of the dataset to dowload. + Allowed type values are ``"dev-clean"``, ``"dev-other"``, ``"test-clean"``, + ``"test-other"``, ``"train-clean-100"``, ``"train-clean-360"`` and + ``"train-other-500"``. (default: ``"train-clean-100"``) + folder_in_archive (str, optional): + The top-level directory of the dataset. (default: ``"LibriSpeech"``) + download (bool, optional): + Whether to download the dataset if it is not found at root path. (default: ``False``). + """ + + _ext_txt = ".trans.txt" + _ext_audio = ".flac" + + def __init__(self, + root: Union[str, Path], + url: str = URL, + folder_in_archive: str = FOLDER_IN_ARCHIVE, + download: bool = False) -> None: + + if url in [ + "dev-clean", + "dev-other", + "test-clean", + "test-other", + "train-clean-100", + "train-clean-360", + "train-other-500", + ]: + + ext_archive = ".tar.gz" + base_url = "http://www.openslr.org/resources/12/" + + url = os.path.join(base_url, url + ext_archive) + + # Get string representation of 'root' in case Path object is passed + root = os.fspath(root) + + basename = os.path.basename(url) + archive = os.path.join(root, basename) + + basename = basename.split(".")[0] + folder_in_archive = os.path.join(folder_in_archive, basename) + + self._path = os.path.join(root, folder_in_archive) + + if download: + if not os.path.isdir(self._path): + if not os.path.isfile(archive): + checksum = _CHECKSUMS.get(url, None) + download_url(url, root, hash_value=checksum) + extract_archive(archive) + + self._walker = sorted(str(p.stem) for p in Path(self._path).glob('*/*/*' + self._ext_audio)) + + def __getitem__(self, n: int) -> Tuple[Tensor, int, str, int, int, int]: + """Load the n-th sample from the dataset. + + Args: + n (int): The index of the sample to be loaded + + Returns: + (Tensor, int, str, int, int, int): + ``(waveform, sample_rate, transcript, speaker_id, chapter_id, utterance_id)`` + """ + fileid = self._walker[n] + return load_librispeech_item(fileid, self._path, self._ext_audio, self._ext_txt) + + def __len__(self) -> int: + return len(self._walker) diff --git a/torchaudio/datasets/libritts.py b/torchaudio/datasets/libritts.py new file mode 100644 index 00000000..2c978c42 --- /dev/null +++ b/torchaudio/datasets/libritts.py @@ -0,0 +1,150 @@ +import os +from typing import Tuple, Union +from pathlib import Path + +import torchaudio +from torch import Tensor +from torch.utils.data import Dataset +from torchaudio.datasets.utils import ( + download_url, + extract_archive, +) + +URL = "train-clean-100" +FOLDER_IN_ARCHIVE = "LibriTTS" +_CHECKSUMS = { + "http://www.openslr.org/60/dev-clean.tar.gz": "0c3076c1e5245bb3f0af7d82087ee207", + "http://www.openslr.org/60/dev-other.tar.gz": "815555d8d75995782ac3ccd7f047213d", + "http://www.openslr.org/60/test-clean.tar.gz": "7bed3bdb047c4c197f1ad3bc412db59f", + "http://www.openslr.org/60/test-other.tar.gz": "ae3258249472a13b5abef2a816f733e4", + "http://www.openslr.org/60/train-clean-100.tar.gz": "4a8c202b78fe1bc0c47916a98f3a2ea8", + "http://www.openslr.org/60/train-clean-360.tar.gz": "a84ef10ddade5fd25df69596a2767b2d", + "http://www.openslr.org/60/train-other-500.tar.gz": "7b181dd5ace343a5f38427999684aa6f", +} + + +def load_libritts_item( + fileid: str, + path: str, + ext_audio: str, + ext_original_txt: str, + ext_normalized_txt: str, +) -> Tuple[Tensor, int, str, str, int, int, str]: + speaker_id, chapter_id, segment_id, utterance_id = fileid.split("_") + utterance_id = fileid + + normalized_text = utterance_id + ext_normalized_txt + normalized_text = os.path.join(path, speaker_id, chapter_id, normalized_text) + + original_text = utterance_id + ext_original_txt + original_text = os.path.join(path, speaker_id, chapter_id, original_text) + + file_audio = utterance_id + ext_audio + file_audio = os.path.join(path, speaker_id, chapter_id, file_audio) + + # Load audio + waveform, sample_rate = torchaudio.load(file_audio) + + # Load original text + with open(original_text) as ft: + original_text = ft.readline() + + # Load normalized text + with open(normalized_text, "r") as ft: + normalized_text = ft.readline() + + return ( + waveform, + sample_rate, + original_text, + normalized_text, + int(speaker_id), + int(chapter_id), + utterance_id, + ) + + +class LIBRITTS(Dataset): + """Create a Dataset for LibriTTS. + + Args: + root (str or Path): Path to the directory where the dataset is found or downloaded. + url (str, optional): The URL to download the dataset from, + or the type of the dataset to dowload. + Allowed type values are ``"dev-clean"``, ``"dev-other"``, ``"test-clean"``, + ``"test-other"``, ``"train-clean-100"``, ``"train-clean-360"`` and + ``"train-other-500"``. (default: ``"train-clean-100"``) + folder_in_archive (str, optional): + The top-level directory of the dataset. (default: ``"LibriTTS"``) + download (bool, optional): + Whether to download the dataset if it is not found at root path. (default: ``False``). + """ + + _ext_original_txt = ".original.txt" + _ext_normalized_txt = ".normalized.txt" + _ext_audio = ".wav" + + def __init__( + self, + root: Union[str, Path], + url: str = URL, + folder_in_archive: str = FOLDER_IN_ARCHIVE, + download: bool = False, + ) -> None: + + if url in [ + "dev-clean", + "dev-other", + "test-clean", + "test-other", + "train-clean-100", + "train-clean-360", + "train-other-500", + ]: + + ext_archive = ".tar.gz" + base_url = "http://www.openslr.org/resources/60/" + + url = os.path.join(base_url, url + ext_archive) + + # Get string representation of 'root' in case Path object is passed + root = os.fspath(root) + + basename = os.path.basename(url) + archive = os.path.join(root, basename) + + basename = basename.split(".")[0] + folder_in_archive = os.path.join(folder_in_archive, basename) + + self._path = os.path.join(root, folder_in_archive) + + if download: + if not os.path.isdir(self._path): + if not os.path.isfile(archive): + checksum = _CHECKSUMS.get(url, None) + download_url(url, root, hash_value=checksum) + extract_archive(archive) + + self._walker = sorted(str(p.stem) for p in Path(self._path).glob('*/*/*' + self._ext_audio)) + + def __getitem__(self, n: int) -> Tuple[Tensor, int, str, str, int, int, str]: + """Load the n-th sample from the dataset. + + Args: + n (int): The index of the sample to be loaded + + Returns: + (Tensor, int, str, str, str, int, int, str): + ``(waveform, sample_rate, original_text, normalized_text, speaker_id, chapter_id, utterance_id)`` + """ + fileid = self._walker[n] + return load_libritts_item( + fileid, + self._path, + self._ext_audio, + self._ext_original_txt, + self._ext_normalized_txt, + ) + + def __len__(self) -> int: + return len(self._walker) diff --git a/torchaudio/datasets/ljspeech.py b/torchaudio/datasets/ljspeech.py new file mode 100644 index 00000000..a0abcbb9 --- /dev/null +++ b/torchaudio/datasets/ljspeech.py @@ -0,0 +1,89 @@ +import os +import csv +from typing import Tuple, Union +from pathlib import Path + +import torchaudio +from torchaudio.datasets.utils import download_url, extract_archive +from torch import Tensor +from torch.utils.data import Dataset + +_RELEASE_CONFIGS = { + "release1": { + "folder_in_archive": "wavs", + "url": "https://data.keithito.com/data/speech/LJSpeech-1.1.tar.bz2", + "checksum": "be1a30453f28eb8dd26af4101ae40cbf2c50413b1bb21936cbcdc6fae3de8aa5", + } +} + + +class LJSPEECH(Dataset): + """Create a Dataset for LJSpeech-1.1. + + Args: + root (str or Path): Path to the directory where the dataset is found or downloaded. + url (str, optional): The URL to download the dataset from. + (default: ``"https://data.keithito.com/data/speech/LJSpeech-1.1.tar.bz2"``) + folder_in_archive (str, optional): + The top-level directory of the dataset. (default: ``"wavs"``) + download (bool, optional): + Whether to download the dataset if it is not found at root path. (default: ``False``). + """ + + def __init__(self, + root: Union[str, Path], + url: str = _RELEASE_CONFIGS["release1"]["url"], + folder_in_archive: str = _RELEASE_CONFIGS["release1"]["folder_in_archive"], + download: bool = False) -> None: + + self._parse_filesystem(root, url, folder_in_archive, download) + + def _parse_filesystem(self, root: str, url: str, folder_in_archive: str, download: bool) -> None: + root = Path(root) + + basename = os.path.basename(url) + archive = root / basename + + basename = Path(basename.split(".tar.bz2")[0]) + folder_in_archive = basename / folder_in_archive + + self._path = root / folder_in_archive + self._metadata_path = root / basename / 'metadata.csv' + + if download: + if not os.path.isdir(self._path): + if not os.path.isfile(archive): + checksum = _RELEASE_CONFIGS["release1"]["checksum"] + download_url(url, root, hash_value=checksum) + extract_archive(archive) + + with open(self._metadata_path, "r", newline='') as metadata: + flist = csv.reader(metadata, delimiter="|", quoting=csv.QUOTE_NONE) + self._flist = list(flist) + + def __getitem__(self, n: int) -> Tuple[Tensor, int, str, str]: + """Load the n-th sample from the dataset. + + Args: + n (int): The index of the sample to be loaded + + Returns: + (Tensor, int, str, str): + ``(waveform, sample_rate, transcript, normalized_transcript)`` + """ + line = self._flist[n] + fileid, transcript, normalized_transcript = line + fileid_audio = self._path / (fileid + ".wav") + + # Load audio + waveform, sample_rate = torchaudio.load(fileid_audio) + + return ( + waveform, + sample_rate, + transcript, + normalized_transcript, + ) + + def __len__(self) -> int: + return len(self._flist) diff --git a/torchaudio/datasets/speechcommands.py b/torchaudio/datasets/speechcommands.py new file mode 100644 index 00000000..d92d6d44 --- /dev/null +++ b/torchaudio/datasets/speechcommands.py @@ -0,0 +1,148 @@ +import os +from typing import Tuple, Optional, Union +from pathlib import Path + +import torchaudio +from torch.utils.data import Dataset +from torch import Tensor +from torchaudio.datasets.utils import ( + download_url, + extract_archive, +) + +FOLDER_IN_ARCHIVE = "SpeechCommands" +URL = "speech_commands_v0.02" +HASH_DIVIDER = "_nohash_" +EXCEPT_FOLDER = "_background_noise_" +_CHECKSUMS = { + "https://storage.googleapis.com/download.tensorflow.org/data/speech_commands_v0.01.tar.gz": + "3cd23799cb2bbdec517f1cc028f8d43c", + "https://storage.googleapis.com/download.tensorflow.org/data/speech_commands_v0.02.tar.gz": + "6b74f3901214cb2c2934e98196829835", +} + + +def _load_list(root, *filenames): + output = [] + for filename in filenames: + filepath = os.path.join(root, filename) + with open(filepath) as fileobj: + output += [os.path.normpath(os.path.join(root, line.strip())) for line in fileobj] + return output + + +def load_speechcommands_item(filepath: str, path: str) -> Tuple[Tensor, int, str, str, int]: + relpath = os.path.relpath(filepath, path) + label, filename = os.path.split(relpath) + # Besides the officially supported split method for datasets defined by "validation_list.txt" + # and "testing_list.txt" over "speech_commands_v0.0x.tar.gz" archives, an alternative split + # method referred to in paragraph 2-3 of Section 7.1, references 13 and 14 of the original + # paper, and the checksums file from the tensorflow_datasets package [1] is also supported. + # Some filenames in those "speech_commands_test_set_v0.0x.tar.gz" archives have the form + # "xxx.wav.wav", so file extensions twice needs to be stripped twice. + # [1] https://github.com/tensorflow/datasets/blob/master/tensorflow_datasets/url_checksums/speech_commands.txt + speaker, _ = os.path.splitext(filename) + speaker, _ = os.path.splitext(speaker) + + speaker_id, utterance_number = speaker.split(HASH_DIVIDER) + utterance_number = int(utterance_number) + + # Load audio + waveform, sample_rate = torchaudio.load(filepath) + return waveform, sample_rate, label, speaker_id, utterance_number + + +class SPEECHCOMMANDS(Dataset): + """Create a Dataset for Speech Commands. + + Args: + root (str or Path): Path to the directory where the dataset is found or downloaded. + url (str, optional): The URL to download the dataset from, + or the type of the dataset to dowload. + Allowed type values are ``"speech_commands_v0.01"`` and ``"speech_commands_v0.02"`` + (default: ``"speech_commands_v0.02"``) + folder_in_archive (str, optional): + The top-level directory of the dataset. (default: ``"SpeechCommands"``) + download (bool, optional): + Whether to download the dataset if it is not found at root path. (default: ``False``). + subset (str or None, optional): + Select a subset of the dataset [None, "training", "validation", "testing"]. None means + the whole dataset. "validation" and "testing" are defined in "validation_list.txt" and + "testing_list.txt", respectively, and "training" is the rest. Details for the files + "validation_list.txt" and "testing_list.txt" are explained in the README of the dataset + and in the introduction of Section 7 of the original paper and its reference 12. The + original paper can be found `here `_. (Default: ``None``) + """ + + def __init__(self, + root: Union[str, Path], + url: str = URL, + folder_in_archive: str = FOLDER_IN_ARCHIVE, + download: bool = False, + subset: Optional[str] = None, + ) -> None: + + assert subset is None or subset in ["training", "validation", "testing"], ( + "When `subset` not None, it must take a value from " + + "{'training', 'validation', 'testing'}." + ) + + if url in [ + "speech_commands_v0.01", + "speech_commands_v0.02", + ]: + base_url = "https://storage.googleapis.com/download.tensorflow.org/data/" + ext_archive = ".tar.gz" + + url = os.path.join(base_url, url + ext_archive) + + # Get string representation of 'root' in case Path object is passed + root = os.fspath(root) + + basename = os.path.basename(url) + archive = os.path.join(root, basename) + + basename = basename.rsplit(".", 2)[0] + folder_in_archive = os.path.join(folder_in_archive, basename) + + self._path = os.path.join(root, folder_in_archive) + + if download: + if not os.path.isdir(self._path): + if not os.path.isfile(archive): + checksum = _CHECKSUMS.get(url, None) + download_url(url, root, hash_value=checksum, hash_type="md5") + extract_archive(archive, self._path) + + if subset == "validation": + self._walker = _load_list(self._path, "validation_list.txt") + elif subset == "testing": + self._walker = _load_list(self._path, "testing_list.txt") + elif subset == "training": + excludes = set(_load_list(self._path, "validation_list.txt", "testing_list.txt")) + walker = sorted(str(p) for p in Path(self._path).glob('*/*.wav')) + self._walker = [ + w for w in walker + if HASH_DIVIDER in w + and EXCEPT_FOLDER not in w + and os.path.normpath(w) not in excludes + ] + else: + walker = sorted(str(p) for p in Path(self._path).glob('*/*.wav')) + self._walker = [w for w in walker if HASH_DIVIDER in w and EXCEPT_FOLDER not in w] + + def __getitem__(self, n: int) -> Tuple[Tensor, int, str, str, int]: + """Load the n-th sample from the dataset. + + Args: + n (int): The index of the sample to be loaded + + Returns: + (Tensor, int, str, str, int): + ``(waveform, sample_rate, label, speaker_id, utterance_number)`` + """ + fileid = self._walker[n] + return load_speechcommands_item(fileid, self._path) + + def __len__(self) -> int: + return len(self._walker) diff --git a/torchaudio/datasets/tedlium.py b/torchaudio/datasets/tedlium.py new file mode 100644 index 00000000..fe6222a4 --- /dev/null +++ b/torchaudio/datasets/tedlium.py @@ -0,0 +1,195 @@ +import os +from typing import Tuple, Union +from pathlib import Path + +import torchaudio +from torch import Tensor +from torch.utils.data import Dataset +from torchaudio.datasets.utils import ( + download_url, + extract_archive, +) + + +_RELEASE_CONFIGS = { + "release1": { + "folder_in_archive": "TEDLIUM_release1", + "url": "http://www.openslr.org/resources/7/TEDLIUM_release1.tar.gz", + "checksum": "30301975fd8c5cac4040c261c0852f57cfa8adbbad2ce78e77e4986957445f27", + "data_path": "", + "subset": "train", + "supported_subsets": ["train", "test", "dev"], + "dict": "TEDLIUM.150K.dic", + }, + "release2": { + "folder_in_archive": "TEDLIUM_release2", + "url": "http://www.openslr.org/resources/19/TEDLIUM_release2.tar.gz", + "checksum": "93281b5fcaaae5c88671c9d000b443cb3c7ea3499ad12010b3934ca41a7b9c58", + "data_path": "", + "subset": "train", + "supported_subsets": ["train", "test", "dev"], + "dict": "TEDLIUM.152k.dic", + }, + "release3": { + "folder_in_archive": "TEDLIUM_release-3", + "url": "http://www.openslr.org/resources/51/TEDLIUM_release-3.tgz", + "checksum": "ad1e454d14d1ad550bc2564c462d87c7a7ec83d4dc2b9210f22ab4973b9eccdb", + "data_path": "data/", + "subset": None, + "supported_subsets": [None], + "dict": "TEDLIUM.152k.dic", + }, +} + + +class TEDLIUM(Dataset): + """ + Create a Dataset for Tedlium. It supports releases 1,2 and 3. + + Args: + root (str or Path): Path to the directory where the dataset is found or downloaded. + release (str, optional): Release version. + Allowed values are ``"release1"``, ``"release2"`` or ``"release3"``. + (default: ``"release1"``). + subset (str, optional): The subset of dataset to use. Valid options are ``"train"``, ``"dev"``, + and ``"test"`` for releases 1&2, ``None`` for release3. Defaults to ``"train"`` or ``None``. + download (bool, optional): + Whether to download the dataset if it is not found at root path. (default: ``False``). + audio_ext (str, optional): extension for audio file (default: ``"audio_ext"``) + """ + def __init__( + self, + root: Union[str, Path], + release: str = "release1", + subset: str = None, + download: bool = False, + audio_ext: str = ".sph" + ) -> None: + self._ext_audio = audio_ext + if release in _RELEASE_CONFIGS.keys(): + folder_in_archive = _RELEASE_CONFIGS[release]["folder_in_archive"] + url = _RELEASE_CONFIGS[release]["url"] + subset = subset if subset else _RELEASE_CONFIGS[release]["subset"] + else: + # Raise warning + raise RuntimeError( + "The release {} does not match any of the supported tedlium releases{} ".format( + release, _RELEASE_CONFIGS.keys(), + ) + ) + if subset not in _RELEASE_CONFIGS[release]["supported_subsets"]: + # Raise warning + raise RuntimeError( + "The subset {} does not match any of the supported tedlium subsets{} ".format( + subset, _RELEASE_CONFIGS[release]["supported_subsets"], + ) + ) + + # Get string representation of 'root' in case Path object is passed + root = os.fspath(root) + + basename = os.path.basename(url) + archive = os.path.join(root, basename) + + basename = basename.split(".")[0] + + self._path = os.path.join(root, folder_in_archive, _RELEASE_CONFIGS[release]["data_path"]) + if subset in ["train", "dev", "test"]: + self._path = os.path.join(self._path, subset) + + if download: + if not os.path.isdir(self._path): + if not os.path.isfile(archive): + checksum = _RELEASE_CONFIGS[release]["checksum"] + download_url(url, root, hash_value=checksum) + extract_archive(archive) + + # Create list for all samples + self._filelist = [] + stm_path = os.path.join(self._path, "stm") + for file in sorted(os.listdir(stm_path)): + if file.endswith(".stm"): + stm_path = os.path.join(self._path, "stm", file) + with open(stm_path) as f: + l = len(f.readlines()) + file = file.replace(".stm", "") + self._filelist.extend((file, line) for line in range(l)) + # Create dict path for later read + self._dict_path = os.path.join(root, folder_in_archive, _RELEASE_CONFIGS[release]["dict"]) + self._phoneme_dict = None + + def _load_tedlium_item(self, fileid: str, line: int, path: str) -> Tuple[Tensor, int, str, int, int, int]: + """Loads a TEDLIUM dataset sample given a file name and corresponding sentence name. + + Args: + fileid (str): File id to identify both text and audio files corresponding to the sample + line (int): Line identifier for the sample inside the text file + path (str): Dataset root path + + Returns: + (Tensor, int, str, int, int, int): + ``(waveform, sample_rate, transcript, talk_id, speaker_id, identifier)`` + """ + transcript_path = os.path.join(path, "stm", fileid) + with open(transcript_path + ".stm") as f: + transcript = f.readlines()[line] + talk_id, _, speaker_id, start_time, end_time, identifier, transcript = transcript.split(" ", 6) + + wave_path = os.path.join(path, "sph", fileid) + waveform, sample_rate = self._load_audio(wave_path + self._ext_audio, start_time=start_time, end_time=end_time) + + return (waveform, sample_rate, transcript, talk_id, speaker_id, identifier) + + def _load_audio(self, path: str, start_time: float, end_time: float, sample_rate: int = 16000) -> [Tensor, int]: + """Default load function used in TEDLIUM dataset, you can overwrite this function to customize functionality + and load individual sentences from a full ted audio talk file. + + Args: + path (str): Path to audio file + start_time (int): Time in seconds where the sample sentence stars + end_time (int): Time in seconds where the sample sentence finishes + sample_rate (float, optional): Sampling rate + + Returns: + [Tensor, int]: Audio tensor representation and sample rate + """ + start_time = int(float(start_time) * sample_rate) + end_time = int(float(end_time) * sample_rate) + + kwargs = {"frame_offset": start_time, "num_frames": end_time - start_time} + + return torchaudio.load(path, **kwargs) + + def __getitem__(self, n: int) -> Tuple[Tensor, int, str, int, int, int]: + """Load the n-th sample from the dataset. + + Args: + n (int): The index of the sample to be loaded + + Returns: + tuple: ``(waveform, sample_rate, transcript, talk_id, speaker_id, identifier)`` + """ + fileid, line = self._filelist[n] + return self._load_tedlium_item(fileid, line, self._path) + + def __len__(self) -> int: + """TEDLIUM dataset custom function overwritting len default behaviour. + + Returns: + int: TEDLIUM dataset length + """ + return len(self._filelist) + + @property + def phoneme_dict(self): + """dict[str, tuple[str]]: Phonemes. Mapping from word to tuple of phonemes. + Note that some words have empty phonemes. + """ + # Read phoneme dictionary + if not self._phoneme_dict: + self._phoneme_dict = {} + with open(self._dict_path, "r", encoding="utf-8") as f: + for line in f.readlines(): + content = line.strip().split() + self._phoneme_dict[content[0]] = tuple(content[1:]) # content[1:] can be empty list + return self._phoneme_dict.copy() diff --git a/torchaudio/datasets/utils.py b/torchaudio/datasets/utils.py new file mode 100644 index 00000000..e105eb64 --- /dev/null +++ b/torchaudio/datasets/utils.py @@ -0,0 +1,284 @@ +import hashlib +import logging +import os +import tarfile +import threading +import urllib +import urllib.request +import zipfile +from queue import Queue +from typing import Any, Iterable, List, Optional + +import torch +from torch.utils.data import Dataset +from torch.utils.model_zoo import tqdm + +from torchaudio._internal.module_utils import deprecated + + +def stream_url(url: str, + start_byte: Optional[int] = None, + block_size: int = 32 * 1024, + progress_bar: bool = True) -> Iterable: + """Stream url by chunk + + Args: + url (str): Url. + start_byte (int or None, optional): Start streaming at that point (Default: ``None``). + block_size (int, optional): Size of chunks to stream (Default: ``32 * 1024``). + progress_bar (bool, optional): Display a progress bar (Default: ``True``). + """ + + # If we already have the whole file, there is no need to download it again + req = urllib.request.Request(url, method="HEAD") + with urllib.request.urlopen(req) as response: + url_size = int(response.info().get("Content-Length", -1)) + if url_size == start_byte: + return + + req = urllib.request.Request(url) + if start_byte: + req.headers["Range"] = "bytes={}-".format(start_byte) + + with urllib.request.urlopen(req) as upointer, tqdm( + unit="B", + unit_scale=True, + unit_divisor=1024, + total=url_size, + disable=not progress_bar, + ) as pbar: + + num_bytes = 0 + while True: + chunk = upointer.read(block_size) + if not chunk: + break + yield chunk + num_bytes += len(chunk) + pbar.update(len(chunk)) + + +def download_url(url: str, + download_folder: str, + filename: Optional[str] = None, + hash_value: Optional[str] = None, + hash_type: str = "sha256", + progress_bar: bool = True, + resume: bool = False) -> None: + """Download file to disk. + + Args: + url (str): Url. + download_folder (str): Folder to download file. + filename (str or None, optional): Name of downloaded file. If None, it is inferred from the url + (Default: ``None``). + hash_value (str or None, optional): Hash for url (Default: ``None``). + hash_type (str, optional): Hash type, among "sha256" and "md5" (Default: ``"sha256"``). + progress_bar (bool, optional): Display a progress bar (Default: ``True``). + resume (bool, optional): Enable resuming download (Default: ``False``). + """ + + req = urllib.request.Request(url, method="HEAD") + req_info = urllib.request.urlopen(req).info() + + # Detect filename + filename = filename or req_info.get_filename() or os.path.basename(url) + filepath = os.path.join(download_folder, filename) + if resume and os.path.exists(filepath): + mode = "ab" + local_size: Optional[int] = os.path.getsize(filepath) + + elif not resume and os.path.exists(filepath): + raise RuntimeError( + "{} already exists. Delete the file manually and retry.".format(filepath) + ) + else: + mode = "wb" + local_size = None + + if hash_value and local_size == int(req_info.get("Content-Length", -1)): + with open(filepath, "rb") as file_obj: + if validate_file(file_obj, hash_value, hash_type): + return + raise RuntimeError( + "The hash of {} does not match. Delete the file manually and retry.".format( + filepath + ) + ) + + with open(filepath, mode) as fpointer: + for chunk in stream_url(url, start_byte=local_size, progress_bar=progress_bar): + fpointer.write(chunk) + + with open(filepath, "rb") as file_obj: + if hash_value and not validate_file(file_obj, hash_value, hash_type): + raise RuntimeError( + "The hash of {} does not match. Delete the file manually and retry.".format( + filepath + ) + ) + + +def validate_file(file_obj: Any, hash_value: str, hash_type: str = "sha256") -> bool: + """Validate a given file object with its hash. + + Args: + file_obj: File object to read from. + hash_value (str): Hash for url. + hash_type (str, optional): Hash type, among "sha256" and "md5" (Default: ``"sha256"``). + + Returns: + bool: return True if its a valid file, else False. + """ + + if hash_type == "sha256": + hash_func = hashlib.sha256() + elif hash_type == "md5": + hash_func = hashlib.md5() + else: + raise ValueError + + while True: + # Read by chunk to avoid filling memory + chunk = file_obj.read(1024 ** 2) + if not chunk: + break + hash_func.update(chunk) + + return hash_func.hexdigest() == hash_value + + +def extract_archive(from_path: str, to_path: Optional[str] = None, overwrite: bool = False) -> List[str]: + """Extract archive. + Args: + from_path (str): the path of the archive. + to_path (str or None, optional): the root path of the extraced files (directory of from_path) + (Default: ``None``) + overwrite (bool, optional): overwrite existing files (Default: ``False``) + + Returns: + List[str]: List of paths to extracted files even if not overwritten. + + Examples: + >>> url = 'http://www.quest.dcs.shef.ac.uk/wmt16_files_mmt/validation.tar.gz' + >>> from_path = './validation.tar.gz' + >>> to_path = './' + >>> torchaudio.datasets.utils.download_from_url(url, from_path) + >>> torchaudio.datasets.utils.extract_archive(from_path, to_path) + """ + + if to_path is None: + to_path = os.path.dirname(from_path) + + try: + with tarfile.open(from_path, "r") as tar: + logging.info("Opened tar file {}.".format(from_path)) + files = [] + for file_ in tar: # type: Any + file_path = os.path.join(to_path, file_.name) + if file_.isfile(): + files.append(file_path) + if os.path.exists(file_path): + logging.info("{} already extracted.".format(file_path)) + if not overwrite: + continue + tar.extract(file_, to_path) + return files + except tarfile.ReadError: + pass + + try: + with zipfile.ZipFile(from_path, "r") as zfile: + logging.info("Opened zip file {}.".format(from_path)) + files = zfile.namelist() + for file_ in files: + file_path = os.path.join(to_path, file_) + if os.path.exists(file_path): + logging.info("{} already extracted.".format(file_path)) + if not overwrite: + continue + zfile.extract(file_, to_path) + return files + except zipfile.BadZipFile: + pass + + raise NotImplementedError("We currently only support tar.gz, tgz, and zip achives.") + + +class _DiskCache(Dataset): + """ + Wrap a dataset so that, whenever a new item is returned, it is saved to disk. + """ + + def __init__(self, dataset: Dataset, location: str = ".cached") -> None: + self.dataset = dataset + self.location = location + + self._id = id(self) + self._cache: List = [None] * len(dataset) + + def __getitem__(self, n: int) -> Any: + if self._cache[n]: + f = self._cache[n] + return torch.load(f) + + f = str(self._id) + "-" + str(n) + f = os.path.join(self.location, f) + item = self.dataset[n] + + self._cache[n] = f + os.makedirs(self.location, exist_ok=True) + torch.save(item, f) + + return item + + def __len__(self) -> int: + return len(self.dataset) + + +@deprecated('', version='0.11') +def diskcache_iterator(dataset: Dataset, location: str = ".cached") -> Dataset: + return _DiskCache(dataset, location) + + +class _ThreadedIterator(threading.Thread): + """ + Prefetch the next queue_length items from iterator in a background thread. + + Example: + >> for i in bg_iterator(range(10)): + >> print(i) + """ + + class _End: + pass + + def __init__(self, generator: Iterable, maxsize: int) -> None: + threading.Thread.__init__(self) + self.queue: Queue = Queue(maxsize) + self.generator = generator + self.daemon = True + self.start() + + def run(self) -> None: + for item in self.generator: + self.queue.put(item) + self.queue.put(self._End) + + def __iter__(self) -> Any: + return self + + def __next__(self) -> Any: + next_item = self.queue.get() + if next_item == self._End: + raise StopIteration + return next_item + + # Required for Python 2.7 compatibility + def next(self) -> Any: + return self.__next__() + + +@deprecated('', version='0.11') +def bg_iterator(iterable: Iterable, maxsize: int) -> Any: + return _ThreadedIterator(iterable, maxsize=maxsize) diff --git a/torchaudio/datasets/vctk.py b/torchaudio/datasets/vctk.py new file mode 100644 index 00000000..65ec854a --- /dev/null +++ b/torchaudio/datasets/vctk.py @@ -0,0 +1,275 @@ +import os +import warnings +from pathlib import Path +from typing import Tuple, Union + +from torch import Tensor +from torch.utils.data import Dataset + +import torchaudio +from torchaudio.datasets.utils import ( + download_url, + extract_archive, +) + +URL = "https://datashare.is.ed.ac.uk/bitstream/handle/10283/3443/VCTK-Corpus-0.92.zip" +FOLDER_IN_ARCHIVE = "VCTK-Corpus" +_CHECKSUMS = { + "https://datashare.is.ed.ac.uk/bitstream/handle/10283/3443/VCTK-Corpus-0.92.zip": "8a6ba2946b36fcbef0212cad601f4bfa" +} + + +def load_vctk_item(fileid: str, + path: str, + ext_audio: str, + ext_txt: str, + folder_audio: str, + folder_txt: str, + downsample: bool = False) -> Tuple[Tensor, int, str, str, str]: + speaker_id, utterance_id = fileid.split("_") + + # Read text + file_txt = os.path.join(path, folder_txt, speaker_id, fileid + ext_txt) + with open(file_txt) as file_text: + utterance = file_text.readlines()[0] + + # Read wav + file_audio = os.path.join(path, folder_audio, speaker_id, fileid + ext_audio) + waveform, sample_rate = torchaudio.load(file_audio) + if downsample: + # TODO Remove this parameter after deprecation + F = torchaudio.functional + T = torchaudio.transforms + # rate + sample = T.Resample(sample_rate, 16000, resampling_method='sinc_interpolation') + waveform = sample(waveform) + # dither + waveform = F.dither(waveform, noise_shaping=True) + + return waveform, sample_rate, utterance, speaker_id, utterance_id + + +class VCTK(Dataset): + """Create a Dataset for VCTK. + + Note: + * **This dataset is no longer publicly available.** Please use :py:class:`VCTK_092` + * Directory ``p315`` is ignored because there is no corresponding text files. + For more information about the dataset visit: https://datashare.is.ed.ac.uk/handle/10283/3443 + + Args: + root (str or Path): Path to the directory where the dataset is found or downloaded. + url (str, optional): Not used as the dataset is no longer publicly available. + folder_in_archive (str, optional): + The top-level directory of the dataset. (default: ``"VCTK-Corpus"``) + download (bool, optional): + Whether to download the dataset if it is not found at root path. (default: ``False``). + Giving ``download=True`` will result in error as the dataset is no longer + publicly available. + downsample (bool, optional): Not used. + """ + + _folder_txt = "txt" + _folder_audio = "wav48" + _ext_txt = ".txt" + _ext_audio = ".wav" + _except_folder = "p315" + + def __init__(self, + root: Union[str, Path], + url: str = URL, + folder_in_archive: str = FOLDER_IN_ARCHIVE, + download: bool = False, + downsample: bool = False) -> None: + + warnings.warn( + 'VCTK class has been deprecated and will be removed in 0.11 release. ' + 'Please use VCTK_092.' + ) + + if downsample: + warnings.warn( + "In the next version, transforms will not be part of the dataset. " + "Please use `downsample=False` to enable this behavior now, " + "and suppress this warning." + ) + + self.downsample = downsample + # Get string representation of 'root' in case Path object is passed + root = os.fspath(root) + + archive = os.path.basename(url) + archive = os.path.join(root, archive) + self._path = os.path.join(root, folder_in_archive) + + if download: + raise RuntimeError( + "This Dataset is no longer available. " + "Please use `VCTK_092` class to download the latest version." + ) + + if not os.path.isdir(self._path): + raise RuntimeError( + "Dataset not found. Please use `VCTK_092` class " + "with `download=True` to donwload the latest version." + ) + + walker = sorted(str(p.stem) for p in Path(self._path).glob('**/*' + self._ext_audio)) + walker = filter(lambda w: self._except_folder not in w, walker) + self._walker = list(walker) + + def __getitem__(self, n: int) -> Tuple[Tensor, int, str, str, str]: + """Load the n-th sample from the dataset. + + Args: + n (int): The index of the sample to be loaded + + Returns: + tuple: ``(waveform, sample_rate, utterance, speaker_id, utterance_id)`` + """ + fileid = self._walker[n] + item = load_vctk_item( + fileid, + self._path, + self._ext_audio, + self._ext_txt, + self._folder_audio, + self._folder_txt, + ) + + # TODO Upon deprecation, uncomment line below and remove following code + # return item + + waveform, sample_rate, utterance, speaker_id, utterance_id = item + return waveform, sample_rate, utterance, speaker_id, utterance_id + + def __len__(self) -> int: + return len(self._walker) + + +SampleType = Tuple[Tensor, int, str, str, str] + + +class VCTK_092(Dataset): + """Create VCTK 0.92 Dataset + + Args: + root (str): Root directory where the dataset's top level directory is found. + mic_id (str, optional): Microphone ID. Either ``"mic1"`` or ``"mic2"``. (default: ``"mic2"``) + download (bool, optional): + Whether to download the dataset if it is not found at root path. (default: ``False``). + url (str, optional): The URL to download the dataset from. + (default: ``"https://datashare.is.ed.ac.uk/bitstream/handle/10283/3443/VCTK-Corpus-0.92.zip"``) + audio_ext (str, optional): Custom audio extension if dataset is converted to non-default audio format. + + Note: + * All the speeches from speaker ``p315`` will be skipped due to the lack of the corresponding text files. + * All the speeches from ``p280`` will be skipped for ``mic_id="mic2"`` due to the lack of the audio files. + * Some of the speeches from speaker ``p362`` will be skipped due to the lack of the audio files. + * See Also: https://datashare.is.ed.ac.uk/handle/10283/3443 + """ + + def __init__( + self, + root: str, + mic_id: str = "mic2", + download: bool = False, + url: str = URL, + audio_ext=".flac", + ): + if mic_id not in ["mic1", "mic2"]: + raise RuntimeError( + f'`mic_id` has to be either "mic1" or "mic2". Found: {mic_id}' + ) + + archive = os.path.join(root, "VCTK-Corpus-0.92.zip") + + self._path = os.path.join(root, "VCTK-Corpus-0.92") + self._txt_dir = os.path.join(self._path, "txt") + self._audio_dir = os.path.join(self._path, "wav48_silence_trimmed") + self._mic_id = mic_id + self._audio_ext = audio_ext + + if download: + if not os.path.isdir(self._path): + if not os.path.isfile(archive): + checksum = _CHECKSUMS.get(url, None) + download_url(url, root, hash_value=checksum, hash_type="md5") + extract_archive(archive, self._path) + + if not os.path.isdir(self._path): + raise RuntimeError( + "Dataset not found. Please use `download=True` to download it." + ) + + # Extracting speaker IDs from the folder structure + self._speaker_ids = sorted(os.listdir(self._txt_dir)) + self._sample_ids = [] + + """ + Due to some insufficient data complexity in the 0.92 version of this dataset, + we start traversing the audio folder structure in accordance with the text folder. + As some of the audio files are missing of either ``mic_1`` or ``mic_2`` but the + text is present for the same, we first check for the existence of the audio file + before adding it to the ``sample_ids`` list. + + Once the ``audio_ids`` are loaded into memory we can quickly access the list for + different parameters required by the user. + """ + for speaker_id in self._speaker_ids: + if speaker_id == "p280" and mic_id == "mic2": + continue + utterance_dir = os.path.join(self._txt_dir, speaker_id) + for utterance_file in sorted( + f for f in os.listdir(utterance_dir) if f.endswith(".txt") + ): + utterance_id = os.path.splitext(utterance_file)[0] + audio_path_mic = os.path.join( + self._audio_dir, + speaker_id, + f"{utterance_id}_{mic_id}{self._audio_ext}", + ) + if speaker_id == "p362" and not os.path.isfile(audio_path_mic): + continue + self._sample_ids.append(utterance_id.split("_")) + + def _load_text(self, file_path) -> str: + with open(file_path) as file_path: + return file_path.readlines()[0] + + def _load_audio(self, file_path) -> Tuple[Tensor, int]: + return torchaudio.load(file_path) + + def _load_sample(self, speaker_id: str, utterance_id: str, mic_id: str) -> SampleType: + transcript_path = os.path.join( + self._txt_dir, speaker_id, f"{speaker_id}_{utterance_id}.txt" + ) + audio_path = os.path.join( + self._audio_dir, + speaker_id, + f"{speaker_id}_{utterance_id}_{mic_id}{self._audio_ext}", + ) + + # Reading text + transcript = self._load_text(transcript_path) + + # Reading FLAC + waveform, sample_rate = self._load_audio(audio_path) + + return (waveform, sample_rate, transcript, speaker_id, utterance_id) + + def __getitem__(self, n: int) -> SampleType: + """Load the n-th sample from the dataset. + + Args: + n (int): The index of the sample to be loaded + + Returns: + (Tensor, int, str, str, str): + ``(waveform, sample_rate, transcript, speaker_id, utterance_id)`` + """ + speaker_id, utterance_id = self._sample_ids[n] + return self._load_sample(speaker_id, utterance_id, self._mic_id) + + def __len__(self) -> int: + return len(self._sample_ids) diff --git a/torchaudio/datasets/yesno.py b/torchaudio/datasets/yesno.py new file mode 100644 index 00000000..f33c1185 --- /dev/null +++ b/torchaudio/datasets/yesno.py @@ -0,0 +1,87 @@ +import os +from pathlib import Path +from typing import List, Tuple, Union + +from torch import Tensor +from torch.utils.data import Dataset + +import torchaudio +from torchaudio.datasets.utils import ( + download_url, + extract_archive, +) + + +_RELEASE_CONFIGS = { + "release1": { + "folder_in_archive": "waves_yesno", + "url": "http://www.openslr.org/resources/1/waves_yesno.tar.gz", + "checksum": "c3f49e0cca421f96b75b41640749167b52118f232498667ca7a5f9416aef8e73", + } +} + + +class YESNO(Dataset): + """Create a Dataset for YesNo. + + Args: + root (str or Path): Path to the directory where the dataset is found or downloaded. + url (str, optional): The URL to download the dataset from. + (default: ``"http://www.openslr.org/resources/1/waves_yesno.tar.gz"``) + folder_in_archive (str, optional): + The top-level directory of the dataset. (default: ``"waves_yesno"``) + download (bool, optional): + Whether to download the dataset if it is not found at root path. (default: ``False``). + """ + + def __init__( + self, + root: Union[str, Path], + url: str = _RELEASE_CONFIGS["release1"]["url"], + folder_in_archive: str = _RELEASE_CONFIGS["release1"]["folder_in_archive"], + download: bool = False + ) -> None: + + self._parse_filesystem(root, url, folder_in_archive, download) + + def _parse_filesystem(self, root: str, url: str, folder_in_archive: str, download: bool) -> None: + root = Path(root) + archive = os.path.basename(url) + archive = root / archive + + self._path = root / folder_in_archive + if download: + if not os.path.isdir(self._path): + if not os.path.isfile(archive): + checksum = _RELEASE_CONFIGS["release1"]["checksum"] + download_url(url, root, hash_value=checksum) + extract_archive(archive) + + if not os.path.isdir(self._path): + raise RuntimeError( + "Dataset not found. Please use `download=True` to download it." + ) + + self._walker = sorted(str(p.stem) for p in Path(self._path).glob("*.wav")) + + def _load_item(self, fileid: str, path: str): + labels = [int(c) for c in fileid.split("_")] + file_audio = os.path.join(path, fileid + ".wav") + waveform, sample_rate = torchaudio.load(file_audio) + return waveform, sample_rate, labels + + def __getitem__(self, n: int) -> Tuple[Tensor, int, List[int]]: + """Load the n-th sample from the dataset. + + Args: + n (int): The index of the sample to be loaded + + Returns: + (Tensor, int, List[int]): ``(waveform, sample_rate, labels)`` + """ + fileid = self._walker[n] + item = self._load_item(fileid, self._path) + return item + + def __len__(self) -> int: + return len(self._walker) diff --git a/torchaudio/functional/__init__.py b/torchaudio/functional/__init__.py new file mode 100644 index 00000000..fc3173cd --- /dev/null +++ b/torchaudio/functional/__init__.py @@ -0,0 +1,105 @@ +from .functional import ( + amplitude_to_DB, + angle, + complex_norm, + compute_deltas, + compute_kaldi_pitch, + create_dct, + create_fb_matrix, + melscale_fbanks, + linear_fbanks, + DB_to_amplitude, + detect_pitch_frequency, + inverse_spectrogram, + griffinlim, + magphase, + mask_along_axis, + mask_along_axis_iid, + mu_law_encoding, + mu_law_decoding, + phase_vocoder, + sliding_window_cmn, + spectrogram, + spectral_centroid, + apply_codec, + resample, + edit_distance, + pitch_shift, + rnnt_loss, +) +from .filtering import ( + allpass_biquad, + band_biquad, + bandpass_biquad, + bandreject_biquad, + bass_biquad, + biquad, + contrast, + dither, + dcshift, + deemph_biquad, + equalizer_biquad, + filtfilt, + flanger, + gain, + highpass_biquad, + lfilter, + lowpass_biquad, + overdrive, + phaser, + riaa_biquad, + treble_biquad, + vad, +) + +__all__ = [ + 'amplitude_to_DB', + 'angle', + 'complex_norm', + 'compute_deltas', + 'compute_kaldi_pitch', + 'create_dct', + 'create_fb_matrix', + 'melscale_fbanks', + 'linear_fbanks', + 'DB_to_amplitude', + 'detect_pitch_frequency', + 'griffinlim', + 'magphase', + 'mask_along_axis', + 'mask_along_axis_iid', + 'mu_law_encoding', + 'mu_law_decoding', + 'phase_vocoder', + 'sliding_window_cmn', + 'spectrogram', + 'inverse_spectrogram', + 'spectral_centroid', + 'allpass_biquad', + 'band_biquad', + 'bandpass_biquad', + 'bandreject_biquad', + 'bass_biquad', + 'biquad', + 'contrast', + 'dither', + 'dcshift', + 'deemph_biquad', + 'equalizer_biquad', + 'filtfilt', + 'flanger', + 'gain', + 'highpass_biquad', + 'lfilter', + 'lowpass_biquad', + 'overdrive', + 'phaser', + 'riaa_biquad', + 'treble_biquad', + 'vad', + 'apply_codec', + 'resample', + 'edit_distance', + 'pitch_shift', + 'rnnt_loss', +] diff --git a/torchaudio/functional/filtering.py b/torchaudio/functional/filtering.py new file mode 100644 index 00000000..0f5d24f6 --- /dev/null +++ b/torchaudio/functional/filtering.py @@ -0,0 +1,1636 @@ +import math +import warnings +from typing import Optional + +import torch +from torch import Tensor + + +def _dB2Linear(x: float) -> float: + return math.exp(x * math.log(10) / 20.0) + + +def _generate_wave_table( + wave_type: str, + data_type: str, + table_size: int, + min: float, + max: float, + phase: float, + device: torch.device, +) -> Tensor: + r"""A helper function for phaser. Generates a table with given parameters. + + Args: + wave_type (str): SINE or TRIANGULAR + data_type (str): desired data_type ( `INT` or `FLOAT` ) + table_size (int): desired table size + min (float): desired min value + max (float): desired max value + phase (float): desired phase + device (torch.device): Torch device on which table must be generated + Returns: + Tensor: A 1D tensor with wave table values + """ + + phase_offset = int(phase / math.pi / 2 * table_size + 0.5) + + t = torch.arange(table_size, device=device, dtype=torch.int32) + + point = (t + phase_offset) % table_size + + d = torch.zeros_like(point, device=device, dtype=torch.float64) + + if wave_type == "SINE": + d = (torch.sin(point.to(torch.float64) / table_size * 2 * math.pi) + 1) / 2 + elif wave_type == "TRIANGLE": + d = point.to(torch.float64) * 2 / table_size + value = torch.div(4 * point, table_size, rounding_mode='floor') + d[value == 0] = d[value == 0] + 0.5 + d[value == 1] = 1.5 - d[value == 1] + d[value == 2] = 1.5 - d[value == 2] + d[value == 3] = d[value == 3] - 1.5 + + d = d * (max - min) + min + + if data_type == "INT": + mask = d < 0 + d[mask] = d[mask] - 0.5 + d[~mask] = d[~mask] + 0.5 + d = d.to(torch.int32) + elif data_type == "FLOAT": + d = d.to(torch.float32) + + return d + + +def allpass_biquad( + waveform: Tensor, sample_rate: int, central_freq: float, Q: float = 0.707 +) -> Tensor: + r"""Design two-pole all-pass filter. Similar to SoX implementation. + + Args: + waveform(torch.Tensor): audio waveform of dimension of `(..., time)` + sample_rate (int): sampling rate of the waveform, e.g. 44100 (Hz) + central_freq (float or torch.Tensor): central frequency (in Hz) + Q (float or torch.Tensor, optional): https://en.wikipedia.org/wiki/Q_factor (Default: ``0.707``) + + Returns: + Tensor: Waveform of dimension of `(..., time)` + + Reference: + - http://sox.sourceforge.net/sox.html + - https://www.w3.org/2011/audio/audio-eq-cookbook.html#APF + """ + dtype = waveform.dtype + device = waveform.device + central_freq = torch.as_tensor(central_freq, dtype=dtype, device=device) + Q = torch.as_tensor(Q, dtype=dtype, device=device) + + w0 = 2 * math.pi * central_freq / sample_rate + + alpha = torch.sin(w0) / 2 / Q + + b0 = 1 - alpha + b1 = -2 * torch.cos(w0) + b2 = 1 + alpha + a0 = 1 + alpha + a1 = -2 * torch.cos(w0) + a2 = 1 - alpha + return biquad(waveform, b0, b1, b2, a0, a1, a2) + + +def band_biquad( + waveform: Tensor, + sample_rate: int, + central_freq: float, + Q: float = 0.707, + noise: bool = False, +) -> Tensor: + r"""Design two-pole band filter. Similar to SoX implementation. + + Args: + waveform (Tensor): audio waveform of dimension of `(..., time)` + sample_rate (int): sampling rate of the waveform, e.g. 44100 (Hz) + central_freq (float or torch.Tensor): central frequency (in Hz) + Q (float or torch.Tensor, optional): https://en.wikipedia.org/wiki/Q_factor (Default: ``0.707``). + noise (bool, optional) : If ``True``, uses the alternate mode for un-pitched audio (e.g. percussion). + If ``False``, uses mode oriented to pitched audio, i.e. voice, singing, + or instrumental music (Default: ``False``). + + Returns: + Tensor: Waveform of dimension of `(..., time)` + + Reference: + - http://sox.sourceforge.net/sox.html + - https://www.w3.org/2011/audio/audio-eq-cookbook.html#APF + """ + dtype = waveform.dtype + device = waveform.device + central_freq = torch.as_tensor(central_freq, dtype=dtype, device=device) + Q = torch.as_tensor(Q, dtype=dtype, device=device) + + w0 = 2 * math.pi * central_freq / sample_rate + bw_Hz = central_freq / Q + + a0 = 1.0 + a2 = torch.exp(-2 * math.pi * bw_Hz / sample_rate) + a1 = -4 * a2 / (1 + a2) * torch.cos(w0) + + b0 = torch.sqrt(1 - a1 * a1 / (4 * a2)) * (1 - a2) + + if noise: + mult = torch.sqrt(((1 + a2) * (1 + a2) - a1 * a1) * (1 - a2) / (1 + a2)) / b0 + b0 = mult * b0 + + b1 = 0.0 + b2 = 0.0 + + return biquad(waveform, b0, b1, b2, a0, a1, a2) + + +def bandpass_biquad( + waveform: Tensor, + sample_rate: int, + central_freq: float, + Q: float = 0.707, + const_skirt_gain: bool = False, +) -> Tensor: + r"""Design two-pole band-pass filter. Similar to SoX implementation. + + Args: + waveform (Tensor): audio waveform of dimension of `(..., time)` + sample_rate (int): sampling rate of the waveform, e.g. 44100 (Hz) + central_freq (float or torch.Tensor): central frequency (in Hz) + Q (float or torch.Tensor, optional): https://en.wikipedia.org/wiki/Q_factor (Default: ``0.707``) + const_skirt_gain (bool, optional) : If ``True``, uses a constant skirt gain (peak gain = Q). + If ``False``, uses a constant 0dB peak gain. (Default: ``False``) + + Returns: + Tensor: Waveform of dimension of `(..., time)` + + Reference: + - http://sox.sourceforge.net/sox.html + - https://www.w3.org/2011/audio/audio-eq-cookbook.html#APF + """ + dtype = waveform.dtype + device = waveform.device + central_freq = torch.as_tensor(central_freq, dtype=dtype, device=device) + Q = torch.as_tensor(Q, dtype=dtype, device=device) + + w0 = 2 * math.pi * central_freq / sample_rate + alpha = torch.sin(w0) / 2 / Q + + temp = torch.sin(w0) / 2 if const_skirt_gain else alpha + b0 = temp + b1 = 0.0 + b2 = -temp + a0 = 1 + alpha + a1 = -2 * torch.cos(w0) + a2 = 1 - alpha + return biquad(waveform, b0, b1, b2, a0, a1, a2) + + +def bandreject_biquad( + waveform: Tensor, sample_rate: int, central_freq: float, Q: float = 0.707 +) -> Tensor: + r"""Design two-pole band-reject filter. Similar to SoX implementation. + + Args: + waveform (Tensor): audio waveform of dimension of `(..., time)` + sample_rate (int): sampling rate of the waveform, e.g. 44100 (Hz) + central_freq (float or torch.Tensor): central frequency (in Hz) + Q (float or torch.Tensor, optional): https://en.wikipedia.org/wiki/Q_factor (Default: ``0.707``) + + Returns: + Tensor: Waveform of dimension of `(..., time)` + + Reference: + - http://sox.sourceforge.net/sox.html + - https://www.w3.org/2011/audio/audio-eq-cookbook.html#APF + """ + dtype = waveform.dtype + device = waveform.device + central_freq = torch.as_tensor(central_freq, dtype=dtype, device=device) + Q = torch.as_tensor(Q, dtype=dtype, device=device) + + w0 = 2 * math.pi * central_freq / sample_rate + alpha = torch.sin(w0) / 2 / Q + + b0 = 1.0 + b1 = -2 * torch.cos(w0) + b2 = 1.0 + a0 = 1 + alpha + a1 = -2 * torch.cos(w0) + a2 = 1 - alpha + return biquad(waveform, b0, b1, b2, a0, a1, a2) + + +def bass_biquad( + waveform: Tensor, + sample_rate: int, + gain: float, + central_freq: float = 100, + Q: float = 0.707, +) -> Tensor: + r"""Design a bass tone-control effect. Similar to SoX implementation. + + Args: + waveform (Tensor): audio waveform of dimension of `(..., time)` + sample_rate (int): sampling rate of the waveform, e.g. 44100 (Hz) + gain (float or torch.Tensor): desired gain at the boost (or attenuation) in dB. + central_freq (float or torch.Tensor, optional): central frequency (in Hz). (Default: ``100``) + Q (float or torch.Tensor, optional): https://en.wikipedia.org/wiki/Q_factor (Default: ``0.707``). + + Returns: + Tensor: Waveform of dimension of `(..., time)` + + Reference: + - http://sox.sourceforge.net/sox.html + - https://www.w3.org/2011/audio/audio-eq-cookbook.html#APF + """ + dtype = waveform.dtype + device = waveform.device + central_freq = torch.as_tensor(central_freq, dtype=dtype, device=device) + Q = torch.as_tensor(Q, dtype=dtype, device=device) + gain = torch.as_tensor(gain, dtype=dtype, device=device) + + w0 = 2 * math.pi * central_freq / sample_rate + alpha = torch.sin(w0) / 2 / Q + A = torch.exp(gain / 40 * math.log(10)) + + temp1 = 2 * torch.sqrt(A) * alpha + temp2 = (A - 1) * torch.cos(w0) + temp3 = (A + 1) * torch.cos(w0) + + b0 = A * ((A + 1) - temp2 + temp1) + b1 = 2 * A * ((A - 1) - temp3) + b2 = A * ((A + 1) - temp2 - temp1) + a0 = (A + 1) + temp2 + temp1 + a1 = -2 * ((A - 1) + temp3) + a2 = (A + 1) + temp2 - temp1 + + return biquad(waveform, b0 / a0, b1 / a0, b2 / a0, a0 / a0, a1 / a0, a2 / a0) + + +def biquad( + waveform: Tensor, b0: float, b1: float, b2: float, a0: float, a1: float, a2: float +) -> Tensor: + r"""Perform a biquad filter of input tensor. Initial conditions set to 0. + https://en.wikipedia.org/wiki/Digital_biquad_filter + + Args: + waveform (Tensor): audio waveform of dimension of `(..., time)` + b0 (float or torch.Tensor): numerator coefficient of current input, x[n] + b1 (float or torch.Tensor): numerator coefficient of input one time step ago x[n-1] + b2 (float or torch.Tensor): numerator coefficient of input two time steps ago x[n-2] + a0 (float or torch.Tensor): denominator coefficient of current output y[n], typically 1 + a1 (float or torch.Tensor): denominator coefficient of current output y[n-1] + a2 (float or torch.Tensor): denominator coefficient of current output y[n-2] + + Returns: + Tensor: Waveform with dimension of `(..., time)` + """ + + device = waveform.device + dtype = waveform.dtype + + b0 = torch.as_tensor(b0, dtype=dtype, device=device).view(1) + b1 = torch.as_tensor(b1, dtype=dtype, device=device).view(1) + b2 = torch.as_tensor(b2, dtype=dtype, device=device).view(1) + a0 = torch.as_tensor(a0, dtype=dtype, device=device).view(1) + a1 = torch.as_tensor(a1, dtype=dtype, device=device).view(1) + a2 = torch.as_tensor(a2, dtype=dtype, device=device).view(1) + + output_waveform = lfilter( + waveform, + torch.cat([a0, a1, a2]), + torch.cat([b0, b1, b2]), + ) + return output_waveform + + +def contrast(waveform: Tensor, enhancement_amount: float = 75.0) -> Tensor: + r"""Apply contrast effect. Similar to SoX implementation. + Comparable with compression, this effect modifies an audio signal to make it sound louder + + Args: + waveform (Tensor): audio waveform of dimension of `(..., time)` + enhancement_amount (float, optional): controls the amount of the enhancement + Allowed range of values for enhancement_amount : 0-100 + Note that enhancement_amount = 0 still gives a significant contrast enhancement + + Returns: + Tensor: Waveform of dimension of `(..., time)` + + Reference: + - http://sox.sourceforge.net/sox.html + """ + + if not 0 <= enhancement_amount <= 100: + raise ValueError("Allowed range of values for enhancement_amount : 0-100") + + contrast = enhancement_amount / 750.0 + + temp1 = waveform * (math.pi / 2) + temp2 = contrast * torch.sin(temp1 * 4) + output_waveform = torch.sin(temp1 + temp2) + + return output_waveform + + +def dcshift( + waveform: Tensor, shift: float, limiter_gain: Optional[float] = None +) -> Tensor: + r"""Apply a DC shift to the audio. Similar to SoX implementation. + This can be useful to remove a DC offset + (caused perhaps by a hardware problem in the recording chain) from the audio + + Args: + waveform (Tensor): audio waveform of dimension of `(..., time)` + shift (float): indicates the amount to shift the audio + Allowed range of values for shift : -2.0 to +2.0 + limiter_gain (float of None, optional): It is used only on peaks to prevent clipping + It should have a value much less than 1 (e.g. 0.05 or 0.02) + + Returns: + Tensor: Waveform of dimension of `(..., time)` + + Reference: + - http://sox.sourceforge.net/sox.html + """ + output_waveform = waveform + limiter_threshold = 0.0 + + if limiter_gain is not None: + limiter_threshold = 1.0 - (abs(shift) - limiter_gain) + + if limiter_gain is not None and shift > 0: + mask = waveform > limiter_threshold + temp = ( + (waveform[mask] - limiter_threshold) + * limiter_gain + / (1 - limiter_threshold) + ) + output_waveform[mask] = (temp + limiter_threshold + shift).clamp( + max=limiter_threshold + ) + output_waveform[~mask] = (waveform[~mask] + shift).clamp(min=-1, max=1) + elif limiter_gain is not None and shift < 0: + mask = waveform < -limiter_threshold + temp = ( + (waveform[mask] + limiter_threshold) + * limiter_gain + / (1 - limiter_threshold) + ) + output_waveform[mask] = (temp - limiter_threshold + shift).clamp( + min=-limiter_threshold + ) + output_waveform[~mask] = (waveform[~mask] + shift).clamp(min=-1, max=1) + else: + output_waveform = (waveform + shift).clamp(min=-1, max=1) + + return output_waveform + + +def deemph_biquad(waveform: Tensor, sample_rate: int) -> Tensor: + r"""Apply ISO 908 CD de-emphasis (shelving) IIR filter. Similar to SoX implementation. + + Args: + waveform (Tensor): audio waveform of dimension of `(..., time)` + sample_rate (int): sampling rate of the waveform, Allowed sample rate ``44100`` or ``48000`` + + Returns: + Tensor: Waveform of dimension of `(..., time)` + + Reference: + - http://sox.sourceforge.net/sox.html + - https://www.w3.org/2011/audio/audio-eq-cookbook.html#APF + """ + + if sample_rate == 44100: + central_freq = 5283 + width_slope = 0.4845 + gain = -9.477 + elif sample_rate == 48000: + central_freq = 5356 + width_slope = 0.479 + gain = -9.62 + else: + raise ValueError("Sample rate must be 44100 (audio-CD) or 48000 (DAT)") + + w0 = 2 * math.pi * central_freq / sample_rate + A = math.exp(gain / 40.0 * math.log(10)) + alpha = math.sin(w0) / 2 * math.sqrt((A + 1 / A) * (1 / width_slope - 1) + 2) + + temp1 = 2 * math.sqrt(A) * alpha + temp2 = (A - 1) * math.cos(w0) + temp3 = (A + 1) * math.cos(w0) + + b0 = A * ((A + 1) + temp2 + temp1) + b1 = -2 * A * ((A - 1) + temp3) + b2 = A * ((A + 1) + temp2 - temp1) + a0 = (A + 1) - temp2 + temp1 + a1 = 2 * ((A - 1) - temp3) + a2 = (A + 1) - temp2 - temp1 + + return biquad(waveform, b0, b1, b2, a0, a1, a2) + + +def _add_noise_shaping(dithered_waveform: Tensor, waveform: Tensor) -> Tensor: + r"""Noise shaping is calculated by error: + error[n] = dithered[n] - original[n] + noise_shaped_waveform[n] = dithered[n] + error[n-1] + """ + wf_shape = waveform.size() + waveform = waveform.reshape(-1, wf_shape[-1]) + + dithered_shape = dithered_waveform.size() + dithered_waveform = dithered_waveform.reshape(-1, dithered_shape[-1]) + + error = dithered_waveform - waveform + + # add error[n-1] to dithered_waveform[n], so offset the error by 1 index + zeros = torch.zeros(1, dtype=error.dtype, device=error.device) + for index in range(error.size()[0]): + err = error[index] + error_offset = torch.cat((zeros, err)) + error[index] = error_offset[: waveform.size()[1]] + + noise_shaped = dithered_waveform + error + return noise_shaped.reshape(dithered_shape[:-1] + noise_shaped.shape[-1:]) + + +def _apply_probability_distribution( + waveform: Tensor, density_function: str = "TPDF" +) -> Tensor: + r"""Apply a probability distribution function on a waveform. + + Triangular probability density function (TPDF) dither noise has a + triangular distribution; values in the center of the range have a higher + probability of occurring. + + Rectangular probability density function (RPDF) dither noise has a + uniform distribution; any value in the specified range has the same + probability of occurring. + + Gaussian probability density function (GPDF) has a normal distribution. + The relationship of probabilities of results follows a bell-shaped, + or Gaussian curve, typical of dither generated by analog sources. + Args: + waveform (Tensor): Tensor of audio of dimension (..., time) + density_function (str, optional): The density function of a + continuous random variable (Default: ``"TPDF"``) + Options: Triangular Probability Density Function - `TPDF` + Rectangular Probability Density Function - `RPDF` + Gaussian Probability Density Function - `GPDF` + Returns: + Tensor: waveform dithered with TPDF + """ + + # pack batch + shape = waveform.size() + waveform = waveform.reshape(-1, shape[-1]) + + channel_size = waveform.size()[0] - 1 + time_size = waveform.size()[-1] - 1 + + random_channel = ( + int( + torch.randint( + channel_size, + [ + 1, + ], + ).item() + ) + if channel_size > 0 + else 0 + ) + random_time = ( + int( + torch.randint( + time_size, + [ + 1, + ], + ).item() + ) + if time_size > 0 + else 0 + ) + + number_of_bits = 16 + up_scaling = 2 ** (number_of_bits - 1) - 2 + signal_scaled = waveform * up_scaling + down_scaling = 2 ** (number_of_bits - 1) + + signal_scaled_dis = waveform + if density_function == "RPDF": + RPDF = waveform[random_channel][random_time] - 0.5 + + signal_scaled_dis = signal_scaled + RPDF + elif density_function == "GPDF": + # TODO Replace by distribution code once + # https://github.com/pytorch/pytorch/issues/29843 is resolved + # gaussian = torch.distributions.normal.Normal(torch.mean(waveform, -1), 1).sample() + + num_rand_variables = 6 + + gaussian = waveform[random_channel][random_time] + for ws in num_rand_variables * [time_size]: + rand_chan = int( + torch.randint( + channel_size, + [ + 1, + ], + ).item() + ) + gaussian += waveform[rand_chan][ + int( + torch.randint( + ws, + [ + 1, + ], + ).item() + ) + ] + + signal_scaled_dis = signal_scaled + gaussian + else: + # dtype needed for https://github.com/pytorch/pytorch/issues/32358 + TPDF = torch.bartlett_window( + time_size + 1, dtype=signal_scaled.dtype, device=signal_scaled.device + ) + TPDF = TPDF.repeat((channel_size + 1), 1) + signal_scaled_dis = signal_scaled + TPDF + + quantised_signal_scaled = torch.round(signal_scaled_dis) + quantised_signal = quantised_signal_scaled / down_scaling + + # unpack batch + return quantised_signal.reshape(shape[:-1] + quantised_signal.shape[-1:]) + + +def dither( + waveform: Tensor, density_function: str = "TPDF", noise_shaping: bool = False +) -> Tensor: + r"""Dither increases the perceived dynamic range of audio stored at a + particular bit-depth by eliminating nonlinear truncation distortion + (i.e. adding minimally perceived noise to mask distortion caused by quantization). + + Args: + waveform (Tensor): Tensor of audio of dimension (..., time) + density_function (str, optional): + The density function of a continuous random variable. One of + ``"TPDF"`` (Triangular Probability Density Function), + ``"RPDF"`` (Rectangular Probability Density Function) or + ``"GPDF"`` (Gaussian Probability Density Function) (Default: ``"TPDF"``). + noise_shaping (bool, optional): a filtering process that shapes the spectral + energy of quantisation error (Default: ``False``) + + Returns: + Tensor: waveform dithered + """ + dithered = _apply_probability_distribution( + waveform, density_function=density_function + ) + + if noise_shaping: + return _add_noise_shaping(dithered, waveform) + else: + return dithered + + +def equalizer_biquad( + waveform: Tensor, + sample_rate: int, + center_freq: float, + gain: float, + Q: float = 0.707, +) -> Tensor: + r"""Design biquad peaking equalizer filter and perform filtering. Similar to SoX implementation. + + Args: + waveform (Tensor): audio waveform of dimension of `(..., time)` + sample_rate (int): sampling rate of the waveform, e.g. 44100 (Hz) + center_freq (float): filter's central frequency + gain (float or torch.Tensor): desired gain at the boost (or attenuation) in dB + Q (float or torch.Tensor, optional): https://en.wikipedia.org/wiki/Q_factor (Default: ``0.707``) + + Returns: + Tensor: Waveform of dimension of `(..., time)` + """ + dtype = waveform.dtype + device = waveform.device + center_freq = torch.as_tensor(center_freq, dtype=dtype, device=device) + Q = torch.as_tensor(Q, dtype=dtype, device=device) + gain = torch.as_tensor(gain, dtype=dtype, device=device) + + w0 = 2 * math.pi * center_freq / sample_rate + A = torch.exp(gain / 40.0 * math.log(10)) + alpha = torch.sin(w0) / 2 / Q + + b0 = 1 + alpha * A + b1 = -2 * torch.cos(w0) + b2 = 1 - alpha * A + a0 = 1 + alpha / A + a1 = -2 * torch.cos(w0) + a2 = 1 - alpha / A + return biquad(waveform, b0, b1, b2, a0, a1, a2) + + +def filtfilt( + waveform: Tensor, a_coeffs: Tensor, b_coeffs: Tensor, clamp: bool = True, +) -> Tensor: + r"""Apply an IIR filter forward and backward to a waveform. + + Inspired by https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.filtfilt.html + + Args: + waveform (Tensor): audio waveform of dimension of `(..., time)`. Must be normalized to -1 to 1. + a_coeffs (Tensor): denominator coefficients of difference equation of dimension of either + 1D with shape `(num_order + 1)` or 2D with shape `(num_filters, num_order + 1)`. + Lower delay coefficients are first, e.g. ``[a0, a1, a2, ...]``. + Must be same size as b_coeffs (pad with 0's as necessary). + b_coeffs (Tensor): numerator coefficients of difference equation of dimension of either + 1D with shape `(num_order + 1)` or 2D with shape `(num_filters, num_order + 1)`. + Lower delay coefficients are first, e.g. ``[b0, b1, b2, ...]``. + Must be same size as a_coeffs (pad with 0's as necessary). + clamp (bool, optional): If ``True``, clamp the output signal to be in the range [-1, 1] (Default: ``True``) + + Returns: + Tensor: Waveform with dimension of either `(..., num_filters, time)` if ``a_coeffs`` and ``b_coeffs`` + are 2D Tensors, or `(..., time)` otherwise. + """ + forward_filtered = lfilter(waveform, a_coeffs, b_coeffs, clamp=False, batching=True) + backward_filtered = lfilter( + forward_filtered.flip(-1), a_coeffs, b_coeffs, clamp=clamp, batching=True, + ).flip(-1) + return backward_filtered + + +def flanger( + waveform: Tensor, + sample_rate: int, + delay: float = 0.0, + depth: float = 2.0, + regen: float = 0.0, + width: float = 71.0, + speed: float = 0.5, + phase: float = 25.0, + modulation: str = "sinusoidal", + interpolation: str = "linear", +) -> Tensor: + r"""Apply a flanger effect to the audio. Similar to SoX implementation. + + Args: + waveform (Tensor): audio waveform of dimension of `(..., channel, time)` . + Max 4 channels allowed + sample_rate (int): sampling rate of the waveform, e.g. 44100 (Hz) + delay (float, optional): desired delay in milliseconds(ms) + Allowed range of values are 0 to 30 + depth (float, optional): desired delay depth in milliseconds(ms) + Allowed range of values are 0 to 10 + regen (float, optional): desired regen(feedback gain) in dB + Allowed range of values are -95 to 95 + width (float, optional): desired width(delay gain) in dB + Allowed range of values are 0 to 100 + speed (float, optional): modulation speed in Hz + Allowed range of values are 0.1 to 10 + phase (float, optional): percentage phase-shift for multi-channel + Allowed range of values are 0 to 100 + modulation (str, optional): Use either "sinusoidal" or "triangular" modulation. (Default: ``sinusoidal``) + interpolation (str, optional): Use either "linear" or "quadratic" for delay-line interpolation. + (Default: ``linear``) + + Returns: + Tensor: Waveform of dimension of `(..., channel, time)` + + Reference: + - http://sox.sourceforge.net/sox.html + + - Scott Lehman, `Effects Explained`_, + + .. _Effects Explained: + https://web.archive.org/web/20051125072557/http://www.harmony-central.com/Effects/effects-explained.html + """ + + if modulation not in ("sinusoidal", "triangular"): + raise ValueError("Only 'sinusoidal' or 'triangular' modulation allowed") + + if interpolation not in ("linear", "quadratic"): + raise ValueError("Only 'linear' or 'quadratic' interpolation allowed") + + actual_shape = waveform.shape + device, dtype = waveform.device, waveform.dtype + + if actual_shape[-2] > 4: + raise ValueError("Max 4 channels allowed") + + # convert to 3D (batch, channels, time) + waveform = waveform.view(-1, actual_shape[-2], actual_shape[-1]) + + # Scaling + feedback_gain = regen / 100 + delay_gain = width / 100 + channel_phase = phase / 100 + delay_min = delay / 1000 + delay_depth = depth / 1000 + + n_channels = waveform.shape[-2] + + if modulation == "sinusoidal": + wave_type = "SINE" + else: + wave_type = "TRIANGLE" + + # Balance output: + in_gain = 1.0 / (1 + delay_gain) + delay_gain = delay_gain / (1 + delay_gain) + + # Balance feedback loop: + delay_gain = delay_gain * (1 - abs(feedback_gain)) + + delay_buf_length = int((delay_min + delay_depth) * sample_rate + 0.5) + delay_buf_length = delay_buf_length + 2 + + delay_bufs = torch.zeros( + waveform.shape[0], n_channels, delay_buf_length, dtype=dtype, device=device + ) + delay_last = torch.zeros(waveform.shape[0], n_channels, dtype=dtype, device=device) + + lfo_length = int(sample_rate / speed) + + table_min = math.floor(delay_min * sample_rate + 0.5) + table_max = delay_buf_length - 2.0 + + lfo = _generate_wave_table( + wave_type=wave_type, + data_type="FLOAT", + table_size=lfo_length, + min=float(table_min), + max=float(table_max), + phase=3 * math.pi / 2, + device=device, + ) + + output_waveform = torch.zeros_like(waveform, dtype=dtype, device=device) + + delay_buf_pos = 0 + lfo_pos = 0 + channel_idxs = torch.arange(0, n_channels, device=device) + + for i in range(waveform.shape[-1]): + + delay_buf_pos = (delay_buf_pos + delay_buf_length - 1) % delay_buf_length + + cur_channel_phase = (channel_idxs * lfo_length * channel_phase + 0.5).to( + torch.int64 + ) + delay_tensor = lfo[(lfo_pos + cur_channel_phase) % lfo_length] + frac_delay = torch.frac(delay_tensor) + delay_tensor = torch.floor(delay_tensor) + + int_delay = delay_tensor.to(torch.int64) + + temp = waveform[:, :, i] + + delay_bufs[:, :, delay_buf_pos] = temp + delay_last * feedback_gain + + delayed_0 = delay_bufs[ + :, channel_idxs, (delay_buf_pos + int_delay) % delay_buf_length + ] + + int_delay = int_delay + 1 + + delayed_1 = delay_bufs[ + :, channel_idxs, (delay_buf_pos + int_delay) % delay_buf_length + ] + + int_delay = int_delay + 1 + + if interpolation == "linear": + delayed = delayed_0 + (delayed_1 - delayed_0) * frac_delay + else: + delayed_2 = delay_bufs[ + :, channel_idxs, (delay_buf_pos + int_delay) % delay_buf_length + ] + + int_delay = int_delay + 1 + + delayed_2 = delayed_2 - delayed_0 + delayed_1 = delayed_1 - delayed_0 + a = delayed_2 * 0.5 - delayed_1 + b = delayed_1 * 2 - delayed_2 * 0.5 + + delayed = delayed_0 + (a * frac_delay + b) * frac_delay + + delay_last = delayed + output_waveform[:, :, i] = waveform[:, :, i] * in_gain + delayed * delay_gain + + lfo_pos = (lfo_pos + 1) % lfo_length + + return output_waveform.clamp(min=-1, max=1).view(actual_shape) + + +def gain(waveform: Tensor, gain_db: float = 1.0) -> Tensor: + r"""Apply amplification or attenuation to the whole waveform. + + Args: + waveform (Tensor): Tensor of audio of dimension (..., time). + gain_db (float, optional) Gain adjustment in decibels (dB) (Default: ``1.0``). + + Returns: + Tensor: the whole waveform amplified by gain_db. + """ + if gain_db == 0: + return waveform + + ratio = 10 ** (gain_db / 20) + + return waveform * ratio + + +def highpass_biquad( + waveform: Tensor, sample_rate: int, cutoff_freq: float, Q: float = 0.707 +) -> Tensor: + r"""Design biquad highpass filter and perform filtering. Similar to SoX implementation. + + Args: + waveform (Tensor): audio waveform of dimension of `(..., time)` + sample_rate (int): sampling rate of the waveform, e.g. 44100 (Hz) + cutoff_freq (float or torch.Tensor): filter cutoff frequency + Q (float or torch.Tensor, optional): https://en.wikipedia.org/wiki/Q_factor (Default: ``0.707``) + + Returns: + Tensor: Waveform dimension of `(..., time)` + """ + dtype = waveform.dtype + device = waveform.device + cutoff_freq = torch.as_tensor(cutoff_freq, dtype=dtype, device=device) + Q = torch.as_tensor(Q, dtype=dtype, device=device) + + w0 = 2 * math.pi * cutoff_freq / sample_rate + alpha = torch.sin(w0) / 2.0 / Q + + b0 = (1 + torch.cos(w0)) / 2 + b1 = -1 - torch.cos(w0) + b2 = b0 + a0 = 1 + alpha + a1 = -2 * torch.cos(w0) + a2 = 1 - alpha + return biquad(waveform, b0, b1, b2, a0, a1, a2) + + +def _lfilter_core_generic_loop(input_signal_windows: Tensor, a_coeffs_flipped: Tensor, padded_output_waveform: Tensor): + n_order = a_coeffs_flipped.size(1) + a_coeffs_flipped = a_coeffs_flipped.unsqueeze(2) + for i_sample, o0 in enumerate(input_signal_windows.permute(2, 0, 1)): + windowed_output_signal = padded_output_waveform[ + :, :, i_sample:i_sample + n_order + ] + o0 -= (windowed_output_signal.transpose(0, 1) @ a_coeffs_flipped)[..., 0].t() + padded_output_waveform[:, :, i_sample + n_order - 1] = o0 + + +try: + _lfilter_core_cpu_loop = torch.ops.torchaudio._lfilter_core_loop +except RuntimeError as err: + assert str(err) == 'No such operator torchaudio::_lfilter_core_loop' + _lfilter_core_cpu_loop = _lfilter_core_generic_loop + + +def _lfilter_core( + waveform: Tensor, + a_coeffs: Tensor, + b_coeffs: Tensor, +) -> Tensor: + + assert a_coeffs.size() == b_coeffs.size() + assert len(waveform.size()) == 3 + assert waveform.device == a_coeffs.device + assert b_coeffs.device == a_coeffs.device + + n_batch, n_channel, n_sample = waveform.size() + n_order = a_coeffs.size(1) + assert n_order > 0 + + # Pad the input and create output + + padded_waveform = torch.nn.functional.pad(waveform, [n_order - 1, 0]) + padded_output_waveform = torch.zeros_like(padded_waveform) + + # Set up the coefficients matrix + # Flip coefficients' order + a_coeffs_flipped = a_coeffs.flip(1) + b_coeffs_flipped = b_coeffs.flip(1) + + # calculate windowed_input_signal in parallel using convolution + input_signal_windows = torch.nn.functional.conv1d( + padded_waveform, + b_coeffs_flipped.unsqueeze(1), + groups=n_channel + ) + + input_signal_windows.div_(a_coeffs[:, :1]) + a_coeffs_flipped.div_(a_coeffs[:, :1]) + + if input_signal_windows.device == torch.device('cpu') and\ + a_coeffs_flipped.device == torch.device('cpu') and\ + padded_output_waveform.device == torch.device('cpu'): + _lfilter_core_cpu_loop(input_signal_windows, a_coeffs_flipped, padded_output_waveform) + else: + _lfilter_core_generic_loop(input_signal_windows, a_coeffs_flipped, padded_output_waveform) + + output = padded_output_waveform[:, :, n_order - 1:] + return output + + +try: + _lfilter = torch.ops.torchaudio._lfilter +except RuntimeError as err: + assert str(err) == 'No such operator torchaudio::_lfilter' + _lfilter = _lfilter_core + + +def lfilter( + waveform: Tensor, + a_coeffs: Tensor, + b_coeffs: Tensor, + clamp: bool = True, + batching: bool = True +) -> Tensor: + r"""Perform an IIR filter by evaluating difference equation. + + Note: + To avoid numerical problems, small filter order is preferred. + Using double precision could also minimize numerical precision errors. + + Args: + waveform (Tensor): audio waveform of dimension of `(..., time)`. Must be normalized to -1 to 1. + a_coeffs (Tensor): denominator coefficients of difference equation of dimension of either + 1D with shape `(num_order + 1)` or 2D with shape `(num_filters, num_order + 1)`. + Lower delays coefficients are first, e.g. ``[a0, a1, a2, ...]``. + Must be same size as b_coeffs (pad with 0's as necessary). + b_coeffs (Tensor): numerator coefficients of difference equation of dimension of either + 1D with shape `(num_order + 1)` or 2D with shape `(num_filters, num_order + 1)`. + Lower delays coefficients are first, e.g. ``[b0, b1, b2, ...]``. + Must be same size as a_coeffs (pad with 0's as necessary). + clamp (bool, optional): If ``True``, clamp the output signal to be in the range [-1, 1] (Default: ``True``) + batching (bool, optional): Effective only when coefficients are 2D. If ``True``, then waveform should be at + least 2D, and the size of second axis from last should equals to ``num_filters``. + The output can be expressed as ``output[..., i, :] = lfilter(waveform[..., i, :], + a_coeffs[i], b_coeffs[i], clamp=clamp, batching=False)``. (Default: ``True``) + + Returns: + Tensor: Waveform with dimension of either `(..., num_filters, time)` if ``a_coeffs`` and ``b_coeffs`` + are 2D Tensors, or `(..., time)` otherwise. + """ + assert a_coeffs.size() == b_coeffs.size() + assert a_coeffs.ndim <= 2 + + if a_coeffs.ndim > 1: + if batching: + assert waveform.ndim > 1 + assert waveform.shape[-2] == a_coeffs.shape[0] + else: + waveform = torch.stack([waveform] * a_coeffs.shape[0], -2) + else: + a_coeffs = a_coeffs.unsqueeze(0) + b_coeffs = b_coeffs.unsqueeze(0) + + # pack batch + shape = waveform.size() + waveform = waveform.reshape(-1, a_coeffs.shape[0], shape[-1]) + output = _lfilter(waveform, a_coeffs, b_coeffs) + + if clamp: + output = torch.clamp(output, min=-1.0, max=1.0) + + # unpack batch + output = output.reshape(shape[:-1] + output.shape[-1:]) + + return output + + +def lowpass_biquad( + waveform: Tensor, sample_rate: int, cutoff_freq: float, Q: float = 0.707 +) -> Tensor: + r"""Design biquad lowpass filter and perform filtering. Similar to SoX implementation. + + Args: + waveform (torch.Tensor): audio waveform of dimension of `(..., time)` + sample_rate (int): sampling rate of the waveform, e.g. 44100 (Hz) + cutoff_freq (float or torch.Tensor): filter cutoff frequency + Q (float or torch.Tensor, optional): https://en.wikipedia.org/wiki/Q_factor (Default: ``0.707``) + + Returns: + Tensor: Waveform of dimension of `(..., time)` + """ + dtype = waveform.dtype + device = waveform.device + cutoff_freq = torch.as_tensor(cutoff_freq, dtype=dtype, device=device) + Q = torch.as_tensor(Q, dtype=dtype, device=device) + + w0 = 2 * math.pi * cutoff_freq / sample_rate + alpha = torch.sin(w0) / 2 / Q + + b0 = (1 - torch.cos(w0)) / 2 + b1 = 1 - torch.cos(w0) + b2 = b0 + a0 = 1 + alpha + a1 = -2 * torch.cos(w0) + a2 = 1 - alpha + return biquad(waveform, b0, b1, b2, a0, a1, a2) + + +def _overdrive_core_loop_generic( + waveform: Tensor, + temp: Tensor, + last_in: Tensor, + last_out: Tensor, + output_waveform: Tensor +): + for i in range(waveform.shape[-1]): + last_out = temp[:, i] - last_in + 0.995 * last_out + last_in = temp[:, i] + output_waveform[:, i] = waveform[:, i] * 0.5 + last_out * 0.75 + + +try: + _overdrive_core_loop_cpu = torch.ops.torchaudio._overdrive_core_loop +except RuntimeError as err: + assert str(err) == 'No such operator torchaudio::_overdrive_core_loop' + _overdrive_core_loop_cpu = _overdrive_core_loop_generic + + +def overdrive(waveform: Tensor, gain: float = 20, colour: float = 20) -> Tensor: + r"""Apply a overdrive effect to the audio. Similar to SoX implementation. + This effect applies a non linear distortion to the audio signal. + + Args: + waveform (Tensor): audio waveform of dimension of `(..., time)` + gain (float, optional): desired gain at the boost (or attenuation) in dB + Allowed range of values are 0 to 100 + colour (float, optional): controls the amount of even harmonic content in the over-driven output + Allowed range of values are 0 to 100 + + Returns: + Tensor: Waveform of dimension of `(..., time)` + + Reference: + - http://sox.sourceforge.net/sox.html + """ + actual_shape = waveform.shape + device, dtype = waveform.device, waveform.dtype + + # convert to 2D (..,time) + waveform = waveform.view(-1, actual_shape[-1]) + + gain = _dB2Linear(gain) + colour = colour / 200 + last_in = torch.zeros(waveform.shape[:-1], dtype=dtype, device=device) + last_out = torch.zeros(waveform.shape[:-1], dtype=dtype, device=device) + + temp = waveform * gain + colour + + mask1 = temp < -1 + temp[mask1] = torch.tensor(-2.0 / 3.0, dtype=dtype, device=device) + # Wrapping the constant with Tensor is required for Torchscript + + mask2 = temp > 1 + temp[mask2] = torch.tensor(2.0 / 3.0, dtype=dtype, device=device) + + mask3 = ~mask1 & ~mask2 + temp[mask3] = temp[mask3] - (temp[mask3] ** 3) * (1.0 / 3) + + output_waveform = torch.zeros_like(waveform, dtype=dtype, device=device) + + # Uses CPU optimized loop function if available for CPU device + if device == torch.device('cpu'): + _overdrive_core_loop_cpu(waveform, temp, last_in, last_out, output_waveform) + else: + _overdrive_core_loop_generic(waveform, temp, last_in, last_out, output_waveform) + + return output_waveform.clamp(min=-1, max=1).view(actual_shape) + + +def phaser( + waveform: Tensor, + sample_rate: int, + gain_in: float = 0.4, + gain_out: float = 0.74, + delay_ms: float = 3.0, + decay: float = 0.4, + mod_speed: float = 0.5, + sinusoidal: bool = True, +) -> Tensor: + r"""Apply a phasing effect to the audio. Similar to SoX implementation. + + Args: + waveform (Tensor): audio waveform of dimension of `(..., time)` + sample_rate (int): sampling rate of the waveform, e.g. 44100 (Hz) + gain_in (float, optional): desired input gain at the boost (or attenuation) in dB + Allowed range of values are 0 to 1 + gain_out (float, optional): desired output gain at the boost (or attenuation) in dB + Allowed range of values are 0 to 1e9 + delay_ms (float, optional): desired delay in milliseconds + Allowed range of values are 0 to 5.0 + decay (float, optional): desired decay relative to gain-in + Allowed range of values are 0 to 0.99 + mod_speed (float, optional): modulation speed in Hz + Allowed range of values are 0.1 to 2 + sinusoidal (bool, optional): If ``True``, uses sinusoidal modulation (preferable for multiple instruments) + If ``False``, uses triangular modulation (gives single instruments a sharper phasing effect) + (Default: ``True``) + + Returns: + Tensor: Waveform of dimension of `(..., time)` + + Reference: + - http://sox.sourceforge.net/sox.html + - Scott Lehman, `Effects Explained`_. + + .. _Effects Explained: + https://web.archive.org/web/20051125072557/http://www.harmony-central.com/Effects/effects-explained.html + """ + actual_shape = waveform.shape + device, dtype = waveform.device, waveform.dtype + + # convert to 2D (channels,time) + waveform = waveform.view(-1, actual_shape[-1]) + + delay_buf_len = int((delay_ms * 0.001 * sample_rate) + 0.5) + delay_buf = torch.zeros( + waveform.shape[0], delay_buf_len, dtype=dtype, device=device + ) + + mod_buf_len = int(sample_rate / mod_speed + 0.5) + + if sinusoidal: + wave_type = "SINE" + else: + wave_type = "TRIANGLE" + + mod_buf = _generate_wave_table( + wave_type=wave_type, + data_type="INT", + table_size=mod_buf_len, + min=1.0, + max=float(delay_buf_len), + phase=math.pi / 2, + device=device, + ) + + delay_pos = 0 + mod_pos = 0 + + output_waveform_pre_gain_list = [] + waveform = waveform * gain_in + delay_buf = delay_buf * decay + waveform_list = [waveform[:, i] for i in range(waveform.size(1))] + delay_buf_list = [delay_buf[:, i] for i in range(delay_buf.size(1))] + mod_buf_list = [mod_buf[i] for i in range(mod_buf.size(0))] + + for i in range(waveform.shape[-1]): + idx = int((delay_pos + mod_buf_list[mod_pos]) % delay_buf_len) + mod_pos = (mod_pos + 1) % mod_buf_len + delay_pos = (delay_pos + 1) % delay_buf_len + temp = (waveform_list[i]) + (delay_buf_list[idx]) + delay_buf_list[delay_pos] = temp * decay + output_waveform_pre_gain_list.append(temp) + + output_waveform = torch.stack(output_waveform_pre_gain_list, dim=1).to( + dtype=dtype, device=device + ) + output_waveform.mul_(gain_out) + + return output_waveform.clamp(min=-1, max=1).view(actual_shape) + + +def riaa_biquad(waveform: Tensor, sample_rate: int) -> Tensor: + r"""Apply RIAA vinyl playback equalization. Similar to SoX implementation. + + Args: + waveform (Tensor): audio waveform of dimension of `(..., time)` + sample_rate (int): sampling rate of the waveform, e.g. 44100 (Hz). + Allowed sample rates in Hz : ``44100``,``48000``,``88200``,``96000`` + + Returns: + Tensor: Waveform of dimension of `(..., time)` + + Reference: + - http://sox.sourceforge.net/sox.html + - https://www.w3.org/2011/audio/audio-eq-cookbook.html#APF + """ + + if sample_rate == 44100: + zeros = [-0.2014898, 0.9233820] + poles = [0.7083149, 0.9924091] + + elif sample_rate == 48000: + zeros = [-0.1766069, 0.9321590] + poles = [0.7396325, 0.9931330] + + elif sample_rate == 88200: + zeros = [-0.1168735, 0.9648312] + poles = [0.8590646, 0.9964002] + + elif sample_rate == 96000: + zeros = [-0.1141486, 0.9676817] + poles = [0.8699137, 0.9966946] + + else: + raise ValueError("Sample rate must be 44.1k, 48k, 88.2k, or 96k") + + # polynomial coefficients with roots zeros[0] and zeros[1] + b0 = 1.0 + b1 = -(zeros[0] + zeros[1]) + b2 = zeros[0] * zeros[1] + + # polynomial coefficients with roots poles[0] and poles[1] + a0 = 1.0 + a1 = -(poles[0] + poles[1]) + a2 = poles[0] * poles[1] + + # Normalize to 0dB at 1kHz + y = 2 * math.pi * 1000 / sample_rate + b_re = b0 + b1 * math.cos(-y) + b2 * math.cos(-2 * y) + a_re = a0 + a1 * math.cos(-y) + a2 * math.cos(-2 * y) + b_im = b1 * math.sin(-y) + b2 * math.sin(-2 * y) + a_im = a1 * math.sin(-y) + a2 * math.sin(-2 * y) + g = 1 / math.sqrt((b_re ** 2 + b_im ** 2) / (a_re ** 2 + a_im ** 2)) + + b0 *= g + b1 *= g + b2 *= g + + return biquad(waveform, b0, b1, b2, a0, a1, a2) + + +def treble_biquad( + waveform: Tensor, + sample_rate: int, + gain: float, + central_freq: float = 3000, + Q: float = 0.707, +) -> Tensor: + r"""Design a treble tone-control effect. Similar to SoX implementation. + + Args: + waveform (Tensor): audio waveform of dimension of `(..., time)` + sample_rate (int): sampling rate of the waveform, e.g. 44100 (Hz) + gain (float or torch.Tensor): desired gain at the boost (or attenuation) in dB. + central_freq (float or torch.Tensor, optional): central frequency (in Hz). (Default: ``3000``) + Q (float or torch.Tensor, optional): https://en.wikipedia.org/wiki/Q_factor (Default: ``0.707``). + + Returns: + Tensor: Waveform of dimension of `(..., time)` + + Reference: + - http://sox.sourceforge.net/sox.html + - https://www.w3.org/2011/audio/audio-eq-cookbook.html#APF + """ + dtype = waveform.dtype + device = waveform.device + central_freq = torch.as_tensor(central_freq, dtype=dtype, device=device) + Q = torch.as_tensor(Q, dtype=dtype, device=device) + gain = torch.as_tensor(gain, dtype=dtype, device=device) + + w0 = 2 * math.pi * central_freq / sample_rate + alpha = torch.sin(w0) / 2 / Q + A = torch.exp(gain / 40 * math.log(10)) + + temp1 = 2 * torch.sqrt(A) * alpha + temp2 = (A - 1) * torch.cos(w0) + temp3 = (A + 1) * torch.cos(w0) + + b0 = A * ((A + 1) + temp2 + temp1) + b1 = -2 * A * ((A - 1) + temp3) + b2 = A * ((A + 1) + temp2 - temp1) + a0 = (A + 1) - temp2 + temp1 + a1 = 2 * ((A - 1) - temp3) + a2 = (A + 1) - temp2 - temp1 + + return biquad(waveform, b0, b1, b2, a0, a1, a2) + + +def _measure( + measure_len_ws: int, + samples: Tensor, + spectrum: Tensor, + noise_spectrum: Tensor, + spectrum_window: Tensor, + spectrum_start: int, + spectrum_end: int, + cepstrum_window: Tensor, + cepstrum_start: int, + cepstrum_end: int, + noise_reduction_amount: float, + measure_smooth_time_mult: float, + noise_up_time_mult: float, + noise_down_time_mult: float, + index_ns: int, + boot_count: int, +) -> float: + + assert spectrum.size()[-1] == noise_spectrum.size()[-1] + + samplesLen_ns = samples.size()[-1] + dft_len_ws = spectrum.size()[-1] + + dftBuf = torch.zeros(dft_len_ws) + + _index_ns = torch.tensor( + [index_ns] + [(index_ns + i) % samplesLen_ns for i in range(1, measure_len_ws)] + ) + dftBuf[:measure_len_ws] = samples[_index_ns] * spectrum_window[:measure_len_ws] + + # memset(c->dftBuf + i, 0, (p->dft_len_ws - i) * sizeof(*c->dftBuf)); + dftBuf[measure_len_ws:dft_len_ws].zero_() + + # lsx_safe_rdft((int)p->dft_len_ws, 1, c->dftBuf); + _dftBuf = torch.fft.rfft(dftBuf) + + # memset(c->dftBuf, 0, p->spectrum_start * sizeof(*c->dftBuf)); + _dftBuf[:spectrum_start].zero_() + + mult: float = ( + boot_count / (1.0 + boot_count) if boot_count >= 0 else measure_smooth_time_mult + ) + + _d = _dftBuf[spectrum_start:spectrum_end].abs() + spectrum[spectrum_start:spectrum_end].mul_(mult).add_(_d * (1 - mult)) + _d = spectrum[spectrum_start:spectrum_end] ** 2 + + _zeros = torch.zeros(spectrum_end - spectrum_start) + _mult = ( + _zeros + if boot_count >= 0 + else torch.where( + _d > noise_spectrum[spectrum_start:spectrum_end], + torch.tensor(noise_up_time_mult), # if + torch.tensor(noise_down_time_mult), # else + ) + ) + + noise_spectrum[spectrum_start:spectrum_end].mul_(_mult).add_(_d * (1 - _mult)) + _d = torch.sqrt( + torch.max( + _zeros, + _d - noise_reduction_amount * noise_spectrum[spectrum_start:spectrum_end], + ) + ) + + _cepstrum_Buf: Tensor = torch.zeros(dft_len_ws >> 1) + _cepstrum_Buf[spectrum_start:spectrum_end] = _d * cepstrum_window + _cepstrum_Buf[spectrum_end:dft_len_ws >> 1].zero_() + + # lsx_safe_rdft((int)p->dft_len_ws >> 1, 1, c->dftBuf); + _cepstrum_Buf = torch.fft.rfft(_cepstrum_Buf) + + result: float = float( + torch.sum(_cepstrum_Buf[cepstrum_start:cepstrum_end].abs().pow(2)) + ) + result = ( + math.log(result / (cepstrum_end - cepstrum_start)) if result > 0 else -math.inf + ) + return max(0, 21 + result) + + +def vad( + waveform: Tensor, + sample_rate: int, + trigger_level: float = 7.0, + trigger_time: float = 0.25, + search_time: float = 1.0, + allowed_gap: float = 0.25, + pre_trigger_time: float = 0.0, + # Fine-tuning parameters + boot_time: float = 0.35, + noise_up_time: float = 0.1, + noise_down_time: float = 0.01, + noise_reduction_amount: float = 1.35, + measure_freq: float = 20.0, + measure_duration: Optional[float] = None, + measure_smooth_time: float = 0.4, + hp_filter_freq: float = 50.0, + lp_filter_freq: float = 6000.0, + hp_lifter_freq: float = 150.0, + lp_lifter_freq: float = 2000.0, +) -> Tensor: + r"""Voice Activity Detector. Similar to SoX implementation. + Attempts to trim silence and quiet background sounds from the ends of recordings of speech. + The algorithm currently uses a simple cepstral power measurement to detect voice, + so may be fooled by other things, especially music. + + The effect can trim only from the front of the audio, + so in order to trim from the back, the reverse effect must also be used. + + Args: + waveform (Tensor): Tensor of audio of dimension `(channels, time)` or `(time)` + Tensor of shape `(channels, time)` is treated as a multi-channel recording + of the same event and the resulting output will be trimmed to the earliest + voice activity in any channel. + sample_rate (int): Sample rate of audio signal. + trigger_level (float, optional): The measurement level used to trigger activity detection. + This may need to be cahnged depending on the noise level, signal level, + and other characteristics of the input audio. (Default: 7.0) + trigger_time (float, optional): The time constant (in seconds) + used to help ignore short bursts of sound. (Default: 0.25) + search_time (float, optional): The amount of audio (in seconds) + to search for quieter/shorter bursts of audio to include prior + to the detected trigger point. (Default: 1.0) + allowed_gap (float, optional): The allowed gap (in seconds) between + quieter/shorter bursts of audio to include prior + to the detected trigger point. (Default: 0.25) + pre_trigger_time (float, optional): The amount of audio (in seconds) to preserve + before the trigger point and any found quieter/shorter bursts. (Default: 0.0) + boot_time (float, optional) The algorithm (internally) uses adaptive noise + estimation/reduction in order to detect the start of the wanted audio. + This option sets the time for the initial noise estimate. (Default: 0.35) + noise_up_time (float, optional) Time constant used by the adaptive noise estimator + for when the noise level is increasing. (Default: 0.1) + noise_down_time (float, optional) Time constant used by the adaptive noise estimator + for when the noise level is decreasing. (Default: 0.01) + noise_reduction_amount (float, optional) Amount of noise reduction to use in + the detection algorithm (e.g. 0, 0.5, ...). (Default: 1.35) + measure_freq (float, optional) Frequency of the algorithm’s + processing/measurements. (Default: 20.0) + measure_duration: (float, optional) Measurement duration. + (Default: Twice the measurement period; i.e. with overlap.) + measure_smooth_time (float, optional) Time constant used to smooth + spectral measurements. (Default: 0.4) + hp_filter_freq (float, optional) "Brick-wall" frequency of high-pass filter applied + at the input to the detector algorithm. (Default: 50.0) + lp_filter_freq (float, optional) "Brick-wall" frequency of low-pass filter applied + at the input to the detector algorithm. (Default: 6000.0) + hp_lifter_freq (float, optional) "Brick-wall" frequency of high-pass lifter used + in the detector algorithm. (Default: 150.0) + lp_lifter_freq (float, optional) "Brick-wall" frequency of low-pass lifter used + in the detector algorithm. (Default: 2000.0) + + Returns: + Tensor: Tensor of audio of dimension `(..., time)`. + + Reference: + - http://sox.sourceforge.net/sox.html + """ + + if waveform.ndim > 2: + warnings.warn( + "Expected input tensor dimension of 1 for single channel" + f" or 2 for multi-channel. Got {waveform.ndim} instead. " + "Batch semantics is not supported. " + "Please refer to https://github.com/pytorch/audio/issues/1348" + " and https://github.com/pytorch/audio/issues/1468." + ) + + measure_duration: float = ( + 2.0 / measure_freq if measure_duration is None else measure_duration + ) + + measure_len_ws = int(sample_rate * measure_duration + 0.5) + measure_len_ns = measure_len_ws + # for (dft_len_ws = 16; dft_len_ws < measure_len_ws; dft_len_ws <<= 1); + dft_len_ws = 16 + while dft_len_ws < measure_len_ws: + dft_len_ws *= 2 + + measure_period_ns = int(sample_rate / measure_freq + 0.5) + measures_len = math.ceil(search_time * measure_freq) + search_pre_trigger_len_ns = measures_len * measure_period_ns + gap_len = int(allowed_gap * measure_freq + 0.5) + + fixed_pre_trigger_len_ns = int(pre_trigger_time * sample_rate + 0.5) + samplesLen_ns = ( + fixed_pre_trigger_len_ns + search_pre_trigger_len_ns + measure_len_ns + ) + + spectrum_window = torch.zeros(measure_len_ws) + for i in range(measure_len_ws): + # sox.h:741 define SOX_SAMPLE_MIN (sox_sample_t)SOX_INT_MIN(32) + spectrum_window[i] = 2.0 / math.sqrt(float(measure_len_ws)) + # lsx_apply_hann(spectrum_window, (int)measure_len_ws); + spectrum_window *= torch.hann_window(measure_len_ws, dtype=torch.float) + + spectrum_start: int = int(hp_filter_freq / sample_rate * dft_len_ws + 0.5) + spectrum_start: int = max(spectrum_start, 1) + spectrum_end: int = int(lp_filter_freq / sample_rate * dft_len_ws + 0.5) + spectrum_end: int = min(spectrum_end, dft_len_ws // 2) + + cepstrum_window = torch.zeros(spectrum_end - spectrum_start) + for i in range(spectrum_end - spectrum_start): + cepstrum_window[i] = 2.0 / math.sqrt(float(spectrum_end) - spectrum_start) + # lsx_apply_hann(cepstrum_window,(int)(spectrum_end - spectrum_start)); + cepstrum_window *= torch.hann_window( + spectrum_end - spectrum_start, dtype=torch.float + ) + + cepstrum_start = math.ceil(sample_rate * 0.5 / lp_lifter_freq) + cepstrum_end = math.floor(sample_rate * 0.5 / hp_lifter_freq) + cepstrum_end = min(cepstrum_end, dft_len_ws // 4) + + assert cepstrum_end > cepstrum_start + + noise_up_time_mult = math.exp(-1.0 / (noise_up_time * measure_freq)) + noise_down_time_mult = math.exp(-1.0 / (noise_down_time * measure_freq)) + measure_smooth_time_mult = math.exp(-1.0 / (measure_smooth_time * measure_freq)) + trigger_meas_time_mult = math.exp(-1.0 / (trigger_time * measure_freq)) + + boot_count_max = int(boot_time * measure_freq - 0.5) + measure_timer_ns = measure_len_ns + boot_count = measures_index = flushedLen_ns = samplesIndex_ns = 0 + + # pack batch + shape = waveform.size() + waveform = waveform.view(-1, shape[-1]) + + n_channels, ilen = waveform.size() + + mean_meas = torch.zeros(n_channels) + samples = torch.zeros(n_channels, samplesLen_ns) + spectrum = torch.zeros(n_channels, dft_len_ws) + noise_spectrum = torch.zeros(n_channels, dft_len_ws) + measures = torch.zeros(n_channels, measures_len) + + has_triggered: bool = False + num_measures_to_flush: int = 0 + pos: int = 0 + + while pos < ilen and not has_triggered: + measure_timer_ns -= 1 + for i in range(n_channels): + samples[i, samplesIndex_ns] = waveform[i, pos] + # if (!p->measure_timer_ns) { + if measure_timer_ns == 0: + index_ns: int = ( + samplesIndex_ns + samplesLen_ns - measure_len_ns + ) % samplesLen_ns + meas: float = _measure( + measure_len_ws=measure_len_ws, + samples=samples[i], + spectrum=spectrum[i], + noise_spectrum=noise_spectrum[i], + spectrum_window=spectrum_window, + spectrum_start=spectrum_start, + spectrum_end=spectrum_end, + cepstrum_window=cepstrum_window, + cepstrum_start=cepstrum_start, + cepstrum_end=cepstrum_end, + noise_reduction_amount=noise_reduction_amount, + measure_smooth_time_mult=measure_smooth_time_mult, + noise_up_time_mult=noise_up_time_mult, + noise_down_time_mult=noise_down_time_mult, + index_ns=index_ns, + boot_count=boot_count, + ) + measures[i, measures_index] = meas + mean_meas[i] = mean_meas[i] * trigger_meas_time_mult + meas * ( + 1.0 - trigger_meas_time_mult + ) + + has_triggered = has_triggered or (mean_meas[i] >= trigger_level) + if has_triggered: + n: int = measures_len + k: int = measures_index + jTrigger: int = n + jZero: int = n + j: int = 0 + + for j in range(n): + if (measures[i, k] >= trigger_level) and ( + j <= jTrigger + gap_len + ): + jZero = jTrigger = j + elif (measures[i, k] == 0) and (jTrigger >= jZero): + jZero = j + k = (k + n - 1) % n + j = min(j, jZero) + # num_measures_to_flush = range_limit(j, num_measures_to_flush, n); + num_measures_to_flush = min(max(num_measures_to_flush, j), n) + # end if has_triggered + # end if (measure_timer_ns == 0): + # end for + samplesIndex_ns += 1 + pos += 1 + # end while + if samplesIndex_ns == samplesLen_ns: + samplesIndex_ns = 0 + if measure_timer_ns == 0: + measure_timer_ns = measure_period_ns + measures_index += 1 + measures_index = measures_index % measures_len + if boot_count >= 0: + boot_count = -1 if boot_count == boot_count_max else boot_count + 1 + + if has_triggered: + flushedLen_ns = (measures_len - num_measures_to_flush) * measure_period_ns + samplesIndex_ns = (samplesIndex_ns + flushedLen_ns) % samplesLen_ns + + res = waveform[:, pos - samplesLen_ns + flushedLen_ns:] + # unpack batch + return res.view(shape[:-1] + res.shape[-1:]) diff --git a/torchaudio/functional/functional.py b/torchaudio/functional/functional.py new file mode 100644 index 00000000..1444ecef --- /dev/null +++ b/torchaudio/functional/functional.py @@ -0,0 +1,1812 @@ +# -*- coding: utf-8 -*- + +from collections.abc import Sequence +import io +import math +import warnings +from typing import Optional, Tuple + +import torch +from torch import Tensor +from torchaudio._internal import module_utils as _mod_utils +import torchaudio + +__all__ = [ + "spectrogram", + "inverse_spectrogram", + "griffinlim", + "amplitude_to_DB", + "DB_to_amplitude", + "compute_deltas", + "compute_kaldi_pitch", + "create_fb_matrix", + "melscale_fbanks", + "linear_fbanks", + "create_dct", + "compute_deltas", + "detect_pitch_frequency", + "DB_to_amplitude", + "mu_law_encoding", + "mu_law_decoding", + "complex_norm", + "angle", + "magphase", + "phase_vocoder", + 'mask_along_axis', + 'mask_along_axis_iid', + 'sliding_window_cmn', + "spectral_centroid", + "apply_codec", + "resample", + "edit_distance", + "pitch_shift", + "rnnt_loss", +] + + +def spectrogram( + waveform: Tensor, + pad: int, + window: Tensor, + n_fft: int, + hop_length: int, + win_length: int, + power: Optional[float], + normalized: bool, + center: bool = True, + pad_mode: str = "reflect", + onesided: bool = True, + return_complex: bool = True, +) -> Tensor: + r"""Create a spectrogram or a batch of spectrograms from a raw audio signal. + The spectrogram can be either magnitude-only or complex. + + Args: + waveform (Tensor): Tensor of audio of dimension `(..., time)` + pad (int): Two sided padding of signal + window (Tensor): Window tensor that is applied/multiplied to each frame/window + n_fft (int): Size of FFT + hop_length (int): Length of hop between STFT windows + win_length (int): Window size + power (float or None): Exponent for the magnitude spectrogram, + (must be > 0) e.g., 1 for energy, 2 for power, etc. + If None, then the complex spectrum is returned instead. + normalized (bool): Whether to normalize by magnitude after stft + center (bool, optional): whether to pad :attr:`waveform` on both sides so + that the :math:`t`-th frame is centered at time :math:`t \times \text{hop\_length}`. + Default: ``True`` + pad_mode (string, optional): controls the padding method used when + :attr:`center` is ``True``. Default: ``"reflect"`` + onesided (bool, optional): controls whether to return half of results to + avoid redundancy. Default: ``True`` + return_complex (bool, optional): + Indicates whether the resulting complex-valued Tensor should be represented with + native complex dtype, such as `torch.cfloat` and `torch.cdouble`, or real dtype + mimicking complex value with an extra dimension for real and imaginary parts. + (See also ``torch.view_as_real``.) + This argument is only effective when ``power=None``. It is ignored for + cases where ``power`` is a number as in those cases, the returned tensor is + power spectrogram, which is a real-valued tensor. + + Returns: + Tensor: Dimension `(..., freq, time)`, freq is + ``n_fft // 2 + 1`` and ``n_fft`` is the number of + Fourier bins, and time is the number of window hops (n_frame). + """ + if power is None and not return_complex: + warnings.warn( + "The use of pseudo complex type in spectrogram is now deprecated." + "Please migrate to native complex type by providing `return_complex=True`. " + "Please refer to https://github.com/pytorch/audio/issues/1337 " + "for more details about torchaudio's plan to migrate to native complex type." + ) + + if pad > 0: + # TODO add "with torch.no_grad():" back when JIT supports it + waveform = torch.nn.functional.pad(waveform, (pad, pad), "constant") + + # pack batch + shape = waveform.size() + waveform = waveform.reshape(-1, shape[-1]) + + # default values are consistent with librosa.core.spectrum._spectrogram + spec_f = torch.stft( + input=waveform, + n_fft=n_fft, + hop_length=hop_length, + win_length=win_length, + window=window, + center=center, + pad_mode=pad_mode, + normalized=False, + onesided=onesided, + return_complex=True, + ) + + # unpack batch + spec_f = spec_f.reshape(shape[:-1] + spec_f.shape[-2:]) + + if normalized: + spec_f /= window.pow(2.).sum().sqrt() + if power is not None: + if power == 1.0: + return spec_f.abs() + return spec_f.abs().pow(power) + if not return_complex: + return torch.view_as_real(spec_f) + return spec_f + + +def inverse_spectrogram( + spectrogram: Tensor, + length: Optional[int], + pad: int, + window: Tensor, + n_fft: int, + hop_length: int, + win_length: int, + normalized: bool, + center: bool = True, + pad_mode: str = "reflect", + onesided: bool = True, +) -> Tensor: + r"""Create an inverse spectrogram or a batch of inverse spectrograms from the provided + complex-valued spectrogram. + + Args: + spectrogram (Tensor): Complex tensor of audio of dimension (..., freq, time). + length (int or None): The output length of the waveform. + pad (int): Two sided padding of signal. It is only effective when ``length`` is provided. + window (Tensor): Window tensor that is applied/multiplied to each frame/window + n_fft (int): Size of FFT + hop_length (int): Length of hop between STFT windows + win_length (int): Window size + normalized (bool): Whether the stft output was normalized by magnitude + center (bool, optional): whether the waveform was padded on both sides so + that the :math:`t`-th frame is centered at time :math:`t \times \text{hop\_length}`. + Default: ``True`` + pad_mode (string, optional): controls the padding method used when + :attr:`center` is ``True``. This parameter is provided for compatibility with the + spectrogram function and is not used. Default: ``"reflect"`` + onesided (bool, optional): controls whether spectrogram was done in onesided mode. + Default: ``True`` + + Returns: + Tensor: Dimension `(..., time)`. Least squares estimation of the original signal. + """ + + if spectrogram.dtype == torch.float32 or spectrogram.dtype == torch.float64: + warnings.warn( + "The use of pseudo complex type in inverse_spectrogram is now deprecated. " + "Please migrate to native complex type by using a complex tensor as input. " + "If the input is generated via spectrogram() function or transform, please use " + "return_complex=True as an argument to that function. " + "Please refer to https://github.com/pytorch/audio/issues/1337 " + "for more details about torchaudio's plan to migrate to native complex type." + ) + spectrogram = torch.view_as_complex(spectrogram) + + if normalized: + spectrogram = spectrogram * window.pow(2.).sum().sqrt() + + # pack batch + shape = spectrogram.size() + spectrogram = spectrogram.reshape(-1, shape[-2], shape[-1]) + + # default values are consistent with librosa.core.spectrum._spectrogram + waveform = torch.istft( + input=spectrogram, + n_fft=n_fft, + hop_length=hop_length, + win_length=win_length, + window=window, + center=center, + normalized=False, + onesided=onesided, + length=length + 2 * pad if length is not None else None, + return_complex=False, + ) + + if length is not None and pad > 0: + # remove padding from front and back + waveform = waveform[:, pad:-pad] + + # unpack batch + waveform = waveform.reshape(shape[:-2] + waveform.shape[-1:]) + + return waveform + + +def _get_complex_dtype(real_dtype: torch.dtype): + if real_dtype == torch.double: + return torch.cdouble + if real_dtype == torch.float: + return torch.cfloat + if real_dtype == torch.half: + return torch.complex32 + raise ValueError(f'Unexpected dtype {real_dtype}') + + +def griffinlim( + specgram: Tensor, + window: Tensor, + n_fft: int, + hop_length: int, + win_length: int, + power: float, + n_iter: int, + momentum: float, + length: Optional[int], + rand_init: bool +) -> Tensor: + r"""Compute waveform from a linear scale magnitude spectrogram using the Griffin-Lim transformation. + + Implementation ported from + *librosa* [:footcite:`brian_mcfee-proc-scipy-2015`], *A fast Griffin-Lim algorithm* [:footcite:`6701851`] + and *Signal estimation from modified short-time Fourier transform* [:footcite:`1172092`]. + + Args: + specgram (Tensor): A magnitude-only STFT spectrogram of dimension `(..., freq, frames)` + where freq is ``n_fft // 2 + 1``. + window (Tensor): Window tensor that is applied/multiplied to each frame/window + n_fft (int): Size of FFT, creates ``n_fft // 2 + 1`` bins + hop_length (int): Length of hop between STFT windows. ( + Default: ``win_length // 2``) + win_length (int): Window size. (Default: ``n_fft``) + power (float): Exponent for the magnitude spectrogram, + (must be > 0) e.g., 1 for energy, 2 for power, etc. + n_iter (int): Number of iteration for phase recovery process. + momentum (float): The momentum parameter for fast Griffin-Lim. + Setting this to 0 recovers the original Griffin-Lim method. + Values near 1 can lead to faster convergence, but above 1 may not converge. + length (int or None): Array length of the expected output. + rand_init (bool): Initializes phase randomly if True, to zero otherwise. + + Returns: + Tensor: waveform of `(..., time)`, where time equals the ``length`` parameter if given. + """ + assert momentum < 1, 'momentum={} > 1 can be unstable'.format(momentum) + assert momentum >= 0, 'momentum={} < 0'.format(momentum) + + # pack batch + shape = specgram.size() + specgram = specgram.reshape([-1] + list(shape[-2:])) + + specgram = specgram.pow(1 / power) + + # initialize the phase + if rand_init: + angles = torch.rand( + specgram.size(), + dtype=_get_complex_dtype(specgram.dtype), device=specgram.device) + else: + angles = torch.full( + specgram.size(), 1, + dtype=_get_complex_dtype(specgram.dtype), device=specgram.device) + + # And initialize the previous iterate to 0 + tprev = torch.tensor(0., dtype=specgram.dtype, device=specgram.device) + for _ in range(n_iter): + # Invert with our current estimate of the phases + inverse = torch.istft(specgram * angles, + n_fft=n_fft, + hop_length=hop_length, + win_length=win_length, + window=window, + length=length) + + # Rebuild the spectrogram + rebuilt = torch.stft( + input=inverse, + n_fft=n_fft, + hop_length=hop_length, + win_length=win_length, + window=window, + center=True, + pad_mode='reflect', + normalized=False, + onesided=True, + return_complex=True, + ) + + # Update our phase estimates + angles = rebuilt + if momentum: + angles = angles - tprev.mul_(momentum / (1 + momentum)) + angles = angles.div(angles.abs().add(1e-16)) + + # Store the previous iterate + tprev = rebuilt + + # Return the final phase estimates + waveform = torch.istft(specgram * angles, + n_fft=n_fft, + hop_length=hop_length, + win_length=win_length, + window=window, + length=length) + + # unpack batch + waveform = waveform.reshape(shape[:-2] + waveform.shape[-1:]) + + return waveform + + +def amplitude_to_DB( + x: Tensor, + multiplier: float, + amin: float, + db_multiplier: float, + top_db: Optional[float] = None +) -> Tensor: + r"""Turn a spectrogram from the power/amplitude scale to the decibel scale. + + The output of each tensor in a batch depends on the maximum value of that tensor, + and so may return different values for an audio clip split into snippets vs. a full clip. + + Args: + + x (Tensor): Input spectrogram(s) before being converted to decibel scale. Input should take + the form `(..., freq, time)`. Batched inputs should include a channel dimension and + have the form `(batch, channel, freq, time)`. + multiplier (float): Use 10. for power and 20. for amplitude + amin (float): Number to clamp ``x`` + db_multiplier (float): Log10(max(reference value and amin)) + top_db (float or None, optional): Minimum negative cut-off in decibels. A reasonable number + is 80. (Default: ``None``) + + Returns: + Tensor: Output tensor in decibel scale + """ + x_db = multiplier * torch.log10(torch.clamp(x, min=amin)) + x_db -= multiplier * db_multiplier + + if top_db is not None: + # Expand batch + shape = x_db.size() + packed_channels = shape[-3] if x_db.dim() > 2 else 1 + x_db = x_db.reshape(-1, packed_channels, shape[-2], shape[-1]) + + x_db = torch.max(x_db, (x_db.amax(dim=(-3, -2, -1)) - top_db).view(-1, 1, 1, 1)) + + # Repack batch + x_db = x_db.reshape(shape) + + return x_db + + +def DB_to_amplitude( + x: Tensor, + ref: float, + power: float +) -> Tensor: + r"""Turn a tensor from the decibel scale to the power/amplitude scale. + + Args: + x (Tensor): Input tensor before being converted to power/amplitude scale. + ref (float): Reference which the output will be scaled by. + power (float): If power equals 1, will compute DB to power. If 0.5, will compute DB to amplitude. + + Returns: + Tensor: Output tensor in power/amplitude scale. + """ + return ref * torch.pow(torch.pow(10.0, 0.1 * x), power) + + +def _hz_to_mel(freq: float, mel_scale: str = "htk") -> float: + r"""Convert Hz to Mels. + + Args: + freqs (float): Frequencies in Hz + mel_scale (str, optional): Scale to use: ``htk`` or ``slaney``. (Default: ``htk``) + + Returns: + mels (float): Frequency in Mels + """ + + if mel_scale not in ['slaney', 'htk']: + raise ValueError('mel_scale should be one of "htk" or "slaney".') + + if mel_scale == "htk": + return 2595.0 * math.log10(1.0 + (freq / 700.0)) + + # Fill in the linear part + f_min = 0.0 + f_sp = 200.0 / 3 + + mels = (freq - f_min) / f_sp + + # Fill in the log-scale part + min_log_hz = 1000.0 + min_log_mel = (min_log_hz - f_min) / f_sp + logstep = math.log(6.4) / 27.0 + + if freq >= min_log_hz: + mels = min_log_mel + math.log(freq / min_log_hz) / logstep + + return mels + + +def _mel_to_hz(mels: Tensor, mel_scale: str = "htk") -> Tensor: + """Convert mel bin numbers to frequencies. + + Args: + mels (Tensor): Mel frequencies + mel_scale (str, optional): Scale to use: ``htk`` or ``slaney``. (Default: ``htk``) + + Returns: + freqs (Tensor): Mels converted in Hz + """ + + if mel_scale not in ['slaney', 'htk']: + raise ValueError('mel_scale should be one of "htk" or "slaney".') + + if mel_scale == "htk": + return 700.0 * (10.0**(mels / 2595.0) - 1.0) + + # Fill in the linear scale + f_min = 0.0 + f_sp = 200.0 / 3 + freqs = f_min + f_sp * mels + + # And now the nonlinear scale + min_log_hz = 1000.0 + min_log_mel = (min_log_hz - f_min) / f_sp + logstep = math.log(6.4) / 27.0 + + log_t = (mels >= min_log_mel) + freqs[log_t] = min_log_hz * torch.exp(logstep * (mels[log_t] - min_log_mel)) + + return freqs + + +def _create_triangular_filterbank( + all_freqs: Tensor, + f_pts: Tensor, +) -> Tensor: + """Create a triangular filter bank. + + Args: + all_freqs (Tensor): STFT freq points of size (`n_freqs`). + f_pts (Tensor): Filter mid points of size (`n_filter`). + + Returns: + fb (Tensor): The filter bank of size (`n_freqs`, `n_filter`). + """ + # Adopted from Librosa + # calculate the difference between each filter mid point and each stft freq point in hertz + f_diff = f_pts[1:] - f_pts[:-1] # (n_filter + 1) + slopes = f_pts.unsqueeze(0) - all_freqs.unsqueeze(1) # (n_freqs, n_filter + 2) + # create overlapping triangles + zero = torch.zeros(1) + down_slopes = (-1.0 * slopes[:, :-2]) / f_diff[:-1] # (n_freqs, n_filter) + up_slopes = slopes[:, 2:] / f_diff[1:] # (n_freqs, n_filter) + fb = torch.max(zero, torch.min(down_slopes, up_slopes)) + + return fb + + +def create_fb_matrix( + n_freqs: int, + f_min: float, + f_max: float, + n_mels: int, + sample_rate: int, + norm: Optional[str] = None, + mel_scale: str = "htk", +) -> Tensor: + r"""Create a frequency bin conversion matrix. + + Args: + n_freqs (int): Number of frequencies to highlight/apply + f_min (float): Minimum frequency (Hz) + f_max (float): Maximum frequency (Hz) + n_mels (int): Number of mel filterbanks + sample_rate (int): Sample rate of the audio waveform + norm (str or None, optional): If 'slaney', divide the triangular mel weights by the width of the mel band + (area normalization). (Default: ``None``) + mel_scale (str, optional): Scale to use: ``htk`` or ``slaney``. (Default: ``htk``) + + Returns: + Tensor: Triangular filter banks (fb matrix) of size (``n_freqs``, ``n_mels``) + meaning number of frequencies to highlight/apply to x the number of filterbanks. + Each column is a filterbank so that assuming there is a matrix A of + size (..., ``n_freqs``), the applied result would be + ``A * create_fb_matrix(A.size(-1), ...)``. + """ + warnings.warn( + "The use of `create_fb_matrix` is now deprecated and will be removed in " + "the 0.11 release. " + "Please migrate your code to use `melscale_fbanks` instead. " + "For more information, please refer to https://github.com/pytorch/audio/issues/1574." + ) + + return melscale_fbanks( + n_freqs=n_freqs, + f_min=f_min, + f_max=f_max, + n_mels=n_mels, + sample_rate=sample_rate, + norm=norm, + mel_scale=mel_scale + ) + + +def melscale_fbanks( + n_freqs: int, + f_min: float, + f_max: float, + n_mels: int, + sample_rate: int, + norm: Optional[str] = None, + mel_scale: str = "htk", +) -> Tensor: + r"""Create a frequency bin conversion matrix. + + Note: + For the sake of the numerical compatibility with librosa, not all the coefficients + in the resulting filter bank has magnitude of 1. + + .. image:: https://download.pytorch.org/torchaudio/doc-assets/mel_fbanks.png + :alt: Visualization of generated filter bank + + Args: + n_freqs (int): Number of frequencies to highlight/apply + f_min (float): Minimum frequency (Hz) + f_max (float): Maximum frequency (Hz) + n_mels (int): Number of mel filterbanks + sample_rate (int): Sample rate of the audio waveform + norm (str or None, optional): If 'slaney', divide the triangular mel weights by the width of the mel band + (area normalization). (Default: ``None``) + mel_scale (str, optional): Scale to use: ``htk`` or ``slaney``. (Default: ``htk``) + + Returns: + Tensor: Triangular filter banks (fb matrix) of size (``n_freqs``, ``n_mels``) + meaning number of frequencies to highlight/apply to x the number of filterbanks. + Each column is a filterbank so that assuming there is a matrix A of + size (..., ``n_freqs``), the applied result would be + ``A * melscale_fbanks(A.size(-1), ...)``. + + """ + + if norm is not None and norm != "slaney": + raise ValueError("norm must be one of None or 'slaney'") + + # freq bins + all_freqs = torch.linspace(0, sample_rate // 2, n_freqs) + + # calculate mel freq bins + m_min = _hz_to_mel(f_min, mel_scale=mel_scale) + m_max = _hz_to_mel(f_max, mel_scale=mel_scale) + + m_pts = torch.linspace(m_min, m_max, n_mels + 2) + f_pts = _mel_to_hz(m_pts, mel_scale=mel_scale) + + # create filterbank + fb = _create_triangular_filterbank(all_freqs, f_pts) + + if norm is not None and norm == "slaney": + # Slaney-style mel is scaled to be approx constant energy per channel + enorm = 2.0 / (f_pts[2:n_mels + 2] - f_pts[:n_mels]) + fb *= enorm.unsqueeze(0) + + if (fb.max(dim=0).values == 0.).any(): + warnings.warn( + "At least one mel filterbank has all zero values. " + f"The value for `n_mels` ({n_mels}) may be set too high. " + f"Or, the value for `n_freqs` ({n_freqs}) may be set too low." + ) + + return fb + + +def linear_fbanks( + n_freqs: int, + f_min: float, + f_max: float, + n_filter: int, + sample_rate: int, +) -> Tensor: + r"""Creates a linear triangular filterbank. + + Note: + For the sake of the numerical compatibility with librosa, not all the coefficients + in the resulting filter bank has magnitude of 1. + + .. image:: https://download.pytorch.org/torchaudio/doc-assets/lin_fbanks.png + :alt: Visualization of generated filter bank + + Args: + n_freqs (int): Number of frequencies to highlight/apply + f_min (float): Minimum frequency (Hz) + f_max (float): Maximum frequency (Hz) + n_filter (int): Number of (linear) triangular filter + sample_rate (int): Sample rate of the audio waveform + + Returns: + Tensor: Triangular filter banks (fb matrix) of size (``n_freqs``, ``n_filter``) + meaning number of frequencies to highlight/apply to x the number of filterbanks. + Each column is a filterbank so that assuming there is a matrix A of + size (..., ``n_freqs``), the applied result would be + ``A * linear_fbanks(A.size(-1), ...)``. + """ + # freq bins + all_freqs = torch.linspace(0, sample_rate // 2, n_freqs) + + # filter mid-points + f_pts = torch.linspace(f_min, f_max, n_filter + 2) + + # create filterbank + fb = _create_triangular_filterbank(all_freqs, f_pts) + + return fb + + +def create_dct( + n_mfcc: int, + n_mels: int, + norm: Optional[str] +) -> Tensor: + r"""Create a DCT transformation matrix with shape (``n_mels``, ``n_mfcc``), + normalized depending on norm. + + Args: + n_mfcc (int): Number of mfc coefficients to retain + n_mels (int): Number of mel filterbanks + norm (str or None): Norm to use (either 'ortho' or None) + + Returns: + Tensor: The transformation matrix, to be right-multiplied to + row-wise data of size (``n_mels``, ``n_mfcc``). + """ + # http://en.wikipedia.org/wiki/Discrete_cosine_transform#DCT-II + n = torch.arange(float(n_mels)) + k = torch.arange(float(n_mfcc)).unsqueeze(1) + dct = torch.cos(math.pi / float(n_mels) * (n + 0.5) * k) # size (n_mfcc, n_mels) + if norm is None: + dct *= 2.0 + else: + assert norm == "ortho" + dct[0] *= 1.0 / math.sqrt(2.0) + dct *= math.sqrt(2.0 / float(n_mels)) + return dct.t() + + +def mu_law_encoding( + x: Tensor, + quantization_channels: int +) -> Tensor: + r"""Encode signal based on mu-law companding. For more info see the + `Wikipedia Entry `_ + + This algorithm assumes the signal has been scaled to between -1 and 1 and + returns a signal encoded with values from 0 to quantization_channels - 1. + + Args: + x (Tensor): Input tensor + quantization_channels (int): Number of channels + + Returns: + Tensor: Input after mu-law encoding + """ + mu = quantization_channels - 1.0 + if not x.is_floating_point(): + x = x.to(torch.float) + mu = torch.tensor(mu, dtype=x.dtype) + x_mu = torch.sign(x) * torch.log1p(mu * torch.abs(x)) / torch.log1p(mu) + x_mu = ((x_mu + 1) / 2 * mu + 0.5).to(torch.int64) + return x_mu + + +def mu_law_decoding( + x_mu: Tensor, + quantization_channels: int +) -> Tensor: + r"""Decode mu-law encoded signal. For more info see the + `Wikipedia Entry `_ + + This expects an input with values between 0 and quantization_channels - 1 + and returns a signal scaled between -1 and 1. + + Args: + x_mu (Tensor): Input tensor + quantization_channels (int): Number of channels + + Returns: + Tensor: Input after mu-law decoding + """ + mu = quantization_channels - 1.0 + if not x_mu.is_floating_point(): + x_mu = x_mu.to(torch.float) + mu = torch.tensor(mu, dtype=x_mu.dtype) + x = ((x_mu) / mu) * 2 - 1.0 + x = torch.sign(x) * (torch.exp(torch.abs(x) * torch.log1p(mu)) - 1.0) / mu + return x + + +@_mod_utils.deprecated( + "Please convert the input Tensor to complex type with `torch.view_as_complex` then " + "use `torch.abs`. " + "Please refer to https://github.com/pytorch/audio/issues/1337 " + "for more details about torchaudio's plan to migrate to native complex type.", + version="0.11", +) +def complex_norm( + complex_tensor: Tensor, + power: float = 1.0 +) -> Tensor: + r"""Compute the norm of complex tensor input. + + Args: + complex_tensor (Tensor): Tensor shape of `(..., complex=2)` + power (float, optional): Power of the norm. (Default: `1.0`). + + Returns: + Tensor: Power of the normed input tensor. Shape of `(..., )` + """ + + # Replace by torch.norm once issue is fixed + # https://github.com/pytorch/pytorch/issues/34279 + return complex_tensor.pow(2.).sum(-1).pow(0.5 * power) + + +@_mod_utils.deprecated( + "Please convert the input Tensor to complex type with `torch.view_as_complex` then " + "use `torch.angle`. " + "Please refer to https://github.com/pytorch/audio/issues/1337 " + "for more details about torchaudio's plan to migrate to native complex type.", + version="0.11", +) +def angle( + complex_tensor: Tensor +) -> Tensor: + r"""Compute the angle of complex tensor input. + + Args: + complex_tensor (Tensor): Tensor shape of `(..., complex=2)` + + Return: + Tensor: Angle of a complex tensor. Shape of `(..., )` + """ + return torch.atan2(complex_tensor[..., 1], complex_tensor[..., 0]) + + +@_mod_utils.deprecated( + "Please convert the input Tensor to complex type with `torch.view_as_complex` then " + "use `torch.abs` and `torch.angle`. " + "Please refer to https://github.com/pytorch/audio/issues/1337 " + "for more details about torchaudio's plan to migrate to native complex type.", + version="0.11", +) +def magphase( + complex_tensor: Tensor, + power: float = 1.0 +) -> Tuple[Tensor, Tensor]: + r"""Separate a complex-valued spectrogram with shape `(..., 2)` into its magnitude and phase. + + Args: + complex_tensor (Tensor): Tensor shape of `(..., complex=2)` + power (float, optional): Power of the norm. (Default: `1.0`) + + Returns: + (Tensor, Tensor): The magnitude and phase of the complex tensor + """ + mag = complex_norm(complex_tensor, power) + phase = angle(complex_tensor) + return mag, phase + + +def phase_vocoder( + complex_specgrams: Tensor, + rate: float, + phase_advance: Tensor +) -> Tensor: + r"""Given a STFT tensor, speed up in time without modifying pitch by a + factor of ``rate``. + + Args: + complex_specgrams (Tensor): + Either a real tensor of dimension of `(..., freq, num_frame, complex=2)` + or a tensor of dimension `(..., freq, num_frame)` with complex dtype. + rate (float): Speed-up factor + phase_advance (Tensor): Expected phase advance in each bin. Dimension of `(freq, 1)` + + Returns: + Tensor: + Stretched spectrogram. The resulting tensor is of the same dtype as the input + spectrogram, but the number of frames is changed to ``ceil(num_frame / rate)``. + + Example - With Tensor of complex dtype + >>> freq, hop_length = 1025, 512 + >>> # (channel, freq, time) + >>> complex_specgrams = torch.randn(2, freq, 300, dtype=torch.cfloat) + >>> rate = 1.3 # Speed up by 30% + >>> phase_advance = torch.linspace( + >>> 0, math.pi * hop_length, freq)[..., None] + >>> x = phase_vocoder(complex_specgrams, rate, phase_advance) + >>> x.shape # with 231 == ceil(300 / 1.3) + torch.Size([2, 1025, 231]) + + Example - With Tensor of real dtype and extra dimension for complex field + >>> freq, hop_length = 1025, 512 + >>> # (channel, freq, time, complex=2) + >>> complex_specgrams = torch.randn(2, freq, 300, 2) + >>> rate = 1.3 # Speed up by 30% + >>> phase_advance = torch.linspace( + >>> 0, math.pi * hop_length, freq)[..., None] + >>> x = phase_vocoder(complex_specgrams, rate, phase_advance) + >>> x.shape # with 231 == ceil(300 / 1.3) + torch.Size([2, 1025, 231, 2]) + """ + if rate == 1.0: + return complex_specgrams + + if not complex_specgrams.is_complex(): + warnings.warn( + "The support for pseudo complex type in `torchaudio.functional.phase_vocoder` and " + "`torchaudio.transforms.TimeStretch` is now deprecated and will be removed " + "from 0.11 release." + "Please migrate to native complex type by converting the input tensor with " + "`torch.view_as_complex`. " + "Please refer to https://github.com/pytorch/audio/issues/1337 " + "for more details about torchaudio's plan to migrate to native complex type." + ) + if complex_specgrams.size(-1) != 2: + raise ValueError( + "complex_specgrams must be either native complex tensors or " + "real valued tensors with shape (..., 2)") + + is_complex = complex_specgrams.is_complex() + + if not is_complex: + complex_specgrams = torch.view_as_complex(complex_specgrams) + + # pack batch + shape = complex_specgrams.size() + complex_specgrams = complex_specgrams.reshape([-1] + list(shape[-2:])) + + # Figures out the corresponding real dtype, i.e. complex128 -> float64, complex64 -> float32 + # Note torch.real is a view so it does not incur any memory copy. + real_dtype = torch.real(complex_specgrams).dtype + time_steps = torch.arange( + 0, + complex_specgrams.size(-1), + rate, + device=complex_specgrams.device, + dtype=real_dtype) + + alphas = time_steps % 1.0 + phase_0 = complex_specgrams[..., :1].angle() + + # Time Padding + complex_specgrams = torch.nn.functional.pad(complex_specgrams, [0, 2]) + + # (new_bins, freq, 2) + complex_specgrams_0 = complex_specgrams.index_select(-1, time_steps.long()) + complex_specgrams_1 = complex_specgrams.index_select(-1, (time_steps + 1).long()) + + angle_0 = complex_specgrams_0.angle() + angle_1 = complex_specgrams_1.angle() + + norm_0 = complex_specgrams_0.abs() + norm_1 = complex_specgrams_1.abs() + + phase = angle_1 - angle_0 - phase_advance + phase = phase - 2 * math.pi * torch.round(phase / (2 * math.pi)) + + # Compute Phase Accum + phase = phase + phase_advance + phase = torch.cat([phase_0, phase[..., :-1]], dim=-1) + phase_acc = torch.cumsum(phase, -1) + + mag = alphas * norm_1 + (1 - alphas) * norm_0 + + complex_specgrams_stretch = torch.polar(mag, phase_acc) + + # unpack batch + complex_specgrams_stretch = complex_specgrams_stretch.reshape(shape[:-2] + complex_specgrams_stretch.shape[1:]) + + if not is_complex: + return torch.view_as_real(complex_specgrams_stretch) + return complex_specgrams_stretch + + +def mask_along_axis_iid( + specgrams: Tensor, + mask_param: int, + mask_value: float, + axis: int +) -> Tensor: + r""" + Apply a mask along ``axis``. Mask will be applied from indices ``[v_0, v_0 + v)``, where + ``v`` is sampled from ``uniform(0, mask_param)``, and ``v_0`` from ``uniform(0, max_v - v)``. + + Args: + specgrams (Tensor): Real spectrograms `(batch, channel, freq, time)` + mask_param (int): Number of columns to be masked will be uniformly sampled from [0, mask_param] + mask_value (float): Value to assign to the masked columns + axis (int): Axis to apply masking on (2 -> frequency, 3 -> time) + + Returns: + Tensor: Masked spectrograms of dimensions `(batch, channel, freq, time)` + """ + + if axis not in [2, 3]: + raise ValueError('Only Frequency and Time masking are supported') + + device = specgrams.device + dtype = specgrams.dtype + + value = torch.rand(specgrams.shape[:2], device=device, dtype=dtype) * mask_param + min_value = torch.rand(specgrams.shape[:2], device=device, dtype=dtype) * (specgrams.size(axis) - value) + + # Create broadcastable mask + mask_start = min_value[..., None, None] + mask_end = (min_value + value)[..., None, None] + mask = torch.arange(0, specgrams.size(axis), device=device, dtype=dtype) + + # Per batch example masking + specgrams = specgrams.transpose(axis, -1) + specgrams = specgrams.masked_fill((mask >= mask_start) & (mask < mask_end), mask_value) + specgrams = specgrams.transpose(axis, -1) + + return specgrams + + +def mask_along_axis( + specgram: Tensor, + mask_param: int, + mask_value: float, + axis: int +) -> Tensor: + r""" + Apply a mask along ``axis``. Mask will be applied from indices ``[v_0, v_0 + v)``, where + ``v`` is sampled from ``uniform(0, mask_param)``, and ``v_0`` from ``uniform(0, max_v - v)``. + All examples will have the same mask interval. + + Args: + specgram (Tensor): Real spectrogram `(channel, freq, time)` + mask_param (int): Number of columns to be masked will be uniformly sampled from [0, mask_param] + mask_value (float): Value to assign to the masked columns + axis (int): Axis to apply masking on (1 -> frequency, 2 -> time) + + Returns: + Tensor: Masked spectrogram of dimensions `(channel, freq, time)` + """ + if axis not in [1, 2]: + raise ValueError('Only Frequency and Time masking are supported') + + # pack batch + shape = specgram.size() + specgram = specgram.reshape([-1] + list(shape[-2:])) + value = torch.rand(1) * mask_param + min_value = torch.rand(1) * (specgram.size(axis) - value) + + mask_start = (min_value.long()).squeeze() + mask_end = (min_value.long() + value.long()).squeeze() + mask = torch.arange(0, specgram.shape[axis], device=specgram.device, dtype=specgram.dtype) + mask = (mask >= mask_start) & (mask < mask_end) + if axis == 1: + mask = mask.unsqueeze(-1) + + assert mask_end - mask_start < mask_param + + specgram = specgram.masked_fill(mask, mask_value) + + # unpack batch + specgram = specgram.reshape(shape[:-2] + specgram.shape[-2:]) + + return specgram + + +def compute_deltas( + specgram: Tensor, + win_length: int = 5, + mode: str = "replicate" +) -> Tensor: + r"""Compute delta coefficients of a tensor, usually a spectrogram: + + .. math:: + d_t = \frac{\sum_{n=1}^{\text{N}} n (c_{t+n} - c_{t-n})}{2 \sum_{n=1}^{\text{N}} n^2} + + where :math:`d_t` is the deltas at time :math:`t`, + :math:`c_t` is the spectrogram coeffcients at time :math:`t`, + :math:`N` is ``(win_length-1)//2``. + + Args: + specgram (Tensor): Tensor of audio of dimension `(..., freq, time)` + win_length (int, optional): The window length used for computing delta (Default: ``5``) + mode (str, optional): Mode parameter passed to padding (Default: ``"replicate"``) + + Returns: + Tensor: Tensor of deltas of dimension `(..., freq, time)` + + Example + >>> specgram = torch.randn(1, 40, 1000) + >>> delta = compute_deltas(specgram) + >>> delta2 = compute_deltas(delta) + """ + device = specgram.device + dtype = specgram.dtype + + # pack batch + shape = specgram.size() + specgram = specgram.reshape(1, -1, shape[-1]) + + assert win_length >= 3 + + n = (win_length - 1) // 2 + + # twice sum of integer squared + denom = n * (n + 1) * (2 * n + 1) / 3 + + specgram = torch.nn.functional.pad(specgram, (n, n), mode=mode) + + kernel = torch.arange(-n, n + 1, 1, device=device, dtype=dtype).repeat(specgram.shape[1], 1, 1) + + output = torch.nn.functional.conv1d(specgram, kernel, groups=specgram.shape[1]) / denom + + # unpack batch + output = output.reshape(shape) + + return output + + +def _compute_nccf( + waveform: Tensor, + sample_rate: int, + frame_time: float, + freq_low: int +) -> Tensor: + r""" + Compute Normalized Cross-Correlation Function (NCCF). + + .. math:: + \phi_i(m) = \frac{\sum_{n=b_i}^{b_i + N-1} w(n) w(m+n)}{\sqrt{E(b_i) E(m+b_i)}}, + + where + :math:`\phi_i(m)` is the NCCF at frame :math:`i` with lag :math:`m`, + :math:`w` is the waveform, + :math:`N` is the length of a frame, + :math:`b_i` is the beginning of frame :math:`i`, + :math:`E(j)` is the energy :math:`\sum_{n=j}^{j+N-1} w^2(n)`. + """ + + EPSILON = 10 ** (-9) + + # Number of lags to check + lags = int(math.ceil(sample_rate / freq_low)) + + frame_size = int(math.ceil(sample_rate * frame_time)) + + waveform_length = waveform.size()[-1] + num_of_frames = int(math.ceil(waveform_length / frame_size)) + + p = lags + num_of_frames * frame_size - waveform_length + waveform = torch.nn.functional.pad(waveform, (0, p)) + + # Compute lags + output_lag = [] + for lag in range(1, lags + 1): + s1 = waveform[..., :-lag].unfold(-1, frame_size, frame_size)[..., :num_of_frames, :] + s2 = waveform[..., lag:].unfold(-1, frame_size, frame_size)[..., :num_of_frames, :] + + output_frames = ( + (s1 * s2).sum(-1) + / (EPSILON + torch.norm(s1, p=2, dim=-1)).pow(2) + / (EPSILON + torch.norm(s2, p=2, dim=-1)).pow(2) + ) + + output_lag.append(output_frames.unsqueeze(-1)) + + nccf = torch.cat(output_lag, -1) + + return nccf + + +def _combine_max( + a: Tuple[Tensor, Tensor], + b: Tuple[Tensor, Tensor], + thresh: float = 0.99 +) -> Tuple[Tensor, Tensor]: + """ + Take value from first if bigger than a multiplicative factor of the second, elementwise. + """ + mask = (a[0] > thresh * b[0]) + values = mask * a[0] + ~mask * b[0] + indices = mask * a[1] + ~mask * b[1] + return values, indices + + +def _find_max_per_frame( + nccf: Tensor, + sample_rate: int, + freq_high: int +) -> Tensor: + r""" + For each frame, take the highest value of NCCF, + apply centered median smoothing, and convert to frequency. + + Note: If the max among all the lags is very close + to the first half of lags, then the latter is taken. + """ + + lag_min = int(math.ceil(sample_rate / freq_high)) + + # Find near enough max that is smallest + + best = torch.max(nccf[..., lag_min:], -1) + + half_size = nccf.shape[-1] // 2 + half = torch.max(nccf[..., lag_min:half_size], -1) + + best = _combine_max(half, best) + indices = best[1] + + # Add back minimal lag + indices += lag_min + # Add 1 empirical calibration offset + indices += 1 + + return indices + + +def _median_smoothing( + indices: Tensor, + win_length: int +) -> Tensor: + r""" + Apply median smoothing to the 1D tensor over the given window. + """ + + # Centered windowed + pad_length = (win_length - 1) // 2 + + # "replicate" padding in any dimension + indices = torch.nn.functional.pad( + indices, (pad_length, 0), mode="constant", value=0. + ) + + indices[..., :pad_length] = torch.cat(pad_length * [indices[..., pad_length].unsqueeze(-1)], dim=-1) + roll = indices.unfold(-1, win_length, 1) + + values, _ = torch.median(roll, -1) + return values + + +def detect_pitch_frequency( + waveform: Tensor, + sample_rate: int, + frame_time: float = 10 ** (-2), + win_length: int = 30, + freq_low: int = 85, + freq_high: int = 3400, +) -> Tensor: + r"""Detect pitch frequency. + + It is implemented using normalized cross-correlation function and median smoothing. + + Args: + waveform (Tensor): Tensor of audio of dimension `(..., freq, time)` + sample_rate (int): The sample rate of the waveform (Hz) + frame_time (float, optional): Duration of a frame (Default: ``10 ** (-2)``). + win_length (int, optional): The window length for median smoothing (in number of frames) (Default: ``30``). + freq_low (int, optional): Lowest frequency that can be detected (Hz) (Default: ``85``). + freq_high (int, optional): Highest frequency that can be detected (Hz) (Default: ``3400``). + + Returns: + Tensor: Tensor of freq of dimension `(..., frame)` + """ + # pack batch + shape = list(waveform.size()) + waveform = waveform.reshape([-1] + shape[-1:]) + + nccf = _compute_nccf(waveform, sample_rate, frame_time, freq_low) + indices = _find_max_per_frame(nccf, sample_rate, freq_high) + indices = _median_smoothing(indices, win_length) + + # Convert indices to frequency + EPSILON = 10 ** (-9) + freq = sample_rate / (EPSILON + indices.to(torch.float)) + + # unpack batch + freq = freq.reshape(shape[:-1] + list(freq.shape[-1:])) + + return freq + + +def sliding_window_cmn( + specgram: Tensor, + cmn_window: int = 600, + min_cmn_window: int = 100, + center: bool = False, + norm_vars: bool = False, +) -> Tensor: + r""" + Apply sliding-window cepstral mean (and optionally variance) normalization per utterance. + + Args: + specgram (Tensor): Tensor of spectrogram of dimension `(..., time, freq)` + cmn_window (int, optional): Window in frames for running average CMN computation (int, default = 600) + min_cmn_window (int, optional): Minimum CMN window used at start of decoding (adds latency only at start). + Only applicable if center == false, ignored if center==true (int, default = 100) + center (bool, optional): If true, use a window centered on the current frame + (to the extent possible, modulo end effects). If false, window is to the left. (bool, default = false) + norm_vars (bool, optional): If true, normalize variance to one. (bool, default = false) + + Returns: + Tensor: Tensor matching input shape `(..., freq, time)` + """ + input_shape = specgram.shape + num_frames, num_feats = input_shape[-2:] + specgram = specgram.view(-1, num_frames, num_feats) + num_channels = specgram.shape[0] + + dtype = specgram.dtype + device = specgram.device + last_window_start = last_window_end = -1 + cur_sum = torch.zeros(num_channels, num_feats, dtype=dtype, device=device) + cur_sumsq = torch.zeros(num_channels, num_feats, dtype=dtype, device=device) + cmn_specgram = torch.zeros( + num_channels, num_frames, num_feats, dtype=dtype, device=device) + for t in range(num_frames): + window_start = 0 + window_end = 0 + if center: + window_start = t - cmn_window // 2 + window_end = window_start + cmn_window + else: + window_start = t - cmn_window + window_end = t + 1 + if window_start < 0: + window_end -= window_start + window_start = 0 + if not center: + if window_end > t: + window_end = max(t + 1, min_cmn_window) + if window_end > num_frames: + window_start -= (window_end - num_frames) + window_end = num_frames + if window_start < 0: + window_start = 0 + if last_window_start == -1: + input_part = specgram[:, window_start: window_end - window_start, :] + cur_sum += torch.sum(input_part, 1) + if norm_vars: + cur_sumsq += torch.cumsum(input_part ** 2, 1)[:, -1, :] + else: + if window_start > last_window_start: + frame_to_remove = specgram[:, last_window_start, :] + cur_sum -= frame_to_remove + if norm_vars: + cur_sumsq -= (frame_to_remove ** 2) + if window_end > last_window_end: + frame_to_add = specgram[:, last_window_end, :] + cur_sum += frame_to_add + if norm_vars: + cur_sumsq += (frame_to_add ** 2) + window_frames = window_end - window_start + last_window_start = window_start + last_window_end = window_end + cmn_specgram[:, t, :] = specgram[:, t, :] - cur_sum / window_frames + if norm_vars: + if window_frames == 1: + cmn_specgram[:, t, :] = torch.zeros( + num_channels, num_feats, dtype=dtype, device=device) + else: + variance = cur_sumsq + variance = variance / window_frames + variance -= ((cur_sum ** 2) / (window_frames ** 2)) + variance = torch.pow(variance, -0.5) + cmn_specgram[:, t, :] *= variance + + cmn_specgram = cmn_specgram.view(input_shape[:-2] + (num_frames, num_feats)) + if len(input_shape) == 2: + cmn_specgram = cmn_specgram.squeeze(0) + return cmn_specgram + + +def spectral_centroid( + waveform: Tensor, + sample_rate: int, + pad: int, + window: Tensor, + n_fft: int, + hop_length: int, + win_length: int, +) -> Tensor: + r""" + Compute the spectral centroid for each channel along the time axis. + + The spectral centroid is defined as the weighted average of the + frequency values, weighted by their magnitude. + + Args: + waveform (Tensor): Tensor of audio of dimension `(..., time)` + sample_rate (int): Sample rate of the audio waveform + pad (int): Two sided padding of signal + window (Tensor): Window tensor that is applied/multiplied to each frame/window + n_fft (int): Size of FFT + hop_length (int): Length of hop between STFT windows + win_length (int): Window size + + Returns: + Tensor: Dimension `(..., time)` + """ + specgram = spectrogram(waveform, pad=pad, window=window, n_fft=n_fft, hop_length=hop_length, + win_length=win_length, power=1., normalized=False) + freqs = torch.linspace(0, sample_rate // 2, steps=1 + n_fft // 2, + device=specgram.device).reshape((-1, 1)) + freq_dim = -2 + return (freqs * specgram).sum(dim=freq_dim) / specgram.sum(dim=freq_dim) + + +@_mod_utils.requires_sox() +def apply_codec( + waveform: Tensor, + sample_rate: int, + format: str, + channels_first: bool = True, + compression: Optional[float] = None, + encoding: Optional[str] = None, + bits_per_sample: Optional[int] = None, +) -> Tensor: + r""" + Apply codecs as a form of augmentation. + + Args: + waveform (Tensor): Audio data. Must be 2 dimensional. See also ```channels_first```. + sample_rate (int): Sample rate of the audio waveform. + format (str): File format. + channels_first (bool, optional): + When True, both the input and output Tensor have dimension `(channel, time)`. + Otherwise, they have dimension `(time, channel)`. + compression (float or None, optional): Used for formats other than WAV. + For more details see :py:func:`torchaudio.backend.sox_io_backend.save`. + encoding (str or None, optional): Changes the encoding for the supported formats. + For more details see :py:func:`torchaudio.backend.sox_io_backend.save`. + bits_per_sample (int or None, optional): Changes the bit depth for the supported formats. + For more details see :py:func:`torchaudio.backend.sox_io_backend.save`. + + Returns: + Tensor: Resulting Tensor. + If ``channels_first=True``, it has `(channel, time)` else `(time, channel)`. + """ + bytes = io.BytesIO() + torchaudio.backend.sox_io_backend.save(bytes, + waveform, + sample_rate, + channels_first, + compression, + format, + encoding, + bits_per_sample + ) + bytes.seek(0) + augmented, _ = torchaudio.sox_effects.sox_effects.apply_effects_file( + bytes, effects=[["rate", f"{sample_rate}"]], channels_first=channels_first, format=format) + return augmented + + +@_mod_utils.requires_kaldi() +def compute_kaldi_pitch( + waveform: torch.Tensor, + sample_rate: float, + frame_length: float = 25.0, + frame_shift: float = 10.0, + min_f0: float = 50, + max_f0: float = 400, + soft_min_f0: float = 10.0, + penalty_factor: float = 0.1, + lowpass_cutoff: float = 1000, + resample_frequency: float = 4000, + delta_pitch: float = 0.005, + nccf_ballast: float = 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, + snip_edges: bool = True, +) -> torch.Tensor: + """Extract pitch based on method described in *A pitch extraction algorithm tuned + for automatic speech recognition* [:footcite:`6854049`]. + + This function computes the equivalent of `compute-kaldi-pitch-feats` from Kaldi. + + Args: + waveform (Tensor): + The input waveform of shape `(..., time)`. + sample_rate (float): + Sample rate of `waveform`. + frame_length (float, optional): + Frame length in milliseconds. (default: 25.0) + frame_shift (float, optional): + Frame shift in milliseconds. (default: 10.0) + min_f0 (float, optional): + Minimum F0 to search for (Hz) (default: 50.0) + max_f0 (float, optional): + Maximum F0 to search for (Hz) (default: 400.0) + soft_min_f0 (float, optional): + Minimum f0, applied in soft way, must not exceed min-f0 (default: 10.0) + penalty_factor (float, optional): + Cost factor for FO change. (default: 0.1) + lowpass_cutoff (float, optional): + Cutoff frequency for LowPass filter (Hz) (default: 1000) + resample_frequency (float, optional): + Frequency that we down-sample the signal to. Must be more than twice lowpass-cutoff. + (default: 4000) + delta_pitch( float, optional): + Smallest relative change in pitch that our algorithm measures. (default: 0.005) + nccf_ballast (float, optional): + Increasing this factor reduces NCCF for quiet frames (default: 7000) + lowpass_filter_width (int, optional): + Integer that determines filter width of lowpass filter, more gives sharper filter. + (default: 1) + upsample_filter_width (int, optional): + Integer that determines filter width when upsampling NCCF. (default: 5) + max_frames_latency (int, optional): + Maximum number of frames of latency that we allow pitch tracking to introduce into + the feature processing (affects output only if ``frames_per_chunk > 0`` and + ``simulate_first_pass_online=True``) (default: 0) + frames_per_chunk (int, optional): + The number of frames used for energy normalization. (default: 0) + simulate_first_pass_online (bool, optional): + If true, the function will output features that correspond to what an online decoder + would see in the first pass of decoding -- not the final version of the features, + which is the default. (default: False) + Relevant if ``frames_per_chunk > 0``. + recompute_frame (int, optional): + Only relevant for compatibility with online pitch extraction. + A non-critical parameter; the frame at which we recompute some of the forward pointers, + after revising our estimate of the signal energy. + Relevant if ``frames_per_chunk > 0``. (default: 500) + snip_edges (bool, optional): + If this is set to false, the incomplete frames near the ending edge won't be snipped, + so that the number of frames is the file size divided by the frame-shift. + This makes different types of features give the same number of frames. (default: True) + + Returns: + Tensor: Pitch feature. Shape: `(batch, frames 2)` where the last dimension + corresponds to pitch and NCCF. + """ + shape = waveform.shape + waveform = waveform.reshape(-1, shape[-1]) + result = torch.ops.torchaudio.kaldi_ComputeKaldiPitch( + waveform, sample_rate, frame_length, frame_shift, + min_f0, max_f0, soft_min_f0, penalty_factor, lowpass_cutoff, + resample_frequency, delta_pitch, nccf_ballast, + lowpass_filter_width, upsample_filter_width, max_frames_latency, + frames_per_chunk, simulate_first_pass_online, recompute_frame, + snip_edges, + ) + result = result.reshape(shape[:-1] + result.shape[-2:]) + return result + + +def _get_sinc_resample_kernel( + orig_freq: int, + new_freq: int, + gcd: int, + lowpass_filter_width: int, + rolloff: float, + resampling_method: str, + beta: Optional[float], + device: torch.device = torch.device("cpu"), + dtype: Optional[torch.dtype] = None): + + if not (int(orig_freq) == orig_freq and int(new_freq) == new_freq): + raise Exception( + "Frequencies must be of integer type to ensure quality resampling computation. " + "To work around this, manually convert both frequencies to integer values " + "that maintain their resampling rate ratio before passing them into the function. " + "Example: To downsample a 44100 hz waveform by a factor of 8, use " + "`orig_freq=8` and `new_freq=1` instead of `orig_freq=44100` and `new_freq=5512.5`. " + "For more information, please refer to https://github.com/pytorch/audio/issues/1487." + ) + + if resampling_method not in ['sinc_interpolation', 'kaiser_window']: + raise ValueError('Invalid resampling method: {}'.format(resampling_method)) + + orig_freq = int(orig_freq) // gcd + new_freq = int(new_freq) // gcd + + assert lowpass_filter_width > 0 + kernels = [] + base_freq = min(orig_freq, new_freq) + # This will perform antialiasing filtering by removing the highest frequencies. + # At first I thought I only needed this when downsampling, but when upsampling + # you will get edge artifacts without this, as the edge is equivalent to zero padding, + # which will add high freq artifacts. + base_freq *= rolloff + + # The key idea of the algorithm is that x(t) can be exactly reconstructed from x[i] (tensor) + # using the sinc interpolation formula: + # x(t) = sum_i x[i] sinc(pi * orig_freq * (i / orig_freq - t)) + # We can then sample the function x(t) with a different sample rate: + # y[j] = x(j / new_freq) + # or, + # y[j] = sum_i x[i] sinc(pi * orig_freq * (i / orig_freq - j / new_freq)) + + # We see here that y[j] is the convolution of x[i] with a specific filter, for which + # we take an FIR approximation, stopping when we see at least `lowpass_filter_width` zeros crossing. + # But y[j+1] is going to have a different set of weights and so on, until y[j + new_freq]. + # Indeed: + # y[j + new_freq] = sum_i x[i] sinc(pi * orig_freq * ((i / orig_freq - (j + new_freq) / new_freq)) + # = sum_i x[i] sinc(pi * orig_freq * ((i - orig_freq) / orig_freq - j / new_freq)) + # = sum_i x[i + orig_freq] sinc(pi * orig_freq * (i / orig_freq - j / new_freq)) + # so y[j+new_freq] uses the same filter as y[j], but on a shifted version of x by `orig_freq`. + # This will explain the F.conv1d after, with a stride of orig_freq. + width = math.ceil(lowpass_filter_width * orig_freq / base_freq) + # If orig_freq is still big after GCD reduction, most filters will be very unbalanced, i.e., + # they will have a lot of almost zero values to the left or to the right... + # There is probably a way to evaluate those filters more efficiently, but this is kept for + # future work. + idx_dtype = dtype if dtype is not None else torch.float64 + idx = torch.arange(-width, width + orig_freq, device=device, dtype=idx_dtype) + + for i in range(new_freq): + t = (-i / new_freq + idx / orig_freq) * base_freq + t = t.clamp_(-lowpass_filter_width, lowpass_filter_width) + + # we do not use built in torch windows here as we need to evaluate the window + # at specific positions, not over a regular grid. + if resampling_method == "sinc_interpolation": + window = torch.cos(t * math.pi / lowpass_filter_width / 2)**2 + else: + # kaiser_window + if beta is None: + beta = 14.769656459379492 + beta_tensor = torch.tensor(float(beta)) + window = torch.i0(beta_tensor * torch.sqrt(1 - (t / lowpass_filter_width) ** 2)) / torch.i0(beta_tensor) + t *= math.pi + kernel = torch.where(t == 0, torch.tensor(1.).to(t), torch.sin(t) / t) + kernel.mul_(window) + kernels.append(kernel) + + scale = base_freq / orig_freq + kernels = torch.stack(kernels).view(new_freq, 1, -1).mul_(scale) + if dtype is None: + kernels = kernels.to(dtype=torch.float32) + return kernels, width + + +def _apply_sinc_resample_kernel( + waveform: Tensor, + orig_freq: int, + new_freq: int, + gcd: int, + kernel: Tensor, + width: int, +): + orig_freq = int(orig_freq) // gcd + new_freq = int(new_freq) // gcd + + # pack batch + shape = waveform.size() + waveform = waveform.view(-1, shape[-1]) + + num_wavs, length = waveform.shape + waveform = torch.nn.functional.pad(waveform, (width, width + orig_freq)) + resampled = torch.nn.functional.conv1d(waveform[:, None], kernel, stride=orig_freq) + resampled = resampled.transpose(1, 2).reshape(num_wavs, -1) + target_length = int(math.ceil(new_freq * length / orig_freq)) + resampled = resampled[..., :target_length] + + # unpack batch + resampled = resampled.view(shape[:-1] + resampled.shape[-1:]) + return resampled + + +def resample( + waveform: Tensor, + orig_freq: int, + new_freq: int, + lowpass_filter_width: int = 6, + rolloff: float = 0.99, + resampling_method: str = "sinc_interpolation", + beta: Optional[float] = None, +) -> Tensor: + r"""Resamples the waveform at the new frequency using bandlimited interpolation. + + https://ccrma.stanford.edu/~jos/resample/Theory_Ideal_Bandlimited_Interpolation.html + + Note: + ``transforms.Resample`` precomputes and reuses the resampling kernel, so using it will result in + more efficient computation if resampling multiple waveforms with the same resampling parameters. + + Args: + waveform (Tensor): The input signal of dimension `(..., time)` + orig_freq (int): The original frequency of the signal + new_freq (int): The desired frequency + lowpass_filter_width (int, optional): Controls the sharpness of the filter, more == sharper + but less efficient. (Default: ``6``) + rolloff (float, optional): The roll-off frequency of the filter, as a fraction of the Nyquist. + Lower values reduce anti-aliasing, but also reduce some of the highest frequencies. (Default: ``0.99``) + resampling_method (str, optional): The resampling method to use. + Options: [``sinc_interpolation``, ``kaiser_window``] (Default: ``'sinc_interpolation'``) + beta (float or None, optional): The shape parameter used for kaiser window. + + Returns: + Tensor: The waveform at the new frequency of dimension `(..., time).` + """ + + assert orig_freq > 0.0 and new_freq > 0.0 + + if orig_freq == new_freq: + return waveform + + gcd = math.gcd(int(orig_freq), int(new_freq)) + + kernel, width = _get_sinc_resample_kernel(orig_freq, new_freq, gcd, lowpass_filter_width, rolloff, + resampling_method, beta, waveform.device, waveform.dtype) + resampled = _apply_sinc_resample_kernel(waveform, orig_freq, new_freq, gcd, kernel, width) + return resampled + + +@torch.jit.unused +def edit_distance(seq1: Sequence, seq2: Sequence) -> int: + """ + Calculate the word level edit (Levenshtein) distance between two sequences. + + The function computes an edit distance allowing deletion, insertion and + substitution. The result is an integer. + + For most applications, the two input sequences should be the same type. If + two strings are given, the output is the edit distance between the two + strings (character edit distance). If two lists of strings are given, the + output is the edit distance between sentences (word edit distance). Users + may want to normalize the output by the length of the reference sequence. + + torchscipt is not supported for this function. + + Args: + seq1 (Sequence): the first sequence to compare. + seq2 (Sequence): the second sequence to compare. + Returns: + int: The distance between the first and second sequences. + """ + len_sent2 = len(seq2) + dold = list(range(len_sent2 + 1)) + dnew = [0 for _ in range(len_sent2 + 1)] + + for i in range(1, len(seq1) + 1): + dnew[0] = i + for j in range(1, len_sent2 + 1): + if seq1[i - 1] == seq2[j - 1]: + dnew[j] = dold[j - 1] + else: + substitution = dold[j - 1] + 1 + insertion = dnew[j - 1] + 1 + deletion = dold[j] + 1 + dnew[j] = min(substitution, insertion, deletion) + + dnew, dold = dold, dnew + + return int(dold[-1]) + + +def pitch_shift( + waveform: Tensor, + sample_rate: int, + n_steps: int, + bins_per_octave: int = 12, + n_fft: int = 512, + win_length: Optional[int] = None, + hop_length: Optional[int] = None, + window: Optional[Tensor] = None, +) -> Tensor: + """ + Shift the pitch of a waveform by ``n_steps`` steps. + + Args: + waveform (Tensor): The input waveform of shape `(..., time)`. + sample_rate (int): Sample rate of `waveform`. + n_steps (int): The (fractional) steps to shift `waveform`. + bins_per_octave (int, optional): The number of steps per octave (Default: ``12``). + n_fft (int, optional): Size of FFT, creates ``n_fft // 2 + 1`` bins (Default: ``512``). + win_length (int or None, optional): Window size. If None, then ``n_fft`` is used. (Default: ``None``). + hop_length (int or None, optional): Length of hop between STFT windows. If None, then + ``win_length // 4`` is used (Default: ``None``). + window (Tensor or None, optional): Window tensor that is applied/multiplied to each frame/window. + If None, then ``torch.hann_window(win_length)`` is used (Default: ``None``). + + + Returns: + Tensor: The pitch-shifted audio waveform of shape `(..., time)`. + """ + if hop_length is None: + hop_length = n_fft // 4 + if win_length is None: + win_length = n_fft + if window is None: + window = torch.hann_window(window_length=win_length, device=waveform.device) + + # pack batch + shape = waveform.size() + waveform = waveform.reshape(-1, shape[-1]) + + ori_len = shape[-1] + rate = 2.0 ** (-float(n_steps) / bins_per_octave) + spec_f = torch.stft(input=waveform, + n_fft=n_fft, + hop_length=hop_length, + win_length=win_length, + window=window, + center=True, + pad_mode='reflect', + normalized=False, + onesided=True, + return_complex=True) + phase_advance = torch.linspace(0, math.pi * hop_length, spec_f.shape[-2], device=spec_f.device)[..., None] + spec_stretch = phase_vocoder(spec_f, rate, phase_advance) + len_stretch = int(round(ori_len / rate)) + waveform_stretch = torch.istft(spec_stretch, + n_fft=n_fft, + hop_length=hop_length, + win_length=win_length, + window=window, + length=len_stretch) + waveform_shift = resample(waveform_stretch, int(sample_rate / rate), sample_rate) + shift_len = waveform_shift.size()[-1] + if shift_len > ori_len: + waveform_shift = waveform_shift[..., :ori_len] + else: + waveform_shift = torch.nn.functional.pad(waveform_shift, [0, ori_len - shift_len]) + + # unpack batch + waveform_shift = waveform_shift.view(shape[:-1] + waveform_shift.shape[-1:]) + return waveform_shift + + +def rnnt_loss( + logits: Tensor, + targets: Tensor, + logit_lengths: Tensor, + target_lengths: Tensor, + blank: int = -1, + clamp: float = -1, + reduction: str = "mean", +): + """Compute the RNN Transducer loss from *Sequence Transduction with Recurrent Neural Networks* + [:footcite:`graves2012sequence`]. + The RNN Transducer loss extends the CTC loss by defining a distribution over output + sequences of all lengths, and by jointly modelling both input-output and output-output + dependencies. + + Args: + logits (Tensor): Tensor of dimension `(batch, max seq length, max target length + 1, class)` + containing output from joiner + targets (Tensor): Tensor of dimension `(batch, max target length)` containing targets with zero padded + logit_lengths (Tensor): Tensor of dimension `(batch)` containing lengths of each sequence from encoder + target_lengths (Tensor): Tensor of dimension `(batch)` containing lengths of targets for each sequence + blank (int, optional): blank label (Default: ``-1``) + clamp (float, optional): clamp for gradients (Default: ``-1``) + reduction (string, optional): Specifies the reduction to apply to the output: + ``'none'`` | ``'mean'`` | ``'sum'``. (Default: ``'mean'``) + Returns: + Tensor: Loss with the reduction option applied. If ``reduction`` is ``'none'``, then size `(batch)`, + otherwise scalar. + """ + if reduction not in ['none', 'mean', 'sum']: + raise ValueError("reduction should be one of 'none', 'mean', or 'sum'") + + if blank < 0: # reinterpret blank index if blank < 0. + blank = logits.shape[-1] + blank + + costs, _ = torch.ops.torchaudio.rnnt_loss( + logits=logits, + targets=targets, + logit_lengths=logit_lengths, + target_lengths=target_lengths, + blank=blank, + clamp=clamp, + ) + + if reduction == 'mean': + return costs.mean() + elif reduction == 'sum': + return costs.sum() + + return costs diff --git a/torchaudio/kaldi_io.py b/torchaudio/kaldi_io.py new file mode 100644 index 00000000..ba1689da --- /dev/null +++ b/torchaudio/kaldi_io.py @@ -0,0 +1,130 @@ +# To use this file, the dependency (https://github.com/vesis84/kaldi-io-for-python) +# needs to be installed. This is a light wrapper around kaldi_io that returns +# torch.Tensors. +from typing import Any, Callable, Iterable, Tuple + +import torch +from torch import Tensor +from torchaudio._internal import module_utils as _mod_utils + +if _mod_utils.is_module_available('kaldi_io', 'numpy'): + import numpy as np + import kaldi_io + + +__all__ = [ + 'read_vec_int_ark', + 'read_vec_flt_scp', + 'read_vec_flt_ark', + 'read_mat_scp', + 'read_mat_ark', +] + + +def _convert_method_output_to_tensor(file_or_fd: Any, + fn: Callable, + convert_contiguous: bool = False) -> Iterable[Tuple[str, Tensor]]: + r"""Takes a method invokes it. The output is converted to a tensor. + + Args: + file_or_fd (str/FileDescriptor): File name or file descriptor + fn (Callable): Function that has the signature (file name/descriptor) and converts it to + Iterable[Tuple[str, Tensor]]. + convert_contiguous (bool, optional): Determines whether the array should be converted into a + contiguous layout. (Default: ``False``) + + Returns: + Iterable[Tuple[str, Tensor]]: The string is the key and the tensor is vec/mat + """ + for key, np_arr in fn(file_or_fd): + if convert_contiguous: + np_arr = np.ascontiguousarray(np_arr) + yield key, torch.from_numpy(np_arr) + + +@_mod_utils.requires_module('kaldi_io', 'numpy') +def read_vec_int_ark(file_or_fd: Any) -> Iterable[Tuple[str, Tensor]]: + r"""Create generator of (key,vector) tuples, which reads from the ark file/stream. + + Args: + file_or_fd (str/FileDescriptor): ark, gzipped ark, pipe or opened file descriptor + + Returns: + Iterable[Tuple[str, Tensor]]: The string is the key and the tensor is the vector read from file + + Example + >>> # read ark to a 'dictionary' + >>> d = { u:d for u,d in torchaudio.kaldi_io.read_vec_int_ark(file) } + """ + # Requires convert_contiguous to be True because elements from int32 vector are + # sorted in tuples: (sizeof(int32), value) so strides are (5,) instead of (4,) which will throw an error + # in from_numpy as it expects strides to be a multiple of 4 (int32). + return _convert_method_output_to_tensor(file_or_fd, kaldi_io.read_vec_int_ark, convert_contiguous=True) + + +@_mod_utils.requires_module('kaldi_io', 'numpy') +def read_vec_flt_scp(file_or_fd: Any) -> Iterable[Tuple[str, Tensor]]: + r"""Create generator of (key,vector) tuples, read according to Kaldi scp. + + Args: + file_or_fd (str/FileDescriptor): scp, gzipped scp, pipe or opened file descriptor + + Returns: + Iterable[Tuple[str, Tensor]]: The string is the key and the tensor is the vector read from file + + Example + >>> # read scp to a 'dictionary' + >>> # d = { u:d for u,d in torchaudio.kaldi_io.read_vec_flt_scp(file) } + """ + return _convert_method_output_to_tensor(file_or_fd, kaldi_io.read_vec_flt_scp) + + +@_mod_utils.requires_module('kaldi_io', 'numpy') +def read_vec_flt_ark(file_or_fd: Any) -> Iterable[Tuple[str, Tensor]]: + r"""Create generator of (key,vector) tuples, which reads from the ark file/stream. + + Args: + file_or_fd (str/FileDescriptor): ark, gzipped ark, pipe or opened file descriptor + + Returns: + Iterable[Tuple[str, Tensor]]: The string is the key and the tensor is the vector read from file + + Example + >>> # read ark to a 'dictionary' + >>> d = { u:d for u,d in torchaudio.kaldi_io.read_vec_flt_ark(file) } + """ + return _convert_method_output_to_tensor(file_or_fd, kaldi_io.read_vec_flt_ark) + + +@_mod_utils.requires_module('kaldi_io', 'numpy') +def read_mat_scp(file_or_fd: Any) -> Iterable[Tuple[str, Tensor]]: + r"""Create generator of (key,matrix) tuples, read according to Kaldi scp. + + Args: + file_or_fd (str/FileDescriptor): scp, gzipped scp, pipe or opened file descriptor + + Returns: + Iterable[Tuple[str, Tensor]]: The string is the key and the tensor is the matrix read from file + + Example + >>> # read scp to a 'dictionary' + >>> d = { u:d for u,d in torchaudio.kaldi_io.read_mat_scp(file) } + """ + return _convert_method_output_to_tensor(file_or_fd, kaldi_io.read_mat_scp) + + +@_mod_utils.requires_module('kaldi_io', 'numpy') +def read_mat_ark(file_or_fd: Any) -> Iterable[Tuple[str, Tensor]]: + r"""Create generator of (key,matrix) tuples, which reads from the ark file/stream. + + Args: + file_or_fd (str/FileDescriptor): ark, gzipped ark, pipe or opened file descriptor + + Returns: + Iterable[Tuple[str, Tensor]]: The string is the key and the tensor is the matrix read from file + + Example + >>> # read ark to a 'dictionary' + >>> d = { u:d for u,d in torchaudio.kaldi_io.read_mat_ark(file) } + """ + return _convert_method_output_to_tensor(file_or_fd, kaldi_io.read_mat_ark) diff --git a/torchaudio/models/__init__.py b/torchaudio/models/__init__.py new file mode 100644 index 00000000..956fc82e --- /dev/null +++ b/torchaudio/models/__init__.py @@ -0,0 +1,31 @@ +from .wav2letter import Wav2Letter +from .wavernn import WaveRNN +from .conv_tasnet import ConvTasNet +from .deepspeech import DeepSpeech +from .tacotron2 import Tacotron2 +from .wav2vec2 import ( + Wav2Vec2Model, + wav2vec2_model, + wav2vec2_base, + wav2vec2_large, + wav2vec2_large_lv60k, + hubert_base, + hubert_large, + hubert_xlarge, +) + +__all__ = [ + 'Wav2Letter', + 'WaveRNN', + 'ConvTasNet', + 'DeepSpeech', + 'Wav2Vec2Model', + 'wav2vec2_model', + 'wav2vec2_base', + 'wav2vec2_large', + 'wav2vec2_large_lv60k', + 'hubert_base', + 'hubert_large', + 'hubert_xlarge', + 'Tacotron2', +] diff --git a/torchaudio/models/conv_tasnet.py b/torchaudio/models/conv_tasnet.py new file mode 100644 index 00000000..39d3dae0 --- /dev/null +++ b/torchaudio/models/conv_tasnet.py @@ -0,0 +1,321 @@ +"""Implements Conv-TasNet with building blocks of it. + +Based on https://github.com/naplab/Conv-TasNet/tree/e66d82a8f956a69749ec8a4ae382217faa097c5c +""" + +from typing import Tuple, Optional + +import torch + + +class ConvBlock(torch.nn.Module): + """1D Convolutional block. + + Args: + io_channels (int): The number of input/output channels, + hidden_channels (int): The number of channels in the internal layers, . + kernel_size (int): The convolution kernel size of the middle layer,

    . + padding (int): Padding value of the convolution in the middle layer. + dilation (int, optional): Dilation value of the convolution in the middle layer. + no_redisual (bool, optional): Disable residual block/output. + + Note: + This implementation corresponds to the "non-causal" setting in the paper. + """ + + def __init__( + self, + io_channels: int, + hidden_channels: int, + kernel_size: int, + padding: int, + dilation: int = 1, + no_residual: bool = False, + ): + super().__init__() + + self.conv_layers = torch.nn.Sequential( + torch.nn.Conv1d( + in_channels=io_channels, out_channels=hidden_channels, kernel_size=1 + ), + torch.nn.PReLU(), + torch.nn.GroupNorm(num_groups=1, num_channels=hidden_channels, eps=1e-08), + torch.nn.Conv1d( + in_channels=hidden_channels, + out_channels=hidden_channels, + kernel_size=kernel_size, + padding=padding, + dilation=dilation, + groups=hidden_channels, + ), + torch.nn.PReLU(), + torch.nn.GroupNorm(num_groups=1, num_channels=hidden_channels, eps=1e-08), + ) + + self.res_out = ( + None + if no_residual + else torch.nn.Conv1d( + in_channels=hidden_channels, out_channels=io_channels, kernel_size=1 + ) + ) + self.skip_out = torch.nn.Conv1d( + in_channels=hidden_channels, out_channels=io_channels, kernel_size=1 + ) + + def forward( + self, input: torch.Tensor + ) -> Tuple[Optional[torch.Tensor], torch.Tensor]: + feature = self.conv_layers(input) + if self.res_out is None: + residual = None + else: + residual = self.res_out(feature) + skip_out = self.skip_out(feature) + return residual, skip_out + + +class MaskGenerator(torch.nn.Module): + """TCN (Temporal Convolution Network) Separation Module + + Generates masks for separation. + + Args: + input_dim (int): Input feature dimension, . + num_sources (int): The number of sources to separate. + kernel_size (int): The convolution kernel size of conv blocks,

    . + num_featrs (int): Input/output feature dimenstion of conv blocks, . + num_hidden (int): Intermediate feature dimention of conv blocks, + num_layers (int): The number of conv blocks in one stack, . + num_stacks (int): The number of conv block stacks, . + msk_activate (str): The activation function of the mask output. + + Note: + This implementation corresponds to the "non-causal" setting in the paper. + """ + + def __init__( + self, + input_dim: int, + num_sources: int, + kernel_size: int, + num_feats: int, + num_hidden: int, + num_layers: int, + num_stacks: int, + msk_activate: str, + ): + super().__init__() + + self.input_dim = input_dim + self.num_sources = num_sources + + self.input_norm = torch.nn.GroupNorm( + num_groups=1, num_channels=input_dim, eps=1e-8 + ) + self.input_conv = torch.nn.Conv1d( + in_channels=input_dim, out_channels=num_feats, kernel_size=1 + ) + + self.receptive_field = 0 + self.conv_layers = torch.nn.ModuleList([]) + for s in range(num_stacks): + for l in range(num_layers): + multi = 2 ** l + self.conv_layers.append( + ConvBlock( + io_channels=num_feats, + hidden_channels=num_hidden, + kernel_size=kernel_size, + dilation=multi, + padding=multi, + # The last ConvBlock does not need residual + no_residual=(l == (num_layers - 1) and s == (num_stacks - 1)), + ) + ) + self.receptive_field += ( + kernel_size if s == 0 and l == 0 else (kernel_size - 1) * multi + ) + self.output_prelu = torch.nn.PReLU() + self.output_conv = torch.nn.Conv1d( + in_channels=num_feats, out_channels=input_dim * num_sources, kernel_size=1, + ) + if msk_activate == "sigmoid": + self.mask_activate = torch.nn.Sigmoid() + elif msk_activate == "relu": + self.mask_activate = torch.nn.ReLU() + else: + raise ValueError(f"Unsupported activation {msk_activate}") + + def forward(self, input: torch.Tensor) -> torch.Tensor: + """Generate separation mask. + + Args: + input (torch.Tensor): 3D Tensor with shape [batch, features, frames] + + Returns: + Tensor: shape [batch, num_sources, features, frames] + """ + batch_size = input.shape[0] + feats = self.input_norm(input) + feats = self.input_conv(feats) + output = 0.0 + for layer in self.conv_layers: + residual, skip = layer(feats) + if residual is not None: # the last conv layer does not produce residual + feats = feats + residual + output = output + skip + output = self.output_prelu(output) + output = self.output_conv(output) + output = self.mask_activate(output) + return output.view(batch_size, self.num_sources, self.input_dim, -1) + + +class ConvTasNet(torch.nn.Module): + """Conv-TasNet: a fully-convolutional time-domain audio separation network + *Conv-TasNet: Surpassing Ideal Time–Frequency Magnitude Masking for Speech Separation* + [:footcite:`Luo_2019`]. + + Args: + num_sources (int, optional): The number of sources to split. + enc_kernel_size (int, optional): The convolution kernel size of the encoder/decoder, . + enc_num_feats (int, optional): The feature dimensions passed to mask generator, . + msk_kernel_size (int, optional): The convolution kernel size of the mask generator,

    . + msk_num_feats (int, optional): The input/output feature dimension of conv block in the mask generator, . + msk_num_hidden_feats (int, optional): The internal feature dimension of conv block of the mask generator, . + msk_num_layers (int, optional): The number of layers in one conv block of the mask generator, . + msk_num_stacks (int, optional): The numbr of conv blocks of the mask generator, . + msk_activate (str, optional): The activation function of the mask output (Default: ``sigmoid``). + + Note: + This implementation corresponds to the "non-causal" setting in the paper. + """ + + def __init__( + self, + num_sources: int = 2, + # encoder/decoder parameters + enc_kernel_size: int = 16, + enc_num_feats: int = 512, + # mask generator parameters + msk_kernel_size: int = 3, + msk_num_feats: int = 128, + msk_num_hidden_feats: int = 512, + msk_num_layers: int = 8, + msk_num_stacks: int = 3, + msk_activate: str = "sigmoid", + ): + super().__init__() + + self.num_sources = num_sources + self.enc_num_feats = enc_num_feats + self.enc_kernel_size = enc_kernel_size + self.enc_stride = enc_kernel_size // 2 + + self.encoder = torch.nn.Conv1d( + in_channels=1, + out_channels=enc_num_feats, + kernel_size=enc_kernel_size, + stride=self.enc_stride, + padding=self.enc_stride, + bias=False, + ) + self.mask_generator = MaskGenerator( + input_dim=enc_num_feats, + num_sources=num_sources, + kernel_size=msk_kernel_size, + num_feats=msk_num_feats, + num_hidden=msk_num_hidden_feats, + num_layers=msk_num_layers, + num_stacks=msk_num_stacks, + msk_activate=msk_activate, + ) + self.decoder = torch.nn.ConvTranspose1d( + in_channels=enc_num_feats, + out_channels=1, + kernel_size=enc_kernel_size, + stride=self.enc_stride, + padding=self.enc_stride, + bias=False, + ) + + def _align_num_frames_with_strides( + self, input: torch.Tensor + ) -> Tuple[torch.Tensor, int]: + """Pad input Tensor so that the end of the input tensor corresponds with + + 1. (if kernel size is odd) the center of the last convolution kernel + or 2. (if kernel size is even) the end of the first half of the last convolution kernel + + Assumption: + The resulting Tensor will be padded with the size of stride (== kernel_width // 2) + on the both ends in Conv1D + + |<--- k_1 --->| + | | |<-- k_n-1 -->| + | | | |<--- k_n --->| + | | | | | + | | | | | + | v v v | + |<---->|<--- input signal --->|<--->|<---->| + stride PAD stride + + Args: + input (torch.Tensor): 3D Tensor with shape (batch_size, channels==1, frames) + + Returns: + Tensor: Padded Tensor + int: Number of paddings performed + """ + batch_size, num_channels, num_frames = input.shape + is_odd = self.enc_kernel_size % 2 + num_strides = (num_frames - is_odd) // self.enc_stride + num_remainings = num_frames - (is_odd + num_strides * self.enc_stride) + if num_remainings == 0: + return input, 0 + + num_paddings = self.enc_stride - num_remainings + pad = torch.zeros( + batch_size, + num_channels, + num_paddings, + dtype=input.dtype, + device=input.device, + ) + return torch.cat([input, pad], 2), num_paddings + + def forward(self, input: torch.Tensor) -> torch.Tensor: + """Perform source separation. Generate audio source waveforms. + + Args: + input (torch.Tensor): 3D Tensor with shape [batch, channel==1, frames] + + Returns: + Tensor: 3D Tensor with shape [batch, channel==num_sources, frames] + """ + if input.ndim != 3 or input.shape[1] != 1: + raise ValueError( + f"Expected 3D tensor (batch, channel==1, frames). Found: {input.shape}" + ) + + # B: batch size + # L: input frame length + # L': padded input frame length + # F: feature dimension + # M: feature frame length + # S: number of sources + + padded, num_pads = self._align_num_frames_with_strides(input) # B, 1, L' + batch_size, num_padded_frames = padded.shape[0], padded.shape[2] + feats = self.encoder(padded) # B, F, M + masked = self.mask_generator(feats) * feats.unsqueeze(1) # B, S, F, M + masked = masked.view( + batch_size * self.num_sources, self.enc_num_feats, -1 + ) # B*S, F, M + decoded = self.decoder(masked) # B*S, 1, L' + output = decoded.view( + batch_size, self.num_sources, num_padded_frames + ) # B, S, L' + if num_pads > 0: + output = output[..., :-num_pads] # B, S, L + return output diff --git a/torchaudio/models/deepspeech.py b/torchaudio/models/deepspeech.py new file mode 100644 index 00000000..41efc07d --- /dev/null +++ b/torchaudio/models/deepspeech.py @@ -0,0 +1,91 @@ +import torch + +__all__ = ["DeepSpeech"] + + +class FullyConnected(torch.nn.Module): + """ + Args: + n_feature: Number of input features + n_hidden: Internal hidden unit size. + """ + + def __init__(self, + n_feature: int, + n_hidden: int, + dropout: float, + relu_max_clip: int = 20) -> None: + super(FullyConnected, self).__init__() + self.fc = torch.nn.Linear(n_feature, n_hidden, bias=True) + self.relu_max_clip = relu_max_clip + self.dropout = dropout + + def forward(self, x: torch.Tensor) -> torch.Tensor: + x = self.fc(x) + x = torch.nn.functional.relu(x) + x = torch.nn.functional.hardtanh(x, 0, self.relu_max_clip) + if self.dropout: + x = torch.nn.functional.dropout(x, self.dropout, self.training) + return x + + +class DeepSpeech(torch.nn.Module): + """ + DeepSpeech model architecture from *Deep Speech: Scaling up end-to-end speech recognition* + [:footcite:`hannun2014deep`]. + + Args: + n_feature: Number of input features + n_hidden: Internal hidden unit size. + n_class: Number of output classes + """ + + def __init__( + self, + n_feature: int, + n_hidden: int = 2048, + n_class: int = 40, + dropout: float = 0.0, + ) -> None: + super(DeepSpeech, self).__init__() + self.n_hidden = n_hidden + self.fc1 = FullyConnected(n_feature, n_hidden, dropout) + self.fc2 = FullyConnected(n_hidden, n_hidden, dropout) + self.fc3 = FullyConnected(n_hidden, n_hidden, dropout) + self.bi_rnn = torch.nn.RNN( + n_hidden, n_hidden, num_layers=1, nonlinearity="relu", bidirectional=True + ) + self.fc4 = FullyConnected(n_hidden, n_hidden, dropout) + self.out = torch.nn.Linear(n_hidden, n_class) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + """ + Args: + x (torch.Tensor): Tensor of dimension (batch, channel, time, feature). + Returns: + Tensor: Predictor tensor of dimension (batch, time, class). + """ + # N x C x T x F + x = self.fc1(x) + # N x C x T x H + x = self.fc2(x) + # N x C x T x H + x = self.fc3(x) + # N x C x T x H + x = x.squeeze(1) + # N x T x H + x = x.transpose(0, 1) + # T x N x H + x, _ = self.bi_rnn(x) + # The fifth (non-recurrent) layer takes both the forward and backward units as inputs + x = x[:, :, :self.n_hidden] + x[:, :, self.n_hidden:] + # T x N x H + x = self.fc4(x) + # T x N x H + x = self.out(x) + # T x N x n_class + x = x.permute(1, 0, 2) + # N x T x n_class + x = torch.nn.functional.log_softmax(x, dim=2) + # N x T x n_class + return x diff --git a/torchaudio/models/tacotron2.py b/torchaudio/models/tacotron2.py new file mode 100644 index 00000000..109d396a --- /dev/null +++ b/torchaudio/models/tacotron2.py @@ -0,0 +1,1109 @@ +# ***************************************************************************** +# Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the NVIDIA CORPORATION nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# ***************************************************************************** + +import warnings +from math import sqrt +from typing import Tuple, List, Optional, Union + +import torch +from torch import nn +from torch import Tensor +from torch.nn import functional as F + + +__all__ = [ + "Tacotron2", +] + + +def _get_linear_layer( + in_dim: int, out_dim: int, bias: bool = True, w_init_gain: str = "linear" +) -> torch.nn.Linear: + r"""Linear layer with xavier uniform initialization. + + Args: + in_dim (int): Size of each input sample. + out_dim (int): Size of each output sample. + bias (bool, optional): If set to ``False``, the layer will not learn an additive bias. (Default: ``True``) + w_init_gain (str, optional): Parameter passed to ``torch.nn.init.calculate_gain`` + for setting the gain parameter of ``xavier_uniform_``. (Default: ``linear``) + + Returns: + (torch.nn.Linear): The corresponding linear layer. + """ + linear = torch.nn.Linear(in_dim, out_dim, bias=bias) + torch.nn.init.xavier_uniform_( + linear.weight, gain=torch.nn.init.calculate_gain(w_init_gain) + ) + return linear + + +def _get_conv1d_layer( + in_channels: int, + out_channels: int, + kernel_size: int = 1, + stride: int = 1, + padding: Optional[Union[str, int, Tuple[int]]] = None, + dilation: int = 1, + bias: bool = True, + w_init_gain: str = "linear", +) -> torch.nn.Conv1d: + r"""1D convolution with xavier uniform initialization. + + Args: + in_channels (int): Number of channels in the input image. + out_channels (int): Number of channels produced by the convolution. + kernel_size (int, optional): Number of channels in the input image. (Default: ``1``) + stride (int, optional): Number of channels in the input image. (Default: ``1``) + padding (str, int or tuple, optional): Padding added to both sides of the input. + (Default: dilation * (kernel_size - 1) / 2) + dilation (int, optional): Number of channels in the input image. (Default: ``1``) + w_init_gain (str, optional): Parameter passed to ``torch.nn.init.calculate_gain`` + for setting the gain parameter of ``xavier_uniform_``. (Default: ``linear``) + + Returns: + (torch.nn.Conv1d): The corresponding Conv1D layer. + """ + if padding is None: + assert kernel_size % 2 == 1 + padding = int(dilation * (kernel_size - 1) / 2) + + conv1d = torch.nn.Conv1d( + in_channels, + out_channels, + kernel_size=kernel_size, + stride=stride, + padding=padding, + dilation=dilation, + bias=bias, + ) + + torch.nn.init.xavier_uniform_( + conv1d.weight, gain=torch.nn.init.calculate_gain(w_init_gain) + ) + + return conv1d + + +def _get_mask_from_lengths(lengths: Tensor) -> Tensor: + r"""Returns a binary mask based on ``lengths``. The ``i``-th row and ``j``-th column of the mask + is ``1`` if ``j`` is smaller than ``i``-th element of ``lengths. + + Args: + lengths (Tensor): The length of each element in the batch, with shape (n_batch, ). + + Returns: + mask (Tensor): The binary mask, with shape (n_batch, max of ``lengths``). + """ + max_len = torch.max(lengths).item() + ids = torch.arange(0, max_len, device=lengths.device, dtype=lengths.dtype) + mask = (ids < lengths.unsqueeze(1)).byte() + mask = torch.le(mask, 0) + return mask + + +class _LocationLayer(nn.Module): + r"""Location layer used in the Attention model. + + Args: + attention_n_filter (int): Number of filters for attention model. + attention_kernel_size (int): Kernel size for attention model. + attention_hidden_dim (int): Dimension of attention hidden representation. + """ + + def __init__( + self, + attention_n_filter: int, + attention_kernel_size: int, + attention_hidden_dim: int, + ): + super().__init__() + padding = int((attention_kernel_size - 1) / 2) + self.location_conv = _get_conv1d_layer( + 2, + attention_n_filter, + kernel_size=attention_kernel_size, + padding=padding, + bias=False, + stride=1, + dilation=1, + ) + self.location_dense = _get_linear_layer( + attention_n_filter, attention_hidden_dim, bias=False, w_init_gain="tanh" + ) + + def forward(self, attention_weights_cat: Tensor) -> Tensor: + r"""Location layer used in the Attention model. + + Args: + attention_weights_cat (Tensor): Cumulative and previous attention weights + with shape (n_batch, 2, max of ``text_lengths``). + + Returns: + processed_attention (Tensor): Cumulative and previous attention weights + with shape (n_batch, ``attention_hidden_dim``). + """ + # (n_batch, attention_n_filter, text_lengths.max()) + processed_attention = self.location_conv(attention_weights_cat) + processed_attention = processed_attention.transpose(1, 2) + # (n_batch, text_lengths.max(), attention_hidden_dim) + processed_attention = self.location_dense(processed_attention) + return processed_attention + + +class _Attention(nn.Module): + r"""Locally sensitive attention model. + + Args: + attention_rnn_dim (int): Number of hidden units for RNN. + encoder_embedding_dim (int): Number of embedding dimensions in the Encoder. + attention_hidden_dim (int): Dimension of attention hidden representation. + attention_location_n_filter (int): Number of filters for Attention model. + attention_location_kernel_size (int): Kernel size for Attention model. + """ + + def __init__( + self, + attention_rnn_dim: int, + encoder_embedding_dim: int, + attention_hidden_dim: int, + attention_location_n_filter: int, + attention_location_kernel_size: int, + ) -> None: + super().__init__() + self.query_layer = _get_linear_layer( + attention_rnn_dim, attention_hidden_dim, bias=False, w_init_gain="tanh" + ) + self.memory_layer = _get_linear_layer( + encoder_embedding_dim, attention_hidden_dim, bias=False, w_init_gain="tanh" + ) + self.v = _get_linear_layer(attention_hidden_dim, 1, bias=False) + self.location_layer = _LocationLayer( + attention_location_n_filter, + attention_location_kernel_size, + attention_hidden_dim, + ) + self.score_mask_value = -float("inf") + + def _get_alignment_energies( + self, query: Tensor, processed_memory: Tensor, attention_weights_cat: Tensor + ) -> Tensor: + r"""Get the alignment vector. + + Args: + query (Tensor): Decoder output with shape (n_batch, n_mels * n_frames_per_step). + processed_memory (Tensor): Processed Encoder outputs + with shape (n_batch, max of ``text_lengths``, attention_hidden_dim). + attention_weights_cat (Tensor): Cumulative and previous attention weights + with shape (n_batch, 2, max of ``text_lengths``). + + Returns: + alignment (Tensor): attention weights, it is a tensor with shape (batch, max of ``text_lengths``). + """ + + processed_query = self.query_layer(query.unsqueeze(1)) + processed_attention_weights = self.location_layer(attention_weights_cat) + energies = self.v( + torch.tanh(processed_query + processed_attention_weights + processed_memory) + ) + + alignment = energies.squeeze(2) + return alignment + + def forward( + self, + attention_hidden_state: Tensor, + memory: Tensor, + processed_memory: Tensor, + attention_weights_cat: Tensor, + mask: Tensor, + ) -> Tuple[Tensor, Tensor]: + r"""Pass the input through the Attention model. + + Args: + attention_hidden_state (Tensor): Attention rnn last output with shape (n_batch, ``attention_rnn_dim``). + memory (Tensor): Encoder outputs with shape (n_batch, max of ``text_lengths``, ``encoder_embedding_dim``). + processed_memory (Tensor): Processed Encoder outputs + with shape (n_batch, max of ``text_lengths``, ``attention_hidden_dim``). + attention_weights_cat (Tensor): Previous and cumulative attention weights + with shape (n_batch, current_num_frames * 2, max of ``text_lengths``). + mask (Tensor): Binary mask for padded data with shape (n_batch, current_num_frames). + + Returns: + attention_context (Tensor): Context vector with shape (n_batch, ``encoder_embedding_dim``). + attention_weights (Tensor): Attention weights with shape (n_batch, max of ``text_lengths``). + """ + alignment = self._get_alignment_energies( + attention_hidden_state, processed_memory, attention_weights_cat + ) + + alignment = alignment.masked_fill(mask, self.score_mask_value) + + attention_weights = F.softmax(alignment, dim=1) + attention_context = torch.bmm(attention_weights.unsqueeze(1), memory) + attention_context = attention_context.squeeze(1) + + return attention_context, attention_weights + + +class _Prenet(nn.Module): + r"""Prenet Module. It is consists of ``len(output_size)`` linear layers. + + Args: + in_dim (int): The size of each input sample. + output_sizes (list): The output dimension of each linear layers. + """ + + def __init__(self, in_dim: int, out_sizes: List[int]) -> None: + super().__init__() + in_sizes = [in_dim] + out_sizes[:-1] + self.layers = nn.ModuleList( + [ + _get_linear_layer(in_size, out_size, bias=False) + for (in_size, out_size) in zip(in_sizes, out_sizes) + ] + ) + + def forward(self, x: Tensor) -> Tensor: + r"""Pass the input through Prenet. + + Args: + x (Tensor): The input sequence to Prenet with shape (n_batch, in_dim). + + Return: + x (Tensor): Tensor with shape (n_batch, sizes[-1]) + """ + + for linear in self.layers: + x = F.dropout(F.relu(linear(x)), p=0.5, training=True) + return x + + +class _Postnet(nn.Module): + r"""Postnet Module. + + Args: + n_mels (int): Number of mel bins. + postnet_embedding_dim (int): Postnet embedding dimension. + postnet_kernel_size (int): Postnet kernel size. + postnet_n_convolution (int): Number of postnet convolutions. + """ + + def __init__( + self, + n_mels: int, + postnet_embedding_dim: int, + postnet_kernel_size: int, + postnet_n_convolution: int, + ): + super().__init__() + self.convolutions = nn.ModuleList() + + for i in range(postnet_n_convolution): + in_channels = n_mels if i == 0 else postnet_embedding_dim + out_channels = n_mels if i == (postnet_n_convolution - 1) else postnet_embedding_dim + init_gain = "linear" if i == (postnet_n_convolution - 1) else "tanh" + num_features = n_mels if i == (postnet_n_convolution - 1) else postnet_embedding_dim + self.convolutions.append( + nn.Sequential( + _get_conv1d_layer( + in_channels, + out_channels, + kernel_size=postnet_kernel_size, + stride=1, + padding=int((postnet_kernel_size - 1) / 2), + dilation=1, + w_init_gain=init_gain, + ), + nn.BatchNorm1d(num_features), + ) + ) + + self.n_convs = len(self.convolutions) + + def forward(self, x: Tensor) -> Tensor: + r"""Pass the input through Postnet. + + Args: + x (Tensor): The input sequence with shape (n_batch, ``n_mels``, max of ``mel_specgram_lengths``). + + Return: + x (Tensor): Tensor with shape (n_batch, ``n_mels``, max of ``mel_specgram_lengths``). + """ + + for i, conv in enumerate(self.convolutions): + if i < self.n_convs - 1: + x = F.dropout(torch.tanh(conv(x)), 0.5, training=self.training) + else: + x = F.dropout(conv(x), 0.5, training=self.training) + + return x + + +class _Encoder(nn.Module): + r"""Encoder Module. + + Args: + encoder_embedding_dim (int): Number of embedding dimensions in the encoder. + encoder_n_convolution (int): Number of convolution layers in the encoder. + encoder_kernel_size (int): The kernel size in the encoder. + + Examples + >>> encoder = _Encoder(3, 512, 5) + >>> input = torch.rand(10, 20, 30) + >>> output = encoder(input) # shape: (10, 30, 512) + """ + + def __init__( + self, + encoder_embedding_dim: int, + encoder_n_convolution: int, + encoder_kernel_size: int, + ) -> None: + super().__init__() + + self.convolutions = nn.ModuleList() + for _ in range(encoder_n_convolution): + conv_layer = nn.Sequential( + _get_conv1d_layer( + encoder_embedding_dim, + encoder_embedding_dim, + kernel_size=encoder_kernel_size, + stride=1, + padding=int((encoder_kernel_size - 1) / 2), + dilation=1, + w_init_gain="relu", + ), + nn.BatchNorm1d(encoder_embedding_dim), + ) + self.convolutions.append(conv_layer) + + self.lstm = nn.LSTM( + encoder_embedding_dim, + int(encoder_embedding_dim / 2), + 1, + batch_first=True, + bidirectional=True, + ) + self.lstm.flatten_parameters() + + def forward(self, x: Tensor, input_lengths: Tensor) -> Tensor: + r"""Pass the input through the Encoder. + + Args: + x (Tensor): The input sequences with shape (n_batch, encoder_embedding_dim, n_seq). + input_lengths (Tensor): The length of each input sequence with shape (n_batch, ). + + Return: + x (Tensor): A tensor with shape (n_batch, n_seq, encoder_embedding_dim). + """ + + for conv in self.convolutions: + x = F.dropout(F.relu(conv(x)), 0.5, self.training) + + x = x.transpose(1, 2) + + input_lengths = input_lengths.cpu() + x = nn.utils.rnn.pack_padded_sequence(x, input_lengths, batch_first=True) + + outputs, _ = self.lstm(x) + outputs, _ = nn.utils.rnn.pad_packed_sequence(outputs, batch_first=True) + + return outputs + + +class _Decoder(nn.Module): + r"""Decoder with Attention model. + + Args: + n_mels (int): number of mel bins + n_frames_per_step (int): number of frames processed per step, only 1 is supported + encoder_embedding_dim (int): the number of embedding dimensions in the encoder. + decoder_rnn_dim (int): number of units in decoder LSTM + decoder_max_step (int): maximum number of output mel spectrograms + decoder_dropout (float): dropout probability for decoder LSTM + decoder_early_stopping (bool): stop decoding when all samples are finished + attention_rnn_dim (int): number of units in attention LSTM + attention_hidden_dim (int): dimension of attention hidden representation + attention_location_n_filter (int): number of filters for attention model + attention_location_kernel_size (int): kernel size for attention model + attention_dropout (float): dropout probability for attention LSTM + prenet_dim (int): number of ReLU units in prenet layers + gate_threshold (float): probability threshold for stop token + """ + + def __init__( + self, + n_mels: int, + n_frames_per_step: int, + encoder_embedding_dim: int, + decoder_rnn_dim: int, + decoder_max_step: int, + decoder_dropout: float, + decoder_early_stopping: bool, + attention_rnn_dim: int, + attention_hidden_dim: int, + attention_location_n_filter: int, + attention_location_kernel_size: int, + attention_dropout: float, + prenet_dim: int, + gate_threshold: float, + ) -> None: + + super().__init__() + self.n_mels = n_mels + self.n_frames_per_step = n_frames_per_step + self.encoder_embedding_dim = encoder_embedding_dim + self.attention_rnn_dim = attention_rnn_dim + self.decoder_rnn_dim = decoder_rnn_dim + self.prenet_dim = prenet_dim + self.decoder_max_step = decoder_max_step + self.gate_threshold = gate_threshold + self.attention_dropout = attention_dropout + self.decoder_dropout = decoder_dropout + self.decoder_early_stopping = decoder_early_stopping + + self.prenet = _Prenet(n_mels * n_frames_per_step, [prenet_dim, prenet_dim]) + + self.attention_rnn = nn.LSTMCell( + prenet_dim + encoder_embedding_dim, attention_rnn_dim + ) + + self.attention_layer = _Attention( + attention_rnn_dim, + encoder_embedding_dim, + attention_hidden_dim, + attention_location_n_filter, + attention_location_kernel_size, + ) + + self.decoder_rnn = nn.LSTMCell( + attention_rnn_dim + encoder_embedding_dim, decoder_rnn_dim, True + ) + + self.linear_projection = _get_linear_layer( + decoder_rnn_dim + encoder_embedding_dim, n_mels * n_frames_per_step + ) + + self.gate_layer = _get_linear_layer( + decoder_rnn_dim + encoder_embedding_dim, 1, bias=True, w_init_gain="sigmoid" + ) + + def _get_initial_frame(self, memory: Tensor) -> Tensor: + r"""Gets all zeros frames to use as the first decoder input. + + Args: + memory (Tensor): Encoder outputs with shape (n_batch, max of ``text_lengths``, ``encoder_embedding_dim``). + + Returns: + decoder_input (Tensor): all zeros frames with shape + (n_batch, max of ``text_lengths``, ``n_mels * n_frames_per_step``). + """ + + n_batch = memory.size(0) + dtype = memory.dtype + device = memory.device + decoder_input = torch.zeros( + n_batch, self.n_mels * self.n_frames_per_step, dtype=dtype, device=device + ) + return decoder_input + + def _initialize_decoder_states( + self, memory: Tensor + ) -> Tuple[Tensor, Tensor, Tensor, Tensor, Tensor, Tensor, Tensor, Tensor]: + r"""Initializes attention rnn states, decoder rnn states, attention + weights, attention cumulative weights, attention context, stores memory + and stores processed memory. + + Args: + memory (Tensor): Encoder outputs with shape (n_batch, max of ``text_lengths``, ``encoder_embedding_dim``). + + Returns: + attention_hidden (Tensor): Hidden state of the attention LSTM with shape (n_batch, ``attention_rnn_dim``). + attention_cell (Tensor): Hidden state of the attention LSTM with shape (n_batch, ``attention_rnn_dim``). + decoder_hidden (Tensor): Hidden state of the decoder LSTM with shape (n_batch, ``decoder_rnn_dim``). + decoder_cell (Tensor): Hidden state of the decoder LSTM with shape (n_batch, ``decoder_rnn_dim``). + attention_weights (Tensor): Attention weights with shape (n_batch, max of ``text_lengths``). + attention_weights_cum (Tensor): Cumulated attention weights with shape (n_batch, max of ``text_lengths``). + attention_context (Tensor): Context vector with shape (n_batch, ``encoder_embedding_dim``). + processed_memory (Tensor): Processed encoder outputs + with shape (n_batch, max of ``text_lengths``, ``attention_hidden_dim``). + """ + n_batch = memory.size(0) + max_time = memory.size(1) + dtype = memory.dtype + device = memory.device + + attention_hidden = torch.zeros( + n_batch, self.attention_rnn_dim, dtype=dtype, device=device + ) + attention_cell = torch.zeros( + n_batch, self.attention_rnn_dim, dtype=dtype, device=device + ) + + decoder_hidden = torch.zeros( + n_batch, self.decoder_rnn_dim, dtype=dtype, device=device + ) + decoder_cell = torch.zeros( + n_batch, self.decoder_rnn_dim, dtype=dtype, device=device + ) + + attention_weights = torch.zeros(n_batch, max_time, dtype=dtype, device=device) + attention_weights_cum = torch.zeros( + n_batch, max_time, dtype=dtype, device=device + ) + attention_context = torch.zeros( + n_batch, self.encoder_embedding_dim, dtype=dtype, device=device + ) + + processed_memory = self.attention_layer.memory_layer(memory) + + return ( + attention_hidden, + attention_cell, + decoder_hidden, + decoder_cell, + attention_weights, + attention_weights_cum, + attention_context, + processed_memory, + ) + + def _parse_decoder_inputs(self, decoder_inputs: Tensor) -> Tensor: + r"""Prepares decoder inputs. + + Args: + decoder_inputs (Tensor): Inputs used for teacher-forced training, i.e. mel-specs, + with shape (n_batch, ``n_mels``, max of ``mel_specgram_lengths``) + + Returns: + inputs (Tensor): Processed decoder inputs with shape (max of ``mel_specgram_lengths``, n_batch, ``n_mels``). + """ + # (n_batch, n_mels, mel_specgram_lengths.max()) -> (n_batch, mel_specgram_lengths.max(), n_mels) + decoder_inputs = decoder_inputs.transpose(1, 2) + decoder_inputs = decoder_inputs.view( + decoder_inputs.size(0), + int(decoder_inputs.size(1) / self.n_frames_per_step), + -1, + ) + # (n_batch, mel_specgram_lengths.max(), n_mels) -> (mel_specgram_lengths.max(), n_batch, n_mels) + decoder_inputs = decoder_inputs.transpose(0, 1) + return decoder_inputs + + def _parse_decoder_outputs( + self, mel_specgram: Tensor, gate_outputs: Tensor, alignments: Tensor + ) -> Tuple[Tensor, Tensor, Tensor]: + r"""Prepares decoder outputs for output + + Args: + mel_specgram (Tensor): mel spectrogram with shape (max of ``mel_specgram_lengths``, n_batch, ``n_mels``) + gate_outputs (Tensor): predicted stop token with shape (max of ``mel_specgram_lengths``, n_batch) + alignments (Tensor): sequence of attention weights from the decoder + with shape (max of ``mel_specgram_lengths``, n_batch, max of ``text_lengths``) + + Returns: + mel_specgram (Tensor): mel spectrogram with shape (n_batch, ``n_mels``, max of ``mel_specgram_lengths``) + gate_outputs (Tensor): predicted stop token with shape (n_batch, max of ``mel_specgram_lengths``) + alignments (Tensor): sequence of attention weights from the decoder + with shape (n_batch, max of ``mel_specgram_lengths``, max of ``text_lengths``) + """ + # (mel_specgram_lengths.max(), n_batch, text_lengths.max()) + # -> (n_batch, mel_specgram_lengths.max(), text_lengths.max()) + alignments = alignments.transpose(0, 1).contiguous() + # (mel_specgram_lengths.max(), n_batch) -> (n_batch, mel_specgram_lengths.max()) + gate_outputs = gate_outputs.transpose(0, 1).contiguous() + # (mel_specgram_lengths.max(), n_batch, n_mels) -> (n_batch, mel_specgram_lengths.max(), n_mels) + mel_specgram = mel_specgram.transpose(0, 1).contiguous() + # decouple frames per step + shape = (mel_specgram.shape[0], -1, self.n_mels) + mel_specgram = mel_specgram.view(*shape) + # (n_batch, mel_specgram_lengths.max(), n_mels) -> (n_batch, n_mels, T_out) + mel_specgram = mel_specgram.transpose(1, 2) + + return mel_specgram, gate_outputs, alignments + + def decode( + self, + decoder_input: Tensor, + attention_hidden: Tensor, + attention_cell: Tensor, + decoder_hidden: Tensor, + decoder_cell: Tensor, + attention_weights: Tensor, + attention_weights_cum: Tensor, + attention_context: Tensor, + memory: Tensor, + processed_memory: Tensor, + mask: Tensor, + ) -> Tuple[Tensor, Tensor, Tensor, Tensor, Tensor, Tensor, Tensor, Tensor, Tensor]: + r"""Decoder step using stored states, attention and memory + + Args: + decoder_input (Tensor): Output of the Prenet with shape (n_batch, ``prenet_dim``). + attention_hidden (Tensor): Hidden state of the attention LSTM with shape (n_batch, ``attention_rnn_dim``). + attention_cell (Tensor): Hidden state of the attention LSTM with shape (n_batch, ``attention_rnn_dim``). + decoder_hidden (Tensor): Hidden state of the decoder LSTM with shape (n_batch, ``decoder_rnn_dim``). + decoder_cell (Tensor): Hidden state of the decoder LSTM with shape (n_batch, ``decoder_rnn_dim``). + attention_weights (Tensor): Attention weights with shape (n_batch, max of ``text_lengths``). + attention_weights_cum (Tensor): Cumulated attention weights with shape (n_batch, max of ``text_lengths``). + attention_context (Tensor): Context vector with shape (n_batch, ``encoder_embedding_dim``). + memory (Tensor): Encoder output with shape (n_batch, max of ``text_lengths``, ``encoder_embedding_dim``). + processed_memory (Tensor): Processed Encoder outputs + with shape (n_batch, max of ``text_lengths``, ``attention_hidden_dim``). + mask (Tensor): Binary mask for padded data with shape (n_batch, current_num_frames). + + Returns: + decoder_output: Predicted mel spectrogram for the current frame with shape (n_batch, ``n_mels``). + gate_prediction (Tensor): Prediction of the stop token with shape (n_batch, ``1``). + attention_hidden (Tensor): Hidden state of the attention LSTM with shape (n_batch, ``attention_rnn_dim``). + attention_cell (Tensor): Hidden state of the attention LSTM with shape (n_batch, ``attention_rnn_dim``). + decoder_hidden (Tensor): Hidden state of the decoder LSTM with shape (n_batch, ``decoder_rnn_dim``). + decoder_cell (Tensor): Hidden state of the decoder LSTM with shape (n_batch, ``decoder_rnn_dim``). + attention_weights (Tensor): Attention weights with shape (n_batch, max of ``text_lengths``). + attention_weights_cum (Tensor): Cumulated attention weights with shape (n_batch, max of ``text_lengths``). + attention_context (Tensor): Context vector with shape (n_batch, ``encoder_embedding_dim``). + """ + cell_input = torch.cat((decoder_input, attention_context), -1) + + attention_hidden, attention_cell = self.attention_rnn( + cell_input, (attention_hidden, attention_cell) + ) + attention_hidden = F.dropout( + attention_hidden, self.attention_dropout, self.training + ) + + attention_weights_cat = torch.cat( + (attention_weights.unsqueeze(1), attention_weights_cum.unsqueeze(1)), dim=1 + ) + attention_context, attention_weights = self.attention_layer( + attention_hidden, memory, processed_memory, attention_weights_cat, mask + ) + + attention_weights_cum += attention_weights + decoder_input = torch.cat((attention_hidden, attention_context), -1) + + decoder_hidden, decoder_cell = self.decoder_rnn( + decoder_input, (decoder_hidden, decoder_cell) + ) + decoder_hidden = F.dropout(decoder_hidden, self.decoder_dropout, self.training) + + decoder_hidden_attention_context = torch.cat( + (decoder_hidden, attention_context), dim=1 + ) + decoder_output = self.linear_projection(decoder_hidden_attention_context) + + gate_prediction = self.gate_layer(decoder_hidden_attention_context) + + return ( + decoder_output, + gate_prediction, + attention_hidden, + attention_cell, + decoder_hidden, + decoder_cell, + attention_weights, + attention_weights_cum, + attention_context, + ) + + def forward( + self, memory: Tensor, mel_specgram_truth: Tensor, memory_lengths: Tensor + ) -> Tuple[Tensor, Tensor, Tensor]: + r"""Decoder forward pass for training. + + Args: + memory (Tensor): Encoder outputs + with shape (n_batch, max of ``text_lengths``, ``encoder_embedding_dim``). + mel_specgram_truth (Tensor): Decoder ground-truth mel-specs for teacher forcing + with shape (n_batch, ``n_mels``, max of ``mel_specgram_lengths``). + memory_lengths (Tensor): Encoder output lengths for attention masking + (the same as ``text_lengths``) with shape (n_batch, ). + + Returns: + mel_specgram (Tensor): Predicted mel spectrogram + with shape (n_batch, ``n_mels``, max of ``mel_specgram_lengths``). + gate_outputs (Tensor): Predicted stop token for each timestep + with shape (n_batch, max of ``mel_specgram_lengths``). + alignments (Tensor): Sequence of attention weights from the decoder + with shape (n_batch, max of ``mel_specgram_lengths``, max of ``text_lengths``). + """ + + decoder_input = self._get_initial_frame(memory).unsqueeze(0) + decoder_inputs = self._parse_decoder_inputs(mel_specgram_truth) + decoder_inputs = torch.cat((decoder_input, decoder_inputs), dim=0) + decoder_inputs = self.prenet(decoder_inputs) + + mask = _get_mask_from_lengths(memory_lengths) + ( + attention_hidden, + attention_cell, + decoder_hidden, + decoder_cell, + attention_weights, + attention_weights_cum, + attention_context, + processed_memory, + ) = self._initialize_decoder_states(memory) + + mel_outputs, gate_outputs, alignments = [], [], [] + while len(mel_outputs) < decoder_inputs.size(0) - 1: + decoder_input = decoder_inputs[len(mel_outputs)] + ( + mel_output, + gate_output, + attention_hidden, + attention_cell, + decoder_hidden, + decoder_cell, + attention_weights, + attention_weights_cum, + attention_context, + ) = self.decode( + decoder_input, + attention_hidden, + attention_cell, + decoder_hidden, + decoder_cell, + attention_weights, + attention_weights_cum, + attention_context, + memory, + processed_memory, + mask, + ) + + mel_outputs += [mel_output.squeeze(1)] + gate_outputs += [gate_output.squeeze()] + alignments += [attention_weights] + + mel_specgram, gate_outputs, alignments = self._parse_decoder_outputs( + torch.stack(mel_outputs), torch.stack(gate_outputs), torch.stack(alignments) + ) + + return mel_specgram, gate_outputs, alignments + + def _get_go_frame(self, memory: Tensor) -> Tensor: + """Gets all zeros frames to use as the first decoder input + + args: + memory (Tensor): Encoder outputs + with shape (n_batch, max of ``text_lengths``, ``encoder_embedding_dim``). + + returns: + decoder_input (Tensor): All zeros frames with shape(n_batch, ``n_mels`` * ``n_frame_per_step``). + """ + + n_batch = memory.size(0) + dtype = memory.dtype + device = memory.device + decoder_input = torch.zeros( + n_batch, self.n_mels * self.n_frames_per_step, dtype=dtype, device=device + ) + return decoder_input + + @torch.jit.export + def infer(self, + memory: Tensor, + memory_lengths: Tensor) -> Tuple[Tensor, Tensor, Tensor, Tensor]: + """Decoder inference + + Args: + memory (Tensor): Encoder outputs + with shape (n_batch, max of ``text_lengths``, ``encoder_embedding_dim``). + memory_lengths (Tensor): Encoder output lengths for attention masking + (the same as ``text_lengths``) with shape (n_batch, ). + + Returns: + mel_specgram (Tensor): Predicted mel spectrogram + with shape (n_batch, ``n_mels``, max of ``mel_specgram_lengths``). + mel_specgram_lengths (Tensor): the length of the predicted mel spectrogram (n_batch, )) + gate_outputs (Tensor): Predicted stop token for each timestep + with shape (n_batch, max of ``mel_specgram_lengths``). + alignments (Tensor): Sequence of attention weights from the decoder + with shape (n_batch, max of ``mel_specgram_lengths``, max of ``text_lengths``). + """ + batch_size, device = memory.size(0), memory.device + + decoder_input = self._get_go_frame(memory) + + mask = _get_mask_from_lengths(memory_lengths) + ( + attention_hidden, + attention_cell, + decoder_hidden, + decoder_cell, + attention_weights, + attention_weights_cum, + attention_context, + processed_memory, + ) = self._initialize_decoder_states(memory) + + mel_specgram_lengths = torch.zeros([batch_size], dtype=torch.int32, device=device) + finished = torch.zeros([batch_size], dtype=torch.bool, device=device) + mel_specgrams: List[Tensor] = [] + gate_outputs: List[Tensor] = [] + alignments: List[Tensor] = [] + for _ in range(self.decoder_max_step): + decoder_input = self.prenet(decoder_input) + ( + mel_specgram, + gate_output, + attention_hidden, + attention_cell, + decoder_hidden, + decoder_cell, + attention_weights, + attention_weights_cum, + attention_context, + ) = self.decode( + decoder_input, + attention_hidden, + attention_cell, + decoder_hidden, + decoder_cell, + attention_weights, + attention_weights_cum, + attention_context, + memory, + processed_memory, + mask, + ) + + mel_specgrams.append(mel_specgram.unsqueeze(0)) + gate_outputs.append(gate_output.transpose(0, 1)) + alignments.append(attention_weights) + mel_specgram_lengths[~finished] += 1 + + finished |= torch.sigmoid(gate_output.squeeze(1)) > self.gate_threshold + if self.decoder_early_stopping and torch.all(finished): + break + + decoder_input = mel_specgram + + if len(mel_specgrams) == self.decoder_max_step: + warnings.warn( + "Reached max decoder steps. The generated spectrogram might not cover " + "the whole transcript.") + + mel_specgrams = torch.cat(mel_specgrams, dim=0) + gate_outputs = torch.cat(gate_outputs, dim=0) + alignments = torch.cat(alignments, dim=0) + + mel_specgrams, gate_outputs, alignments = self._parse_decoder_outputs( + mel_specgrams, gate_outputs, alignments + ) + + return mel_specgrams, mel_specgram_lengths, gate_outputs, alignments + + +class Tacotron2(nn.Module): + r"""Tacotron2 model based on the implementation from + `Nvidia `_. + + The original implementation was introduced in + *Natural TTS Synthesis by Conditioning WaveNet on Mel Spectrogram Predictions* + [:footcite:`shen2018natural`]. + + Args: + mask_padding (bool, optional): Use mask padding (Default: ``False``). + n_mels (int, optional): Number of mel bins (Default: ``80``). + n_symbol (int, optional): Number of symbols for the input text (Default: ``148``). + n_frames_per_step (int, optional): Number of frames processed per step, only 1 is supported (Default: ``1``). + symbol_embedding_dim (int, optional): Input embedding dimension (Default: ``512``). + encoder_n_convolution (int, optional): Number of encoder convolutions (Default: ``3``). + encoder_kernel_size (int, optional): Encoder kernel size (Default: ``5``). + encoder_embedding_dim (int, optional): Encoder embedding dimension (Default: ``512``). + decoder_rnn_dim (int, optional): Number of units in decoder LSTM (Default: ``1024``). + decoder_max_step (int, optional): Maximum number of output mel spectrograms (Default: ``2000``). + decoder_dropout (float, optional): Dropout probability for decoder LSTM (Default: ``0.1``). + decoder_early_stopping (bool, optional): Continue decoding after all samples are finished (Default: ``True``). + attention_rnn_dim (int, optional): Number of units in attention LSTM (Default: ``1024``). + attention_hidden_dim (int, optional): Dimension of attention hidden representation (Default: ``128``). + attention_location_n_filter (int, optional): Number of filters for attention model (Default: ``32``). + attention_location_kernel_size (int, optional): Kernel size for attention model (Default: ``31``). + attention_dropout (float, optional): Dropout probability for attention LSTM (Default: ``0.1``). + prenet_dim (int, optional): Number of ReLU units in prenet layers (Default: ``256``). + postnet_n_convolution (int, optional): Number of postnet convolutions (Default: ``5``). + postnet_kernel_size (int, optional): Postnet kernel size (Default: ``5``). + postnet_embedding_dim (int, optional): Postnet embedding dimension (Default: ``512``). + gate_threshold (float, optional): Probability threshold for stop token (Default: ``0.5``). + """ + + def __init__( + self, + mask_padding: bool = False, + n_mels: int = 80, + n_symbol: int = 148, + n_frames_per_step: int = 1, + symbol_embedding_dim: int = 512, + encoder_embedding_dim: int = 512, + encoder_n_convolution: int = 3, + encoder_kernel_size: int = 5, + decoder_rnn_dim: int = 1024, + decoder_max_step: int = 2000, + decoder_dropout: float = 0.1, + decoder_early_stopping: bool = True, + attention_rnn_dim: int = 1024, + attention_hidden_dim: int = 128, + attention_location_n_filter: int = 32, + attention_location_kernel_size: int = 31, + attention_dropout: float = 0.1, + prenet_dim: int = 256, + postnet_n_convolution: int = 5, + postnet_kernel_size: int = 5, + postnet_embedding_dim: int = 512, + gate_threshold: float = 0.5, + ) -> None: + super().__init__() + + self.mask_padding = mask_padding + self.n_mels = n_mels + self.n_frames_per_step = n_frames_per_step + self.embedding = nn.Embedding(n_symbol, symbol_embedding_dim) + std = sqrt(2.0 / (n_symbol + symbol_embedding_dim)) + val = sqrt(3.0) * std + self.embedding.weight.data.uniform_(-val, val) + self.encoder = _Encoder( + encoder_embedding_dim, encoder_n_convolution, encoder_kernel_size + ) + self.decoder = _Decoder( + n_mels, + n_frames_per_step, + encoder_embedding_dim, + decoder_rnn_dim, + decoder_max_step, + decoder_dropout, + decoder_early_stopping, + attention_rnn_dim, + attention_hidden_dim, + attention_location_n_filter, + attention_location_kernel_size, + attention_dropout, + prenet_dim, + gate_threshold, + ) + self.postnet = _Postnet( + n_mels, postnet_embedding_dim, postnet_kernel_size, postnet_n_convolution + ) + + def forward( + self, + tokens: Tensor, + token_lengths: Tensor, + mel_specgram: Tensor, + mel_specgram_lengths: Tensor, + ) -> Tuple[Tensor, Tensor, Tensor, Tensor]: + r"""Pass the input through the Tacotron2 model. This is in teacher + forcing mode, which is generally used for training. + + The input ``tokens`` should be padded with zeros to length max of ``token_lengths``. + The input ``mel_specgram`` should be padded with zeros to length max of ``mel_specgram_lengths``. + + Args: + tokens (Tensor): The input tokens to Tacotron2 with shape `(n_batch, max of token_lengths)`. + token_lengths (Tensor): The valid length of each sample in ``tokens`` with shape `(n_batch, )`. + mel_specgram (Tensor): The target mel spectrogram + with shape `(n_batch, n_mels, max of mel_specgram_lengths)`. + mel_specgram_lengths (Tensor): The length of each mel spectrogram with shape `(n_batch, )`. + + Returns: + [Tensor, Tensor, Tensor, Tensor]: + Tensor + Mel spectrogram before Postnet with shape `(n_batch, n_mels, max of mel_specgram_lengths)`. + Tensor + Mel spectrogram after Postnet with shape `(n_batch, n_mels, max of mel_specgram_lengths)`. + Tensor + The output for stop token at each time step with shape `(n_batch, max of mel_specgram_lengths)`. + Tensor + Sequence of attention weights from the decoder with + shape `(n_batch, max of mel_specgram_lengths, max of token_lengths)`. + """ + + embedded_inputs = self.embedding(tokens).transpose(1, 2) + + encoder_outputs = self.encoder(embedded_inputs, token_lengths) + mel_specgram, gate_outputs, alignments = self.decoder( + encoder_outputs, mel_specgram, memory_lengths=token_lengths + ) + + mel_specgram_postnet = self.postnet(mel_specgram) + mel_specgram_postnet = mel_specgram + mel_specgram_postnet + + if self.mask_padding: + mask = _get_mask_from_lengths(mel_specgram_lengths) + mask = mask.expand(self.n_mels, mask.size(0), mask.size(1)) + mask = mask.permute(1, 0, 2) + + mel_specgram.masked_fill_(mask, 0.0) + mel_specgram_postnet.masked_fill_(mask, 0.0) + gate_outputs.masked_fill_(mask[:, 0, :], 1e3) + + return mel_specgram, mel_specgram_postnet, gate_outputs, alignments + + @torch.jit.export + def infer(self, tokens: Tensor, lengths: Optional[Tensor] = None) -> Tuple[Tensor, Tensor, Tensor]: + r"""Using Tacotron2 for inference. The input is a batch of encoded + sentences (``tokens``) and its corresponding lengths (``lengths``). The + output is the generated mel spectrograms, its corresponding lengths, and + the attention weights from the decoder. + + The input `tokens` should be padded with zeros to length max of ``lengths``. + + Args: + tokens (Tensor): The input tokens to Tacotron2 with shape `(n_batch, max of lengths)`. + lengths (Tensor or None, optional): + The valid length of each sample in ``tokens`` with shape `(n_batch, )`. + If ``None``, it is assumed that the all the tokens are valid. Default: ``None`` + + Returns: + (Tensor, Tensor, Tensor): + Tensor + The predicted mel spectrogram with shape `(n_batch, n_mels, max of mel_specgram_lengths)`. + Tensor + The length of the predicted mel spectrogram with shape `(n_batch, )`. + Tensor + Sequence of attention weights from the decoder with shape + `(n_batch, max of mel_specgram_lengths, max of lengths)`. + """ + n_batch, max_length = tokens.shape + if lengths is None: + lengths = torch.tensor([max_length]).expand(n_batch).to(tokens.device, tokens.dtype) + + assert lengths is not None # For TorchScript compiler + + embedded_inputs = self.embedding(tokens).transpose(1, 2) + encoder_outputs = self.encoder(embedded_inputs, lengths) + mel_specgram, mel_specgram_lengths, _, alignments = self.decoder.infer( + encoder_outputs, lengths + ) + + mel_outputs_postnet = self.postnet(mel_specgram) + mel_outputs_postnet = mel_specgram + mel_outputs_postnet + + alignments = alignments.unfold(1, n_batch, n_batch).transpose(0, 2) + + return mel_outputs_postnet, mel_specgram_lengths, alignments diff --git a/torchaudio/models/wav2letter.py b/torchaudio/models/wav2letter.py new file mode 100644 index 00000000..4d93e743 --- /dev/null +++ b/torchaudio/models/wav2letter.py @@ -0,0 +1,74 @@ +from torch import Tensor +from torch import nn + +__all__ = [ + "Wav2Letter", +] + + +class Wav2Letter(nn.Module): + r"""Wav2Letter model architecture from *Wav2Letter: an End-to-End ConvNet-based Speech + Recognition System* [:footcite:`collobert2016wav2letter`]. + + :math:`\text{padding} = \frac{\text{ceil}(\text{kernel} - \text{stride})}{2}` + + Args: + num_classes (int, optional): Number of classes to be classified. (Default: ``40``) + input_type (str, optional): Wav2Letter can use as input: ``waveform``, ``power_spectrum`` + or ``mfcc`` (Default: ``waveform``). + num_features (int, optional): Number of input features that the network will receive (Default: ``1``). + """ + + def __init__(self, num_classes: int = 40, + input_type: str = "waveform", + num_features: int = 1) -> None: + super(Wav2Letter, self).__init__() + + acoustic_num_features = 250 if input_type == "waveform" else num_features + acoustic_model = nn.Sequential( + nn.Conv1d(in_channels=acoustic_num_features, out_channels=250, kernel_size=48, stride=2, padding=23), + nn.ReLU(inplace=True), + nn.Conv1d(in_channels=250, out_channels=250, kernel_size=7, stride=1, padding=3), + nn.ReLU(inplace=True), + nn.Conv1d(in_channels=250, out_channels=250, kernel_size=7, stride=1, padding=3), + nn.ReLU(inplace=True), + nn.Conv1d(in_channels=250, out_channels=250, kernel_size=7, stride=1, padding=3), + nn.ReLU(inplace=True), + nn.Conv1d(in_channels=250, out_channels=250, kernel_size=7, stride=1, padding=3), + nn.ReLU(inplace=True), + nn.Conv1d(in_channels=250, out_channels=250, kernel_size=7, stride=1, padding=3), + nn.ReLU(inplace=True), + nn.Conv1d(in_channels=250, out_channels=250, kernel_size=7, stride=1, padding=3), + nn.ReLU(inplace=True), + nn.Conv1d(in_channels=250, out_channels=250, kernel_size=7, stride=1, padding=3), + nn.ReLU(inplace=True), + nn.Conv1d(in_channels=250, out_channels=2000, kernel_size=32, stride=1, padding=16), + nn.ReLU(inplace=True), + nn.Conv1d(in_channels=2000, out_channels=2000, kernel_size=1, stride=1, padding=0), + nn.ReLU(inplace=True), + nn.Conv1d(in_channels=2000, out_channels=num_classes, kernel_size=1, stride=1, padding=0), + nn.ReLU(inplace=True) + ) + + if input_type == "waveform": + waveform_model = nn.Sequential( + nn.Conv1d(in_channels=num_features, out_channels=250, kernel_size=250, stride=160, padding=45), + nn.ReLU(inplace=True) + ) + self.acoustic_model = nn.Sequential(waveform_model, acoustic_model) + + if input_type in ["power_spectrum", "mfcc"]: + self.acoustic_model = acoustic_model + + def forward(self, x: Tensor) -> Tensor: + r""" + Args: + x (torch.Tensor): Tensor of dimension (batch_size, num_features, input_length). + + Returns: + Tensor: Predictor tensor of dimension (batch_size, number_of_classes, input_length). + """ + + x = self.acoustic_model(x) + x = nn.functional.log_softmax(x, dim=1) + return x diff --git a/torchaudio/models/wav2vec2/__init__.py b/torchaudio/models/wav2vec2/__init__.py new file mode 100644 index 00000000..f0538a4a --- /dev/null +++ b/torchaudio/models/wav2vec2/__init__.py @@ -0,0 +1,23 @@ +from .model import ( + Wav2Vec2Model, + wav2vec2_model, + wav2vec2_base, + wav2vec2_large, + wav2vec2_large_lv60k, + hubert_base, + hubert_large, + hubert_xlarge, +) +from . import utils + +__all__ = [ + 'Wav2Vec2Model', + 'wav2vec2_model', + 'wav2vec2_base', + 'wav2vec2_large', + 'wav2vec2_large_lv60k', + 'hubert_base', + 'hubert_large', + 'hubert_xlarge', + 'utils', +] diff --git a/torchaudio/models/wav2vec2/components.py b/torchaudio/models/wav2vec2/components.py new file mode 100644 index 00000000..7093fc9d --- /dev/null +++ b/torchaudio/models/wav2vec2/components.py @@ -0,0 +1,717 @@ +import logging +from typing import Optional, Tuple, List + +import torch +from torch import Tensor, nn +from torch.nn import Module + +_LG = logging.getLogger(__name__) + + +class LayerNorm(nn.LayerNorm): + """Layer norm with transpose""" + def forward(self, input: Tensor) -> Tensor: + x = input.transpose(-2, -1) + x = nn.functional.layer_norm( + x, self.normalized_shape, self.weight, self.bias, self.eps) + x = x.transpose(-2, -1) + return x + + +class ConvLayerBlock(Module): + """Convolution unit of FeatureExtractor""" + def __init__( + self, + in_channels: int, + out_channels: int, + kernel_size: int, + stride: int, + bias: bool, + layer_norm: Optional[Module], + ): + super().__init__() + self.kernel_size = kernel_size + self.stride = stride + self.layer_norm = layer_norm + self.conv = nn.Conv1d( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, + stride=stride, + bias=bias, + ) + + def forward( + self, + x: Tensor, + length: Optional[Tensor], + ) -> Tuple[Tensor, Optional[Tensor]]: + """ + Args: + x (Tensor): Shape: ``[batch, in_channels, in_frame]``. + length (Tensor or None, optional): Shape ``[batch, ]``. + Returns: + Tensor: Shape ``[batch, out_channels, out_frames]``. + Optional[Tensor]: Shape ``[batch, ]``. + """ + x = self.conv(x) + if self.layer_norm is not None: + x = self.layer_norm(x) + x = nn.functional.gelu(x) + + if length is not None: + length = torch.div(length - self.kernel_size, self.stride, rounding_mode='floor') + 1 + # When input length is 0, the resulting length can be negative. So fix it here. + length = torch.max(torch.zeros_like(length), length) + return x, length + + +class FeatureExtractor(Module): + """Extract features from audio + + Args: + conv_layers (nn.ModuleList): + convolution layers + """ + def __init__( + self, + conv_layers: nn.ModuleList, + ): + super().__init__() + self.conv_layers = conv_layers + + def forward( + self, + x: Tensor, + length: Optional[Tensor], + ) -> Tuple[Tensor, Optional[Tensor]]: + """ + Args: + x (Tensor): + Input Tensor representing a batch of audio, + shape: ``[batch, time]``. + length (Tensor or None, optional): + Valid length of each input sample. shape: ``[batch, ]``. + + Returns: + Tensor: + The resulting feature, shape: ``[batch, frame, feature]`` + Optional[Tensor]: + Valid length of each output sample. shape: ``[batch, ]``. + """ + if x.ndim != 2: + raise ValueError( + "Expected the input Tensor to be 2D (batch, time), " + "but received {list(x.shape)}") + + x = x.unsqueeze(1) # (batch, channel==1, frame) + for layer in self.conv_layers: + x, length = layer(x, length) # (batch, feature, frame) + x = x.transpose(1, 2) # (batch, frame, feature) + return x, length + + +class FeatureProjection(Module): + """Layer that connects FeatureExtractor and Encoder + + Projects features to encoder dimension. + + Args: + in_features (int): Input feature dim. + out_features (int): Output feature dim. + dropout (float): Dropout probability. + """ + def __init__( + self, + in_features: int, + out_features: int, + dropout: float, + ): + super().__init__() + self.layer_norm = nn.LayerNorm(in_features) + self.projection = nn.Linear(in_features, out_features,) + self.dropout = nn.Dropout(dropout) + + def forward(self, x): + """ + Args: + x (Tensor): + Feature Tensor. shape: ``[batch, frame, in_feature]`` + Returns: + Tensor: Projected features. ``[batch, frame, out_feature]``. + """ + x = self.layer_norm(x) + x = self.projection(x) + x = self.dropout(x) + return x + + +class ConvolutionalPositionalEmbedding(Module): + """Positional embedding which is placed at the beginning of Transformer. + + Args: + embed_dim (int): Feature dimension of the input Tensor. + kernel_size (int): The number of frames to be use. + groups (int): The number of groups in feature dimensions. + """ + def __init__( + self, + embed_dim: int, + kernel_size: int, + groups: int, + ): + super().__init__() + self.embed_dim = embed_dim + self.conv = nn.Conv1d( + in_channels=embed_dim, + out_channels=embed_dim, + kernel_size=kernel_size, + padding=kernel_size // 2, + groups=groups, + ) + self.conv = nn.utils.weight_norm(self.conv, name="weight", dim=2) + self.num_remove: int = 1 if kernel_size % 2 == 0 else 0 + + def __prepare_scriptable__(self): + for hook in self.conv._forward_pre_hooks.values(): + # The hook we want to remove is an instance of WeightNorm class, so + # normally we would do `if isinstance(...)` but this class is not accessible + # because of shadowing, so we check the module name directly. + # https://github.com/pytorch/pytorch/blob/be0ca00c5ce260eb5bcec3237357f7a30cc08983/torch/nn/utils/__init__.py#L3 + if ( + hook.__module__ == 'torch.nn.utils.weight_norm' and + hook.__class__.__name__ == 'WeightNorm' + ): + _LG.warning('Removing weight_norm from %s', self.__class__.__name__) + torch.nn.utils.remove_weight_norm(self.conv) + return self + + def forward(self, x): + """ + Args: + x (Tensor): shape ``[batch, frame, feature]``. + + Returns: + Tensor: The resulting feature. Shape ``[batch, frame, feature]``. + """ + x = x.transpose(-2, -1) + x = self.conv(x) + if self.num_remove > 0: + x = x[..., :-self.num_remove] + x = torch.nn.functional.gelu(x) + x = x.transpose(-2, -1) + return x + + +class SelfAttention(Module): + """Multihead Self Attention module + + Args: + embed_dim (int): Total dimension of the model. + num_heads (int): The number of heads. + dropout (float, optional): + Dropout probabiliry on attn_output_weights. Default: ``0.0`` + """ + def __init__( + self, + embed_dim: int, + num_heads: int, + dropout: float = 0.0, + ): + super().__init__() + head_dim = embed_dim // num_heads + if head_dim * num_heads != embed_dim: + raise ValueError(f"`embed_dim ({embed_dim})` is not divisible by `num_heads ({num_heads})`") + + self.embed_dim = embed_dim + self.num_heads = num_heads + self.dropout = dropout + self.head_dim = head_dim + + self.scaling = self.head_dim ** -0.5 + + self.k_proj = nn.Linear(embed_dim, embed_dim, bias=True) + self.v_proj = nn.Linear(embed_dim, embed_dim, bias=True) + self.q_proj = nn.Linear(embed_dim, embed_dim, bias=True) + self.out_proj = nn.Linear(embed_dim, embed_dim, bias=True) + + def forward( + self, + x: Tensor, + attention_mask: Optional[Tensor] = None, + ) -> Tensor: + """ + Args: + x (Tensor): shape: ``[batch_size, sequence_length, embed_dim]``. + attention_mask (Tensor or None, optional): + shape: ``[batch_size, 1, sequence_length, sequence_length]`` + + Returns: + Tensor: The resulting tensor. shape: ``[batch, sequence_length, embed_dim]`` + """ + if x.ndim != 3 or x.shape[2] != self.embed_dim: + raise ValueError( + f"The expected input shape is (batch, sequence, embed_dim=={self.embed_dim}). " + f"Found {x.shape}." + ) + batch_size, length, embed_dim = x.size() + if attention_mask is not None: + shape_ = (batch_size, 1, length, length) + if attention_mask.size() != shape_: + raise ValueError( + f"The expected attention mask shape is {shape_}. " + f"Found {attention_mask.size()}." + ) + + shape = (batch_size, length, self.num_heads, self.head_dim) + q = self.q_proj(x).view(*shape).transpose(2, 1) # B, nH, L, Hd + k = self.k_proj(x).view(*shape).permute(0, 2, 3, 1) # B, nH, Hd, L + v = self.v_proj(x).view(*shape).transpose(2, 1) # B, nH, L, Hd + + weights = self.scaling * (q @ k) # B, nH, L, L + if attention_mask is not None: + weights += attention_mask + + weights = torch.nn.functional.softmax(weights, dim=-1) + weights = torch.nn.functional.dropout(weights, p=self.dropout, training=self.training) + + output = weights @ v # B, nH, L, Hd + output = output.transpose(2, 1).reshape(batch_size, length, embed_dim) + + output = self.out_proj(output) + return output + + +class FeedForward(Module): + """Layer that follows attention layer in encoder layer. + """ + def __init__( + self, + io_features: int, + intermediate_features: int, + intermediate_dropout: float, + output_dropout: float, + ): + super().__init__() + self.intermediate_dense = nn.Linear(io_features, intermediate_features) + self.intermediate_dropout = nn.Dropout(intermediate_dropout) + self.output_dense = nn.Linear(intermediate_features, io_features) + self.output_dropout = nn.Dropout(output_dropout) + + def forward(self, x): + """ + Args: + x (Tensor): shape: `(batch, sequence_length, io_features)` + Returns: + x (Tensor): shape: `(batch, sequence_length, io_features)` + """ + x = self.intermediate_dense(x) + x = torch.nn.functional.gelu(x) + x = self.intermediate_dropout(x) + + x = self.output_dense(x) + x = self.output_dropout(x) + return x + + +class EncoderLayer(Module): + """A layer unit in encoder. Combines multihead self attention and feed forward. + """ + def __init__( + self, + attention: Module, + dropout: float, + layer_norm_first: bool, + feed_forward: Module, + ): + super().__init__() + self.attention = attention + self.dropout = nn.Dropout(dropout) + self.layer_norm = nn.LayerNorm(attention.embed_dim) + self.layer_norm_first = layer_norm_first + self.feed_forward = feed_forward + self.final_layer_norm = nn.LayerNorm(attention.embed_dim) + + def forward( + self, + x: Tensor, + attention_mask: Optional[Tensor] = None, + ): + """ + Args: + x (Tensor): shape: `(batch, sequence_length, embed_dim)` + attention_mask (Tensor or None, optional): + shape: `(batch, 1, sequence_length, sequence_length)` + """ + residual = x + + if self.layer_norm_first: + x = self.layer_norm(x) + + x = self.attention(x, attention_mask) + x = self.dropout(x) + x = residual + x + + if self.layer_norm_first: + x = x + self.feed_forward(self.final_layer_norm(x)) + else: + x = self.layer_norm(x) + x = self.final_layer_norm(x + self.feed_forward(x)) + return x + + +class Transformer(Module): + def __init__( + self, + pos_conv_embed: Module, + dropout: float, + layers: Module, + layer_norm_first: bool, + layer_drop: float, + ): + super().__init__() + self.pos_conv_embed = pos_conv_embed + self.layer_norm = nn.LayerNorm(pos_conv_embed.embed_dim) + self.layer_norm_first = layer_norm_first + self.layer_drop = layer_drop + self.dropout = nn.Dropout(dropout) + self.layers = layers + + def _preprocess(self, x: Tensor): + x = x + self.pos_conv_embed(x) + + if self.layer_norm_first: + x = self.layer_norm(x) + + x = self.dropout(x) + return x + + def forward( + self, + x: Tensor, + attention_mask: Optional[Tensor] = None, + ): + x = self._preprocess(x) + for layer in self.layers: + if not (self.training and torch.rand(1).item() <= self.layer_drop): + x = layer(x, attention_mask) + + if not self.layer_norm_first: + x = self.layer_norm(x) + + return x + + def get_intermediate_outputs( + self, + x: Tensor, + attention_mask: Optional[Tensor] = None, + num_layers: Optional[int] = None, + ) -> List[Tensor]: + if num_layers is not None: + if not 0 < num_layers <= len(self.layers): + raise ValueError(f'`num_layers` must be between [1, {len(self.layers)}]') + + ret: List[Tensor] = [] + x = self._preprocess(x) + for layer in self.layers: + x = layer(x, attention_mask) + ret.append(x) + if num_layers is not None and len(ret) >= num_layers: + return ret + return ret + + +class Encoder(Module): + def __init__( + self, + feature_projection: Module, + transformer: Module, + ): + super().__init__() + self.feature_projection = feature_projection + self.transformer = transformer + + def _preprocess( + self, + features: Tensor, + lengths: Optional[Tensor] = None, + ) -> Tuple[Tensor, Optional[Tensor]]: + x = self.feature_projection(features) + + mask: Optional[Tensor] = None + if lengths is not None: + batch_size, max_len, _ = x.shape + # create mask for padded elements and zero-out them + mask = torch.arange(max_len, device=lengths.device).expand(batch_size, max_len) >= lengths[:, None] + x[mask] = 0.0 + # extend the mask to attention shape and set weight + mask = -10000.0 * mask[:, None, None, :].to(dtype=features.dtype) + mask = mask.expand(batch_size, 1, max_len, max_len) + return x, mask + + def forward( + self, + features: Tensor, + lengths: Optional[Tensor] = None, + ) -> Tensor: + x, mask = self._preprocess(features, lengths) + x = self.transformer(x, attention_mask=mask) + return x + + def extract_features( + self, + features: Tensor, + lengths: Optional[Tensor] = None, + num_layers: Optional[int] = None, + ) -> List[Tensor]: + x, masks = self._preprocess(features, lengths) + return self.transformer.get_intermediate_outputs( + x, attention_mask=masks, num_layers=num_layers) + + +################################################################################ +def _get_feature_extractor( + norm_mode: str, + shapes: List[Tuple[int, int, int]], + bias: bool, +) -> FeatureExtractor: + """ + Args: + norm_mode (str): + Either "group_norm" or "layer_norm". + If "group_norm", then a single normalization is applied + in the first convolution block. Otherwise, all the convolution + blocks will have layer normalization. + This option corresponds to "extractor_mode" from fairseq. + Expected values are "group_norm" for Base arch, and + "layer_norm" for Large arch. + shapes (list of tuple of int): + Configuration of convolution layers. List of convolution configuration, + i.e. ``[(output_channel, kernel_size, stride), ...]`` + This option corresponds to "conv_feature_layers" from fairseq. + Expected values are + ``[(512, 10, 5)] + [(512, 3, 2)] * 4 + [(512, 2, 2)] * 2`` + for all the architectures. + bias (bool): + Whether to include bias term to each convolution operation. + This option corresponds to "conv_bias" from fairseq. + Expected values are False for Base arch, and True for Large arch. + + See Also: + * Original implementation + https://github.com/pytorch/fairseq/blob/425c36eafff535fe7337f8bdd5ace22ebacc78cb/fairseq/models/wav2vec/wav2vec2.py#L666-L733 + * "extractor_mode" + - Def and base: + https://github.com/pytorch/fairseq/blob/425c36eafff535fe7337f8bdd5ace22ebacc78cb/fairseq/models/wav2vec/wav2vec2.py#L38-L45 + - Large: + https://github.com/pytorch/fairseq/blob/425c36eafff535fe7337f8bdd5ace22ebacc78cb/examples/wav2vec/config/pretraining/wav2vec2_large_librivox.yaml#L52 + * "conv_feature_layers" + - Def, base and large: + https://github.com/pytorch/fairseq/blob/425c36eafff535fe7337f8bdd5ace22ebacc78cb/fairseq/models/wav2vec/wav2vec2.py#L94-L100 + * "conv_bias" + - Def and base: + https://github.com/pytorch/fairseq/blob/425c36eafff535fe7337f8bdd5ace22ebacc78cb/fairseq/models/wav2vec/wav2vec2.py#L101-L103 + - Large: + https://github.com/pytorch/fairseq/blob/425c36eafff535fe7337f8bdd5ace22ebacc78cb/examples/wav2vec/config/pretraining/wav2vec2_large_librivox.yaml#L61 + """ + assert norm_mode in ["group_norm", "layer_norm"] + blocks = [] + in_channels = 1 + for i, (out_channels, kernel_size, stride) in enumerate(shapes): + normalization = None + if norm_mode == "group_norm" and i == 0: + normalization = nn.GroupNorm( + num_groups=out_channels, + num_channels=out_channels, + affine=True, + ) + elif norm_mode == "layer_norm": + normalization = LayerNorm( + normalized_shape=out_channels, + elementwise_affine=True, + ) + blocks.append( + ConvLayerBlock( + in_channels=in_channels, + out_channels=out_channels, + kernel_size=kernel_size, + stride=stride, + bias=bias, + layer_norm=normalization, + ) + ) + in_channels = out_channels + return FeatureExtractor(nn.ModuleList(blocks)) + + +def _get_encoder( + in_features: int, + embed_dim: int, + dropout_input: float, + pos_conv_kernel: int, + pos_conv_groups: int, + num_layers: int, + num_heads: int, + attention_dropout: float, + ff_interm_features: int, + ff_interm_dropout: float, + dropout: float, + layer_norm_first: bool, + layer_drop: float, +) -> Encoder: + """ + Args: + in_features (int): The number of input features. + embed_dim (int): + The dimension of embedding. + This option corresponds to "encoder_embed_dim" from fairseq. + Expected values are 768 for Base arch, and 1024 for Large arch. + dropout_input (float): + The dropout probability applied after the input feature is projected + to ``embed_dim``. + This option corresponds to "dropout_input" from fairseq. + Expected values are 0.1 for both Base and Large arch. + pos_conv_kernel (int): + The kernel size of convolutional positional embeddings. + This option corresponds to "conv_pos" from fairseq. + Expected values are 128 for both Base and Large arch. + pos_conv_groups (int): + The number of groups of convolutional positional embeddings. + This option corresponds to "conv_pos_groups" from fairseq. + Expected values are 16 for both Base and Large arch. + num_layers (int): + The number of self attention layers in transformer block. + This option corresponds to "encoder_layers" from fairseq. + Expected values are 12 for Base and 24 for Large arch. + num_heads (int): + The number of heads in self attention layers. + This option corresponds to "encoder_attention_heads" from fairseq. + Expected values are 12 for Base and 16 for Large arch. + attention_dropout (float): + The dropout probability applied after softmax in self-attention layer. + This option corresponds to "attention_dropout" from fairseq. + Expected values are 0.1 for Base and 0.0 for Large arch. + ff_interm_features (int): + The dimension of hidden features in feed forward layer. + This option corresponds to "encoder_ffn_embed_dim" from fairseq. + Expected values are 3072 for Base and 4096 for Large arch. + ff_interm_dropout (float): + The dropout probability applied in feedforward layer. + This option correspinds to "activation_dropout" from fairseq. + Expected values are 0.1 for both Base and Large arch. + dropout (float): + The dropout probability applied at the end of feed forward layer. + This option corresponds to "dropout" from fairseq. + Expected values are 0.1 for Base and 0.0 for Large arch. + layer_norm_first (bool): + Control the order of layer norm in transformer layer and each encoder layer. + If True, in transformer layer, layer norm is applied before features are fed + to encoder layers. In encoder layer, two layer norms are applied before and after + self attention. + If False, in transformer layer, layer norm is applied after features are fed + to encoder layers. In encoder layer, two layer norms are applied after self + attention, before and after feed forward. + This option corresponds to "layer_norm_first" from fairseq. + Expected values are False for Base and True for Large arch. + layer_drop (float): + Probability to drop each encoder layer during training. + This option corresponds to "layerdrop" from fairseq. + Expected values are 0.1 for both Base and Large arch. + + See Also: + * "encoder_embed_dim" + - Def and base + https://github.com/pytorch/fairseq/blob/425c36eafff535fe7337f8bdd5ace22ebacc78cb/fairseq/models/wav2vec/wav2vec2.py#L49-L51 + - Large + https://github.com/pytorch/fairseq/blob/425c36eafff535fe7337f8bdd5ace22ebacc78cb/examples/wav2vec/config/pretraining/wav2vec2_large_librivox.yaml#L64 + * "dropout_input" + - Def, base and large + https://github.com/pytorch/fairseq/blob/425c36eafff535fe7337f8bdd5ace22ebacc78cb/fairseq/models/wav2vec/wav2vec2.py#L75-L78 + * "conv_pos" + - Def, base and large + NOTE: The description is wrong. + https://github.com/pytorch/fairseq/blob/425c36eafff535fe7337f8bdd5ace22ebacc78cb/fairseq/models/wav2vec/wav2vec2.py#L204-L207 + - Usage + https://github.com/pytorch/fairseq/blob/425c36eafff535fe7337f8bdd5ace22ebacc78cb/fairseq/models/wav2vec/wav2vec2.py#L756 + * "conv_pos_groups" + - Def, base and large + https://github.com/pytorch/fairseq/blob/425c36eafff535fe7337f8bdd5ace22ebacc78cb/fairseq/models/wav2vec/wav2vec2.py#L208-L211 + * "encoder_layers" + - Def and base + https://github.com/pytorch/fairseq/blob/425c36eafff535fe7337f8bdd5ace22ebacc78cb/fairseq/models/wav2vec/wav2vec2.py#L46-L48 + - Large + https://github.com/pytorch/fairseq/blob/425c36eafff535fe7337f8bdd5ace22ebacc78cb/examples/wav2vec/config/pretraining/wav2vec2_large_librivox.yaml#L63 + * "encoder_attention_heads" + - Def and base + https://github.com/pytorch/fairseq/blob/425c36eafff535fe7337f8bdd5ace22ebacc78cb/fairseq/models/wav2vec/wav2vec2.py#L55-L57 + - Large + https://github.com/pytorch/fairseq/blob/425c36eafff535fe7337f8bdd5ace22ebacc78cb/examples/wav2vec/config/pretraining/wav2vec2_large_librivox.yaml#L66 + * "attention_dropout" + - Def and base + https://github.com/pytorch/fairseq/blob/425c36eafff535fe7337f8bdd5ace22ebacc78cb/fairseq/models/wav2vec/wav2vec2.py#L66-L68 + - Large + https://github.com/pytorch/fairseq/blob/425c36eafff535fe7337f8bdd5ace22ebacc78cb/examples/wav2vec/config/pretraining/wav2vec2_large_librivox.yaml#L60 + * "encoder_ffn_embed_dim" + - Def and base + https://github.com/pytorch/fairseq/blob/425c36eafff535fe7337f8bdd5ace22ebacc78cb/fairseq/models/wav2vec/wav2vec2.py#L52-L54 + - Large + https://github.com/pytorch/fairseq/blob/425c36eafff535fe7337f8bdd5ace22ebacc78cb/examples/wav2vec/config/pretraining/wav2vec2_large_librivox.yaml#L65 + * "activation_dropout" + - Def + https://github.com/pytorch/fairseq/blob/425c36eafff535fe7337f8bdd5ace22ebacc78cb/fairseq/models/wav2vec/wav2vec2.py#L69-L71 + - Base + https://github.com/pytorch/fairseq/blob/425c36eafff535fe7337f8bdd5ace22ebacc78cb/examples/wav2vec/config/finetuning/base_960h.yaml#L55 + - Large + https://github.com/pytorch/fairseq/blob/425c36eafff535fe7337f8bdd5ace22ebacc78cb/examples/wav2vec/config/finetuning/vox_960h.yaml#L55 + * "dropout" + - Def and base + https://github.com/pytorch/fairseq/blob/425c36eafff535fe7337f8bdd5ace22ebacc78cb/fairseq/models/wav2vec/wav2vec2.py#L63-L65 + - Large + https://github.com/pytorch/fairseq/blob/425c36eafff535fe7337f8bdd5ace22ebacc78cb/examples/wav2vec/config/pretraining/wav2vec2_large_librivox.yaml#L59 + * "layer_norm_first" + - Def and base + https://github.com/pytorch/fairseq/blob/425c36eafff535fe7337f8bdd5ace22ebacc78cb/fairseq/models/wav2vec/wav2vec2.py#L91-L93 + - Large + https://github.com/pytorch/fairseq/blob/425c36eafff535fe7337f8bdd5ace22ebacc78cb/examples/wav2vec/config/pretraining/wav2vec2_large_librivox.yaml#L53 + * "layerdrop" + - Def + https://github.com/pytorch/fairseq/blob/425c36eafff535fe7337f8bdd5ace22ebacc78cb/fairseq/models/wav2vec/wav2vec2.py#L72-L74 + - Base + https://github.com/pytorch/fairseq/blob/425c36eafff535fe7337f8bdd5ace22ebacc78cb/examples/wav2vec/config/finetuning/base_960h.yaml#L54 + - Large + https://github.com/pytorch/fairseq/blob/425c36eafff535fe7337f8bdd5ace22ebacc78cb/examples/wav2vec/config/finetuning/vox_960h.yaml#L54 + """ + feature_projection = FeatureProjection(in_features, embed_dim, dropout_input) + pos_conv = ConvolutionalPositionalEmbedding(embed_dim, pos_conv_kernel, pos_conv_groups) + + # Original impl + # https://github.com/pytorch/fairseq/blob/425c36eafff535fe7337f8bdd5ace22ebacc78cb/fairseq/models/wav2vec/wav2vec2.py#L768-L782 + encoder_layers = nn.ModuleList() + for _ in range(num_layers): + attention = SelfAttention( + embed_dim=embed_dim, + num_heads=num_heads, + dropout=attention_dropout, + ) + feed_forward = FeedForward( + io_features=embed_dim, + intermediate_features=ff_interm_features, + intermediate_dropout=ff_interm_dropout, + output_dropout=dropout, + ) + encoder_layers.append( + EncoderLayer( + attention=attention, + dropout=dropout, + layer_norm_first=layer_norm_first, + feed_forward=feed_forward, + ) + ) + transformer = Transformer( + pos_conv_embed=pos_conv, + dropout=dropout, + layers=encoder_layers, + layer_norm_first=not layer_norm_first, + layer_drop=layer_drop, + ) + return Encoder(feature_projection, transformer) diff --git a/torchaudio/models/wav2vec2/model.py b/torchaudio/models/wav2vec2/model.py new file mode 100644 index 00000000..a28742a2 --- /dev/null +++ b/torchaudio/models/wav2vec2/model.py @@ -0,0 +1,590 @@ +from typing import Optional, Tuple, List + +import torch +from torch import Tensor +from torch.nn import Module + +from . import components + + +class Wav2Vec2Model(Module): + """torchaudio.models.Wav2Vec2Model(feature_extractor: torch.nn.Module, encoder: torch.nn.Module, aux: Optional[torch.nn.Module] = None) + + Encoder model used in *wav2vec 2.0* [:footcite:`baevski2020wav2vec`]. + + Note: + To build the model, please use one of the factory functions. + + Args: + feature_extractor (torch.nn.Module): + Feature extractor that extracts feature vectors from raw audio Tensor. + + encoder (torch.nn.Module): + Encoder that converts the audio features into the sequence of probability + distribution (in negative log-likelihood) over labels. + + aux (torch.nn.Module or None, optional): + Auxiliary module. If provided, the output from encoder is passed to this module. + """ # noqa: E501 + def __init__( + self, + feature_extractor: Module, + encoder: Module, + aux: Optional[Module] = None, + ): + super().__init__() + self.feature_extractor = feature_extractor + self.encoder = encoder + self.aux = aux + + @torch.jit.export + def extract_features( + self, + waveforms: Tensor, + lengths: Optional[Tensor] = None, + num_layers: Optional[int] = None, + ) -> Tuple[List[Tensor], Optional[Tensor]]: + """Extract feature vectors from raw waveforms + + This returns the list of outputs from the intermediate layers of + transformer block in encoder. + + Args: + waveforms (Tensor): Audio tensor of shape `(batch, frames)`. + lengths (Tensor or None, optional): + Indicates the valid length of each audio in the batch. + Shape: `(batch, )`. + When the ``waveforms`` contains audios with different durations, + by providing ``lengths`` argument, the model will compute + the corresponding valid output lengths and apply proper mask in + transformer attention layer. + If ``None``, it is assumed that the entire audio waveform + length is valid. + num_layers (int or None, optional): + If given, limit the number of intermediate layers to go through. + Providing `1` will stop the computation after going through one + intermediate layers. If not given, the outputs from all the + intermediate layers are returned. + + Returns: + (List[Tensor], Optional[Tensor]): + List of Tensors + Features from requested layers. + Each Tensor is of shape: `(batch, time frame, feature dimension)` + Tensor or None + If ``lengths`` argument was provided, a Tensor of shape `(batch, )` + is returned. + It indicates the valid length in time axis of each feature Tensor. + """ + x, lengths = self.feature_extractor(waveforms, lengths) + x = self.encoder.extract_features(x, lengths, num_layers) + return x, lengths + + def forward( + self, + waveforms: Tensor, + lengths: Optional[Tensor] = None, + ) -> Tuple[Tensor, Optional[Tensor]]: + """Compute the sequence of probability distribution over labels. + + Args: + waveforms (Tensor): Audio tensor of shape `(batch, frames)`. + lengths (Tensor or None, optional): + Indicates the valid length of each audio in the batch. + Shape: `(batch, )`. + When the ``waveforms`` contains audios with different durations, + by providing ``lengths`` argument, the model will compute + the corresponding valid output lengths and apply proper mask in + transformer attention layer. + If ``None``, it is assumed that all the audio in ``waveforms`` + have valid length. Default: ``None``. + + Returns: + (Tensor, Optional[Tensor]): + Tensor + The sequences of probability distribution (in logit) over labels. + Shape: `(batch, frames, num labels)`. + Tensor or None + If ``lengths`` argument was provided, a Tensor of shape `(batch, )` + is returned. + It indicates the valid length in time axis of the output Tensor. + """ + x, lengths = self.feature_extractor(waveforms, lengths) + x = self.encoder(x, lengths) + if self.aux is not None: + x = self.aux(x) + return x, lengths + + +def wav2vec2_model( + extractor_mode: str, + extractor_conv_layer_config: Optional[List[Tuple[int, int, int]]], + extractor_conv_bias: bool, + encoder_embed_dim: int, + encoder_projection_dropout: float, + encoder_pos_conv_kernel: int, + encoder_pos_conv_groups: int, + encoder_num_layers: int, + encoder_num_heads: int, + encoder_attention_dropout: float, + encoder_ff_interm_features: int, + encoder_ff_interm_dropout: float, + encoder_dropout: float, + encoder_layer_norm_first: bool, + encoder_layer_drop: float, + aux_num_out: Optional[int], +) -> Wav2Vec2Model: + # Overriding the signature so that the return type is correct on Sphinx + """wav2vec2_model(extractor_mode: str, extractor_conv_layer_config: Optional[List[Tuple[int, int, int]]], extractor_conv_bias: bool, encoder_embed_dim: int, encoder_projection_dropout: float, encoder_pos_conv_kernel: int, encoder_pos_conv_groups: int, encoder_num_layers: int, encoder_num_heads: int, encoder_attention_dropout: float, encoder_ff_interm_features: int, encoder_ff_interm_dropout: float, encoder_dropout: float, encoder_layer_norm_first: bool, encoder_layer_drop: float, aux_num_out: Optional[int]) -> torchaudio.models.Wav2Vec2Model + + Build a custom Wav2Vec2Model + + Note: + The "feature extractor" below corresponds to + `ConvFeatureExtractionModel `__ + in the original ``fairseq`` implementation. + This is referred as "(convolutional) feature encoder" in the *wav2vec 2.0* + [:footcite:`baevski2020wav2vec`] paper. + + The "encoder" below corresponds to `TransformerEncoder `__, + and this is referred as "Transformer" in the paper. + + Args: + extractor_mode (str): Operation mode of feature extractor. + Valid values are ``"group_norm"`` or ``"layer_norm"``. + If ``"group_norm"``, then a single normalization is applied + in the first convolution block. Otherwise, all the convolution + blocks will have layer normalization. + + This option corresponds to ``extractor_mode`` from ``fairseq``. + extractor_conv_layer_config (list of integer tuples or None): + Configuration of convolution layers in feature extractor. + List of convolution configuration, + i.e. ``[(output_channel, kernel_size, stride), ...]`` + + If ``None`` is provided, then the following default value is used. + + .. code-block:: python + + [ + (512, 10, 5), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 2, 2), + (512, 2, 2), + ] + + This option corresponds to ``conv_feature_layers`` from ``fairseq``. + + extractor_conv_bias (bool): + Whether to include bias term to each convolution operation. + + This option corresponds to ``conv_bias`` from ``fairseq``. + + encoder_embed_dim (int): + The dimension of embedding in encoder. + + This option corresponds to ``encoder_embed_dim`` from ``fairseq``. + + encoder_projection_dropout (float): + The dropout probability applied after the input feature is projected + to ``encoder_embed_dim``. + + This option corresponds to ``dropout_input`` from ``fairseq``. + + encoder_pos_conv_kernel (int): + The kernel size of convolutional positional embeddings. + + This option corresponds to ``conv_pos`` from ``fairseq``. + + encoder_pos_conv_groups (int): + The number of groups of convolutional positional embeddings. + + This option corresponds to ``conv_pos_groups`` from ``fairseq``. + + encoder_num_layers (int): + The number of self attention layers in transformer block. + + This option corresponds to ``encoder_layers`` from ``fairseq``. + + encoder_num_heads (int): + The number of heads in self attention layers. + + This option corresponds to ``encoder_attention_heads`` from ``fairseq``. + + encoder_attention_dropout (float): + The dropout probability applied after softmax in self-attention layer. + + This option corresponds to ``attention_dropout`` from ``fairseq``. + + encoder_ff_interm_features (int): + The dimension of hidden features in feed forward layer. + + This option corresponds to ``encoder_ffn_embed_dim`` from ``fairseq``. + + encoder_ff_interm_dropout (float): + The dropout probability applied in feedforward layer. + + This option correspinds to ``activation_dropout`` from ``fairseq``. + + encoder_dropout (float): + The dropout probability applied at the end of feed forward layer. + + This option corresponds to ``dropout`` from ``fairseq``. + + encoder_layer_norm_first (bool): + Control the order of layer norm in transformer layer and each encoder layer. + If True, in transformer layer, layer norm is applied before features are fed + to encoder layers. In encoder layer, two layer norms are applied before and after + self attention. + If False, in transformer layer, layer norm is applied after features are fed + to encoder layers. In encoder layer, two layer norms are applied after self + attention, before and after feed forward. + + This option corresponds to ``layer_norm_first`` from ``fairseq``. + + encoder_layer_drop (float): + Probability to drop each encoder layer during training. + + This option corresponds to ``layerdrop`` from ``fairseq``. + + aux_num_out (int or None): + When provided, attach an extra linear layer on top of encoder, which can be + used for fine-tuning. + + Returns: + Wav2Vec2Model: + The resulting model. + """ # noqa: E501 + if extractor_conv_layer_config is None: + extractor_conv_layer_config = [(512, 10, 5)] + [(512, 3, 2)] * 4 + [(512, 2, 2)] * 2 + + feature_extractor = components._get_feature_extractor( + extractor_mode, extractor_conv_layer_config, extractor_conv_bias) + encoder = components._get_encoder( + in_features=extractor_conv_layer_config[-1][0], + embed_dim=encoder_embed_dim, + dropout_input=encoder_projection_dropout, + pos_conv_kernel=encoder_pos_conv_kernel, + pos_conv_groups=encoder_pos_conv_groups, + num_layers=encoder_num_layers, + num_heads=encoder_num_heads, + attention_dropout=encoder_attention_dropout, + ff_interm_features=encoder_ff_interm_features, + ff_interm_dropout=encoder_ff_interm_dropout, + dropout=encoder_dropout, + layer_norm_first=encoder_layer_norm_first, + layer_drop=encoder_layer_drop, + ) + aux = None + if aux_num_out is not None: + aux = torch.nn.Linear(in_features=encoder_embed_dim, out_features=aux_num_out) + return Wav2Vec2Model(feature_extractor, encoder, aux) + + +def wav2vec2_base( + encoder_projection_dropout: float = 0.1, + encoder_attention_dropout: float = 0.1, + encoder_ff_interm_dropout: float = 0.1, + encoder_dropout: float = 0.1, + encoder_layer_drop: float = 0.1, + aux_num_out: Optional[int] = None, +) -> Wav2Vec2Model: + # Overriding the signature so that the return type is correct on Sphinx + """wav2vec2_base(encoder_projection_dropout: float = 0.1, encoder_attention_dropout: float = 0.1, encoder_ff_interm_dropout: float = 0.1, encoder_dropout: float = 0.1, encoder_layer_drop: float = 0.1, aux_num_out: Optional[int] = None) -> torchaudio.models.Wav2Vec2Model + + Build Wav2Vec2Model with "base" architecture from *wav2vec 2.0* [:footcite:`baevski2020wav2vec`] + + Args: + encoder_projection_dropout (float): + See :py:func:`wav2vec2_model`. + encoder_attention_dropout (float): + See :py:func:`wav2vec2_model`. + encoder_ff_interm_dropout (float): + See :py:func:`wav2vec2_model`. + encoder_dropout (float): + See :py:func:`wav2vec2_model`. + encoder_layer_drop (float): + See :py:func:`wav2vec2_model`. + aux_num_out (int or None, optional): + See :py:func:`wav2vec2_model`. + + Returns: + Wav2Vec2Model: + The resulting model. + """ # noqa: E501 + return wav2vec2_model( + extractor_mode="group_norm", + extractor_conv_layer_config=None, + extractor_conv_bias=False, + encoder_embed_dim=768, + encoder_projection_dropout=encoder_projection_dropout, + encoder_pos_conv_kernel=128, + encoder_pos_conv_groups=16, + encoder_num_layers=12, + encoder_num_heads=12, + encoder_attention_dropout=encoder_attention_dropout, + encoder_ff_interm_features=3072, + encoder_ff_interm_dropout=encoder_ff_interm_dropout, + encoder_dropout=encoder_dropout, + encoder_layer_norm_first=False, + encoder_layer_drop=encoder_layer_drop, + aux_num_out=aux_num_out, + ) + + +def wav2vec2_large( + encoder_projection_dropout: float = 0.1, + encoder_attention_dropout: float = 0.1, + encoder_ff_interm_dropout: float = 0.1, + encoder_dropout: float = 0.1, + encoder_layer_drop: float = 0.1, + aux_num_out: Optional[int] = None, +) -> Wav2Vec2Model: + # Overriding the signature so that the return type is correct on Sphinx + """wav2vec2_large(encoder_projection_dropout: float = 0.1, encoder_attention_dropout: float = 0.1, encoder_ff_interm_dropout: float = 0.1, encoder_dropout: float = 0.1, encoder_layer_drop: float = 0.1, aux_num_out: Optional[int] = None) -> torchaudio.models.Wav2Vec2Model + + Build Wav2Vec2Model with "large" architecture from *wav2vec 2.0* [:footcite:`baevski2020wav2vec`] + + Args: + encoder_projection_dropout (float): + See :py:func:`wav2vec2_model`. + encoder_attention_dropout (float): + See :py:func:`wav2vec2_model`. + encoder_ff_interm_dropout (float): + See :py:func:`wav2vec2_model`. + encoder_dropout (float): + See :py:func:`wav2vec2_model`. + encoder_layer_drop (float): + See :py:func:`wav2vec2_model`. + aux_num_out (int or None, optional): + See :py:func:`wav2vec2_model`. + + Returns: + Wav2Vec2Model: + The resulting model. + """ # noqa: E501 + return wav2vec2_model( + extractor_mode="group_norm", + extractor_conv_layer_config=None, + extractor_conv_bias=False, + encoder_embed_dim=1024, + encoder_projection_dropout=encoder_projection_dropout, + encoder_pos_conv_kernel=128, + encoder_pos_conv_groups=16, + encoder_num_layers=24, + encoder_num_heads=16, + encoder_attention_dropout=encoder_attention_dropout, + encoder_ff_interm_features=4096, + encoder_ff_interm_dropout=encoder_ff_interm_dropout, + encoder_dropout=encoder_dropout, + encoder_layer_norm_first=False, + encoder_layer_drop=encoder_layer_drop, + aux_num_out=aux_num_out, + ) + + +def wav2vec2_large_lv60k( + encoder_projection_dropout: float = 0.1, + encoder_attention_dropout: float = 0.0, + encoder_ff_interm_dropout: float = 0.1, + encoder_dropout: float = 0.0, + encoder_layer_drop: float = 0.1, + aux_num_out: Optional[int] = None, +) -> Wav2Vec2Model: + # Overriding the signature so that the return type is correct on Sphinx + """wav2vec2_large_lv60k( encoder_projection_dropout: float = 0.1, encoder_attention_dropout: float = 0.0, encoder_ff_interm_dropout: float = 0.1, encoder_dropout: float = 0.0, encoder_layer_drop: float = 0.1, aux_num_out: Optional[int] = None) -> torchaudio.models.Wav2Vec2Model + + Build Wav2Vec2Model with "large lv-60k" architecture from *wav2vec 2.0* [:footcite:`baevski2020wav2vec`] + + Args: + encoder_projection_dropout (float): + See :py:func:`wav2vec2_model`. + encoder_attention_dropout (float): + See :py:func:`wav2vec2_model`. + encoder_ff_interm_dropout (float): + See :py:func:`wav2vec2_model`. + encoder_dropout (float): + See :py:func:`wav2vec2_model`. + encoder_layer_drop (float): + See :py:func:`wav2vec2_model`. + aux_num_out (int or None, optional): + See :py:func:`wav2vec2_model`. + + Returns: + Wav2Vec2Model: + The resulting model. + """ # noqa: E501 + return wav2vec2_model( + extractor_mode="layer_norm", + extractor_conv_layer_config=None, + extractor_conv_bias=True, + encoder_embed_dim=1024, + encoder_projection_dropout=encoder_projection_dropout, + encoder_pos_conv_kernel=128, + encoder_pos_conv_groups=16, + encoder_num_layers=24, + encoder_num_heads=16, + encoder_attention_dropout=encoder_attention_dropout, + encoder_ff_interm_features=4096, + encoder_ff_interm_dropout=encoder_ff_interm_dropout, + encoder_dropout=encoder_dropout, + encoder_layer_norm_first=True, + encoder_layer_drop=encoder_layer_drop, + aux_num_out=aux_num_out, + ) + + +def hubert_base( + encoder_projection_dropout: float = 0.1, + encoder_attention_dropout: float = 0.1, + encoder_ff_interm_dropout: float = 0.0, + encoder_dropout: float = 0.1, + encoder_layer_drop: float = 0.05, + aux_num_out: Optional[int] = None, +) -> Wav2Vec2Model: + # Overriding the signature so that the return type is correct on Sphinx + """hubert_base(encoder_projection_dropout: float = 0.1, encoder_attention_dropout: float = 0.1, encoder_ff_interm_dropout: float = 0.0, encoder_dropout: float = 0.1, encoder_layer_drop: float = 0.05, aux_num_out: Optional[int] = None) -> torchaudio.models.Wav2Vec2Model + + Build HuBERT model with "base" architecture from *HuBERT* [:footcite:`hsu2021hubert`] + + Args: + encoder_projection_dropout (float): + See :py:func:`wav2vec2_model`. + encoder_attention_dropout (float): + See :py:func:`wav2vec2_model`. + encoder_ff_interm_dropout (float): + See :py:func:`wav2vec2_model`. + encoder_dropout (float): + See :py:func:`wav2vec2_model`. + encoder_layer_drop (float): + See :py:func:`wav2vec2_model`. + aux_num_out (int or None, optional): + See :py:func:`wav2vec2_model`. + + Returns: + Wav2Vec2Model: + The resulting model. + """ # noqa: E501 + return wav2vec2_model( + extractor_mode='group_norm', + extractor_conv_layer_config=None, + extractor_conv_bias=False, + encoder_embed_dim=768, + encoder_projection_dropout=encoder_projection_dropout, + encoder_pos_conv_kernel=128, + encoder_pos_conv_groups=16, + encoder_num_layers=12, + encoder_num_heads=12, + encoder_attention_dropout=encoder_attention_dropout, + encoder_ff_interm_features=3072, + encoder_ff_interm_dropout=encoder_ff_interm_dropout, + encoder_dropout=encoder_dropout, + encoder_layer_norm_first=False, + encoder_layer_drop=encoder_layer_drop, + aux_num_out=aux_num_out, + ) + + +def hubert_large( + encoder_projection_dropout: float = 0.0, + encoder_attention_dropout: float = 0.0, + encoder_ff_interm_dropout: float = 0.0, + encoder_dropout: float = 0.0, + encoder_layer_drop: float = 0.0, + aux_num_out: Optional[int] = None, +) -> Wav2Vec2Model: + # Overriding the signature so that the return type is correct on Sphinx + """hubert_large(encoder_projection_dropout: float = 0.0, encoder_attention_dropout: float = 0.0, encoder_ff_interm_dropout: float = 0.0, encoder_dropout: float = 0.0, encoder_layer_drop: float = 0.0, aux_num_out: Optional[int] = None) -> torchaudio.models.Wav2Vec2Model + + Build HuBERT model with "large" architecture from *HuBERT* [:footcite:`hsu2021hubert`] + + Args: + encoder_projection_dropout (float): + See :py:func:`wav2vec2_model`. + encoder_attention_dropout (float): + See :py:func:`wav2vec2_model`. + encoder_ff_interm_dropout (float): + See :py:func:`wav2vec2_model`. + encoder_dropout (float): + See :py:func:`wav2vec2_model`. + encoder_layer_drop (float): + See :py:func:`wav2vec2_model`. + aux_num_out (int or None, optional): + See :py:func:`wav2vec2_model`. + + Returns: + Wav2Vec2Model: + The resulting model. + """ # noqa: E501 + return wav2vec2_model( + extractor_mode='layer_norm', + extractor_conv_layer_config=None, + extractor_conv_bias=False, + encoder_embed_dim=1024, + encoder_projection_dropout=encoder_projection_dropout, + encoder_pos_conv_kernel=128, + encoder_pos_conv_groups=16, + encoder_num_layers=24, + encoder_num_heads=16, + encoder_attention_dropout=encoder_attention_dropout, + encoder_ff_interm_features=4096, + encoder_ff_interm_dropout=encoder_ff_interm_dropout, + encoder_dropout=encoder_dropout, + encoder_layer_norm_first=True, + encoder_layer_drop=encoder_layer_drop, + aux_num_out=aux_num_out, + ) + + +def hubert_xlarge( + encoder_projection_dropout: float = 0.0, + encoder_attention_dropout: float = 0.0, + encoder_ff_interm_dropout: float = 0.0, + encoder_dropout: float = 0.0, + encoder_layer_drop: float = 0.0, + aux_num_out: Optional[int] = None, +) -> Wav2Vec2Model: + # Overriding the signature so that the return type is correct on Sphinx + """hubert_xlarge(encoder_projection_dropout: float = 0.0, encoder_attention_dropout: float = 0.0, encoder_ff_interm_dropout: float = 0.0, encoder_dropout: float = 0.0, encoder_layer_drop: float = 0.0, aux_num_out: Optional[int] = None) -> torchaudio.models.Wav2Vec2Model + + Build HuBERT model with "extra large" architecture from *HuBERT* [:footcite:`hsu2021hubert`] + + Args: + encoder_projection_dropout (float): + See :py:func:`wav2vec2_model`. + encoder_attention_dropout (float): + See :py:func:`wav2vec2_model`. + encoder_ff_interm_dropout (float): + See :py:func:`wav2vec2_model`. + encoder_dropout (float): + See :py:func:`wav2vec2_model`. + encoder_layer_drop (float): + See :py:func:`wav2vec2_model`. + aux_num_out (int or None, optional): + See :py:func:`wav2vec2_model`. + + Returns: + Wav2Vec2Model: + The resulting model. + """ # noqa: E501 + return wav2vec2_model( + extractor_mode='layer_norm', + extractor_conv_layer_config=None, + extractor_conv_bias=False, + encoder_embed_dim=1280, + encoder_projection_dropout=encoder_projection_dropout, + encoder_pos_conv_kernel=128, + encoder_pos_conv_groups=16, + encoder_num_layers=48, + encoder_num_heads=16, + encoder_attention_dropout=encoder_attention_dropout, + encoder_ff_interm_features=5120, + encoder_ff_interm_dropout=encoder_ff_interm_dropout, + encoder_dropout=encoder_dropout, + encoder_layer_norm_first=True, + encoder_layer_drop=encoder_layer_drop, + aux_num_out=aux_num_out, + ) diff --git a/torchaudio/models/wav2vec2/utils/__init__.py b/torchaudio/models/wav2vec2/utils/__init__.py new file mode 100644 index 00000000..9d9fb218 --- /dev/null +++ b/torchaudio/models/wav2vec2/utils/__init__.py @@ -0,0 +1,7 @@ +from .import_huggingface import import_huggingface_model +from .import_fairseq import import_fairseq_model + +__all__ = [ + 'import_huggingface_model', + 'import_fairseq_model', +] diff --git a/torchaudio/models/wav2vec2/utils/import_fairseq.py b/torchaudio/models/wav2vec2/utils/import_fairseq.py new file mode 100644 index 00000000..e285d3d1 --- /dev/null +++ b/torchaudio/models/wav2vec2/utils/import_fairseq.py @@ -0,0 +1,219 @@ +"""Import fariseq's wav2vec2.0 pretrained weights to torchaudios's format. + +For this module to work, you need `fairseq`. +""" +import re + +from torch.nn import Module + +from ..model import Wav2Vec2Model, wav2vec2_model + + +def _parse_config(w2v_model): + encoder = w2v_model.encoder + conv_layers = w2v_model.feature_extractor.conv_layers + + extractor_mode = 'layer_norm' + if 'GroupNorm' in conv_layers[0][2].__class__.__name__: + extractor_mode = 'group_norm' + else: + extractor_mode = 'layer_norm' + + conv_layer_config = [(l[0].out_channels, l[0].kernel_size[0], l[0].stride[0]) for l in conv_layers] + + if all(l[0].bias is None for l in conv_layers): + conv_bias = False + elif all(l[0].bias is not None for l in conv_layers): + conv_bias = True + else: + raise ValueError( + 'Either all the convolutions layers have bias term or none of them should.') + + config = { + 'extractor_mode': extractor_mode, + 'extractor_conv_layer_config': conv_layer_config, + 'extractor_conv_bias': conv_bias, + 'encoder_embed_dim': w2v_model.post_extract_proj.out_features, + 'encoder_projection_dropout': w2v_model.dropout_input.p, + 'encoder_pos_conv_kernel': encoder.pos_conv[0].kernel_size[0], + 'encoder_pos_conv_groups': encoder.pos_conv[0].groups, + 'encoder_num_layers': len(encoder.layers), + 'encoder_num_heads': encoder.layers[0].self_attn.num_heads, + 'encoder_attention_dropout': encoder.layers[0].self_attn.dropout_module.p, + 'encoder_ff_interm_features': encoder.layers[0].fc1.out_features, + 'encoder_ff_interm_dropout': encoder.layers[0].dropout2.p, + 'encoder_dropout': encoder.layers[0].dropout3.p, + 'encoder_layer_norm_first': encoder.layer_norm_first, + 'encoder_layer_drop': encoder.layerdrop, + } + return config + + +def _map_key(key): + key_ = key + if key.startswith('w2v_model.'): + key = key.replace('w2v_model.', '') + if re.match(r'(mask_emb|quantizer|project_q|final_proj|mask_emb)', key): + return None + # Feature Extractor + # Group norm when "extractor_mode" is "default". + # (Only the first layer) + # "conv_layers.0.2.weight" -> "conv_layers.0.layer_norm.weight" + # "conv_layers.0.2.bias" -> "conv_layers.0.layer_norm.bias" + match = re.match(r'feature_extractor\.conv_layers\.0\.2\.(weight|bias)', key) + if match: + return f"feature_extractor.conv_layers.0.layer_norm.{match.group(1)}" + # Convolutions + # "conv_layers.X.0.weight" -> "conv_layers.X.conv.weight" + # "conv_layers.X.0.bias" -> "conv_layers.X.conv.bias" + match = re.match(r'feature_extractor\.conv_layers\.(\d+)\.0\.(weight|bias)', key) + if match: + return f"feature_extractor.conv_layers.{match.group(1)}.conv.{match.group(2)}" + # Layer norm when "extractor_mode" is "layer_norm". + # "conv_layers.X.2.1.weight" -> "conv_layers.X.layer_norm.weight" + # "conv_layers.X.2.1.bias" -> "conv_layers.X.layer_norm.bias" + match = re.match(r'feature_extractor\.conv_layers\.(\d+)\.2\.1\.(weight|bias)', key) + if match: + return f"feature_extractor.conv_layers.{match.group(1)}.layer_norm.{match.group(2)}" + match = re.match(r"post_extract_proj\.(weight|bias)", key) + # Encoder - Feature projection + if match: + return f"encoder.feature_projection.projection.{match.group(1)}" + match = re.match(r"layer_norm\.(weight|bias)", key) + if match: + return f"encoder.feature_projection.layer_norm.{match.group(1)}" + # Encoder - Transformer - Convolutional positional embedding + match = re.match(r"encoder\.pos_conv\.0\.(bias|weight_g|weight_v)", key) + if match: + return f"encoder.transformer.pos_conv_embed.conv.{match.group(1)}" + match = re.match(r"encoder\.layer_norm\.(weight|bias)", key) + if match: + return f"encoder.transformer.layer_norm.{match.group(1)}" + # Encoder - Transformer - Self attention layers + match = re.match(r"encoder\.layers\.(\d+)\.self_attn\.((k_|v_|q_|out_)proj\.(weight|bias))", key) + if match: + return f"encoder.transformer.layers.{match.group(1)}.attention.{match.group(2)}" + match = re.match(r"encoder\.layers\.(\d+)\.self_attn_layer_norm\.(weight|bias)", key) + if match: + return f"encoder.transformer.layers.{match.group(1)}.layer_norm.{match.group(2)}" + match = re.match(r"encoder\.layers\.(\d+)\.fc1\.(weight|bias)", key) + if match: + return f"encoder.transformer.layers.{match.group(1)}.feed_forward.intermediate_dense.{match.group(2)}" + match = re.match(r"encoder\.layers\.(\d+)\.fc2\.(weight|bias)", key) + if match: + return f"encoder.transformer.layers.{match.group(1)}.feed_forward.output_dense.{match.group(2)}" + match = re.match(r"encoder\.layers\.(\d+)\.final_layer_norm\.(weight|bias)", key) + if match: + return f"encoder.transformer.layers.{match.group(1)}.final_layer_norm.{match.group(2)}" + match = re.match(r"proj\.(weight|bias)", key) + # Auxiliary Module + # Only relevant when loading fine-tuned models + if match: + return f"aux.{match.group(1)}" + # HuBERT Extension + if key in ['label_embs_concat']: + return key + raise ValueError(f'Unexpected key: {key_}') + + +def _convert_state_dict(state_dict): + converted = {} + for k, v in state_dict.items(): + k = _map_key(k) + if k is not None: + converted[k] = v + return converted + + +def import_fairseq_model(original: Module) -> Wav2Vec2Model: + # Overriding the signature so that the types are correct on Sphinx + """import_fairseq_model(original: torch.nn.Module) -> torchaudio.models.Wav2Vec2Model + + Build Wav2Vec2Model from the corresponding model object of `fairseq`_. + + Args: + original (torch.nn.Module): + An instance of fairseq's Wav2Vec2.0 or HuBERT model. + One of ``fairseq.models.wav2vec.wav2vec2_asr.Wav2VecEncoder``, + ``fairseq.models.wav2vec.wav2vec2.Wav2Vec2Model`` or + ``fairseq.models.hubert.hubert_asr.HubertEncoder``. + + Returns: + Wav2Vec2Model: Imported model. + + Example - Loading pretrain-only model + >>> from torchaudio.models.wav2vec2.utils import import_fairseq_model + >>> + >>> # Load model using fairseq + >>> model_file = 'wav2vec_small.pt' + >>> model, _, _ = fairseq.checkpoint_utils.load_model_ensemble_and_task([model_file]) + >>> original = model[0] + >>> imported = import_fairseq_model(original) + >>> + >>> # Perform feature extraction + >>> waveform, _ = torchaudio.load('audio.wav') + >>> features, _ = imported.extract_features(waveform) + >>> + >>> # Compare result with the original model from fairseq + >>> reference = original.feature_extractor(waveform).transpose(1, 2) + >>> torch.testing.assert_allclose(features, reference) + + Example - Fine-tuned model + >>> from torchaudio.models.wav2vec2.utils import import_fairseq_model + >>> + >>> # Load model using fairseq + >>> model_file = 'wav2vec_small_960h.pt' + >>> model, _, _ = fairseq.checkpoint_utils.load_model_ensemble_and_task([model_file]) + >>> original = model[0] + >>> imported = import_fairseq_model(original.w2v_encoder) + >>> + >>> # Perform encoding + >>> waveform, _ = torchaudio.load('audio.wav') + >>> emission, _ = imported(waveform) + >>> + >>> # Compare result with the original model from fairseq + >>> mask = torch.zeros_like(waveform) + >>> reference = original(waveform, mask)['encoder_out'].transpose(0, 1) + >>> torch.testing.assert_allclose(emission, reference) + + .. _fairseq: https://github.com/pytorch/fairseq + """ + class_ = original.__class__.__name__ + if class_ == 'Wav2Vec2Model': + return _import_wav2vec2_pretraining(original) + if class_ == 'Wav2VecEncoder': + return _import_wav2vec2_finetuning(original) + if class_ == 'HubertModel': + return _import_hubert_pretraining(original) + if class_ == 'HubertEncoder': + return _import_hubert_finetuning(original) + raise ValueError( + f'Expected an instance of `Wav2Vec2Model` or `Wav2VecEncoder`. Found: {class_}') + + +def _import_wav2vec2_finetuning(original: Module) -> Wav2Vec2Model: + config = _parse_config(original.w2v_model) + model = wav2vec2_model(**config, aux_num_out=original.proj.out_features) + model.load_state_dict(_convert_state_dict(original.state_dict())) + return model + + +def _import_wav2vec2_pretraining(original: Module) -> Wav2Vec2Model: + config = _parse_config(original) + model = wav2vec2_model(**config, aux_num_out=None) + model.load_state_dict(_convert_state_dict(original.state_dict()), strict=False) + return model + + +def _import_hubert_finetuning(original: Module) -> Wav2Vec2Model: + config = _parse_config(original.w2v_model) + model = wav2vec2_model(**config, aux_num_out=original.proj.out_features) + model.load_state_dict(_convert_state_dict(original.state_dict()), strict=False) + return model + + +def _import_hubert_pretraining(original: Module) -> Wav2Vec2Model: + config = _parse_config(original) + model = wav2vec2_model(**config, aux_num_out=None) + model.load_state_dict(_convert_state_dict(original.state_dict()), strict=False) + return model diff --git a/torchaudio/models/wav2vec2/utils/import_huggingface.py b/torchaudio/models/wav2vec2/utils/import_huggingface.py new file mode 100644 index 00000000..c1a5c413 --- /dev/null +++ b/torchaudio/models/wav2vec2/utils/import_huggingface.py @@ -0,0 +1,80 @@ +"""Import Hugging Face transformers's wav2vec2.0 pretrained weights to torchaudios's format. +""" +import logging + +from torch.nn import Module + +from ..model import Wav2Vec2Model, wav2vec2_model + +_LG = logging.getLogger(__name__) + + +def _get_config(cfg): + config = { + 'extractor_mode': f'{cfg.feat_extract_norm}_norm', + 'extractor_conv_layer_config': list(zip(cfg.conv_dim, cfg.conv_kernel, cfg.conv_stride)), + 'extractor_conv_bias': cfg.conv_bias, + 'encoder_embed_dim': cfg.hidden_size, + 'encoder_projection_dropout': cfg.feat_proj_dropout, + 'encoder_pos_conv_kernel': cfg.num_conv_pos_embeddings, + 'encoder_pos_conv_groups': cfg.num_conv_pos_embedding_groups, + 'encoder_num_layers': cfg.num_hidden_layers, + 'encoder_num_heads': cfg.num_attention_heads, + 'encoder_attention_dropout': cfg.attention_dropout, + 'encoder_ff_interm_features': cfg.intermediate_size, + 'encoder_ff_interm_dropout': cfg.activation_dropout, + 'encoder_dropout': cfg.hidden_dropout, + 'encoder_layer_norm_first': cfg.do_stable_layer_norm, + 'encoder_layer_drop': cfg.layerdrop, + } + return config + + +def _build(config, original): + if original.__class__.__name__ == 'Wav2Vec2ForCTC': + aux_num_out = original.config.vocab_size + wav2vec2 = original.wav2vec2 + else: + _LG.warning( + 'The model is not an instance of Wav2Vec2ForCTC. ' + '"lm_head" module is not imported.') + aux_num_out = None + wav2vec2 = original + imported = wav2vec2_model(**config, aux_num_out=aux_num_out) + imported.feature_extractor.load_state_dict(wav2vec2.feature_extractor.state_dict()) + imported.encoder.feature_projection.load_state_dict(wav2vec2.feature_projection.state_dict()) + imported.encoder.transformer.load_state_dict(wav2vec2.encoder.state_dict()) + if original.__class__.__name__ == 'Wav2Vec2ForCTC': + imported.aux.load_state_dict(original.lm_head.state_dict()) + return imported + + +def import_huggingface_model(original: Module) -> Wav2Vec2Model: + """import_huggingface_model(original: torch.nn.Module) -> torchaudio.models.Wav2Vec2Model + + Build Wav2Vec2Model from the corresponding model object of Hugging Face's `Transformers`_. + + Args: + original (torch.nn.Module): An instance of ``Wav2Vec2ForCTC`` from ``transformers``. + + Returns: + Wav2Vec2Model: Imported model. + + Example + >>> from torchaudio.models.wav2vec2.utils import import_huggingface_model + >>> + >>> original = Wav2Vec2ForCTC.from_pretrained("facebook/wav2vec2-base-960h") + >>> model = import_huggingface_model(original) + >>> + >>> waveforms, _ = torchaudio.load("audio.wav") + >>> logits, _ = model(waveforms) + + .. _Transformers: https://huggingface.co/transformers/ + """ + _LG.info('Importing model.') + _LG.info('Loading model configuration.') + config = _get_config(original.config) + _LG.debug(' - config: %s', config) + _LG.info('Building model.') + imported = _build(config, original) + return imported diff --git a/torchaudio/models/wavernn.py b/torchaudio/models/wavernn.py new file mode 100644 index 00000000..9f920619 --- /dev/null +++ b/torchaudio/models/wavernn.py @@ -0,0 +1,411 @@ +from typing import List, Tuple, Optional +import math + +import torch +from torch import Tensor +from torch import nn +import torch.nn.functional as F + +__all__ = [ + "ResBlock", + "MelResNet", + "Stretch2d", + "UpsampleNetwork", + "WaveRNN", +] + + +class ResBlock(nn.Module): + r"""ResNet block based on *Efficient Neural Audio Synthesis* [:footcite:`kalchbrenner2018efficient`]. + + Args: + n_freq: the number of bins in a spectrogram. (Default: ``128``) + + Examples + >>> resblock = ResBlock() + >>> input = torch.rand(10, 128, 512) # a random spectrogram + >>> output = resblock(input) # shape: (10, 128, 512) + """ + + def __init__(self, n_freq: int = 128) -> None: + super().__init__() + + self.resblock_model = nn.Sequential( + nn.Conv1d(in_channels=n_freq, out_channels=n_freq, kernel_size=1, bias=False), + nn.BatchNorm1d(n_freq), + nn.ReLU(inplace=True), + nn.Conv1d(in_channels=n_freq, out_channels=n_freq, kernel_size=1, bias=False), + nn.BatchNorm1d(n_freq) + ) + + def forward(self, specgram: Tensor) -> Tensor: + r"""Pass the input through the ResBlock layer. + Args: + specgram (Tensor): the input sequence to the ResBlock layer (n_batch, n_freq, n_time). + + Return: + Tensor shape: (n_batch, n_freq, n_time) + """ + + return self.resblock_model(specgram) + specgram + + +class MelResNet(nn.Module): + r"""MelResNet layer uses a stack of ResBlocks on spectrogram. + + Args: + n_res_block: the number of ResBlock in stack. (Default: ``10``) + n_freq: the number of bins in a spectrogram. (Default: ``128``) + n_hidden: the number of hidden dimensions of resblock. (Default: ``128``) + n_output: the number of output dimensions of melresnet. (Default: ``128``) + kernel_size: the number of kernel size in the first Conv1d layer. (Default: ``5``) + + Examples + >>> melresnet = MelResNet() + >>> input = torch.rand(10, 128, 512) # a random spectrogram + >>> output = melresnet(input) # shape: (10, 128, 508) + """ + + def __init__(self, + n_res_block: int = 10, + n_freq: int = 128, + n_hidden: int = 128, + n_output: int = 128, + kernel_size: int = 5) -> None: + super().__init__() + + ResBlocks = [ResBlock(n_hidden) for _ in range(n_res_block)] + + self.melresnet_model = nn.Sequential( + nn.Conv1d(in_channels=n_freq, out_channels=n_hidden, kernel_size=kernel_size, bias=False), + nn.BatchNorm1d(n_hidden), + nn.ReLU(inplace=True), + *ResBlocks, + nn.Conv1d(in_channels=n_hidden, out_channels=n_output, kernel_size=1) + ) + + def forward(self, specgram: Tensor) -> Tensor: + r"""Pass the input through the MelResNet layer. + Args: + specgram (Tensor): the input sequence to the MelResNet layer (n_batch, n_freq, n_time). + + Return: + Tensor shape: (n_batch, n_output, n_time - kernel_size + 1) + """ + + return self.melresnet_model(specgram) + + +class Stretch2d(nn.Module): + r"""Upscale the frequency and time dimensions of a spectrogram. + + Args: + time_scale: the scale factor in time dimension + freq_scale: the scale factor in frequency dimension + + Examples + >>> stretch2d = Stretch2d(time_scale=10, freq_scale=5) + + >>> input = torch.rand(10, 100, 512) # a random spectrogram + >>> output = stretch2d(input) # shape: (10, 500, 5120) + """ + + def __init__(self, + time_scale: int, + freq_scale: int) -> None: + super().__init__() + + self.freq_scale = freq_scale + self.time_scale = time_scale + + def forward(self, specgram: Tensor) -> Tensor: + r"""Pass the input through the Stretch2d layer. + + Args: + specgram (Tensor): the input sequence to the Stretch2d layer (..., n_freq, n_time). + + Return: + Tensor shape: (..., n_freq * freq_scale, n_time * time_scale) + """ + + return specgram.repeat_interleave(self.freq_scale, -2).repeat_interleave(self.time_scale, -1) + + +class UpsampleNetwork(nn.Module): + r"""Upscale the dimensions of a spectrogram. + + Args: + upsample_scales: the list of upsample scales. + n_res_block: the number of ResBlock in stack. (Default: ``10``) + n_freq: the number of bins in a spectrogram. (Default: ``128``) + n_hidden: the number of hidden dimensions of resblock. (Default: ``128``) + n_output: the number of output dimensions of melresnet. (Default: ``128``) + kernel_size: the number of kernel size in the first Conv1d layer. (Default: ``5``) + + Examples + >>> upsamplenetwork = UpsampleNetwork(upsample_scales=[4, 4, 16]) + >>> input = torch.rand(10, 128, 10) # a random spectrogram + >>> output = upsamplenetwork(input) # shape: (10, 1536, 128), (10, 1536, 128) + """ + + def __init__(self, + upsample_scales: List[int], + n_res_block: int = 10, + n_freq: int = 128, + n_hidden: int = 128, + n_output: int = 128, + kernel_size: int = 5) -> None: + super().__init__() + + total_scale = 1 + for upsample_scale in upsample_scales: + total_scale *= upsample_scale + self.total_scale: int = total_scale + + self.indent = (kernel_size - 1) // 2 * total_scale + self.resnet = MelResNet(n_res_block, n_freq, n_hidden, n_output, kernel_size) + self.resnet_stretch = Stretch2d(total_scale, 1) + + up_layers = [] + for scale in upsample_scales: + stretch = Stretch2d(scale, 1) + conv = nn.Conv2d(in_channels=1, + out_channels=1, + kernel_size=(1, scale * 2 + 1), + padding=(0, scale), + bias=False) + conv.weight.data.fill_(1. / (scale * 2 + 1)) + up_layers.append(stretch) + up_layers.append(conv) + self.upsample_layers = nn.Sequential(*up_layers) + + def forward(self, specgram: Tensor) -> Tuple[Tensor, Tensor]: + r"""Pass the input through the UpsampleNetwork layer. + + Args: + specgram (Tensor): the input sequence to the UpsampleNetwork layer (n_batch, n_freq, n_time) + + Return: + Tensor shape: (n_batch, n_freq, (n_time - kernel_size + 1) * total_scale), + (n_batch, n_output, (n_time - kernel_size + 1) * total_scale) + where total_scale is the product of all elements in upsample_scales. + """ + + resnet_output = self.resnet(specgram).unsqueeze(1) + resnet_output = self.resnet_stretch(resnet_output) + resnet_output = resnet_output.squeeze(1) + + specgram = specgram.unsqueeze(1) + upsampling_output = self.upsample_layers(specgram) + upsampling_output = upsampling_output.squeeze(1)[:, :, self.indent:-self.indent] + + return upsampling_output, resnet_output + + +class WaveRNN(nn.Module): + r"""WaveRNN model based on the implementation from `fatchord `_. + + The original implementation was introduced in *Efficient Neural Audio Synthesis* + [:footcite:`kalchbrenner2018efficient`]. The input channels of waveform and spectrogram have to be 1. + The product of `upsample_scales` must equal `hop_length`. + + Args: + upsample_scales: the list of upsample scales. + n_classes: the number of output classes. + hop_length: the number of samples between the starts of consecutive frames. + n_res_block: the number of ResBlock in stack. (Default: ``10``) + n_rnn: the dimension of RNN layer. (Default: ``512``) + n_fc: the dimension of fully connected layer. (Default: ``512``) + kernel_size: the number of kernel size in the first Conv1d layer. (Default: ``5``) + n_freq: the number of bins in a spectrogram. (Default: ``128``) + n_hidden: the number of hidden dimensions of resblock. (Default: ``128``) + n_output: the number of output dimensions of melresnet. (Default: ``128``) + + Example + >>> wavernn = WaveRNN(upsample_scales=[5,5,8], n_classes=512, hop_length=200) + >>> waveform, sample_rate = torchaudio.load(file) + >>> # waveform shape: (n_batch, n_channel, (n_time - kernel_size + 1) * hop_length) + >>> specgram = MelSpectrogram(sample_rate)(waveform) # shape: (n_batch, n_channel, n_freq, n_time) + >>> output = wavernn(waveform, specgram) + >>> # output shape: (n_batch, n_channel, (n_time - kernel_size + 1) * hop_length, n_classes) + """ + + def __init__(self, + upsample_scales: List[int], + n_classes: int, + hop_length: int, + n_res_block: int = 10, + n_rnn: int = 512, + n_fc: int = 512, + kernel_size: int = 5, + n_freq: int = 128, + n_hidden: int = 128, + n_output: int = 128) -> None: + super().__init__() + + self.kernel_size = kernel_size + self._pad = (kernel_size - 1 if kernel_size % 2 else kernel_size) // 2 + self.n_rnn = n_rnn + self.n_aux = n_output // 4 + self.hop_length = hop_length + self.n_classes = n_classes + self.n_bits: int = int(math.log2(self.n_classes)) + + total_scale = 1 + for upsample_scale in upsample_scales: + total_scale *= upsample_scale + if total_scale != self.hop_length: + raise ValueError(f"Expected: total_scale == hop_length, but found {total_scale} != {hop_length}") + + self.upsample = UpsampleNetwork(upsample_scales, + n_res_block, + n_freq, + n_hidden, + n_output, + kernel_size) + self.fc = nn.Linear(n_freq + self.n_aux + 1, n_rnn) + + self.rnn1 = nn.GRU(n_rnn, n_rnn, batch_first=True) + self.rnn2 = nn.GRU(n_rnn + self.n_aux, n_rnn, batch_first=True) + + self.relu1 = nn.ReLU(inplace=True) + self.relu2 = nn.ReLU(inplace=True) + + self.fc1 = nn.Linear(n_rnn + self.n_aux, n_fc) + self.fc2 = nn.Linear(n_fc + self.n_aux, n_fc) + self.fc3 = nn.Linear(n_fc, self.n_classes) + + def forward(self, waveform: Tensor, specgram: Tensor) -> Tensor: + r"""Pass the input through the WaveRNN model. + + Args: + waveform: the input waveform to the WaveRNN layer (n_batch, 1, (n_time - kernel_size + 1) * hop_length) + specgram: the input spectrogram to the WaveRNN layer (n_batch, 1, n_freq, n_time) + + Return: + Tensor: shape (n_batch, 1, (n_time - kernel_size + 1) * hop_length, n_classes) + """ + + assert waveform.size(1) == 1, 'Require the input channel of waveform is 1' + assert specgram.size(1) == 1, 'Require the input channel of specgram is 1' + # remove channel dimension until the end + waveform, specgram = waveform.squeeze(1), specgram.squeeze(1) + + batch_size = waveform.size(0) + h1 = torch.zeros(1, batch_size, self.n_rnn, dtype=waveform.dtype, device=waveform.device) + h2 = torch.zeros(1, batch_size, self.n_rnn, dtype=waveform.dtype, device=waveform.device) + # output of upsample: + # specgram: (n_batch, n_freq, (n_time - kernel_size + 1) * total_scale) + # aux: (n_batch, n_output, (n_time - kernel_size + 1) * total_scale) + specgram, aux = self.upsample(specgram) + specgram = specgram.transpose(1, 2) + aux = aux.transpose(1, 2) + + aux_idx = [self.n_aux * i for i in range(5)] + a1 = aux[:, :, aux_idx[0]:aux_idx[1]] + a2 = aux[:, :, aux_idx[1]:aux_idx[2]] + a3 = aux[:, :, aux_idx[2]:aux_idx[3]] + a4 = aux[:, :, aux_idx[3]:aux_idx[4]] + + x = torch.cat([waveform.unsqueeze(-1), specgram, a1], dim=-1) + x = self.fc(x) + res = x + x, _ = self.rnn1(x, h1) + + x = x + res + res = x + x = torch.cat([x, a2], dim=-1) + x, _ = self.rnn2(x, h2) + + x = x + res + x = torch.cat([x, a3], dim=-1) + x = self.fc1(x) + x = self.relu1(x) + + x = torch.cat([x, a4], dim=-1) + x = self.fc2(x) + x = self.relu2(x) + x = self.fc3(x) + + # bring back channel dimension + return x.unsqueeze(1) + + @torch.jit.export + def infer(self, specgram: Tensor, lengths: Optional[Tensor] = None) -> Tuple[Tensor, Optional[Tensor]]: + r"""Inference method of WaveRNN. + + This function currently only supports multinomial sampling, which assumes the + network is trained on cross entropy loss. + + Args: + specgram (Tensor): + Batch of spectrograms. Shape: `(n_batch, n_freq, n_time)`. + lengths (Tensor or None, optional): + Indicates the valid length of each audio in the batch. + Shape: `(batch, )`. + When the ``specgram`` contains spectrograms with different durations, + by providing ``lengths`` argument, the model will compute + the corresponding valid output lengths. + If ``None``, it is assumed that all the audio in ``waveforms`` + have valid length. Default: ``None``. + + Returns: + (Tensor, Optional[Tensor]): + Tensor + The inferred waveform of size `(n_batch, 1, n_time)`. + 1 stands for a single channel. + Tensor or None + If ``lengths`` argument was provided, a Tensor of shape `(batch, )` + is returned. + It indicates the valid length in time axis of the output Tensor. + """ + + device = specgram.device + dtype = specgram.dtype + + specgram = torch.nn.functional.pad(specgram, (self._pad, self._pad)) + specgram, aux = self.upsample(specgram) + if lengths is not None: + lengths = lengths * self.upsample.total_scale + + output: List[Tensor] = [] + b_size, _, seq_len = specgram.size() + + h1 = torch.zeros((1, b_size, self.n_rnn), device=device, dtype=dtype) + h2 = torch.zeros((1, b_size, self.n_rnn), device=device, dtype=dtype) + x = torch.zeros((b_size, 1), device=device, dtype=dtype) + + aux_split = [aux[:, self.n_aux * i: self.n_aux * (i + 1), :] for i in range(4)] + + for i in range(seq_len): + + m_t = specgram[:, :, i] + + a1_t, a2_t, a3_t, a4_t = [a[:, :, i] for a in aux_split] + + x = torch.cat([x, m_t, a1_t], dim=1) + x = self.fc(x) + _, h1 = self.rnn1(x.unsqueeze(1), h1) + + x = x + h1[0] + inp = torch.cat([x, a2_t], dim=1) + _, h2 = self.rnn2(inp.unsqueeze(1), h2) + + x = x + h2[0] + x = torch.cat([x, a3_t], dim=1) + x = F.relu(self.fc1(x)) + + x = torch.cat([x, a4_t], dim=1) + x = F.relu(self.fc2(x)) + + logits = self.fc3(x) + + posterior = F.softmax(logits, dim=1) + + x = torch.multinomial(posterior, 1).float() + # Transform label [0, 2 ** n_bits - 1] to waveform [-1, 1] + x = 2 * x / (2 ** self.n_bits - 1.0) - 1.0 + + output.append(x) + + return torch.stack(output).permute(1, 2, 0), lengths diff --git a/torchaudio/pipelines/__init__.py b/torchaudio/pipelines/__init__.py new file mode 100644 index 00000000..40f251d7 --- /dev/null +++ b/torchaudio/pipelines/__init__.py @@ -0,0 +1,57 @@ +from ._wav2vec2 import ( + Wav2Vec2Bundle, + Wav2Vec2ASRBundle, + WAV2VEC2_BASE, + WAV2VEC2_LARGE, + WAV2VEC2_LARGE_LV60K, + WAV2VEC2_ASR_BASE_10M, + WAV2VEC2_ASR_BASE_100H, + WAV2VEC2_ASR_BASE_960H, + WAV2VEC2_ASR_LARGE_10M, + WAV2VEC2_ASR_LARGE_100H, + WAV2VEC2_ASR_LARGE_960H, + WAV2VEC2_ASR_LARGE_LV60K_10M, + WAV2VEC2_ASR_LARGE_LV60K_100H, + WAV2VEC2_ASR_LARGE_LV60K_960H, + WAV2VEC2_XLSR53, + HUBERT_BASE, + HUBERT_LARGE, + HUBERT_XLARGE, + HUBERT_ASR_LARGE, + HUBERT_ASR_XLARGE, +) +from ._tts import ( + Tacotron2TTSBundle, + TACOTRON2_GRIFFINLIM_CHAR_LJSPEECH, + TACOTRON2_GRIFFINLIM_PHONE_LJSPEECH, + TACOTRON2_WAVERNN_CHAR_LJSPEECH, + TACOTRON2_WAVERNN_PHONE_LJSPEECH, +) + +__all__ = [ + 'Wav2Vec2Bundle', + 'Wav2Vec2ASRBundle', + 'WAV2VEC2_BASE', + 'WAV2VEC2_LARGE', + 'WAV2VEC2_LARGE_LV60K', + 'WAV2VEC2_ASR_BASE_10M', + 'WAV2VEC2_ASR_BASE_100H', + 'WAV2VEC2_ASR_BASE_960H', + 'WAV2VEC2_ASR_LARGE_10M', + 'WAV2VEC2_ASR_LARGE_100H', + 'WAV2VEC2_ASR_LARGE_960H', + 'WAV2VEC2_ASR_LARGE_LV60K_10M', + 'WAV2VEC2_ASR_LARGE_LV60K_100H', + 'WAV2VEC2_ASR_LARGE_LV60K_960H', + 'WAV2VEC2_XLSR53', + 'HUBERT_BASE', + 'HUBERT_LARGE', + 'HUBERT_XLARGE', + 'HUBERT_ASR_LARGE', + 'HUBERT_ASR_XLARGE', + 'Tacotron2TTSBundle', + 'TACOTRON2_GRIFFINLIM_CHAR_LJSPEECH', + 'TACOTRON2_GRIFFINLIM_PHONE_LJSPEECH', + 'TACOTRON2_WAVERNN_CHAR_LJSPEECH', + 'TACOTRON2_WAVERNN_PHONE_LJSPEECH', +] diff --git a/torchaudio/pipelines/_tts/__init__.py b/torchaudio/pipelines/_tts/__init__.py new file mode 100644 index 00000000..c361707c --- /dev/null +++ b/torchaudio/pipelines/_tts/__init__.py @@ -0,0 +1,16 @@ +from .interface import Tacotron2TTSBundle +from .impl import ( + TACOTRON2_GRIFFINLIM_CHAR_LJSPEECH, + TACOTRON2_GRIFFINLIM_PHONE_LJSPEECH, + TACOTRON2_WAVERNN_CHAR_LJSPEECH, + TACOTRON2_WAVERNN_PHONE_LJSPEECH, +) + + +__all__ = [ + 'Tacotron2TTSBundle', + 'TACOTRON2_GRIFFINLIM_CHAR_LJSPEECH', + 'TACOTRON2_GRIFFINLIM_PHONE_LJSPEECH', + 'TACOTRON2_WAVERNN_CHAR_LJSPEECH', + 'TACOTRON2_WAVERNN_PHONE_LJSPEECH', +] diff --git a/torchaudio/pipelines/_tts/impl.py b/torchaudio/pipelines/_tts/impl.py new file mode 100644 index 00000000..c73bf7a7 --- /dev/null +++ b/torchaudio/pipelines/_tts/impl.py @@ -0,0 +1,356 @@ +from dataclasses import dataclass +import re +from typing import Union, Optional, Dict, Any, Tuple, List + +import torch +from torch import Tensor +from torch.hub import load_state_dict_from_url + +from torchaudio.models import Tacotron2, WaveRNN +from torchaudio.functional import mu_law_decoding +from torchaudio.transforms import InverseMelScale, GriffinLim +from . import utils +from .interface import Tacotron2TTSBundle + +__all__ = [] + +_BASE_URL = 'https://download.pytorch.org/torchaudio/models' + + +################################################################################ +# Pipeline implementation - Text Processor +################################################################################ + + +class _EnglishCharProcessor(Tacotron2TTSBundle.TextProcessor): + def __init__(self): + super().__init__() + self._tokens = utils._get_chars() + self._mapping = {s: i for i, s in enumerate(self._tokens)} + + @property + def tokens(self): + return self._tokens + + def __call__(self, texts: Union[str, List[str]]) -> Tuple[Tensor, Tensor]: + if isinstance(texts, str): + texts = [texts] + indices = [[self._mapping[c] for c in t.lower() if c in self._mapping] for t in texts] + return utils._to_tensor(indices) + + +class _EnglishPhoneProcessor(Tacotron2TTSBundle.TextProcessor): + def __init__(self, *, dl_kwargs=None): + super().__init__() + self._tokens = utils._get_phones() + self._mapping = {p: i for i, p in enumerate(self._tokens)} + self._phonemizer = utils._load_phonemizer( + 'en_us_cmudict_forward.pt', dl_kwargs=dl_kwargs) + self._pattern = r"(\[[A-Z]+?\]|[_!'(),.:;? -])" + + @property + def tokens(self): + return self._tokens + + def __call__(self, texts: Union[str, List[str]]) -> Tuple[Tensor, Tensor]: + if isinstance(texts, str): + texts = [texts] + + indices = [] + for phones in self._phonemizer(texts, lang='en_us'): + # '[F][UW][B][AA][R]!' -> ['F', 'UW', 'B', 'AA', 'R', '!'] + ret = [re.sub(r'[\[\]]', '', r) for r in re.findall(self._pattern, phones)] + indices.append([self._mapping[p] for p in ret]) + return utils._to_tensor(indices) + + +################################################################################ +# Pipeline implementation - Vocoder +################################################################################ + +class _WaveRNNVocoder(torch.nn.Module, Tacotron2TTSBundle.Vocoder): + def __init__( + self, + model: WaveRNN, + min_level_db: Optional[float] = -100 + ): + super().__init__() + self._sample_rate = 22050 + self._model = model + self._min_level_db = min_level_db + + @property + def sample_rate(self): + return self._sample_rate + + def forward(self, mel_spec, lengths=None): + mel_spec = torch.exp(mel_spec) + mel_spec = 20 * torch.log10(torch.clamp(mel_spec, min=1e-5)) + if self._min_level_db is not None: + mel_spec = (self._min_level_db - mel_spec) / self._min_level_db + mel_spec = torch.clamp(mel_spec, min=0, max=1) + waveform, lengths = self._model.infer(mel_spec, lengths) + waveform = utils._unnormalize_waveform(waveform, self._model.n_bits) + waveform = mu_law_decoding(waveform, self._model.n_classes) + waveform = waveform.squeeze(1) + return waveform, lengths + + +class _GriffinLimVocoder(torch.nn.Module, Tacotron2TTSBundle.Vocoder): + def __init__(self): + super().__init__() + self._sample_rate = 22050 + self._inv_mel = InverseMelScale( + n_stft=(1024 // 2 + 1), + n_mels=80, + sample_rate=self.sample_rate, + f_min=0., + f_max=8000., + mel_scale="slaney", + norm='slaney', + ) + self._griffin_lim = GriffinLim( + n_fft=1024, + power=1, + hop_length=256, + win_length=1024, + ) + + @property + def sample_rate(self): + return self._sample_rate + + def forward(self, mel_spec, lengths=None): + mel_spec = torch.exp(mel_spec) + mel_spec = mel_spec.clone().detach().requires_grad_(True) + spec = self._inv_mel(mel_spec) + spec = spec.detach().requires_grad_(False) + waveforms = self._griffin_lim(spec) + return waveforms, lengths + + +################################################################################ +# Bundle classes mixins +################################################################################ + + +class _CharMixin: + def get_text_processor(self) -> Tacotron2TTSBundle.TextProcessor: + return _EnglishCharProcessor() + + +class _PhoneMixin: + def get_text_processor(self, *, dl_kwargs=None) -> Tacotron2TTSBundle.TextProcessor: + return _EnglishPhoneProcessor(dl_kwargs=dl_kwargs) + + +@dataclass +class _Tacotron2Mixin: + _tacotron2_path: str + _tacotron2_params: Dict[str, Any] + + def get_tacotron2(self, *, dl_kwargs=None) -> Tacotron2: + model = Tacotron2(**self._tacotron2_params) + url = f'{_BASE_URL}/{self._tacotron2_path}' + dl_kwargs = {} if dl_kwargs is None else dl_kwargs + state_dict = load_state_dict_from_url(url, **dl_kwargs) + model.load_state_dict(state_dict) + model.eval() + return model + + +@dataclass +class _WaveRNNMixin: + _wavernn_path: Optional[str] + _wavernn_params: Optional[Dict[str, Any]] + + def get_vocoder(self, *, dl_kwargs=None): + wavernn = self._get_wavernn(dl_kwargs=dl_kwargs) + return _WaveRNNVocoder(wavernn) + + def _get_wavernn(self, *, dl_kwargs=None): + model = WaveRNN(**self._wavernn_params) + url = f'{_BASE_URL}/{self._wavernn_path}' + dl_kwargs = {} if dl_kwargs is None else dl_kwargs + state_dict = load_state_dict_from_url(url, **dl_kwargs) + model.load_state_dict(state_dict) + model.eval() + return model + + +class _GriffinLimMixin: + def get_vocoder(self, **_): + return _GriffinLimVocoder() + + +################################################################################ +# Bundle classes +################################################################################ + + +@dataclass +class _Tacotron2WaveRNNCharBundle(_WaveRNNMixin, _Tacotron2Mixin, _CharMixin, Tacotron2TTSBundle): + pass + + +@dataclass +class _Tacotron2WaveRNNPhoneBundle(_WaveRNNMixin, _Tacotron2Mixin, _PhoneMixin, Tacotron2TTSBundle): + pass + + +@dataclass +class _Tacotron2GriffinLimCharBundle(_GriffinLimMixin, _Tacotron2Mixin, _CharMixin, Tacotron2TTSBundle): + pass + + +@dataclass +class _Tacotron2GriffinLimPhoneBundle(_GriffinLimMixin, _Tacotron2Mixin, _PhoneMixin, Tacotron2TTSBundle): + pass + + +################################################################################ +# Instantiate bundle objects +################################################################################ + + +TACOTRON2_GRIFFINLIM_CHAR_LJSPEECH = _Tacotron2GriffinLimCharBundle( + _tacotron2_path='tacotron2_english_characters_1500_epochs_ljspeech.pth', + _tacotron2_params=utils._get_taco_params(n_symbols=38), +) +TACOTRON2_GRIFFINLIM_CHAR_LJSPEECH.__doc__ = ( + '''Character-based TTS pipeline with :py:class:`torchaudio.models.Tacotron2` and +:py:class:`torchaudio.transforms.GriffinLim`. + +The text processor encodes the input texts character-by-character. + +Tacotron2 was trained on *LJSpeech* [:footcite:`ljspeech17`] for 1,500 epochs. +You can find the training script `here `__. +The default parameters were used. + +The vocoder is based on :py:class:`torchaudio.transforms.GriffinLim`. + +Please refer to :func:`torchaudio.pipelines.Tacotron2TTSBundle` for the usage. + +Example - "Hello world! T T S stands for Text to Speech!" + + .. image:: https://download.pytorch.org/torchaudio/doc-assets/TACOTRON2_GRIFFINLIM_CHAR_LJSPEECH.png + :alt: Spectrogram generated by Tacotron2 + + .. raw:: html + + +''') # noqa: E501 + +TACOTRON2_GRIFFINLIM_PHONE_LJSPEECH = _Tacotron2GriffinLimPhoneBundle( + _tacotron2_path='tacotron2_english_phonemes_1500_epochs_ljspeech.pth', + _tacotron2_params=utils._get_taco_params(n_symbols=96), +) +TACOTRON2_GRIFFINLIM_PHONE_LJSPEECH.__doc__ = ( + '''Phoneme-based TTS pipeline with :py:class:`torchaudio.models.Tacotron2` and +:py:class:`torchaudio.transforms.GriffinLim`. + +The text processor encodes the input texts based on phoneme. +It uses `DeepPhonemizer `__ to convert +graphemes to phonemes. +The model (*en_us_cmudict_forward*) was trained on +`CMUDict `__. + +Tacotron2 was trained on *LJSpeech* [:footcite:`ljspeech17`] for 1,500 epochs. +You can find the training script `here `__. +The text processor is set to the *"english_phonemes"*. + +The vocoder is based on :py:class:`torchaudio.transforms.GriffinLim`. + +Please refer to :func:`torchaudio.pipelines.Tacotron2TTSBundle` for the usage. + +Example - "Hello world! T T S stands for Text to Speech!" + + .. image:: https://download.pytorch.org/torchaudio/doc-assets/TACOTRON2_GRIFFINLIM_PHONE_LJSPEECH.png + :alt: Spectrogram generated by Tacotron2 + + .. raw:: html + + +''') # noqa: E501 + +TACOTRON2_WAVERNN_CHAR_LJSPEECH = _Tacotron2WaveRNNCharBundle( + _tacotron2_path='tacotron2_english_characters_1500_epochs_wavernn_ljspeech.pth', + _tacotron2_params=utils._get_taco_params(n_symbols=38), + _wavernn_path='wavernn_10k_epochs_8bits_ljspeech.pth', + _wavernn_params=utils._get_wrnn_params(), +) +TACOTRON2_WAVERNN_CHAR_LJSPEECH.__doc__ = ( + '''Character-based TTS pipeline with :py:class:`torchaudio.models.Tacotron2` and +:py:class:`torchaudio.models.WaveRNN`. + +The text processor encodes the input texts character-by-character. + +Tacotron2 was trained on *LJSpeech* [:footcite:`ljspeech17`] for 1,500 epochs. +You can find the training script `here `__. +The following parameters were used; ``win_length=1100``, ``hop_length=275``, ``n_fft=2048``, +``mel_fmin=40``, and ``mel_fmax=11025``. + +The vocder is based on :py:class:`torchaudio.models.WaveRNN`. +It was trained on 8 bits depth waveform of *LJSpeech* [:footcite:`ljspeech17`] for 10,000 epochs. +You can find the training script `here `__. + +Please refer to :func:`torchaudio.pipelines.Tacotron2TTSBundle` for the usage. + +Example - "Hello world! T T S stands for Text to Speech!" + + .. image:: https://download.pytorch.org/torchaudio/doc-assets/TACOTRON2_WAVERNN_CHAR_LJSPEECH.png + :alt: Spectrogram generated by Tacotron2 + + .. raw:: html + + +''') # noqa: E501 + +TACOTRON2_WAVERNN_PHONE_LJSPEECH = _Tacotron2WaveRNNPhoneBundle( + _tacotron2_path='tacotron2_english_phonemes_1500_epochs_wavernn_ljspeech.pth', + _tacotron2_params=utils._get_taco_params(n_symbols=96), + _wavernn_path='wavernn_10k_epochs_8bits_ljspeech.pth', + _wavernn_params=utils._get_wrnn_params(), +) +TACOTRON2_WAVERNN_PHONE_LJSPEECH.__doc__ = ( + '''Phoneme-based TTS pipeline with :py:class:`torchaudio.models.Tacotron2` and +:py:class:`torchaudio.models.WaveRNN`. + +The text processor encodes the input texts based on phoneme. +It uses `DeepPhonemizer `__ to convert +graphemes to phonemes. +The model (*en_us_cmudict_forward*) was trained on +`CMUDict `__. + +Tacotron2 was trained on *LJSpeech* [:footcite:`ljspeech17`] for 1,500 epochs. +You can find the training script `here `__. +The following parameters were used; ``win_length=1100``, ``hop_length=275``, ``n_fft=2048``, +``mel_fmin=40``, and ``mel_fmax=11025``. + +The vocder is based on :py:class:`torchaudio.models.WaveRNN`. +It was trained on 8 bits depth waveform of *LJSpeech* [:footcite:`ljspeech17`] for 10,000 epochs. +You can find the training script `here `__. + +Please refer to :func:`torchaudio.pipelines.Tacotron2TTSBundle` for the usage. + +Example - "Hello world! T T S stands for Text to Speech!" + + .. image:: https://download.pytorch.org/torchaudio/doc-assets/TACOTRON2_WAVERNN_PHONE_LJSPEECH.png + :alt: Spectrogram generated by Tacotron2 + + .. raw:: html + + +''') # noqa: E501 diff --git a/torchaudio/pipelines/_tts/interface.py b/torchaudio/pipelines/_tts/interface.py new file mode 100644 index 00000000..e78e7833 --- /dev/null +++ b/torchaudio/pipelines/_tts/interface.py @@ -0,0 +1,272 @@ +from abc import ABC, abstractmethod +from typing import Union, List, Tuple, Optional + +from torch import Tensor + +from torchaudio.models import Tacotron2 + + +class _TextProcessor(ABC): + @property + @abstractmethod + def tokens(self): + """The tokens that the each value in the processed tensor represent. + + See :func:`torchaudio.pipelines.Tacotron2TTSBundle.get_text_processor` for the usage. + + :type: List[str] + """ + + @abstractmethod + def __call__(self, texts: Union[str, List[str]]) -> Tuple[Tensor, Tensor]: + """Encode the given (batch of) texts into numerical tensors + + See :func:`torchaudio.pipelines.Tacotron2TTSBundle.get_text_processor` for the usage. + + Args: + text (str or list of str): The input texts. + + Returns: + (Tensor, Tensor): + Tensor: + The encoded texts. Shape: `(batch, max length)` + Tensor: + The valid length of each sample in the batch. Shape: `(batch, )`. + """ + + +class _Vocoder(ABC): + @property + @abstractmethod + def sample_rate(self): + """The sample rate of the resulting waveform + + See :func:`torchaudio.pipelines.Tacotron2TTSBundle.get_vocoder` for the usage. + + :type: float + """ + + @abstractmethod + def __call__(self, specgrams: Tensor, lengths: Optional[Tensor] = None) -> Tuple[Tensor, Optional[Tensor]]: + """Generate waveform from the given input, such as spectrogram + + See :func:`torchaudio.pipelines.Tacotron2TTSBundle.get_vocoder` for the usage. + + Args: + specgrams (Tensor): + The input spectrogram. Shape: `(batch, frequency bins, time)`. + The expected shape depends on the implementation. + lengths (Tensor, or None, optional): + The valid length of each sample in the batch. Shape: `(batch, )`. + (Default: `None`) + + Returns: + (Tensor, Optional[Tensor]): + Tensor: + The generated waveform. Shape: `(batch, max length)` + Tensor or None: + The valid length of each sample in the batch. Shape: `(batch, )`. + """ + + +class Tacotron2TTSBundle(ABC): + """Data class that bundles associated information to use pretrained Tacotron2 and vocoder. + + This class provides interfaces for instantiating the pretrained model along with + the information necessary to retrieve pretrained weights and additional data + to be used with the model. + + Torchaudio library instantiates objects of this class, each of which represents + a different pretrained model. Client code should access pretrained models via these + instances. + + Please see below for the usage and the available values. + + Example - Character-based TTS pipeline with Tacotron2 and WaveRNN + >>> import torchaudio + >>> + >>> text = "Hello, T T S !" + >>> bundle = torchaudio.pipelines.TACOTRON2_WAVERNN_CHAR_LJSPEECH + >>> + >>> # Build processor, Tacotron2 and WaveRNN model + >>> processor = bundle.get_text_processor() + >>> tacotron2 = bundle.get_tacotron2() + Downloading: + 100%|███████████████████████████████| 107M/107M [00:01<00:00, 87.9MB/s] + >>> vocoder = bundle.get_vocoder() + Downloading: + 100%|███████████████████████████████| 16.7M/16.7M [00:00<00:00, 78.1MB/s] + >>> + >>> # Encode text + >>> input, lengths = processor(text) + >>> + >>> # Generate (mel-scale) spectrogram + >>> specgram, lengths, _ = tacotron2.infer(input, lengths) + >>> + >>> # Convert spectrogram to waveform + >>> waveforms, lengths = vocoder(specgram, lengths) + >>> + >>> torchaudio.save('hello-tts.wav', waveforms[0], vocoder.sample_rate) + + Example - Phoneme-based TTS pipeline with Tacotron2 and WaveRNN + >>> + >>> # Note: + >>> # This bundle uses pre-trained DeepPhonemizer as + >>> # the text pre-processor. + >>> # Please install deep-phonemizer. + >>> # See https://github.com/as-ideas/DeepPhonemizer + >>> # The pretrained weight is automatically downloaded. + >>> + >>> import torchaudio + >>> + >>> text = "Hello, TTS!" + >>> bundle = torchaudio.pipelines.TACOTRON2_WAVERNN_PHONEME_LJSPEECH + >>> + >>> # Build processor, Tacotron2 and WaveRNN model + >>> processor = bundle.get_text_processor() + Downloading: + 100%|███████████████████████████████| 63.6M/63.6M [00:04<00:00, 15.3MB/s] + >>> tacotron2 = bundle.get_tacotron2() + Downloading: + 100%|███████████████████████████████| 107M/107M [00:01<00:00, 87.9MB/s] + >>> vocoder = bundle.get_vocoder() + Downloading: + 100%|███████████████████████████████| 16.7M/16.7M [00:00<00:00, 78.1MB/s] + >>> + >>> # Encode text + >>> input, lengths = processor(text) + >>> + >>> # Generate (mel-scale) spectrogram + >>> specgram, lengths, _ = tacotron2.infer(input, lengths) + >>> + >>> # Convert spectrogram to waveform + >>> waveforms, lengths = vocoder(specgram, lengths) + >>> + >>> torchaudio.save('hello-tts.wav', waveforms[0], vocoder.sample_rate) + """ + + # Using the inner class so that these interfaces are not directly exposed on + # `torchaudio.pipelines`, but still listed in documentation. + # The thing is, text processing and vocoder are generic and we do not know what kind of + # new text processing and vocoder will be added in the future, so we want to make these + # interfaces specific to this Tacotron2TTS pipeline. + class TextProcessor(_TextProcessor): + """Interface of the text processing part of Tacotron2TTS pipeline + + See :func:`torchaudio.pipelines.Tacotron2TTSBundle.get_text_processor` for the usage. + """ + + class Vocoder(_Vocoder): + """Interface of the vocoder part of Tacotron2TTS pipeline + + See :func:`torchaudio.pipelines.Tacotron2TTSBundle.get_vocoder` for the usage. + """ + + @abstractmethod + def get_text_processor(self, *, dl_kwargs=None) -> TextProcessor: + # Overriding the signature so that the return type is correct on Sphinx + """get_text_processor(self, *, dl_kwargs=None) -> torchaudio.pipelines.Tacotron2TTSBundle.TextProcessor + + Create a text processor + + For character-based pipeline, this processor splits the input text by character. + For phoneme-based pipeline, this processor converts the input text (grapheme) to + phonemes. + + If a pre-trained weight file is necessary, + :func:`torch.hub.download_url_to_file` is used to downloaded it. + + Args: + dl_kwargs (dictionary of keyword arguments,): + Passed to :func:`torch.hub.download_url_to_file`. + + Returns: + TTSTextProcessor: + A callable which takes a string or a list of strings as input and + returns Tensor of encoded texts and Tensor of valid lengths. + The object also has ``tokens`` property, which allows to recover the + tokenized form. + + Example - Character-based + >>> text = [ + >>> "Hello World!", + >>> "Text-to-speech!", + >>> ] + >>> bundle = torchaudio.pipelines.TACOTRON2_WAVERNN_CHAR_LJSPEECH + >>> processor = bundle.get_text_processor() + >>> input, lengths = processor(text) + >>> + >>> print(input) + tensor([[19, 16, 23, 23, 26, 11, 34, 26, 29, 23, 15, 2, 0, 0, 0], + [31, 16, 35, 31, 1, 31, 26, 1, 30, 27, 16, 16, 14, 19, 2]], + dtype=torch.int32) + >>> + >>> print(lengths) + tensor([12, 15], dtype=torch.int32) + >>> + >>> print([processor.tokens[i] for i in input[0, :lengths[0]]]) + ['h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '!'] + >>> print([processor.tokens[i] for i in input[1, :lengths[1]]]) + ['t', 'e', 'x', 't', '-', 't', 'o', '-', 's', 'p', 'e', 'e', 'c', 'h', '!'] + + Example - Phoneme-based + >>> text = [ + >>> "Hello, T T S !", + >>> "Text-to-speech!", + >>> ] + >>> bundle = torchaudio.pipelines.TACOTRON2_WAVERNN_PHONE_LJSPEECH + >>> processor = bundle.get_text_processor() + Downloading: + 100%|███████████████████████████████| 63.6M/63.6M [00:04<00:00, 15.3MB/s] + >>> input, lengths = processor(text) + >>> + >>> print(input) + tensor([[54, 20, 65, 69, 11, 92, 44, 65, 38, 2, 0, 0, 0, 0], + [81, 40, 64, 79, 81, 1, 81, 20, 1, 79, 77, 59, 37, 2]], + dtype=torch.int32) + >>> + >>> print(lengths) + tensor([10, 14], dtype=torch.int32) + >>> + >>> print([processor.tokens[i] for i in input[0]]) + ['HH', 'AH', 'L', 'OW', ' ', 'W', 'ER', 'L', 'D', '!', '_', '_', '_', '_'] + >>> print([processor.tokens[i] for i in input[1]]) + ['T', 'EH', 'K', 'S', 'T', '-', 'T', 'AH', '-', 'S', 'P', 'IY', 'CH', '!'] + """ + + @abstractmethod + def get_vocoder(self, *, dl_kwargs=None) -> Vocoder: + # Overriding the signature so that the return type is correct on Sphinx + """get_vocoder(self, *, dl_kwargs=None) -> torchaudio.pipelines.Tacotron2TTSBundle.Vocoder + + Create a vocoder module, based off of either WaveRNN or GriffinLim. + + If a pre-trained weight file is necessary, + :func:`torch.hub.load_state_dict_from_url` is used to downloaded it. + + Args: + dl_kwargs (dictionary of keyword arguments): + Passed to :func:`torch.hub.load_state_dict_from_url`. + + Returns: + Callable[[Tensor, Optional[Tensor]], Tuple[Tensor, Optional[Tensor]]]: + A vocoder module, which takes spectrogram Tensor and an optional + length Tensor, then returns resulting waveform Tensor and an optional + length Tensor. + """ + + @abstractmethod + def get_tacotron2(self, *, dl_kwargs=None) -> Tacotron2: + # Overriding the signature so that the return type is correct on Sphinx + """get_tacotron2(self, *, dl_kwargs=None) -> torchaudio.models.Tacotron2 + + Create a Tacotron2 model with pre-trained weight. + + Args: + dl_kwargs (dictionary of keyword arguments): + Passed to :func:`torch.hub.load_state_dict_from_url`. + + Returns: + Tacotron2: + The resulting model. + """ diff --git a/torchaudio/pipelines/_tts/utils.py b/torchaudio/pipelines/_tts/utils.py new file mode 100644 index 00000000..54a5c9a4 --- /dev/null +++ b/torchaudio/pipelines/_tts/utils.py @@ -0,0 +1,229 @@ +import os +import logging + +import torch + +from torchaudio._internal import module_utils as _mod_utils + + +def _get_chars(): + return ( + '_', + '-', + '!', + "'", + '(', + ')', + ',', + '.', + ':', + ';', + '?', + ' ', + 'a', + 'b', + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i', + 'j', + 'k', + 'l', + 'm', + 'n', + 'o', + 'p', + 'q', + 'r', + 's', + 't', + 'u', + 'v', + 'w', + 'x', + 'y', + 'z', + ) + + +def _get_phones(): + return ( + "_", + "-", + "!", + "'", + "(", + ")", + ",", + ".", + ":", + ";", + "?", + " ", + "AA", + "AA0", + "AA1", + "AA2", + "AE", + "AE0", + "AE1", + "AE2", + "AH", + "AH0", + "AH1", + "AH2", + "AO", + "AO0", + "AO1", + "AO2", + "AW", + "AW0", + "AW1", + "AW2", + "AY", + "AY0", + "AY1", + "AY2", + "B", + "CH", + "D", + "DH", + "EH", + "EH0", + "EH1", + "EH2", + "ER", + "ER0", + "ER1", + "ER2", + "EY", + "EY0", + "EY1", + "EY2", + "F", + "G", + "HH", + "IH", + "IH0", + "IH1", + "IH2", + "IY", + "IY0", + "IY1", + "IY2", + "JH", + "K", + "L", + "M", + "N", + "NG", + "OW", + "OW0", + "OW1", + "OW2", + "OY", + "OY0", + "OY1", + "OY2", + "P", + "R", + "S", + "SH", + "T", + "TH", + "UH", + "UH0", + "UH1", + "UH2", + "UW", + "UW0", + "UW1", + "UW2", + "V", + "W", + "Y", + "Z", + "ZH" + ) + + +def _to_tensor(indices): + lengths = torch.tensor([len(i) for i in indices], dtype=torch.int32) + values = [torch.tensor(i) for i in indices] + values = torch.nn.utils.rnn.pad_sequence(values, batch_first=True) + return values, lengths + + +def _load_phonemizer(file, dl_kwargs): + if not _mod_utils.is_module_available('dp'): + raise RuntimeError('DeepPhonemizer is not installed. Please install it.') + + from dp.phonemizer import Phonemizer + + # By default, dp issues DEBUG level log. + logger = logging.getLogger('dp') + orig_level = logger.level + logger.setLevel(logging.INFO) + try: + url = f'https://public-asai-dl-models.s3.eu-central-1.amazonaws.com/DeepPhonemizer/{file}' + directory = os.path.join(torch.hub.get_dir(), 'checkpoints') + os.makedirs(directory, exist_ok=True) + path = os.path.join(directory, file) + if not os.path.exists(path): + dl_kwargs = {} if dl_kwargs is None else dl_kwargs + torch.hub.download_url_to_file(url, path, **dl_kwargs) + return Phonemizer.from_checkpoint(path) + finally: + logger.setLevel(orig_level) + + +def _unnormalize_waveform(waveform: torch.Tensor, bits: int) -> torch.Tensor: + r"""Transform waveform [-1, 1] to label [0, 2 ** bits - 1]""" + waveform = torch.clamp(waveform, -1, 1) + waveform = (waveform + 1.0) * (2 ** bits - 1) / 2 + return torch.clamp(waveform, 0, 2 ** bits - 1).int() + + +def _get_taco_params(n_symbols): + return { + 'mask_padding': False, + 'n_mels': 80, + 'n_frames_per_step': 1, + 'symbol_embedding_dim': 512, + 'encoder_embedding_dim': 512, + 'encoder_n_convolution': 3, + 'encoder_kernel_size': 5, + 'decoder_rnn_dim': 1024, + 'decoder_max_step': 2000, + 'decoder_dropout': 0.1, + 'decoder_early_stopping': True, + 'attention_rnn_dim': 1024, + 'attention_hidden_dim': 128, + 'attention_location_n_filter': 32, + 'attention_location_kernel_size': 31, + 'attention_dropout': 0.1, + 'prenet_dim': 256, + 'postnet_n_convolution': 5, + 'postnet_kernel_size': 5, + 'postnet_embedding_dim': 512, + 'gate_threshold': 0.5, + 'n_symbol': n_symbols, + } + + +def _get_wrnn_params(): + return { + 'upsample_scales': [5, 5, 11], + 'n_classes': 2 ** 8, # n_bits = 8 + 'hop_length': 275, + 'n_res_block': 10, + 'n_rnn': 512, + 'n_fc': 512, + 'kernel_size': 5, + 'n_freq': 80, + 'n_hidden': 128, + 'n_output': 128 + } diff --git a/torchaudio/pipelines/_wav2vec2.py b/torchaudio/pipelines/_wav2vec2.py new file mode 100644 index 00000000..69586084 --- /dev/null +++ b/torchaudio/pipelines/_wav2vec2.py @@ -0,0 +1,1001 @@ +from dataclasses import dataclass +from typing import Dict, Tuple, Any + +from torch.hub import load_state_dict_from_url + +from torchaudio.models import wav2vec2_model, Wav2Vec2Model + +__all__ = [] + + +@dataclass +class Wav2Vec2Bundle: + """torchaudio.pipelines.Wav2Vec2Bundle() + + Data class that bundles associated information to use pretrained Wav2Vec2Model. + + This class provides interfaces for instantiating the pretrained model along with + the information necessary to retrieve pretrained weights and additional data + to be used with the model. + + Torchaudio library instantiates objects of this class, each of which represents + a different pretrained model. Client code should access pretrained models via these + instances. + + Please see below for the usage and the available values. + + Example - Feature Extraction + >>> import torchaudio + >>> + >>> bundle = torchaudio.pipelines.HUBERT_BASE + >>> + >>> # Build the model and load pretrained weight. + >>> model = bundle.get_model() + Downloading: + 100%|███████████████████████████████| 360M/360M [00:06<00:00, 60.6MB/s] + >>> + >>> # Resample audio to the expected sampling rate + >>> waveform = torchaudio.functional.resample(waveform, sample_rate, bundle.sample_rate) + >>> + >>> # Extract acoustic features + >>> features, _ = model.extract_features(waveform) + """ # noqa: E501 + _path: str + _params: Dict[str, Any] + _sample_rate: float + + @property + def sample_rate(self) -> float: + """Sample rate of the audio that the model is trained on. + + :type: float + """ + return self._sample_rate + + def get_model(self, *, dl_kwargs=None) -> Wav2Vec2Model: + # Overriding the signature so that the return type is correct on Sphinx + """get_model(self, *, dl_kwargs=None) -> torchaudio.models.Wav2Vec2Model + + Construct the model and load the pretrained weight. + + The weight file is downloaded from the internet and cached with + :func:`torch.hub.load_state_dict_from_url` + + Args: + dl_kwargs (dictionary of keyword arguments): Passed to :func:`torch.hub.load_state_dict_from_url`. + """ + model = wav2vec2_model(**self._params) + url = f'https://download.pytorch.org/torchaudio/models/{self._path}' + dl_kwargs = {} if dl_kwargs is None else dl_kwargs + state_dict = load_state_dict_from_url(url, **dl_kwargs) + model.load_state_dict(state_dict) + model.eval() + return model + + +@dataclass +class Wav2Vec2ASRBundle(Wav2Vec2Bundle): + """torchaudio.pipelines.Wav2Vec2ASRBundle() + + Data class that bundles associated information to use pretrained Wav2Vec2Model. + + This class provides interfaces for instantiating the pretrained model along with + the information necessary to retrieve pretrained weights and additional data + to be used with the model. + + Torchaudio library instantiates objects of this class, each of which represents + a different pretrained model. Client code should access pretrained models via these + instances. + + Please see below for the usage and the available values. + + Example - ASR + >>> import torchaudio + >>> + >>> bundle = torchaudio.pipelines.HUBERT_ASR_LARGE + >>> + >>> # Build the model and load pretrained weight. + >>> model = bundle.get_model() + Downloading: + 100%|███████████████████████████████| 1.18G/1.18G [00:17<00:00, 73.8MB/s] + >>> + >>> # Check the corresponding labels of the output. + >>> labels = bundle.get_labels() + >>> print(labels) + ('', '', '', '', '|', 'E', 'T', 'A', 'O', 'N', 'I', 'H', 'S', 'R', 'D', 'L', 'U', 'M', 'W', 'C', 'F', 'G', 'Y', 'P', 'B', 'V', 'K', "'", 'X', 'J', 'Q', 'Z') + >>> + >>> # Resample audio to the expected sampling rate + >>> waveform = torchaudio.functional.resample(waveform, sample_rate, bundle.sample_rate) + >>> + >>> # Infer the label probability distribution + >>> emissions, _ = model(waveform) + >>> + >>> # Pass emission to decoder + >>> # `ctc_decode` is for illustration purpose only + >>> transcripts = ctc_decode(emissions, labels) + """ # noqa: E501 + _labels: Tuple[str] + + def get_labels( + self, + *, + bos: str = '', + pad: str = '', + eos: str = '', + unk: str = '', + ) -> Tuple[str]: + """The output class labels (only applicable to fine-tuned bundles) + + The first four tokens are BOS, padding, EOS and UNK tokens and they can be customized. + + Args: + bos (str, optional): Beginning of sentence token. (default: ``''``) + pad (str, optional): Padding token. (default: ``''``) + eos (str, optional): End of sentence token. (default: ``''``) + unk (str, optional): Token for unknown class. (default: ``''``) + + Returns: + Tuple[str]: + For models fine-tuned on ASR, returns the tuple of strings representing + the output class labels. + + Example + >>> import torchaudio + >>> torchaudio.models.HUBERT_ASR_LARGE.get_labels() + ('', '', '', '', '|', 'E', 'T', 'A', 'O', 'N', 'I', 'H', 'S', 'R', 'D', 'L', 'U', 'M', 'W', 'C', 'F', 'G', 'Y', 'P', 'B', 'V', 'K', "'", 'X', 'J', 'Q', 'Z') + """ # noqa: E501 + if self._labels is None: + raise ValueError('Pre-trained models do not have labels.') + return (bos, pad, eos, unk, *self._labels) + + +def _get_labels(): + return ( + '|', + 'E', + 'T', + 'A', + 'O', + 'N', + 'I', + 'H', + 'S', + 'R', + 'D', + 'L', + 'U', + 'M', + 'W', + 'C', + 'F', + 'G', + 'Y', + 'P', + 'B', + 'V', + 'K', + "'", + 'X', + 'J', + 'Q', + 'Z', + ) + + +WAV2VEC2_BASE = Wav2Vec2Bundle( + _path='wav2vec2_fairseq_base_ls960.pth', + _params={ + 'extractor_mode': 'group_norm', + 'extractor_conv_layer_config': [ + (512, 10, 5), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 2, 2), + (512, 2, 2), + ], + 'extractor_conv_bias': False, + 'encoder_embed_dim': 768, + 'encoder_projection_dropout': 0.1, + 'encoder_pos_conv_kernel': 128, + 'encoder_pos_conv_groups': 16, + 'encoder_num_layers': 12, + 'encoder_num_heads': 12, + 'encoder_attention_dropout': 0.1, + 'encoder_ff_interm_features': 3072, + 'encoder_ff_interm_dropout': 0.0, + 'encoder_dropout': 0.1, + 'encoder_layer_norm_first': False, + 'encoder_layer_drop': 0.05, + "aux_num_out": None, + }, + _sample_rate=16000, +) +WAV2VEC2_BASE.__doc__ = """wav2vec 2.0 model with "Base" configuration. + +Pre-trained on 960 hours of unlabeled audio from *LibriSpeech* dataset [:footcite:`7178964`] +(the combination of "train-clean-100", "train-clean-360", and "train-other-500"). +Not fine-tuned. + +Originally published by the authors of *wav2vec 2.0* [:footcite:`baevski2020wav2vec`] under MIT License and +redistributed with the same license. +[`License `__, +`Source `__] + +Please refer to :func:`torchaudio.pipelines.Wav2Vec2Bundle` for the usage. +""" # noqa: E501 + +WAV2VEC2_ASR_BASE_10M = Wav2Vec2ASRBundle( + _path='wav2vec2_fairseq_base_ls960_asr_ll10m.pth', + _params={ + 'extractor_mode': 'group_norm', + 'extractor_conv_layer_config': [ + (512, 10, 5), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 2, 2), + (512, 2, 2), + ], + 'extractor_conv_bias': False, + 'encoder_embed_dim': 768, + 'encoder_projection_dropout': 0.1, + 'encoder_pos_conv_kernel': 128, + 'encoder_pos_conv_groups': 16, + 'encoder_num_layers': 12, + 'encoder_num_heads': 12, + 'encoder_attention_dropout': 0.1, + 'encoder_ff_interm_features': 3072, + 'encoder_ff_interm_dropout': 0.0, + 'encoder_dropout': 0.1, + 'encoder_layer_norm_first': False, + 'encoder_layer_drop': 0.05, + "aux_num_out": 32, + }, + _labels=_get_labels(), + _sample_rate=16000, +) +WAV2VEC2_ASR_BASE_10M.__doc__ = """Build "base" wav2vec2 model with an extra linear module + +Pre-trained on 960 hours of unlabeled audio from *LibriSpeech* dataset [:footcite:`7178964`] +(the combination of "train-clean-100", "train-clean-360", and "train-other-500"), and +fine-tuned for ASR on 10 minutes of transcribed audio from *Libri-Light* dataset +[:footcite:`librilight`] ("train-10min" subset). + +Originally published by the authors of *wav2vec 2.0* [:footcite:`baevski2020wav2vec`] under MIT License and +redistributed with the same license. +[`License `__, +`Source `__] + +Please refer to :func:`torchaudio.pipelines.Wav2Vec2ASRBundle` for the usage. +""" # noqa: E501 + +WAV2VEC2_ASR_BASE_100H = Wav2Vec2ASRBundle( + 'wav2vec2_fairseq_base_ls960_asr_ls100.pth', + { + 'extractor_mode': 'group_norm', + 'extractor_conv_layer_config': [ + (512, 10, 5), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 2, 2), + (512, 2, 2), + ], + 'extractor_conv_bias': False, + 'encoder_embed_dim': 768, + 'encoder_projection_dropout': 0.1, + 'encoder_pos_conv_kernel': 128, + 'encoder_pos_conv_groups': 16, + 'encoder_num_layers': 12, + 'encoder_num_heads': 12, + 'encoder_attention_dropout': 0.1, + 'encoder_ff_interm_features': 3072, + 'encoder_ff_interm_dropout': 0.0, + 'encoder_dropout': 0.1, + 'encoder_layer_norm_first': False, + 'encoder_layer_drop': 0.05, + "aux_num_out": 32, + }, + _labels=_get_labels(), + _sample_rate=16000, +) + +WAV2VEC2_ASR_BASE_100H.__doc__ = """Build "base" wav2vec2 model with an extra linear module + +Pre-trained on 960 hours of unlabeled audio from *LibriSpeech* dataset [:footcite:`7178964`] +(the combination of "train-clean-100", "train-clean-360", and "train-other-500"), and +fine-tuned for ASR on 100 hours of transcribed audio from "train-clean-100" subset. + +Originally published by the authors of *wav2vec 2.0* [:footcite:`baevski2020wav2vec`] under MIT License and +redistributed with the same license. +[`License `__, +`Source `__] + +Please refer to :func:`torchaudio.pipelines.Wav2Vec2ASRBundle` for the usage. +""" # noqa: E501 + +WAV2VEC2_ASR_BASE_960H = Wav2Vec2ASRBundle( + 'wav2vec2_fairseq_base_ls960_asr_ls960.pth', + { + "extractor_mode": "group_norm", + "extractor_conv_layer_config": [ + (512, 10, 5), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 2, 2), + (512, 2, 2), + ], + "extractor_conv_bias": False, + "encoder_embed_dim": 768, + "encoder_projection_dropout": 0.1, + "encoder_pos_conv_kernel": 128, + "encoder_pos_conv_groups": 16, + "encoder_num_layers": 12, + "encoder_num_heads": 12, + "encoder_attention_dropout": 0.1, + "encoder_ff_interm_features": 3072, + "encoder_ff_interm_dropout": 0.0, + "encoder_dropout": 0.1, + "encoder_layer_norm_first": False, + "encoder_layer_drop": 0.05, + "aux_num_out": 32, + }, + _labels=_get_labels(), + _sample_rate=16000, +) +WAV2VEC2_ASR_BASE_960H.__doc__ = """Build "base" wav2vec2 model with an extra linear module + +Pre-trained on 960 hours of unlabeled audio from *LibriSpeech* dataset [:footcite:`7178964`] +(the combination of "train-clean-100", "train-clean-360", and "train-other-500"), and +fine-tuned for ASR on the same audio with the corresponding transcripts. + +Originally published by the authors of *wav2vec 2.0* [:footcite:`baevski2020wav2vec`] under MIT License and +redistributed with the same license. +[`License `__, +`Source `__] + +Please refer to :func:`torchaudio.pipelines.Wav2Vec2ASRBundle` for the usage. +""" # noqa: E501 + +WAV2VEC2_LARGE = Wav2Vec2Bundle( + 'wav2vec2_fairseq_large_ls960.pth', + { + "extractor_mode": "group_norm", + "extractor_conv_layer_config": [ + (512, 10, 5), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 2, 2), + (512, 2, 2), + ], + "extractor_conv_bias": False, + "encoder_embed_dim": 1024, + "encoder_projection_dropout": 0.1, + "encoder_pos_conv_kernel": 128, + "encoder_pos_conv_groups": 16, + "encoder_num_layers": 24, + "encoder_num_heads": 16, + "encoder_attention_dropout": 0.1, + "encoder_ff_interm_features": 4096, + "encoder_ff_interm_dropout": 0.0, + "encoder_dropout": 0.0, + "encoder_layer_norm_first": False, + "encoder_layer_drop": 0.2, + "aux_num_out": None, + }, + _sample_rate=16000, +) +WAV2VEC2_LARGE.__doc__ = """Build "large" wav2vec2 model. + +Pre-trained on 960 hours of unlabeled audio from *LibriSpeech* dataset [:footcite:`7178964`] +(the combination of "train-clean-100", "train-clean-360", and "train-other-500"). +Not fine-tuned. + +Originally published by the authors of *wav2vec 2.0* [:footcite:`baevski2020wav2vec`] under MIT License and +redistributed with the same license. +[`License `__, +`Source `__] + +Please refer to :func:`torchaudio.pipelines.Wav2Vec2Bundle` for the usage. +""" # noqa: E501 + +WAV2VEC2_ASR_LARGE_10M = Wav2Vec2ASRBundle( + 'wav2vec2_fairseq_large_ls960_asr_ll10m.pth', + { + "extractor_mode": "group_norm", + "extractor_conv_layer_config": [ + (512, 10, 5), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 2, 2), + (512, 2, 2), + ], + "extractor_conv_bias": False, + "encoder_embed_dim": 1024, + "encoder_projection_dropout": 0.1, + "encoder_pos_conv_kernel": 128, + "encoder_pos_conv_groups": 16, + "encoder_num_layers": 24, + "encoder_num_heads": 16, + "encoder_attention_dropout": 0.1, + "encoder_ff_interm_features": 4096, + "encoder_ff_interm_dropout": 0.0, + "encoder_dropout": 0.0, + "encoder_layer_norm_first": False, + "encoder_layer_drop": 0.2, + "aux_num_out": 32, + }, + _labels=_get_labels(), + _sample_rate=16000, +) +WAV2VEC2_ASR_LARGE_10M.__doc__ = """Build "large" wav2vec2 model with an extra linear module + +Pre-trained on 960 hours of unlabeled audio from *LibriSpeech* dataset [:footcite:`7178964`] +(the combination of "train-clean-100", "train-clean-360", and "train-other-500"), and +fine-tuned for ASR on 10 minutes of transcribed audio from *Libri-Light* dataset +[:footcite:`librilight`] ("train-10min" subset). + +Originally published by the authors of *wav2vec 2.0* [:footcite:`baevski2020wav2vec`] under MIT License and +redistributed with the same license. +[`License `__, +`Source `__] + +Please refer to :func:`torchaudio.pipelines.Wav2Vec2ASRBundle` for the usage. +""" # noqa: E501 + +WAV2VEC2_ASR_LARGE_100H = Wav2Vec2ASRBundle( + 'wav2vec2_fairseq_large_ls960_asr_ls100.pth', + { + "extractor_mode": "group_norm", + "extractor_conv_layer_config": [ + (512, 10, 5), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 2, 2), + (512, 2, 2), + ], + "extractor_conv_bias": False, + "encoder_embed_dim": 1024, + "encoder_projection_dropout": 0.1, + "encoder_pos_conv_kernel": 128, + "encoder_pos_conv_groups": 16, + "encoder_num_layers": 24, + "encoder_num_heads": 16, + "encoder_attention_dropout": 0.1, + "encoder_ff_interm_features": 4096, + "encoder_ff_interm_dropout": 0.0, + "encoder_dropout": 0.0, + "encoder_layer_norm_first": False, + "encoder_layer_drop": 0.2, + "aux_num_out": 32, + }, + _labels=_get_labels(), + _sample_rate=16000, +) +WAV2VEC2_ASR_LARGE_100H.__doc__ = """Build "large" wav2vec2 model with an extra linear module + +Pre-trained on 960 hours of unlabeled audio from *LibriSpeech* dataset [:footcite:`7178964`] +(the combination of "train-clean-100", "train-clean-360", and "train-other-500"), and +fine-tuned for ASR on 100 hours of transcribed audio from +the same dataset ("train-clean-100" subset). + +Originally published by the authors of *wav2vec 2.0* [:footcite:`baevski2020wav2vec`] under MIT License and +redistributed with the same license. +[`License `__, +`Source `__] + +Please refer to :func:`torchaudio.pipelines.Wav2Vec2ASRBundle` for the usage. +""" # noqa: E501 + +WAV2VEC2_ASR_LARGE_960H = Wav2Vec2ASRBundle( + 'wav2vec2_fairseq_large_ls960_asr_ls960.pth', + { + "extractor_mode": "group_norm", + "extractor_conv_layer_config": [ + (512, 10, 5), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 2, 2), + (512, 2, 2), + ], + "extractor_conv_bias": False, + "encoder_embed_dim": 1024, + "encoder_projection_dropout": 0.1, + "encoder_pos_conv_kernel": 128, + "encoder_pos_conv_groups": 16, + "encoder_num_layers": 24, + "encoder_num_heads": 16, + "encoder_attention_dropout": 0.1, + "encoder_ff_interm_features": 4096, + "encoder_ff_interm_dropout": 0.0, + "encoder_dropout": 0.0, + "encoder_layer_norm_first": False, + "encoder_layer_drop": 0.2, + "aux_num_out": 32, + }, + _labels=_get_labels(), + _sample_rate=16000, +) +WAV2VEC2_ASR_LARGE_960H.__doc__ = """Build "large" wav2vec2 model with an extra linear module + +Pre-trained on 960 hours of unlabeled audio from *LibriSpeech* dataset [:footcite:`7178964`] +(the combination of "train-clean-100", "train-clean-360", and "train-other-500"), and +fine-tuned for ASR on the same audio with the corresponding transcripts. + +Originally published by the authors of *wav2vec 2.0* [:footcite:`baevski2020wav2vec`] under MIT License and +redistributed with the same license. +[`License `__, +`Source `__] + +Please refer to :func:`torchaudio.pipelines.Wav2Vec2ASRBundle` for the usage. +""" # noqa: E501 + +WAV2VEC2_LARGE_LV60K = Wav2Vec2Bundle( + 'wav2vec2_fairseq_large_lv60k.pth', + { + "extractor_mode": "layer_norm", + "extractor_conv_layer_config": [ + (512, 10, 5), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 2, 2), + (512, 2, 2), + ], + "extractor_conv_bias": True, + "encoder_embed_dim": 1024, + "encoder_projection_dropout": 0.1, + "encoder_pos_conv_kernel": 128, + "encoder_pos_conv_groups": 16, + "encoder_num_layers": 24, + "encoder_num_heads": 16, + "encoder_attention_dropout": 0.1, + "encoder_ff_interm_features": 4096, + "encoder_ff_interm_dropout": 0.0, + "encoder_dropout": 0.0, + "encoder_layer_norm_first": True, + "encoder_layer_drop": 0.0, + "aux_num_out": None, + }, + _sample_rate=16000, +) +WAV2VEC2_LARGE_LV60K.__doc__ = """Build "large-lv60k" wav2vec2 model. + +Pre-trained on 60,000 hours of unlabeled audio from +*Libri-Light* dataset [:footcite:`librilight`]. +Not fine-tuned. + +Originally published by the authors of *wav2vec 2.0* [:footcite:`baevski2020wav2vec`] under MIT License and +redistributed with the same license. +[`License `__, +`Source `__] + +Please refer to :func:`torchaudio.pipelines.Wav2Vec2Bundle` for the usage. +""" # noqa: E501 + +WAV2VEC2_ASR_LARGE_LV60K_10M = Wav2Vec2ASRBundle( + 'wav2vec2_fairseq_large_lv60k_asr_ll10m.pth', + { + "extractor_mode": "layer_norm", + "extractor_conv_layer_config": [ + (512, 10, 5), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 2, 2), + (512, 2, 2), + ], + "extractor_conv_bias": True, + "encoder_embed_dim": 1024, + "encoder_projection_dropout": 0.1, + "encoder_pos_conv_kernel": 128, + "encoder_pos_conv_groups": 16, + "encoder_num_layers": 24, + "encoder_num_heads": 16, + "encoder_attention_dropout": 0.1, + "encoder_ff_interm_features": 4096, + "encoder_ff_interm_dropout": 0.0, + "encoder_dropout": 0.0, + "encoder_layer_norm_first": True, + "encoder_layer_drop": 0.0, + "aux_num_out": 32, + }, + _labels=_get_labels(), + _sample_rate=16000, +) +WAV2VEC2_ASR_LARGE_LV60K_10M.__doc__ = """Build "large-lv60k" wav2vec2 model with an extra linear module + +Pre-trained on 60,000 hours of unlabeled audio from +*Libri-Light* dataset [:footcite:`librilight`], and +fine-tuned for ASR on 10 minutes of transcribed audio from +the same dataset ("train-10min" subset). + +Originally published by the authors of *wav2vec 2.0* [:footcite:`baevski2020wav2vec`] under MIT License and +redistributed with the same license. +[`License `__, +`Source `__] + +Please refer to :func:`torchaudio.pipelines.Wav2Vec2ASRBundle` for the usage. +""" # noqa: E501 + +WAV2VEC2_ASR_LARGE_LV60K_100H = Wav2Vec2ASRBundle( + 'wav2vec2_fairseq_large_lv60k_asr_ls100.pth', + { + "extractor_mode": "layer_norm", + "extractor_conv_layer_config": [ + (512, 10, 5), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 2, 2), + (512, 2, 2), + ], + "extractor_conv_bias": True, + "encoder_embed_dim": 1024, + "encoder_projection_dropout": 0.1, + "encoder_pos_conv_kernel": 128, + "encoder_pos_conv_groups": 16, + "encoder_num_layers": 24, + "encoder_num_heads": 16, + "encoder_attention_dropout": 0.1, + "encoder_ff_interm_features": 4096, + "encoder_ff_interm_dropout": 0.0, + "encoder_dropout": 0.0, + "encoder_layer_norm_first": True, + "encoder_layer_drop": 0.0, + "aux_num_out": 32, + }, + _labels=_get_labels(), + _sample_rate=16000, +) +WAV2VEC2_ASR_LARGE_LV60K_100H.__doc__ = """Build "large-lv60k" wav2vec2 model with an extra linear module + +Pre-trained on 60,000 hours of unlabeled audio from +*Libri-Light* dataset [:footcite:`librilight`], and +fine-tuned for ASR on 100 hours of transcribed audio from +*LibriSpeech* dataset [:footcite:`7178964`] ("train-clean-100" subset). + +Originally published by the authors of *wav2vec 2.0* [:footcite:`baevski2020wav2vec`] under MIT License and +redistributed with the same license. +[`License `__, +`Source `__] + +Please refer to :func:`torchaudio.pipelines.Wav2Vec2ASRBundle` for the usage. +""" # noqa: E501 + +WAV2VEC2_ASR_LARGE_LV60K_960H = Wav2Vec2ASRBundle( + 'wav2vec2_fairseq_large_lv60k_asr_ls960.pth', + { + "extractor_mode": "layer_norm", + "extractor_conv_layer_config": [ + (512, 10, 5), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 2, 2), + (512, 2, 2), + ], + "extractor_conv_bias": True, + "encoder_embed_dim": 1024, + "encoder_projection_dropout": 0.1, + "encoder_pos_conv_kernel": 128, + "encoder_pos_conv_groups": 16, + "encoder_num_layers": 24, + "encoder_num_heads": 16, + "encoder_attention_dropout": 0.1, + "encoder_ff_interm_features": 4096, + "encoder_ff_interm_dropout": 0.0, + "encoder_dropout": 0.0, + "encoder_layer_norm_first": True, + "encoder_layer_drop": 0.0, + "aux_num_out": 32, + }, + _labels=_get_labels(), + _sample_rate=16000, +) +WAV2VEC2_ASR_LARGE_LV60K_960H.__doc__ = """Build "large-lv60k" wav2vec2 model with an extra linear module + +Pre-trained on 60,000 hours of unlabeled audio from *Libri-Light* +[:footcite:`librilight`] dataset, and +fine-tuned for ASR on 960 hours of transcribed audio from +*LibriSpeech* dataset [:footcite:`7178964`] +(the combination of "train-clean-100", "train-clean-360", and "train-other-500"). + +Originally published by the authors of *wav2vec 2.0* [:footcite:`baevski2020wav2vec`] under MIT License and +redistributed with the same license. +[`License `__, +`Source `__] + +Please refer to :func:`torchaudio.pipelines.Wav2Vec2ASRBundle` for the usage. +""" # noqa: E501 + +WAV2VEC2_XLSR53 = Wav2Vec2Bundle( + 'wav2vec2_fairseq_large_xlsr53.pth', + { + "extractor_mode": "layer_norm", + "extractor_conv_layer_config": [ + (512, 10, 5), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 2, 2), + (512, 2, 2), + ], + "extractor_conv_bias": True, + "encoder_embed_dim": 1024, + "encoder_projection_dropout": 0.0, + "encoder_pos_conv_kernel": 128, + "encoder_pos_conv_groups": 16, + "encoder_num_layers": 24, + "encoder_num_heads": 16, + "encoder_attention_dropout": 0.0, + "encoder_ff_interm_features": 4096, + "encoder_ff_interm_dropout": 0.0, + "encoder_dropout": 0.0, + "encoder_layer_norm_first": True, + "encoder_layer_drop": 0.0, + "aux_num_out": None, + }, + _sample_rate=16000, +) +WAV2VEC2_XLSR53.__doc__ = """wav2vec 2.0 model with "Base" configuration. + +Trained on 56,000 hours of unlabeled audio from multiple datasets ( +*Multilingual LibriSpeech* [:footcite:`Pratap_2020`], +*CommonVoice* [:footcite:`ardila2020common`] and +*BABEL* [:footcite:`Gales2014SpeechRA`]). +Not fine-tuned. + +Originally published by the authors of +*Unsupervised Cross-lingual Representation Learning for Speech Recognition* +[:footcite:`conneau2020unsupervised`] under MIT License and redistributed with the same license. +[`License `__, +`Source `__] + +Please refer to :func:`torchaudio.pipelines.Wav2Vec2Bundle` for the usage. +""" # noqa: E501 + +HUBERT_BASE = Wav2Vec2Bundle( + 'hubert_fairseq_base_ls960.pth', + { + 'extractor_mode': 'group_norm', + 'extractor_conv_layer_config': [ + (512, 10, 5), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 2, 2), + (512, 2, 2), + ], + 'extractor_conv_bias': False, + 'encoder_embed_dim': 768, + 'encoder_projection_dropout': 0.1, + 'encoder_pos_conv_kernel': 128, + 'encoder_pos_conv_groups': 16, + 'encoder_num_layers': 12, + 'encoder_num_heads': 12, + 'encoder_attention_dropout': 0.1, + 'encoder_ff_interm_features': 3072, + 'encoder_ff_interm_dropout': 0.0, + 'encoder_dropout': 0.1, + 'encoder_layer_norm_first': False, + 'encoder_layer_drop': 0.05, + 'aux_num_out': None, + }, + _sample_rate=16000, +) +HUBERT_BASE.__doc__ = """HuBERT model with "Base" configuration. + +Pre-trained on 960 hours of unlabeled audio from *LibriSpeech* dataset [:footcite:`7178964`] +(the combination of "train-clean-100", "train-clean-360", and "train-other-500"). +Not fine-tuned. + +Originally published by the authors of *HuBERT* [:footcite:`hsu2021hubert`] under MIT License and +redistributed with the same license. +[`License `__, +`Source `__] + +Please refer to :func:`torchaudio.pipelines.Wav2Vec2Bundle` for the usage. +""" # noqa: E501 + +HUBERT_LARGE = Wav2Vec2Bundle( + 'hubert_fairseq_large_ll60k.pth', + { + 'extractor_mode': 'layer_norm', + 'extractor_conv_layer_config': [ + (512, 10, 5), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 2, 2), + (512, 2, 2), + ], + 'extractor_conv_bias': False, + 'encoder_embed_dim': 1024, + 'encoder_projection_dropout': 0.0, + 'encoder_pos_conv_kernel': 128, + 'encoder_pos_conv_groups': 16, + 'encoder_num_layers': 24, + 'encoder_num_heads': 16, + 'encoder_attention_dropout': 0.0, + 'encoder_ff_interm_features': 4096, + 'encoder_ff_interm_dropout': 0.0, + 'encoder_dropout': 0.0, + 'encoder_layer_norm_first': True, + 'encoder_layer_drop': 0.0, + 'aux_num_out': None, + }, + _sample_rate=16000, +) +HUBERT_LARGE.__doc__ = """HuBERT model with "Large" configuration. + +Pre-trained on 60,000 hours of unlabeled audio from +*Libri-Light* dataset [:footcite:`librilight`]. +Not fine-tuned. + +Originally published by the authors of *HuBERT* [:footcite:`hsu2021hubert`] under MIT License and +redistributed with the same license. +[`License `__, +`Source `__] + +Please refer to :func:`torchaudio.pipelines.Wav2Vec2Bundle` for the usage. +""" # noqa: E501 + +HUBERT_XLARGE = Wav2Vec2Bundle( + 'hubert_fairseq_xlarge_ll60k.pth', + { + 'extractor_mode': 'layer_norm', + 'extractor_conv_layer_config': [ + (512, 10, 5), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 2, 2), + (512, 2, 2), + ], + 'extractor_conv_bias': False, + 'encoder_embed_dim': 1280, + 'encoder_projection_dropout': 0.0, + 'encoder_pos_conv_kernel': 128, + 'encoder_pos_conv_groups': 16, + 'encoder_num_layers': 48, + 'encoder_num_heads': 16, + 'encoder_attention_dropout': 0.0, + 'encoder_ff_interm_features': 5120, + 'encoder_ff_interm_dropout': 0.0, + 'encoder_dropout': 0.0, + 'encoder_layer_norm_first': True, + 'encoder_layer_drop': 0.0, + 'aux_num_out': None, + }, + _sample_rate=16000, +) +HUBERT_XLARGE.__doc__ = """HuBERT model with "Extra Large" configuration. + +Pre-trained on 60,000 hours of unlabeled audio from +*Libri-Light* dataset [:footcite:`librilight`]. +Not fine-tuned. + +Originally published by the authors of *HuBERT* [:footcite:`hsu2021hubert`] under MIT License and +redistributed with the same license. +[`License `__, +`Source `__] + +Please refer to :func:`torchaudio.pipelines.Wav2Vec2Bundle` for the usage. +""" # noqa: E501 + +HUBERT_ASR_LARGE = Wav2Vec2ASRBundle( + 'hubert_fairseq_large_ll60k_asr_ls960.pth', + { + 'extractor_mode': 'layer_norm', + 'extractor_conv_layer_config': [ + (512, 10, 5), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 2, 2), + (512, 2, 2), + ], + 'extractor_conv_bias': False, + 'encoder_embed_dim': 1024, + 'encoder_projection_dropout': 0.0, + 'encoder_pos_conv_kernel': 128, + 'encoder_pos_conv_groups': 16, + 'encoder_num_layers': 24, + 'encoder_num_heads': 16, + 'encoder_attention_dropout': 0.0, + 'encoder_ff_interm_features': 4096, + 'encoder_ff_interm_dropout': 0.1, + 'encoder_dropout': 0.0, + 'encoder_layer_norm_first': True, + 'encoder_layer_drop': 0.1, + 'aux_num_out': 32, + }, + _labels=_get_labels(), + _sample_rate=16000, +) +HUBERT_ASR_LARGE.__doc__ = """HuBERT model with "Large" configuration. + +Pre-trained on 60,000 hours of unlabeled audio from +*Libri-Light* dataset [:footcite:`librilight`], and +fine-tuned for ASR on 960 hours of transcribed audio from +*LibriSpeech* dataset [:footcite:`7178964`] +(the combination of "train-clean-100", "train-clean-360", and "train-other-500"). + +Originally published by the authors of *HuBERT* [:footcite:`hsu2021hubert`] under MIT License and +redistributed with the same license. +[`License `__, +`Source `__] + +Please refer to :func:`torchaudio.pipelines.Wav2Vec2ASRBundle` for the usage. +""" # noqa: E501 + +HUBERT_ASR_XLARGE = Wav2Vec2ASRBundle( + 'hubert_fairseq_xlarge_ll60k_asr_ls960.pth', + { + 'extractor_mode': 'layer_norm', + 'extractor_conv_layer_config': [ + (512, 10, 5), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 3, 2), + (512, 2, 2), + (512, 2, 2), + ], + 'extractor_conv_bias': False, + 'encoder_embed_dim': 1280, + 'encoder_projection_dropout': 0.0, + 'encoder_pos_conv_kernel': 128, + 'encoder_pos_conv_groups': 16, + 'encoder_num_layers': 48, + 'encoder_num_heads': 16, + 'encoder_attention_dropout': 0.0, + 'encoder_ff_interm_features': 5120, + 'encoder_ff_interm_dropout': 0.1, + 'encoder_dropout': 0.0, + 'encoder_layer_norm_first': True, + 'encoder_layer_drop': 0.1, + 'aux_num_out': 32, + }, + _labels=_get_labels(), + _sample_rate=16000, +) +HUBERT_ASR_XLARGE.__doc__ = """HuBERT model with "Extra Large" configuration. + +Pre-trained on 60,000 hours of unlabeled audio from +*Libri-Light* dataset [:footcite:`librilight`], and +fine-tuned for ASR on 960 hours of transcribed audio from +*LibriSpeech* dataset [:footcite:`7178964`] +(the combination of "train-clean-100", "train-clean-360", and "train-other-500"). + +Originally published by the authors of *HuBERT* [:footcite:`hsu2021hubert`] under MIT License and +redistributed with the same license. +[`License `__, +`Source `__] + +Please refer to :func:`torchaudio.pipelines.Wav2Vec2ASRBundle` for the usage. +""" # noqa: E501 diff --git a/torchaudio/prototype/__init__.py b/torchaudio/prototype/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/torchaudio/sox_effects/__init__.py b/torchaudio/sox_effects/__init__.py new file mode 100644 index 00000000..3c46cebd --- /dev/null +++ b/torchaudio/sox_effects/__init__.py @@ -0,0 +1,22 @@ +from torchaudio._internal import module_utils as _mod_utils +from .sox_effects import ( + init_sox_effects, + shutdown_sox_effects, + effect_names, + apply_effects_tensor, + apply_effects_file, +) + + +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/torchaudio/sox_effects/sox_effects.py b/torchaudio/sox_effects/sox_effects.py new file mode 100644 index 00000000..c17c3568 --- /dev/null +++ b/torchaudio/sox_effects/sox_effects.py @@ -0,0 +1,273 @@ +import os +from typing import List, Tuple, Optional + +import torch + +import torchaudio +from torchaudio._internal import module_utils as _mod_utils +from torchaudio.utils.sox_utils import list_effects + + +@_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. + """ + torch.ops.torchaudio.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. + """ + torch.ops.torchaudio.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 + >>> torchaudio.sox_effects.effect_names() + ['allpass', 'band', 'bandpass', ... ] + """ + return list(list_effects().keys()) + + +@_mod_utils.requires_sox() +def apply_effects_tensor( + tensor: torch.Tensor, + sample_rate: int, + effects: List[List[str]], + channels_first: bool = True, +) -> Tuple[torch.Tensor, int]: + """Apply sox effects to given Tensor + + 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 (torch.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 * torch.rand([2, sample_rate * 1]) - 1 + >>> waveform.shape + torch.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 + torch.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 + + Example - Torchscript-able transform + >>> + >>> # Use `apply_effects_tensor` in `torch.nn.Module` and dump it to file, + >>> # then run sox effect via Torchscript runtime. + >>> + >>> class SoxEffectTransform(torch.nn.Module): + ... effects: List[List[str]] + ... + ... def __init__(self, effects: List[List[str]]): + ... super().__init__() + ... self.effects = effects + ... + ... def forward(self, tensor: torch.Tensor, sample_rate: int): + ... return sox_effects.apply_effects_tensor( + ... tensor, sample_rate, self.effects) + ... + ... + >>> # Create transform object + >>> effects = [ + ... ["lowpass", "-1", "300"], # apply single-pole lowpass filter + ... ["rate", "8000"], # change sample rate to 8000 + ... ] + >>> transform = SoxEffectTensorTransform(effects, input_sample_rate) + >>> + >>> # Dump it to file and load + >>> path = 'sox_effect.zip' + >>> torch.jit.script(trans).save(path) + >>> transform = torch.jit.load(path) + >>> + >>>> # Run transform + >>> waveform, input_sample_rate = torchaudio.load("input.wav") + >>> waveform, sample_rate = transform(waveform, input_sample_rate) + >>> assert sample_rate == 8000 + """ + return torch.ops.torchaudio.sox_effects_apply_effects_tensor( + tensor, sample_rate, effects, channels_first) + + +@_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[torch.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): + Source of audio data. When the function is not compiled by TorchScript, + (e.g. ``torch.jit.script``), the following types are accepted: + + * ``path-like``: file path + * ``file-like``: Object with ``read(size: int) -> bytes`` method, + which returns byte string of at most ``size`` length. + + When the function is compiled by TorchScript, only ``str`` type is allowed. + + Note: This argument is intentionally annotated as ``str`` only for + TorchScript compiler compatibility. + 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 + torch.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(torch.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, _ = torchaudio.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 = torch.utils.data.DataLoader(dataset, batch_size=32) + >>> for batch in loader: + >>> pass + """ + if not torch.jit.is_scripting(): + if hasattr(path, 'read'): + return torchaudio._torchaudio.apply_effects_fileobj( + path, effects, normalize, channels_first, format) + path = os.fspath(path) + return torch.ops.torchaudio.sox_effects_apply_effects_file( + path, effects, normalize, channels_first, format) diff --git a/torchaudio/transforms.py b/torchaudio/transforms.py new file mode 100644 index 00000000..eae3a50e --- /dev/null +++ b/torchaudio/transforms.py @@ -0,0 +1,2050 @@ +# -*- coding: utf-8 -*- + +import math +import warnings +from typing import Callable, Optional + +import torch +from torch import Tensor +from torchaudio import functional as F + +from .functional.functional import ( + _get_sinc_resample_kernel, + _apply_sinc_resample_kernel, +) + +__all__ = [ + 'Spectrogram', + 'InverseSpectrogram', + 'GriffinLim', + 'AmplitudeToDB', + 'MelScale', + 'InverseMelScale', + 'MelSpectrogram', + 'MFCC', + 'LFCC', + 'MuLawEncoding', + 'MuLawDecoding', + 'Resample', + 'ComplexNorm', + 'TimeStretch', + 'Fade', + 'FrequencyMasking', + 'TimeMasking', + 'SlidingWindowCmn', + 'Vad', + 'SpectralCentroid', + 'Vol', + 'ComputeDeltas', + 'PitchShift', + 'RNNTLoss', + 'PSD', + 'MVDR', +] + + +class Spectrogram(torch.nn.Module): + r"""Create a spectrogram from a audio signal. + + Args: + n_fft (int, optional): Size of FFT, creates ``n_fft // 2 + 1`` bins. (Default: ``400``) + win_length (int or None, optional): Window size. (Default: ``n_fft``) + hop_length (int or None, optional): Length of hop between STFT windows. (Default: ``win_length // 2``) + pad (int, optional): Two sided padding of signal. (Default: ``0``) + window_fn (Callable[..., Tensor], optional): A function to create a window tensor + that is applied/multiplied to each frame/window. (Default: ``torch.hann_window``) + power (float or None, optional): Exponent for the magnitude spectrogram, + (must be > 0) e.g., 1 for energy, 2 for power, etc. + If None, then the complex spectrum is returned instead. (Default: ``2``) + normalized (bool, optional): Whether to normalize by magnitude after stft. (Default: ``False``) + wkwargs (dict or None, optional): Arguments for window function. (Default: ``None``) + center (bool, optional): whether to pad :attr:`waveform` on both sides so + that the :math:`t`-th frame is centered at time :math:`t \times \text{hop\_length}`. + (Default: ``True``) + pad_mode (string, optional): controls the padding method used when + :attr:`center` is ``True``. (Default: ``"reflect"``) + onesided (bool, optional): controls whether to return half of results to + avoid redundancy (Default: ``True``) + return_complex (bool, optional): + Indicates whether the resulting complex-valued Tensor should be represented with + native complex dtype, such as `torch.cfloat` and `torch.cdouble`, or real dtype + mimicking complex value with an extra dimension for real and imaginary parts. + (See also ``torch.view_as_real``.) + This argument is only effective when ``power=None``. It is ignored for + cases where ``power`` is a number as in those cases, the returned tensor is + power spectrogram, which is a real-valued tensor. + + Example + >>> waveform, sample_rate = torchaudio.load('test.wav', normalize=True) + >>> transform = torchaudio.transforms.Spectrogram(n_fft=800) + >>> spectrogram = transform(waveform) + + """ + __constants__ = ['n_fft', 'win_length', 'hop_length', 'pad', 'power', 'normalized'] + + def __init__(self, + n_fft: int = 400, + win_length: Optional[int] = None, + hop_length: Optional[int] = None, + pad: int = 0, + window_fn: Callable[..., Tensor] = torch.hann_window, + power: Optional[float] = 2., + normalized: bool = False, + wkwargs: Optional[dict] = None, + center: bool = True, + pad_mode: str = "reflect", + onesided: bool = True, + return_complex: bool = True) -> None: + super(Spectrogram, self).__init__() + self.n_fft = n_fft + # number of FFT bins. the returned STFT result will have n_fft // 2 + 1 + # number of frequencies due to onesided=True in torch.stft + self.win_length = win_length if win_length is not None else n_fft + self.hop_length = hop_length if hop_length is not None else self.win_length // 2 + window = window_fn(self.win_length) if wkwargs is None else window_fn(self.win_length, **wkwargs) + self.register_buffer('window', window) + self.pad = pad + self.power = power + self.normalized = normalized + self.center = center + self.pad_mode = pad_mode + self.onesided = onesided + self.return_complex = return_complex + + def forward(self, waveform: Tensor) -> Tensor: + r""" + Args: + waveform (Tensor): Tensor of audio of dimension (..., time). + + Returns: + Tensor: Dimension (..., freq, time), where freq is + ``n_fft // 2 + 1`` where ``n_fft`` is the number of + Fourier bins, and time is the number of window hops (n_frame). + """ + return F.spectrogram( + waveform, + self.pad, + self.window, + self.n_fft, + self.hop_length, + self.win_length, + self.power, + self.normalized, + self.center, + self.pad_mode, + self.onesided, + self.return_complex, + ) + + +class InverseSpectrogram(torch.nn.Module): + r"""Create an inverse spectrogram to recover an audio signal from a spectrogram. + + Args: + n_fft (int, optional): Size of FFT, creates ``n_fft // 2 + 1`` bins. (Default: ``400``) + win_length (int or None, optional): Window size. (Default: ``n_fft``) + hop_length (int or None, optional): Length of hop between STFT windows. (Default: ``win_length // 2``) + pad (int, optional): Two sided padding of signal. (Default: ``0``) + window_fn (Callable[..., Tensor], optional): A function to create a window tensor + that is applied/multiplied to each frame/window. (Default: ``torch.hann_window``) + normalized (bool, optional): Whether the spectrogram was normalized by magnitude after stft. + (Default: ``False``) + wkwargs (dict or None, optional): Arguments for window function. (Default: ``None``) + center (bool, optional): whether the signal in spectrogram was padded on both sides so + that the :math:`t`-th frame is centered at time :math:`t \times \text{hop\_length}`. + (Default: ``True``) + pad_mode (string, optional): controls the padding method used when + :attr:`center` is ``True``. (Default: ``"reflect"``) + onesided (bool, optional): controls whether spectrogram was used to return half of results to + avoid redundancy (Default: ``True``) + + Example + >>> batch, freq, time = 2, 257, 100 + >>> length = 25344 + >>> spectrogram = torch.randn(batch, freq, time, dtype=torch.cdouble) + >>> transform = transforms.InverseSpectrogram(n_fft=512) + >>> waveform = transform(spectrogram, length) + """ + __constants__ = ['n_fft', 'win_length', 'hop_length', 'pad', 'power', 'normalized'] + + def __init__(self, + n_fft: int = 400, + win_length: Optional[int] = None, + hop_length: Optional[int] = None, + pad: int = 0, + window_fn: Callable[..., Tensor] = torch.hann_window, + normalized: bool = False, + wkwargs: Optional[dict] = None, + center: bool = True, + pad_mode: str = "reflect", + onesided: bool = True) -> None: + super(InverseSpectrogram, self).__init__() + self.n_fft = n_fft + # number of FFT bins. the returned STFT result will have n_fft // 2 + 1 + # number of frequencies due to onesided=True in torch.stft + self.win_length = win_length if win_length is not None else n_fft + self.hop_length = hop_length if hop_length is not None else self.win_length // 2 + window = window_fn(self.win_length) if wkwargs is None else window_fn(self.win_length, **wkwargs) + self.register_buffer('window', window) + self.pad = pad + self.normalized = normalized + self.center = center + self.pad_mode = pad_mode + self.onesided = onesided + + def forward(self, spectrogram: Tensor, length: Optional[int] = None) -> Tensor: + r""" + Args: + spectrogram (Tensor): Complex tensor of audio of dimension (..., freq, time). + length (int or None, optional): The output length of the waveform. + + Returns: + Tensor: Dimension (..., time), Least squares estimation of the original signal. + """ + return F.inverse_spectrogram( + spectrogram, + length, + self.pad, + self.window, + self.n_fft, + self.hop_length, + self.win_length, + self.normalized, + self.center, + self.pad_mode, + self.onesided, + ) + + +class GriffinLim(torch.nn.Module): + r"""Compute waveform from a linear scale magnitude spectrogram using the Griffin-Lim transformation. + + Implementation ported from + *librosa* [:footcite:`brian_mcfee-proc-scipy-2015`], *A fast Griffin-Lim algorithm* [:footcite:`6701851`] + and *Signal estimation from modified short-time Fourier transform* [:footcite:`1172092`]. + + Args: + n_fft (int, optional): Size of FFT, creates ``n_fft // 2 + 1`` bins. (Default: ``400``) + n_iter (int, optional): Number of iteration for phase recovery process. (Default: ``32``) + win_length (int or None, optional): Window size. (Default: ``n_fft``) + hop_length (int or None, optional): Length of hop between STFT windows. (Default: ``win_length // 2``) + window_fn (Callable[..., Tensor], optional): A function to create a window tensor + that is applied/multiplied to each frame/window. (Default: ``torch.hann_window``) + power (float, optional): Exponent for the magnitude spectrogram, + (must be > 0) e.g., 1 for energy, 2 for power, etc. (Default: ``2``) + wkwargs (dict or None, optional): Arguments for window function. (Default: ``None``) + momentum (float, optional): The momentum parameter for fast Griffin-Lim. + Setting this to 0 recovers the original Griffin-Lim method. + Values near 1 can lead to faster convergence, but above 1 may not converge. (Default: ``0.99``) + length (int, optional): Array length of the expected output. (Default: ``None``) + rand_init (bool, optional): Initializes phase randomly if True and to zero otherwise. (Default: ``True``) + + Example + >>> batch, freq, time = 2, 257, 100 + >>> spectrogram = torch.randn(batch, freq, time) + >>> transform = transforms.GriffinLim(n_fft=512) + >>> waveform = transform(spectrogram) + """ + __constants__ = ['n_fft', 'n_iter', 'win_length', 'hop_length', 'power', + 'length', 'momentum', 'rand_init'] + + def __init__(self, + n_fft: int = 400, + n_iter: int = 32, + win_length: Optional[int] = None, + hop_length: Optional[int] = None, + window_fn: Callable[..., Tensor] = torch.hann_window, + power: float = 2., + wkwargs: Optional[dict] = None, + momentum: float = 0.99, + length: Optional[int] = None, + rand_init: bool = True) -> None: + super(GriffinLim, self).__init__() + + assert momentum < 1, 'momentum={} > 1 can be unstable'.format(momentum) + assert momentum >= 0, 'momentum={} < 0'.format(momentum) + + self.n_fft = n_fft + self.n_iter = n_iter + self.win_length = win_length if win_length is not None else n_fft + self.hop_length = hop_length if hop_length is not None else self.win_length // 2 + window = window_fn(self.win_length) if wkwargs is None else window_fn(self.win_length, **wkwargs) + self.register_buffer('window', window) + self.length = length + self.power = power + self.momentum = momentum / (1 + momentum) + self.rand_init = rand_init + + def forward(self, specgram: Tensor) -> Tensor: + r""" + Args: + specgram (Tensor): + A magnitude-only STFT spectrogram of dimension (..., freq, frames) + where freq is ``n_fft // 2 + 1``. + + Returns: + Tensor: waveform of (..., time), where time equals the ``length`` parameter if given. + """ + return F.griffinlim(specgram, self.window, self.n_fft, self.hop_length, self.win_length, self.power, + self.n_iter, self.momentum, self.length, self.rand_init) + + +class AmplitudeToDB(torch.nn.Module): + r"""Turn a tensor from the power/amplitude scale to the decibel scale. + + This output depends on the maximum value in the input tensor, and so + may return different values for an audio clip split into snippets vs. a + a full clip. + + Args: + stype (str, optional): scale of input tensor ('power' or 'magnitude'). The + power being the elementwise square of the magnitude. (Default: ``'power'``) + top_db (float or None, optional): minimum negative cut-off in decibels. A reasonable + number is 80. (Default: ``None``) + """ + __constants__ = ['multiplier', 'amin', 'ref_value', 'db_multiplier'] + + def __init__(self, stype: str = 'power', top_db: Optional[float] = None) -> None: + super(AmplitudeToDB, self).__init__() + self.stype = stype + if top_db is not None and top_db < 0: + raise ValueError('top_db must be positive value') + self.top_db = top_db + self.multiplier = 10.0 if stype == 'power' else 20.0 + self.amin = 1e-10 + self.ref_value = 1.0 + self.db_multiplier = math.log10(max(self.amin, self.ref_value)) + + def forward(self, x: Tensor) -> Tensor: + r"""Numerically stable implementation from Librosa. + + https://librosa.org/doc/latest/generated/librosa.amplitude_to_db.html + + Args: + x (Tensor): Input tensor before being converted to decibel scale. + + Returns: + Tensor: Output tensor in decibel scale. + """ + return F.amplitude_to_DB(x, self.multiplier, self.amin, self.db_multiplier, self.top_db) + + +class MelScale(torch.nn.Module): + r"""Turn a normal STFT into a mel frequency STFT, using a conversion + matrix. This uses triangular filter banks. + + User can control which device the filter bank (`fb`) is (e.g. fb.to(spec_f.device)). + + Args: + n_mels (int, optional): Number of mel filterbanks. (Default: ``128``) + sample_rate (int, optional): Sample rate of audio signal. (Default: ``16000``) + f_min (float, optional): Minimum frequency. (Default: ``0.``) + f_max (float or None, optional): Maximum frequency. (Default: ``sample_rate // 2``) + n_stft (int, optional): Number of bins in STFT. See ``n_fft`` in :class:`Spectrogram`. (Default: ``201``) + norm (str or None, optional): If 'slaney', divide the triangular mel weights by the width of the mel band + (area normalization). (Default: ``None``) + mel_scale (str, optional): Scale to use: ``htk`` or ``slaney``. (Default: ``htk``) + + See also: + :py:func:`torchaudio.functional.melscale_fbanks` - The function used to + generate the filter banks. + """ + __constants__ = ['n_mels', 'sample_rate', 'f_min', 'f_max'] + + def __init__(self, + n_mels: int = 128, + sample_rate: int = 16000, + f_min: float = 0., + f_max: Optional[float] = None, + n_stft: int = 201, + norm: Optional[str] = None, + mel_scale: str = "htk") -> None: + super(MelScale, self).__init__() + self.n_mels = n_mels + self.sample_rate = sample_rate + self.f_max = f_max if f_max is not None else float(sample_rate // 2) + self.f_min = f_min + self.norm = norm + self.mel_scale = mel_scale + + assert f_min <= self.f_max, 'Require f_min: {} < f_max: {}'.format(f_min, self.f_max) + fb = F.melscale_fbanks( + n_stft, self.f_min, self.f_max, self.n_mels, self.sample_rate, self.norm, + self.mel_scale) + self.register_buffer('fb', fb) + + def forward(self, specgram: Tensor) -> Tensor: + r""" + Args: + specgram (Tensor): A spectrogram STFT of dimension (..., freq, time). + + Returns: + Tensor: Mel frequency spectrogram of size (..., ``n_mels``, time). + """ + + # (..., time, freq) dot (freq, n_mels) -> (..., n_mels, time) + mel_specgram = torch.matmul(specgram.transpose(-1, -2), self.fb).transpose(-1, -2) + + return mel_specgram + + +class InverseMelScale(torch.nn.Module): + r"""Solve for a normal STFT from a mel frequency STFT, using a conversion + matrix. This uses triangular filter banks. + + It minimizes the euclidian norm between the input mel-spectrogram and the product between + the estimated spectrogram and the filter banks using SGD. + + Args: + n_stft (int): Number of bins in STFT. See ``n_fft`` in :class:`Spectrogram`. + n_mels (int, optional): Number of mel filterbanks. (Default: ``128``) + sample_rate (int, optional): Sample rate of audio signal. (Default: ``16000``) + f_min (float, optional): Minimum frequency. (Default: ``0.``) + f_max (float or None, optional): Maximum frequency. (Default: ``sample_rate // 2``) + max_iter (int, optional): Maximum number of optimization iterations. (Default: ``100000``) + tolerance_loss (float, optional): Value of loss to stop optimization at. (Default: ``1e-5``) + tolerance_change (float, optional): Difference in losses to stop optimization at. (Default: ``1e-8``) + sgdargs (dict or None, optional): Arguments for the SGD optimizer. (Default: ``None``) + norm (str or None, optional): If 'slaney', divide the triangular mel weights by the width of the mel band + (area normalization). (Default: ``None``) + mel_scale (str, optional): Scale to use: ``htk`` or ``slaney``. (Default: ``htk``) + """ + __constants__ = ['n_stft', 'n_mels', 'sample_rate', 'f_min', 'f_max', 'max_iter', 'tolerance_loss', + 'tolerance_change', 'sgdargs'] + + def __init__(self, + n_stft: int, + n_mels: int = 128, + sample_rate: int = 16000, + f_min: float = 0., + f_max: Optional[float] = None, + max_iter: int = 100000, + tolerance_loss: float = 1e-5, + tolerance_change: float = 1e-8, + sgdargs: Optional[dict] = None, + norm: Optional[str] = None, + mel_scale: str = "htk") -> None: + super(InverseMelScale, self).__init__() + self.n_mels = n_mels + self.sample_rate = sample_rate + self.f_max = f_max or float(sample_rate // 2) + self.f_min = f_min + self.max_iter = max_iter + self.tolerance_loss = tolerance_loss + self.tolerance_change = tolerance_change + self.sgdargs = sgdargs or {'lr': 0.1, 'momentum': 0.9} + + assert f_min <= self.f_max, 'Require f_min: {} < f_max: {}'.format(f_min, self.f_max) + + fb = F.melscale_fbanks(n_stft, self.f_min, self.f_max, self.n_mels, self.sample_rate, + norm, mel_scale) + self.register_buffer('fb', fb) + + def forward(self, melspec: Tensor) -> Tensor: + r""" + Args: + melspec (Tensor): A Mel frequency spectrogram of dimension (..., ``n_mels``, time) + + Returns: + Tensor: Linear scale spectrogram of size (..., freq, time) + """ + # pack batch + shape = melspec.size() + melspec = melspec.view(-1, shape[-2], shape[-1]) + + n_mels, time = shape[-2], shape[-1] + freq, _ = self.fb.size() # (freq, n_mels) + melspec = melspec.transpose(-1, -2) + assert self.n_mels == n_mels + + specgram = torch.rand(melspec.size()[0], time, freq, requires_grad=True, + dtype=melspec.dtype, device=melspec.device) + + optim = torch.optim.SGD([specgram], **self.sgdargs) + + loss = float('inf') + for _ in range(self.max_iter): + optim.zero_grad() + diff = melspec - specgram.matmul(self.fb) + new_loss = diff.pow(2).sum(axis=-1).mean() + # take sum over mel-frequency then average over other dimensions + # so that loss threshold is applied par unit timeframe + new_loss.backward() + optim.step() + specgram.data = specgram.data.clamp(min=0) + + new_loss = new_loss.item() + if new_loss < self.tolerance_loss or abs(loss - new_loss) < self.tolerance_change: + break + loss = new_loss + + specgram.requires_grad_(False) + specgram = specgram.clamp(min=0).transpose(-1, -2) + + # unpack batch + specgram = specgram.view(shape[:-2] + (freq, time)) + return specgram + + +class MelSpectrogram(torch.nn.Module): + r"""Create MelSpectrogram for a raw audio signal. + + This is a composition of :py:func:`torchaudio.transforms.Spectrogram` and + and :py:func:`torchaudio.transforms.MelScale`. + + Sources + * https://gist.github.com/kastnerkyle/179d6e9a88202ab0a2fe + * https://timsainb.github.io/spectrograms-mfccs-and-inversion-in-python.html + * http://haythamfayek.com/2016/04/21/speech-processing-for-machine-learning.html + + Args: + sample_rate (int, optional): Sample rate of audio signal. (Default: ``16000``) + n_fft (int, optional): Size of FFT, creates ``n_fft // 2 + 1`` bins. (Default: ``400``) + win_length (int or None, optional): Window size. (Default: ``n_fft``) + hop_length (int or None, optional): Length of hop between STFT windows. (Default: ``win_length // 2``) + f_min (float, optional): Minimum frequency. (Default: ``0.``) + f_max (float or None, optional): Maximum frequency. (Default: ``None``) + pad (int, optional): Two sided padding of signal. (Default: ``0``) + n_mels (int, optional): Number of mel filterbanks. (Default: ``128``) + window_fn (Callable[..., Tensor], optional): A function to create a window tensor + that is applied/multiplied to each frame/window. (Default: ``torch.hann_window``) + power (float, optional): Exponent for the magnitude spectrogram, + (must be > 0) e.g., 1 for energy, 2 for power, etc. (Default: ``2``) + normalized (bool, optional): Whether to normalize by magnitude after stft. (Default: ``False``) + wkwargs (Dict[..., ...] or None, optional): Arguments for window function. (Default: ``None``) + center (bool, optional): whether to pad :attr:`waveform` on both sides so + that the :math:`t`-th frame is centered at time :math:`t \times \text{hop\_length}`. + (Default: ``True``) + pad_mode (string, optional): controls the padding method used when + :attr:`center` is ``True``. (Default: ``"reflect"``) + onesided (bool, optional): controls whether to return half of results to + avoid redundancy. (Default: ``True``) + norm (str or None, optional): If 'slaney', divide the triangular mel weights by the width of the mel band + (area normalization). (Default: ``None``) + mel_scale (str, optional): Scale to use: ``htk`` or ``slaney``. (Default: ``htk``) + + Example + >>> waveform, sample_rate = torchaudio.load('test.wav', normalize=True) + >>> transform = transforms.MelSpectrogram(sample_rate) + >>> mel_specgram = transform(waveform) # (channel, n_mels, time) + + See also: + :py:func:`torchaudio.functional.melscale_fbanks` - The function used to + generate the filter banks. + """ + __constants__ = ['sample_rate', 'n_fft', 'win_length', 'hop_length', 'pad', 'n_mels', 'f_min'] + + def __init__(self, + sample_rate: int = 16000, + n_fft: int = 400, + win_length: Optional[int] = None, + hop_length: Optional[int] = None, + f_min: float = 0., + f_max: Optional[float] = None, + pad: int = 0, + n_mels: int = 128, + window_fn: Callable[..., Tensor] = torch.hann_window, + power: float = 2., + normalized: bool = False, + wkwargs: Optional[dict] = None, + center: bool = True, + pad_mode: str = "reflect", + onesided: bool = True, + norm: Optional[str] = None, + mel_scale: str = "htk") -> None: + super(MelSpectrogram, self).__init__() + self.sample_rate = sample_rate + self.n_fft = n_fft + self.win_length = win_length if win_length is not None else n_fft + self.hop_length = hop_length if hop_length is not None else self.win_length // 2 + self.pad = pad + self.power = power + self.normalized = normalized + self.n_mels = n_mels # number of mel frequency bins + self.f_max = f_max + self.f_min = f_min + self.spectrogram = Spectrogram(n_fft=self.n_fft, win_length=self.win_length, + hop_length=self.hop_length, + pad=self.pad, window_fn=window_fn, power=self.power, + normalized=self.normalized, wkwargs=wkwargs, + center=center, pad_mode=pad_mode, onesided=onesided) + self.mel_scale = MelScale( + self.n_mels, + self.sample_rate, + self.f_min, + self.f_max, + self.n_fft // 2 + 1, + norm, + mel_scale + ) + + def forward(self, waveform: Tensor) -> Tensor: + r""" + Args: + waveform (Tensor): Tensor of audio of dimension (..., time). + + Returns: + Tensor: Mel frequency spectrogram of size (..., ``n_mels``, time). + """ + specgram = self.spectrogram(waveform) + mel_specgram = self.mel_scale(specgram) + return mel_specgram + + +class MFCC(torch.nn.Module): + r"""Create the Mel-frequency cepstrum coefficients from an audio signal. + + By default, this calculates the MFCC on the DB-scaled Mel spectrogram. + This is not the textbook implementation, but is implemented here to + give consistency with librosa. + + This output depends on the maximum value in the input spectrogram, and so + may return different values for an audio clip split into snippets vs. a + a full clip. + + Args: + sample_rate (int, optional): Sample rate of audio signal. (Default: ``16000``) + n_mfcc (int, optional): Number of mfc coefficients to retain. (Default: ``40``) + dct_type (int, optional): type of DCT (discrete cosine transform) to use. (Default: ``2``) + norm (str, optional): norm to use. (Default: ``'ortho'``) + log_mels (bool, optional): whether to use log-mel spectrograms instead of db-scaled. (Default: ``False``) + melkwargs (dict or None, optional): arguments for MelSpectrogram. (Default: ``None``) + + See also: + :py:func:`torchaudio.functional.melscale_fbanks` - The function used to + generate the filter banks. + """ + __constants__ = ['sample_rate', 'n_mfcc', 'dct_type', 'top_db', 'log_mels'] + + def __init__(self, + sample_rate: int = 16000, + n_mfcc: int = 40, + dct_type: int = 2, + norm: str = 'ortho', + log_mels: bool = False, + melkwargs: Optional[dict] = None) -> None: + super(MFCC, self).__init__() + supported_dct_types = [2] + if dct_type not in supported_dct_types: + raise ValueError('DCT type not supported: {}'.format(dct_type)) + self.sample_rate = sample_rate + self.n_mfcc = n_mfcc + self.dct_type = dct_type + self.norm = norm + self.top_db = 80.0 + self.amplitude_to_DB = AmplitudeToDB('power', self.top_db) + + melkwargs = melkwargs or {} + self.MelSpectrogram = MelSpectrogram(sample_rate=self.sample_rate, **melkwargs) + + if self.n_mfcc > self.MelSpectrogram.n_mels: + raise ValueError('Cannot select more MFCC coefficients than # mel bins') + dct_mat = F.create_dct(self.n_mfcc, self.MelSpectrogram.n_mels, self.norm) + self.register_buffer('dct_mat', dct_mat) + self.log_mels = log_mels + + def forward(self, waveform: Tensor) -> Tensor: + r""" + Args: + waveform (Tensor): Tensor of audio of dimension (..., time). + + Returns: + Tensor: specgram_mel_db of size (..., ``n_mfcc``, time). + """ + mel_specgram = self.MelSpectrogram(waveform) + if self.log_mels: + log_offset = 1e-6 + mel_specgram = torch.log(mel_specgram + log_offset) + else: + mel_specgram = self.amplitude_to_DB(mel_specgram) + + # (..., time, n_mels) dot (n_mels, n_mfcc) -> (..., n_nfcc, time) + mfcc = torch.matmul(mel_specgram.transpose(-1, -2), self.dct_mat).transpose(-1, -2) + return mfcc + + +class LFCC(torch.nn.Module): + r"""Create the linear-frequency cepstrum coefficients from an audio signal. + + By default, this calculates the LFCC on the DB-scaled linear filtered spectrogram. + This is not the textbook implementation, but is implemented here to + give consistency with librosa. + + This output depends on the maximum value in the input spectrogram, and so + may return different values for an audio clip split into snippets vs. a + a full clip. + + Args: + sample_rate (int, optional): Sample rate of audio signal. (Default: ``16000``) + n_filter (int, optional): Number of linear filters to apply. (Default: ``128``) + n_lfcc (int, optional): Number of lfc coefficients to retain. (Default: ``40``) + f_min (float, optional): Minimum frequency. (Default: ``0.``) + f_max (float or None, optional): Maximum frequency. (Default: ``None``) + dct_type (int, optional): type of DCT (discrete cosine transform) to use. (Default: ``2``) + norm (str, optional): norm to use. (Default: ``'ortho'``) + log_lf (bool, optional): whether to use log-lf spectrograms instead of db-scaled. (Default: ``False``) + speckwargs (dict or None, optional): arguments for Spectrogram. (Default: ``None``) + + + See also: + :py:func:`torchaudio.functional.linear_fbanks` - The function used to + generate the filter banks. + """ + __constants__ = ['sample_rate', 'n_filter', 'n_lfcc', 'dct_type', 'top_db', 'log_lf'] + + def __init__(self, + sample_rate: int = 16000, + n_filter: int = 128, + f_min: float = 0., + f_max: Optional[float] = None, + n_lfcc: int = 40, + dct_type: int = 2, + norm: str = 'ortho', + log_lf: bool = False, + speckwargs: Optional[dict] = None) -> None: + super(LFCC, self).__init__() + supported_dct_types = [2] + if dct_type not in supported_dct_types: + raise ValueError('DCT type not supported: {}'.format(dct_type)) + self.sample_rate = sample_rate + self.f_min = f_min + self.f_max = f_max if f_max is not None else float(sample_rate // 2) + self.n_filter = n_filter + self.n_lfcc = n_lfcc + self.dct_type = dct_type + self.norm = norm + self.top_db = 80.0 + self.amplitude_to_DB = AmplitudeToDB('power', self.top_db) + + speckwargs = speckwargs or {} + self.Spectrogram = Spectrogram(**speckwargs) + + if self.n_lfcc > self.Spectrogram.n_fft: + raise ValueError('Cannot select more LFCC coefficients than # fft bins') + + filter_mat = F.linear_fbanks( + n_freqs=self.Spectrogram.n_fft // 2 + 1, + f_min=self.f_min, + f_max=self.f_max, + n_filter=self.n_filter, + sample_rate=self.sample_rate, + ) + self.register_buffer("filter_mat", filter_mat) + + dct_mat = F.create_dct(self.n_lfcc, self.n_filter, self.norm) + self.register_buffer('dct_mat', dct_mat) + self.log_lf = log_lf + + def forward(self, waveform: Tensor) -> Tensor: + r""" + Args: + waveform (Tensor): Tensor of audio of dimension (..., time). + + Returns: + Tensor: Linear Frequency Cepstral Coefficients of size (..., ``n_lfcc``, time). + """ + specgram = self.Spectrogram(waveform) + + # (..., time, freq) dot (freq, n_filter) -> (..., n_filter, time) + specgram = torch.matmul(specgram.transpose(-1, -2), self.filter_mat).transpose(-1, -2) + + if self.log_lf: + log_offset = 1e-6 + specgram = torch.log(specgram + log_offset) + else: + specgram = self.amplitude_to_DB(specgram) + + # (..., time, n_filter) dot (n_filter, n_lfcc) -> (..., n_lfcc, time) + lfcc = torch.matmul(specgram.transpose(-1, -2), self.dct_mat).transpose(-1, -2) + return lfcc + + +class MuLawEncoding(torch.nn.Module): + r"""Encode signal based on mu-law companding. For more info see the + `Wikipedia Entry `_ + + This algorithm assumes the signal has been scaled to between -1 and 1 and + returns a signal encoded with values from 0 to quantization_channels - 1 + + Args: + quantization_channels (int, optional): Number of channels. (Default: ``256``) + + Example + >>> waveform, sample_rate = torchaudio.load('test.wav', normalize=True) + >>> transform = torchaudio.transforms.MuLawEncoding(quantization_channels=512) + >>> mulawtrans = transform(waveform) + + """ + __constants__ = ['quantization_channels'] + + def __init__(self, quantization_channels: int = 256) -> None: + super(MuLawEncoding, self).__init__() + self.quantization_channels = quantization_channels + + def forward(self, x: Tensor) -> Tensor: + r""" + Args: + x (Tensor): A signal to be encoded. + + Returns: + Tensor: An encoded signal. + """ + return F.mu_law_encoding(x, self.quantization_channels) + + +class MuLawDecoding(torch.nn.Module): + r"""Decode mu-law encoded signal. For more info see the + `Wikipedia Entry `_ + + This expects an input with values between 0 and quantization_channels - 1 + and returns a signal scaled between -1 and 1. + + Args: + quantization_channels (int, optional): Number of channels. (Default: ``256``) + + Example + >>> waveform, sample_rate = torchaudio.load('test.wav', normalize=True) + >>> transform = torchaudio.transforms.MuLawDecoding(quantization_channels=512) + >>> mulawtrans = transform(waveform) + """ + __constants__ = ['quantization_channels'] + + def __init__(self, quantization_channels: int = 256) -> None: + super(MuLawDecoding, self).__init__() + self.quantization_channels = quantization_channels + + def forward(self, x_mu: Tensor) -> Tensor: + r""" + Args: + x_mu (Tensor): A mu-law encoded signal which needs to be decoded. + + Returns: + Tensor: The signal decoded. + """ + return F.mu_law_decoding(x_mu, self.quantization_channels) + + +class Resample(torch.nn.Module): + r"""Resample a signal from one frequency to another. A resampling method can be given. + + Note: + If resampling on waveforms of higher precision than float32, there may be a small loss of precision + because the kernel is cached once as float32. If high precision resampling is important for your application, + the functional form will retain higher precision, but run slower because it does not cache the kernel. + Alternatively, you could rewrite a transform that caches a higher precision kernel. + + Args: + orig_freq (int, optional): The original frequency of the signal. (Default: ``16000``) + new_freq (int, optional): The desired frequency. (Default: ``16000``) + resampling_method (str, optional): The resampling method to use. + Options: [``sinc_interpolation``, ``kaiser_window``] (Default: ``'sinc_interpolation'``) + lowpass_filter_width (int, optional): Controls the sharpness of the filter, more == sharper + but less efficient. (Default: ``6``) + rolloff (float, optional): The roll-off frequency of the filter, as a fraction of the Nyquist. + Lower values reduce anti-aliasing, but also reduce some of the highest frequencies. (Default: ``0.99``) + beta (float or None, optional): The shape parameter used for kaiser window. + dtype (torch.device, optional): + Determnines the precision that resampling kernel is pre-computed and cached. If not provided, + kernel is computed with ``torch.float64`` then cached as ``torch.float32``. + If you need higher precision, provide ``torch.float64``, and the pre-computed kernel is computed and + cached as ``torch.float64``. If you use resample with lower precision, then instead of providing this + providing this argument, please use ``Resample.to(dtype)``, so that the kernel generation is still + carried out on ``torch.float64``. + + Example + >>> waveform, sample_rate = torchaudio.load('test.wav', normalize=True) + >>> transform = transforms.Resample(sample_rate, sample_rate/10) + >>> waveform = transform(waveform) + """ + + def __init__( + self, + orig_freq: int = 16000, + new_freq: int = 16000, + resampling_method: str = 'sinc_interpolation', + lowpass_filter_width: int = 6, + rolloff: float = 0.99, + beta: Optional[float] = None, + *, + dtype: Optional[torch.dtype] = None, + ) -> None: + super().__init__() + + self.orig_freq = orig_freq + self.new_freq = new_freq + self.gcd = math.gcd(int(self.orig_freq), int(self.new_freq)) + self.resampling_method = resampling_method + self.lowpass_filter_width = lowpass_filter_width + self.rolloff = rolloff + self.beta = beta + + if self.orig_freq != self.new_freq: + kernel, self.width = _get_sinc_resample_kernel( + self.orig_freq, self.new_freq, self.gcd, + self.lowpass_filter_width, self.rolloff, + self.resampling_method, beta, dtype=dtype) + self.register_buffer('kernel', kernel) + + def forward(self, waveform: Tensor) -> Tensor: + r""" + Args: + waveform (Tensor): Tensor of audio of dimension (..., time). + + Returns: + Tensor: Output signal of dimension (..., time). + """ + if self.orig_freq == self.new_freq: + return waveform + return _apply_sinc_resample_kernel( + waveform, self.orig_freq, self.new_freq, self.gcd, + self.kernel, self.width) + + +class ComplexNorm(torch.nn.Module): + r"""Compute the norm of complex tensor input. + + Args: + power (float, optional): Power of the norm. (Default: to ``1.0``) + + Example + >>> complex_tensor = ... # Tensor shape of (…, complex=2) + >>> transform = transforms.ComplexNorm(power=2) + >>> complex_norm = transform(complex_tensor) + """ + __constants__ = ['power'] + + def __init__(self, power: float = 1.0) -> None: + warnings.warn( + 'torchaudio.transforms.ComplexNorm has been deprecated ' + 'and will be removed from future release.' + 'Please convert the input Tensor to complex type with `torch.view_as_complex` then ' + 'use `torch.abs` and `torch.angle`. ' + 'Please refer to https://github.com/pytorch/audio/issues/1337 ' + "for more details about torchaudio's plan to migrate to native complex type." + ) + super(ComplexNorm, self).__init__() + self.power = power + + def forward(self, complex_tensor: Tensor) -> Tensor: + r""" + Args: + complex_tensor (Tensor): Tensor shape of `(..., complex=2)`. + + Returns: + Tensor: norm of the input tensor, shape of `(..., )`. + """ + return F.complex_norm(complex_tensor, self.power) + + +class ComputeDeltas(torch.nn.Module): + r"""Compute delta coefficients of a tensor, usually a spectrogram. + + See `torchaudio.functional.compute_deltas` for more details. + + Args: + win_length (int, optional): The window length used for computing delta. (Default: ``5``) + mode (str, optional): Mode parameter passed to padding. (Default: ``'replicate'``) + """ + __constants__ = ['win_length'] + + def __init__(self, win_length: int = 5, mode: str = "replicate") -> None: + super(ComputeDeltas, self).__init__() + self.win_length = win_length + self.mode = mode + + def forward(self, specgram: Tensor) -> Tensor: + r""" + Args: + specgram (Tensor): Tensor of audio of dimension (..., freq, time). + + Returns: + Tensor: Tensor of deltas of dimension (..., freq, time). + """ + return F.compute_deltas(specgram, win_length=self.win_length, mode=self.mode) + + +class TimeStretch(torch.nn.Module): + r"""Stretch stft in time without modifying pitch for a given rate. + + Proposed in *SpecAugment* [:footcite:`specaugment`]. + + Args: + hop_length (int or None, optional): Length of hop between STFT windows. (Default: ``win_length // 2``) + n_freq (int, optional): number of filter banks from stft. (Default: ``201``) + fixed_rate (float or None, optional): rate to speed up or slow down by. + If None is provided, rate must be passed to the forward method. (Default: ``None``) + + Example + >>> spectrogram = torchaudio.transforms.Spectrogram() + >>> stretch = torchaudio.transforms.TimeStretch() + >>> + >>> original = spectrogram(waveform) + >>> streched_1_2 = stretch(original, 1.2) + >>> streched_0_9 = stretch(original, 0.9) + + .. image:: https://download.pytorch.org/torchaudio/doc-assets/specaugment_time_stretch_1.png + :width: 600 + :alt: Spectrogram streched by 1.2 + + .. image:: https://download.pytorch.org/torchaudio/doc-assets/specaugment_time_stretch_2.png + :width: 600 + :alt: The original spectrogram + + .. image:: https://download.pytorch.org/torchaudio/doc-assets/specaugment_time_stretch_3.png + :width: 600 + :alt: Spectrogram streched by 0.9 + + """ + __constants__ = ['fixed_rate'] + + def __init__(self, + hop_length: Optional[int] = None, + n_freq: int = 201, + fixed_rate: Optional[float] = None) -> None: + super(TimeStretch, self).__init__() + + self.fixed_rate = fixed_rate + + n_fft = (n_freq - 1) * 2 + hop_length = hop_length if hop_length is not None else n_fft // 2 + self.register_buffer('phase_advance', torch.linspace(0, math.pi * hop_length, n_freq)[..., None]) + + def forward(self, complex_specgrams: Tensor, overriding_rate: Optional[float] = None) -> Tensor: + r""" + Args: + complex_specgrams (Tensor): + Either a real tensor of dimension of `(..., freq, num_frame, complex=2)` + or a tensor of dimension `(..., freq, num_frame)` with complex dtype. + overriding_rate (float or None, optional): speed up to apply to this batch. + If no rate is passed, use ``self.fixed_rate``. (Default: ``None``) + + Returns: + Tensor: + Stretched spectrogram. The resulting tensor is of the same dtype as the input + spectrogram, but the number of frames is changed to ``ceil(num_frame / rate)``. + """ + if overriding_rate is None: + if self.fixed_rate is None: + raise ValueError( + "If no fixed_rate is specified, must pass a valid rate to the forward method.") + rate = self.fixed_rate + else: + rate = overriding_rate + return F.phase_vocoder(complex_specgrams, rate, self.phase_advance) + + +class Fade(torch.nn.Module): + r"""Add a fade in and/or fade out to an waveform. + + Args: + fade_in_len (int, optional): Length of fade-in (time frames). (Default: ``0``) + fade_out_len (int, optional): Length of fade-out (time frames). (Default: ``0``) + fade_shape (str, optional): Shape of fade. Must be one of: "quarter_sine", + "half_sine", "linear", "logarithmic", "exponential". (Default: ``"linear"``) + + Example + >>> waveform, sample_rate = torchaudio.load('test.wav', normalize=True) + >>> transform = transforms.Fade(fade_in_len=sample_rate, fade_out_len=2 * sample_rate, fade_shape='linear') + >>> faded_waveform = transform(waveform) + """ + + def __init__(self, + fade_in_len: int = 0, + fade_out_len: int = 0, + fade_shape: str = "linear") -> None: + super(Fade, self).__init__() + self.fade_in_len = fade_in_len + self.fade_out_len = fade_out_len + self.fade_shape = fade_shape + + def forward(self, waveform: Tensor) -> Tensor: + r""" + Args: + waveform (Tensor): Tensor of audio of dimension `(..., time)`. + + Returns: + Tensor: Tensor of audio of dimension `(..., time)`. + """ + waveform_length = waveform.size()[-1] + device = waveform.device + return ( + self._fade_in(waveform_length, device) + * self._fade_out(waveform_length, device) + * waveform + ) + + def _fade_in(self, waveform_length: int, device: torch.device) -> Tensor: + fade = torch.linspace(0, 1, self.fade_in_len, device=device) + ones = torch.ones(waveform_length - self.fade_in_len, device=device) + + if self.fade_shape == "linear": + fade = fade + + if self.fade_shape == "exponential": + fade = torch.pow(2, (fade - 1)) * fade + + if self.fade_shape == "logarithmic": + fade = torch.log10(.1 + fade) + 1 + + if self.fade_shape == "quarter_sine": + fade = torch.sin(fade * math.pi / 2) + + if self.fade_shape == "half_sine": + fade = torch.sin(fade * math.pi - math.pi / 2) / 2 + 0.5 + + return torch.cat((fade, ones)).clamp_(0, 1) + + def _fade_out(self, waveform_length: int, device: torch.device) -> Tensor: + fade = torch.linspace(0, 1, self.fade_out_len, device=device) + ones = torch.ones(waveform_length - self.fade_out_len, device=device) + + if self.fade_shape == "linear": + fade = - fade + 1 + + if self.fade_shape == "exponential": + fade = torch.pow(2, - fade) * (1 - fade) + + if self.fade_shape == "logarithmic": + fade = torch.log10(1.1 - fade) + 1 + + if self.fade_shape == "quarter_sine": + fade = torch.sin(fade * math.pi / 2 + math.pi / 2) + + if self.fade_shape == "half_sine": + fade = torch.sin(fade * math.pi + math.pi / 2) / 2 + 0.5 + + return torch.cat((ones, fade)).clamp_(0, 1) + + +class _AxisMasking(torch.nn.Module): + r"""Apply masking to a spectrogram. + + Args: + mask_param (int): Maximum possible length of the mask. + axis (int): What dimension the mask is applied on. + iid_masks (bool): Applies iid masks to each of the examples in the batch dimension. + This option is applicable only when the input tensor is 4D. + """ + __constants__ = ['mask_param', 'axis', 'iid_masks'] + + def __init__(self, mask_param: int, axis: int, iid_masks: bool) -> None: + + super(_AxisMasking, self).__init__() + self.mask_param = mask_param + self.axis = axis + self.iid_masks = iid_masks + + def forward(self, specgram: Tensor, mask_value: float = 0.) -> Tensor: + r""" + Args: + specgram (Tensor): Tensor of dimension `(..., freq, time)`. + mask_value (float): Value to assign to the masked columns. + + Returns: + Tensor: Masked spectrogram of dimensions `(..., freq, time)`. + """ + # if iid_masks flag marked and specgram has a batch dimension + if self.iid_masks and specgram.dim() == 4: + return F.mask_along_axis_iid(specgram, self.mask_param, mask_value, self.axis + 1) + else: + return F.mask_along_axis(specgram, self.mask_param, mask_value, self.axis) + + +class FrequencyMasking(_AxisMasking): + r"""Apply masking to a spectrogram in the frequency domain. + + Proposed in *SpecAugment* [:footcite:`specaugment`]. + + Args: + freq_mask_param (int): maximum possible length of the mask. + Indices uniformly sampled from [0, freq_mask_param). + iid_masks (bool, optional): whether to apply different masks to each + example/channel in the batch. (Default: ``False``) + This option is applicable only when the input tensor is 4D. + + Example + >>> spectrogram = torchaudio.transforms.Spectrogram() + >>> masking = torchaudio.transforms.FrequencyMasking(freq_mask_param=80) + >>> + >>> original = spectrogram(waveform) + >>> masked = masking(original) + + .. image:: https://download.pytorch.org/torchaudio/doc-assets/specaugment_freq_masking1.png + :alt: The original spectrogram + + .. image:: https://download.pytorch.org/torchaudio/doc-assets/specaugment_freq_masking2.png + :alt: The spectrogram masked along frequency axis + """ + + def __init__(self, freq_mask_param: int, iid_masks: bool = False) -> None: + super(FrequencyMasking, self).__init__(freq_mask_param, 1, iid_masks) + + +class TimeMasking(_AxisMasking): + r"""Apply masking to a spectrogram in the time domain. + + Proposed in *SpecAugment* [:footcite:`specaugment`]. + + Args: + time_mask_param (int): maximum possible length of the mask. + Indices uniformly sampled from [0, time_mask_param). + iid_masks (bool, optional): whether to apply different masks to each + example/channel in the batch. (Default: ``False``) + This option is applicable only when the input tensor is 4D. + + Example + >>> spectrogram = torchaudio.transforms.Spectrogram() + >>> masking = torchaudio.transforms.TimeMasking(time_mask_param=80) + >>> + >>> original = spectrogram(waveform) + >>> masked = masking(original) + + .. image:: https://download.pytorch.org/torchaudio/doc-assets/specaugment_time_masking1.png + :alt: The original spectrogram + + .. image:: https://download.pytorch.org/torchaudio/doc-assets/specaugment_time_masking2.png + :alt: The spectrogram masked along time axis + """ + + def __init__(self, time_mask_param: int, iid_masks: bool = False) -> None: + super(TimeMasking, self).__init__(time_mask_param, 2, iid_masks) + + +class Vol(torch.nn.Module): + r"""Add a volume to an waveform. + + Args: + gain (float): Interpreted according to the given gain_type: + If ``gain_type`` = ``amplitude``, ``gain`` is a positive amplitude ratio. + If ``gain_type`` = ``power``, ``gain`` is a power (voltage squared). + If ``gain_type`` = ``db``, ``gain`` is in decibels. + gain_type (str, optional): Type of gain. One of: ``amplitude``, ``power``, ``db`` (Default: ``amplitude``) + """ + + def __init__(self, gain: float, gain_type: str = 'amplitude'): + super(Vol, self).__init__() + self.gain = gain + self.gain_type = gain_type + + if gain_type in ['amplitude', 'power'] and gain < 0: + raise ValueError("If gain_type = amplitude or power, gain must be positive.") + + def forward(self, waveform: Tensor) -> Tensor: + r""" + Args: + waveform (Tensor): Tensor of audio of dimension `(..., time)`. + + Returns: + Tensor: Tensor of audio of dimension `(..., time)`. + """ + if self.gain_type == "amplitude": + waveform = waveform * self.gain + + if self.gain_type == "db": + waveform = F.gain(waveform, self.gain) + + if self.gain_type == "power": + waveform = F.gain(waveform, 10 * math.log10(self.gain)) + + return torch.clamp(waveform, -1, 1) + + +class SlidingWindowCmn(torch.nn.Module): + r""" + Apply sliding-window cepstral mean (and optionally variance) normalization per utterance. + + Args: + cmn_window (int, optional): Window in frames for running average CMN computation (int, default = 600) + min_cmn_window (int, optional): Minimum CMN window used at start of decoding (adds latency only at start). + Only applicable if center == false, ignored if center==true (int, default = 100) + center (bool, optional): If true, use a window centered on the current frame + (to the extent possible, modulo end effects). If false, window is to the left. (bool, default = false) + norm_vars (bool, optional): If true, normalize variance to one. (bool, default = false) + """ + + def __init__(self, + cmn_window: int = 600, + min_cmn_window: int = 100, + center: bool = False, + norm_vars: bool = False) -> None: + super().__init__() + self.cmn_window = cmn_window + self.min_cmn_window = min_cmn_window + self.center = center + self.norm_vars = norm_vars + + def forward(self, specgram: Tensor) -> Tensor: + r""" + Args: + specgram (Tensor): Tensor of spectrogram of dimension `(..., time, freq)`. + + Returns: + Tensor: Tensor of spectrogram of dimension `(..., time, freq)`. + """ + cmn_specgram = F.sliding_window_cmn( + specgram, self.cmn_window, self.min_cmn_window, self.center, self.norm_vars) + return cmn_specgram + + +class Vad(torch.nn.Module): + r"""Voice Activity Detector. Similar to SoX implementation. + Attempts to trim silence and quiet background sounds from the ends of recordings of speech. + The algorithm currently uses a simple cepstral power measurement to detect voice, + so may be fooled by other things, especially music. + + The effect can trim only from the front of the audio, + so in order to trim from the back, the reverse effect must also be used. + + Args: + sample_rate (int): Sample rate of audio signal. + trigger_level (float, optional): The measurement level used to trigger activity detection. + This may need to be cahnged depending on the noise level, signal level, + and other characteristics of the input audio. (Default: 7.0) + trigger_time (float, optional): The time constant (in seconds) + used to help ignore short bursts of sound. (Default: 0.25) + search_time (float, optional): The amount of audio (in seconds) + to search for quieter/shorter bursts of audio to include prior + to the detected trigger point. (Default: 1.0) + allowed_gap (float, optional): The allowed gap (in seconds) between + quiteter/shorter bursts of audio to include prior + to the detected trigger point. (Default: 0.25) + pre_trigger_time (float, optional): The amount of audio (in seconds) to preserve + before the trigger point and any found quieter/shorter bursts. (Default: 0.0) + boot_time (float, optional) The algorithm (internally) uses adaptive noise + estimation/reduction in order to detect the start of the wanted audio. + This option sets the time for the initial noise estimate. (Default: 0.35) + noise_up_time (float, optional) Time constant used by the adaptive noise estimator + for when the noise level is increasing. (Default: 0.1) + noise_down_time (float, optional) Time constant used by the adaptive noise estimator + for when the noise level is decreasing. (Default: 0.01) + noise_reduction_amount (float, optional) Amount of noise reduction to use in + the detection algorithm (e.g. 0, 0.5, ...). (Default: 1.35) + measure_freq (float, optional) Frequency of the algorithm’s + processing/measurements. (Default: 20.0) + measure_duration: (float or None, optional) Measurement duration. + (Default: Twice the measurement period; i.e. with overlap.) + measure_smooth_time (float, optional) Time constant used to smooth + spectral measurements. (Default: 0.4) + hp_filter_freq (float, optional) "Brick-wall" frequency of high-pass filter applied + at the input to the detector algorithm. (Default: 50.0) + lp_filter_freq (float, optional) "Brick-wall" frequency of low-pass filter applied + at the input to the detector algorithm. (Default: 6000.0) + hp_lifter_freq (float, optional) "Brick-wall" frequency of high-pass lifter used + in the detector algorithm. (Default: 150.0) + lp_lifter_freq (float, optional) "Brick-wall" frequency of low-pass lifter used + in the detector algorithm. (Default: 2000.0) + + Reference: + - http://sox.sourceforge.net/sox.html + """ + + def __init__(self, + sample_rate: int, + trigger_level: float = 7.0, + trigger_time: float = 0.25, + search_time: float = 1.0, + allowed_gap: float = 0.25, + pre_trigger_time: float = 0.0, + boot_time: float = .35, + noise_up_time: float = .1, + noise_down_time: float = .01, + noise_reduction_amount: float = 1.35, + measure_freq: float = 20.0, + measure_duration: Optional[float] = None, + measure_smooth_time: float = .4, + hp_filter_freq: float = 50., + lp_filter_freq: float = 6000., + hp_lifter_freq: float = 150., + lp_lifter_freq: float = 2000.) -> None: + super().__init__() + + self.sample_rate = sample_rate + self.trigger_level = trigger_level + self.trigger_time = trigger_time + self.search_time = search_time + self.allowed_gap = allowed_gap + self.pre_trigger_time = pre_trigger_time + self.boot_time = boot_time + self.noise_up_time = noise_up_time + self.noise_down_time = noise_down_time + self.noise_reduction_amount = noise_reduction_amount + self.measure_freq = measure_freq + self.measure_duration = measure_duration + self.measure_smooth_time = measure_smooth_time + self.hp_filter_freq = hp_filter_freq + self.lp_filter_freq = lp_filter_freq + self.hp_lifter_freq = hp_lifter_freq + self.lp_lifter_freq = lp_lifter_freq + + def forward(self, waveform: Tensor) -> Tensor: + r""" + Args: + waveform (Tensor): Tensor of audio of dimension `(channels, time)` or `(time)` + Tensor of shape `(channels, time)` is treated as a multi-channel recording + of the same event and the resulting output will be trimmed to the earliest + voice activity in any channel. + """ + return F.vad( + waveform=waveform, + sample_rate=self.sample_rate, + trigger_level=self.trigger_level, + trigger_time=self.trigger_time, + search_time=self.search_time, + allowed_gap=self.allowed_gap, + pre_trigger_time=self.pre_trigger_time, + boot_time=self.boot_time, + noise_up_time=self.noise_up_time, + noise_down_time=self.noise_down_time, + noise_reduction_amount=self.noise_reduction_amount, + measure_freq=self.measure_freq, + measure_duration=self.measure_duration, + measure_smooth_time=self.measure_smooth_time, + hp_filter_freq=self.hp_filter_freq, + lp_filter_freq=self.lp_filter_freq, + hp_lifter_freq=self.hp_lifter_freq, + lp_lifter_freq=self.lp_lifter_freq, + ) + + +class SpectralCentroid(torch.nn.Module): + r"""Compute the spectral centroid for each channel along the time axis. + + The spectral centroid is defined as the weighted average of the + frequency values, weighted by their magnitude. + + Args: + sample_rate (int): Sample rate of audio signal. + n_fft (int, optional): Size of FFT, creates ``n_fft // 2 + 1`` bins. (Default: ``400``) + win_length (int or None, optional): Window size. (Default: ``n_fft``) + hop_length (int or None, optional): Length of hop between STFT windows. (Default: ``win_length // 2``) + pad (int, optional): Two sided padding of signal. (Default: ``0``) + window_fn (Callable[..., Tensor], optional): A function to create a window tensor + that is applied/multiplied to each frame/window. (Default: ``torch.hann_window``) + wkwargs (dict or None, optional): Arguments for window function. (Default: ``None``) + + Example + >>> waveform, sample_rate = torchaudio.load('test.wav', normalize=True) + >>> transform = transforms.SpectralCentroid(sample_rate) + >>> spectral_centroid = transform(waveform) # (channel, time) + """ + __constants__ = ['sample_rate', 'n_fft', 'win_length', 'hop_length', 'pad'] + + def __init__(self, + sample_rate: int, + n_fft: int = 400, + win_length: Optional[int] = None, + hop_length: Optional[int] = None, + pad: int = 0, + window_fn: Callable[..., Tensor] = torch.hann_window, + wkwargs: Optional[dict] = None) -> None: + super(SpectralCentroid, self).__init__() + self.sample_rate = sample_rate + self.n_fft = n_fft + self.win_length = win_length if win_length is not None else n_fft + self.hop_length = hop_length if hop_length is not None else self.win_length // 2 + window = window_fn(self.win_length) if wkwargs is None else window_fn(self.win_length, **wkwargs) + self.register_buffer('window', window) + self.pad = pad + + def forward(self, waveform: Tensor) -> Tensor: + r""" + Args: + waveform (Tensor): Tensor of audio of dimension `(..., time)`. + + Returns: + Tensor: Spectral Centroid of size `(..., time)`. + """ + + return F.spectral_centroid(waveform, self.sample_rate, self.pad, self.window, self.n_fft, self.hop_length, + self.win_length) + + +class PitchShift(torch.nn.Module): + r"""Shift the pitch of a waveform by ``n_steps`` steps. + + Args: + waveform (Tensor): The input waveform of shape `(..., time)`. + sample_rate (int): Sample rate of `waveform`. + n_steps (int): The (fractional) steps to shift `waveform`. + bins_per_octave (int, optional): The number of steps per octave (Default : ``12``). + n_fft (int, optional): Size of FFT, creates ``n_fft // 2 + 1`` bins (Default: ``512``). + win_length (int or None, optional): Window size. If None, then ``n_fft`` is used. (Default: ``None``). + hop_length (int or None, optional): Length of hop between STFT windows. If None, then ``win_length // 4`` + is used (Default: ``None``). + window (Tensor or None, optional): Window tensor that is applied/multiplied to each frame/window. + If None, then ``torch.hann_window(win_length)`` is used (Default: ``None``). + + Example + >>> waveform, sample_rate = torchaudio.load('test.wav', normalize=True) + >>> transform = transforms.PitchShift(sample_rate, 4) + >>> waveform_shift = transform(waveform) # (channel, time) + """ + __constants__ = ['sample_rate', 'n_steps', 'bins_per_octave', 'n_fft', 'win_length', 'hop_length'] + + def __init__(self, + sample_rate: int, + n_steps: int, + bins_per_octave: int = 12, + n_fft: int = 512, + win_length: Optional[int] = None, + hop_length: Optional[int] = None, + window_fn: Callable[..., Tensor] = torch.hann_window, + wkwargs: Optional[dict] = None) -> None: + super(PitchShift, self).__init__() + self.n_steps = n_steps + self.bins_per_octave = bins_per_octave + self.sample_rate = sample_rate + self.n_fft = n_fft + self.win_length = win_length if win_length is not None else n_fft + self.hop_length = hop_length if hop_length is not None else self.win_length // 4 + window = window_fn(self.win_length) if wkwargs is None else window_fn(self.win_length, **wkwargs) + self.register_buffer('window', window) + + def forward(self, waveform: Tensor) -> Tensor: + r""" + Args: + waveform (Tensor): Tensor of audio of dimension `(..., time)`. + + Returns: + Tensor: The pitch-shifted audio of shape `(..., time)`. + """ + + return F.pitch_shift(waveform, self.sample_rate, self.n_steps, self.bins_per_octave, self.n_fft, + self.win_length, self.hop_length, self.window) + + +class RNNTLoss(torch.nn.Module): + """Compute the RNN Transducer loss from *Sequence Transduction with Recurrent Neural Networks* + [:footcite:`graves2012sequence`]. + The RNN Transducer loss extends the CTC loss by defining a distribution over output + sequences of all lengths, and by jointly modelling both input-output and output-output + dependencies. + + Args: + blank (int, optional): blank label (Default: ``-1``) + clamp (float, optional): clamp for gradients (Default: ``-1``) + reduction (string, optional): Specifies the reduction to apply to the output: + ``'none'`` | ``'mean'`` | ``'sum'``. (Default: ``'mean'``) + + Example + >>> # Hypothetical values + >>> logits = torch.tensor([[[[0.1, 0.6, 0.1, 0.1, 0.1], + >>> [0.1, 0.1, 0.6, 0.1, 0.1], + >>> [0.1, 0.1, 0.2, 0.8, 0.1]], + >>> [[0.1, 0.6, 0.1, 0.1, 0.1], + >>> [0.1, 0.1, 0.2, 0.1, 0.1], + >>> [0.7, 0.1, 0.2, 0.1, 0.1]]]], + >>> dtype=torch.float32, + >>> requires_grad=True) + >>> targets = torch.tensor([[1, 2]], dtype=torch.int) + >>> logit_lengths = torch.tensor([2], dtype=torch.int) + >>> target_lengths = torch.tensor([2], dtype=torch.int) + >>> transform = transforms.RNNTLoss(blank=0) + >>> loss = transform(logits, targets, logit_lengths, target_lengths) + >>> loss.backward() + """ + + def __init__( + self, + blank: int = -1, + clamp: float = -1., + reduction: str = "mean", + ): + super().__init__() + self.blank = blank + self.clamp = clamp + self.reduction = reduction + + def forward( + self, + logits: Tensor, + targets: Tensor, + logit_lengths: Tensor, + target_lengths: Tensor, + ): + """ + Args: + logits (Tensor): Tensor of dimension `(batch, max seq length, max target length + 1, class)` + containing output from joiner + targets (Tensor): Tensor of dimension `(batch, max target length)` containing targets with zero padded + logit_lengths (Tensor): Tensor of dimension `(batch)` containing lengths of each sequence from encoder + target_lengths (Tensor): Tensor of dimension `(batch)` containing lengths of targets for each sequence + Returns: + Tensor: Loss with the reduction option applied. If ``reduction`` is ``'none'``, then size (batch), + otherwise scalar. + """ + return F.rnnt_loss( + logits, + targets, + logit_lengths, + target_lengths, + self.blank, + self.clamp, + self.reduction + ) + + +def _get_mat_trace(input: torch.Tensor, dim1: int = -1, dim2: int = -2) -> torch.Tensor: + r"""Compute the trace of a Tensor along ``dim1`` and ``dim2`` dimensions. + + Args: + input (torch.Tensor): Tensor of dimension `(..., channel, channel)` + dim1 (int, optional): the first dimension of the diagonal matrix + (Default: -1) + dim2 (int, optional): the second dimension of the diagonal matrix + (Default: -2) + + Returns: + torch.Tensor: trace of the input Tensor + """ + assert input.ndim >= 2, "The dimension of the tensor must be at least 2." + assert input.shape[dim1] == input.shape[dim2],\ + "The size of ``dim1`` and ``dim2`` must be the same." + input = torch.diagonal(input, 0, dim1=dim1, dim2=dim2) + return input.sum(dim=-1) + + +class PSD(torch.nn.Module): + r"""Compute cross-channel power spectral density (PSD) matrix. + + Args: + multi_mask (bool, optional): whether to use multi-channel Time-Frequency masks. (Default: ``False``) + normalize (bool, optional): whether normalize the mask along the time dimension. + eps (float, optional): a value added to the denominator in mask normalization. (Default: 1e-15) + """ + + def __init__(self, multi_mask: bool = False, normalize: bool = True, eps: float = 1e-15): + super().__init__() + self.multi_mask = multi_mask + self.normalize = normalize + self.eps = eps + + def forward(self, specgram: torch.Tensor, mask: Optional[torch.Tensor] = None): + """ + Args: + specgram (torch.Tensor): multi-channel complex-valued STFT matrix. + Tensor of dimension `(..., channel, freq, time)` + mask (torch.Tensor or None, optional): Time-Frequency mask for normalization. + Tensor of dimension `(..., freq, time)` if multi_mask is ``False`` or + of dimension `(..., channel, freq, time)` if multi_mask is ``True`` + + Returns: + Tensor: PSD matrix of the input STFT matrix. + Tensor of dimension `(..., freq, channel, channel)` + """ + # outer product: + # (..., ch_1, freq, time) x (..., ch_2, freq, time) -> (..., time, ch_1, ch_2) + psd = torch.einsum("...cft,...eft->...ftce", [specgram, specgram.conj()]) + + if mask is not None: + if self.multi_mask: + # Averaging mask along channel dimension + mask = mask.mean(dim=-3) # (..., freq, time) + + # Normalized mask along time dimension: + if self.normalize: + mask = mask / (mask.sum(dim=-1, keepdim=True) + self.eps) + + psd = psd * mask.unsqueeze(-1).unsqueeze(-1) + + psd = psd.sum(dim=-3) + return psd + + +class MVDR(torch.nn.Module): + """Minimum Variance Distortionless Response (MVDR) module that performs MVDR beamforming with Time-Frequency masks. + + Based on https://github.com/espnet/espnet/blob/master/espnet2/enh/layers/beamformer.py + + We provide three solutions of MVDR beamforming. One is based on *reference channel selection* + [:footcite:`souden2009optimal`] (``solution=ref_channel``). + + .. math:: + \\textbf{w}_{\\text{MVDR}}(f) =\ + \\frac{{{\\bf{\\Phi}_{\\textbf{NN}}^{-1}}(f){\\bf{\\Phi}_{\\textbf{SS}}}}(f)}\ + {\\text{Trace}({{{\\bf{\\Phi}_{\\textbf{NN}}^{-1}}(f) \\bf{\\Phi}_{\\textbf{SS}}}(f))}}\\bm{u} + + where :math:`\\bf{\\Phi}_{\\textbf{SS}}` and :math:`\\bf{\\Phi}_{\\textbf{NN}}` are the covariance\ + matrices of speech and noise, respectively. :math:`\\bf{u}` is an one-hot vector to determine the\ + reference channel. + + The other two solutions are based on the steering vector (``solution=stv_evd`` or ``solution=stv_power``). + + .. math:: + \\textbf{w}_{\\text{MVDR}}(f) =\ + \\frac{{{\\bf{\\Phi}_{\\textbf{NN}}^{-1}}(f){\\bm{v}}(f)}}\ + {{\\bm{v}^{\\mathsf{H}}}(f){\\bf{\\Phi}_{\\textbf{NN}}^{-1}}(f){\\bm{v}}(f)} + + where :math:`\\bm{v}` is the acoustic transfer function or the steering vector.\ + :math:`.^{\\mathsf{H}}` denotes the Hermitian Conjugate operation. + + We apply either *eigenvalue decomposition* + [:footcite:`higuchi2016robust`] or the *power method* [:footcite:`mises1929praktische`] to get the + steering vector from the PSD matrix of speech. + + After estimating the beamforming weight, the enhanced Short-time Fourier Transform (STFT) is obtained by + + .. math:: + \\hat{\\bf{S}} = {\\bf{w}^\\mathsf{H}}{\\bf{Y}}, {\\bf{w}} \\in \\mathbb{C}^{M \\times F} + + where :math:`\\bf{Y}` and :math:`\\hat{\\bf{S}}` are the STFT of the multi-channel noisy speech and\ + the single-channel enhanced speech, respectively. + + For online streaming audio, we provide a *recursive method* [:footcite:`higuchi2017online`] to update the + PSD matrices of speech and noise, respectively. + + Args: + ref_channel (int, optional): the reference channel for beamforming. (Default: ``0``) + solution (str, optional): the solution to get MVDR weight. + Options: [``ref_channel``, ``stv_evd``, ``stv_power``]. (Default: ``ref_channel``) + multi_mask (bool, optional): whether to use multi-channel Time-Frequency masks. (Default: ``False``) + diag_loading (bool, optional): whether apply diagonal loading on the psd matrix of noise. + (Default: ``True``) + diag_eps (float, optional): the coefficient multipied to the identity matrix for diagonal loading. + (Default: 1e-7) + online (bool, optional): whether to update the mvdr vector based on the previous psd matrices. + (Default: ``False``) + + Note: + The MVDR Module requires the input STFT to be double precision (``torch.complex128`` or ``torch.cdouble``), + to improve the numerical stability. You can downgrade the precision to ``torch.float`` after generating the + enhanced waveform for ASR joint training. + + Note: + If you use ``stv_evd`` solution, the gradient of the same input may not be identical if the + eigenvalues of the PSD matrix are not distinct (i.e. some eigenvalues are close or identical). + """ + + def __init__( + self, + ref_channel: int = 0, + solution: str = "ref_channel", + multi_mask: bool = False, + diag_loading: bool = True, + diag_eps: float = 1e-7, + online: bool = False, + ): + super().__init__() + assert solution in ["ref_channel", "stv_evd", "stv_power"],\ + "Unknown solution provided. Must be one of [``ref_channel``, ``stv_evd``, ``stv_power``]." + self.ref_channel = ref_channel + self.solution = solution + self.multi_mask = multi_mask + self.diag_loading = diag_loading + self.diag_eps = diag_eps + self.online = online + self.psd = PSD(multi_mask) + + psd_s: torch.Tensor = torch.zeros(1) + psd_n: torch.Tensor = torch.zeros(1) + mask_sum_s: torch.Tensor = torch.zeros(1) + mask_sum_n: torch.Tensor = torch.zeros(1) + self.register_buffer('psd_s', psd_s) + self.register_buffer('psd_n', psd_n) + self.register_buffer('mask_sum_s', mask_sum_s) + self.register_buffer('mask_sum_n', mask_sum_n) + + def _get_updated_mvdr_vector( + self, + psd_s: torch.Tensor, + psd_n: torch.Tensor, + mask_s: torch.Tensor, + mask_n: torch.Tensor, + reference_vector: torch.Tensor, + solution: str = 'ref_channel', + diagonal_loading: bool = True, + diag_eps: float = 1e-7, + eps: float = 1e-8, + ) -> torch.Tensor: + r"""Recursively update the MVDR beamforming vector. + + Args: + psd_s (torch.Tensor): psd matrix of target speech + psd_n (torch.Tensor): psd matrix of noise + mask_s (torch.Tensor): T-F mask of target speech + mask_n (torch.Tensor): T-F mask of noise + reference_vector (torch.Tensor): one-hot reference channel matrix + solution (str, optional): the solution to estimate the beamforming weight + (Default: ``ref_channel``) + diagonal_loading (bool, optional): whether to apply diagonal loading to psd_n + (Default: ``True``) + diag_eps (float, optional): The coefficient multipied to the identity matrix for diagonal loading + (Default: 1e-7) + eps (float, optional): a value added to the denominator in mask normalization. (Default: 1e-8) + + Returns: + Tensor: the mvdr beamforming weight matrix + """ + if self.multi_mask: + # Averaging mask along channel dimension + mask_s = mask_s.mean(dim=-3) # (..., freq, time) + mask_n = mask_n.mean(dim=-3) # (..., freq, time) + if self.psd_s.ndim == 1: + self.psd_s = psd_s + self.psd_n = psd_n + self.mask_sum_s = mask_s.sum(dim=-1) + self.mask_sum_n = mask_n.sum(dim=-1) + return self._get_mvdr_vector(psd_s, psd_n, reference_vector, solution, diagonal_loading, diag_eps, eps) + else: + psd_s = self._get_updated_psd_speech(psd_s, mask_s) + psd_n = self._get_updated_psd_noise(psd_n, mask_n) + self.psd_s = psd_s + self.psd_n = psd_n + self.mask_sum_s = self.mask_sum_s + mask_s.sum(dim=-1) + self.mask_sum_n = self.mask_sum_n + mask_n.sum(dim=-1) + return self._get_mvdr_vector(psd_s, psd_n, reference_vector, solution, diagonal_loading, diag_eps, eps) + + def _get_updated_psd_speech(self, psd_s: torch.Tensor, mask_s: torch.Tensor) -> torch.Tensor: + r"""Update psd of speech recursively. + + Args: + psd_s (torch.Tensor): psd matrix of target speech + mask_s (torch.Tensor): T-F mask of target speech + + Returns: + torch.Tensor: the updated psd of speech + """ + numerator = self.mask_sum_s / (self.mask_sum_s + mask_s.sum(dim=-1)) + denominator = 1 / (self.mask_sum_s + mask_s.sum(dim=-1)) + psd_s = self.psd_s * numerator[..., None, None] + psd_s * denominator[..., None, None] + return psd_s + + def _get_updated_psd_noise(self, psd_n: torch.Tensor, mask_n: torch.Tensor) -> torch.Tensor: + r"""Update psd of noise recursively. + + Args: + psd_n (torch.Tensor): psd matrix of target noise + mask_n (torch.Tensor): T-F mask of target noise + + Returns: + torch.Tensor: the updated psd of noise + """ + numerator = self.mask_sum_n / (self.mask_sum_n + mask_n.sum(dim=-1)) + denominator = 1 / (self.mask_sum_n + mask_n.sum(dim=-1)) + psd_n = self.psd_n * numerator[..., None, None] + psd_n * denominator[..., None, None] + return psd_n + + def _get_mvdr_vector( + self, + psd_s: torch.Tensor, + psd_n: torch.Tensor, + reference_vector: torch.Tensor, + solution: str = 'ref_channel', + diagonal_loading: bool = True, + diag_eps: float = 1e-7, + eps: float = 1e-8, + ) -> torch.Tensor: + r"""Compute beamforming vector by the reference channel selection method. + + Args: + psd_s (torch.Tensor): psd matrix of target speech + psd_n (torch.Tensor): psd matrix of noise + reference_vector (torch.Tensor): one-hot reference channel matrix + solution (str, optional): the solution to estimate the beamforming weight + (Default: ``ref_channel``) + diagonal_loading (bool, optional): whether to apply diagonal loading to psd_n + (Default: ``True``) + diag_eps (float, optional): The coefficient multipied to the identity matrix for diagonal loading + (Default: 1e-7) + eps (float, optional): a value added to the denominator in mask normalization. Default: 1e-8 + + Returns: + torch.Tensor: the mvdr beamforming weight matrix + """ + if diagonal_loading: + psd_n = self._tik_reg(psd_n, reg=diag_eps, eps=eps) + if solution == "ref_channel": + numerator = torch.linalg.solve(psd_n, psd_s) # psd_n.inv() @ psd_s + # ws: (..., C, C) / (...,) -> (..., C, C) + ws = numerator / (_get_mat_trace(numerator)[..., None, None] + eps) + # h: (..., F, C_1, C_2) x (..., C_2) -> (..., F, C_1) + beamform_vector = torch.einsum("...fec,...c->...fe", [ws, reference_vector]) + else: + if solution == "stv_evd": + stv = self._get_steering_vector_evd(psd_s) + else: + stv = self._get_steering_vector_power(psd_s, psd_n, reference_vector) + # numerator = psd_n.inv() @ stv + numerator = torch.linalg.solve(psd_n, stv).squeeze(-1) # (..., freq, channel) + # denominator = stv^H @ psd_n.inv() @ stv + denominator = torch.einsum("...d,...d->...", [stv.conj().squeeze(-1), numerator]) + # normalzie the numerator + scale = stv.squeeze(-1)[..., self.ref_channel, None].conj() + beamform_vector = numerator * scale / (denominator.real.unsqueeze(-1) + eps) + + return beamform_vector + + def _get_steering_vector_evd(self, psd_s: torch.Tensor) -> torch.Tensor: + r"""Estimate the steering vector by eigenvalue decomposition. + + Args: + psd_s (torch.tensor): covariance matrix of speech + Tensor of dimension `(..., freq, channel, channel)` + + Returns: + torch.Tensor: the enhanced STFT + Tensor of dimension `(..., freq, channel, 1)` + """ + w, v = torch.linalg.eig(psd_s) # (..., freq, channel, channel) + _, indices = torch.max(w.abs(), dim=-1, keepdim=True) + indices = indices.unsqueeze(-1) + stv = v.gather(-1, indices.expand(psd_s.shape[:-1] + (1,))) # (..., freq, channel, 1) + return stv + + def _get_steering_vector_power( + self, + psd_s: torch.Tensor, + psd_n: torch.Tensor, + reference_vector: torch.Tensor + ) -> torch.Tensor: + r"""Estimate the steering vector by the power method. + + Args: + psd_s (torch.tensor): covariance matrix of speech + Tensor of dimension `(..., freq, channel, channel)` + psd_n (torch.Tensor): covariance matrix of noise + Tensor of dimension `(..., freq, channel, channel)` + reference_vector (torch.Tensor): one-hot reference channel matrix + + Returns: + torch.Tensor: the enhanced STFT + Tensor of dimension `(..., freq, channel, 1)` + """ + phi = torch.linalg.solve(psd_n, psd_s) # psd_n.inv() @ psd_s + stv = torch.einsum("...fec,...c->...fe", [phi, reference_vector]) + stv = stv.unsqueeze(-1) + stv = torch.matmul(phi, stv) + stv = torch.matmul(psd_s, stv) + return stv + + def _apply_beamforming_vector( + self, + specgram: torch.Tensor, + beamform_vector: torch.Tensor + ) -> torch.Tensor: + r"""Apply the beamforming weight to the noisy STFT + Args: + specgram (torch.tensor): multi-channel noisy STFT + Tensor of dimension `(..., channel, freq, time)` + beamform_vector (torch.Tensor): beamforming weight matrix + Tensor of dimension `(..., freq, channel)` + + Returns: + torch.Tensor: the enhanced STFT + Tensor of dimension `(..., freq, time)` + """ + # (..., channel) x (..., channel, freq, time) -> (..., freq, time) + specgram_enhanced = torch.einsum("...fc,...cft->...ft", [beamform_vector.conj(), specgram]) + return specgram_enhanced + + def _tik_reg( + self, + mat: torch.Tensor, + reg: float = 1e-7, + eps: float = 1e-8 + ) -> torch.Tensor: + """Perform Tikhonov regularization (only modifying real part). + Args: + mat (torch.Tensor): input matrix (..., channel, channel) + reg (float, optional): regularization factor (Default: 1e-8) + eps (float, optional): a value to avoid the correlation matrix is all-zero (Default: 1e-8) + + Returns: + torch.Tensor: regularized matrix (..., channel, channel) + """ + # Add eps + C = mat.size(-1) + eye = torch.eye(C, dtype=mat.dtype, device=mat.device) + with torch.no_grad(): + epsilon = _get_mat_trace(mat).real[..., None, None] * reg + # in case that correlation_matrix is all-zero + epsilon = epsilon + eps + mat = mat + epsilon * eye[..., :, :] + return mat + + def forward( + self, + specgram: torch.Tensor, + mask_s: torch.Tensor, + mask_n: Optional[torch.Tensor] = None + ) -> torch.Tensor: + """Perform MVDR beamforming. + + Args: + specgram (torch.Tensor): the multi-channel STF of the noisy speech. + Tensor of dimension `(..., channel, freq, time)` + mask_s (torch.Tensor): Time-Frequency mask of target speech. + Tensor of dimension `(..., freq, time)` if multi_mask is ``False`` + or or dimension `(..., channel, freq, time)` if multi_mask is ``True`` + mask_n (torch.Tensor or None, optional): Time-Frequency mask of noise. + Tensor of dimension `(..., freq, time)` if multi_mask is ``False`` + or or dimension `(..., channel, freq, time)` if multi_mask is ``True`` + (Default: None) + + Returns: + torch.Tensor: The single-channel STFT of the enhanced speech. + Tensor of dimension `(..., freq, time)` + """ + if specgram.ndim < 3: + raise ValueError( + f"Expected at least 3D tensor (..., channel, freq, time). Found: {specgram.shape}" + ) + if specgram.dtype != torch.cdouble: + raise ValueError( + f"The type of ``specgram`` tensor must be ``torch.cdouble``. Found: {specgram.dtype}" + ) + + if mask_n is None: + warnings.warn( + "``mask_n`` is not provided, use ``1 - mask_s`` as ``mask_n``." + ) + mask_n = 1 - mask_s + + shape = specgram.size() + + # pack batch + specgram = specgram.reshape(-1, shape[-3], shape[-2], shape[-1]) + if self.multi_mask: + mask_s = mask_s.reshape(-1, shape[-3], shape[-2], shape[-1]) + mask_n = mask_n.reshape(-1, shape[-3], shape[-2], shape[-1]) + else: + mask_s = mask_s.reshape(-1, shape[-2], shape[-1]) + mask_n = mask_n.reshape(-1, shape[-2], shape[-1]) + + psd_s = self.psd(specgram, mask_s) # (..., freq, time, channel, channel) + psd_n = self.psd(specgram, mask_n) # (..., freq, time, channel, channel) + + u = torch.zeros( + specgram.size()[:-2], + device=specgram.device, + dtype=torch.cdouble + ) # (..., channel) + u[..., self.ref_channel].fill_(1) + + if self.online: + w_mvdr = self._get_updated_mvdr_vector( + psd_s, + psd_n, + mask_s, + mask_n, + u, + self.solution, + self.diag_loading, + self.diag_eps + ) + else: + w_mvdr = self._get_mvdr_vector( + psd_s, + psd_n, + u, + self.solution, + self.diag_loading, + self.diag_eps + ) + + specgram_enhanced = self._apply_beamforming_vector(specgram, w_mvdr) + + # unpack batch + specgram_enhanced = specgram_enhanced.reshape(shape[:-3] + shape[-2:]) + + return specgram_enhanced diff --git a/torchaudio/utils/__init__.py b/torchaudio/utils/__init__.py new file mode 100644 index 00000000..578a5192 --- /dev/null +++ b/torchaudio/utils/__init__.py @@ -0,0 +1,8 @@ +from . import ( + sox_utils, +) +from torchaudio._internal import module_utils as _mod_utils + + +if _mod_utils.is_sox_available(): + sox_utils.set_verbosity(1) diff --git a/torchaudio/utils/sox_utils.py b/torchaudio/utils/sox_utils.py new file mode 100644 index 00000000..384f370a --- /dev/null +++ b/torchaudio/utils/sox_utils.py @@ -0,0 +1,102 @@ +from typing import List, Dict + +import torch +from torchaudio._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 + """ + torch.ops.torchaudio.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 + """ + torch.ops.torchaudio.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 + """ + torch.ops.torchaudio.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 + """ + torch.ops.torchaudio.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(torch.ops.torchaudio.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 torch.ops.torchaudio.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 torch.ops.torchaudio.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 torch.ops.torchaudio.sox_utils_get_buffer_size() -- GitLab

    9SejltZ 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 z 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 a&i=jW|9tkpoB5xg|8?enKJ(v4@BbI@h%s3J literal 0 HcmV?d00001 diff --git a/test/torchaudio_unittest/assets/steam-train-whistle-daniel_simon.wav b/test/torchaudio_unittest/assets/steam-train-whistle-daniel_simon.wav new file mode 100644 index 0000000000000000000000000000000000000000..773dc0f79c12e6b94d8ba0e6fd21ba0238ae3c4a GIT binary patch literal 1107596 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 !&h`OW@gzV%R@ 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�jB2jPny61ot|@v5ugb3TpGlu` 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

B2>d3|7VJp~BA^R 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") + if args.ctc: + logger.info("| decoding a ctc model") + if args.rnnt: + logger.info("| decoding a rnnt model") + + # Load ensemble + logger.info("| loading model(s) from {}".format(args.path)) + models, _model_args = load_ensemble_for_inference( + args.path.split(":"), + task, + model_arg_overrides=eval(args.model_overrides), # noqa + ) + optimize_models(args, use_cuda, models) + + # Initialize generator + generator = task.build_generator(models, args) + + sp = spm.SentencePieceProcessor() + sp.Load(os.path.join(args.data, "spm.model")) + return task, generator, models, sp, tgt_dict + + +def transcribe_file(args, task, generator, models, sp, tgt_dict): + path = args.input_file + if not os.path.exists(path): + raise FileNotFoundError("Audio file not found: {}".format(path)) + waveform, sample_rate = torchaudio.load_wav(path) + waveform = waveform.mean(0, True) + waveform = torchaudio.transforms.Resample( + orig_freq=sample_rate, new_freq=16000 + )(waveform) + + start = time.time() + transcription = transcribe( + waveform, args, task, generator, models, sp, tgt_dict + ) + transcription_time = time.time() - start + return transcription_time, transcription + + +def get_microphone_transcription(args, task, generator, models, sp, tgt_dict): + for (waveform, sample_rate) in get_microphone_chunks(): + waveform = torchaudio.transforms.Resample( + orig_freq=sample_rate, new_freq=16000 + )(waveform.reshape(1, -1)) + transcription = transcribe( + waveform, args, task, generator, models, sp, tgt_dict + ) + yield transcription diff --git a/examples/interactive_asr/vad.py b/examples/interactive_asr/vad.py new file mode 100644 index 00000000..bc942032 --- /dev/null +++ b/examples/interactive_asr/vad.py @@ -0,0 +1,269 @@ +#!/usr/bin/env python3 +# Copyright (c) 2017-present, Facebook, Inc. +# All rights reserved. +# +# This source code is licensed under the license found in the LICENSE file in +# the root directory of this source tree. An additional grant of patent rights +# can be found in the PATENTS file in the same directory. +""" +Following `a simple but efficient real-time voice activity detection algorithm +`__. + +There are three criteria to decide if a frame contains speech: energy, most +dominant frequency, and spectral flatness. If any two of those are higher than +a minimum plus a threshold, then the frame contains speech. In the offline +case, the list of frames is postprocessed to remove too short silence and +speech sequences. In the online case here, inertia is added before switching +from speech to silence or vice versa. +""" + +from collections import deque + +import numpy as np +import torch +import queue + +import librosa +import pyaudio +import torchaudio + + +def compute_spectral_flatness(frame, epsilon=0.01): + # epsilon protects against log(0) + geometric_mean = torch.exp((frame + epsilon).log().mean(-1)) - epsilon + arithmetic_mean = frame.mean(-1) + return -10 * torch.log10(epsilon + geometric_mean / arithmetic_mean) + + +class VoiceActivityDetection: + def __init__( + self, + num_init_frames=30, + ignore_silent_count=4, + ignore_speech_count=1, + energy_prim_thresh=60, + frequency_prim_thresh=10, + spectral_flatness_prim_thresh=3, + verbose=False, + ): + + self.num_init_frames = num_init_frames + self.ignore_silent_count = ignore_silent_count + self.ignore_speech_count = ignore_speech_count + + self.energy_prim_thresh = energy_prim_thresh + self.frequency_prim_thresh = frequency_prim_thresh + self.spectral_flatness_prim_thresh = spectral_flatness_prim_thresh + + self.verbose = verbose + + self.speech_mark = True + self.silence_mark = False + + self.silent_count = 0 + self.speech_count = 0 + self.n = 0 + + if self.verbose: + self.energy_list = [] + self.frequency_list = [] + self.spectral_flatness_list = [] + + def iter(self, frame): + + frame_fft = torch.rfft(frame, 1) + amplitudes = torchaudio.functional.complex_norm(frame_fft) + + # Compute frame energy + energy = frame.pow(2).sum(-1) + + # Most dominant frequency component + frequency = amplitudes.argmax() + + # Spectral flatness measure + spectral_flatness = compute_spectral_flatness(amplitudes) + + if self.verbose: + self.energy_list.append(energy) + self.frequency_list.append(frequency) + self.spectral_flatness_list.append(spectral_flatness) + + if self.n == 0: + self.min_energy = energy + self.min_frequency = frequency + self.min_spectral_flatness = spectral_flatness + elif self.n < self.num_init_frames: + self.min_energy = min(energy, self.min_energy) + self.min_frequency = min(frequency, self.min_frequency) + self.min_spectral_flatness = min( + spectral_flatness, self.min_spectral_flatness + ) + + self.n += 1 + + # Add 1. to avoid log(0) + thresh_energy = self.energy_prim_thresh * torch.log(1.0 + self.min_energy) + thresh_frequency = self.frequency_prim_thresh + thresh_spectral_flatness = self.spectral_flatness_prim_thresh + + # Check all three conditions + + counter = 0 + if energy - self.min_energy >= thresh_energy: + counter += 1 + if frequency - self.min_frequency >= thresh_frequency: + counter += 1 + if spectral_flatness - self.min_spectral_flatness >= thresh_spectral_flatness: + counter += 1 + + # Detection + if counter > 1: + # Speech detected + self.speech_count += 1 + # Inertia against switching + if ( + self.n >= self.num_init_frames + and self.speech_count <= self.ignore_speech_count + ): + # Too soon to change + return self.silence_mark + else: + self.silent_count = 0 + return self.speech_mark + else: + # Silence detected + self.min_energy = ((self.silent_count * self.min_energy) + energy) / ( + self.silent_count + 1 + ) + self.silent_count += 1 + # Inertia against switching + if ( + self.n >= self.num_init_frames + and self.silent_count <= self.ignore_silent_count + ): + # Too soon to change + return self.speech_mark + else: + self.speech_count = 0 + return self.silence_mark + + +class MicrophoneStream: + """Opens a recording stream as a generator yielding the audio chunks.""" + + def __init__(self, device=None, rate=22050, chunk=2205): + """ + The 22050 is the librosa default, which is what our models were + trained on. The ratio of [chunk / rate] is the amount of time between + audio samples - for example, with these defaults, + an audio fragment will be processed every tenth of a second. + """ + self._rate = rate + self._chunk = chunk + self._device = device + + # Create a thread-safe buffer of audio data + self._buff = queue.Queue() + self.closed = True + + def __enter__(self): + self._audio_interface = pyaudio.PyAudio() + self._audio_stream = self._audio_interface.open( + # format=pyaudio.paInt16, + format=pyaudio.paFloat32, + # The API currently only supports 1-channel (mono) audio + # https://goo.gl/z757pE + channels=1, + rate=self._rate, + input=True, + frames_per_buffer=self._chunk, + input_device_index=self._device, + # Run the audio stream asynchronously to fill the buffer object. + # This is necessary so that the input device's buffer doesn't + # overflow while the calling thread makes network requests, etc. + stream_callback=self._fill_buffer, + ) + + self.closed = False + + return self + + def __exit__(self, type, value, traceback): + self._audio_stream.stop_stream() + self._audio_stream.close() + self.closed = True + # Signal the generator to terminate so that the client's + # streaming_recognize method will not block the process termination. + self._buff.put(None) + self._audio_interface.terminate() + + def _fill_buffer(self, in_data, frame_count, time_info, status_flags): + """Continuously collect data from the audio stream, into the buffer.""" + self._buff.put(in_data) + return None, pyaudio.paContinue + + def generator(self): + while not self.closed: + # Use a blocking get() to ensure there's at least one chunk of + # data, and stop iteration if the chunk is None, indicating the + # end of the audio stream. + chunk = self._buff.get() + if chunk is None: + return + data = [chunk] + + # Now consume whatever other data's still buffered. + while True: + try: + chunk = self._buff.get(block=False) + if chunk is None: + return + data.append(chunk) + except queue.Empty: + break + + ans = np.fromstring(b"".join(data), dtype=np.float32) + # yield uniform-sized chunks + ans = np.split(ans, np.shape(ans)[0] / self._chunk) + # Resample the audio to 22050, librosa default + for chunk in ans: + yield librosa.core.resample(chunk, self._rate, 22050) + + +def get_microphone_chunks( + min_to_cumulate=5, # 0.5 seconds + max_to_cumulate=100, # 10 seconds + precumulate=5, + max_to_visualize=100, +): + + vad = VoiceActivityDetection() + + cumulated = [] + precumulated = deque(maxlen=precumulate) + + with MicrophoneStream() as stream: + audio_generator = stream.generator() + chunk_length = stream._chunk + waveform = torch.zeros(max_to_visualize * chunk_length) + + for chunk in audio_generator: + # Is speech? + + chunk = torch.tensor(chunk) + is_speech = vad.iter(chunk) + + # Cumulate speech + + if is_speech or cumulated: + cumulated.append(chunk) + else: + precumulated.append(chunk) + + if (not is_speech and len(cumulated) >= min_to_cumulate) or ( + len(cumulated) > max_to_cumulate + ): + waveform = torch.cat(list(precumulated) + cumulated, -1) + yield (waveform * stream._rate, stream._rate) + cumulated = [] + precumulated = deque(maxlen=precumulate) diff --git a/examples/libtorchaudio/.gitignore b/examples/libtorchaudio/.gitignore new file mode 100644 index 00000000..d2c4084e --- /dev/null +++ b/examples/libtorchaudio/.gitignore @@ -0,0 +1,4 @@ +build +data/output.wav +*.zip +output diff --git a/examples/libtorchaudio/CMakeLists.txt b/examples/libtorchaudio/CMakeLists.txt new file mode 100644 index 00000000..b4cf58b3 --- /dev/null +++ b/examples/libtorchaudio/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.5) + +project(libtorchaudio-cpp-example) + +SET(BUILD_SOX ON CACHE BOOL "Build libsox into libtorchaudio") + +SET(BUILD_KALDI OFF CACHE BOOL "Build Kaldi into libtorchaudio") +SET(BUILD_RNNT ON CACHE BOOL "Build RNN transducer into libtorchaudio") +SET(BUILD_TORCHAUDIO_PYTHON_EXTENSION OFF CACHE BOOL "Build Python binding") + +find_package(Torch REQUIRED) +message("libtorchaudio CMakeLists: ${TORCH_CXX_FLAGS}") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${TORCH_CXX_FLAGS}") + +add_subdirectory(../.. libtorchaudio) +add_subdirectory(augmentation) +add_subdirectory(speech_recognition) diff --git a/examples/libtorchaudio/README.md b/examples/libtorchaudio/README.md new file mode 100644 index 00000000..cfed769c --- /dev/null +++ b/examples/libtorchaudio/README.md @@ -0,0 +1,30 @@ +# Libtorchaudio Examples + +* [Augmentation](./augmentation) +* [Speech Recognition with wav2vec2.0](./speech_recognition) + +## Build + +The example applications in this directory depend on `libtorch` and `libtorchaudio`. +If you have a working `PyTorch`, you already have `libtorch`. +Please refer to [this tutorial](https://pytorch.org/tutorials/advanced/torch_script_custom_classes.html) for the use of `libtorch` and TorchScript. + +`libtorchaudio` is the library of torchaudio's C++ components without Python component. +It is currently not distributed, and it will be built alongside with the applications. + +The following commands will build `libtorchaudio` and applications. + +```bash +git submodule update +mkdir build +cd build +cmake -GNinja \ + -DCMAKE_PREFIX_PATH="$(python -c 'import torch;print(torch.utils.cmake_prefix_path)')" \ + -DBUILD_SOX=ON \ + -DBUILD_KALDI=OFF \ + -DBUILD_RNNT=ON \ + .. +cmake --build . +``` + +For the usages of each application, refer to the corresponding application directory. diff --git a/examples/libtorchaudio/augmentation/CMakeLists.txt b/examples/libtorchaudio/augmentation/CMakeLists.txt new file mode 100644 index 00000000..e9bfece9 --- /dev/null +++ b/examples/libtorchaudio/augmentation/CMakeLists.txt @@ -0,0 +1,3 @@ +add_executable(augment main.cpp) +target_link_libraries(augment "${TORCH_LIBRARIES}" "${TORCHAUDIO_LIBRARY}") +set_property(TARGET augment PROPERTY CXX_STANDARD 14) diff --git a/examples/libtorchaudio/augmentation/README.md b/examples/libtorchaudio/augmentation/README.md new file mode 100644 index 00000000..b78248af --- /dev/null +++ b/examples/libtorchaudio/augmentation/README.md @@ -0,0 +1,36 @@ +# Augmentation + +This example demonstrates how you can use torchaudio's I/O features and augmentations in C++ application. + +**NOTE** +This example uses `"sox_io"` backend, thus does not work on Windows. + +## Steps +### 1. Create augmentation pipeline TorchScript file. + +First, we implement our data process pipeline as a regular Python, and save it as a TorchScript object. +We will load and execute it in our C++ application. The C++ code is found in [`main.cpp`](./main.cpp). + +```python +python create_jittable_pipeline.py \ + --rir-path "../data/rir.wav" \ + --output-path "./pipeline.zip" +``` + +### 2. Build the application + +Please refer to [the top level README.md](../README.md) + +### 3. Run the application + +Now we run the C++ application `augment`, with the TorchScript object we created in Step.1 and an input audio file. + +In [the top level directory](../) + +```bash +input_audio_file="./data/input.wav" +./build/augmentation/augment ./augmentation/pipeline.zip "${input_audio_file}" "output.wav" +``` + +When you give a clean speech file, the output audio sounds like it's a phone conversation. + diff --git a/examples/libtorchaudio/augmentation/create_jittable_pipeline.py b/examples/libtorchaudio/augmentation/create_jittable_pipeline.py new file mode 100644 index 00000000..ac33a837 --- /dev/null +++ b/examples/libtorchaudio/augmentation/create_jittable_pipeline.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +""" +Create a data preprocess pipeline that can be run with libtorchaudio +""" +import os +import argparse + +import torch +import torchaudio + + +class Pipeline(torch.nn.Module): + """Example audio process pipeline. + + This example load waveform from a file then apply effects and save it to a file. + """ + def __init__(self, rir_path: str): + super().__init__() + rir, sample_rate = torchaudio.load(rir_path) + self.register_buffer('rir', rir) + self.rir_sample_rate: int = sample_rate + + def forward(self, input_path: str, output_path: str): + torchaudio.sox_effects.init_sox_effects() + + # 1. load audio + waveform, sample_rate = torchaudio.load(input_path) + + # 2. Add background noise + alpha = 0.01 + waveform = alpha * torch.randn_like(waveform) + (1 - alpha) * waveform + + # 3. Reample the RIR filter to much the audio sample rate + rir, _ = torchaudio.sox_effects.apply_effects_tensor( + self.rir, self.rir_sample_rate, effects=[["rate", str(sample_rate)]]) + rir = rir / torch.norm(rir, p=2) + rir = torch.flip(rir, [1]) + + # 4. Apply RIR filter + waveform = torch.nn.functional.pad(waveform, (rir.shape[1] - 1, 0)) + waveform = torch.nn.functional.conv1d(waveform[None, ...], rir[None, ...])[0] + + # Save + torchaudio.save(output_path, waveform, sample_rate) + + +def _create_jit_pipeline(rir_path, output_path): + module = torch.jit.script(Pipeline(rir_path)) + print("*" * 40) + print("* Pipeline code") + print("*" * 40) + print() + print(module.code) + print("*" * 40) + module.save(output_path) + + +def _get_path(*paths): + return os.path.join(os.path.dirname(__file__), *paths) + + +def _parse_args(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--rir-path", + default=_get_path("..", "data", "rir.wav"), + help="Audio dara for room impulse response." + ) + parser.add_argument( + "--output-path", + default=_get_path("pipeline.zip"), + help="Output JIT file." + ) + return parser.parse_args() + + +def _main(): + args = _parse_args() + _create_jit_pipeline(args.rir_path, args.output_path) + + +if __name__ == '__main__': + _main() diff --git a/examples/libtorchaudio/augmentation/main.cpp b/examples/libtorchaudio/augmentation/main.cpp new file mode 100644 index 00000000..fe45d28a --- /dev/null +++ b/examples/libtorchaudio/augmentation/main.cpp @@ -0,0 +1,21 @@ +#include + +int main(int argc, char* argv[]) { + if (argc !=4) { + std::cerr << "Usage: " << argv[0] << " " << std::endl; + return -1; + } + + torch::jit::script::Module module; + std::cout << "Loading module from: " << argv[1] << std::endl; + try { + module = torch::jit::load(argv[1]); + } catch (const c10::Error &error) { + std::cerr << "Failed to load the module:" << error.what() << std::endl; + return -1; + } + + std::cout << "Performing the process ..." << std::endl; + module.forward({c10::IValue(argv[2]), c10::IValue(argv[3])}); + std::cout << "Done." << std::endl; +} diff --git a/examples/libtorchaudio/build.sh b/examples/libtorchaudio/build.sh new file mode 100644 index 00000000..ac51caf3 --- /dev/null +++ b/examples/libtorchaudio/build.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +set -eux + +this_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +build_dir="${this_dir}/build" + +mkdir -p "${build_dir}" +cd "${build_dir}" + +git submodule update +cmake -GNinja \ + -DCMAKE_PREFIX_PATH="$(python -c 'import torch;print(torch.utils.cmake_prefix_path)')" \ + -DBUILD_SOX=ON \ + -DBUILD_KALDI=OFF \ + .. +cmake --build . diff --git a/examples/libtorchaudio/data/README.md b/examples/libtorchaudio/data/README.md new file mode 100644 index 00000000..6e2ff190 --- /dev/null +++ b/examples/libtorchaudio/data/README.md @@ -0,0 +1,5 @@ +The files in this directory are originated from [VOiCES](https://iqtlabs.github.io/voices/) dataset, which is licensed under Creative Commos BY 4.0. They are modified to fit into the tutorial. + +* `input.wav`: `VOiCES_devkit/source-16k/train/sp0307/Lab41-SRI-VOiCES-src-sp0307-ch127535-sg0042.wav` + +* `rir.wav`: `VOiCES_devkit/distant-16k/room-response/rm1/impulse/Lab41-SRI-VOiCES-rm1-impulse-mc01-stu-clo.wav` diff --git a/examples/libtorchaudio/data/input.wav b/examples/libtorchaudio/data/input.wav new file mode 100644 index 0000000000000000000000000000000000000000..004a33532ea2547c10c0074b967733ba91edd9f8 GIT binary patch literal 108844 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 literal 0 HcmV?d00001 diff --git a/examples/libtorchaudio/data/rir.wav b/examples/libtorchaudio/data/rir.wav new file mode 100644 index 0000000000000000000000000000000000000000..c9e6d836818ff5ea8cfaaadeab69316561b32465 GIT binary patch literal 9338 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 \ + --output-path "./pipeline-fairseq/" +``` + +The above command should create the following TorchScript object files in the output directory. + +``` +decoder.zip encoder.zip loader.zip +``` + +* `loader.zip` loads audio file and generate waveform Tensor. +* `encoder.zip` receives waveform Tensor and generates the sequence of probability distribution over the label. +* `decoder.zip` receives the probability distribution over the label and generates a transcript. + +### 1.2. From Hugging Face Transformers + + +[Hugging Face Transformers](https://huggingface.co/transformers/index.html) and [Hugging Face Model Hub](https://huggingface.co/models) provides `wav2vec2.0` models fine-tuned on variety of datasets and languages. + +We can also import the model published on Hugging Face Hub and run it in our C++ application. +In the following example, we will try the Geremeny model, ([facebook/wav2vec2-large-xlsr-53-german](https://huggingface.co/facebook/wav2vec2-large-xlsr-53-german/tree/main)) on [VoxForge Germany dataset](http://www.voxforge.org/de/downloads). + +```bash +mkdir -p pipeline-hf +python build_pipeline_from_huggingface_transformers.py \ + --model facebook/wav2vec2-large-xlsr-53-german \ + --output-path ./pipeline-hf/ +``` + +The resulting TorchScript object files should be same as the `fairseq` example. + +## 2. Build the application + +Please refer to [the top level README.md](../README.md) + +## 3. Run the application + +Now we run the C++ application [`transcribe`](./transcribe.cpp), with the TorchScript object we created in Step.1.1. and an input audio file. + +```bash +../build/speech_recognition/transcribe ./pipeline-fairseq ../data/input.wav +``` + +This will output something like the following. + +``` +Loading module from: ./pipeline/loader.zip +Loading module from: ./pipeline/encoder.zip +Loading module from: ./pipeline/decoder.zip +Loading the audio +Running inference +Generating the transcription +I HAD THAT CURIOSITY BESIDE ME AT THIS MOMENT +Done. +``` + +## 4. Evaluate the pipeline on Librispeech dataset + +Let's evaluate this word error rate (WER) of this application using [Librispeech dataset](https://www.openslr.org/12). + +### 4.1. Create a list of audio paths + +For the sake of simplifying our C++ code, we will first parse the Librispeech dataset to get the list of audio path + +```bash +python parse_librispeech.py /LibriSpeech/test-clean ./flist.txt +``` + +The list should look like the following; + +```bash +head flist.txt + +1089-134691-0000 /LibriSpeech/test-clean/1089/134691/1089-134691-0000.flac HE COULD WAIT NO LONGER +``` + +### 4.2. Run the transcription + +[`transcribe_list`](./transcribe_list.cpp) processes the input flist list and feed the audio path one by one to the pipeline, then generate reference file and hypothesis file. + +```bash +../build/speech_recognition/transcribe_list ./pipeline-fairseq ./flist.txt +``` + +### 4.3. Score WER + +You need `sclite` for this step. You can download the code from [SCTK repository](https://github.com/usnistgov/SCTK). + +```bash +# in the output directory +sclite -r ref.trn -h hyp.trn -i wsj -o pralign -o sum +``` + +WER can be found in the resulting `hyp.trn.sys`. Check out the column that starts with `Sum/Avg` the first column of the third block is `100 - WER`. + +In our test, we got the following results. + +| model | Fine Tune | test-clean | test-other | +|:-----------------------------------------:|----------:|:----------:|:----------:| +| Base
`wav2vec_small_960` | 960h | 3.1 | 7.7 | +| Large
`wav2vec_big_960` | 960h | 2.6 | 5.9 | +| Large (LV-60)
`wav2vec2_vox_960h_new` | 960h | 2.9 | 6.2 | +| Large (LV-60) + Self Training
`wav2vec_vox_960h_pl` | 960h | 1.9 | 4.5 | + + +You can also check `hyp.trn.pra` file to see what errors were made. + +``` +id: (3528-168669-0005) +Scores: (#C #S #D #I) 7 1 0 0 +REF: there is a stone to be RAISED heavy +HYP: there is a stone to be RACED heavy +Eval: S +``` + +## 5. Evaluate the pipeline on VoxForge dataset + +Now we use the pipeline we created in step 1.2. This time with German language dataset from VoxForge. + +### 5.1. Create a list of audio paths + +Download an archive from http://www.repository.voxforge1.org/downloads/de/Trunk/Audio/Main/16kHz_16bit/, and extract it to your local file system, then run the following to generate the file list. + +```bash +python parse_voxforge.py > ./flist-de.txt +``` + +The list should look like + +```bash +head flist-de.txt +de5-001 /datasets/voxforge/de/guenter-20140214-afn/wav/de5-001.wav ES SOLL ETWA FÜNFZIGTAUSEND VERSCHIEDENE SORTEN GEBEN +``` + +### 5.2. Run the application and score WER + +This process is same as the Librispeech example. We just use the pipeline with the Germany model and file list of Germany dataset. Refer to the corresponding ssection in Librispeech evaluation.. + +```bash +../build/speech_recognition/transcribe_list ./pipeline-hf ./flist-de.txt +``` + +Then + +```bash +# in the output directory +sclite -r ref.trn -h hyp.trn -i wsj -o pralign -o sum +``` + +You can find the detail of evalauation result in PRA. + +``` +id: (guenter-20140214-afn/mfc/de5-012) +Scores: (#C #S #D #I) 4 1 1 0 +REF: die ausgaben kÖnnen gigantisch STEIGE N +HYP: die ausgaben kÖnnen gigantisch ****** STEIGEN +Eval: D S +``` diff --git a/examples/libtorchaudio/speech_recognition/build_pipeline_from_fairseq.py b/examples/libtorchaudio/speech_recognition/build_pipeline_from_fairseq.py new file mode 100644 index 00000000..a6da0ae1 --- /dev/null +++ b/examples/libtorchaudio/speech_recognition/build_pipeline_from_fairseq.py @@ -0,0 +1,182 @@ +#!/usr/bin/evn python3 +"""Build Speech Recognition pipeline based on fairseq's wav2vec2.0 and dump it to TorchScript file. + +To use this script, you need `fairseq`. +""" +import os +import argparse +import logging + +import torch +from torch.utils.mobile_optimizer import optimize_for_mobile +import torchaudio +from torchaudio.models.wav2vec2.utils.import_fairseq import import_fairseq_model +import fairseq + +from greedy_decoder import Decoder + +_LG = logging.getLogger(__name__) + + +def _parse_args(): + parser = argparse.ArgumentParser( + description=__doc__, + ) + parser.add_argument( + '--model-file', + required=True, + help='Path to the input pretrained weight file.' + ) + parser.add_argument( + '--dict-dir', + help=( + 'Path to the directory in which `dict.ltr.txt` file is found. ' + 'Required only when the model is finetuned.' + ) + ) + parser.add_argument( + '--output-path', + help='Path to the directory, where the TorchScript-ed pipelines are saved.', + ) + parser.add_argument( + '--test-file', + help='Path to a test audio file.', + ) + parser.add_argument( + '--debug', + action='store_true', + help=( + 'When enabled, individual components are separately tested ' + 'for the numerical compatibility and TorchScript compatibility.' + ) + ) + parser.add_argument( + '--quantize', + action='store_true', + help='Apply quantization to model.' + ) + parser.add_argument( + '--optimize-for-mobile', + action='store_true', + help='Apply optmization for mobile.' + ) + return parser.parse_args() + + +class Loader(torch.nn.Module): + def forward(self, audio_path: str) -> torch.Tensor: + waveform, sample_rate = torchaudio.load(audio_path) + if sample_rate != 16000: + waveform = torchaudio.functional.resample(waveform, float(sample_rate), 16000.) + return waveform + + +class Encoder(torch.nn.Module): + def __init__(self, encoder: torch.nn.Module): + super().__init__() + self.encoder = encoder + + def forward(self, waveform: torch.Tensor) -> torch.Tensor: + result, _ = self.encoder(waveform) + return result[0] + + +def _get_decoder(): + labels = [ + "", + "", + "", + "", + "|", + "E", + "T", + "A", + "O", + "N", + "I", + "H", + "S", + "R", + "D", + "L", + "U", + "M", + "W", + "C", + "F", + "G", + "Y", + "P", + "B", + "V", + "K", + "'", + "X", + "J", + "Q", + "Z", + ] + return Decoder(labels) + + +def _load_fairseq_model(input_file, data_dir=None): + overrides = {} + if data_dir: + overrides['data'] = data_dir + + model, _, _ = fairseq.checkpoint_utils.load_model_ensemble_and_task( + [input_file], arg_overrides=overrides + ) + model = model[0] + return model + + +def _get_model(model_file, dict_dir): + original = _load_fairseq_model(model_file, dict_dir) + model = import_fairseq_model(original.w2v_encoder) + return model + + +def _main(): + args = _parse_args() + _init_logging(args.debug) + loader = Loader() + model = _get_model(args.model_file, args.dict_dir).eval() + encoder = Encoder(model) + decoder = _get_decoder() + _LG.info(encoder) + + if args.quantize: + _LG.info('Quantizing the model') + model.encoder.transformer.pos_conv_embed.__prepare_scriptable__() + encoder = torch.quantization.quantize_dynamic( + encoder, qconfig_spec={torch.nn.Linear}, dtype=torch.qint8) + _LG.info(encoder) + + # test + if args.test_file: + _LG.info('Testing with %s', args.test_file) + waveform = loader(args.test_file) + emission = encoder(waveform) + transcript = decoder(emission) + _LG.info(transcript) + + torch.jit.script(loader).save(os.path.join(args.output_path, 'loader.zip')) + torch.jit.script(decoder).save(os.path.join(args.output_path, 'decoder.zip')) + scripted = torch.jit.script(encoder) + if args.optimize_for_mobile: + scripted = optimize_for_mobile(scripted) + scripted.save(os.path.join(args.output_path, 'encoder.zip')) + + +def _init_logging(debug=False): + level = logging.DEBUG if debug else logging.INFO + format_ = ( + '%(message)s' if not debug else + '%(asctime)s: %(levelname)7s: %(funcName)10s: %(message)s' + ) + logging.basicConfig(level=level, format=format_) + + +if __name__ == '__main__': + _main() diff --git a/examples/libtorchaudio/speech_recognition/build_pipeline_from_huggingface_transformers.py b/examples/libtorchaudio/speech_recognition/build_pipeline_from_huggingface_transformers.py new file mode 100644 index 00000000..10323d96 --- /dev/null +++ b/examples/libtorchaudio/speech_recognition/build_pipeline_from_huggingface_transformers.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 +import argparse +import logging +import os + +import torch +import torchaudio +from torchaudio.models.wav2vec2.utils.import_huggingface import import_huggingface_model +from greedy_decoder import Decoder + +_LG = logging.getLogger(__name__) + + +def _parse_args(): + parser = argparse.ArgumentParser( + description=__doc__, + ) + parser.add_argument( + '--model', + required=True, + help='Path to the input pretrained weight file.' + ) + parser.add_argument( + '--output-path', + help='Path to the directory, where the Torchscript-ed pipelines are saved.', + ) + parser.add_argument( + '--test-file', + help='Path to a test audio file.', + ) + parser.add_argument( + '--quantize', + action='store_true', + help='Quantize the model.', + ) + parser.add_argument( + '--debug', + action='store_true', + help=( + 'When enabled, individual components are separately tested ' + 'for the numerical compatibility and TorchScript compatibility.' + ) + ) + return parser.parse_args() + + +class Loader(torch.nn.Module): + def forward(self, audio_path: str) -> torch.Tensor: + waveform, sample_rate = torchaudio.load(audio_path) + if sample_rate != 16000: + waveform = torchaudio.functional.resample(waveform, float(sample_rate), 16000.) + return waveform + + +class Encoder(torch.nn.Module): + def __init__(self, encoder: torch.nn.Module): + super().__init__() + self.encoder = encoder + + def forward(self, waveform: torch.Tensor) -> torch.Tensor: + result, _ = self.encoder(waveform) + return result[0] + + +def _get_model(model_id): + from transformers import Wav2Vec2ForCTC, Wav2Vec2Processor + tokenizer = Wav2Vec2Processor.from_pretrained(model_id).tokenizer + labels = [k for k, v in sorted(tokenizer.get_vocab().items(), key=lambda kv: kv[1])] + original = Wav2Vec2ForCTC.from_pretrained(model_id) + model = import_huggingface_model(original) + return model.eval(), labels + + +def _get_decoder(labels): + return Decoder(labels) + + +def _main(): + args = _parse_args() + _init_logging(args.debug) + _LG.info('Loading model: %s', args.model) + model, labels = _get_model(args.model) + _LG.info('Labels: %s', labels) + _LG.info('Building pipeline') + loader = Loader() + encoder = Encoder(model) + decoder = _get_decoder(labels) + _LG.info(encoder) + + if args.quantize: + _LG.info('Quantizing the model') + model.encoder.transformer.pos_conv_embed.__prepare_scriptable__() + encoder = torch.quantization.quantize_dynamic( + encoder, qconfig_spec={torch.nn.Linear}, dtype=torch.qint8) + _LG.info(encoder) + + # test + if args.test_file: + _LG.info('Testing with %s', args.test_file) + waveform = loader(args.test_file) + emission = encoder(waveform) + transcript = decoder(emission) + _LG.info(transcript) + + torch.jit.script(loader).save(os.path.join(args.output_path, 'loader.zip')) + torch.jit.script(encoder).save(os.path.join(args.output_path, 'encoder.zip')) + torch.jit.script(decoder).save(os.path.join(args.output_path, 'decoder.zip')) + + +def _init_logging(debug=False): + level = logging.DEBUG if debug else logging.INFO + format_ = ( + '%(message)s' if not debug else + '%(asctime)s: %(levelname)7s: %(funcName)10s: %(message)s' + ) + logging.basicConfig(level=level, format=format_) + + +if __name__ == '__main__': + _main() diff --git a/examples/libtorchaudio/speech_recognition/greedy_decoder.py b/examples/libtorchaudio/speech_recognition/greedy_decoder.py new file mode 100644 index 00000000..3303c330 --- /dev/null +++ b/examples/libtorchaudio/speech_recognition/greedy_decoder.py @@ -0,0 +1,28 @@ +import torch + + +class Decoder(torch.nn.Module): + def __init__(self, labels): + super().__init__() + self.labels = labels + + def forward(self, logits: torch.Tensor) -> str: + """Given a sequence logits over labels, get the best path string + + Args: + logits (Tensor): Logit tensors. Shape `[num_seq, num_label]`. + + Returns: + str: The resulting transcript + """ + best_path = torch.argmax(logits, dim=-1) # [num_seq,] + best_path = torch.unique_consecutive(best_path, dim=-1) + hypothesis = '' + for i in best_path: + char = self.labels[i] + if char in ['', '']: + continue + if char == '|': + char = ' ' + hypothesis += char + return hypothesis diff --git a/examples/libtorchaudio/speech_recognition/parse_librispeech.py b/examples/libtorchaudio/speech_recognition/parse_librispeech.py new file mode 100644 index 00000000..dcd7aaf9 --- /dev/null +++ b/examples/libtorchaudio/speech_recognition/parse_librispeech.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +"""Parse a directory contains Librispeech dataset. + +Recursively search for "*.trans.txt" file in the given directory and print out + +`\\t\\t` + +example: python parse_librispeech.py LibriSpeech/test-clean + + 1089-134691-0000\t/LibriSpeech/test-clean/1089/134691/1089-134691-0000.flac\tHE COULD WAIT NO LONGER + ... + +Dataset can be obtained from https://www.openslr.org/12 +""" +import argparse +from pathlib import Path + + +def _parse_args(): + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawTextHelpFormatter, + ) + parser.add_argument( + 'input_dir', + type=Path, + help='Directory where `*.trans.txt` files are searched.' + ) + return parser.parse_args() + + +def _parse_transcript(path): + with open(path) as trans_fileobj: + for line in trans_fileobj: + line = line.strip() + if line: + yield line.split(' ', maxsplit=1) + + +def _parse_directory(root_dir: Path): + for trans_file in root_dir.glob('**/*.trans.txt'): + trans_dir = trans_file.parent + for id_, transcription in _parse_transcript(trans_file): + audio_path = trans_dir / f'{id_}.flac' + yield id_, audio_path, transcription + + +def _main(): + args = _parse_args() + for id_, path, transcription in _parse_directory(args.input_dir): + print(f'{id_}\t{path}\t{transcription}') + + +if __name__ == '__main__': + _main() diff --git a/examples/libtorchaudio/speech_recognition/parse_voxforge.py b/examples/libtorchaudio/speech_recognition/parse_voxforge.py new file mode 100644 index 00000000..ea88c608 --- /dev/null +++ b/examples/libtorchaudio/speech_recognition/parse_voxforge.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python +"""Parse a directory contains VoxForge dataset. + +Recursively search for "PROMPTS" file in the given directory and print out + +`\\t\\t` + +example: python parse_voxforge.py voxforge/de/Helge-20150608-aku + + de5-001\t/datasets/voxforge/de/guenter-20140214-afn/wav/de5-001.wav\tES SOLL ETWA FÜNFZIGTAUSEND VERSCHIEDENE SORTEN GEBEN + ... + +Dataset can be obtained from http://www.repository.voxforge1.org/downloads/de/Trunk/Audio/Main/16kHz_16bit/ +""" # noqa: E501 +import os +import argparse +from pathlib import Path + + +def _parse_args(): + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawTextHelpFormatter, + ) + parser.add_argument( + 'input_dir', + type=Path, + help='Directory where `*.trans.txt` files are searched.' + ) + return parser.parse_args() + + +def _parse_prompts(path): + base_dir = path.parent.parent + with open(path) as trans_fileobj: + for line in trans_fileobj: + line = line.strip() + if not line: + continue + + id_, transcript = line.split(' ', maxsplit=1) + if not transcript: + continue + + transcript = transcript.upper() + filename = id_.split('/')[-1] + audio_path = base_dir / 'wav' / f'{filename}.wav' + if os.path.exists(audio_path): + yield id_, audio_path, transcript + + +def _parse_directory(root_dir: Path): + for prompt_file in root_dir.glob('**/PROMPTS'): + try: + yield from _parse_prompts(prompt_file) + except UnicodeDecodeError: + pass + + +def _main(): + args = _parse_args() + for id_, path, transcription in _parse_directory(args.input_dir): + print(f'{id_}\t{path}\t{transcription}') + + +if __name__ == '__main__': + _main() diff --git a/examples/libtorchaudio/speech_recognition/transcribe.cpp b/examples/libtorchaudio/speech_recognition/transcribe.cpp new file mode 100644 index 00000000..e6d65b17 --- /dev/null +++ b/examples/libtorchaudio/speech_recognition/transcribe.cpp @@ -0,0 +1,38 @@ +#include + +int main(int argc, char* argv[]) { + if (argc != 3) { + std::cerr << "Usage: " << argv[0] << " " << std::endl; + return -1; + } + + torch::jit::script::Module loader, encoder, decoder; + std::cout << "Loading module from: " << argv[1] << std::endl; + try { + loader = torch::jit::load(std::string(argv[1]) + "/loader.zip"); + } catch (const c10::Error &error) { + std::cerr << "Failed to load the module:" << error.what() << std::endl; + return -1; + } + try { + encoder = torch::jit::load(std::string(argv[1]) + "/encoder.zip"); + } catch (const c10::Error &error) { + std::cerr << "Failed to load the module:" << error.what() << std::endl; + return -1; + } + try { + decoder = torch::jit::load(std::string(argv[1]) + "/decoder.zip"); + } catch (const c10::Error &error) { + std::cerr << "Failed to load the module:" << error.what() << std::endl; + return -1; + } + + std::cout << "Loading the audio" << std::endl; + auto waveform = loader.forward({c10::IValue(argv[2])}); + std::cout << "Running inference" << std::endl; + auto emission = encoder.forward({waveform}); + std::cout << "Generating the transcription" << std::endl; + auto result = decoder.forward({emission}); + std::cout << result.toString()->string() << std::endl; + std::cout << "Done." << std::endl; +} diff --git a/examples/libtorchaudio/speech_recognition/transcribe_list.cpp b/examples/libtorchaudio/speech_recognition/transcribe_list.cpp new file mode 100644 index 00000000..458a98f5 --- /dev/null +++ b/examples/libtorchaudio/speech_recognition/transcribe_list.cpp @@ -0,0 +1,66 @@ +#include +#include + + +int main(int argc, char* argv[]) { + if (argc != 4) { + std::cerr << "Usage: " << argv[0] << " \n" << std::endl; + std::cerr << " is `\t\t`" << std::endl; + return -1; + } + + torch::jit::script::Module loader, encoder, decoder; + std::cout << "Loading module from: " << argv[1] << std::endl; + try { + loader = torch::jit::load(std::string(argv[1]) + "/loader.zip"); + } catch (const c10::Error &error) { + std::cerr << "Failed to load the module:" << error.what() << std::endl; + return -1; + } + try { + encoder = torch::jit::load(std::string(argv[1]) + "/encoder.zip"); + } catch (const c10::Error &error) { + std::cerr << "Failed to load the module:" << error.what() << std::endl; + return -1; + } + try { + decoder = torch::jit::load(std::string(argv[1]) + "/decoder.zip"); + } catch (const c10::Error &error) { + std::cerr << "Failed to load the module:" << error.what() << std::endl; + return -1; + } + + std::ifstream input_file(argv[2]); + std::string output_dir(argv[3]); + std::ofstream output_ref(output_dir + "/ref.trn"); + std::ofstream output_hyp(output_dir + "/hyp.trn"); + std::string line; + std::chrono::milliseconds t_encode(0); + std::chrono::milliseconds t_decode(0); + while(std::getline(input_file, line)) { + std::istringstream iline(line); + std::string id; + std::string path; + std::string reference; + std::getline(iline, id, '\t'); + std::getline(iline, path, '\t'); + std::getline(iline, reference, '\t'); + + auto waveform = loader.forward({c10::IValue(path)}); + std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now(); + auto emission = encoder.forward({waveform}); + std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now(); + auto result = decoder.forward({emission}); + std::chrono::steady_clock::time_point t2 = std::chrono::steady_clock::now(); + + t_encode += std::chrono::duration_cast(t1 - t0); + t_decode += std::chrono::duration_cast(t2 - t1); + + auto hypothesis = result.toString()->string(); + output_hyp << hypothesis << " (" << id << ")" << std::endl; + output_ref << reference << " (" << id << ")" << std::endl; + std::cout << id << '\t' << hypothesis << std::endl; + } + std::cout << "Time (encode): " << t_encode.count() << " [ms]" << std::endl; + std::cout << "Time (decode): " << t_decode.count() << " [ms]" << std::endl; +} diff --git a/examples/pipeline_tacotron2/README.md b/examples/pipeline_tacotron2/README.md new file mode 100644 index 00000000..aa90d060 --- /dev/null +++ b/examples/pipeline_tacotron2/README.md @@ -0,0 +1,258 @@ +This is an example pipeline for text-to-speech using Tacotron2. + +Here is a [colab example](https://colab.research.google.com/drive/1MPcn1_G5lKozxZ7v8b9yucOD5X5cLK4j?usp=sharing) +that shows how the text-to-speech pipeline is used during inference with the built-in pretrained models. + +## Install required packages + +Required packages +```bash +pip install librosa tqdm inflect joblib +``` + +To use tensorboard +```bash +pip install tensorboard pillow +``` + +## Training Tacotron2 with character as input + +The training of Tacotron2 can be invoked with the following command. + +```bash +python train.py \ + --learning-rate 1e-3 \ + --epochs 1501 \ + --anneal-steps 500 1000 1500 \ + --anneal-factor 0.1 \ + --batch-size 96 \ + --weight-decay 1e-6 \ + --grad-clip 1.0 \ + --text-preprocessor english_characters \ + --logging-dir ./logs \ + --checkpoint-path ./ckpt.pth \ + --dataset-path ./ +``` + +The training script will use all GPUs that is available, please set the +environment variable `CUDA_VISIBLE_DEVICES` if you don't want all GPUs to be used. +The newest checkpoint will be saved to `./ckpt.pth` and the checkpoint with the best validation +loss will be saved to `./best_ckpt.pth`. +The training log will be saved to `./logs/train.log` and the tensorboard results will also +be in `./logs`. + +If `./ckpt.pth` already exist, this script will automatically load the file and try to continue +training from the checkpoint. + +This command takes around 36 hours to train on 8 NVIDIA Tesla V100 GPUs. + +To train the Tacotron2 model to work with the [pretrained wavernn](https://pytorch.org/audio/main/models.html#id10) +with checkpoint_name `"wavernn_10k_epochs_8bits_ljspeech"`, please run the following command instead. + +```bash +python train.py + --learning-rate 1e-3 \ + --epochs 1501 \ + --anneal-steps 500 1000 1500 \ + --anneal-factor 0.1 \ + --sample-rate 22050 \ + --n-fft 2048 \ + --hop-length 275 \ + --win-length 1100 \ + --mel-fmin 40 \ + --mel-fmax 11025 \ + --batch-size 96 \ + --weight-decay 1e-6 \ + --grad-clip 1.0 \ + --text-preprocessor english_characters \ + --logging-dir ./wavernn_logs \ + --checkpoint-path ./ckpt_wavernn.pth \ + --dataset-path ./ +``` + + +## Training Tacotron2 with phoneme as input + +#### Dependencies + +This example use the [DeepPhonemizer](https://github.com/as-ideas/DeepPhonemizer) as +the phonemizer (the function to turn text into phonemes), +please install it with the following command (the code is tested with version 0.0.15). + +```bash +pip install deep-phonemizer==0.0.15 +``` + +Then download the model weights from [their website](https://github.com/as-ideas/DeepPhonemizer) + +The link to the checkpoint that is tested with this example is +[https://public-asai-dl-models.s3.eu-central-1.amazonaws.com/DeepPhonemizer/en_us_cmudict_forward.pt](https://public-asai-dl-models.s3.eu-central-1.amazonaws.com/DeepPhonemizer/en_us_cmudict_forward.pt). + +#### Running training script + +The training of Tacotron2 with english phonemes as input can be invoked with the following command. + +```bash +python train.py \ + --workers 12 \ + --learning-rate 1e-3 \ + --epochs 1501 \ + --anneal-steps 500 1000 1500 \ + --anneal-factor 0.1 \ + --batch-size 96 \ + --weight-decay 1e-6 \ + --grad-clip 1.0 \ + --text-preprocessor english_phonemes \ + --phonemizer DeepPhonemizer \ + --phonemizer-checkpoint ./en_us_cmudict_forward.pt \ + --cmudict-root ./ \ + --logging-dir ./english_phonemes_logs \ + --checkpoint-path ./english_phonemes_ckpt.pth \ + --dataset-path ./ +``` + +Similar to the previous examples, this command will save the log in the directory `./english_phonemes_logs` +and the checkpoint will be saved to `./english_phonemes_ckpt.pth`. + + +To train the Tacotron2 model with english phonemes that works with the +[pretrained wavernn](https://pytorch.org/audio/main/models.html#id10) +with checkpoint_name `"wavernn_10k_epochs_8bits_ljspeech"`, please run the following command. + +```bash +python train.py \ + --workers 12 \ + --learning-rate 1e-3 \ + --epochs 1501 \ + --anneal-steps 500 1000 1500 \ + --anneal-factor 0.1 \ + --sample-rate 22050 \ + --n-fft 2048 \ + --hop-length 275 \ + --win-length 1100 \ + --mel-fmin 40 \ + --mel-fmax 11025 \ + --batch-size 96 \ + --weight-decay 1e-6 \ + --grad-clip 1.0 \ + --text-preprocessor english_phonemes \ + --phonemizer DeepPhonemizer \ + --phonemizer-checkpoint ./en_us_cmudict_forward.pt \ + --cmudict-root ./ \ + --logging-dir ./english_phonemes_wavernn_logs \ + --checkpoint-path ./english_phonemes_wavernn_ckpt.pth \ + --dataset-path ./ +``` + + +## Text-to-speech pipeline + +Here we present an example of how to use Tacotron2 to generate audio from text. +The text-to-speech pipeline goes as follows: +1. text preprocessing: encoder the text into list of symbols (the symbols can represent characters, phonemes, etc.) +2. spectrogram generation: after retrieving the list of symbols, we feed this list to a Tacotron2 model and the model +will output the mel spectrogram. +3. time-domain conversion: when the mel spectrogram is generated, we need to convert it into audio with a vocoder. +Currently, there are three vocoders being supported in this script, which includes the +[WaveRNN](https://pytorch.org/audio/stable/models/wavernn.html), +[Griffin-Lim](https://pytorch.org/audio/stable/transforms.html#griffinlim), and +[Nvidia's WaveGlow](https://pytorch.org/hub/nvidia_deeplearningexamples_tacotron2/). + +The spectro parameters including `n-fft`, `mel-fmin`, `mel-fmax` should be set to the values +used during the training of Tacotron2. + + +#### Pretrained WaveRNN as the Vocoder + +The following command will generate a waveform to `./outputs.wav` +with the text "Hello world!" using WaveRNN as the vocoder. + +```bash +python inference.py --checkpoint-path ${model_path} \ + --vocoder wavernn \ + --n-fft 2048 \ + --mel-fmin 40 \ + --mel-fmax 11025 \ + --input-text "Hello world!" \ + --text-preprocessor english_characters \ + --output-path "./outputs.wav" +``` + +If you want to generate a waveform with a different text with phonemes +as the input to Tacotron2, please use the `--text-preprocessor english_phonemes`. +The following is an example. +(Remember to install the [DeepPhonemizer](https://github.com/as-ideas/DeepPhonemizer) +and download their pretrained weights. + +```bash +python inference.py --checkpoint-path ${model_path} \ + --vocoder wavernn \ + --n-fft 2048 \ + --mel-fmin 40 \ + --mel-fmax 11025 \ + --input-text "Hello world!" \ + --text-preprocessor english_phonemes \ + --phonimizer DeepPhonemizer \ + --phoimizer-checkpoint ./en_us_cmudict_forward.pt \ + --cmudict-root ./ \ + --output-path "./outputs.wav" +``` + +To use torchaudio pretrained models, please see the following example command. +For Tacotron2, we use the checkpoint named `"tacotron2_english_phonemes_1500_epochs_wavernn_ljspeech"`, and +for WaveRNN, we use the checkpoint named `"wavernn_10k_epochs_8bits_ljspeech"`. +See https://pytorch.org/audio/stable/models.html for more checkpoint options for Tacotron2 and WaveRNN. + +```bash +python inference.py \ + --checkpoint-path tacotron2_english_phonemes_1500_epochs_wavernn_ljspeech \ + --wavernn-checkpoint-path wavernn_10k_epochs_8bits_ljspeech \ + --vocoder wavernn \ + --n-fft 2048 \ + --mel-fmin 40 \ + --mel-fmax 11025 \ + --input-text "Hello world!" \ + --text-preprocessor english_phonemes \ + --phonimizer DeepPhonemizer \ + --phoimizer-checkpoint ./en_us_cmudict_forward.pt \ + --cmudict-root ./ \ + --output-path "./outputs.wav" +``` + +#### Griffin-Lim's algorithm as the Vocoder + +The following command will generate a waveform to `./outputs.wav` +with the text "Hello world!" using Griffin-Lim's algorithm as the vocoder. + +```bash +python inference.py --checkpoint-path ${model_path} \ + --vocoder griffin_lim \ + --n-fft 1024 \ + --mel-fmin 0 \ + --mel-fmax 8000 \ + --input-text "Hello world!" \ + --text-preprocessor english_characters \ + --output-path "./outputs.wav" +``` + + +#### Nvidia's Waveglow as the Vocoder + +The following command will generate a waveform to `./outputs.wav` +with the text `"Hello world!"` using Nvidia's WaveGlow as the vocoder. +The WaveGlow is loaded using the following torchhub's API. + +```python +torch.hub.load('NVIDIA/DeepLearningExamples:torchhub', 'nvidia_waveglow', model_math='fp16') +``` + +```bash +python inference.py --checkpoint-path ${model_path} \ + --vocoder nvidia_waveglow \ + --n-fft 1024 \ + --mel-fmin 0 \ + --mel-fmax 8000 \ + --input-text "Hello world!" \ + --text-preprocessor english_characters \ + --output-path "./outputs.wav" +``` diff --git a/examples/pipeline_tacotron2/datasets.py b/examples/pipeline_tacotron2/datasets.py new file mode 100644 index 00000000..a5a68203 --- /dev/null +++ b/examples/pipeline_tacotron2/datasets.py @@ -0,0 +1,171 @@ +# ***************************************************************************** +# Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the NVIDIA CORPORATION nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# ***************************************************************************** + +from typing import Tuple, Callable, List + +import torch +from torch import Tensor + +from torch.utils.data.dataset import random_split +from torchaudio.datasets import LJSPEECH + + +class SpectralNormalization(torch.nn.Module): + def forward(self, input): + return torch.log(torch.clamp(input, min=1e-5)) + + +class InverseSpectralNormalization(torch.nn.Module): + def forward(self, input): + return torch.exp(input) + + +class MapMemoryCache(torch.utils.data.Dataset): + r"""Wrap a dataset so that, whenever a new item is returned, it is saved to memory. + """ + + def __init__(self, dataset): + self.dataset = dataset + self._cache = [None] * len(dataset) + + def __getitem__(self, n): + if self._cache[n] is not None: + return self._cache[n] + + item = self.dataset[n] + self._cache[n] = item + + return item + + def __len__(self): + return len(self.dataset) + + +class Processed(torch.utils.data.Dataset): + def __init__(self, dataset, transforms, text_preprocessor): + self.dataset = dataset + self.transforms = transforms + self.text_preprocessor = text_preprocessor + + def __getitem__(self, key): + item = self.dataset[key] + return self.process_datapoint(item) + + def __len__(self): + return len(self.dataset) + + def process_datapoint(self, item): + melspec = self.transforms(item[0]) + text_norm = torch.IntTensor(self.text_preprocessor(item[2])) + return text_norm, torch.squeeze(melspec, 0) + + +def split_process_dataset(dataset: str, + file_path: str, + val_ratio: float, + transforms: Callable, + text_preprocessor: Callable[[str], List[int]], + ) -> Tuple[torch.utils.data.Dataset, torch.utils.data.Dataset]: + """Returns the Training and validation datasets. + + Args: + dataset (str): The dataset to use. Avaliable options: [`'ljspeech'`] + file_path (str): Path to the data. + val_ratio (float): Path to the data. + transforms (callable): A function/transform that takes in a waveform and + returns a transformed waveform (mel spectrogram in this example). + text_preprocess (callable): A function that takes in a string and + returns a list of integers representing each of the symbol in the string. + + Returns: + train_dataset (`torch.utils.data.Dataset`): The training set. + val_dataset (`torch.utils.data.Dataset`): The validation set. + """ + if dataset == 'ljspeech': + data = LJSPEECH(root=file_path, download=False) + + val_length = int(len(data) * val_ratio) + lengths = [len(data) - val_length, val_length] + train_dataset, val_dataset = random_split(data, lengths) + else: + raise ValueError(f"Expected datasets: `ljspeech`, but found {dataset}") + + train_dataset = Processed(train_dataset, transforms, text_preprocessor) + val_dataset = Processed(val_dataset, transforms, text_preprocessor) + + train_dataset = MapMemoryCache(train_dataset) + val_dataset = MapMemoryCache(val_dataset) + + return train_dataset, val_dataset + + +def text_mel_collate_fn(batch: Tuple[Tensor, Tensor], + n_frames_per_step: int = 1) -> Tuple[Tensor, Tensor, Tensor, Tensor, Tensor]: + """The collate function padding and adjusting the data based on `n_frames_per_step`. + Modified from https://github.com/NVIDIA/DeepLearningExamples + + Args: + batch (tuple of two tensors): the first tensor is the mel spectrogram with shape + (n_batch, n_mels, n_frames), the second tensor is the text with shape (n_batch, ). + n_frames_per_step (int, optional): The number of frames to advance every step. + + Returns: + text_padded (Tensor): The input text to Tacotron2 with shape (n_batch, max of ``text_lengths``). + text_lengths (Tensor): The length of each text with shape (n_batch). + mel_specgram_padded (Tensor): The target mel spectrogram + with shape (n_batch, n_mels, max of ``mel_specgram_lengths``) + mel_specgram_lengths (Tensor): The length of each mel spectrogram with shape (n_batch). + gate_padded (Tensor): The ground truth gate output + with shape (n_batch, max of ``mel_specgram_lengths``) + """ + text_lengths, ids_sorted_decreasing = torch.sort( + torch.LongTensor([len(x[0]) for x in batch]), dim=0, descending=True) + max_input_len = text_lengths[0] + + text_padded = torch.zeros((len(batch), max_input_len), dtype=torch.int64) + for i in range(len(ids_sorted_decreasing)): + text = batch[ids_sorted_decreasing[i]][0] + text_padded[i, :text.size(0)] = text + + # Right zero-pad mel-spec + num_mels = batch[0][1].size(0) + max_target_len = max([x[1].size(1) for x in batch]) + if max_target_len % n_frames_per_step != 0: + max_target_len += n_frames_per_step - max_target_len % n_frames_per_step + assert max_target_len % n_frames_per_step == 0 + + # include mel padded and gate padded + mel_specgram_padded = torch.zeros((len(batch), num_mels, max_target_len), dtype=torch.float32) + gate_padded = torch.zeros((len(batch), max_target_len), dtype=torch.float32) + mel_specgram_lengths = torch.LongTensor(len(batch)) + for i in range(len(ids_sorted_decreasing)): + mel = batch[ids_sorted_decreasing[i]][1] + mel_specgram_padded[i, :, :mel.size(1)] = mel + mel_specgram_lengths[i] = mel.size(1) + gate_padded[i, mel.size(1) - 1:] = 1 + + return text_padded, text_lengths, mel_specgram_padded, mel_specgram_lengths, gate_padded diff --git a/examples/pipeline_tacotron2/inference.py b/examples/pipeline_tacotron2/inference.py new file mode 100644 index 00000000..a89fffb5 --- /dev/null +++ b/examples/pipeline_tacotron2/inference.py @@ -0,0 +1,353 @@ +""" +Text-to-speech pipeline using Tacotron2. +""" + +from functools import partial +import argparse +import os +import random +import sys + +import torch +import torchaudio +import numpy as np +from torchaudio.models import Tacotron2 +from torchaudio.models import tacotron2 as pretrained_tacotron2 + +from utils import prepare_input_sequence +from datasets import InverseSpectralNormalization +from text.text_preprocessing import ( + available_symbol_set, + available_phonemizers, + get_symbol_list, + text_to_sequence, +) + + +def parse_args(): + r""" + Parse commandline arguments. + """ + from torchaudio.models.tacotron2 import _MODEL_CONFIG_AND_URLS as tacotron2_config_and_urls + from torchaudio.models.wavernn import _MODEL_CONFIG_AND_URLS as wavernn_config_and_urls + + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + '--checkpoint-name', + type=str, + default=None, + choices=list(tacotron2_config_and_urls.keys()), + help='[string] The name of the checkpoint to load.' + ) + parser.add_argument( + '--checkpoint-path', + type=str, + default=None, + help='[string] Path to the checkpoint file.' + ) + parser.add_argument( + '--output-path', + type=str, + default="./audio.wav", + help='[string] Path to the output .wav file.' + ) + parser.add_argument( + '--input-text', + '-i', + type=str, + default="Hello world", + help='[string] Type in something here and TTS will generate it!' + ) + parser.add_argument( + '--vocoder', + default='nvidia_waveglow', + choices=['griffin_lim', 'wavernn', 'nvidia_waveglow'], + type=str, + help="Select the vocoder to use.", + ) + parser.add_argument( + "--jit", + default=False, + action="store_true", + help="If used, the model and inference function is jitted." + ) + + preprocessor = parser.add_argument_group('text preprocessor setup') + preprocessor.add_argument( + '--text-preprocessor', + default='english_characters', + type=str, + choices=available_symbol_set, + help='select text preprocessor to use.' + ) + preprocessor.add_argument( + '--phonemizer', + default="DeepPhonemizer", + type=str, + choices=available_phonemizers, + help='select phonemizer to use, only used when text-preprocessor is "english_phonemes"' + ) + preprocessor.add_argument( + '--phonemizer-checkpoint', + default="./en_us_cmudict_forward.pt", + type=str, + help='the path or name of the checkpoint for the phonemizer, ' + 'only used when text-preprocessor is "english_phonemes"' + ) + preprocessor.add_argument( + '--cmudict-root', + default="./", + type=str, + help='the root directory for storing CMU dictionary files' + ) + + audio = parser.add_argument_group('audio parameters') + audio.add_argument( + '--sample-rate', + default=22050, + type=int, + help='Sampling rate' + ) + audio.add_argument( + '--n-fft', + default=1024, + type=int, + help='Filter length for STFT' + ) + audio.add_argument( + '--n-mels', + default=80, + type=int, + help='' + ) + audio.add_argument( + '--mel-fmin', + default=0.0, + type=float, + help='Minimum mel frequency' + ) + audio.add_argument( + '--mel-fmax', + default=8000.0, + type=float, + help='Maximum mel frequency' + ) + + # parameters for WaveRNN + wavernn = parser.add_argument_group('WaveRNN parameters') + wavernn.add_argument( + '--wavernn-checkpoint-name', + default="wavernn_10k_epochs_8bits_ljspeech", + choices=list(wavernn_config_and_urls.keys()), + help="Select the WaveRNN checkpoint." + ) + wavernn.add_argument( + "--wavernn-loss", + default="crossentropy", + choices=["crossentropy"], + type=str, + help="The type of loss the WaveRNN pretrained model is trained on.", + ) + wavernn.add_argument( + "--wavernn-no-batch-inference", + default=False, + action="store_true", + help="Don't use batch inference for WaveRNN inference." + ) + wavernn.add_argument( + "--wavernn-no-mulaw", + default=False, + action="store_true", + help="Don't use mulaw decoder to decode the signal." + ) + wavernn.add_argument( + "--wavernn-batch-timesteps", + default=11000, + type=int, + help="The time steps for each batch. Only used when batch inference is used", + ) + wavernn.add_argument( + "--wavernn-batch-overlap", + default=550, + type=int, + help="The overlapping time steps between batches. Only used when batch inference is used", + ) + + return parser + + +def unwrap_distributed(state_dict): + r"""torch.distributed.DistributedDataParallel wraps the model with an additional "module.". + This function unwraps this layer so that the weights can be loaded on models with a single GPU. + + Args: + state_dict: Original state_dict. + + Return: + unwrapped_state_dict: Unwrapped state_dict. + """ + + return {k.replace('module.', ''): v for k, v in state_dict.items()} + + +def nvidia_waveglow_vocode(mel_specgram, device, jit=False): + waveglow = torch.hub.load('NVIDIA/DeepLearningExamples:torchhub', 'nvidia_waveglow', model_math='fp16') + waveglow = waveglow.remove_weightnorm(waveglow) + waveglow = waveglow.to(device) + waveglow.eval() + + if args.jit: + raise ValueError("Vocoder option `nvidia_waveglow is not jittable.") + + with torch.no_grad(): + waveform = waveglow.infer(mel_specgram).cpu() + + return waveform + + +def wavernn_vocode(mel_specgram, wavernn_checkpoint_name, wavernn_loss, wavernn_no_mulaw, + wavernn_no_batch_inference, wavernn_batch_timesteps, wavernn_batch_overlap, + device, jit): + from torchaudio.models import wavernn + sys.path.append(os.path.join(os.path.dirname(__file__), "../pipeline_wavernn")) + from wavernn_inference_wrapper import WaveRNNInferenceWrapper + from processing import NormalizeDB + + wavernn_model = wavernn(wavernn_checkpoint_name).eval().to(device) + wavernn_inference_model = WaveRNNInferenceWrapper(wavernn_model) + + if jit: + wavernn_inference_model = torch.jit.script(wavernn_inference_model) + + # WaveRNN spectro setting for default checkpoint + # n_fft = 2048 + # n_mels = 80 + # win_length = 1100 + # hop_length = 275 + # f_min = 40 + # f_max = 11025 + + transforms = torch.nn.Sequential( + InverseSpectralNormalization(), + NormalizeDB(min_level_db=-100, normalization=True), + ) + mel_specgram = transforms(mel_specgram.cpu()) + + with torch.no_grad(): + waveform = wavernn_inference_model(mel_specgram.to(device), + loss_name=wavernn_loss, + mulaw=(not wavernn_no_mulaw), + batched=(not wavernn_no_batch_inference), + timesteps=wavernn_batch_timesteps, + overlap=wavernn_batch_overlap,) + return waveform.unsqueeze(0) + + +def griffin_lim_vocode(mel_specgram, n_fft, n_mels, sample_rate, mel_fmin, mel_fmax, jit, ): + from torchaudio.transforms import GriffinLim, InverseMelScale + + inv_norm = InverseSpectralNormalization() + inv_mel = InverseMelScale( + n_stft=(n_fft // 2 + 1), + n_mels=n_mels, + sample_rate=sample_rate, + f_min=mel_fmin, + f_max=mel_fmax, + mel_scale="slaney", + norm='slaney', + ) + griffin_lim = GriffinLim( + n_fft=n_fft, + power=1, + hop_length=256, + win_length=1024, + ) + + vocoder = torch.nn.Sequential( + inv_norm, + inv_mel, + griffin_lim + ) + + if jit: + vocoder = torch.jit.script(vocoder) + + waveform = vocoder(mel_specgram.cpu()) + return waveform + + +def main(args): + torch.manual_seed(0) + random.seed(0) + np.random.seed(0) + + device = "cuda" if torch.cuda.is_available() else "cpu" + + if args.checkpoint_path is None and args.checkpoint_name is None: + raise ValueError("Either --checkpoint-path or --checkpoint-name must be specified.") + elif args.checkpoint_path is not None and args.checkpoint_name is not None: + raise ValueError("Both --checkpoint-path and --checkpoint-name are specified, " + "can only specify one.") + + n_symbols = len(get_symbol_list(args.text_preprocessor)) + text_preprocessor = partial( + text_to_sequence, + symbol_list=args.text_preprocessor, + phonemizer=args.phonemizer, + checkpoint=args.phonemizer_checkpoint, + cmudict_root=args.cmudict_root, + ) + + if args.checkpoint_path is not None: + tacotron2 = Tacotron2(n_symbol=n_symbols) + tacotron2.load_state_dict( + unwrap_distributed(torch.load(args.checkpoint_path, map_location=device)['state_dict'])) + tacotron2 = tacotron2.to(device).eval() + elif args.checkpoint_name is not None: + tacotron2 = pretrained_tacotron2(args.checkpoint_name).to(device).eval() + + if n_symbols != tacotron2.n_symbols: + raise ValueError("the number of symbols for text_preprocessor ({n_symbols}) " + "should match the number of symbols for the" + "pretrained tacotron2 ({tacotron2.n_symbols}).") + + if args.jit: + tacotron2 = torch.jit.script(tacotron2) + + sequences, lengths = prepare_input_sequence([args.input_text], + text_processor=text_preprocessor) + sequences, lengths = sequences.long().to(device), lengths.long().to(device) + with torch.no_grad(): + mel_specgram, _, _ = tacotron2.infer(sequences, lengths) + + if args.vocoder == "nvidia_waveglow": + waveform = nvidia_waveglow_vocode(mel_specgram=mel_specgram, device=device, jit=args.jit) + + elif args.vocoder == "wavernn": + waveform = wavernn_vocode(mel_specgram=mel_specgram, + wavernn_checkpoint_name=args.wavernn_checkpoint_name, + wavernn_loss=args.wavernn_loss, + wavernn_no_mulaw=args.wavernn_no_mulaw, + wavernn_no_batch_inference=args.wavernn_no_batch_inference, + wavernn_batch_timesteps=args.wavernn_batch_timesteps, + wavernn_batch_overlap=args.wavernn_batch_overlap, + device=device, + jit=args.jit) + + elif args.vocoder == "griffin_lim": + waveform = griffin_lim_vocode(mel_specgram=mel_specgram, + n_fft=args.n_fft, + n_mels=args.n_mels, + sample_rate=args.sample_rate, + mel_fmin=args.mel_fmin, + mel_fmax=args.mel_fmax, + jit=args.jit) + + torchaudio.save(args.output_path, waveform, args.sample_rate) + + +if __name__ == "__main__": + parser = parse_args() + args, _ = parser.parse_known_args() + + main(args) diff --git a/examples/pipeline_tacotron2/loss.py b/examples/pipeline_tacotron2/loss.py new file mode 100644 index 00000000..38f4b8bb --- /dev/null +++ b/examples/pipeline_tacotron2/loss.py @@ -0,0 +1,82 @@ +# ***************************************************************************** +# Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the NVIDIA CORPORATION nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# ***************************************************************************** + +from typing import Tuple + +from torch import nn, Tensor + + +class Tacotron2Loss(nn.Module): + """Tacotron2 loss function modified from: + https://github.com/NVIDIA/DeepLearningExamples/blob/master/PyTorch/SpeechSynthesis/Tacotron2/tacotron2/loss_function.py + """ + + def __init__(self): + super().__init__() + + self.mse_loss = nn.MSELoss(reduction="mean") + self.bce_loss = nn.BCEWithLogitsLoss(reduction="mean") + + def forward( + self, + model_outputs: Tuple[Tensor, Tensor, Tensor], + targets: Tuple[Tensor, Tensor], + ) -> Tuple[Tensor, Tensor, Tensor]: + r"""Pass the input through the Tacotron2 loss. + + The original implementation was introduced in + *Natural TTS Synthesis by Conditioning WaveNet on Mel Spectrogram Predictions* + [:footcite:`shen2018natural`]. + + Args: + model_outputs (tuple of three Tensors): The outputs of the + Tacotron2. These outputs should include three items: + (1) the predicted mel spectrogram before the postnet (``mel_specgram``) + with shape (batch, mel, time). + (2) predicted mel spectrogram after the postnet (``mel_specgram_postnet``) + with shape (batch, mel, time), and + (3) the stop token prediction (``gate_out``) with shape (batch, ). + targets (tuple of two Tensors): The ground truth mel spectrogram (batch, mel, time) and + stop token with shape (batch, ). + + Returns: + mel_loss (Tensor): The mean MSE of the mel_specgram and ground truth mel spectrogram + with shape ``torch.Size([])``. + mel_postnet_loss (Tensor): The mean MSE of the mel_specgram_postnet and + ground truth mel spectrogram with shape ``torch.Size([])``. + gate_loss (Tensor): The mean binary cross entropy loss of + the prediction on the stop token with shape ``torch.Size([])``. + """ + mel_target, gate_target = targets[0], targets[1] + gate_target = gate_target.view(-1, 1) + + mel_specgram, mel_specgram_postnet, gate_out = model_outputs + gate_out = gate_out.view(-1, 1) + mel_loss = self.mse_loss(mel_specgram, mel_target) + mel_postnet_loss = self.mse_loss(mel_specgram_postnet, mel_target) + gate_loss = self.bce_loss(gate_out, gate_target) + return mel_loss, mel_postnet_loss, gate_loss diff --git a/examples/pipeline_tacotron2/text/__init__.py b/examples/pipeline_tacotron2/text/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/examples/pipeline_tacotron2/text/numbers.py b/examples/pipeline_tacotron2/text/numbers.py new file mode 100644 index 00000000..d42e82e1 --- /dev/null +++ b/examples/pipeline_tacotron2/text/numbers.py @@ -0,0 +1,116 @@ +# ***************************************************************************** +# Copyright (c) 2017 Keith Ito +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ***************************************************************************** +""" +Modified from https://github.com/keithito/tacotron +""" + +import inflect +import re + + +_inflect = inflect.engine() +_comma_number_re = re.compile(r'([0-9][0-9\,]+[0-9])') +_pounds_re = re.compile(r'£([0-9\,]*[0-9]+)') +_dollars_re = re.compile(r'\$([0-9\.\,]*[0-9]+)') +_decimal_number_re = re.compile(r'([0-9]+\.[0-9]+)') +_ordinal_re = re.compile(r'[0-9]+(st|nd|rd|th)') +_number_re = re.compile(r'[0-9]+') + + +def _remove_commas(text: str) -> str: + return re.sub(_comma_number_re, lambda m: m.group(1).replace(',', ''), text) + + +def _expand_pounds(text: str) -> str: + return re.sub(_pounds_re, r'\1 pounds', text) + + +def _expand_dollars_repl_fn(m): + """The replacement function for expanding dollars.""" + match = m.group(1) + parts = match.split('.') + if len(parts) > 2: + return match + ' dollars' # Unexpected format + dollars = int(parts[0]) if parts[0] else 0 + if len(parts) > 1 and parts[1]: + if len(parts[1]) == 1: + # handle the case where we have one digit after the decimal point + cents = int(parts[1]) * 10 + else: + cents = int(parts[1]) + else: + cents = 0 + if dollars and cents: + dollar_unit = 'dollar' if dollars == 1 else 'dollars' + cent_unit = 'cent' if cents == 1 else 'cents' + return '%s %s, %s %s' % (dollars, dollar_unit, cents, cent_unit) + elif dollars: + dollar_unit = 'dollar' if dollars == 1 else 'dollars' + return '%s %s' % (dollars, dollar_unit) + elif cents: + cent_unit = 'cent' if cents == 1 else 'cents' + return '%s %s' % (cents, cent_unit) + else: + return 'zero dollars' + + +def _expand_dollars(text: str) -> str: + return re.sub(_dollars_re, _expand_dollars_repl_fn, text) + + +def _expand_decimal_point(text: str) -> str: + return re.sub(_decimal_number_re, lambda m: m.group(1).replace('.', ' point '), text) + + +def _expand_ordinal(text: str) -> str: + return re.sub(_ordinal_re, lambda m: _inflect.number_to_words(m.group(0)), text) + + +def _expand_number_repl_fn(m): + """The replacement function for expanding number.""" + num = int(m.group(0)) + if num > 1000 and num < 3000: + if num == 2000: + return 'two thousand' + elif num > 2000 and num < 2010: + return 'two thousand ' + _inflect.number_to_words(num % 100) + elif num % 100 == 0: + return _inflect.number_to_words(num // 100) + ' hundred' + else: + return _inflect.number_to_words(num, andword='', zero='oh', group=2).replace(', ', ' ') + else: + return _inflect.number_to_words(num, andword='') + + +def _expand_number(text: str) -> str: + return re.sub(_number_re, _expand_number_repl_fn, text) + + +def normalize_numbers(text: str) -> str: + text = _remove_commas(text) + text = _expand_pounds(text) + text = _expand_dollars(text) + text = _expand_decimal_point(text) + text = _expand_ordinal(text) + text = _expand_number(text) + return text diff --git a/examples/pipeline_tacotron2/text/text_preprocessing.py b/examples/pipeline_tacotron2/text/text_preprocessing.py new file mode 100644 index 00000000..8ca6ae49 --- /dev/null +++ b/examples/pipeline_tacotron2/text/text_preprocessing.py @@ -0,0 +1,164 @@ +# ***************************************************************************** +# Copyright (c) 2017 Keith Ito +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# ***************************************************************************** +""" +Modified from https://github.com/keithito/tacotron +""" + +from typing import List, Union, Optional +import re + +from unidecode import unidecode +from torchaudio.datasets import CMUDict + +from .numbers import normalize_numbers + + +# Regular expression matching whitespace: +_whitespace_re = re.compile(r'\s+') + +# List of (regular expression, replacement) pairs for abbreviations: +_abbreviations = [(re.compile('\\b%s\\.' % x[0], re.IGNORECASE), x[1]) for x in [ + ('mrs', 'misess'), + ('mr', 'mister'), + ('dr', 'doctor'), + ('st', 'saint'), + ('co', 'company'), + ('jr', 'junior'), + ('maj', 'major'), + ('gen', 'general'), + ('drs', 'doctors'), + ('rev', 'reverend'), + ('lt', 'lieutenant'), + ('hon', 'honorable'), + ('sgt', 'sergeant'), + ('capt', 'captain'), + ('esq', 'esquire'), + ('ltd', 'limited'), + ('col', 'colonel'), + ('ft', 'fort'), +]] + +_pad = '_' +_punctuation = '!\'(),.:;? ' +_special = '-' +_letters = 'abcdefghijklmnopqrstuvwxyz' + +symbols = [_pad] + list(_special) + list(_punctuation) + list(_letters) +_phonemizer = None + + +available_symbol_set = set(["english_characters", "english_phonemes"]) +available_phonemizers = set(["DeepPhonemizer"]) + + +def get_symbol_list(symbol_list: str = "english_characters", + cmudict_root: Optional[str] = "./") -> List[str]: + if symbol_list == "english_characters": + return [_pad] + list(_special) + list(_punctuation) + list(_letters) + elif symbol_list == "english_phonemes": + return [_pad] + list(_special) + list(_punctuation) + CMUDict(cmudict_root).symbols + else: + raise ValueError(f"The `symbol_list` {symbol_list} is not supported." + f"Supported `symbol_list` includes {available_symbol_set}.") + + +def word_to_phonemes(sent: str, phonemizer: str, checkpoint: str) -> List[str]: + if phonemizer == "DeepPhonemizer": + from dp.phonemizer import Phonemizer + global _phonemizer + _other_symbols = ''.join(list(_special) + list(_punctuation)) + _phone_symbols_re = r'(\[[A-Z]+?\]|' + '[' + _other_symbols + '])' # [\[([A-Z]+?)\]|[-!'(),.:;? ]] + + if _phonemizer is None: + # using a global variable so that we don't have to relode checkpoint + # everytime this function is called + _phonemizer = Phonemizer.from_checkpoint(checkpoint) + + # Example: + # sent = "hello world!" + # '[HH][AH][L][OW] [W][ER][L][D]!' + sent = _phonemizer(sent, lang='en_us') + + # ['[HH]', '[AH]', '[L]', '[OW]', ' ', '[W]', '[ER]', '[L]', '[D]', '!'] + ret = re.findall(_phone_symbols_re, sent) + + # ['HH', 'AH', 'L', 'OW', ' ', 'W', 'ER', 'L', 'D', '!'] + ret = [r.replace("[", "").replace("]", "") for r in ret] + + return ret + else: + raise ValueError(f"The `phonemizer` {phonemizer} is not supported. " + "Supported `symbol_list` includes `'DeepPhonemizer'`.") + + +def text_to_sequence(sent: str, + symbol_list: Union[str, List[str]] = "english_characters", + phonemizer: Optional[str] = "DeepPhonemizer", + checkpoint: Optional[str] = "./en_us_cmudict_forward.pt", + cmudict_root: Optional[str] = "./") -> List[int]: + r'''Converts a string of text to a sequence of IDs corresponding to the symbols in the text. + + Args: + sent (str): The input sentence to convert to a sequence. + symbol_list (str or List of string, optional): When the input is a string, available options include + "english_characters" and "english_phonemes". When the input is a list of string, ``symbol_list`` will + directly be used as the symbol to encode. (Default: "english_characters") + phonemizer (str or None, optional): The phonemizer to use. Only used when ``symbol_list`` is "english_phonemes". + Available options include "DeepPhonemizer". (Default: "DeepPhonemizer") + checkpoint (str or None, optional): The path to the checkpoint of the phonemizer. Only used when + ``symbol_list`` is "english_phonemes". (Default: "./en_us_cmudict_forward.pt") + cmudict_root (str or None, optional): The path to the directory where the CMUDict dataset is found or + downloaded. Only used when ``symbol_list`` is "english_phonemes". (Default: "./") + + Returns: + List of integers corresponding to the symbols in the sentence. + + Examples: + >>> text_to_sequence("hello world!", "english_characters") + [19, 16, 23, 23, 26, 11, 34, 26, 29, 23, 15, 2] + >>> text_to_sequence("hello world!", "english_phonemes") + [54, 20, 65, 69, 11, 92, 44, 65, 38, 2] + ''' + if symbol_list == "english_phonemes": + if any(param is None for param in [phonemizer, checkpoint, cmudict_root]): + raise ValueError( + "When `symbol_list` is 'english_phonemes', " + "all of `phonemizer`, `checkpoint`, and `cmudict_root` must be provided.") + + sent = unidecode(sent) # convert to ascii + sent = sent.lower() # lower case + sent = normalize_numbers(sent) # expand numbers + for regex, replacement in _abbreviations: # expand abbreviations + sent = re.sub(regex, replacement, sent) + sent = re.sub(_whitespace_re, ' ', sent) # collapse whitespace + + if isinstance(symbol_list, list): + symbols = symbol_list + elif isinstance(symbol_list, str): + symbols = get_symbol_list(symbol_list, cmudict_root=cmudict_root) + if symbol_list == "english_phonemes": + sent = word_to_phonemes(sent, phonemizer=phonemizer, checkpoint=checkpoint) + + _symbol_to_id = {s: i for i, s in enumerate(symbols)} + + return [_symbol_to_id[s] for s in sent if s in _symbol_to_id] diff --git a/examples/pipeline_tacotron2/train.py b/examples/pipeline_tacotron2/train.py new file mode 100644 index 00000000..4fe93000 --- /dev/null +++ b/examples/pipeline_tacotron2/train.py @@ -0,0 +1,528 @@ +# ***************************************************************************** +# Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the NVIDIA CORPORATION nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# ***************************************************************************** +""" +Modified from +https://github.com/NVIDIA/DeepLearningExamples/blob/master/PyTorch/SpeechSynthesis/Tacotron2/train.py +""" + +import argparse +from datetime import datetime +from functools import partial +import logging +import random +import os +from time import time + +import torch +import torchaudio +import torch.multiprocessing as mp +import torch.distributed as dist +from torch.utils.tensorboard import SummaryWriter +from torch.utils.data import DataLoader +from torch.optim import Adam +from torchaudio.models import Tacotron2 +from tqdm import tqdm +import matplotlib.pyplot as plt +plt.switch_backend('agg') + +from datasets import text_mel_collate_fn, split_process_dataset, SpectralNormalization +from utils import save_checkpoint +from loss import Tacotron2Loss +from text.text_preprocessing import ( + available_symbol_set, + available_phonemizers, + get_symbol_list, + text_to_sequence, +) + + +logging.basicConfig(format='%(asctime)s %(levelname)-8s %(message)s', + level=logging.INFO, datefmt='%Y-%m-%d %H:%M:%S') +logger = logging.getLogger(os.path.basename(__file__)) + + +def parse_args(parser): + """Parse commandline arguments.""" + + parser.add_argument("--dataset", default="ljspeech", choices=["ljspeech"], type=str, + help="select dataset to train with") + parser.add_argument('--logging-dir', type=str, default=None, + help='directory to save the log files') + parser.add_argument('--dataset-path', type=str, default='./', + help='path to dataset') + parser.add_argument("--val-ratio", default=0.1, type=float, + help="the ratio of waveforms for validation") + + parser.add_argument('--anneal-steps', nargs='*', + help='epochs after which decrease learning rate') + parser.add_argument('--anneal-factor', type=float, choices=[0.1, 0.3], default=0.1, + help='factor for annealing learning rate') + + parser.add_argument('--master-addr', default=None, type=str, + help='the address to use for distributed training') + parser.add_argument('--master-port', default=None, type=str, + help='the port to use for distributed training') + + preprocessor = parser.add_argument_group('text preprocessor setup') + preprocessor.add_argument('--text-preprocessor', default='english_characters', type=str, + choices=available_symbol_set, + help='select text preprocessor to use.') + preprocessor.add_argument('--phonemizer', type=str, choices=available_phonemizers, + help='select phonemizer to use, only used when text-preprocessor is "english_phonemes"') + preprocessor.add_argument('--phonemizer-checkpoint', type=str, + help='the path or name of the checkpoint for the phonemizer, ' + 'only used when text-preprocessor is "english_phonemes"') + preprocessor.add_argument('--cmudict-root', default="./", type=str, + help='the root directory for storing cmudictionary files') + + # training + training = parser.add_argument_group('training setup') + training.add_argument('--epochs', type=int, required=True, + help='number of total epochs to run') + training.add_argument('--checkpoint-path', type=str, default='', + help='checkpoint path. If a file exists, ' + 'the program will load it and resume training.') + training.add_argument('--workers', default=8, type=int, + help="number of data loading workers") + training.add_argument("--validate-and-checkpoint-freq", default=10, type=int, metavar="N", + help="validation and saving checkpoint frequency in epochs",) + training.add_argument("--logging-freq", default=10, type=int, metavar="N", + help="logging frequency in epochs") + + optimization = parser.add_argument_group('optimization setup') + optimization.add_argument('--learning-rate', default=1e-3, type=float, + help='initial learing rate') + optimization.add_argument('--weight-decay', default=1e-6, type=float, + help='weight decay') + optimization.add_argument('--batch-size', default=32, type=int, + help='batch size per GPU') + optimization.add_argument('--grad-clip', default=5.0, type=float, + help='clipping gradient with maximum gradient norm value') + + # model parameters + model = parser.add_argument_group('model parameters') + model.add_argument('--mask-padding', action='store_true', default=False, + help='use mask padding') + model.add_argument('--symbols-embedding-dim', default=512, type=int, + help='input embedding dimension') + + # encoder + model.add_argument('--encoder-embedding-dim', default=512, type=int, + help='encoder embedding dimension') + model.add_argument('--encoder-n-convolution', default=3, type=int, + help='number of encoder convolutions') + model.add_argument('--encoder-kernel-size', default=5, type=int, + help='encoder kernel size') + # decoder + model.add_argument('--n-frames-per-step', default=1, type=int, + help='number of frames processed per step (currently only 1 is supported)') + model.add_argument('--decoder-rnn-dim', default=1024, type=int, + help='number of units in decoder LSTM') + model.add_argument('--decoder-dropout', default=0.1, type=float, + help='dropout probability for decoder LSTM') + model.add_argument('--decoder-max-step', default=2000, type=int, + help='maximum number of output mel spectrograms') + model.add_argument('--decoder-no-early-stopping', action='store_true', default=False, + help='stop decoding only when all samples are finished') + + # attention model + model.add_argument('--attention-hidden-dim', default=128, type=int, + help='dimension of attention hidden representation') + model.add_argument('--attention-rnn-dim', default=1024, type=int, + help='number of units in attention LSTM') + model.add_argument('--attention-location-n-filter', default=32, type=int, + help='number of filters for location-sensitive attention') + model.add_argument('--attention-location-kernel-size', default=31, type=int, + help='kernel size for location-sensitive attention') + model.add_argument('--attention-dropout', default=0.1, type=float, + help='dropout probability for attention LSTM') + + model.add_argument('--prenet-dim', default=256, type=int, + help='number of ReLU units in prenet layers') + + # mel-post processing network parameters + model.add_argument('--postnet-n-convolution', default=5, type=float, + help='number of postnet convolutions') + model.add_argument('--postnet-kernel-size', default=5, type=float, + help='postnet kernel size') + model.add_argument('--postnet-embedding-dim', default=512, type=float, + help='postnet embedding dimension') + + model.add_argument('--gate-threshold', default=0.5, type=float, + help='probability threshold for stop token') + + # audio parameters + audio = parser.add_argument_group('audio parameters') + audio.add_argument('--sample-rate', default=22050, type=int, + help='Sampling rate') + audio.add_argument('--n-fft', default=1024, type=int, + help='Filter length for STFT') + audio.add_argument('--hop-length', default=256, type=int, + help='Hop (stride) length') + audio.add_argument('--win-length', default=1024, type=int, + help='Window length') + audio.add_argument('--n-mels', default=80, type=int, + help='') + audio.add_argument('--mel-fmin', default=0.0, type=float, + help='Minimum mel frequency') + audio.add_argument('--mel-fmax', default=8000.0, type=float, + help='Maximum mel frequency') + + return parser + + +def adjust_learning_rate(epoch, optimizer, learning_rate, + anneal_steps, anneal_factor): + """Adjust learning rate base on the initial setting.""" + p = 0 + if anneal_steps is not None: + for _, a_step in enumerate(anneal_steps): + if epoch >= int(a_step): + p = p + 1 + + if anneal_factor == 0.3: + lr = learning_rate * ((0.1 ** (p // 2)) * (1.0 if p % 2 == 0 else 0.3)) + else: + lr = learning_rate * (anneal_factor ** p) + + for param_group in optimizer.param_groups: + param_group['lr'] = lr + + +def to_gpu(x): + x = x.contiguous() + if torch.cuda.is_available(): + x = x.cuda(non_blocking=True) + return x + + +def batch_to_gpu(batch): + text_padded, text_lengths, mel_specgram_padded, mel_specgram_lengths, gate_padded = batch + text_padded = to_gpu(text_padded).long() + text_lengths = to_gpu(text_lengths).long() + mel_specgram_padded = to_gpu(mel_specgram_padded).float() + gate_padded = to_gpu(gate_padded).float() + mel_specgram_lengths = to_gpu(mel_specgram_lengths).long() + x = (text_padded, text_lengths, mel_specgram_padded, mel_specgram_lengths) + y = (mel_specgram_padded, gate_padded) + return x, y + + +def training_step(model, train_batch, batch_idx): + (text_padded, text_lengths, mel_specgram_padded, mel_specgram_lengths), y = batch_to_gpu(train_batch) + y_pred = model(text_padded, text_lengths, mel_specgram_padded, mel_specgram_lengths) + y[0].requires_grad = False + y[1].requires_grad = False + losses = Tacotron2Loss()(y_pred[:3], y) + return losses[0] + losses[1] + losses[2], losses + + +def validation_step(model, val_batch, batch_idx): + (text_padded, text_lengths, mel_specgram_padded, mel_specgram_lengths), y = batch_to_gpu(val_batch) + y_pred = model(text_padded, text_lengths, mel_specgram_padded, mel_specgram_lengths) + losses = Tacotron2Loss()(y_pred[:3], y) + return losses[0] + losses[1] + losses[2], losses + + +def reduce_tensor(tensor, world_size): + rt = tensor.clone() + dist.all_reduce(rt, op=dist.ReduceOp.SUM) + if rt.is_floating_point(): + rt = rt / world_size + else: + rt = rt // world_size + return rt + + +def log_additional_info(writer, model, loader, epoch): + model.eval() + data = next(iter(loader)) + with torch.no_grad(): + (text_padded, text_lengths, mel_specgram_padded, mel_specgram_lengths), _ = batch_to_gpu(data) + y_pred = model(text_padded, text_lengths, mel_specgram_padded, mel_specgram_lengths) + mel_out, mel_out_postnet, gate_out, alignment = y_pred + + fig = plt.figure() + ax = plt.gca() + ax.imshow(mel_out[0].cpu().numpy()) + writer.add_figure("trn/mel_out", fig, epoch) + fig = plt.figure() + ax = plt.gca() + ax.imshow(mel_out_postnet[0].cpu().numpy()) + writer.add_figure("trn/mel_out_postnet", fig, epoch) + writer.add_image("trn/gate_out", torch.tile(gate_out[:1], (10, 1)), epoch, dataformats="HW") + writer.add_image("trn/alignment", alignment[0], epoch, dataformats="HW") + + +def get_datasets(args): + text_preprocessor = partial( + text_to_sequence, + symbol_list=args.text_preprocessor, + phonemizer=args.phonemizer, + checkpoint=args.phonemizer_checkpoint, + cmudict_root=args.cmudict_root, + ) + + transforms = torch.nn.Sequential( + torchaudio.transforms.MelSpectrogram( + sample_rate=args.sample_rate, + n_fft=args.n_fft, + win_length=args.win_length, + hop_length=args.hop_length, + f_min=args.mel_fmin, + f_max=args.mel_fmax, + n_mels=args.n_mels, + mel_scale='slaney', + normalized=False, + power=1, + norm='slaney', + ), + SpectralNormalization() + ) + trainset, valset = split_process_dataset( + args.dataset, args.dataset_path, args.val_ratio, transforms, text_preprocessor) + return trainset, valset + + +def train(rank, world_size, args): + dist.init_process_group("nccl", rank=rank, world_size=world_size) + + if rank == 0 and args.logging_dir: + if not os.path.isdir(args.logging_dir): + os.makedirs(args.logging_dir) + filehandler = logging.FileHandler(os.path.join(args.logging_dir, 'train.log')) + filehandler.setLevel(logging.INFO) + logger.addHandler(filehandler) + + writer = SummaryWriter(log_dir=args.logging_dir) + else: + writer = None + + torch.manual_seed(0) + + torch.cuda.set_device(rank) + + symbols = get_symbol_list(args.text_preprocessor) + + model = Tacotron2( + mask_padding=args.mask_padding, + n_mels=args.n_mels, + n_symbol=len(symbols), + n_frames_per_step=args.n_frames_per_step, + symbol_embedding_dim=args.symbols_embedding_dim, + encoder_embedding_dim=args.encoder_embedding_dim, + encoder_n_convolution=args.encoder_n_convolution, + encoder_kernel_size=args.encoder_kernel_size, + decoder_rnn_dim=args.decoder_rnn_dim, + decoder_max_step=args.decoder_max_step, + decoder_dropout=args.decoder_dropout, + decoder_early_stopping=(not args.decoder_no_early_stopping), + attention_rnn_dim=args.attention_rnn_dim, + attention_hidden_dim=args.attention_hidden_dim, + attention_location_n_filter=args.attention_location_n_filter, + attention_location_kernel_size=args.attention_location_kernel_size, + attention_dropout=args.attention_dropout, + prenet_dim=args.prenet_dim, + postnet_n_convolution=args.postnet_n_convolution, + postnet_kernel_size=args.postnet_kernel_size, + postnet_embedding_dim=args.postnet_embedding_dim, + gate_threshold=args.gate_threshold, + ).cuda(rank) + model = torch.nn.SyncBatchNorm.convert_sync_batchnorm(model) + model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[rank]) + + optimizer = Adam(model.parameters(), lr=args.learning_rate) + + best_loss = float("inf") + start_epoch = 0 + + if args.checkpoint_path and os.path.isfile(args.checkpoint_path): + logger.info(f"Checkpoint: loading '{args.checkpoint_path}'") + map_location = {'cuda:%d' % 0: 'cuda:%d' % rank} + checkpoint = torch.load(args.checkpoint_path, map_location=map_location) + + start_epoch = checkpoint["epoch"] + best_loss = checkpoint["best_loss"] + + model.load_state_dict(checkpoint["state_dict"]) + optimizer.load_state_dict(checkpoint["optimizer"]) + + logger.info( + f"Checkpoint: loaded '{args.checkpoint_path}' at epoch {checkpoint['epoch']}" + ) + + trainset, valset = get_datasets(args) + + train_sampler = torch.utils.data.distributed.DistributedSampler( + trainset, + shuffle=True, + num_replicas=world_size, + rank=rank, + ) + val_sampler = torch.utils.data.distributed.DistributedSampler( + valset, + shuffle=False, + num_replicas=world_size, + rank=rank, + ) + + loader_params = { + "batch_size": args.batch_size, + "num_workers": args.workers, + "prefetch_factor": 1024, + 'persistent_workers': True, + "shuffle": False, + "pin_memory": True, + "drop_last": False, + "collate_fn": partial(text_mel_collate_fn, n_frames_per_step=args.n_frames_per_step), + } + + train_loader = DataLoader(trainset, sampler=train_sampler, **loader_params) + val_loader = DataLoader(valset, sampler=val_sampler, **loader_params) + dist.barrier() + + for epoch in range(start_epoch, args.epochs): + start = time() + + model.train() + trn_loss, counts = 0, 0 + + if rank == 0: + iterator = tqdm(enumerate(train_loader), desc=f"Epoch {epoch}", total=len(train_loader)) + else: + iterator = enumerate(train_loader) + + for i, batch in iterator: + adjust_learning_rate(epoch, optimizer, args.learning_rate, + args.anneal_steps, args.anneal_factor) + + model.zero_grad() + + loss, losses = training_step(model, batch, i) + + loss.backward() + torch.nn.utils.clip_grad_norm_( + model.parameters(), args.grad_clip) + + optimizer.step() + + if rank == 0 and writer: + global_iters = epoch * len(train_loader) + writer.add_scalar("trn/mel_loss", losses[0], global_iters) + writer.add_scalar("trn/mel_postnet_loss", losses[1], global_iters) + writer.add_scalar("trn/gate_loss", losses[2], global_iters) + + trn_loss += loss * len(batch[0]) + counts += len(batch[0]) + + trn_loss = trn_loss / counts + + trn_loss = reduce_tensor(trn_loss, world_size) + if rank == 0: + logger.info(f"[Epoch: {epoch}] time: {time()-start}; trn_loss: {trn_loss}") + if writer: + writer.add_scalar("trn_loss", trn_loss, epoch) + + if ((epoch + 1) % args.validate_and_checkpoint_freq == 0) or (epoch == args.epochs - 1): + + val_start_time = time() + model.eval() + + val_loss, counts = 0, 0 + iterator = tqdm(enumerate(val_loader), desc=f"[Rank: {rank}; Epoch: {epoch}; Eval]", total=len(val_loader)) + + with torch.no_grad(): + for val_batch_idx, val_batch in iterator: + val_loss = val_loss + validation_step(model, val_batch, val_batch_idx)[0] * len(val_batch[0]) + counts = counts + len(val_batch[0]) + val_loss = val_loss / counts + + val_loss = reduce_tensor(val_loss, world_size) + if rank == 0 and writer: + writer.add_scalar("val_loss", val_loss, epoch) + log_additional_info(writer, model, val_loader, epoch) + + if rank == 0: + is_best = val_loss < best_loss + best_loss = min(val_loss, best_loss) + logger.info(f"[Rank: {rank}, Epoch: {epoch}; Eval] time: {time()-val_start_time}; val_loss: {val_loss}") + logger.info(f"[Epoch: {epoch}] Saving checkpoint to {args.checkpoint_path}") + save_checkpoint( + { + "epoch": epoch + 1, + "state_dict": model.state_dict(), + "best_loss": best_loss, + "optimizer": optimizer.state_dict(), + }, + is_best, + args.checkpoint_path, + ) + + dist.destroy_process_group() + + +def main(args): + logger.info("Start time: {}".format(str(datetime.now()))) + + torch.manual_seed(0) + random.seed(0) + + if args.master_addr is not None: + os.environ['MASTER_ADDR'] = args.master_addr + elif 'MASTER_ADDR' not in os.environ: + os.environ['MASTER_ADDR'] = 'localhost' + + if args.master_port is not None: + os.environ['MASTER_PORT'] = args.master_port + elif 'MASTER_PORT' not in os.environ: + os.environ['MASTER_PORT'] = '17778' + + device_counts = torch.cuda.device_count() + + logger.info(f"# available GPUs: {device_counts}") + + # download dataset is not already downloaded + if args.dataset == 'ljspeech': + if not os.path.exists(os.path.join(args.dataset_path, 'LJSpeech-1.1')): + from torchaudio.datasets import LJSPEECH + LJSPEECH(root=args.dataset_path, download=True) + + if device_counts == 1: + train(0, 1, args) + else: + mp.spawn(train, args=(device_counts, args, ), + nprocs=device_counts, join=True) + + logger.info(f"End time: {datetime.now()}") + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='PyTorch Tacotron 2 Training') + parser = parse_args(parser) + args, _ = parser.parse_known_args() + + main(args) diff --git a/examples/pipeline_tacotron2/utils.py b/examples/pipeline_tacotron2/utils.py new file mode 100644 index 00000000..dca668a1 --- /dev/null +++ b/examples/pipeline_tacotron2/utils.py @@ -0,0 +1,76 @@ +# ***************************************************************************** +# Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the NVIDIA CORPORATION nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL NVIDIA CORPORATION BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# ***************************************************************************** + +import logging +import os +import shutil +from typing import List, Tuple, Callable + +import torch +from torch import Tensor + + +def save_checkpoint(state, is_best, filename): + r"""Save the model to a temporary file first, then copy it to filename, + in case signals interrupt the torch.save() process. + """ + torch.save(state, filename) + logging.info(f"Checkpoint saved to {filename}") + + if is_best: + path, best_filename = os.path.split(filename) + best_filename = os.path.join(path, "best_" + best_filename) + shutil.copyfile(filename, best_filename) + logging.info(f"Current best checkpoint saved to {best_filename}") + + +def pad_sequences(batch: List[Tensor]) -> Tuple[Tensor, Tensor]: + r"""Right zero-pad all one-hot text sequences to max input length. + + Modified from https://github.com/NVIDIA/DeepLearningExamples. + """ + input_lengths, ids_sorted_decreasing = torch.sort( + torch.LongTensor([len(x) for x in batch]), dim=0, descending=True) + max_input_len = input_lengths[0] + + text_padded = torch.LongTensor(len(batch), max_input_len) + text_padded.zero_() + for i in range(len(ids_sorted_decreasing)): + text = batch[ids_sorted_decreasing[i]] + text_padded[i, :text.size(0)] = text + + return text_padded, input_lengths + + +def prepare_input_sequence(texts: List[str], + text_processor: Callable[[str], List[int]]) -> Tuple[Tensor, Tensor]: + d = [] + for text in texts: + d.append(torch.IntTensor(text_processor(text)[:])) + + text_padded, input_lengths = pad_sequences(d) + return text_padded, input_lengths diff --git a/examples/pipeline_wav2letter/README.md b/examples/pipeline_wav2letter/README.md new file mode 100644 index 00000000..afecf5c2 --- /dev/null +++ b/examples/pipeline_wav2letter/README.md @@ -0,0 +1,50 @@ +This is an example pipeline for speech recognition using a greedy or Viterbi CTC decoder, along with the Wav2Letter model trained on LibriSpeech, see [Wav2Letter: an End-to-End ConvNet-based Speech Recognition System](https://arxiv.org/pdf/1609.03193.pdf). Wav2Letter and LibriSpeech are available in torchaudio. + +### Usage + +More information about each command line parameters is available with the `--help` option. An example can be invoked as follows. +```bash +DATASET_ROOT = // +DATASET_FOLDER_IN_ARCHIVE = 'LibriSpeech' + +python main.py \ + --reduce-lr-valid \ + --dataset-root "${DATASET_ROOT}" \ + --dataset-folder-in-archive "${DATASET_FOLDER_IN_ARCHIVE}" \ + --dataset-train train-clean-100 train-clean-360 train-other-500 \ + --dataset-valid dev-clean \ + --batch-size 128 \ + --learning-rate .6 \ + --momentum .8 \ + --weight-decay .00001 \ + --clip-grad 0. \ + --gamma .99 \ + --hop-length 160 \ + --win-length 400 \ + --n-bins 13 \ + --normalize \ + --optimizer adadelta \ + --scheduler reduceonplateau \ + --epochs 40 +``` + +With these default parameters, we get 13.3 %CER and 41.9 %WER on dev-clean after 40 epochs (character and word error rates, respectively) while training on train-clean. The tail of the output is the following. + +```json +... +{"name": "train", "epoch": 40, "batch char error": 925, "batch char total": 22563, "batch char error rate": 0.040996321411159865, "epoch char error": 1135098.0, "epoch char total": 23857713.0, "epoch char error rate": 0.047577821059378154, "batch word error": 791, "batch word total": 4308, "batch word error rate": 0.18361188486536675, "epoch word error": 942906.0, "epoch word total": 4569507.0, "epoch word error rate": 0.20634742435015418, "lr": 0.06, "batch size": 128, "n_channel": 13, "n_time": 1685, "dataset length": 132096.0, "iteration": 1032.0, "loss": 0.07428030669689178, "cumulative loss": 90.47326805442572, "average loss": 0.08766789540157531, "iteration time": 1.9895553588867188, "epoch time": 2036.8874564170837} +{"name": "train", "epoch": 40, "batch char error": 1131, "batch char total": 24260, "batch char error rate": 0.0466199505358615, "epoch char error": 1136229.0, "epoch char total": 23881973.0, "epoch char error rate": 0.04757684802675223, "batch word error": 957, "batch word total": 4657, "batch word error rate": 0.2054971011380717, "epoch word error": 943863.0, "epoch word total": 4574164.0, "epoch word error rate": 0.20634655862798099, "lr": 0.06, "batch size": 128, "n_channel": 13, "n_time": 1641, "dataset length": 132224.0, "iteration": 1033.0, "loss": 0.08775319904088974, "cumulative loss": 90.5610212534666, "average loss": 0.08766797798012256, "iteration time": 2.108018159866333, "epoch time": 2038.99547457695} +{"name": "train", "epoch": 40, "batch char error": 1099, "batch char total": 23526, "batch char error rate": 0.0467142735696676, "epoch char error": 1137328.0, "epoch char total": 23905499.0, "epoch char error rate": 0.04757599914563591, "batch word error": 936, "batch word total": 4544, "batch word error rate": 0.20598591549295775, "epoch word error": 944799.0, "epoch word total": 4578708.0, "epoch word error rate": 0.20634620071863066, "lr": 0.06, "batch size": 128, "n_channel": 13, "n_time": 1682, "dataset length": 132352.0, "iteration": 1034.0, "loss": 0.0791337713599205, "cumulative loss": 90.64015502482653, "average loss": 0.08765972439538348, "iteration time": 2.0329701900482178, "epoch time": 2041.0284447669983} +{"name": "train", "epoch": 40, "batch char error": 1023, "batch char total": 22399, "batch char error rate": 0.045671681771507655, "epoch char error": 1138351.0, "epoch char total": 23927898.0, "epoch char error rate": 0.04757421650660664, "batch word error": 863, "batch word total": 4318, "batch word error rate": 0.1998610467809171, "epoch word error": 945662.0, "epoch word total": 4583026.0, "epoch word error rate": 0.20634009058643787, "lr": 0.06, "batch size": 128, "n_channel": 13, "n_time": 1644, "dataset length": 132480.0, "iteration": 1035.0, "loss": 0.07874362915754318, "cumulative loss": 90.71889865398407, "average loss": 0.08765110981061262, "iteration time": 1.9106628894805908, "epoch time": 2042.9391076564789} +{"name": "validation", "epoch": 40, "cumulative loss": 12.095281183719635, "dataset length": 2688.0, "iteration": 21.0, "batch char error": 1867, "batch char total": 14792, "batch char error rate": 0.12621687398593834, "epoch char error": 37119.0, "epoch char total": 280923.0, "epoch char error rate": 0.13213229247872194, "batch word error": 1155, "batch word total": 2841, "batch word error rate": 0.4065469904963041, "epoch word error": 22601.0, "epoch word total": 54008.0, "epoch word error rate": 0.418475040734706, "average loss": 0.575965770653316, "validation time": 24.185853481292725} +``` +As can be seen in the output above, the information reported at each iteration and epoch (e.g. loss, character error rate, word error rate) is printed to standard output in the form of one json per line. One way to import the output in python with pandas is by saving the standard output to a file, and then using `pandas.read_json(filename, lines=True)`. + +## Structure of pipeline + +* `main.py` -- the entry point +* `ctc_decoders.py` -- the greedy CTC decoder +* `datasets.py` -- the function to split and process librispeech, a collate factory function +* `languagemodels.py` -- a class to encode and decode strings +* `metrics.py` -- the levenshtein edit distance +* `utils.py` -- functions to log metrics, save checkpoint, and count parameters diff --git a/examples/pipeline_wav2letter/ctc_decoders.py b/examples/pipeline_wav2letter/ctc_decoders.py new file mode 100644 index 00000000..b4f155d6 --- /dev/null +++ b/examples/pipeline_wav2letter/ctc_decoders.py @@ -0,0 +1,15 @@ +from torch import topk + + +class GreedyDecoder: + def __call__(self, outputs): + """Greedy Decoder. Returns highest probability of class labels for each timestep + + Args: + outputs (torch.Tensor): shape (input length, batch size, number of classes (including blank)) + + Returns: + torch.Tensor: class labels per time step. + """ + _, indices = topk(outputs, k=1, dim=-1) + return indices[..., 0] diff --git a/examples/pipeline_wav2letter/datasets.py b/examples/pipeline_wav2letter/datasets.py new file mode 100644 index 00000000..79b05b2c --- /dev/null +++ b/examples/pipeline_wav2letter/datasets.py @@ -0,0 +1,113 @@ +import torch +from torchaudio.datasets import LIBRISPEECH + + +class MapMemoryCache(torch.utils.data.Dataset): + """ + Wrap a dataset so that, whenever a new item is returned, it is saved to memory. + """ + + def __init__(self, dataset): + self.dataset = dataset + self._cache = [None] * len(dataset) + + def __getitem__(self, n): + if self._cache[n] is not None: + return self._cache[n] + + item = self.dataset[n] + self._cache[n] = item + + return item + + def __len__(self): + return len(self.dataset) + + +class Processed(torch.utils.data.Dataset): + def __init__(self, dataset, transforms, encode): + self.dataset = dataset + self.transforms = transforms + self.encode = encode + + def __getitem__(self, key): + item = self.dataset[key] + return self.process_datapoint(item) + + def __len__(self): + return len(self.dataset) + + def process_datapoint(self, item): + transformed = item[0] + target = item[2].lower() + + transformed = self.transforms(transformed) + transformed = transformed[0, ...].transpose(0, -1) + + target = self.encode(target) + target = torch.tensor(target, dtype=torch.long, device=transformed.device) + + return transformed, target + + +def split_process_librispeech( + datasets, transforms, language_model, root, folder_in_archive, +): + def create(tags, cache=True): + + if isinstance(tags, str): + tags = [tags] + if isinstance(transforms, list): + transform_list = transforms + else: + transform_list = [transforms] + + data = torch.utils.data.ConcatDataset( + [ + Processed( + LIBRISPEECH( + root, tag, folder_in_archive=folder_in_archive, download=False, + ), + transform, + language_model.encode, + ) + for tag, transform in zip(tags, transform_list) + ] + ) + + data = MapMemoryCache(data) + return data + + # For performance, we cache all datasets + return tuple(create(dataset) for dataset in datasets) + + +def collate_factory(model_length_function, transforms=None): + + if transforms is None: + transforms = torch.nn.Sequential() + + def collate_fn(batch): + + tensors = [transforms(b[0]) for b in batch if b] + + tensors_lengths = torch.tensor( + [model_length_function(t) for t in tensors], + dtype=torch.long, + device=tensors[0].device, + ) + + tensors = torch.nn.utils.rnn.pad_sequence(tensors, batch_first=True) + tensors = tensors.transpose(1, -1) + + targets = [b[1] for b in batch if b] + target_lengths = torch.tensor( + [target.shape[0] for target in targets], + dtype=torch.long, + device=tensors.device, + ) + targets = torch.nn.utils.rnn.pad_sequence(targets, batch_first=True) + + return tensors, targets, tensors_lengths, target_lengths + + return collate_fn diff --git a/examples/pipeline_wav2letter/languagemodels.py b/examples/pipeline_wav2letter/languagemodels.py new file mode 100644 index 00000000..d66858ea --- /dev/null +++ b/examples/pipeline_wav2letter/languagemodels.py @@ -0,0 +1,38 @@ +import collections +import itertools + + +class LanguageModel: + def __init__(self, labels, char_blank, char_space): + + self.char_space = char_space + self.char_blank = char_blank + + labels = list(labels) + self.length = len(labels) + enumerated = list(enumerate(labels)) + flipped = [(sub[1], sub[0]) for sub in enumerated] + + d1 = collections.OrderedDict(enumerated) + d2 = collections.OrderedDict(flipped) + self.mapping = {**d1, **d2} + + def encode(self, iterable): + if isinstance(iterable, list): + return [self.encode(i) for i in iterable] + else: + return [self.mapping[i] + self.mapping[self.char_blank] for i in iterable] + + def decode(self, tensor): + if len(tensor) > 0 and isinstance(tensor[0], list): + return [self.decode(t) for t in tensor] + else: + # not idempotent, since clean string + x = (self.mapping[i] for i in tensor) + x = "".join(i for i, _ in itertools.groupby(x)) + x = x.replace(self.char_blank, "") + # x = x.strip() + return x + + def __len__(self): + return self.length diff --git a/examples/pipeline_wav2letter/main.py b/examples/pipeline_wav2letter/main.py new file mode 100644 index 00000000..16682230 --- /dev/null +++ b/examples/pipeline_wav2letter/main.py @@ -0,0 +1,663 @@ +import argparse +import logging +import os +import string +from datetime import datetime +from time import time + +import torch +import torchaudio +from torch.optim import SGD, Adadelta, Adam, AdamW +from torch.optim.lr_scheduler import ExponentialLR, ReduceLROnPlateau +from torch.utils.data import DataLoader +from torchaudio.datasets.utils import bg_iterator +from torchaudio.functional import edit_distance +from torchaudio.models.wav2letter import Wav2Letter + +from ctc_decoders import GreedyDecoder +from datasets import collate_factory, split_process_librispeech +from languagemodels import LanguageModel +from transforms import Normalize, UnsqueezeFirst +from utils import MetricLogger, count_parameters, save_checkpoint + + +def parse_args(): + parser = argparse.ArgumentParser() + + parser.add_argument( + "--type", + metavar="T", + default="mfcc", + choices=["waveform", "mfcc"], + help="input type for model", + ) + parser.add_argument( + "--freq-mask", + default=0, + type=int, + metavar="N", + help="maximal width of frequency mask", + ) + parser.add_argument( + "--win-length", + default=400, + type=int, + metavar="N", + help="width of spectrogram window", + ) + parser.add_argument( + "--hop-length", + default=160, + type=int, + metavar="N", + help="width of spectrogram window", + ) + parser.add_argument( + "--time-mask", + default=0, + type=int, + metavar="N", + help="maximal width of time mask", + ) + parser.add_argument( + "--workers", + default=0, + type=int, + metavar="N", + help="number of data loading workers", + ) + parser.add_argument( + "--checkpoint", + default="", + type=str, + metavar="PATH", + help="path to latest checkpoint", + ) + parser.add_argument( + "--epochs", + default=200, + type=int, + metavar="N", + help="number of total epochs to run", + ) + parser.add_argument( + "--start-epoch", default=0, type=int, metavar="N", help="manual epoch number" + ) + parser.add_argument( + "--reduce-lr-valid", + action="store_true", + help="reduce learning rate based on validation loss", + ) + parser.add_argument( + "--normalize", action="store_true", help="normalize model input" + ) + parser.add_argument( + "--progress-bar", action="store_true", help="use progress bar while training" + ) + parser.add_argument( + "--decoder", + metavar="D", + default="greedy", + choices=["greedy"], + help="decoder to use", + ) + parser.add_argument( + "--batch-size", default=128, type=int, metavar="N", help="mini-batch size" + ) + parser.add_argument( + "--n-bins", + default=13, + type=int, + metavar="N", + help="number of bins in transforms", + ) + parser.add_argument( + "--optimizer", + metavar="OPT", + default="adadelta", + choices=["sgd", "adadelta", "adam", "adamw"], + help="optimizer to use", + ) + parser.add_argument( + "--scheduler", + metavar="S", + default="reduceonplateau", + choices=["exponential", "reduceonplateau"], + help="optimizer to use", + ) + parser.add_argument( + "--learning-rate", + default=0.6, + type=float, + metavar="LR", + help="initial learning rate", + ) + parser.add_argument( + "--gamma", + default=0.99, + type=float, + metavar="GAMMA", + help="learning rate exponential decay constant", + ) + parser.add_argument( + "--momentum", default=0.8, type=float, metavar="M", help="momentum" + ) + parser.add_argument( + "--weight-decay", default=1e-5, type=float, metavar="W", help="weight decay" + ) + parser.add_argument("--eps", metavar="EPS", type=float, default=1e-8) + parser.add_argument("--rho", metavar="RHO", type=float, default=0.95) + parser.add_argument("--clip-grad", metavar="NORM", type=float, default=0.0) + parser.add_argument( + "--dataset-root", + type=str, + help="specify dataset root folder", + ) + parser.add_argument( + "--dataset-folder-in-archive", + type=str, + help="specify dataset folder in archive", + ) + parser.add_argument( + "--dataset-train", + default=["train-clean-100"], + nargs="+", + type=str, + help="select which part of librispeech to train with", + ) + parser.add_argument( + "--dataset-valid", + default=["dev-clean"], + nargs="+", + type=str, + help="select which part of librispeech to validate with", + ) + parser.add_argument( + "--distributed", action="store_true", help="enable DistributedDataParallel" + ) + parser.add_argument("--seed", type=int, default=0, help="random seed") + parser.add_argument( + "--world-size", type=int, default=8, help="the world size to initiate DPP" + ) + parser.add_argument("--jit", action="store_true", help="if used, model is jitted") + + args = parser.parse_args() + logging.info(args) + return args + + +def setup_distributed(rank, world_size): + os.environ["MASTER_ADDR"] = "localhost" + os.environ["MASTER_PORT"] = "12355" + + # initialize the process group + torch.distributed.init_process_group("nccl", rank=rank, world_size=world_size) + + +def model_length_function(tensor): + if tensor.shape[1] == 1: + # waveform mode + return int(tensor.shape[0]) // 160 // 2 + 1 + return int(tensor.shape[0]) // 2 + 1 + + +def compute_error_rates(outputs, targets, decoder, language_model, metric): + output = outputs.transpose(0, 1).to("cpu") + output = decoder(output) + + # Compute CER + + output = language_model.decode(output.tolist()) + target = language_model.decode(targets.tolist()) + + print_length = 20 + for i in range(2): + # Print a few examples + output_print = output[i].ljust(print_length)[:print_length] + target_print = target[i].ljust(print_length)[:print_length] + logging.info("Target: %s Output: %s", target_print, output_print) + + cers = [edit_distance(t, o) for t, o in zip(target, output)] + cers = sum(cers) + n = sum(len(t) for t in target) + metric["batch char error"] = cers + metric["batch char total"] = n + metric["batch char error rate"] = cers / n + metric["epoch char error"] += cers + metric["epoch char total"] += n + metric["epoch char error rate"] = metric["epoch char error"] / metric["epoch char total"] + + # Compute WER + + output = [o.split(language_model.char_space) for o in output] + target = [t.split(language_model.char_space) for t in target] + + wers = [edit_distance(t, o) for t, o in zip(target, output)] + wers = sum(wers) + n = sum(len(t) for t in target) + metric["batch word error"] = wers + metric["batch word total"] = n + metric["batch word error rate"] = wers / n + metric["epoch word error"] += wers + metric["epoch word total"] += n + metric["epoch word error rate"] = metric["epoch word error"] / metric["epoch word total"] + + +def train_one_epoch( + model, + criterion, + optimizer, + scheduler, + data_loader, + decoder, + language_model, + device, + epoch, + clip_grad, + disable_logger=False, + reduce_lr_on_plateau=False, +): + + model.train() + + metric = MetricLogger("train", disable=disable_logger) + metric["epoch"] = epoch + + for inputs, targets, tensors_lengths, target_lengths in bg_iterator( + data_loader, maxsize=2 + ): + + start = time() + inputs = inputs.to(device, non_blocking=True) + targets = targets.to(device, non_blocking=True) + + # keep batch first for data parallel + outputs = model(inputs).transpose(-1, -2).transpose(0, 1) + + # CTC + # outputs: input length, batch size, number of classes (including blank) + # targets: batch size, max target length + # input_lengths: batch size + # target_lengths: batch size + + loss = criterion(outputs, targets, tensors_lengths, target_lengths) + + optimizer.zero_grad() + loss.backward() + + if clip_grad > 0: + metric["gradient"] = torch.nn.utils.clip_grad_norm_( + model.parameters(), clip_grad + ) + + optimizer.step() + + compute_error_rates(outputs, targets, decoder, language_model, metric) + + try: + metric["lr"] = scheduler.get_last_lr()[0] + except AttributeError: + metric["lr"] = optimizer.param_groups[0]["lr"] + + metric["batch size"] = len(inputs) + metric["n_channel"] = inputs.shape[1] + metric["n_time"] = inputs.shape[-1] + metric["dataset length"] += metric["batch size"] + metric["iteration"] += 1 + metric["loss"] = loss.item() + metric["cumulative loss"] += metric["loss"] + metric["average loss"] = metric["cumulative loss"] / metric["iteration"] + metric["iteration time"] = time() - start + metric["epoch time"] += metric["iteration time"] + metric() + + if reduce_lr_on_plateau and isinstance(scheduler, ReduceLROnPlateau): + scheduler.step(metric["average loss"]) + elif not isinstance(scheduler, ReduceLROnPlateau): + scheduler.step() + + +def evaluate( + model, + criterion, + data_loader, + decoder, + language_model, + device, + epoch, + disable_logger=False, +): + + with torch.no_grad(): + + model.eval() + start = time() + metric = MetricLogger("validation", disable=disable_logger) + metric["epoch"] = epoch + + for inputs, targets, tensors_lengths, target_lengths in bg_iterator( + data_loader, maxsize=2 + ): + + inputs = inputs.to(device, non_blocking=True) + targets = targets.to(device, non_blocking=True) + + # keep batch first for data parallel + outputs = model(inputs).transpose(-1, -2).transpose(0, 1) + + # CTC + # outputs: input length, batch size, number of classes (including blank) + # targets: batch size, max target length + # input_lengths: batch size + # target_lengths: batch size + + metric["cumulative loss"] += criterion( + outputs, targets, tensors_lengths, target_lengths + ).item() + + metric["dataset length"] += len(inputs) + metric["iteration"] += 1 + + compute_error_rates(outputs, targets, decoder, language_model, metric) + + metric["average loss"] = metric["cumulative loss"] / metric["iteration"] + metric["validation time"] = time() - start + metric() + + return metric["average loss"] + + +def main(rank, args): + + # Distributed setup + + if args.distributed: + setup_distributed(rank, args.world_size) + + not_main_rank = args.distributed and rank != 0 + + logging.info("Start time: %s", datetime.now()) + + # Explicitly set seed to make sure models created in separate processes + # start from same random weights and biases + torch.manual_seed(args.seed) + + # Empty CUDA cache + torch.cuda.empty_cache() + + # Change backend for flac files + torchaudio.set_audio_backend("soundfile") + + # Transforms + + melkwargs = { + "n_fft": args.win_length, + "n_mels": args.n_bins, + "hop_length": args.hop_length, + } + + sample_rate_original = 16000 + + if args.type == "mfcc": + transforms = torch.nn.Sequential( + torchaudio.transforms.MFCC( + sample_rate=sample_rate_original, + n_mfcc=args.n_bins, + melkwargs=melkwargs, + ), + ) + num_features = args.n_bins + elif args.type == "waveform": + transforms = torch.nn.Sequential(UnsqueezeFirst()) + num_features = 1 + else: + raise ValueError("Model type not supported") + + if args.normalize: + transforms = torch.nn.Sequential(transforms, Normalize()) + + augmentations = torch.nn.Sequential() + if args.freq_mask: + augmentations = torch.nn.Sequential( + augmentations, + torchaudio.transforms.FrequencyMasking(freq_mask_param=args.freq_mask), + ) + if args.time_mask: + augmentations = torch.nn.Sequential( + augmentations, + torchaudio.transforms.TimeMasking(time_mask_param=args.time_mask), + ) + + # Text preprocessing + + char_blank = "*" + char_space = " " + char_apostrophe = "'" + labels = char_blank + char_space + char_apostrophe + string.ascii_lowercase + language_model = LanguageModel(labels, char_blank, char_space) + + # Dataset + + training, validation = split_process_librispeech( + [args.dataset_train, args.dataset_valid], + [transforms, transforms], + language_model, + root=args.dataset_root, + folder_in_archive=args.dataset_folder_in_archive, + ) + + # Decoder + + if args.decoder == "greedy": + decoder = GreedyDecoder() + else: + raise ValueError("Selected decoder not supported") + + # Model + + model = Wav2Letter( + num_classes=language_model.length, + input_type=args.type, + num_features=num_features, + ) + + if args.jit: + model = torch.jit.script(model) + + if args.distributed: + n = torch.cuda.device_count() // args.world_size + devices = list(range(rank * n, (rank + 1) * n)) + model = model.to(devices[0]) + model = torch.nn.parallel.DistributedDataParallel(model, device_ids=devices) + else: + devices = ["cuda" if torch.cuda.is_available() else "cpu"] + model = model.to(devices[0], non_blocking=True) + model = torch.nn.DataParallel(model) + + n = count_parameters(model) + logging.info("Number of parameters: %s", n) + + # Optimizer + + if args.optimizer == "adadelta": + optimizer = Adadelta( + model.parameters(), + lr=args.learning_rate, + weight_decay=args.weight_decay, + eps=args.eps, + rho=args.rho, + ) + elif args.optimizer == "sgd": + optimizer = SGD( + model.parameters(), + lr=args.learning_rate, + momentum=args.momentum, + weight_decay=args.weight_decay, + ) + elif args.optimizer == "adam": + optimizer = Adam( + model.parameters(), + lr=args.learning_rate, + momentum=args.momentum, + weight_decay=args.weight_decay, + ) + elif args.optimizer == "adamw": + optimizer = AdamW( + model.parameters(), + lr=args.learning_rate, + momentum=args.momentum, + weight_decay=args.weight_decay, + ) + else: + raise ValueError("Selected optimizer not supported") + + if args.scheduler == "exponential": + scheduler = ExponentialLR(optimizer, gamma=args.gamma) + elif args.scheduler == "reduceonplateau": + scheduler = ReduceLROnPlateau(optimizer, patience=10, threshold=1e-3) + else: + raise ValueError("Selected scheduler not supported") + + criterion = torch.nn.CTCLoss( + blank=language_model.mapping[char_blank], zero_infinity=False + ) + + # Data Loader + + collate_fn_train = collate_factory(model_length_function, augmentations) + collate_fn_valid = collate_factory(model_length_function) + + loader_training_params = { + "num_workers": args.workers, + "pin_memory": True, + "shuffle": True, + "drop_last": True, + } + loader_validation_params = loader_training_params.copy() + loader_validation_params["shuffle"] = False + + loader_training = DataLoader( + training, + batch_size=args.batch_size, + collate_fn=collate_fn_train, + **loader_training_params, + ) + loader_validation = DataLoader( + validation, + batch_size=args.batch_size, + collate_fn=collate_fn_valid, + **loader_validation_params, + ) + + # Setup checkpoint + + best_loss = 1.0 + + load_checkpoint = args.checkpoint and os.path.isfile(args.checkpoint) + + if args.distributed: + torch.distributed.barrier() + + if load_checkpoint: + logging.info("Checkpoint: loading %s", args.checkpoint) + checkpoint = torch.load(args.checkpoint) + + args.start_epoch = checkpoint["epoch"] + best_loss = checkpoint["best_loss"] + + model.load_state_dict(checkpoint["state_dict"]) + optimizer.load_state_dict(checkpoint["optimizer"]) + scheduler.load_state_dict(checkpoint["scheduler"]) + + logging.info( + "Checkpoint: loaded '%s' at epoch %s", args.checkpoint, checkpoint["epoch"] + ) + else: + logging.info("Checkpoint: not found") + + save_checkpoint( + { + "epoch": args.start_epoch, + "state_dict": model.state_dict(), + "best_loss": best_loss, + "optimizer": optimizer.state_dict(), + "scheduler": scheduler.state_dict(), + }, + False, + args.checkpoint, + not_main_rank, + ) + + if args.distributed: + torch.distributed.barrier() + + torch.autograd.set_detect_anomaly(False) + + for epoch in range(args.start_epoch, args.epochs): + + logging.info("Epoch: %s", epoch) + + train_one_epoch( + model, + criterion, + optimizer, + scheduler, + loader_training, + decoder, + language_model, + devices[0], + epoch, + args.clip_grad, + not_main_rank, + not args.reduce_lr_valid, + ) + + loss = evaluate( + model, + criterion, + loader_validation, + decoder, + language_model, + devices[0], + epoch, + not_main_rank, + ) + + if args.reduce_lr_valid and isinstance(scheduler, ReduceLROnPlateau): + scheduler.step(loss) + + is_best = loss < best_loss + best_loss = min(loss, best_loss) + save_checkpoint( + { + "epoch": epoch + 1, + "state_dict": model.state_dict(), + "best_loss": best_loss, + "optimizer": optimizer.state_dict(), + "scheduler": scheduler.state_dict(), + }, + is_best, + args.checkpoint, + not_main_rank, + ) + + logging.info("End time: %s", datetime.now()) + + if args.distributed: + torch.distributed.destroy_process_group() + + +def spawn_main(main, args): + if args.distributed: + torch.multiprocessing.spawn( + main, args=(args,), nprocs=args.world_size, join=True + ) + else: + main(0, args) + + +if __name__ == "__main__": + + logging.basicConfig(level=logging.INFO) + args = parse_args() + spawn_main(main, args) diff --git a/examples/pipeline_wav2letter/transforms.py b/examples/pipeline_wav2letter/transforms.py new file mode 100644 index 00000000..f1d9115c --- /dev/null +++ b/examples/pipeline_wav2letter/transforms.py @@ -0,0 +1,11 @@ +import torch + + +class Normalize(torch.nn.Module): + def forward(self, tensor): + return (tensor - tensor.mean(-1, keepdim=True)) / tensor.std(-1, keepdim=True) + + +class UnsqueezeFirst(torch.nn.Module): + def forward(self, tensor): + return tensor.unsqueeze(0) diff --git a/examples/pipeline_wav2letter/utils.py b/examples/pipeline_wav2letter/utils.py new file mode 100644 index 00000000..7cd07a2a --- /dev/null +++ b/examples/pipeline_wav2letter/utils.py @@ -0,0 +1,55 @@ +import json +import logging +import os +import shutil +from collections import defaultdict + +import torch + + +class MetricLogger(defaultdict): + def __init__(self, name, print_freq=1, disable=False): + super().__init__(lambda: 0.0) + self.disable = disable + self.print_freq = print_freq + self._iter = 0 + self["name"] = name + + def __str__(self): + return json.dumps(self) + + def __call__(self): + self._iter = (self._iter + 1) % self.print_freq + if not self.disable and not self._iter: + print(self, flush=True) + + +def save_checkpoint(state, is_best, filename, disable): + """ + Save the model to a temporary file first, + then copy it to filename, in case the signal interrupts + the torch.save() process. + """ + + if disable: + return + + if filename == "": + return + + tempfile = filename + ".temp" + + # Remove tempfile in case interuption during the copying from tempfile to filename + if os.path.isfile(tempfile): + os.remove(tempfile) + + torch.save(state, tempfile) + if os.path.isfile(tempfile): + os.rename(tempfile, filename) + if is_best: + shutil.copyfile(filename, "model_best.pth.tar") + logging.warning("Checkpoint: saved") + + +def count_parameters(model): + return sum(p.numel() for p in model.parameters() if p.requires_grad) diff --git a/examples/pipeline_wavernn/README.md b/examples/pipeline_wavernn/README.md new file mode 100644 index 00000000..170f4f89 --- /dev/null +++ b/examples/pipeline_wavernn/README.md @@ -0,0 +1,47 @@ +This is an example vocoder pipeline using the WaveRNN model trained with LJSpeech. WaveRNN model is based on the implementation from [this repository](https://github.com/fatchord/WaveRNN). The original implementation was +introduced in "Efficient Neural Audio Synthesis". WaveRNN and LJSpeech are available in torchaudio. + +### Usage + +An example can be invoked as follows. +``` +python main.py \ + --batch-size 256 \ + --learning-rate 1e-4 \ + --n-freq 80 \ + --loss 'crossentropy' \ + --n-bits 8 \ +``` + +For inference, an example can be invoked as follows. +Please refer to the [documentation](https://pytorch.org/audio/master/models.html#id10) for +available checkpoints. +``` +python inference.py \ + --checkpoint-name wavernn_10k_epochs_8bits_ljspeech \ + --output-wav-path ./output.wav +``` + +This example would generate a file named `output.wav` in the current working directory. + +### Output + +The information reported at each iteration and epoch (e.g. loss) is printed to standard output in the form of one json per line. Here is an example python function to parse the output if redirected to a file. +```python +def read_json(filename): + """ + Convert the standard output saved to filename into a pandas dataframe for analysis. + """ + + import pandas + import json + + with open(filename, "r") as f: + data = f.read() + + # pandas doesn't read single quotes for json + data = data.replace("'", '"') + + data = [json.loads(l) for l in data.splitlines()] + return pandas.DataFrame(data) +``` diff --git a/examples/pipeline_wavernn/datasets.py b/examples/pipeline_wavernn/datasets.py new file mode 100644 index 00000000..ba836690 --- /dev/null +++ b/examples/pipeline_wavernn/datasets.py @@ -0,0 +1,121 @@ +import random + +import torch +from torch.utils.data.dataset import random_split +from torchaudio.datasets import LJSPEECH, LIBRITTS +from torchaudio.transforms import MuLawEncoding + +from processing import bits_to_normalized_waveform, normalized_waveform_to_bits + + +class MapMemoryCache(torch.utils.data.Dataset): + r"""Wrap a dataset so that, whenever a new item is returned, it is saved to memory. + """ + + def __init__(self, dataset): + self.dataset = dataset + self._cache = [None] * len(dataset) + + def __getitem__(self, n): + if self._cache[n] is not None: + return self._cache[n] + + item = self.dataset[n] + self._cache[n] = item + + return item + + def __len__(self): + return len(self.dataset) + + +class Processed(torch.utils.data.Dataset): + def __init__(self, dataset, transforms): + self.dataset = dataset + self.transforms = transforms + + def __getitem__(self, key): + item = self.dataset[key] + return self.process_datapoint(item) + + def __len__(self): + return len(self.dataset) + + def process_datapoint(self, item): + specgram = self.transforms(item[0]) + return item[0].squeeze(0), specgram + + +def split_process_dataset(args, transforms): + if args.dataset == 'ljspeech': + data = LJSPEECH(root=args.file_path, download=False) + + val_length = int(len(data) * args.val_ratio) + lengths = [len(data) - val_length, val_length] + train_dataset, val_dataset = random_split(data, lengths) + + elif args.dataset == 'libritts': + train_dataset = LIBRITTS(root=args.file_path, url='train-clean-100', download=False) + val_dataset = LIBRITTS(root=args.file_path, url='dev-clean', download=False) + + else: + raise ValueError(f"Expected dataset: `ljspeech` or `libritts`, but found {args.dataset}") + + train_dataset = Processed(train_dataset, transforms) + val_dataset = Processed(val_dataset, transforms) + + train_dataset = MapMemoryCache(train_dataset) + val_dataset = MapMemoryCache(val_dataset) + + return train_dataset, val_dataset + + +def collate_factory(args): + def raw_collate(batch): + + pad = (args.kernel_size - 1) // 2 + + # input waveform length + wave_length = args.hop_length * args.seq_len_factor + # input spectrogram length + spec_length = args.seq_len_factor + pad * 2 + + # max start postion in spectrogram + max_offsets = [x[1].shape[-1] - (spec_length + pad * 2) for x in batch] + + # random start postion in spectrogram + spec_offsets = [random.randint(0, offset) for offset in max_offsets] + # random start postion in waveform + wave_offsets = [(offset + pad) * args.hop_length for offset in spec_offsets] + + waveform_combine = [ + x[0][wave_offsets[i]: wave_offsets[i] + wave_length + 1] + for i, x in enumerate(batch) + ] + specgram = [ + x[1][:, spec_offsets[i]: spec_offsets[i] + spec_length] + for i, x in enumerate(batch) + ] + + specgram = torch.stack(specgram) + waveform_combine = torch.stack(waveform_combine) + + waveform = waveform_combine[:, :wave_length] + target = waveform_combine[:, 1:] + + # waveform: [-1, 1], target: [0, 2**bits-1] if loss = 'crossentropy' + if args.loss == "crossentropy": + + if args.mulaw: + mulaw_encode = MuLawEncoding(2 ** args.n_bits) + waveform = mulaw_encode(waveform) + target = mulaw_encode(target) + + waveform = bits_to_normalized_waveform(waveform, args.n_bits) + + else: + target = normalized_waveform_to_bits(target, args.n_bits) + + return waveform.unsqueeze(1), specgram.unsqueeze(1), target.unsqueeze(1) + + return raw_collate diff --git a/examples/pipeline_wavernn/inference.py b/examples/pipeline_wavernn/inference.py new file mode 100644 index 00000000..08b608a4 --- /dev/null +++ b/examples/pipeline_wavernn/inference.py @@ -0,0 +1,88 @@ +import argparse + +import torch +import torchaudio +from torchaudio.transforms import MelSpectrogram +from torchaudio.models import wavernn +from torchaudio.models.wavernn import _MODEL_CONFIG_AND_URLS +from torchaudio.datasets import LJSPEECH + +from wavernn_inference_wrapper import WaveRNNInferenceWrapper +from processing import NormalizeDB + + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument( + "--output-wav-path", default="./output.wav", type=str, metavar="PATH", + help="The path to output the reconstructed wav file.", + ) + parser.add_argument( + "--jit", default=False, action="store_true", + help="If used, the model and inference function is jitted." + ) + parser.add_argument( + "--no-batch-inference", default=False, action="store_true", + help="Don't use batch inference." + ) + parser.add_argument( + "--no-mulaw", default=False, action="store_true", + help="Don't use mulaw decoder to decoder the signal." + ) + parser.add_argument( + "--checkpoint-name", default="wavernn_10k_epochs_8bits_ljspeech", + choices=list(_MODEL_CONFIG_AND_URLS.keys()), + help="Select the WaveRNN checkpoint." + ) + parser.add_argument( + "--batch-timesteps", default=100, type=int, + help="The time steps for each batch. Only used when batch inference is used", + ) + parser.add_argument( + "--batch-overlap", default=5, type=int, + help="The overlapping time steps between batches. Only used when batch inference is used", + ) + args = parser.parse_args() + return args + + +def main(args): + device = "cuda" if torch.cuda.is_available() else "cpu" + waveform, sample_rate, _, _ = LJSPEECH("./", download=True)[0] + + mel_kwargs = { + 'sample_rate': sample_rate, + 'n_fft': 2048, + 'f_min': 40., + 'n_mels': 80, + 'win_length': 1100, + 'hop_length': 275, + 'mel_scale': 'slaney', + 'norm': 'slaney', + 'power': 1, + } + transforms = torch.nn.Sequential( + MelSpectrogram(**mel_kwargs), + NormalizeDB(min_level_db=-100, normalization=True), + ) + mel_specgram = transforms(waveform) + + wavernn_model = wavernn(args.checkpoint_name).eval().to(device) + wavernn_inference_model = WaveRNNInferenceWrapper(wavernn_model) + + if args.jit: + wavernn_inference_model = torch.jit.script(wavernn_inference_model) + + with torch.no_grad(): + output = wavernn_inference_model(mel_specgram.to(device), + mulaw=(not args.no_mulaw), + batched=(not args.no_batch_inference), + timesteps=args.batch_timesteps, + overlap=args.batch_overlap,) + + torchaudio.save(args.output_wav_path, output, sample_rate=sample_rate) + + +if __name__ == "__main__": + args = parse_args() + main(args) diff --git a/examples/pipeline_wavernn/losses.py b/examples/pipeline_wavernn/losses.py new file mode 100644 index 00000000..a4494b05 --- /dev/null +++ b/examples/pipeline_wavernn/losses.py @@ -0,0 +1,119 @@ +import math + +import torch +from torch import nn as nn +from torch.nn import functional as F + + +class LongCrossEntropyLoss(nn.Module): + r""" CrossEntropy loss + """ + + def __init__(self): + super(LongCrossEntropyLoss, self).__init__() + + def forward(self, output, target): + output = output.transpose(1, 2) + target = target.long() + + criterion = nn.CrossEntropyLoss() + return criterion(output, target) + + +class MoLLoss(nn.Module): + r""" Discretized mixture of logistic distributions loss + + Adapted from wavenet vocoder + (https://github.com/r9y9/wavenet_vocoder/blob/master/wavenet_vocoder/mixture.py) + Explanation of loss (https://github.com/Rayhane-mamah/Tacotron-2/issues/155) + + Args: + y_hat (Tensor): Predicted output (n_batch x n_time x n_channel) + y (Tensor): Target (n_batch x n_time x 1) + num_classes (int): Number of classes + log_scale_min (float): Log scale minimum value + reduce (bool): If True, the losses are averaged or summed for each minibatch + + Returns + Tensor: loss + """ + + def __init__(self, num_classes=65536, log_scale_min=None, reduce=True): + super(MoLLoss, self).__init__() + self.num_classes = num_classes + self.log_scale_min = log_scale_min + self.reduce = reduce + + def forward(self, y_hat, y): + y = y.unsqueeze(-1) + + if self.log_scale_min is None: + self.log_scale_min = math.log(1e-14) + + assert y_hat.dim() == 3 + assert y_hat.size(-1) % 3 == 0 + + nr_mix = y_hat.size(-1) // 3 + + # unpack parameters (n_batch, n_time, num_mixtures) x 3 + logit_probs = y_hat[:, :, :nr_mix] + means = y_hat[:, :, nr_mix: 2 * nr_mix] + log_scales = torch.clamp( + y_hat[:, :, 2 * nr_mix: 3 * nr_mix], min=self.log_scale_min + ) + + # (n_batch x n_time x 1) to (n_batch x n_time x num_mixtures) + y = y.expand_as(means) + + centered_y = y - means + inv_stdv = torch.exp(-log_scales) + plus_in = inv_stdv * (centered_y + 1.0 / (self.num_classes - 1)) + cdf_plus = torch.sigmoid(plus_in) + min_in = inv_stdv * (centered_y - 1.0 / (self.num_classes - 1)) + cdf_min = torch.sigmoid(min_in) + + # log probability for edge case of 0 (before scaling) + # equivalent: torch.log(F.sigmoid(plus_in)) + log_cdf_plus = plus_in - F.softplus(plus_in) + + # log probability for edge case of 255 (before scaling) + # equivalent: (1 - F.sigmoid(min_in)).log() + log_one_minus_cdf_min = -F.softplus(min_in) + + # probability for all other cases + cdf_delta = cdf_plus - cdf_min + + mid_in = inv_stdv * centered_y + # log probability in the center of the bin, to be used in extreme cases + log_pdf_mid = mid_in - log_scales - 2.0 * F.softplus(mid_in) + + inner_inner_cond = (cdf_delta > 1e-5).float() + + inner_inner_out = inner_inner_cond * torch.log( + torch.clamp(cdf_delta, min=1e-12) + ) + (1.0 - inner_inner_cond) * ( + log_pdf_mid - math.log((self.num_classes - 1) / 2) + ) + inner_cond = (y > 0.999).float() + inner_out = ( + inner_cond * log_one_minus_cdf_min + (1.0 - inner_cond) * inner_inner_out + ) + cond = (y < -0.999).float() + log_probs = cond * log_cdf_plus + (1.0 - cond) * inner_out + + log_probs = log_probs + F.log_softmax(logit_probs, -1) + + if self.reduce: + return -torch.mean(_log_sum_exp(log_probs)) + else: + return -_log_sum_exp(log_probs).unsqueeze(-1) + + +def _log_sum_exp(x): + r""" Numerically stable log_sum_exp implementation that prevents overflow + """ + + axis = len(x.size()) - 1 + m, _ = torch.max(x, dim=axis) + m2, _ = torch.max(x, dim=axis, keepdim=True) + return m + torch.log(torch.sum(torch.exp(x - m2), dim=axis)) diff --git a/examples/pipeline_wavernn/main.py b/examples/pipeline_wavernn/main.py new file mode 100644 index 00000000..3d561f09 --- /dev/null +++ b/examples/pipeline_wavernn/main.py @@ -0,0 +1,399 @@ +import argparse +import logging +import os +from collections import defaultdict +from datetime import datetime +from time import time +from typing import List + +import torch +import torchaudio +from torch.optim import Adam +from torch.utils.data import DataLoader +from torchaudio.datasets.utils import bg_iterator +from torchaudio.models.wavernn import WaveRNN + +from datasets import collate_factory, split_process_dataset +from losses import LongCrossEntropyLoss, MoLLoss +from processing import NormalizeDB +from utils import MetricLogger, count_parameters, save_checkpoint + + +def parse_args(): + parser = argparse.ArgumentParser() + + parser.add_argument( + "--workers", + default=4, + type=int, + metavar="N", + help="number of data loading workers", + ) + parser.add_argument( + "--checkpoint", + default="", + type=str, + metavar="PATH", + help="path to latest checkpoint", + ) + parser.add_argument( + "--epochs", + default=8000, + type=int, + metavar="N", + help="number of total epochs to run", + ) + parser.add_argument( + "--start-epoch", default=0, type=int, metavar="N", help="manual epoch number" + ) + parser.add_argument( + "--print-freq", + default=10, + type=int, + metavar="N", + help="print frequency in epochs", + ) + parser.add_argument( + "--dataset", + default="ljspeech", + choices=["ljspeech", "libritts"], + type=str, + help="select dataset to train with", + ) + parser.add_argument( + "--batch-size", default=256, type=int, metavar="N", help="mini-batch size" + ) + parser.add_argument( + "--learning-rate", default=1e-4, type=float, metavar="LR", help="learning rate", + ) + parser.add_argument("--clip-grad", metavar="NORM", type=float, default=4.0) + parser.add_argument( + "--mulaw", + default=True, + action="store_true", + help="if used, waveform is mulaw encoded", + ) + parser.add_argument( + "--jit", default=False, action="store_true", help="if used, model is jitted" + ) + parser.add_argument( + "--upsample-scales", + default=[5, 5, 11], + type=List[int], + help="the list of upsample scales", + ) + parser.add_argument( + "--n-bits", default=8, type=int, help="the bits of output waveform", + ) + parser.add_argument( + "--sample-rate", + default=22050, + type=int, + help="the rate of audio dimensions (samples per second)", + ) + parser.add_argument( + "--hop-length", + default=275, + type=int, + help="the number of samples between the starts of consecutive frames", + ) + parser.add_argument( + "--win-length", default=1100, type=int, help="the length of the STFT window", + ) + parser.add_argument( + "--f-min", default=40.0, type=float, help="the minimum frequency", + ) + parser.add_argument( + "--min-level-db", + default=-100, + type=float, + help="the minimum db value for spectrogam normalization", + ) + parser.add_argument( + "--n-res-block", default=10, type=int, help="the number of ResBlock in stack", + ) + parser.add_argument( + "--n-rnn", default=512, type=int, help="the dimension of RNN layer", + ) + parser.add_argument( + "--n-fc", default=512, type=int, help="the dimension of fully connected layer", + ) + parser.add_argument( + "--kernel-size", + default=5, + type=int, + help="the number of kernel size in the first Conv1d layer", + ) + parser.add_argument( + "--n-freq", default=80, type=int, help="the number of spectrogram bins to use", + ) + parser.add_argument( + "--n-hidden-melresnet", + default=128, + type=int, + help="the number of hidden dimensions of resblock in melresnet", + ) + parser.add_argument( + "--n-output-melresnet", default=128, type=int, help="the output dimension of melresnet", + ) + parser.add_argument( + "--n-fft", default=2048, type=int, help="the number of Fourier bins", + ) + parser.add_argument( + "--loss", + default="crossentropy", + choices=["crossentropy", "mol"], + type=str, + help="the type of loss", + ) + parser.add_argument( + "--seq-len-factor", + default=5, + type=int, + help="the length of each waveform to process per batch = hop_length * seq_len_factor", + ) + parser.add_argument( + "--val-ratio", + default=0.1, + type=float, + help="the ratio of waveforms for validation", + ) + parser.add_argument( + "--file-path", default="", type=str, help="the path of audio files", + ) + parser.add_argument( + "--normalization", default=True, action="store_true", help="if True, spectrogram is normalized", + ) + + args = parser.parse_args() + return args + + +def train_one_epoch(model, criterion, optimizer, data_loader, device, epoch): + + model.train() + + sums = defaultdict(lambda: 0.0) + start1 = time() + + metric = MetricLogger("train_iteration") + metric["epoch"] = epoch + + for waveform, specgram, target in bg_iterator(data_loader, maxsize=2): + + start2 = time() + + waveform = waveform.to(device) + specgram = specgram.to(device) + target = target.to(device) + + output = model(waveform, specgram) + output, target = output.squeeze(1), target.squeeze(1) + + loss = criterion(output, target) + loss_item = loss.item() + sums["loss"] += loss_item + metric["loss"] = loss_item + + optimizer.zero_grad() + loss.backward() + + if args.clip_grad > 0: + gradient = torch.nn.utils.clip_grad_norm_( + model.parameters(), args.clip_grad + ) + sums["gradient"] += gradient.item() + metric["gradient"] = gradient.item() + + optimizer.step() + + metric["iteration"] = sums["iteration"] + metric["time"] = time() - start2 + metric() + sums["iteration"] += 1 + + avg_loss = sums["loss"] / len(data_loader) + + metric = MetricLogger("train_epoch") + metric["epoch"] = epoch + metric["loss"] = sums["loss"] / len(data_loader) + metric["gradient"] = avg_loss + metric["time"] = time() - start1 + metric() + + +def validate(model, criterion, data_loader, device, epoch): + + with torch.no_grad(): + + model.eval() + sums = defaultdict(lambda: 0.0) + start = time() + + for waveform, specgram, target in bg_iterator(data_loader, maxsize=2): + + waveform = waveform.to(device) + specgram = specgram.to(device) + target = target.to(device) + + output = model(waveform, specgram) + output, target = output.squeeze(1), target.squeeze(1) + + loss = criterion(output, target) + sums["loss"] += loss.item() + + avg_loss = sums["loss"] / len(data_loader) + + metric = MetricLogger("validation") + metric["epoch"] = epoch + metric["loss"] = avg_loss + metric["time"] = time() - start + metric() + + return avg_loss + + +def main(args): + + devices = ["cuda" if torch.cuda.is_available() else "cpu"] + + logging.info("Start time: {}".format(str(datetime.now()))) + + melkwargs = { + "n_fft": args.n_fft, + "power": 1, + "hop_length": args.hop_length, + "win_length": args.win_length, + } + + transforms = torch.nn.Sequential( + torchaudio.transforms.MelSpectrogram( + sample_rate=args.sample_rate, + n_mels=args.n_freq, + f_min=args.f_min, + mel_scale='slaney', + norm='slaney', + **melkwargs, + ), + NormalizeDB(min_level_db=args.min_level_db, normalization=args.normalization), + ) + + train_dataset, val_dataset = split_process_dataset(args, transforms) + + loader_training_params = { + "num_workers": args.workers, + "pin_memory": False, + "shuffle": True, + "drop_last": False, + } + loader_validation_params = loader_training_params.copy() + loader_validation_params["shuffle"] = False + + collate_fn = collate_factory(args) + + train_loader = DataLoader( + train_dataset, + batch_size=args.batch_size, + collate_fn=collate_fn, + **loader_training_params, + ) + val_loader = DataLoader( + val_dataset, + batch_size=args.batch_size, + collate_fn=collate_fn, + **loader_validation_params, + ) + + n_classes = 2 ** args.n_bits if args.loss == "crossentropy" else 30 + + model = WaveRNN( + upsample_scales=args.upsample_scales, + n_classes=n_classes, + hop_length=args.hop_length, + n_res_block=args.n_res_block, + n_rnn=args.n_rnn, + n_fc=args.n_fc, + kernel_size=args.kernel_size, + n_freq=args.n_freq, + n_hidden=args.n_hidden_melresnet, + n_output=args.n_output_melresnet, + ) + + if args.jit: + model = torch.jit.script(model) + + model = torch.nn.DataParallel(model) + model = model.to(devices[0], non_blocking=True) + + n = count_parameters(model) + logging.info(f"Number of parameters: {n}") + + # Optimizer + optimizer_params = { + "lr": args.learning_rate, + } + + optimizer = Adam(model.parameters(), **optimizer_params) + + criterion = LongCrossEntropyLoss() if args.loss == "crossentropy" else MoLLoss() + + best_loss = 10.0 + + if args.checkpoint and os.path.isfile(args.checkpoint): + logging.info(f"Checkpoint: loading '{args.checkpoint}'") + checkpoint = torch.load(args.checkpoint) + + args.start_epoch = checkpoint["epoch"] + best_loss = checkpoint["best_loss"] + + model.load_state_dict(checkpoint["state_dict"]) + optimizer.load_state_dict(checkpoint["optimizer"]) + + logging.info( + f"Checkpoint: loaded '{args.checkpoint}' at epoch {checkpoint['epoch']}" + ) + else: + logging.info("Checkpoint: not found") + + save_checkpoint( + { + "epoch": args.start_epoch, + "state_dict": model.state_dict(), + "best_loss": best_loss, + "optimizer": optimizer.state_dict(), + }, + False, + args.checkpoint, + ) + + for epoch in range(args.start_epoch, args.epochs): + + train_one_epoch( + model, criterion, optimizer, train_loader, devices[0], epoch, + ) + + if not (epoch + 1) % args.print_freq or epoch == args.epochs - 1: + + sum_loss = validate(model, criterion, val_loader, devices[0], epoch) + + is_best = sum_loss < best_loss + best_loss = min(sum_loss, best_loss) + save_checkpoint( + { + "epoch": epoch + 1, + "state_dict": model.state_dict(), + "best_loss": best_loss, + "optimizer": optimizer.state_dict(), + }, + is_best, + args.checkpoint, + ) + + logging.info(f"End time: {datetime.now()}") + + +if __name__ == "__main__": + + logging.basicConfig(level=logging.INFO) + args = parse_args() + main(args) diff --git a/examples/pipeline_wavernn/processing.py b/examples/pipeline_wavernn/processing.py new file mode 100644 index 00000000..db2b1ee9 --- /dev/null +++ b/examples/pipeline_wavernn/processing.py @@ -0,0 +1,36 @@ +import torch +import torch.nn as nn + + +class NormalizeDB(nn.Module): + r"""Normalize the spectrogram with a minimum db value + """ + + def __init__(self, min_level_db, normalization): + super().__init__() + self.min_level_db = min_level_db + self.normalization = normalization + + def forward(self, specgram): + specgram = torch.log10(torch.clamp(specgram.squeeze(0), min=1e-5)) + if self.normalization: + return torch.clamp( + (self.min_level_db - 20 * specgram) / self.min_level_db, min=0, max=1 + ) + return specgram + + +def normalized_waveform_to_bits(waveform: torch.Tensor, bits: int) -> torch.Tensor: + r"""Transform waveform [-1, 1] to label [0, 2 ** bits - 1] + """ + + assert abs(waveform).max() <= 1.0 + waveform = (waveform + 1.0) * (2 ** bits - 1) / 2 + return torch.clamp(waveform, 0, 2 ** bits - 1).int() + + +def bits_to_normalized_waveform(label: torch.Tensor, bits: int) -> torch.Tensor: + r"""Transform label [0, 2 ** bits - 1] to waveform [-1, 1] + """ + + return 2 * label / (2 ** bits - 1.0) - 1.0 diff --git a/examples/pipeline_wavernn/utils.py b/examples/pipeline_wavernn/utils.py new file mode 100644 index 00000000..e924c9f5 --- /dev/null +++ b/examples/pipeline_wavernn/utils.py @@ -0,0 +1,61 @@ +import logging +import os +import shutil +from collections import defaultdict, deque + +import torch + + +class MetricLogger: + r"""Logger for model metrics + """ + + def __init__(self, group, print_freq=1): + self.print_freq = print_freq + self._iter = 0 + self.data = defaultdict(lambda: deque(maxlen=self.print_freq)) + self.data["group"].append(group) + + def __setitem__(self, key, value): + self.data[key].append(value) + + def _get_last(self): + return {k: v[-1] for k, v in self.data.items()} + + def __str__(self): + return str(self._get_last()) + + def __call__(self): + self._iter = (self._iter + 1) % self.print_freq + if not self._iter: + print(self, flush=True) + + +def save_checkpoint(state, is_best, filename): + r"""Save the model to a temporary file first, + then copy it to filename, in case the signal interrupts + the torch.save() process. + """ + + if filename == "": + return + + tempfile = filename + ".temp" + + # Remove tempfile in case interuption during the copying from tempfile to filename + if os.path.isfile(tempfile): + os.remove(tempfile) + + torch.save(state, tempfile) + if os.path.isfile(tempfile): + os.rename(tempfile, filename) + if is_best: + shutil.copyfile(filename, "model_best.pth.tar") + logging.info("Checkpoint: saved") + + +def count_parameters(model): + r"""Count the total number of parameters in the model + """ + + return sum(p.numel() for p in model.parameters() if p.requires_grad) diff --git a/examples/pipeline_wavernn/wavernn_inference_wrapper.py b/examples/pipeline_wavernn/wavernn_inference_wrapper.py new file mode 100644 index 00000000..5d5c4db7 --- /dev/null +++ b/examples/pipeline_wavernn/wavernn_inference_wrapper.py @@ -0,0 +1,181 @@ +# ***************************************************************************** +# Copyright (c) 2019 fatchord (https://github.com/fatchord) +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# ***************************************************************************** + + +from torchaudio.models.wavernn import WaveRNN +import torch +import torchaudio +from torch import Tensor + +from processing import normalized_waveform_to_bits + + +def _fold_with_overlap(x: Tensor, timesteps: int, overlap: int) -> Tensor: + r'''Fold the tensor with overlap for quick batched inference. + Overlap will be used for crossfading in xfade_and_unfold(). + + x = [[h1, h2, ... hn]] + Where each h is a vector of conditioning channels + Eg: timesteps=2, overlap=1 with x.size(1)=10 + folded = [[h1, h2, h3, h4], + [h4, h5, h6, h7], + [h7, h8, h9, h10]] + + Args: + x (tensor): Upsampled conditioning channels of size (1, timesteps, channel). + timesteps (int): Timesteps for each index of batch. + overlap (int): Timesteps for both xfade and rnn warmup. + + Return: + folded (tensor): folded tensor of size (n_folds, timesteps + 2 * overlap, channel). + ''' + + _, channels, total_len = x.size() + + # Calculate variables needed + n_folds = (total_len - overlap) // (timesteps + overlap) + extended_len = n_folds * (overlap + timesteps) + overlap + remaining = total_len - extended_len + + # Pad if some time steps poking out + if remaining != 0: + n_folds += 1 + padding = timesteps + 2 * overlap - remaining + x = torch.nn.functional.pad(x, (0, padding)) + + folded = torch.zeros((n_folds, channels, timesteps + 2 * overlap), device=x.device) + + # Get the values for the folded tensor + for i in range(n_folds): + start = i * (timesteps + overlap) + end = start + timesteps + 2 * overlap + folded[i] = x[0, :, start:end] + + return folded + + +def _xfade_and_unfold(y: Tensor, overlap: int) -> Tensor: + r'''Applies a crossfade and unfolds into a 1d array. + + y = [[seq1], + [seq2], + [seq3]] + Apply a gain envelope at both ends of the sequences + y = [[seq1_in, seq1_timesteps, seq1_out], + [seq2_in, seq2_timesteps, seq2_out], + [seq3_in, seq3_timesteps, seq3_out]] + Stagger and add up the groups of samples: + [seq1_in, seq1_timesteps, (seq1_out + seq2_in), seq2_timesteps, ...] + + Args: + y (Tensor): Batched sequences of audio samples of size + (num_folds, channels, timesteps + 2 * overlap). + overlap (int): Timesteps for both xfade and rnn warmup. + + Returns: + unfolded waveform (Tensor) : waveform in a 1d tensor of size (channels, total_len). + ''' + + num_folds, channels, length = y.shape + timesteps = length - 2 * overlap + total_len = num_folds * (timesteps + overlap) + overlap + + # Need some silence for the rnn warmup + silence_len = overlap // 2 + fade_len = overlap - silence_len + silence = torch.zeros((silence_len), dtype=y.dtype, device=y.device) + linear = torch.ones((silence_len), dtype=y.dtype, device=y.device) + + # Equal power crossfade + t = torch.linspace(-1, 1, fade_len, dtype=y.dtype, device=y.device) + fade_in = torch.sqrt(0.5 * (1 + t)) + fade_out = torch.sqrt(0.5 * (1 - t)) + + # Concat the silence to the fades + fade_in = torch.cat([silence, fade_in]) + fade_out = torch.cat([linear, fade_out]) + + # Apply the gain to the overlap samples + y[:, :, :overlap] *= fade_in + y[:, :, -overlap:] *= fade_out + + unfolded = torch.zeros((channels, total_len), dtype=y.dtype, device=y.device) + + # Loop to add up all the samples + for i in range(num_folds): + start = i * (timesteps + overlap) + end = start + timesteps + 2 * overlap + unfolded[:, start:end] += y[i] + + return unfolded + + +class WaveRNNInferenceWrapper(torch.nn.Module): + + def __init__(self, wavernn: WaveRNN): + super().__init__() + self.wavernn_model = wavernn + + def forward(self, + specgram: Tensor, + mulaw: bool = True, + batched: bool = True, + timesteps: int = 100, + overlap: int = 5) -> Tensor: + r"""Inference function for WaveRNN. + + Based on the implementation from + https://github.com/fatchord/WaveRNN/blob/master/models/fatchord_version.py. + + + Currently only supports multinomial sampling. + + Args: + specgram (Tensor): spectrogram of size (n_mels, n_time) + mulaw (bool, optional): Whether to perform mulaw decoding (Default: ``True``). + batched (bool, optional): Whether to perform batch prediction. Using batch prediction + will significantly increase the inference speed (Default: ``True``). + timesteps (int, optional): The time steps for each batch. Only used when `batched` + is set to True (Default: ``100``). + overlap (int, optional): The overlapping time steps between batches. Only used when + `batched` is set to True (Default: ``5``). + + Returns: + waveform (Tensor): Reconstructed waveform of size (1, n_time, ). + 1 represents single channel. + """ + specgram = specgram.unsqueeze(0) + if batched: + specgram = _fold_with_overlap(specgram, timesteps, overlap) + + output = self.wavernn_model.infer(specgram).cpu() + + if mulaw: + output = normalized_waveform_to_bits(output, self.wavernn_model.n_bits) + output = torchaudio.functional.mu_law_decoding(output, self.wavernn_model.n_classes) + + if batched: + output = _xfade_and_unfold(output, overlap) + else: + output = output[0] + + return output diff --git a/examples/source_separation/README.md b/examples/source_separation/README.md new file mode 100644 index 00000000..4f2487a5 --- /dev/null +++ b/examples/source_separation/README.md @@ -0,0 +1,76 @@ +# Source Separation Example + +This directory contains reference implementations for source separations. For the detail of each model, please checkout the followings. + +- [Conv-TasNet](./conv_tasnet/README.md) + +## Usage + +### Overview + +To training a model, you can use [`lightning_train.py`](./lightning_train.py). This script takes the form of +`lightning_train.py [parameters]` + + ``` + python lightning_train.py \ + [--data-dir DATA_DIR] \ + [--num-gpu NUM_GPU] \ + [--num-workers NUM_WORKERS] \ + ... + + # For the detail of the parameter values, use; + python lightning_train.py --help + ``` + +This script runs training in PyTorch-Lightning framework with Distributed Data Parallel (DDP) backend. +### SLURM + +